This is an old revision of the document!
−Table of Contents
MapServer
Pacchetti installati su Debian Sarge:
- cgi-mapserver
- mapserver-bin
- mapserver-doc
- perl-mapscript
- python-mapscript
- php4-mapscript (da pkg-grass.alioth.debian.org, non c'è in testing.)
Una introduzione: MapServer 5.x Tutorial.
CGI MapServer
Lo script mapserv
è l'eseguibile CGI del pacchetto MapServer, accetta parametri GET e POST e viene usato come motore per generare singole immagini o complesse pagine web interattive. Si può fare un test per vedere se MapServer funziona in questo modo: preparare un MapFile di nome /var/www/hello-world.map
con il seguente contenuto:
MAP NAME HELLO STATUS ON EXTENT 0 0 4000 3000 SIZE 400 300 IMAGECOLOR 255 200 255 WEB IMAGEPATH "/tmp/" IMAGEURL "/tmp/" END LAYER NAME "credits" STATUS DEFAULT TRANSFORM FALSE TYPE ANNOTATION FEATURE POINTS 200 150 END TEXT 'Hello World!' END CLASS LABEL TYPE BITMAP COLOR 0 0 0 END END END END
Con il browser si richiede la pagina http://host/cgi-bin/mapserv?map=/var/www/hello-world.map&mode=map
. Lo script CGI genera al volo l'immagine, in questo caso un box 400 x 300 pixel con un'etichetta di testo al centro.
In questo caso lo script CGI ha ricevuto come parametri GET il nome del file .map
che definisce l'immagine da generare e il parametro mode=map
che indica di restituire semplicemente l'oggetto bitmap, senza la pagina interattiva predefinita (mode=browse
).
MapFile
Un MapFile è indispensabile per generare una mappa: definisce gli oggetti che verranno visualizzati e altri dettagli della mappa stessa (dimensioni, colori, ecc.). Gli oggetti sono organizzati in strati (layer) che vengono sovrapposti. La sintassi di un MapFile è spiegata nella MapFile Reference. Qui abbiamo l'indice della documentazione.
Anche il sito umn.mapserver.ch ha una buona guida sui mapfile.
MapFile con parametri
È possibile passare a run-time alcuni parametri al mapfile, tramite i parametri della richiesta inviata al CGI-BIN. Vedere la pagina Run-time Substitution della documentazione.
Ad esempio è possibile definire un MAP.LAYER.FILTER
di questo tipo:
FILTER ("multimedia = '%multimedia%' and seats >= %nseats% and sound = '%sound%')
Nella query CGI sarà sufficiente aggiungere i parametri multimedia=yes&seats=100&sound=yes. Non tutti i parametri del mapfile accetta la sostituzione di variabile, ecco l'elenco:
* LAYER.DATA
* LAYER.TILEINDEX
* LAYER.CONNECTION
* LAYER.FILTER
* CLASS.EXPRESSION
Se il prametro viene usato per comporre una query SQL (come nell'esempio precedente) questa possibilità espone al rischio di SQL injection. Per evitare questo rischio si definiscono i pattern di validazione per ciascuna variabile:
<code xml>
METADATA
'multimedia_validation_pattern' '^yes|no$'
'sound_validation_pattern' '^yes|no$'
'nseats_validation_pattern' '^[0-9]{1,3}$'
END
</code>
===== MapFile collegato a PostGIS =====
In questo caso gli oggetti da visualizzare nella mappa sono contenuti in un database PostGIS. Abbiamo la tabella
tracksegmentswaypoints
, nella colonna wpt
ci sono dei oggetti di tipo POINT
memorizzati in coordinate latitudine/longitudine. Per ogni punto viene visualizzato un simbolo (cerchio) e un'etichetta presa dal campo descr
del record. Viene anche specificata una clausola WHERE idtype = 3
per limitare il numero di punti visualizzati. L'estensione della mappa EXTENT
è espressa in gradi di latitudine e longitudine. Si deve fornire nella directory font
il file con il font .ttf e la mappa nomi-font…
Vedere anche questa mail molto esplicativa.
<file>
MAP
EXTENT 10 43 12 44
IMAGETYPE “png”
SIZE 640 480
IMAGECOLOR 200 255 200
FONTSET “fonts/fonts.txt”
OUTPUTFORMAT
NAME “png”
DRIVER “GD/PNG”
MIMETYPE “image/png”
IMAGEMODE RGB
EXTENSION “png”
FORMATOPTION “interlace=on”
END
SYMBOL
NAME “circle”
TYPE ellipse
FILLED true
POINTS
5 5
END
END
LAYER
NAME 'waypoints'
TYPE point
STATUS default
CONNECTIONTYPE postgis
CONNECTION “user=strade password= dbname=strade host=127.0.0.1”
DATA “wpt from waypoints”
FILTER “idtype = 3”
LABELITEM “descr”
CLASS
STYLE
SYMBOL “circle”
COLOR 255 255 255
OUTLINECOLOR 240 0 0
END
LABEL
POSITION auto
SIZE 8
COLOR 0 0 0
TYPE truetype
FONT arial
END
END
END
END
</file>
Se si vuole aggiungere un layer vettoriale (linee), supponendo di avere la tabella che contiene il campo
trkseg di tipo
LINESTRING, si può aggiungere una sezione di questo tipo:
<file>
LAYER
NAME 'tracksegments'
TYPE line
STATUS default
CONNECTIONTYPE postgis
CONNECTION “user=strade password= dbname=strade host=127.0.0.1”
DATA “trkseg from tracksegments”
CLASS
STYLE
COLOR 0 0 0
END
END
END
</file>
Infine proviamo ad aggiungere un layer vettoriale contenente poligoni, supponendo di avere la tabella
DATAcountries
che contiene il campo poly
di tipo POLYGON
. Notare che i layer vengono sovrapposti uno dopo l'altro, quindi in generale il layer dei poligoni deve essere dichiarato prima del layer che contiene i punti e le linee. Quindi si può aggiungere prima degli altri layer una sezione di questo tipo:
<file>
LAYER
NAME 'polygon'
TYPE polygon
STATUS default
CONNECTIONTYPE postgis
CONNECTION “user=strade password= dbname=strade host=127.0.0.1”
DATA “poly from countries”
FILTER “idcountry = 39”
CLASS
STYLE
COLOR 255 255 255
OUTLINECOLOR 35 137 232
END
END
END
</file>
==== Calusola DATA del mapfile ====
La sintassi della clausola può essere più evoluta di quelle viste sopra, ad esempio può contenere una
SELECT oppure può specificare l'indice univoco o lo SRID (sistema di riferimento) da utilizzare. Fare attenzione alle maiuscole e minuscole!
<code>
DATA “the_geom from (SELECT * FROM …) as foo using unique field_id using SRID=4326”
</code>
Specificare lo
oidusing unique
è indispensabile se la query o view sottostante non ha il campo . Tale campo esisteva implicitamente nelle tabelle (ma non nelle view) con versioni di PostgreSQL precedenti alla 8.1. Il costruttore di query del MapServer ha bisogno di un indice univoco e se non specificato espressamente utilizza il campo
oid.
Specificare la clausola
find_srid()using SRID
evita a MapServer l'onere di una chiamata alla funzione per determinare automaticamente il sistema di riferimento della geometria.
===== Mappa interattiva con un MapServer Template =====
Se vogliamo una mappa interattiva (cliccabile) invece di una semplice immagine statica si utilizza la modalità
mode=browse del cgi-bin (invece del
mode=map). In tal caso nel file .map è necessario specificare il percorso di un file .html (il template) che funzionerà da contesto dentro il quale l'immagine mappa viene mostrata:
<code>
TEMPLATE '/var/www/default/maps/geocaches.html'
</code>
La richiesta al server web sarà quindi qualcosa del tipo: http://host/cgi-bin/mapserv?map=/var/www/maps/mappa.map&mode=browse.
Il file .html deve avere un'opportuna struttura e deve contenere degli speciali segnaposto che MapServer interpreta e sostituisce con gli attuali valori. Ad esempio se il codice html contiene le seguenti righe:
<code html>
<input type=“hidden” name=“map” value=“[map]”>
<input type=“hidden” name=“imgext” value=“[mapext]”>
<input type=“hidden” name=“scale” value=“[scale]”>
<input type=“hidden” name=“imgxy” value=“[center]”>
</code>
quando viene fatta la richiesta al server web, il contenuto generato al volo sarà qualcosa del tipo:
<code html>
<input type=“hidden” name=“map” value=“/var/www/maps/geocaches.map”>
<input type=“hidden” name=“imgext” value=“1777697.577948 4101470.140269 3222674.575580 5185202.888493”>
<input type=“hidden” name=“scale” value=“6400000.000628”>
<input type=“hidden” name=“imgxy” value=“320.0 240.0”>
</code>
La pagina html può contenere dei campi che influenzano il comportamento dell'immagine in seguito al click (pan, zoom-in, zoom-out). Per maggiori dettagli ed esempi su come si cotruisce un template .html per MapServer, vedere la guida di riferimento ai template.
===== PHP-MapScript =====
Si tratta di una estensione del linguaggio PHP che trasforma le funzionalità di MapServer in oggetti del linguaggio. In pratica - invece di generare le mappe a partire da file .map, che sono inevitabilmente poco flessibili - si definiscono degli oggetti nel linguaggio PHP, si assegnano le opportune proprietà ad essi (grosso modo equivalenti alle direttive che si impostano nei file .map) e alla fine si genera l'immagine corrispondente. In questo modo la generazione della mappa è interamente controllata dall'applicativo web che la richiede.
L'unione di questo linguaggio e della tecnologia AJAX consente di creare applicativi web estremamente interattivi ed efficaci. Esempi significativi sono p.mapper, ka-Map, OpenLayers.
La configurazione di un server con Apache + MapScript + pMapper non è semplicissima, soprattutto se si vuole stare attenti alla sicurezza. Veder in proposito la pagina MapScript Filesystem Hierarchy and Permissions.
Qui di seguito un estratto che genera una mappa minimale e la salva in un file su disco. Il file può essere eventualmente servito al client tramite readfile().
<code php>
Create the new MapServer MAP object.
$map = ms_newMapObj(
);
$map→outputformat→set('driver', 'GD/PNG');
$map→outputformat→set('extension', 'png');
$map→outputformat→set('transparent', MS_ON);
$map→set('width', $mapfile_image_width);
$map→set('height', $mapfile_image_height);
$map→web→set('imagepath', $ms_imagepath);
$map→web→set('imageurl', $ms_imageurl);
$map→setextent($xmin - $margin, $ymin - $margin, $xmax + $margin, $ymax + $margin);
Add the only one layer.
$layer = ms_newLayerObj($map);
$layer→set('name', 'bnd');
$layer→set('type', MS_LAYER_POLYGON);
$layer→set('status', MS_DEFAULT);
$layer→set('connectiontype', MS_POSTGIS);
$layer→set('connection', $DB_CONNECT);
$layer→set('data', 'bnd FROM vmap0_polbnda using unique id and using SRID=4326');
Add some colors.
$class = ms_newClassObj($layer);
$class→setexpression(“([id] < 100)”);
$class→setexpression(“([tile_id] = 11)”);
$class→setexpression(“('[nam]' eq 'TOSCANA')”);
$style = ms_newStyleObj($class);
For a MS_LAYER_LINE, this is the color of the line.
For a MS_LAYER_POLYGON, this is the color of the filling.
$style→color→setRGB(0, 255, 255);
For a MS_LAYER_POLYGON, this is the color of the outline.
$style→outlinecolor→setRGB(255, 0, 0);
Not used by MS_LAYER_LINE and MS_LAYER_POLYGON?
$style→backgroundcolor→setRGB(255, 0, 0);
Save the image into the MapServer IMAGEPATH.
$image = $map→draw();
$image_url = $image→saveWebImage('MS_PNG',1,1,0);
Save the image in other directory (useful for permanent storage).
$image→saveImage($image_filename, $map);
</code>
Alcune note su setexpression(): serve a determinare quando una classe (e soprattutto gli stili associati) vengono usati per gli oggetti di un determinato layer. La sintassi per l'espressione è alquanto astrusa: i nomi degli attributi dell'oggetto geografico (dallo shapefile o dal database come in questo esempio) vanno tra parentesi quadrate, però se si tratta di espressioni stringa vanno racchiuse tra apici. Il tutto va racchiuso tra parentesi tonde. La sintassi completa è documentata nel manuale dell'oggetto class.
Purtroppo non esiste un metodo per assegnare colori diversi in base ad un attributo dell'oggetto, si devono definire tante classi per quanti sono i colori da utilizzare e definire altrettante
setexpression() che selezionino gli oggetti a cui assegnare il colore.
==== MapScript su GRASS/PostGIS via OGR ====
MapScript può attingere ai dati GRASS tramite le librerie OGR. GRASS può mantenere gli attributi di un vettoriale dentro un database PostGIS. Normalmente le credenziali usate da GRASS per accedere al database sono memorizzati nel file
HOME$HOME/.grasslogin6
.
Per fare in modo che questo meccanismo di autenticazione venga usato anche da MapScript (cioè dal PHP), devono essere soddisfatte le seguenti condizioni:
* Il file .grasslogin6
deve essere leggibile dall'utente del server web (es. www-data in Debian).
* La variabile d'ambiente HOME
deve puntare alla directory che contiene il file.
Normalmente la variabile non è impostata durante l'esecuzione del PHP, la si può impostare con la funzione
.grasslogin6putenv()
. Nell'esempio seguente viene eseguito il programma ogrinfo su un file che a sua volta richiama GRASS e PostGIS, di fatto utilizzando le credenziali in :
<code php>
putenv(“HOME=/var/www”);
system(“ogrinfo -ro /home/niccolo/grass/Toscana/PERMANENT/vector/t_fiumi_principali/head”);
</code>
ATTENZIONE: impostare le variabili d'ambiente in PHP potrebbe essere vietato se è attivo il PHP safe mode.
Una modo per impostare la variabile d'ambiente per tutta un'applicazione PHP senza dover modificare tutti i sorgenti potrebbe essere questa:
creare un file
log_statement = true.htaccess
con questo contenuto:
<file>
php_value auto_prepend_file env.php
</file>
creare poi il file env.php
con questo contenuto:
<code php>
<? putenv('HOME=/var/www') ?>
</code>
===== Proiezioni nei file .map =====
I layer presenti in un file .map possono essere memorizzati in datum diversi, per sovrapporli correttamente è quindi opportuno specificare (nel mapfile) il sistema di coordinate usato da ciascun layer. Omettendo queste indicazioni si possono produrre errori grossolani se ad esempio si uniscono dati la cui latitudine/longitudine è espressa in gradi (ad esempio usando il sistema WGS84 comune agli apparecchi GPS) con dati espressi in metri (ad esempio se si utilizza una delle proiezioni UTM). Oppure gli errori possono essere minimi con discostamenti di pochi metri.
La mappa che viene generata ha anch'essa la sua proiezione, che in generale può essere diversa da quelle utilizzate dai layer che la compongono.
I parametri che interessano in queste circostanze sono i seguenti:
<file>
MAP
# Proiezione utilizzata per disegnare la mappa.
# Qui usiamo la proiezione Monte Mario / Italy zone 2
PROJECTION
“init=epsg:3004”
END
# In questa proiezione l'unità di misura sono i metri,
# parametro indispensabile per ottenere una SCALEBAR corretta.
UNITS meters
# Per determinare esattamente la zona compresa nella mappa.
# Ci si deve esprimere nella corretta unità di misura (metri in
# questo caso) e rispetto al sistema di riferimento scelto.
EXTENT 1777697 4101470 3222674 5185202
…
LAYER
NAME 'Confini regionali'
TYPE polygon
…
# Questi dati sono nel datum NAD83.
PROJECTION
“init=epsg:4269”
END
…
</file>
===== Debug query su PostGIS =====
Una mappa basata su una view di Postgres risultava particolarmente lenta ad essere eseguita. E' risultato che MapServer utilizza un cursore per effettuare la query. Attivando l'opzione in
/etc/postgresql/postgresql.conf'', si è visto che la query era qualcosa del tipo:
<code sql>
BEGIN;
DECLARE mycursor BINARY CURSOR FOR
SELECT
toponimo,
wpt
FROM wpt_comuni_view
WHERE (
wpt &&
setSRID('BOX3D(4.83 36, 20.16 47.5)'::BOX3D, 4326)
);
FETCH ALL IN mycursor;
</code>
Utilizzando la semplice SELECT il risultato era quasi istantaneo, ma con il CURSOR invece c'era un'attesa di oltre un minuto (su circa 8000 record). E' risultato che il query planner di Postgres, nel caso del cursore, utilizzava un algoritmo teso a restituire in poco tempo solo una piccola percentuale dei record (questo è in genere lo scopo dei cursori), mentre il FETCH ALL risultava assolutamente penalizzato.
Non potendo intervenire sul costruttore di query del MapServer si è risolto aggiungendo una clausola ORDER BY alla VIEW sottostante. In questo modo il query planner viene indotto ad utlizzare un algoritmo più adatto allo scopo. Vedere in proposito questa mail.
tecnica/gps_cartografia_gis/mapserver.1304494307.txt.gz · Last modified: 2011/05/04 09:31 by niccolo