Dimensions d’un jeu de tuiles

Author

Thomas Bonfort

Contact

tbonfort at terriscope.fr

Author

Seth Girvin

Contact

sgirvin at compass.ie

Author

Jerome Boue

Last Updated

2018/06/29

Version

1.8

Introduction

Les couches WMS dans MapServer peuvent supporter les dimensions, voir WMS Dimension pour les détails. MapCache supporte également les dimensions pour les jeux de tuiles.

Stocker des caches de tuiles distincts pour différentes dimensions est utile dans un certain nombre de situations comme créer des caches pour différentes limites spatiales, altitudes ou périodes de temps. Cela fournit aussi un mécanisme pour changer de Mapfile selon une valeur de dimension donnée.

MapCache utilise les dimensions pour:

  • créer la structure de dossiers des caches disque et SQLite qui utilisent la variable de substitution {dim} dans le template de leur configuration, voir Template Structure dans Caches disque et Using Multiple SQLite Database Files dans Caches SQLite.

  • créer le schéma de données des caches SQLite qui utilisent la variable de substitution :dim, voir Custom Schema dans Caches SQLite.

  • construire les requêtes à destination des sources WMS: toute dimension spécifiée pour un jeu de tuile est transmise à sa source WMS lorsqu’un défaut de cache survient.

Activation des dimensions dans MapCache

Les dimensions sont activées dans le fichier de configuration au niveau du Jeu de tuiles. Chaque jeu de tuile peut définir ses propres dimensions à l’aide d’une section <dimensions> qui inclut un ou plusieurs éléments <dimension>.

Attributs de dimension

Plusieurs attributs sont définis pour l’élément <dimension>.

  • name (requis): Le nom de la dimension dans une requête WMS (sans le préfixe dim_ spécifié dans WMS 1.1.1 Specification).

  • default (requis): La valeur par défaut de la dimension lorsqu’aucune valeur n’est fournie explicitement dans la requête WMS.

  • type (required): MapCache defines several ways for specifying a dimension: values, regex, sqlite, postgresql, elasticsearch and time. They are described in the following sections.

  • unit (optionnel): unité physique de la dimension. Cette information n’est utilisée que dans la réponse à une requête GetCapabilities.

Types de dimensions

Several types of dimensions are supported by MapCache: Values, Regex, SQLite, PostgreSQL, ElasticSearch and Time. They can be classified into two groups depending on how dimension values are defined:

  • Dimensions de premier niveau: values et regex. Les valeurs de la dimension sont spécifiées statiquement dans le fichier de configuration.

  • Second level dimensions types: SQLite, PostgreSQL, ElasticSearch and Time. Dimensions values are stored in a dynamic backend (SQLite file, PostgreSQL database or ElasticSearch index) so that an update in the dimension values does not involve a server restart.

Dimensions de premier niveau

Les dimensions de premier niveau définissent leurs valeurs dans le fichier de configuration.

Dimensions de type values

Une dimension de type values donne la liste de toutes ses valeurs possibles. Le nom de la dimension est utilisée comme clé dans une requête WMS ou WMTS, par exemple &DIM1=foobarbaz.

Lorsqu’un client ne fournit pas de valeur explicite pour la dimension, alors la valeur par défaut est prise en compte (c’est-à-dire foobar dans cet exemple).

<tileset name="LayerName">
    <source>LayerSource</source>
    <cache>sqlite</cache>
    <grid>GoogleMapsCompatible</grid>
    <format>PNG</format>
    <metatile>5 5</metatile>
    <metabuffer>10</metabuffer>
    <dimensions>
        <dimension type="values" name="DIM1" default="foobar">
            <value>foobar</value>
            <value>foobarbaz</value>
            <value>foo</value>
            <value>bar</value>
        </dimension>
    </dimensions>
</tileset>

Dimensions de type regex

Une alternative à une liste de valeurs en dur, est l’utilisation d’expressions régulières.

L’exemple suivant crée une dimension MAPFILE, afin d’utiliser le même jeu de tuiles MapCache avec différents Mapfiles MapServer. MapCache n’a pas besoin de connaître les noms des Mapfiles, qui peuvent alors être créés après le démarrage de MapCache.

Lorsqu’un utilisateur passe MAPFILE=/path/to/mapfile, MapCache vérifie que la chaîne /path/to/mapfile répond à l’expression régulière (PCRE) définie dans le fichier de configuration. Dans cet exemple, l’expression régulière valide un nom composé de caractères alphanumériques séparés par des barres obliques (/) et se terminant par « .map » ( [a-zA-Z0-9./]*.map$ ), mais échoue en présence de deux points consécutifs (..) afin d’empêcher toute traversée du système de fichiers.

<dimension type="regex" name="MAPFILE" default="/path/to/mapfile.map">
        <regex>^(?!.*\.\.)[a-zA-Z0-9\./]*\.map$</regex>
</dimension>

Dimensions de deuxième niveau

Second level dimensions store values in a dynamic back-end, a SQLite file, a PostgreSQL database or an ElasticSearch index. This allows for adding new dimension values without modifying MapCache configuration file, which otherwise would need to restart the server.

Dimensions de type sqlite

Dans une dimension de type sqlite les valeurs de dimension sont stockées dans un fichier SQLite. De plus, chaque valeur référence une liste de valeurs internes, dites de sous-dimension, qui sont utilisées pour formuler les requêtes au cache.

Par exemple, soit une dimension « sensor » dont les valeurs possibles représentent divers satellites d’observation de la Terre. Une requête WMS avec cette dimension peut contenir: ...&dim_sensor=spot&... Dans cet exemple, chaque capteur référence plusieurs types de données selon une sous-dimension interne nommée « product ». Un exemple du contenu de la dimension « sensor » est représenté sur la table suivante:

Dimension « sensor »

Sous-dimension « product »

spot

spot-img1

spot

spot-img2

phr

phr-img1

phr

phr-img2

phr

phr-img3

La requête WMS se passe au niveau de la dimension « sensor ». La requête sur le cache a lieu au niveau de la sous-dimension « product ». Dans le cas d’un défaut de cache, les valeurs de la sous-dimension « product » sont utilisées pour formuler une requête à la source de données.

Une requête à une dimension de type sqlite peut alors renvoyer plusieurs valeurs de sous-dimensions, chacune faisant référence à des tuiles distinctes. Assembler ces tuiles est décrit dans la section Tile Assembly Policies plus loin.

A SQLite dimension is configured by specifying three elements:

  • <dbfile>: Location of the SQLite file implementing the dimension

  • <list_query>: SQL query returning all sub-dimension values for the dimension, e.g. spot-img1, spot-img2, phr-img1, phr-img2 and phr-img3 in the « sensor » dimension example.

  • <validate_query>: SQL query returning the list of sub-dimension values for a given dimension value, e.g. spot-img1 and spot-img2 for the spot dimension value in the « sensor » dimension example.

<!-- "sqlite" dimension

     This example defines a "sensor" dimension whose possible values are
     stored in the /data/sensor.sqlite SQLite file. The default value is
     "phr".

     A WMS request with that dimension may contain, e.g.
     "... &dim_sensor=spot& ..."
-->
<dimension type="sqlite" name="sensor" default="phr">

   <!-- Access Point -->
   <dbfile>/data/sensor.sqlite</dbfile>

   <!-- All-values Query -->
   <list_query>
      SELECT distinct(product) FROM dim
   </list_query>

   <!-- Selected-values Query -->
   <validate_query>
      SELECT product FROM dim
       WHERE sensor=:dim
   </validate_query>

</dimension>

PostgreSQL Dimensions

PostgreSQL dimensions are very similar to SQLite dimensions. In particular, the sub-dimension feature described in the section above is also available with PostgreSQL dimensions. They differ only in the back-end implementation which is a PostgreSQL database, and in the way they are configured. A PostgreSQL dimension is configured by specifying three elements:

  • <connection>: Connection information of the PostgreSQL database implementing the dimension, as expected by the PQconnectdb() function of the libpq - C library.

  • <list_query>: SQL query returning all sub-dimension values for the dimension, e.g. spot-img1, spot-img2, phr-img1, phr-img2 and phr-img3 in the « sensor » dimension example.

  • <validate_query>: SQL query returning the list of sub-dimension values for a given dimension value, e.g. spot-img1 and spot-img2 for the spot dimension value in the « sensor » dimension example.

<!-- "postgresql" dimension

     This example defines a "sensor" dimension whose possible values are
     stored in the postgresql://mapcache:mapcache@localhost:5433/mapcache
     PostgreSQL database. The default value is "phr".

     A WMS request with that dimension may contain, e.g.
     "... &dim_sensor=spot& ..."
-->
<dimension type="postgresql" name="sensor" default="phr">

   <!-- Access Point -->
  <connection>
    host=localhost user=mapcache password=mapcache dbname=mapcache port=5433
  </connection>

   <!-- All-values Query -->
  <list_query>SELECT distinct(product) FROM dim</list_query>

   <!-- Selected-values Query -->
  <validate_query>SELECT product FROM dim WHERE sensor=:dim</validate_query>

</dimension>

ElasticSearch Dimensions

ElasticSearch dimensions are similar to SQLite or PostgreSQL dimensions. In particular, the sub-dimension feature described in the section above is also available with ElasticSearch dimensions. ElasticSearch main difference with SQLite and PostgreSQL is its query language, Query-DSL, based on JSON instead of SQL. Therefore, <list_query> and <validate_query> elements shall contain queries expressed in that language. Inserting JSON data in XML configuration may need to encapsulate it in a <![CDATA[ ... ]]> element.

In addition, the complex structure of ElasticSearch responses, also expressed in JSON, raises the need for extraction instructions to get sub-dimension values from query responses.

For example, let’s assume that this ElasticSearch query:

{   "size": 0,
    "aggs": { "distinct_product": { "terms": { "field": "product/keyword" } } },
    "query": { "term": { "sensor": "phr" } }
}

returns this response:

{   "took": 0,
    "timed_out": false,
    "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 },
    "hits": { "total": 5, "max_score": 0, "hits": [] },
    "aggregations": {
        "distinct_product": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
                { "key": "phr_img1", "doc_count": 1 },
                { "key": "phr_img2", "doc_count": 1 },
                { "key": "phr_img3", "doc_count": 1 }
            ]
        }
    }
}

The expected sub-dimension values are then:

[ "phr_img1", "phr_img2", "phr_img3" ]

This list is obtained by extracting the aggregations item from the outer dictionary, then distinct_product , then buckets. Finally the key item is to be extracted from each dictionary of the bucket list. The extraction instructions take the form of a path-like list of keywords to be used to extract sub-dimension values from JSON response provided by ElasticSearch:

[ "aggregations", "distinct_product", "buckets", "key" ]

This is specified in the MapCache configuration file by way of new XML elements, namely <list_response> and <validate_response>, containing extraction instructions respectively for <list_query> and <validate_query> queries.

An ElasticSearch dimensions is defined by specifying five elements:

  • <http>: Connection information of the ElasticSearch index implementing the dimension. This element contains two sub-elements:

    • <url>: Search URL of the ElasticSearch index

    • <headers>: HTTP headers needed to access the ElasticSearch index. It must contain the tag: <Content-Type>application/json</Content-Type>.

  • <list_query>: Query DSL query for getting all sub-dimension values for the dimension. This query is expressed in JSON format and may need to be escaped in a <![CDATA[ ... ]]> tag.

  • <validate_query>: Query DSL query for getting the list of sub-dimension values for a given dimension value. This query is expressed in JSON format and may need to be escaped in a <![CDATA[ ... ]]> tag.

  • <list_response>: List of successive keywords needed to extract actual sub-dimension values from the raw ElasticSearch response to the <list_query> query.

  • <validate_response>: List of successive keywords needed to extract actual sub-dimension values from the raw ElasticSearch response to the <validate_query> query.

<!-- "elasticsearch" dimension

     This example defines a "sensor" dimension whose possible values are
     stored in the http://localhost:9200/sensor ElasticSearch index. The
     default value is "phr".

     A WMS request with that dimension may contain, e.g.
     "... &dim_sensor=spot& ..."
-->
<dimension type="elasticsearch" name="sensor" default="phr">

  <!-- Access Point -->
  <http>
    <url>http://localhost:9200/sensor/_search</url>
    <headers>
      <Content-Type>application/json</Content-Type>
    </headers>
  </http>

  <!-- Selected-values Query -->
  <validate_query><![CDATA[
    { "size": 0,
      "aggs": {
        "products": { "terms": { "field": "product.keyword" } }
      },
      "query": { "term": { "sensor": ":dim" } }
    }
  ]]></validate_query>

  <!-- Selected-values Response Extration Instructions -->
  <validate_response><![CDATA[
    [ "aggregations", "products", "buckets", "key" ]
  ]]></validate_response>

  <!-- All-values Query -->
  <list_query><![CDATA[
    { "size": 0,
      "aggs": {
        "products": { "terms": { "field": "product.keyword" } }
      }
    }
  ]]></list_query>

  <!-- All-values Response Extration Instructions -->
  <list_response><![CDATA[
    [ "aggregations", "products", "buckets", "key" ]
  ]]></list_response>
</dimension>

Dimensions de type time

MapCache supports WMTS and WMS requests that include a TIME parameter, for both timestamps and time intervals. See MS RFC 96: Time Dimension Support in MapCache Tilesets initial proposal for further details.

Up to Mapcache 1.6.1, defining a Time dimension involves a SQLite back-end. Two elements must be provided:

  • <dbfile>: Emplacement du fichier SQLite qui sert de support à la dimension de type time

  • <query>: Requête SQL qui renvoie la liste des valeurs de dates disponibles dans un intervalle de temps donné. Dans cette requête, la date de début et la date de fin sont représentées par les variables de substitution :start_timestamp et :end_time_stamp. Les valeurs renvoyées doivent être au format de date yyyy-mm-ddThh:mm:ssZ, tel que spécifié dans la RFC 3339 mais restreint à l’UTC.

From MapCache 1.8 on, dynamic back-ends for Time dimensions can be PostgreSQL databases or ElasticSearch indices in addition to SQLite files. Configuring a Time dimension with a specific back-end is made by setting a time="true" attribute within a SQLite, PostgreSQL or ElasticSearch dimension configuration.

Still, MapCache keeps an (almost) backward compatible configuration of Time dimension with an implicit SQLite back-end. Example in the following section highlights differences.

Time values in the dynamic back-end are expressed in Unix time, i.e. the number of seconds that have elapsed since January 1, 1970 (midnight UTC).

Une requête temporelle avec un intervalle peut renvoyer plusieurs valeurs de dates, chacune faisant référence à des tuiles distinctes. Assembler ces tuiles est décrit dans la section Tile Assembly Policies plus loin.

Configuring a Time Dimension using a default SQLite back-end

<!-- "time" dimension

     This example defines a "time" dimension whose possible values
     are stored in the /path/to/mapcache-time.sqlite SQLite file. The
     default value is "2010".

     A WMS request with that dimension may contain, e.g.  "...
     &time=1999-08-11T11:03:07Z/2001-09-21T08:17:56Z& ..."
-->
<dimension type="time" name="time" default="2010">
    <dbfile>/path/to/mapcache-time.sqlite</dbfile>
    <query>
         SELECT strftime("%Y-%m-%dT%H:%M:%SZ",ts) FROM time
          WHERE ts &gt;= datetime(:start_timestamp,"unixepoch")
            AND ts &lt;= datetime(:end_timestamp,"unixepoch")
       ORDER BY ts DESC
    </query>

</dimension>
<!-- "time" dimension

     This example defines a "time" dimension whose possible values
     are stored in the /path/to/mapcache-time.sqlite SQLite file. The
     default value is "2010".

     A WMS request with that dimension may contain, e.g.  "...
     &time=1999-08-11T11:03:07Z/2001-09-21T08:17:56Z& ..."
-->
<dimension type="time" name="time" default="2010">
    <dbfile>/path/to/mapcache-time.sqlite</dbfile>
    <validate_query>
         SELECT strftime("%Y-%m-%dT%H:%M:%SZ",ts) FROM time
          WHERE ts &gt;= datetime(:start_timestamp,"unixepoch")
            AND ts &lt;= datetime(:end_timestamp,"unixepoch")
       ORDER BY ts DESC
    </validate_query>
    <list_query>SELECT ts FROM time</list_query>
</dimension>

Up to MapCache 1.6.1

From MapCache 1.8 on

Configuring a Time dimension using an explicit back-end

 <!-- "time" dimension, "new-style" definition using a SQLite back-end

      This example defines a "time" dimension whose possible values are
      stored in the /data/times.sqlite SQLite file. The default value is
      "d1".

      A WMS request with that dimension may contain, e.g.
      "... &time=1999-08-11T11:03:07Z/2001-09-21T08:17:56Z& ..."
 -->
 <dimension type="sqlite" name="time" default="d1" time="true">
   <dbfile>/data/times.sqlite</dbfile>
   <list_query>SELECT ts FROM timedim</list_query>
   <validate_query>
        SELECT strftime("%Y-%m-%dT%H:%M:%SZ",ts) FROM timedim
         WHERE ts &gt;= datetime(:start_timestamp,"unixepoch")
           AND ts &lt;= datetime(:end_timestamp,"unixepoch")
      ORDER BY ts DESC
   </validate_query>
 </dimension>
 <!-- "time" dimension, "new-style" definition using a PostgreSQL back-end

      This example defines a "time" dimension whose possible values are
      stored in the postgresql://mapcache:mapcache@localhost:5433/time
      PostgreSQL database. The default value is "d1".

      A WMS request with that dimension may contain, e.g.
      "... &time=1999-08-11T11:03:07Z/2001-09-21T08:17:56Z& ..."
 -->
 <dimension type="postgresql" name="time" default="d1" time="true">
   <connection>
     host=localhost user=mapcache password=mapcache dbname=times port=5433
   </connection>
   <list_query>SELECT ts FROM timedim</list_query>
   <validate_query>
        SELECT to_char(ts,'YYYY-MM-DD"T"HH24:MI:SS"Z"') FROM timedim
         WHERE ts &gt;= to_timestamp(:start_timestamp)
           AND ts &lt;= to_timestamp(:end_timestamp)
      ORDER BY ts DESC
   </validate_query>
 </dimension>
 <!-- "time" dimension, "new-style" definition using an ElasticSearch back-end

      This example defines a "time" dimension whose possible values are
      stored in the http://localhost:9200/times ElasticSearch index. The
      default value is "d1".

      A WMS request with that dimension may contain, e.g.
      "... &time=1999-08-11T11:03:07Z/2001-09-21T08:17:56Z& ..."
 -->
 <dimension type="elasticsearch" name="time" default="d1" time="true">
   <http>
     <url>http://localhost:9200/times/_search</url>
     <headers>
       <Content-Type>application/json</Content-Type>
     </headers>
   </http>

   <list_query><![CDATA[
     { "query": { "match_all": { } } }
   ]]></list_query>
   <list_response><![CDATA[
     [ "hits", "hits", "_source", "ts" ]
   ]]></list_response>

   <validate_query><![CDATA[
     { "query": {
         "range": {
           "ts": { "gte": :start_timestamp, "lte": :end_timestamp }
         }
       },
       "sort": { "ts": { "order": "desc" } },
       "script_fields": {
         "iso_ts": {
           "lang": "painless",
           "source": "SimpleDateFormat f=new SimpleDateFormat(params.fmt); f.setTimeZone(params.tz); return (f.format(doc.ts.value.getMillis()))",
           "params": {
             "fmt": "yyyy-MM-dd'T'HH:mm:ss'Z'",
             "tz: "UTC"
           }
         }
       }
     }
   ]]></validate_query>
   <validate_response><![CDATA[
     [ "hits", "hits", "fields", "iso_ts", 0 ]
   ]]></validate_response>

 </dimension>

Politiques d’assemblage des tuiles

Lorsque plusieurs valeurs de temps ou de sous-dimensions sont renvoyées suite à une requête à une dimension de deuxième niveau, la tuile résultante est un assemblage de toutes les tuiles simples associées à chaque valeur. La façon dont ces tuiles sont assemblées est définie avec l’élément <assembly_type> de la section <dimensions>. Seulement deux valeurs sont prises en charge par MapCache:

  • none: Aucun assemblage n’est réalisé, la tuile résultante est la première tuile récupérée du cache. C’est la valeur par défaut.

  • stack: Chaque pixel de la tuile résultante est rempli avec le premier pixel opaque trouvé dans toutes les tuiles dans leur ordre de récupération, par exemple:

    Type d’assemblage: stack
    ../_images/mapcache-assembly-stack-1.png ../_images/mapcache-assembly-stack-2.png ../_images/mapcache-assembly-stack-3.png

    Produit 1

    Produit 2

    Assemblage stack des produits 1 et 2

Il est possible de choisir si la tuile résultante doit être stockée dans le cache ou non, via l’élément <store_assemblies> de la section <dimensions>. Les deux valeurs possibles sont true et false. La valeur par défaut est true.

L’élément <subdimensions_read_only> de la section <dimensions> indique si la source de données doit être interrogée en cas de défaut de cache. Les deux valeurs possibles sont true et false. La valeur par défaut est false.

<dimensions>
    <assembly_type>stack</assembly_type>
    <store_assemblies>true</store_assemblies>
    <subdimensions_read_only>false</subdimensions_read_only>
    <dimension >...</dimension>
</dimensions>

Stockage des dimensions

Dans un cache de type disque, les tuiles sont stockées dans une structure de dossier ressemblant à base/gridname/DIM1/value/xx/xx/xx/xx/xx/xx.png (où DIM1 est la valeur de dimension).

L’ordre des éléments <dimension> à l’intérieur de la section <dimensions> est important car il est utilisé pour créer la structure de répertoires pour le cache de type disque. Si l’ordre est modifié, toutes les tuiles qui ont été déjà stockées dans le cache sont invalidées (elles deviennent inaccessibles à MapCache mais ne sont pas détruites).

Les « templates » peuvent être utilisés pour adapter la structure des dossiers, par exemple:

<cache name="tmpl" type="disk">
    <template>/tmp/template-test/{tileset}#{grid}#{dim}/{z}/{x}/{y}.{ext}</template>
</cache>

Les « templates » peuvent aussi être utilisés pour adapter le nom d’un fichier SQLite, par exemple:

<!-- the following will create databases named TilesetName-#DimensionValue.db -->
<cache name="sqlite" type="sqlite3">
   <dbfile>{tileset}-{dim}.db</dbfile>
   <detect_blank />
</cache>

Accès à un cache de tuiles à l’aide des dimensions

Pour obtenir une tuile avec une dimension spécifique, il suffit d’ajouter le nom de la dimension et sa valeur à la requête, par exemple: &DIM1=value

Seuls les clients WMS et WMTS supportent le passage des dimensions à MapCache. Les autres services de tuiles, tels que gmaps (GoogleMaps), tms et KML ne supportent pas les dimensions. Toutefois, WMTS permet l’accès à une tuile selon un schéma d’adressage x,y,z, ce qui ouvre la possibilité à des clients de tuiles (par exemple une couche OpenLayers ol.source.XYZ) d’accéder à un jeu de tuiles avec dimensions grâce à une réécriture d’URL sur le serveur web.

# a URL such as the following
http://servername/tiles/13/3914/2687.png
# can be rewritten into a WMTS request - note WMTS uses {z}/{y}/{x} rather than {z}/{x}/{y}
# so these values need to be swapped
http://servername/mapcache/wmts/1.0.0/layername/default/dimension/GoogleMapsCompatible/13/2687/3914.png

Alimenter un cache de tuiles à l’aide des dimensions

L’outil d’alimentation de MapCache autorise d’alimenter des dimensions spécifiques avec la syntaxe suivante:

mapcache_seed -c mapcache_config.xml -t tileset1 -e -1187000,6695000,-605000,7450000 -z 7,10 --dimension DIMENSION_NAME=DIMENSION_VALUE