OSM: Zugriff auf die Attribute des Admin_Centre beim Rendern von Verwaltungsgrenzen

In der OpenStreetMap-Datenbank sind die Informationen über Verwaltungseinheiten (Gemeinden, Kreise, Länder) meist auf die Grenzrelation und den Knoten mit der Rolle admin_centre verteilt. Will man zum Rendern der Grenze oder Fläche einer Verwaltungseinheit auf Informationen zurückgreifen, die beim Knoten gespeichert sind, z.B. das Attribut population oder name:xx, dann stellt man fest, dass üblichen Werkzeuge diese Informationen nicht bereit stellen. Nachfolgend werden Methoden beschrieben, wie man dieses Problem bei mit osm2pgsql erstellten Datenbanken und bei aus OSM erzeugten Shape-Dateien lösen kann.

PostGis / osm2pgsql

osm2pgsql überträgt zwar Attribute der äußeren Wege, also der Wege mit Rolle outer, auf das für die Relation erzeugte Polygon, für den Knoten mit der Rolle admin_centre wird nichts vergleichbares gemacht. Es wird auch kein Link vom Polygon zum Knoten erzeugt. Die im Slim-Modus von osm2pgsql erzeugte Tabelle <prefix>_rels enthält zwar in der Spalte members sämtliche Mitglieder der Relation und deren OSM-ID, aber leider in einer nicht direkt und effizient mit SQL auswertbaren Form. Beispiel:

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}


Die Member der Relation werden also in einem Array abgelegt, zwei aufeinander folgende Array-Elemente pro Member. Mit einer kleinen Funktion kann man die Node-Id des Admin_Centre extrahieren:

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$

Damit kann man nun die Polygone der Verwaltungsgrenzen zusammen mit den Attributen des Admin_Centre abfragen:

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 ;

Je nach Anwendungsfall und Performanceanforderung kann man damit auch die benötigten Attribute in die jeweils andere Tabelle übertragen oder eine Tabelle mit der Zuordnung von Knoten zu Relationen erstellen:

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');

Shape-Dateien

Hierfür wird die Osmium-Bibliothek, das darauf basierende Javascript-Tool osmjs und QGis benutzt. Mit osmjs können auf einfache Weise Shape-Dateien aus den Objekten einer OSM-Datei zu erzeugt werden. Wie bei osm2pgsql werden dabei zwar Polygone aus Grenzrelationen erstellt, aber ohne Bezug zum admin_centre. Es ist jedoch möglich, eine CSV-Datei zu erstellen, welche diesen Bezug enthält:

/*
  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");
}

Das Skript erstellt eine Shape-Datei mit den Grenzpolygonen, eine Shape-Datei mit den Place-Knoten und eine CSV-Datei, welche die Ids der Grenzpolygone und der zugehörigen Place-Knoten enthält. Dabei wird berücksichtigt, dass osmjs den Polygonen eine Pseudo-ID mit dem Wert [2 * OSM-ID der Relation + 1 zuweist.

Diese drei Dateien lädt man als Layer in QGis. Dann verknüpft man den CSV-Daten-Layer über die Knoten-ID mit dem Knoten-Layer und anschliessend den Polygon-Layer über die Pseudo-ID mit dem Knoten-Layer. Will man die Daten mit Tilemill / Mapnik verenden, kann man die Polygon-Layer als Shape-Datei speichern.

Themen: