User:CMartin/Nominatim-Ersatz
Prolog
Wenn man etwas programmiert und zeitgleich in anderen Projekten, wie OSM hängt, kommen einen Ideen, die beide Projekte verbinden. Diese Ideen werden angegangen und stellen sich oft früher oder später als unzumutbar oder einfach unmöglich heraus. Manchmal entstehen kleine Dinge, die man dann aber aus Eitelkeit oder Angst doch nicht der breiten Öffentlichkeit zugänglich macht. Und, sagen wir in einem von tausend Fällen, passiert das, was man sich bei jeder Idee erhofft. Es reift zu einem Produkt, worauf man andere Nutzer los lassen kann.
Das hiesige Programm hat eine, OSM völlig ausschließende Idee. Eine Terminübersicht. Da Terminen ja oft ein Ort zugeordnet werden, dachte ich mir, warum dem Nutzer, der vllt. nicht weiß, wo das ist, eine Karte anzeigen (und dann vllt. noch die Adresse oder andere Zusatzdaten, wie Geschoss, Rollstuhlfähigkeit oder Raumkapazität). Da viele Orte in OSM eingetragen sind (von wem wohl), dachte ich erstmal ans naheliegende, an Nominatim. Ich bastelte ein kleines Skript, was die ungefähr 250 Orte einmal täglich abfragte und die Koordinaten und die Adresse in einer DB speichert. Leider gabs da ein paar Probleme[1]... Daher kramte ich ein recht weit gereiftes, aber stillgelegtes Projekt raus und fingerte einige dort die wichtigen Programmteile heraus. Leider war das alte System ohne Doku auf einem anderen OS entwickelt worden. Daher folgt nun die Doku (so weit ich mich noch erinnere).
Das System
- vServer im Beta-Test bei EUServ
- 10GB Speicher
- 500MB RAM
Die Software
- CentOS 5.5
- php-5.3
- war bereits aufgespielt, da ich das ZendFramework mit GData-Funktionalität nutze
- Installiert über epel & remi
- mysql-5.1
- war bereits aufgespielt, da ich das ZendFramework mit GData-Funktionalität nutze
- Installiert über epel & remi
- postgresql 8.4
- da osm2pgsql Funktionalitäten von PostgreSQL 8.2 verwendet, CentOS aber nur die 8.1 im Repo hat, musste ein eigenes Repo her. Auf [2] wurde ich fündig. Ich musste in der /etc/yum.repo.d/CentOS-Base.repo ein exclude=postgresql* einfügen. Außerdem musste ich alle bisherigen PostgreSQL-Dinge deinstallieren (yum remove postgresql*).
- protobuf-c (min. 0.14)
cd /tmp wget http://protobuf-c.googlecode.com/files/protobuf-c-0.14.tar.gz tar xfv http://protobuf-c.googlecode.com/files/protobuf-c-0.14.tar.gz cd protobuf-c-0.14 ./configure make make install
- Muss unbedingt VOR osm2pgsql installiert werden! Evtl. wird das Paket protobuf von nöten sein. Einfach installieren mit
yum install protobuf
Datenimport
Vorbereitung
Erstmal (einmalig) muss ein Nutzer und eine Datenbank erstellt werden und die DB muss auf unseren Import vorbereitet werden.
su - postgres createuser osm createdb -O osm osm
Nun müssen wir ein paar Skripte anlaufen lassen, um die Datenbank vorzubereiten. Ich hab sie bei Nominatim/Installation abgeguckt:
createlang plpgsql osm cat /usr/share/pgsql/contrib/_int.sql | psql osm cat /usr/share/pgsql/contrib/pg_trgm.sql | psql osm cat /usr/share/pgsql/contrib/lwpostgis.sql | psql osm cat /usr/share/pgsql/contrib/spatial_ref_sys.sql | psql osm
Bevor der Import beginnt brauchen wir noch ein paar Tabellen
psql osm create table planet_osm_line (); create table planet_osm_point (); create table planet_osm_polygon (); create table planet_osm_roads (); create table planet_osm_ways (); create table planet_osm_rels (); create table planet_osm_nodes ();
Folgendes ist nur für das eigene Skript:
create table th_osm_line (); create table th_osm_point (); create table th_osm_polygon (); create table th_osm_roads (); create table th_osm_ways (); create table th_osm_rels (); create table th_osm_nodes (); \q
Das ganze kommt noch aus einer Zeit, wo ich die Tabellen für das Fertige Programm genutzt habe. Da ist es nicht sinnvoll, wenn dem User während des Imports Daten fehlen. Deshalb werden die Daten nach dem Import und der Nachbearbeitung in die th_*-Tabellen umkopiert. th hab ich gewählt, weil ich die Thüringen.osm einlese...
Um alle gewünschten Daten zu bekommen, müssen wir noch die Style-Datei ('/usr/local/share/osm2pgsql/default.style') anpassen. So z.B.:
# OsmType Tag DataType Flags node,way note text delete # These tags can be long but are useless for rendering node,way source text delete # This indicates that we shouldn't store them node,way access text linear node,way addr:city text polygon node,way addr:country text polygon node,way addr:housename text polygon node,way addr:housenumber text polygon node,way addr:interpolation text polygon node,way addr:postcode text polygon node,way addr:street text polygon node,way addr:suburb text polygon node,way admin_level text linear node,way aerialway text linear node,way aeroway text polygon node,way amenity text nocache,polygon node,way area text # hard coded support for area=1/yes => polygon is in osm2pgsql node,way barrier text linear node,way bicycle text nocache node,way brand text linear node,way bridge text linear node,way boundary text polygon node,way building text polygon node,way building:level text polygon node,way building:capacity text polygon node capital text linear node,way capacity text linear node,way construction text linear node,way covered text linear node,way cutting text linear node,way denomination text linear node,way description text polygon node,way description:1 text polygon node,way description:2 text polygon node,way description:3 text polygon node,way disused text linear node ele text linear node,way email text polygon node,way embankment text linear node,way fax text polygon node,way foot text linear node,way highway text linear node,way historic text polygon node,way horse text linear node,way junction text linear node,way landuse text polygon node,way layer text linear node,way leisure text polygon node,way lock text linear node,way man_made text polygon node,way military text polygon node,way motorcar text linear node,way name text polygon node,way name:de text polygon node,way name:en text polygon node,way alt_name text polygon node,way old_name text polygon node,way hist_name text polygon node,way natural text polygon # natural=coastline tags are discarded by a hard coded rule in osm2pgsql node,way oneway text linear node,way operator text linear node,way phone text polygon node poi text node,way power text polygon node,way power_source text linear node,way place text linear node,way railway text linear node,way ref text polygon node,way religion text nocache node,way route text linear node,way service text linear node,way shop text polygon node,way sport text polygon node,way surface text linear node,way telephon text polygon node,way toll text linear node,way tourism text polygon way tracktype text linear node,way tunnel text linear node,way url text polygon node,way waterway text polygon node,way website text polygon node,way wetland text polygon node,way wheelchair text polygon node,way width text linear node,way wikipedia text polygon node,way wikipedia:de text polygon node,way wikipedia:en text polygon node,way wood text linear node,way z_order int4 linear # This is calculated during import way way_area real # This is calculated during import
Import
Und nun der eigentliche Import. Da hier verschiedene Sachen zum Einsatz kommen, führe ich es als .sh-Skript aus. Ich führe das Skript als User postgres aus
su - postgres sh osm.sh
osm.sh
#!/bin/sh # Ordner, wo wir arbeiten cd /tmp/ # Thüringendatei wget http://download.geofabrik.de/osm/europe/germany/thueringen.osm.pbf # Import osm2pgsql -r pbf -S /usr/local/share/osm2pgsql/default.style -lsc -d osm -C 500 /tmp/thueringen.osm.pbf # Nachbearbeitung php /var/www/html/osm/interpolation.php php /var/www/html/osm/copy_table.php # Datei löschen (Speicherplatz usw.) rm thueringen.osm.bz2
interpolation.php
Die Datei berechnet zu Adressen ohne Straße den Straßennamen, dann werden Interpolationen berechnet. Das ganze kann etwas dauern ;-).
<?php //Interpolationen und fehlende Adressen //prüfen, ob Hausnummer zum Interpolationstyp gehört function is_inter_point($nr,$inter) { if(($inter=='all')&&(preg_match('/^\d+$/',$nr))) return 1; elseif(($inter=='even')&&(preg_match('/^\d+$/',$nr))&&($nr % 2 == 0)) return 1; elseif(($inter=='odd')&&(preg_match('/^\d+$/',$nr))&&($nr % 2 == 1)) return 1; elseif(($inter=='alphabetic')&&(preg_match('/^\s*\d+\s*[a-zA-Z]\s*$/',$nr))) return 1; else return 0; } //Interpolierung function interpol($start,$end,$line,$intertype) { $new = array(); if($intertype=='all') { if($start['housenumber']>$end['housenumber']) { $anzahl = $start['housenumber'] - $end['housenumber']; $anzahl1 = $anzahl; $i=1; for($anzahl;$anzahl>1;$anzahl--) { $query = "INSERT INTO planet_osm_point (\"addr:housenumber\", \"addr:street\", \"addr:suburb\", \"addr:city\", \"addr:postcode\", way) VALUES ('". ($start['housenumber']+$i)."','". $start['street']."','". $start['suburb']."','". $start['city']."','". $start['postcode']."',". "ST_Line_Interpolate_Point(st_linefromtext('LINESTRING(".implode(',',$line).")',4326),".$i/$anzahl1."))"; pg_query($query); $i++; } } else { $anzahl = $end['housenumber'] - $start['housenumber']; $anzahl1 = $anzahl; $i=1; for($anzahl;$anzahl>1;$anzahl--) { $query = "INSERT INTO planet_osm_point (\"addr:housenumber\", \"addr:street\", \"addr:suburb\", \"addr:city\", \"addr:postcode\", way) VALUES ('". ($start['housenumber']+$i)."','". $start['street']."','". $start['suburb']."','". $start['city']."','". $start['postcode']."',". "ST_Line_Interpolate_Point(st_linefromtext('LINESTRING(".implode(',',$line).")',4326),".$i/$anzahl1."))"; pg_query($query); $i++; } } } elseif(($intertype=='even')||($intertype=='odd')) { if($start['housenumber']>$end['housenumber']) { $anzahl = ($start['housenumber'] - $end['housenumber'])/2; $anzahl1 = $anzahl; $i=1; for($anzahl;$anzahl>1;$anzahl--) { $query = "INSERT INTO planet_osm_point (\"addr:housenumber\", \"addr:street\", \"addr:suburb\", \"addr:city\", \"addr:postcode\", way) VALUES ('". ($start['housenumber']-($i*2))."','". $start['street']."','". $start['suburb']."','". $start['city']."','". $start['postcode']."',". "ST_Line_Interpolate_Point(st_linefromtext('LINESTRING(".implode(',',$line).")',4326),".$i/$anzahl1."))"; pg_query($query); $i++; } } else { $anzahl = ($end['housenumber'] - $start['housenumber'])/2; $anzahl1 = $anzahl; $i=1; for($anzahl;$anzahl>1;$anzahl--) { $query = "INSERT INTO planet_osm_point (\"addr:housenumber\", \"addr:street\", \"addr:suburb\", \"addr:city\", \"addr:postcode\", way) VALUES ('". ($start['housenumber']+($i*2))."','". $start['street']."','". $start['suburb']."','". $start['city']."','". $start['postcode']."',". "ST_Line_Interpolate_Point(st_linefromtext('LINESTRING(".implode(',',$line).")',4326),".$i/$anzahl1."))"; pg_query($query); $i++; } } } elseif($intertype=='alphabetic') { $number = array( 1 => 'a', 2 => 'b', 3 => 'c', 4 => 'd', 5 => 'e', 6 => 'f', 7 => 'g', 8 => 'h', 9 => 'i', 10 => 'j', 11 => 'k', 12 => 'l', 13 => 'm', 14 => 'n', 16 => 'o', 17 => 'p', 18 => 'q', 19 => 'r', 20 => 's', 21 => 't', 22 => 'v', 23 => 'w', 24 => 'x', 25 => 'y', 26 => 'z' ); // $alpha = array_flip($number); preg_match('/([a-zA-Z])/',$start['housenumber'],$match_start); preg_match('/([a-zA-Z])/',$end['housenumber'],$match_end); preg_match('/([0-9]*)/',$start['housenumber'],$match_start_nr); preg_match('/([0-9]*)/',$end['housenumber'],$match_end_nr); if($match_end_nr[1]==$match_start_nr[1]) { $anzahl = $alpha[strtolower($match_start[1])]-$alpha[strtolower($match_end[1])]; if($alpha[strtolower($match_start[1])]>$alpha[strtolower($match_end[1])]) { $anzahl = $alpha[strtolower($match_start[1])]-$alpha[strtolower($match_end[1])]; $anzahl1 = $anzahl; $i=1; for($anzahl;$anzahl>1;$anzahl--) { $query = "INSERT INTO planet_osm_point (\"addr:housenumber\", \"addr:street\", \"addr:suburb\", \"addr:city\", \"addr:postcode\", way) VALUES ('". ($match_start_nr[1].$number[$alpha[strtolower($match_start[1])]-$i])."','". $start['street']."','". $start['suburb']."','". $start['city']."','". $start['postcode']."',". "ST_Line_Interpolate_Point(st_linefromtext('LINESTRING(".implode(',',$line).")',4326),".$i/$anzahl1."))"; pg_query($query); $i++; } } else { $anzahl = $alpha[strtolower($match_end[1])]-$alpha[strtolower($match_start[1])]; $anzahl1 = $anzahl; $i=1; for($anzahl;$anzahl>1;$anzahl--) { $query = "INSERT INTO planet_osm_point (\"addr:housenumber\", \"addr:street\", \"addr:suburb\", \"addr:city\", \"addr:postcode\", way) VALUES ('". ($match_start_nr[1].$number[$alpha[strtolower($match_start[1])]+$i])."','". $start['street']."','". $start['suburb']."','". $start['city']."','". $start['postcode']."',". "ST_Line_Interpolate_Point(st_linefromtext('LINESTRING(".implode(',',$line).")',4326),".$i/$anzahl1."))"; pg_query($query); $i++; } } } } } //PostgreSQL $db2 = pg_connect('dbname=osm user=postgres'); //Fehlende Straßennamen ergänzen $result = pg_query("select st_astext(way) as way, osm_id as id from planet_osm_point where \"addr:street\" IS NULL AND (\"addr:housenumber\" IS NOT NULL OR \"addr:housename\" IS NOT NULL)"); while($number = pg_fetch_assoc($result)) { $result_street = pg_query("SELECT name FROM planet_osm_roads WHERE ST_DWithin(way,GeomFromText('".$number['way']."',4326), 200) AND name != AND highway IN ('residential','living_street','primary','secondary','tertiary','unclassified','road','service','pedestrian') ORDER BY ST_distance(way, GeomFromText('".$number['way']."',4326)) limit 1;"); $name = pg_fetch_assoc($result_street); pg_query("UPDATE planet_osm_point SET \"addr:street\" = '".$name['name']."' WHERE \"osm_id\" = '".$number['id']."'"); //print $name['name']." ".$number['number']."\n"; } $result = pg_query("select st_astext(way) as way, osm_id as id from planet_osm_line where \"addr:street\" IS NULL AND (\"addr:housenumber\" IS NOT NULL OR \"addr:housename\" IS NOT NULL)"); while($number = pg_fetch_assoc($result)) { $result_street = pg_query("SELECT name FROM planet_osm_roads WHERE ST_DWithin(way,GeomFromText('".$number['way']."',4326), 200) AND name != AND highway IN ('residential','living_street','primary','secondary','tertiary','unclassified','road','service','pedestrian') ORDER BY ST_distance(way, GeomFromText('".$number['way']."',4326)) limit 1;"); $name = pg_fetch_assoc($result_street); pg_query("UPDATE planet_osm_line SET \"addr:street\" = '".$name['name']."' WHERE \"osm_id\" = '".$number['id']."'"); //print $name['name']." ".$number['number']."\n"; } $result = pg_query("select st_astext(way) as way, osm_id as id from planet_osm_polygon where \"addr:street\" IS NULL AND (\"addr:housenumber\" IS NOT NULL OR \"addr:housename\" IS NOT NULL)"); while($number = pg_fetch_assoc($result)) { $result_street = pg_query("SELECT name FROM planet_osm_roads WHERE ST_DWithin(way,GeomFromText('".$number['way']."',4326), 200) AND name != AND highway IN ('residential','living_street','primary','secondary','tertiary','unclassified','road','service','pedestrian') ORDER BY ST_distance(way, GeomFromText('".$number['way']."',4326)) limit 1;"); $name = pg_fetch_assoc($result_street); pg_query("UPDATE planet_osm_polygon SET \"addr:street\" = '".$name['name']."' WHERE \"osm_id\" = '".$number['id']."'"); //print $name['name']." ".$number['number']."\n"; } //Alle Interpolationen $result = pg_query("select \"addr:interpolation\" as inter, st_astext(way) as street from planet_osm_line where \"addr:interpolation\" IS NOT NULL"); while($interpolation = pg_fetch_assoc($result)) { $linepoints = array(); $start = array(); //Punkte abfragen //LINESTRING(10.9327161 50.6831537,10.9330285 50.6831367) preg_match_all("$\w+\(([^)]*)\)$",$interpolation['street'],$match); if(count($match)>1) { $koord = explode(',',$match[1][0]); foreach ($koord as $a) { //Punktabfrage $result2 = pg_query("select \"addr:housenumber\" as housenumber,\"addr:postcode\" as postcode,\"addr:street\" as street,\"addr:city\" as city,\"addr:suburb\" as suburb, st_astext(way) as koord from planet_osm_point where \"addr:housenumber\" IS NOT NULL AND way = st_pointfromtext('POINT(".$a.")',4326)"); if(pg_num_rows($result2)==1) { $point = pg_fetch_assoc($result2); //Wenn bereits Startwert festgelegt & Punkt wichtig if((count($start)>0)&&(is_inter_point($point['housenumber'],$interpolation['inter']))) { //Punkt hinzufügen $linepoints[] = $a; //Interpolieren interpol($start,$point,$linepoints,$interpolation['inter']); // Neuen Startwert festlegen $start = $point; //Liniencache leeren $linepoints = array($a); } elseif((count($start)==0)&&(is_inter_point($point['housenumber'],$interpolation['inter']))) { //Startwert festlegen $start = $point; //Liniencache leeren $linepoints = array($a); } else { //unwichtiger Punkt $linepoints[] = $a; } } else { $linepoints[] = $a; } } } } pg_close; ?>
copy_table.php
Das Skript kopiert einfach nur die Tabellen ins Produktivsystem
<?php /* * Tabellen kopieren */ $db2 = pg_connect('dbname=osm user=postgres'); // Tabelle erstellen pg_query("DROP TABLE th_osm_point,th_osm_line,th_osm_polygon,th_osm_nodes,th_osm_rels,th_osm_ways,th_osm_roads"); pg_query("CREATE TABLE th_osm_point AS (SELECT * FROM planet_osm_point)"); pg_query("CREATE TABLE th_osm_line AS (SELECT * FROM planet_osm_line)"); pg_query("CREATE TABLE th_osm_polygon AS (SELECT * FROM planet_osm_polygon)"); pg_query("CREATE TABLE th_osm_nodes AS (SELECT * FROM planet_osm_nodes)"); pg_query("CREATE TABLE th_osm_rels AS (SELECT * FROM planet_osm_rels)"); pg_query("CREATE TABLE th_osm_ways AS (SELECT * FROM planet_osm_ways)"); pg_query("CREATE TABLE th_osm_roads AS (SELECT * FROM planet_osm_roads)"); //Zum Speicher sparen folgendes auskommentieren: //pg_query("DROP TABLE planet_osm_point,planet_osm_line,planet_osm_polygon,planet_osm_nodes,planet_osm_rels,planet_osm_ways,planet_osm_roads"); pg_close(); ?>