#!/usr/bin/perl

=head1 NAME

split_kml.pl - split a KML from rowmaps.com into several smaller KML files,
for easier conversion to GPX.

=head1 DESCRIPTION

Britain has thousands of miles of public rights of way: footpaths,
bridleways, restricted byways and bridleways open to all traffic. You can
use these to explore the countryside, take a shortcut through a town or
city, or get fit.

Many public rights of way aren't properly mapped, even on Ordnance Survey
maps. Although Ordnance Survey maps show public rights of way, this data
comes from the responsible local authorities. Ordnance Survey hasn't
actually surveyed the routes.

Local authorities maintain a "definitive map" of rights of way in their
areas. These are legally definitive, but the situation on the ground is, as
ever, a little more nuanced. Often local authority data will show public
rights of way in slightly different places to the local authority's own
markers and signs on the ground. Sometimes, routes evolve (legally they
won't, unless the definitive map is updated, but they do evolve on the
ground, and in many cases that's all that matters).

OpenStreetMap is the perfect place to map these routes.

Barry Cornelius has kindly collated data public rights of way data from 112
authorities in England and Wales, and made them available on his
L<http://www.rowmaps.com/> site. Data is generally available in KML format.
I prefer to work with GPX, since it is better supported by OsmAnd.

This script is designed to split large KML files from rowmaps.com into
several smaller ones. For example, the file for Gloucestershire (a largely
rural area) is very large, so it's better to split it into separate files
based on type of public right of way, and then further into smaller files.
Grab the "converted" file from rowmaps.com, and rename it to (say)
gloucestershire.kml. Run the script like:

  split_kml.pl --type Footpath --files 5 gloucestershire.kml

This will take all the footpaths from gloucestershire.kml and split them
into 5 separate KML files, named gloucestershire_footpath_1.kml and so on.

You'll want to run the script for each type of public right of way.

Once you've done this, use L<http://www.gpsvisualizer.com/> to convert the
files to GPX. Yes, this is an annoying manual process, but it's mostly a
one-off.

Optional extra: add colours to each type of file. I find it useful to
colour-code public rights of way. To make bridleways show in red, add this
to your GPX files, just after the opening C<< <gpx> >> tag:

  <extensions>
    <color>#ccff0000</color>
  </extensions>

The colour includes an alpha channel (cc) to make the lines
semi-transparent. This is important: without it, the tracks will be opaque,
and you won't be able to see if they're actually mapped on OpenStreetMap.

Copy the GPX files to the "tracks" directory within the OsmAnd directory on
your Android phone, and configure OsmAnd to show them.

Now (finally) comes the fun part: get out and start surveying missing
paths, and then add them to OpenStreetMap!

=head1 SYNOPSIS

  split_kml.pl OPTIONS kml_file

Supported options:

=head2 C<--type>

Specifies the type of public right of way to include. Values are usually
one of:

  Footpath
  Bridleway
  Restricted Byway
  BOAT

Look for C<< <Style id="..."> >> in the KML file. Defaults to 'Footpath'.

=head2 C<--files>

Specifies the number of files to split the input KML file into. Note: if
there are under 500 public rights of way of the specified type, this
option's value is ignored, and a single output file containing all the
public rights of way of the specified type is used.

If your phone doesn't have much RAM, use a higher number here so you can
view the file just for the immediate area you're focusing on.

=head2 C<--debug>

Enable debug output.

=head2 C<--help>

Show full help.

=head1 LICENSE

Copyright (c) 2018, Nick Johnston.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

=cut

use strict;
use warnings;

use Getopt::Long;
use Pod::Usage;
use XML::LibXML;

use constant MIN_PROWS_PER_FILE => 500;

GetOptions(
  'type=s'     => \my $type,
  'segments=i' => \my $number_of_files,
  'debug'      => \my $debug,
  'help'       => \my $help,
);

if ($help) {
  pod2usage(verbose => 2);
}

$type            ||= 'Footpath';
$number_of_files ||= 5;

# Type of right of way to process
my $wanted_type_of_prow = $type;

my $kml_file = shift @ARGV;

$kml_file or pod2usage("KML file must be specified.");

my $dom = parse_xml($kml_file);

my $number_of_prows_of_interest = get_number_of_prows_of_interest($dom, $wanted_type_of_prow);

if ($number_of_prows_of_interest < MIN_PROWS_PER_FILE) {
  $number_of_files = 1;
}

debug("There are $number_of_prows_of_interest PRoWs of type $type in $kml_file.");

my $tracks_per_file = int($number_of_prows_of_interest / $number_of_files);

my $offset = 0;

(my $prow_for_filename = lc $wanted_type_of_prow) =~ s/ /_/g;

while ($number_of_files--) {
  (my $output_kml_file = $kml_file) =~ s/(\.kml)/'_' . $prow_for_filename . '_' . ($number_of_files + 1) . $1/e;

  save_subset_to_kml(
    $output_kml_file,
    $wanted_type_of_prow,
    $offset,
    $tracks_per_file,
  );

  $offset += $tracks_per_file;
}

sub get_number_of_prows_of_interest {
  my ($dom, $wanted_type_of_prow) = @_;

  my $xpc = XML::LibXML::XPathContext->new($dom->documentElement);
  $xpc->registerNs('kml' => 'http://www.opengis.net/kml/2.2');

  return $xpc->findnodes(
    "//kml:Folder/kml:name[text() = '$wanted_type_of_prow']/../kml:Placemark"
  )->size;
}

sub save_subset_to_kml {
  my ($output_kml_file, $wanted_type_of_prow, $offset, $size) = @_;

  $offset ||= 0;

  my $dom = parse_xml($kml_file);

  my $xpc = XML::LibXML::XPathContext->new($dom->documentElement);
  $xpc->registerNs('kml' => 'http://www.opengis.net/kml/2.2');

  foreach my $folder ($xpc->findnodes('//kml:Folder')) {
    my $type_of_prow = $folder->getElementsByTagName('name')->shift->textContent;

    # Only keep the types of public right of way that we're interested in
    if ($type_of_prow ne $wanted_type_of_prow) {
      $folder->unbindNode;
      next;
    }

    $xpc->setContextNode($folder);

    my @prows_of_this_type = $xpc->findnodes('kml:Placemark');

    my $count = 0;
    my $limit = $offset + $size;

    foreach my $prow (@prows_of_this_type) {
      $count++;

      # Skip the things we want to keep ...
      next if ($count >= $offset && $count < $limit);

      # ... and delete everything else
      $prow->unbindNode;
    }
  }

  print "Output file: $output_kml_file\n";
  $dom->toFile($output_kml_file, 1);
}

sub parse_xml {
  my $file = shift;

  my $parser = XML::LibXML->new;
  $parser->keep_blanks(0);

  return $parser->load_xml(
    location => $kml_file
  );
}

sub debug {
  return unless $debug;

  print "@_\n";
}
