"""Markings for the K2 relief map: climbing routes, a title, a legend and a scale bar, as an SVG that lines up
with k2.png pixel for pixel.

    python markings.py FONT.ttf k2-markings.svg

Needs fonttools (pip install fonttools) for the letters, any TrueType/OpenType font (the picture uses Inter Bold),
and the heightmap.py that made k2.png, which defines the map's frame.
"""
import math
import sys

from fontTools.pens.svgPathPen import SVGPathPen
from fontTools.pens.transformPen import TransformPen
from fontTools.ttLib import TTFont

from heightmap import PIXEL, frame, to_xy

# The same four numbers you gave heightmap.py, and the "Map size" you will give the generator.
LAT, LON, WIDTH_KM, HEIGHT_KM = 35.875, 76.52, 18, 13.5
MAP_MM = 200

crs, to_metres, (W, H) = frame(LAT, LON, WIDTH_KM, HEIGHT_KM)
MM = W / MAP_MM                     # SVG units (= heightmap pixels) per millimetre of the finished map


def px(lat, lon):
    """Latitude/longitude -> a position in the heightmap, in pixels from its top-left corner."""
    x, y = to_xy(crs, lat, lon)
    return (x - to_metres.c) / PIXEL, (to_metres.f - y) / PIXEL


# Routes: waypoints from the summit down (latitude, longitude). These follow the ridge crests of the elevation
# model, they are not GPS tracks. Replace them with your own tracks if you have them.
ROUTES = {
    "1": ("Abruzzi", [(35.8809, 76.513), (35.8797, 76.5222), (35.8745, 76.5298), (35.8747, 76.5358),
                           (35.8733, 76.5391), (35.8732, 76.5474), (35.8726, 76.549)]),
    "2": ("Cesen", [(35.8809, 76.513), (35.8802, 76.5206), (35.8794, 76.522), (35.8746, 76.5222),
                    (35.8718, 76.524), (35.8662, 76.5244), (35.8637, 76.526), (35.8604, 76.5265),
                    (35.8566, 76.5287), (35.8545, 76.5291)]),
    "3": ("Magic Line", [(35.8809, 76.513), (35.8747, 76.5103), (35.8688, 76.5094), (35.8598, 76.502),
                         (35.8539, 76.4938), (35.85, 76.4916), (35.8419, 76.4901), (35.8369, 76.4917),
                         (35.8295, 76.4924)]),
    "4": ("North Ridge", [(35.8809, 76.513), (35.8828, 76.5131), (35.8861, 76.5096), (35.8913, 76.5067),
                          (35.9019, 76.4965), (35.9041, 76.4956), (35.9079, 76.4972)]),
    "5": ("NE Ridge", [(35.8809, 76.513), (35.8822, 76.5147), (35.8826, 76.5173), (35.8836, 76.5195),
                       (35.8867, 76.5224), (35.8894, 76.5234), (35.891, 76.5261), (35.8985, 76.5324),
                       (35.9042, 76.5302), (35.9061, 76.5281), (35.9067, 76.5264)]),
}
ROUTE_WIDTH = 1.0                   # mm on the finished map
NUDGE = {"5": (-17, -9)}             # pixels: a number that would land on a slope is moved onto flat ground


def smooth(points, rounds=2):
    """Chaikin corner cutting: the waypoints become a flowing line."""
    for _ in range(rounds):
        out = [points[0]]
        for (x0, y0), (x1, y1) in zip(points, points[1:]):
            out += [(0.75 * x0 + 0.25 * x1, 0.75 * y0 + 0.25 * y1), (0.25 * x0 + 0.75 * x1, 0.25 * y0 + 0.75 * y1)]
        points = out + [points[-1]]
    return points


def ribbon(points, width):
    """A polyline as a filled strip `width` wide, so it is engraved as a bold line and not as a hairline."""
    left, right = [], []
    for i, (x, y) in enumerate(points):
        a, b = points[max(i - 1, 0)], points[min(i + 1, len(points) - 1)]
        dx, dy = b[0] - a[0], b[1] - a[1]
        n = math.hypot(dx, dy) or 1
        left.append((x - dy / n * width / 2, y + dx / n * width / 2))
        right.append((x + dy / n * width / 2, y - dx / n * width / 2))
    return left + right[::-1]


def polygon(points):
    return "M" + " L".join(f"{x:.2f} {y:.2f}" for x, y in points) + " Z"


def box(x, y, w, h):
    return f"M{x:.1f} {y:.1f} h{w:.1f} v{h:.1f} h{-w:.1f} Z"


# Text as outlines: the generator does not read <text>.
font = TTFont(sys.argv[1])
glyphs, cmap, upm = font.getGlyphSet(), font.getBestCmap(), font["head"].unitsPerEm
cap_height = font["OS/2"].sCapHeight


def text(s, x, y, cap_mm, anchor="start", tracking=0.0):
    """Outlines of `s` with capitals `cap_mm` tall, baseline at y, starting at / centred on / ending at x."""
    k = cap_mm * MM / cap_height
    width = sum(font["hmtx"][cmap[ord(c)]][0] + tracking * upm for c in s) * k
    x -= {"start": 0, "middle": width / 2, "end": width}[anchor]
    pen = SVGPathPen(glyphs)
    for c in s:
        name = cmap[ord(c)]
        glyphs[name].draw(TransformPen(pen, (k, 0, 0, -k, x, y)))        # font y points up, SVG y points down
        x += (font["hmtx"][name][0] + tracking * upm) * k
    return pen.getCommands()


shapes = []

# The routes, each with its number just beyond its foot.
for key, (name, waypoints) in ROUTES.items():
    line = smooth([px(*p) for p in waypoints])
    shapes.append(polygon(ribbon(line, ROUTE_WIDTH * MM)))
    (x0, y0), (x1, y1) = line[-4], line[-1]
    n = math.hypot(x1 - x0, y1 - y0) or 1
    dx, dy = NUDGE.get(key, (0, 0))
    shapes.append(text(key, x1 + (x1 - x0) / n * 5 * MM + dx, y1 + (y1 - y0) / n * 5 * MM + dy + 1.7 * MM,
                       3.4, "middle"))

# Title on the flat ground north of the peak.
shapes.append(text("K2", 172, 62, 12, tracking=0.04))
shapes.append(text("8,611 m", 182, 80, 3.4, tracking=0.04))

# Legend in two columns on the glacier south of it (3 mm capitals). Positions come from looking at the flat
# area in the generator's preview: the text has to stay clear of the first step up on every side.
for i, (key, (name, _)) in enumerate(ROUTES.items()):
    x, row = (118, i) if i < 3 else (240, i - 2)
    shapes.append(text(f"{key} {name.upper()}", x, 408 + row * 5.3 * MM, 3))

# A 2 km scale bar on the flat ground to the east.
bar, x, y = 2000 / PIXEL, 524, 300
shapes += [box(x, y - 0.4 * MM, bar, 0.8 * MM), box(x, y - 2 * MM, 0.8 * MM, 4 * MM),
           box(x + bar - 0.8 * MM, y - 2 * MM, 0.8 * MM, 4 * MM)]
shapes.append(text("2 KM", x, y - 4 * MM, 3, tracking=0.02))

with open(sys.argv[2], "w") as f:
    f.write(f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}" viewBox="0 0 {W} {H}">\n')
    f.writelines(f'<path d="{d}"/>\n' for d in shapes)
    f.write("</svg>\n")
print(f"{len(shapes)} shapes, {W} x {H}")
