Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Convert OSM to Polygon-File
#!/usr/bin/perl -w
#
# osm2poly.pl - convert an relation .osm file into polygon file on stdout
#
# The input file must be an .osm file with one relation and all its ways
# and nodes. This may be a multipolygon relation or any other relation
# which has closed ways.
# osm2poly.pl can work with mostly unordered relations, i.e. all way
# parts can be at any position in the relation. Only: inner ways must
# not touch outer...
#
# Usage: osm2poly.pl file.osm
# Author: Hanno Hecker <vetinari+osm@ankh-morp.org>
# Version: 0.4
# $Id$
#
use strict;
use Math::Polygon;
use File::Basename;
my $need_name = 0;
my $in_relation = 0;
my $file = shift;
if (!defined $file or $file =~ /^-(h|\?)$/) {
die "$0: Usage: $0 FILE.osm\n",
" convert an relation .osm file into polygon file on stdout\n",
" use '$0 -' to read from stdin\n";
}
if ($file eq '-') {
open FILE, "<&", \*STDIN;
}
else {
open FILE, "<", $file
or die "$0: Failed to open file $file: $!\n";
}
my $name = basename($file, ".osm");
if ($name =~ /^(\d+|-)$/) {
$need_name = 1;
}
my %nodes;
my %ways;
my (@outer, @outer_left);
my (@inner, @inner_left);
my @poly;
my @part;
my $way_id = undef;
my $part_nr = 0;
my $is_outer = 1;
while (<FILE>) {
if (/^\s*<node\s+/) {
my ($id, $lon, $lat);
/lon=(["'])(-?\d+\.\d+)\1/ and $lon = $2;
/lat=(["'])(-?\d+\.\d+)\1/ and $lat = $2;
/id=(["'])(\d+)\1/ and $id = $2;
if (defined $lon and defined $lat and $id) {
$nodes{$id} = [$lon, $lat];
}
}
elsif (/^\s*<way\s+/) {
/id=(["'])(\d+)\1/ and $way_id = $2;
}
elsif (/^\s*<nd\s+ref=(["'])(\d+)\1/) {
die "dangling reference to node $2"
unless exists $nodes{$2};
push @{ $ways{$way_id} }, $nodes{$2}
if defined $way_id;
}
elsif (/^\s*<\/way/) {
$way_id = undef;
}
elsif (/^\s*<relation /) {
$in_relation = 1;
}
elsif (/^\s*<member\s+type=(["'])way\1\s+ref=(["'])(\d+)\2\s+role=(['"])(.*?)\4/) {
die "dangling reference to way $3"
unless exists $ways{$3};
if ($5 eq "" or ($5 eq "outer" or $5 eq "exclave")) {
push @outer, $3;
}
elsif ($3 eq "inner" or $3 eq "enclave") {
push @inner, $3;
}
else {
warn "unknown type '$5' for ref $3\n";
}
}
elsif (/^\s*<\/relation/) {
$in_relation = 0;
}
elsif ($in_relation and $need_name) {
if (/^\s*<tag\s+k=(["'])name\1\s+v=(["'])(.*?)\2/) {
$name = $3;
$name =~ s/\s+/_/g;
}
}
}
#
# connected($first, $second):
# checks if the way $first is connected to the last node of $second
# return values:
# 1 if first node of first way equals last node of second way
# -1 if last node of first way equals last node of second way
# 0 else
sub connected {
my ($first, $second) = @_;
return 1
if ($ways{$first}->[0]->[0] == $ways{$second}->[-1]->[0]
and
$ways{$first}->[0]->[1] == $ways{$second}->[-1]->[1]);
return -1
if ($ways{$first}->[-1]->[0] == $ways{$second}->[-1]->[0]
and
$ways{$first}->[-1]->[1] == $ways{$second}->[-1]->[1]);
return 0;
}
while (@outer) {
my $wid = shift @outer;
my $last = $wid;
my $conn;
@part = ();
if (@outer) {
$conn = connected($outer[-1], $wid);
if ($conn == -1) {
@{ $ways{ $wid } } = reverse @{ $ways{ $wid } };
}
}
push @part, @{ $ways{ $wid } };
while (@outer) {
$wid = shift @outer;
$conn = connected($wid, $last);
if ($conn == 1) {
# nothing to do
}
elsif ($conn == -1) {
@{ $ways{$wid} } = reverse @{ $ways{$wid} };
}
else {
my $found = 0;
my $old_wid = $wid;
foreach my $w (@outer) {
$conn = connected($w, $last);
next unless $conn;
$found = 1;
$wid = $w;
@{ $ways{$wid} } = reverse @{ $ways{$wid} }
if $conn == -1;
last;
}
if (!$found) {
push @outer_left, $wid;
next;
}
else {
@outer = grep { $_ != $wid } @outer;
push @outer, $old_wid;
}
}
push @part, @{ $ways{ $wid } };
$last = $wid;
}
push @poly, [ @part ]
if @part;
if (@outer_left) {
@outer = @outer_left;
@outer_left = ();
}
}
push @poly, [];
while (@inner) {
my $wid = shift @inner;
my $last = $wid;
my $conn;
@part = ();
if (@inner) {
$conn = connected($inner[-1], $wid);
if ($conn == -1) {
@{ $ways{ $wid } } = reverse @{ $ways{ $wid } };
}
}
push @part, @{ $ways{ $wid } };
while (@inner) {
$wid = shift @inner;
$conn = connected($wid, $last);
if ($conn == 1) {
# nothing to do
}
elsif ($conn == -1) {
@{ $ways{$wid} } = reverse @{ $ways{$wid} };
}
else {
my $found = 0;
my $old_wid = $wid;
foreach my $w (@inner) {
$conn = connected($w, $last);
next unless $conn;
$found = 1;
$wid = $w;
@{ $ways{$wid} } = reverse @{ $ways{$wid} }
if $conn == -1;
last;
}
if (!$found) {
push @inner_left, $wid;
next;
}
else {
@inner = grep { $_ != $wid } @inner;
push @inner, $old_wid;
}
}
push @part, @{ $ways{ $wid } };
$last = $wid;
}
push @poly, [ @part ]
if @part;
if (@inner_left) {
@inner = @inner_left;
@inner_left = ();
}
}
my @polygons = ();
foreach my $p (@poly) {
if (@{ $p } == 0) {
$is_outer = 0;
next;
}
if ($p->[0][0] != $p->[-1][0] and $p->[0][1] != $p->[-1][1]) {
warn "$0: closing polygon...\n";
push @{ $p }, $p->[0]; # close polygon
}
push @polygons, { outer => $is_outer, poly => Math::Polygon->new(@{ $p }) };
}
@poly = sort {
$a->{poly}->contains(
$b->{poly}->point( 1 + int(rand($b->{poly}->nrPoints - 2)) )
)
? 1
: -1 } @polygons;
# failed to detect from relation
if ($need_name and $name =~ /^(\d+|-)$/) {
$name = "unknown";
}
print "$name\n";
foreach my $p (@poly) {
++$part_nr;
print "", ($p->{outer} ? "" : "!"), "$part_nr\n";
foreach ($p->{poly}->points) {
printf "\t%E\t%E\n", $_->[0], $_->[1];
}
print "END\n";
}
print "END\n";
# END
# vim: ts=4 sw=4 expandtab syn=perl
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment