Reprojecting OSM data with Perl
Jump to navigation
Jump to search
The following code can be used to reproject OSM data to (OSGB, aka EPSG:27700) by adding 'x' and 'y' (XML) attributes:
#!/usr/bin/perl
use warnings;
use strict;
use Geo::Proj4;
use XML::LibXSLT;
use XML::LibXML;
use XML::DOM;
my $osgb36 = Geo::Proj4->new(init => "epsg:27700") or die Geo::Proj4->error;
project_xsl();
sub project_dom {
my $parser = new XML::DOM::Parser;
my $doc = $parser->parse(\*STDIN);
my $nodes = $doc->getElementsByTagName ("node");
my $n = $nodes->getLength;
for (my $i = 0; $i < $n; $i++) {
my $node = $nodes->item($i);
my $lat = $node->getAttributeNode("lat")->getValue();
my $lon = $node->getAttributeNode("lon")->getValue();
my ($x, $y) = $osgb36->forward($lat, $lon) or die;
$node->setAttribute("x", $x);
$node->setAttribute("y", $y);
}
print $doc->toString();
}
sub project_xsl {
XML::LibXSLT->register_function("urn:proj", "toosgb",
sub {
# args: lat, lon
my ($x, $y) = $osgb36->forward(@_) or die;
my $result = XML::LibXML::NodeList->new;
$result->push(XML::LibXML::Attr->new("x", $x));
$result->push(XML::LibXML::Attr->new("y", $y));
return $result;
});
XML::LibXSLT->register_function("urn:proj", "bounds",
sub {
# args: bbox string
my @b = split ',', shift;
my $result = XML::LibXML::NodeList->new;
if ($#b == 3) {
$result->push(XML::LibXML::Attr->new("minlat", $b[0]));
$result->push(XML::LibXML::Attr->new("minlon", $b[1]));
$result->push(XML::LibXML::Attr->new("maxlat", $b[2]));
$result->push(XML::LibXML::Attr->new("maxlon", $b[3]));
}
return $result;
});
my $xslt = XML::LibXSLT->new();
my $doc = get_stylesheet();
my $stylesheet = $xslt->parse_stylesheet($doc);
my $osm = XML::LibXML->load_xml(IO=>\*STDIN, no_cdata=>1);
my $output = $stylesheet->transform($osm) or die;
print $stylesheet->output_as_chars($output);
}
sub get_stylesheet
{
return XML::LibXML->load_xml(string=><<'EOF', no_cdata=>1);
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:proj="urn:proj"
version="1.0">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="bound[@box]">
<xsl:copy-of select="."/>
<bounds>
<xsl:copy-of select="proj:bounds(@box)"/>
</bounds>
</xsl:template>
<xsl:template match="way|relation">
<xsl:copy>
<xsl:copy-of select="@id"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="node">
<xsl:copy>
<xsl:copy-of select="@id|@lat|@lon"/>
<xsl:copy-of select="proj:toosgb(@lat,@lon)"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
EOF
}
I hope this is useful to others wanting to use a projected version of an OSM extract.
The above code includes two working methods - the stylesheet version, though longer, may be useful if you're already processing OSM with XSLT.