#!/bin/bash
# tools/world-map <ne_110m_admin_0_countries.zip> <out.svg> — The countries' map (telemetry FR-GEO-002).
# Turns Natural Earth's 1:110m countries (public domain, https://www.naturalearthdata.com) into an SVG of one path
# per country, id "c-<ISO 3166-1 alpha-2>", in an equirectangular projection from 84°N to 60°S (Antarctica left
# out), 1000 × 400. The report shows it with <use>, colouring the countries that had visitors. Countries without an
# ISO code ("-99") are drawn in the base layer only.
set -euo pipefail
[ $# -eq 2 ] || { echo "usage: world-map <ne_110m_admin_0_countries.zip> <out.svg>" >&2; exit 2; }
zip="$1"; out="$2"
work="$(mktemp -d "${XDG_CACHE_HOME:-$HOME/.cache}/world-map.XXXXXX")"; trap 'rm -rf "$work"' EXIT
unzip -q -o -d "$work" -- "$zip" '*.shp' '*.dbf'
shp=$(ls "$work"/*.shp); dbf=$(ls "$work"/*.dbf)

# The ISO code of each record, from the .dbf (ISO_A2_EH, which fixes France and Norway, else ISO_A2).
od -An -v -t u1 -w1 -- "$dbf" | gawk '
{ b[NR - 1] = $1 + 0 }
function le(o, n,   v, i) { v = 0; for (i = n - 1; i >= 0; i--) v = v * 256 + b[o + i]; return v }
function str(o, n,   s, i) { s = ""; for (i = 0; i < n; i++) if (b[o + i] > 32) s = s sprintf("%c", b[o + i]); return s }
END {
  records = le(4, 4); header = le(8, 2); length_ = le(10, 2)
  pos = 1; o = 32; want = -1
  while (b[o] != 13) {
    name = str(o, 11); flen = b[o + 16]
    if (name == "ISO_A2_EH") eh = pos; if (name == "ISO_A2") a2 = pos
    start[name] = pos; size[name] = flen; pos += flen; o += 32
  }
  for (r = 0; r < records; r++) {
    base = header + r * length_
    code = str(base + start["ISO_A2_EH"], size["ISO_A2_EH"])
    if (code !~ /^[A-Z][A-Z]$/) code = str(base + start["ISO_A2"], size["ISO_A2"])
    if (code !~ /^[A-Z][A-Z]$/) code = ""
    print code
  }
}' > "$work/codes"

# The polygons of each record, from the .shp, projected and rounded to a tenth.
od -An -v -t u1 -w1 -- "$shp" | gawk -v codes="$work/codes" '
{ b[NR - 1] = $1 + 0 }
function be(o,   v, i) { v = 0; for (i = 0; i < 4; i++) v = v * 256 + b[o + i]; return v }
function le(o,   v, i) { v = 0; for (i = 3; i >= 0; i--) v = v * 256 + b[o + i]; return v }
function dbl(o,   sign, e, m, i) {
  sign = (b[o + 7] >= 128) ? -1 : 1
  e = (b[o + 7] % 128) * 16 + int(b[o + 6] / 16)
  m = b[o + 6] % 16
  for (i = 5; i >= 0; i--) m = m * 256 + b[o + i]
  if (e == 0 && m == 0) return 0
  return sign * (1 + m / 4503599627370496) * 2 ^ (e - 1023)
}
function x(lon) { return sprintf("%.1f", (lon + 180) / 360 * 1000) }
function y(lat) { if (lat < -60) lat = -60; return sprintf("%.1f", (84 - lat) / 144 * 400) }
BEGIN { n = 0; while ((getline line < codes) > 0) code[n++] = line }
END {
  o = 100; r = 0; total = length(b)
  printf "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1000 400\">\n<!-- Countries: Natural Earth 1:110m, public domain (naturalearthdata.com). Made by tools/world-map. -->\n<defs>\n"
  while (o + 8 <= total) {
    len = be(o + 4) * 2; c = o + 8
    if (le(c) == 5) {
      parts = le(c + 36); points = le(c + 40); p0 = c + 44; pts = p0 + parts * 4
      d = ""; minlat = 90
      for (p = 0; p < parts; p++) {
        first = le(p0 + p * 4); last = (p + 1 < parts) ? le(p0 + (p + 1) * 4) : points
        ring = ""; prev = ""; south = 1
        for (i = first; i < last; i++) {
          lon = dbl(pts + i * 16); lat = dbl(pts + i * 16 + 8)
          if (lat > -60) south = 0
          pt = x(lon) "," y(lat)
          if (pt != prev) { ring = ring (ring == "" ? "M" : "L") pt; prev = pt }
        }
        if (!south) d = d ring "Z"
      }
      if (d != "") { id = (code[r] != "") ? code[r] : "x" r; printf "<path id=\"c-%s\" d=\"%s\"/>\n", id, d; ids[++k] = id }
    }
    o += 8 + len; r++
  }
  printf "</defs>\n<g id=\"land\">\n"
  for (i = 1; i <= k; i++) printf "<use href=\"#c-%s\"/>\n", ids[i]
  printf "</g>\n</svg>\n"
}' > "$out"
echo "world-map: $(grep -c '<path ' "$out") countries, $(du -h -- "$out" | cut -f1) in $out"
