x osm-Dateien zu einer Datei zusammenfassen?

Ich hätte da ein superhochaufwendiges™ Skript:


#!/usr/bin/perl
#-------------------------------------------------------------------------------
#	$Id: osmmerge.pl,v 1.7 2013/02/11 22:15:45 wolf Exp $
#-------------------------------------------------------------------------------
#	Aufruf:
#		perl osmmerge.pl *.osm >result
#
#	Verschmilzt beliebig viele OSM-Dateien.
#	Bei gleichem Typ und gleicher Id wird das Objekt mit der hoechsten Version
#	Sind auch die Versionen gleich, das Objekt aus der "linkesten" Datei.
#-------------------------------------------------------------------------------

use strict;
use utf8;

#-------------------------------------------------------------------------------
#	Ausgabe in UTF-8
#-------------------------------------------------------------------------------

binmode STDOUT, ':utf8';

#-------------------------------------------------------------------------------
#	Loese "*.ext" pattern auf und sammle die Dateinamen im array @name
#-------------------------------------------------------------------------------

my @name = ();
foreach my $arg (@ARGV) {

	push (@name, glob($arg));
}

#-------------------------------------------------------------------------------
#	Zur Kontrolle zeige die Liste der Dateien.
#-------------------------------------------------------------------------------

warn "Bearbeite Dateien: ", join (', ', @name), ".\n";

#-------------------------------------------------------------------------------
#	Diese Arrays haben jeweils einen Eintrag je Eingabedatei
#-------------------------------------------------------------------------------

my @handle;
my @entry;
my @osmid;
my @version;
my $index;

#-------------------------------------------------------------------------------
#	Je Eingabedatei
#-------------------------------------------------------------------------------

for ($index=0; $index<@name; $index++) {

	#----------------------------------------------------------------------
	#	Oeffne die Datei und nehme als UTF-8 Codierung an
	#----------------------------------------------------------------------

	my $handle;

	unless (open ($handle, '<:utf8', $name[$index])) {

		#--------------------------------------------------------------
		#	Meldung wenn Oeffnen gescheitert
		#--------------------------------------------------------------

		warn "Kann Datei \"$name[$index]\" nicht oeffnen: $!\n";

		#--------------------------------------------------------------
		#	Und Abbruch.
		#	Die "exit" Zeile kann auskommentiert werden, dann
		#	werden fehlende oder nicht lesbare Datein uebersprungen. 
		#--------------------------------------------------------------

		exit 1;
	}

	#----------------------------------------------------------------------
	#	Datei geoeffnet. Lese das <?xml
	#----------------------------------------------------------------------

	$_ = <$handle>;
	unless (m#<\?xml#) {

		warn "Datei \"$name[$index]\": in erster Zeile kein \"<?xml\".\n";
		exit 1;
	}

	#----------------------------------------------------------------------
	#	Lese das <osm
	#----------------------------------------------------------------------

	$_ = <$handle>;
	unless (m#<osm#) {

		warn "Datei \"$name[$index]\": in zweiter Zeile kein \"<osm\".\n";
		exit 1;
	}

	#----------------------------------------------------------------------
	#	Speichere das Filehandle
	#----------------------------------------------------------------------

	$handle[$index] = $handle;

	#----------------------------------------------------------------------
	#	Lese erstes Objekt
	#----------------------------------------------------------------------

	&readEntry ($index);

	#----------------------------------------------------------------------
	#	Ueberspringe unbekannte Objekte
	#----------------------------------------------------------------------

	while ($entry[$index] !~ /^\s*<(changeset|node|way|relation)/) {

		&readEntry ($index);
		last if $entry[$index] eq '';
	}
}

#-------------------------------------------------------------------------------
#	Je Eingabedatei ein Element gelesen, also startbereit.
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
#	Header der Ausgabedatei
#-------------------------------------------------------------------------------

print "<?xml version='1.0' encoding='UTF-8'?>\n<osm version=\"0.6\" generator=\"osmmerge.pl\">\n";

#-------------------------------------------------------------------------------
#	Bearbeite bis alle Eingabedateien voll bearbeitet sind.
#-------------------------------------------------------------------------------

for(;;) {

	#-----------------------------------------------------------------------
	#	Finde die kleinste Id.
	#	Da mehrere Eingabedatein dieses Objekt enthalten koennen,
	#	sammle die Indizes dieser Dateien im Array @index.
	#-----------------------------------------------------------------------

	my @index;

	#-----------------------------------------------------------------------
	#	Initialisiere "bisher kleinste gefundene Id" auf sehr hohen Wert
	#-----------------------------------------------------------------------

	my $osmid = '~';

	#-----------------------------------------------------------------------
	#	Iteriere ueber die Eingabedateien:
	#-----------------------------------------------------------------------

	for ($index=0; $index<@name; $index++) {

		#---------------------------------------------------------------
		#	Falls Objekt in Eingabedatei GROESSER ist als das kleinste
		#	bisher gefundene, ignoriere das Objekt
		#---------------------------------------------------------------

		next if $osmid[$index] gt $osmid;

		#---------------------------------------------------------------
		#	Falls Objekt in Eingabedatei KLEINER ist als das kleinste
		#	bisher gefundene, merke dessen Id und verwerfe alle bisher
		#	gefundenen Indizes.
		#---------------------------------------------------------------

		if ($osmid[$index] lt $osmid) {

			$osmid = $osmid[$index];
			@index=();
		}

		#---------------------------------------------------------------
		#	Aspeichere aktuellen Index im Indexsammler.
		#---------------------------------------------------------------

		push (@index, $index);
	}

	#-----------------------------------------------------------------------
	#	Wenn kein Index gefunden, sind alle Dateien am Ende angelangt,
	#	und wir beenden die Schleife.
	#-----------------------------------------------------------------------

	last unless @index;

	#-----------------------------------------------------------------------
	#	Suche die groesste Version und lade zu den gefundenen Indizes
	#	das naechste Objekt aus der jeweiligen Datein.
	#
	#	version:	bisher gefundene hoechste Version
	#	entry:		das zugehoerige Objekt.
	#-----------------------------------------------------------------------

	my $version = -1;
	my $entry;

	#-----------------------------------------------------------------------
	#	Fuer alle Eintrage im Index:
	#-----------------------------------------------------------------------

	foreach $index (@index) {

		#---------------------------------------------------------------
		#	Wenn die Version zum jeweiligen GROESSER ist als die
		#	groesste bisher bekannte, speichere diese Version
		#	und den zugehoerigen XML-Text.
		#---------------------------------------------------------------

		if ($version[$index]>$version) {

			$version = $version[$index];
			$entry = $entry[$index];
		}

		#---------------------------------------------------------------
		#	Sodann ueberschreibe das bearbeitete Element durch
		#	neu eingelesenes.
		#---------------------------------------------------------------

		readEntry($index);
	}

	#-----------------------------------------------------------------------
	#	Gebe den XML-Texte des Elementes mit der hoechsten Version aus.
	#-----------------------------------------------------------------------

	print $entry;
}

#-------------------------------------------------------------------------------
#	Alle Eingabedateien bis zum Ende bearbeitet. Schliesse das "<osm>" ab.
#-------------------------------------------------------------------------------

print "</osm>\n";

exit 0;

#-------------------------------------------------------------------------------
#	Lese das naechste Objekt zu einem bestimmten Index
#-------------------------------------------------------------------------------

sub readEntry {

	#-----------------------------------------------------------------------
	#	Index aus Parameterliste
	#-----------------------------------------------------------------------

	my ($index) = @_;

	#-----------------------------------------------------------------------
	#	Bestimme das Filehandle und lese die erste (oder einzige) Zeile
	#-----------------------------------------------------------------------

	my $handle = $handle[$index];
	my $entry = <$handle>;

	#-----------------------------------------------------------------------
	#	Ueberspringe Leerzeilen
	#-----------------------------------------------------------------------

	while ($entry =~ /^\s+$/) { $entry = <$handle>; }

	#-----------------------------------------------------------------------
	#	Falls das Element in der Zeile NICHT beendet ist...
	#-----------------------------------------------------------------------

	unless (($entry =~ m#/>\s*$#) || ($entry =~ m#</\w+>\s*$#) || $entry eq '') {

		#---------------------------------------------------------------
		#	Lese weitere Zeilen
		#---------------------------------------------------------------

		do {
			$entry .= $_ = <$handle>;
		} while ($_ && !m#^\s*</#);

		#---------------------------------------------------------------
		#	bis das Ende-Element gefunden ist.
		#---------------------------------------------------------------
	}

	#-----------------------------------------------------------------------
	#	Speichere den XML-Code des Objektes unter dem angegeben Index.
	#-----------------------------------------------------------------------

	$entry[$index]=$entry;

	#-----------------------------------------------------------------------
	#	Ende der Datei-Marker, wenn keiner der erwarteten Typen gefunden.
	#-----------------------------------------------------------------------

	my $type = '~';

	#-----------------------------------------------------------------------
	#	Bestimme den Objekt-Typ und ordne ihm einen Kennbuchstaben zu.
	#	Die Objekte sind zuerst nach Kennbuchstaben, dann nach Id sortiert.
	#-----------------------------------------------------------------------

	$type = "a" if $entry =~ /^\s*<changeset/;
	$type = "b" if $entry =~ /^\s*<node/;
	$type = "c" if $entry =~ /^\s*<way/;
	$type = "d" if $entry =~ /^\s*<relation/;

	#-----------------------------------------------------------------------
	#	Suchen den Id-Wert im XML: id="###" oder id='###'
	#	Die Id kann auch negativ sein.
	#-----------------------------------------------------------------------

	my $id = $entry =~ /id=["'](-?[1-9]\d*)["']/ ? $1 * 1.0 : 0;

	#-----------------------------------------------------------------------
	#	Ich hoffe, dass maxbe das hier nicht sieht.
	#-----------------------------------------------------------------------
	#	Konstruiere aus Typ und Id eine "osmid":
	#	Die Id wird mit einem Offset von 5*10^9 versehen,
	#	mit 10 Stellen und fuehrenden Nullen formatiert,
	#	und dann an den Typ angehaengt.
	#	Damit erzeugt ein Stringvergleich auf der "osmid"
	#	die gewuenschte Sortierreihenfolge.
	#
	#-----------------------------------------------------------------------

	$osmid[$index]=sprintf '%s-%010.0f', $type, $id + 5e9;

	#-----------------------------------------------------------------------
	#	Suchen den Versions-Wert im XML: version="###" oder version='###'
	#	Der Versionswert kann auch fehlen.
	#-----------------------------------------------------------------------

	$version[$index] = $entry =~ /version=["']([1-9]\d*)["']/ ? int $1 : 0;
}

#-------------------------------------------------------------------------------
#	$Id: osmmerge.pl,v 1.7 2013/02/11 22:15:45 wolf Exp $
#-------------------------------------------------------------------------------
__END__

Have fun.

Gruß Wolf

Edit: jetzt auch grob ungehörige Quotes und überaus ungehörige negative ids und gänzlich ungehörige fehlende Versionsnummern und Leerzeilen und Kopfzeilen und was auch immer erlaubt. Und mit ein paar Zeilen Kommentar ergänzt.


$ perl ../../bin/osmmerge.pl Oberpfalz.osm Mittelfranken.osm > result
$ cat result 
<?xml version='1.0' encoding='UTF-8'?>
<osm version="0.6" generator="osmmerge.pl">
</osm>
$

Schade :frowning:

Nahmd,

Da das Skript keinen XML-Parser enthält, geht es von einem ganz bestimmten Format aus.
Zeig mal die ersten paar Zeilen von Deinen Dateien.

Falls die Dateien eine BBox definieren → entsprechenden Kommentar im Skript beachten.

Gruß Wolf


$ head Oberpfalz.osm 
<?xml version='1.0' encoding='UTF-8'?>
<osm version='0.6' upload='false' generator='JOSM'>
  <node id='-5191118' visible='true' lat='49.4648038658' lon='11.7813332341' />
  <node id='-5191116' visible='true' lat='49.4650386866' lon='11.7815495847' />
  <node id='-5191114' visible='true' lat='49.4653578758' lon='11.7818638913' />
  <node id='-5191112' visible='true' lat='49.4661894486' lon='11.7822815027' />
  <node id='-5191110' visible='true' lat='49.4663091778' lon='11.7823718007' />
  <node id='-5191108' visible='true' lat='49.4664238236' lon='11.7825312441' />
  <node id='-5191106' visible='true' lat='49.4664740729' lon='11.7827571616' />
  <node id='-5191104' visible='true' lat='49.4664809774' lon='11.7828104215' />
$
$ head Fuerth.osm
<?xml version='1.0' encoding='UTF-8'?>
<osm version="0.6" generator="osmconvert 0.3">
        <bounds minlat="49.35435" minlon="10.68002" maxlat="49.54901" maxlon="11.02595"/>
        <node id="15432251" lat="49.4167003" lon="10.9858232" version="3" timestamp="2009-10-23T21:34:46Z" changeset="2933199" uid="13363" user="CaptainCrunch">
                <tag k="name" v="Unterasbach"/>
                <tag k="railway" v="halt"/>
        </node>
        <node id="15432252" lat="49.4169583" lon="10.9648829" version="5" timestamp="2009-11-14T17:08:43Z" changeset="3114941" uid="83876" user="BurnyB">
                <tag k="name" v="Oberasbach"/>
                <tag k="railway" v="halt"/>

EDIT:
Ok, wenn ich die bbox-Zeile rausnehme, kann ich Fürth & Nürnberg verschmelzen und stimmt mit osmconvert’s merge überein:

23284454 Feb 9 18:05 Fürth.osm
107875070 Feb 9 18:05 Nürnberg.osm
130660206 Feb 9 18:07 merge_osmconvert.osm
130660202 Feb 9 18:06 result
$ diff merge_osmconvert.osm result |head
2c2
<

$

Nahmd,

Die Datei hat die falschen Quotes. Nur ["] ist gehörig, ['] ist grob ungehörig. Schäm Dich! :stuck_out_tongue:

Gruß Wolf

Edit: Ich hab’ schnell 'ne Version gemacht, die beides frisst.

Auch mit der grob ungehörigen Version lassen sich die JOSM-Mittelfranken.osm & Oberpfalz nicht mergen:


$ cat result 
<?xml version='1.0' encoding='UTF-8'?>
<osm version="0.6" generator="osmmerge.pl">
</osm>

Nahmd,

Du hast negative Node-Ids. Die gibts in der OSM-Datenbank nicht.
Werden jetzt aber auch gefressen.

Gruß Wolf

Ja, das ist ganz normal:
JOSM vergibt negative IDs für Punkte (auch Wege), die man im Editor neu erstellt hat und noch nicht hochgeladen wurden.

EDIT:
Auch mit der überaus ungehörigen Version klappt es nicht.

Nahmd,

Ok. Ich speicher schon mal aus JOSM, aber nur um das Zeug kurz danach wieder zu laden. Reingeguckt habe ich noch nie. Und wie gesagt, die negativen Zahlen geh jetzt auch. Aber nur, solange das Zeug in der Reihenfolge node, way, relation kommt und innerhalb der jeweiligen Gruppe nach ids sortiert ist: -2, -1, 1, 2, …

.oO( und auch nur bis id=5 Milliarden. Der danach nötige Upgrade zur Pro-Version wird schweineteuer werden. )

Gruß Wolf

Nö, der bug muss woanders sein (für osmconvert ist das auch in der “Lang-Versoin” kein Problem"):


$ cat n.osm 
<?xml version='1.0' encoding='UTF-8'?>
<osm version='0.6' upload='false' generator='JOSM'>
  <node id='-408038' visible='true' lat='49.1861474038' lon='11.0836863015' />
  <node id='-408036' visible='true' lat='49.18607107' lon='11.0835941604' />
</osm>
$ cat p.osm 
<?xml version='1.0' encoding='UTF-8'?>
<osm version='0.6' upload='false' generator='JOSM'>
  <node id='-191118' visible='true' lat='49.4648038658' lon='11.7813332341' />
  <node id='-191116' visible='true' lat='49.4650386866' lon='11.7815495847' />
</osm>
$ perl ../osmmerge.pl n.osm p.osm > result
$ cat result
<?xml version='1.0' encoding='UTF-8'?>
<osm version="0.6" generator="osmmerge.pl">
</osm>
$ osmconvert n.osm p.osm > result2
$ cat result2
<?xml version='1.0' encoding='UTF-8'?>
<osm version="0.6" generator="osmconvert 0.7N">
        <node id="-408038" lat="49.1861474" lon="11.0836863"/>
        <node id="-408036" lat="49.186071" lon="11.0835941"/>
        <node id="-191118" lat="49.4648038" lon="11.7813332"/>
        <node id="-191116" lat="49.4650386" lon="11.7815495"/>
</osm>

Nahmd,

Hmpf! Jetzt fehlen die Versionsnummern.

Code ist so geändert, dass er auch mit fehlenden Versionsnummern zurechtkommt.
(Obwohl das langsam mit meinem Gewissen nimmer zu vereinbaren ist.)

Gruß Wolf

Vielen Dank für Deine Bemühungen :smiley:

Das ist ja jetzt wie 1806: Mfr und Opf. glücklich vereint.

Du kommst aus dem Rheinland?
Aber nicht zufällig aus der Rheinpfalz, die war ja auch mal bayerisch :wink:

Nahmd,

Hehe!
Und? Schon mit 200 Dateien gleichzeitig probiert?

.oO( darf auch 200× die gleiche sein :stuck_out_tongue: )

Yepp. Bayrisch-kölsche Coproduktion.

Nö.
Aber ich hab immer wieder mal die Provinzler aus dem Rheintal zwischen Köln und Koblenz im Zug sitzen, die in Köln (zumindest glauben sie es) einen draufgemacht haben. Meinen Eindruck von denen verrate ich aber nur bei Anwesenheit meines Anwalts. :confused:

Gruß Wolf

Hallo Jan,

zuerst einmal ist es wichtig, dass die .osm-Dateien nicht geclippt wurden, sondern auch im Grenzbereich ganze Wege enthalten – zumindest was die way-Objekte betrifft. Die zgehörigen außerhalb liegenden Knoten dürfen jeweils fehlen. Die regionalen Extrakte von geofabrik.de erfüllen diese Voraussetzung.

Dann ist es unter Linux ganz einfach:

./osmconvert *.osm -o=../ausgabeverzeichnis/gesamt.osm

Wie du siehst, ist es ratsam für die Ausgabe-OSM-Datei ein anderes Verzeichnis zu wählen, weil sie sonst eventuell als Eingabedatei verwendet wird. Das gäbe ein durcheinander. :slight_smile:

Grüße
Markus

alle in josm laden (vielleicht nicht alle auf einmal) und die ebenen markieren und mergen? :slight_smile:

Hallo Markus,

ja das wäre ja super, wenn der Aufrugf so einfach wäre. Nur leider bekomme ich folgende Fehlermeldung:

osmconvert Error: could not get 183500800 bytes of memory.

Zufällig irgendeine Idee, woran es liegen kann? Die Daten stammen übrigens alle von der Overpass-Api (Node und Way (inkl. Node))

Gruß

Jan

Nahmd,

Sind die Objekte möglicherweise nicht nach Id sortiert?

Dann muss ein Programm die sortieren, um die Doubletten zu finden. Und am einfachsten sortiert es sich, wenn alle Objekte im Speicher liegen. :slight_smile:

Gruß Wolf

Wieviel Speicher steht denn deinem System (bzw. deinem User) zur Verfügung? Ich hatte mal auf einer extrem eingeschränkten Cloud Testinstanz mit 400MB (!) Speicher auch Probleme und musste mit dem Parameter --hash-memory den maximal zu belegenden Speicher solange nach unten schrauben, bis es gepasst hat. Wie das funktioniert ist unter “osmconvert --help” im Abschnitt “Tuning” gut beschrieben.
Edit: Ah, ich sehe gerade, ich hatte das für osmfilter eingesetzt, sollte aber mit osmconvert analog laufen.

Hallo Netzwolf und mmd,

Ne, daran liegt es (glaube ich) nicht, wenn ich nämlich “per hand” (osmconvert x.osm b .osm u.osm -o=xbu.osm) ausführe, klappt es problemlos.

Also das hier ist ein Windows 7 x64 System mit 8gb Arbeitsspeicher. Also ich glaube es müsste eigentlich ausreichend sein. ( Wenn ich mir die Extrakte der GEofabrik merge (D-A-CH) klappt es ja auch und das sind eindeutig mehr Daten :wink: )

Aber ich werde es mal probieren. Vielen Dank für den Hinweis.

Gruß

Jan

Kurzer Blick in http://m.m.i24.cc/osmconvert.c verrät, dass das eigentlich nicht wirklich etwas bringt. Warum?

Pro Datei wird ein konstanter prefetch-Puffer von ~183MB angefordert, der sich auch nicht über Parameter konfigurieren lässt. Spätestens bei 10-15 OSM-Dateien dürfte dann Schluß sein, denn das Binary für Windows ist 32bit, kann also mit ganzen 8GB Speicher nicht wirklich etwas anfangen. Die Grenze dürfte irgendwo zwischen 2GB und 3GB liegen. Da hilft nur ein Aufteilen in kleinere Pakete, oder Marqqs kann das sonst irgendwie korrigieren.