OSM: Utiliser les attributs de l’admin_centre pour rendre des limites administratives

Dans la base de données OpenStreetMap, les informations sur les entités administratives (communes, départements, etc.) se trouvent généralement sur le noeud place=* qui est membre de la relation du type boundary avec le rôle admin_centre. Si on a besoin de ces informations, comme par exemple population ou name:xx, pour choisir le style du polygone ou y assigner un libellée, les structures des données crées par les outils ne permettent pas d'accéder aux informations du noeud admin_centre à partir du polygone. Cet article présente des solutions pour les bases de données PostGis crées avec osm2pgsql et les fichiers shape crées avec osmium.

PostGis / osm2pgsql

osm2pgsql reporte bien les attributs des chemins «outer» sur le polygone qu'il crée pour une relation du type «boundary» mais pas les attributs du centre administratif. En mode «slim» osm2pgsql crée bien une table <prefix>_rels qui contient pour chaque relation ses attributs et ses membres avec le rôle et l'id, mais sous une forme qui n'est pas directement exploitable avec SQL. Un exemple :
psql -d osm -c  "select members from planet_osm_rels\
                     where hstore(tags)->'name'='Corneilla-la-Rivière';"
                               members
----------------------------------------------------------------------------
{w121772197,outer,w100006391,outer,w121772204,outer,n287557758,admin_centre}
On trouve les membres d'une relation dans un array, deux valeurs par membre, le type et l'identificateur du membre et son rôle. Une petite fonction SQL permet d'acceder à l'identificateur de l'admin_centre :
CREATE OR REPLACE FUNCTION admin_centre(anyarray)
   RETURNS bigint
   LANGUAGE sql
   AS $function$
      SELECT cast(substring($1[i] from 2) as bigint)
         FROM generate_series(array_lower($1,1),array_upper($1,1),2) i 
         WHERE $1[i+1]='admin_centre';$function$
Cette fonction permet de faire des requêtes sur les relations des limites administratives avec les attributs de leurs centres administratifs :
select poly.name, point.name, point."name:ca", point.population 
   from planet_osm_rels r, 
        planet_osm_polygon poly, 
        planet_osm_point point 
   where poly.admin_level='8' 
         and admin_centre(r.members) = point.osm_id 
         and r.id = -poly.osm_id ;
Si ce type de requête n'est pas suffisamment efficace on peut copier les attributs des noeuds sur les polygones ou bien créer un table de références :
create table admin_rel 
   as select -id as rel_id,
             admin_centre(members) as admin_centre
        from planet_osm_rels 
        where hstore(tags)->'boundary'='administrative' and 
              hstore(tags)->'admin_level'  in ('8','6','4');

fichiers «shape»

On utilise pour cela l'outil osmjs et QGis. osmjs basé sur la bibliothèque osmium permet d'extraire des objets d'un fichier osm sous forme de fichier «shape». Comme osm2pgsql, cet outil crée des polygones pour les relations «boundary» mais ne tient pas compte du rôle admin_centre. Par contre il est possible de produire un fichier csv qui contient les identificateurs des polygones et de leurs noeuds admin_centre :
/*
  Osmium Javascript Example:  shape_export.js

  run with: osmjs -2 -m -l sparsetable -j shape_export.js OSMFILE
*/

var shp_places = Osmium.Output.Shapefile.open('./places', 'point');
shp_places.add_field('id', 'string', 12);
shp_places.add_field('type', 'string', 32);
shp_places.add_field('name', 'string', 32);

var shp_boundaries = Osmium.Output.Shapefile.open('./boundaries', 'polygon');
shp_boundaries.add_field('id', 'integer', 10);
shp_boundaries.add_field('type', 'string', 32);
shp_boundaries.add_field('boundary', 'string', 255);
shp_boundaries.add_field('admin_level', 'string', 255);

var csv_file = Osmium.Output.CSV.open("./rels.csv");

var node_tags = {
    place: { village: 'villaget', city: 'city', town: 'town'} //,
    //shop: { supermarket: 'supermarket' }
}

Osmium.Callbacks.init = function() {
    print("Init");
}
Osmium.Callbacks.node = function() {
    for (var key in this.tags) {
        if (node_tags[key]) {
            var type = node_tags[key][this.tags[key]];
            if (type) {
                shp_places.add(this.geom, { id: this.id, type: type, name: this.tags.name });
            }
        }
    }
}
Osmium.Callbacks.area = function() {
    if (this.tags.boundary) {
        shp_boundaries.add(this.geom, { id: this.id, type: this.tags.boundary, admin_level: this.tags.admin_level});
    }
}
Osmium.Callbacks.relation = function() {
   for (var key in this.members) {
      for (var key2 in this.members[key]) {
         if (key2=='role' && this.members[key].role=='admin_centre'){
            csv_file.print(this.id*2+1, this.members[key].ref, 
                           this.members[key].role, this.members[key].type);
         }
      }
   }
}
Osmium.Callbacks.end = function() {
    shp_places.close();
    shp_boundaries.close();
    csv_file.close();
    print("Done");
}
Ce scripte produit un fichier shape avec les polygones, un avec les noeuds et und fichier csv qui relie les uns aux autres par leurs identificateurs. Il tient compte du fait que osmjs assigne des pseudo-identificatuers aux polygones qu'il calcule avec la formule [2 * osm_id de la relation + 1. On charge ces trois fichers dans QGis. On crée une jointure des données csv avec la couche des noeuds et ensuite une jointure de la couche des polygones avec les données csv. La couche polygone a maintenant accès aux données des noeuds associés. Si on enregistre la couche polygone dans un ficher au format shape, ce fichier contiendra toutes les données et peut être utilisé avec mapnik / tilemill.