Scribe – Une nouvelle façon de faire du mapfile

Créer une belle carte qui véhicule efficacement son message n'est pas chose facile. On dispose souvent d'une grande quantité de données qu'on ne souhaite pas afficher en tout temps ou qu'on souhaite afficher mais avec un style qui varie en fonction du niveau d'échelle. Lorsqu'on défini le style d'une carte, chaque petit détail est important. Le processus se fait généralement de facon itérative et implique beaucoup de modifications, à tous les niveaux d'échelle.

Dans Mapserver, la gestion de l'échelle se fait avec les tags MINSCALEDENOM et MAXSCALEDENOM au niveau du LAYER ou de la CLASS. Cela veut dire qu'aussitôt qu'un élément du style change pour un LAYER ou une CLASS donné, à un certain niveau d'échelle, un nouveau LAYER ou une nouvelle CLASS doit être créé. On se trouve ainsi a réécrire beaucoup de texte pour ne modifier peut-être qu'un seul paramètre. La tâche devient encore plus fastidieuse si on doit changer un paramètre (par exemple la couleur d'une route) définie dans 16 LAYERs différents.

"Scribe" est un outil en python que nous avons créé pour faciliter l'écriture d'un "mapfile" en utilisant des variables et des raccourcis pour la gestion des échelles via des niveaux (maintenant la norme).  Cette façon de faire s'apparente à "Basemaps" mais est plus simple d'utilisation et généralement moins verbeuse.

Gestion des niveaux d'échelle

LAYER {
    1-16 {
        NAME: 'land'
        TYPE: POLYGON
        @layerconfig
        DATA {
            1-4: '110m_physical/ne_110m_land'
            5-10: '50m_physical/ne_50m_land'
            11-16: '10m_physical/ne_10m_land'
        }
        CLASS {
            STYLE {
                COLOR {
                    1-6: '#EEECDF'
                    7-16: '#AAA89B'
                }
                OUTLINECOLOR: 200 200 200
                OUTLINEWIDTH: @land_ol_width
            }
         }
     }
 }

Dans l'exemple précédent, un LAYER appelé "land" est créé. Le tag "1-16" signifie que ce LAYER doit etre affiché des niveaux d'échelle 1 à 16. Le script traduit automatiquement ces niveaux en MINSCALEDENOM et MAXSCALEDENOM. De plus, les données utilisées (DATA) des niveaux 1 à 4 sont au 110m tandis qu'elles sont au 50m des niveaux 5 à 10 et au 10m des niveaux 11 à 16. La couleur (COLOR) elle aussi change en fonction de l'échelle. En utilisant cette nomenclature, il devient très facile de modifier un paramètre pour un ou plusieurs niveaux d'échelle sans avoir à réécrire beaucoup de texte ou à faire des modifications à plusieurs endroits.

Définition et utilisation de variables

"Scribe" ne permet pas seulement de gérer les échelles mais il permet aussi de définir des variables réutilisables. Dans l'exemple ci-haut, les variables "layerconfig" et "land_ol_width" sont appelées a l'aide d'un "@". Ces variables sont définies de la facon suivante:

VARIABLES {
    layerconfig {
        GROUP: 'default'
        STATUS: ON
        PROJECTION {{
            'init=epsg:4326'
        }}
        PROCESSING: 'LABEL_NO_CLIP=ON'
        PROCESSING: 'CLOSE_CONNECTION=DEFER'
     }
     land_ol_width: 1
 }

La variable "layerconfig" contient plusieurs paramètres utilisés dans la définition de presque tous les LAYERs. Ainsi, pour chaque nouveau LAYER, il suffit d'écrire "@layerconfig" pour que tous les paramètres entrent dans la définition du LAYER. L'autre variable 'land_ol_width' prend une valeur unique.

Il est à noter que dans la définition de la variable "layerconfig", PROJECTION est suivi de deux "{". Cette syntaxe permet de gérer les tags comme PROJECTION, METADATA, PATTERN etc qui ne contiennent aucun paramètre, seulement du texte.

Blocs de commentaires

"Scribe" permet également d'utiliser des blocs de commentaire, ce qui n'est pas possible dans Mapserver seulement. D'autres types de commentaire sur une seule ligne peuvent aussi être écrits et apparaîtront dans le "mapfile" résultant.

LAYER {
    1-16 {
        NAME: 'land'
        TYPE: POLYGON
        @layerconfig
        DATA {
            1-4: '110m_physical/ne_110m_land'
            5-10: '50m_physical/ne_50m_land'
            11-16: '10m_physical/ne_10m_land'
        }
        CLASS {
            STYLE {
                COLOR {
                    1-6: '#EEECDF'
                    7-16: '#AAA89B'
                }                
                ##Les commentaires précédés par ## apparaissent
                ##dans le mapfile résultant.
                ##Les blocs de commentaires entre /* */
                ## n'apparaissent pas dans le mapfile résultant.
                /*
                OUTLINECOLOR: 200 200 200
                OUTLINEWIDTH: @land_ol_width
                */                
             }
         }
     }
 }

Conclusion

Finalement, pour exécuter le script, il suffit d'utiliser une seule commande, configurable avec certaines options:

python scribe.py

Le résultat est un mapfile parfaitement indenté avec gestion des échelles et souvent beaucoup plus de ligne que ce qu'il a fallu écrire. "Scribe" représente donc une économie importante en temps et produire des cartes de qualité devient plus facile et agréable.

Il est fort probable que "Scribe" évoluera et que de nouvelles fonctionnalités s'ajouteront.

Pour plus d'information ou pour utiliser "Scribe", consulter l'adresse suivante :

https://github.com/solutionsmapgears/Scribe.git Pour tout commentaire ou pour rapporter un bug : Charles-Éric Bourget cbourget@mapgears.com

Nouveaux OUTPUTFORMAT de Mapserver 6.0

Une des nouveautés de Mapserver 6.0 est l’ajout de nouveaux formats de sortie liés aux requêtes GetFeature via WFS. Attention par contre, on doit obligatoirement installer GDAL/OGR 1.8. Dans mon cas test sur mon serveur Debian, je devais utiliser une connexion Oracle. J'ai donc compilé GDAL/OGR avec le support Oracle et compilé Mapserver pour lire ce format via GDAL/OGR. J'ai aussi préparé un fichier regroupant les formats de sortie suivant: OUTPUTFORMAT   NAME "SHAPEZIP"   DRIVER "OGR/ESRI Shapefile"   MIMETYPE "application/shapefile"   FORMATOPTION "STORAGE=filesystem"   FORMATOPTION "FORM=zip"   FORMATOPTION "FILENAME=result.zip" END OUTPUTFORMAT   NAME "MIDMIF"   DRIVER "OGR/MapInfo File"   FORMATOPTION "STORAGE=filesystem"   FORMATOPTION "FORM=multipart"   FORMATOPTION "DSCO:FORMAT=MIF"   FORMATOPTION "FILENAME=result.mif" END OUTPUTFORMAT   NAME "MultiMIDMIF"   DRIVER "OGR/MapInfo File"   FORMATOPTION "STORAGE=filesystem"   FORMATOPTION "FORM=multipart"   FORMATOPTION "DSCO:FORMAT=MIF"   FORMATOPTION "FILENAME=result" END OUTPUTFORMAT   NAME "CSV"   DRIVER "OGR/CSV"   MIMETYPE "text/csv"   FORMATOPTION "LCO:GEOMETRY=AS_WKT"   FORMATOPTION "STORAGE=filesystem"   FORMATOPTION "FORM=simple"   FORMATOPTION "FILENAME=result.csv" END OUTPUTFORMAT   NAME "CSVSTREAM"   DRIVER "OGR/CSV"   MIMETYPE "text/csv; streamed"   FORMATOPTION "LCO:GEOMETRY=AS_WKT"   FORMATOPTION "STORAGE=stream"   FORMATOPTION "FORM=simple"   FORMATOPTION "FILENAME=result.csv" #-- If “stream” then the datasource will be created with a name “/vsistdout” as an attempt to write directly to stdout. Only a few OGR drivers will work properly in this mode (ie. CSV, perhaps kml, gml) END OUTPUTFORMAT   NAME "OGRGML"   DRIVER "OGR/GML"   MIMETYPE "text/xml; subtype=gml/2.1.2; driver=ogr"   FORMATOPTION "STORAGE=memory"   FORMATOPTION "FORM=multipart"   FORMATOPTION "FILENAME=result.gml" END OUTPUTFORMAT   NAME kml   DRIVER "KML"   MIMETYPE "application/vnd.google-earth.kml+xml"   IMAGEMODE RGB   EXTENSION "kml"   FORMATOPTION 'ATTACHMENT=result.kml'   FORMATOPTION "maxfeaturestodraw=100" END OUTPUTFORMAT   NAME kmz   DRIVER "KMZ"   MIMETYPE "application/vnd.google-earth.kmz"   IMAGEMODE RGB   EXTENSION "kmz"   FORMATOPTION 'ATTACHMENT=result.kmz' END OUTPUTFORMAT   NAME "geojson"   DRIVER "TEMPLATE"   MIMETYPE "application/json; subtype=geojson"   FORMATOPTION "FILE=../template/geojson.txml" END OUTPUTFORMAT   NAME "customxml"   DRIVER "TEMPLATE"   FORMATOPTION "FILE=mon_template.xml" END J'ai testé avec ce Mapfile WFS plusieurs de ces formats. Pour le type “SHAPEZIP” par exemple, on fait simplement cette requête URL: http://sigtest.gouc.qc/cgi-bin/extract?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&typename=SMDTS_TERR&OUTPUTFORMAT=SHAPEZIP NOTE 1: Je n'ai pas ajouter de clause FILTER pour mon test mais c'est possible. Voir ici pour plus d'explications NOTE 2: J'ai n'ai pas fait d'essais avec le format de sortie TEMPLATE qui nous permet de personnaliser un format de sortie texte comme geojson par exemple. Cette option est très puissante et mérite d'être mieux connue. Imaginer utiliser Mapserver pour générer du code javascript pour jQuery par exemple...! NOTE 3: Par comparaison, avec Geoserver qui support déjà ce type de format de sortie (SHAPEFILES) depuis plus longtemps: http://sigtest.gouv.qc:8180/geoserver/wfs?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&typename=SMDTS_TERR&OUTPUTFORMAT=SHAPE-ZIP

Database-driven mapfile

Avez-vous déjà essayé de construire une mapfile qui serait dynamiquement contrôlé par la base de données? À l'occasion, cette astuce peut aider à produire des services Web cartographiques basés sur des données dynamiques d'un système d'envergure. Voici une description de table provenant de Postgesql: Table "public.om_users" Column      | Type                   | Modifiers ------------+------------------------+------------------------------------- om_id       | integer                | not null longitude   | numeric(12,9)          | latitude    | numeric(12,9)          | color       | character varying(11)  | default '255 0 0'::character varying label       | character varying(100) | the_geom    | geometry               | feature     | character varying(32)  | default 'circle'::character varying size        | integer                | default 5 outline     | character varying(11)  | default '0 0 0'::character varying Indexes: "om_users_pkey" PRIMARY KEY, btree (om_id) Check constraints: "enforce_dims_the_geom" CHECK (st_ndims(the_geom) = 2) "enforce_geotype_the_geom" CHECK (geometrytype(the_geom) = 'POINT'::text OR the_geom IS NULL) "enforce_srid_the_geom" CHECK (st_srid(the_geom) = 4326) On place simplement les champs de données au bon endroit dans le mapfile. Pour chacun des enregistrements, mapserver prendra les données de la table pour cartographier l'entité. LAYER   NAME "UsersLayers"   INCLUDE "ec/ec_connec_db_pg.map"   DATA "the_geom from om_users using srid=4326"   TYPE POINT   METADATA     "wms_name" "UsersLayer"     "wms_title" "UsersLayer"     "wms_server_version" "1.1.1"     "wms_srs" "EPSG:4326 EPSG:900913"     "gml_featureid" "oid"     "gml_include_items" "all"   END   PROJECTION     "init=epsg:4326"   END   CLASSITEM "om_id"   CLASS   NAME "Status"   STYLE     SYMBOL [feature]       COLOR [color]       OUTLINECOLOR [outline]       SIZE [size]     END   END END

A new XML Mapfile Format in Mapserver 5.6

Build a mapfile for Mapserver from plain text is not an easy job.  We can use a Syntax coloring Editor like SciTE, PsPad or UltraEdit, but we all hope have a nice and easy  mapfile editor for build our web map service.  Sice version 5.6, Mapserver has a XML schema has been defined to encode mapfiles in XML format. We can get some input from Mapserver Wiki for example, how to implement it and how to convert existing mapfile. <?xml version="1.0" encoding="UTF-8"?> <Map name="GMAP-DEMO" version="5.6.0" status="ON" xmlns="http://www.mapserver.org/mapserver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mapserver.org/mapserver"> <extent>-2200000 -712631 3072800 3840000</extent> <fontSet>../etc/fonts.txt</fontSet> <imageColor red="255" green="255" blue="255"/> <Layer name="bathymetry" type="RASTER" status="ON"> <data>bath_mapserver.tif</data> <Metadata> <item name="DESCRIPTION">Elevation/Bathymetry</item> </Metadata> </Layer> <Layer name="popplace" type="POINT" status="ON"> <Class name="Cities"> <color red="0" green="0" blue="0"/>< expression>1</expression> <Label type="TRUETYPE"> <align>LEFT</align> <color red="255" green="0" blue="0"/> <font>sans-italic</font> <outlineColor red="255" green="255" blue="255"/> <partials>FALSE</partials> <position>AUTO</position> <size>8</size> </Label> ...So this feature is not realy exciting BUT now we have a tool to parse any mapfile and option to build new client interface for Mapfile.  This is a prety good news... :-)

How to produce 8bits AGG homemade colors palette with Mapserver

The format of the output image affects the quality and size of the image to be transferred to the client on the web. We are always trying to minimize the size without losing image quality. With Mapserver, we can use the format AGG (24bit or 32bit PNG) of high quality although it's also a format that produces larger image size(in Kb). But, it's possible to force Mapserver to produce images of excellent qualities AGG 8bit format. The default image format output in Mapserver is 8bit PNG format (256 colors). To test a 24bits or 32bits format, you must overwrite the default format in your mapfile with this in your MAP tag. OUTPUTFORMAT    NAME "png"    MIMETYPE "image/png"    DRIVER "GD/PNG"    EXTENSION "png"    #--IMAGEMODE RGB #-- Driver PNG 24bit    IMAGEMODE RGBA #-- Driver PNG 32bit    TRANSPARENT ON END The default AGG format in Mapserver is a 24bits driver but you can use a 8bits driver. This type of driver in your mapfile will cause a slower performence of Mapserver. You can specify a predefined colors palette to improved performance and get accurate colors of your map. To build your homemade colors palette, you have first to produce a 8bits AGG image by mapserver with this driver in your mapfile: OUTPUTFORMAT    NAME "png8bitaggauto"    DRIVER "AGG/PNG"    MIMETYPE "image/png"    IMAGEMODE "pc256"    EXTENSION "png"    FORMATOPTION "TRANSPARENT=ON"    FORMATOPTION "INTERLACE=OFF"    FORMATOPTION "QUANTIZE_FORCE=ON"    FORMATOPTION "QUANTIZE_COLORS=256" END I suggest to produce more than just one image. Get images from multiple scales and different sectors of your WMS to get every colors. After, build a mosaic all images with an imaging tool like Microsoft Paint or Gymp. Finally, use gdalinfo to get your palette colors and paste them in a ASCII file. To use your new palette colors and realy improve your Mapserver AGG image output, add this driver specification in your mapfile(in MAP tag) #-- Driver AGG 8bits OUTPUTFORMAT    NAME "png8bitsagg"    DRIVER "AGG/PNG"    MIMETYPE "image/png"    IMAGEMODE "rgba"    EXTENSION "png"    FORMATOPTION "TRANSPARENT=ON"    FORMATOPTION "PALETTE_FORCE=TRUE"    FORMATOPTION "PALETTE=E:ms4wpalpal_agg256.txt"    FORMATOPTION "INTERLACE=OFF" END

Optimizing your mapfile for Mapserver

There are many tricks to optimize your mapfile.  After many years to build mapfiles for Mapserver, I learned many trick to optimise a mapfile.  I try here to highlight some of them:
  1. Manage your EPSG file if you specify espg code.  Only keep in your file projections used in your systems.  The original file have approx 545k and Mapserver read this file each time you add in your mapfile instruction like  "init=epsg:32198".  You can reduce it to 4k. Or, you can put the projections specify in your mapfile at the top of your epsg file...  so Mapserver don't need to scan the whole epsg file.
  2. Lowercase epsg intruction like "init=epsg:32198".  Mapserver use C function "strcmp" in lowercase first.
  3. Try to be "brief"... Don't put extra default instruction like STATUS ON or LABELCACHE ON... STATUS and LABELCACHE are ON by default.  The less you have to read, faster you are. And try to limit your comment to...
  4. In the layer definition the tag EXPRESSION can be very costly.  Alway try to use "regular expression"(REGEX) it's much faster!  If you add something like that EXPRESSION (("[DESCRIPTION]" eq "Park") OR ("[DESCRIPTION]" eq "Base") Mapserver gona built sql string then gona filtre your data! It work but it's not optimize.  In this example we just put directly in the mapfile EXPRESSION /^Park$|^Base$/ and Mapserver use it without any string manipulation.  And change this EXPRESSION ("[DESCRIPTION]" eq "Park") to this EXPRESSION "Park"!
  5. Try not to re-project your data it's costly overhead.
  6. Shapefile format ALWAYS de faster way to publish data.
  7. Shptree each of your shapefile AND don't specify shp in DATA tag.  This one is very peculiar one! If you put DATA like this DATA "/srv/data/park.shp" Mapserver use a built in reader and don't use index qix file.  But if you type DATA "/srv/data/park" Mapserver gona use OGR to read data and OGR alway use qix file!
  8. For large shapefiles, use tile4ms program to tile them.  Follow those instruction to split your shapefile.  This trick is very very usefull
  9. If you use PostGIS or Oracle Data, try to connect to only one server and keep your connection open with this instruction in each layer PROCESSING "CLOSE_CONNECTION=DEFER"
  10. Never use SIZEUNITS in the LAYER tag.  Put it in the MAP tag.
  11. If you build a mapfile for a Web map service(WMS) always add STATUS OFF in each layer.  In this way Mapserver don't prepared all cartographics parametres like labeling engine.
  12. Finally, use debug option to find out witch layers are to slow.  The best trick her is to use shp2img program to eliminate overhead of your network.  To use debug option you have to insert in the MAP tag this CONFIG MS_ERRORFILE "/srv/log/debug.log" and in each layer this tag DEBUG 5  aIn the log file check the msDrawMap for each layer.
Shp2imp example on Windows shp2img -m C:Testimg2.map -l png -o C:Testext.png -e 219589 5347860 226045 5353101 Log example [Fri Feb 15 16:05:14 2008].742000 msDrawRasterLayerLow(tif): entering. [Fri Feb 15 16:05:14 2008].762000 msDrawGDAL(): src=0,0,33477,25307, dst=0,26,935,707 [Fri Feb 15 16:05:14 2008].762000 msDrawGDAL(): red,green,blue,alpha bands = 1,2,3,0 [Fri Feb 15 16:05:15 2008].2000 msDrawMap(): Layer 2 (tif), 0.260sp