#!/usr/bin/env perl
# -*- perl -*-

#
# Author: Slaven Rezic
#
# Copyright (C) 2019,2024 Slaven Rezic. All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
# Mail: slaven@rezic.de
# WWW:  http://www.rezic.de/eserte/
#

use strict;
use warnings;
use FindBin;
use lib "$FindBin::RealBin/../lib", "$FindBin::RealBin/..";

use Getopt::Long;
use LWP::UserAgent;
use XML::LibXML;

use Strassen::Core;

sub get_node ($);
sub get_way  ($);
sub get_relation ($);
sub get_geom_for_feature ($$);

my $api_root_url = 'https://api.openstreetmap.org/api/0.6';
my $overpass_api_url = 'https://overpass-api.de/api/interpreter';

sub usage () { die "usage: $0 [--full-features] [--as-org-link] osmURL ...\n" }

GetOptions(
    "full-feature|full-features!" => \my $do_full_features,
    "as-org-link" => \my $as_org_link,
)
    or usage;

my @in_urls = @ARGV
    or usage;

my @elems;
for my $in_url (@in_urls) {
    if ($in_url =~ m{(node|way|relation)/(\d+)}) {
	my($type, $id) = ($1, $2);
	push @elems, [$type, $id];
    } else {
	die "Cannot parse '$in_url'";
    }
}

my $s = Strassen->new;
$s->set_global_directive(map => 'polar');
$s->set_global_directive(encoding => 'utf-8');

my $ua = LWP::UserAgent->new;

for my $elem (@elems) {
    my($type, $id) = @$elem;
    if      ($type eq 'node') {
	my $node = get_node $id;
	$s->push([$node->{name}, [$node->{pos}], 'X']);
    } else {
	my @coords = get_geom_for_feature($type, $id);
	if ($type eq 'way') {
	    my $way = get_way $id;
	    if (!$do_full_features) {
		$s->push([$way->{name}, [@coords[$#coords/2]], 'X']);
	    } else {
		$s->push([$way->{name}, [@coords], 'X']);
	    }
	} elsif ($type eq 'relation') {
	    my $relation = get_relation $id;
	    if (!$do_full_features) {
		$s->push([$relation->{name}, [@coords[$#coords/2]], 'X']);
	    } else {
		$s->push([$relation->{name}, [@coords], 'X']);
	    }
	} else {
	    die "SHOULD NEVER HAPPEN: unhandled type $type";
	}
    }
}

if ($as_org_link) {
    require I18N::Langinfo;
    my $codeset = I18N::Langinfo::langinfo(I18N::Langinfo::CODESET());
    $codeset = lc $codeset;
    binmode STDOUT, ":encoding($codeset)";

    $s->init;
    while() {
	my $r = $s->next;
	my @c = @{ $r->[Strassen::COORDS] };
	last if !@c;
	my($lon,$lat) = split /,/, $c[0];
	print "[[geo:$lat,$lon?z=19][$r->[Strassen::NAME]]]\n";
    }
} else {
    $s->write("-");
}

sub _get_name {
    my($elem) = @_;
    my @name_vals;
    for my $key (qw(name addr:street addr:housenumber addr:suburb addr:postcode addr:city addr:country)) {
	my $val = $elem->findvalue('./tag[@k="'.$key.'"]/@v');
	if (defined $val && $val ne '') {
	    push @name_vals, $val;
	}
    }
    my $name = join(" ", @name_vals);
    if (!defined $name) {
	$name = '';
    }
    $name;
}

sub get_node ($) {
    my $id = shift;
    my $api_url = "$api_root_url/node/$id";
    my $resp = $ua->get($api_url);
    if ($resp->is_success) {
	my $data = $resp->decoded_content(charset => 'none');
	my $dom = XML::LibXML->load_xml(string => $data);
	my $root = $dom->documentElement;
	my($elem) = $root->findnodes('/osm/*');
	my $lat = $elem->findvalue('./@lat');
	my $lon = $elem->findvalue('./@lon');
	my $name = _get_name($elem);
	return {
		name => $name,
		pos  => "$lon,$lat",
	       };
    } else {
	die "Failed to fetch $api_url: " . $resp->as_string;
    }
}

sub get_way ($) {
    my $id = shift;
    my $api_url = "$api_root_url/way/$id";
    my $resp = $ua->get($api_url);
    if ($resp->is_success) {
	my $data = $resp->decoded_content(charset => 'none');
	my $dom = XML::LibXML->load_xml(string => $data);
	my $root = $dom->documentElement;
	my($elem) = $root->findnodes('/osm/*');
	my(@node_ids) = map { $_->findvalue('./@ref') } $elem->findnodes('./nd');
	my $name = _get_name($elem);
	return {
		name     => $name,
		node_ids => \@node_ids,
	       };
    } else {
	die "Failed to fetch $api_url: " . $resp->as_string;
    }
}

# just return name, no coordinates
sub get_relation ($) {
    my $id = shift;
    my $api_url = "$api_root_url/relation/$id";
    my $resp = $ua->get($api_url);
    if ($resp->is_success) {
	my $data = $resp->decoded_content(charset => 'none');
	my $dom = XML::LibXML->load_xml(string => $data);
	my $root = $dom->documentElement;
	my($elem) = $root->findnodes('/osm/*');
	my $name = _get_name($elem);
	return {
		name => $name,
	       };
    } else {
	die "Failed to fetch $api_url: " . $resp->as_string;
    }
}

sub get_geom_for_feature ($$) {
    my($type, $id) = @_;
    $type =~ m{^(way|relation)$}
	or die "Only way or relation allowed";
    $id =~ m{^\d+$}
	or die "id does not look like a number";
    my $query = <<"EOF";
[out:xml];
$type($id);
(._;>;);
out geom;
EOF
    my $resp = $ua->post($overpass_api_url, { data => $query });
    if (!$resp->is_success) {
	die "ERROR: while fetching $overpass_api_url:\n" . $resp->dump(maxlength => 4) . "\n" . $resp->decoded_content;
    }

    my @coords;

    my $root = XML::LibXML->new->parse_string($resp->decoded_content)->documentElement;
    for my $node ($root->findnodes('/osm/node')) {
	my($lat,$lon) = ($node->findvalue('./@lat'), $node->findvalue('./@lon'));
	push @coords, "$lon,$lat";
    }

    @coords;
}

__END__

=head1 NAME

osmelem2bbd - download OSM features and convert into bbd format

=head1 SYNOPSIS

    osmelem2bbd [--full-features] https://www.openstreetmap.org/node/1234 https://www.openstreetmap.org/way/5678 https://www.openstreetmap.org/relation/9012

=head1 DESCRIPTION

Download name and coordinates of the specified openstreetmap features
(nodes, ways, or relations). Feature specification may be a full or
partial URL, that is, both C<https://www.openstreetmap.org/node/1234>
and C<node/1234> is recognized.

By default, only the only (nodes) or middle (ways) or a random one
(relations) coordinate is used. With option C<--full-features> the
generated output contains all feature coordinates.

=head2 BUGS

With C<--full-features>, a centroid should probably be used instead of
a random coordinate for relation features.

With C<--full-features>, all coordinates are connected for relation
features, even between unconnected child features.

=head2 EXAMPLES

Download some sights in Berlin, convert them to a GPX waypoints file,
and upload to a GPS device:

    (
    TARGET_GPX=/tmp/berlin_sights.gpx
    ./miscsrc/osmelem2bbd https://www.openstreetmap.org/way/518071791 https://www.openstreetmap.org/node/2418372191 https://www.openstreetmap.org/relation/2198410 | ./miscsrc/bbd2gpx - > $TARGET_GPX
    ./miscsrc/gps-mount.pl --gpx-cp $TARGET_GPX
    )

=head1 SEE ALSO

L<gps-mount.pl>.

=cut
