User:CMartin/Nominatim-Ersatz

From OpenStreetMap Wiki
Jump to: navigation, search

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();
?>

Das Ortungs-Skript

TBC