#!/usr/bin/env python
#
# GeoTiler - library to create maps using tiles from a map provider
#
# Copyright (C) 2014-2020 by Artur Wroblewski <wrobell@riseup.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#
# This file incorporates work covered by the following copyright and
# permission notice (restored, based on setup.py file from
# https://github.com/stamen/modestmaps-py):
#
#   Copyright (C) 2007-2013 by Michal Migurski and other contributors
#   License: BSD
#

import argparse
import bz2
import cairo
import functools
import gzip
import itertools
import lzma
import math
import os.path
from lxml import etree

import geotiler

FILE_OPENER = {
    'xz': lzma.LZMAFile,
    'bz2': bz2.BZ2File,
    'gz': gzip.GzipFile,
}

NAMESPACES = {
    'gpx': 'http://www.topografix.com/GPX/1/1',
}

get_attr = lambda n, a: float(n.get(a))
get_point = lambda n: (get_attr(n, 'lon'), get_attr(n, 'lat'))

def read_positions(filename):
    _, ext = os.path.splitext(filename)
    ext = ext[1:]

    f_open = FILE_OPENER.get(ext, open)

    with f_open(filename) as f:
        tree = etree.parse(f)
        nodes = tree.xpath('//gpx:trkpt', namespaces=NAMESPACES)
        return (get_point(n) for n in nodes)


#
# parse arguments
#
desc = """
Read positions from set of GPX files and draw them on a map.

The map size is set to 1920x1080 and can be changed with commandline
parameter. The zoom and extents of the map are calculated automatically.
"""

parser = argparse.ArgumentParser(description=desc)
parser.add_argument(
    '-v', '--verbose', dest='verbose', help='Make a bunch of noise',
    action='store_true'
)
providers = geotiler.providers()
parser.add_argument(
    '-p', '--provider', dest='provider', choices=providers, default='osm',
    help='map provider id'
)
parser.add_argument(
    '-s', '--size', dest='size', nargs=2, type=int, default=(1920, 1080),
    help='size of map image'
)
parser.add_argument(
    '-r', '--radius', dest='radius', type=float, default=0.5,
    help='radius of drawn point of a position'
)
parser.add_argument(
    '-a', '--alpha', dest='alpha', type=float, default=0.5,
    help='color alpha of drawn point of a position'
)
parser.add_argument(
    '--cache', dest='cache', choices=['redis'], default='redis',
    help='specify caching strategy'
)
parser.add_argument('filename', nargs='+', help='GPX file')
parser.add_argument('output', help='Output PNG file')

args = parser.parse_args()

if args.cache == 'redis':
    import redis
    from geotiler.cache import redis_downloader

    client = redis.Redis('localhost')
    downloader = redis_downloader(client)

#
# read positions and determine map extents
#
positions = itertools.chain.from_iterable((read_positions(f) for f in args.filename))
positions = list(positions)
x, y = zip(*positions)
extent = min(x), min(y), max(x), max(y)

#
# render map image
#
mm = geotiler.Map(size=args.size, extent=extent, provider=args.provider)
if mm.zoom > 18: # FIXME: max zoom in API missing
    mm = geotiler.Map(zoom=18, extent=extent)

render_map = functools.partial(geotiler.render_map, downloader=downloader)
img = render_map(mm)

#
# render positions
#
width, height = mm.size
buff = bytearray(img.convert('RGBA').tobytes('raw', 'BGRA'))
surface = cairo.ImageSurface.create_for_data(
    buff, cairo.FORMAT_ARGB32, width, height
)
cr = cairo.Context(surface)

points = (mm.rev_geocode(p) for p in positions)
for x, y in points:
    cr.set_source_rgba(1.0, 0.0, 0.0, args.alpha)
    cr.arc(x, y, args.radius, 0, 2 * math.pi)
    cr.fill()
    cr.stroke()

surface.write_to_png(args.output)

# vim:et sts=4 sw=4:
