Relations/Relations to GPX

From OpenStreetMap Wiki
Jump to navigation Jump to search

What it does

This little Perl script takes a bunch of route relations, and writes them out as a single GPX file.

The idea is that you use it in conjunction with a GPX visualiser. Just for example, if you were a ranger for your local cycle route, and wanted to embed a map of the route on your website, you could use this.

Possible future development

  • a web interface
  • the ability to load a list of ways, or the results of a XAPI query
  • investigate using GML rather than GPX, which with OpenLayers' BBOX strategy should work better for big datasets

Code

Some consideration

  • Due to http calls code may be slow.
  • Program uses STDIN & STDOUT.
  • I do not calculate bbox, because gpsbabel and gpsvisualizer do not need it.
  • Was checked on single relation 5583485.
  • Was checked on many relations 9327, 9333, 34610, 47904.
#!/usr/bin/perl -w

use strict;
use warnings;

use LWP::Simple qw(get);
use XML::Mini::Document;

sub read_relations() {

    sub check_if_natural_number($) {
        my ($checked_string) = @_;
        unless ( $checked_string =~ /^\d+$/ ) {
            print "\nI got a $checked_string, which doesn't look like a valid relation id!\n";
            exit 1;
        }
    }

    my @relations = <STDIN>;
    chomp @relations;
    map { check_if_natural_number($_) } @relations;
    return @relations;
}

sub call_osm_api($$) {
    my ( $member_type, $member_id ) = @_;

    sub osm_api_url($$) {
        my ( $member_type, $member_id ) = @_;

        my $common_part = "http://www.openstreetmap.org/api/0.6/";

        if ( $member_type eq 'relation' ) {
            return "$common_part/relation/$member_id";
        }
        if ( $member_type eq 'way' ) {
            return "$common_part/way/$member_id/full";
        }
    }

    my $url      = osm_api_url( $member_type, $member_id );
    my $osm_data = get($url);
    unless ( defined($osm_data) ) {
        print "\nOSM knows nothing about the $member_type $member_id.\n";
        exit 1;
    }
    my $parser = XML::Mini::Document->new();
    $parser->fromString($osm_data);
    return $parser->toHash();
}

sub read_ways_from_relation(@) {
    my @relations = @_;

    sub filter_ways($) {
        my ($all_memberes) = @_;
        return grep( { $_->{type} eq 'way' } @$all_memberes );
    }

    sub extract_ids(@) {
        return map( { $_->{ref} } @_ );
    }

    my @ways = ();
    foreach my $relation_id (@relations) {
        my $relation = call_osm_api( "relation", $relation_id );
        push @ways,
          extract_ids( filter_ways( $relation->{osm}->{relation}->{member} ) );
    }
    return \@ways;
}

sub ways_as_nodes($) {
    my ($way_ids) = @_;

    sub node_lat_lon($$) {
        my ( $node_id, $nodes_details ) = @_;

        my @node      = grep { $_->{id} eq $node_id } @$nodes_details;
        my $node_data = $node[0];
        return {
            lat => $node_data->{lat},
            lon => $node_data->{lon},
        };
    }

    sub collect_nodes_data($) {
        my ($way_data)    = @_;
        my $nodes_in_way  = $way_data->{osm}->{way}->{nd};
        my $nodes_details = $way_data->{osm}->{node};

        my @nodes_ids = map( { $_->{ref} } @$nodes_in_way );
        my @nodes = map( { node_lat_lon( $_, $nodes_details ) } @nodes_ids );
        return \@nodes;
    }

    my @ways = ();

    foreach my $way_id (@$way_ids) {
        my $way_data = call_osm_api( 'way', $way_id );
        push @ways, collect_nodes_data($way_data);
    }
    return \@ways;
}

sub dump_gpx_to_stdout($) {
    my ($ways) = @_;

    my $xml      = XML::Mini::Document->new();
    my $xml_root = $xml->getRoot();

    my $xml_header = $xml_root->header('xml');
    $xml_header->attribute( 'version',  '1.0' );
    $xml_header->attribute( 'encoding', 'UTF-8' );

    my $gpx = $xml_root->createChild('gpx');
    $gpx->attribute( 'xmlns',   'http://www.topografix.com/GPX/1/0' );
    $gpx->attribute( 'version', '1.0' );
    $gpx->attribute( 'creator', 'https://wiki.openstreetmap.org/wiki/Relations/Relations_to_GPX' );

    my $trk = $gpx->createChild('trk');
    foreach my $way (@$ways) {
        my $trkseg = $trk->createChild('trkseg');
        foreach my $node (@$way) {
            my $trkpt = $trkseg->createChild('trkpt');
            $trkpt->attribute( 'lat', $node->{lat} );
            $trkpt->attribute( 'lon', $node->{lon} );
        }
    }

    print $xml->toString();
}

dump_gpx_to_stdout(
    ways_as_nodes( 
        read_ways_from_relation(
            read_relations()
        )
    )
);