Files
proton-drive-linux/scripts/natural-earth-land.py
T
nvrl fde9c2d271 feat(core,fuse,gui): Proton theme, photos on the map and an opt-in street map
- The Proton accent preference becomes a whole Proton theme that also
  holds against a GTK theme imported from gtk.css; folders get Proton's
  purple folder icon while it is on.
- Places map markers show one of each town's photos with a count, and
  zoomed in on a town every photo sits where it was taken
  (Request::PlaceLocations). Photos taken on one spot are listed in a
  popover. "Show on map" in the photo viewer goes to the Places map.
- The built-in map draws country borders, sea and land in their own
  colours.
- An opt-in street map from OpenFreeMap (vector tiles drawn with cairo,
  cached on disk up to 256 MB), with the OpenStreetMap credit on the map
  and in About.
- Translations for all catalogs; changelog folded into 2.3.0.
2026-09-28 03:12:57 +02:00

88 lines
3.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""Regenerate crates/pdfs-gui/data/land.txt from Natural Earth's land polygons,
and crates/pdfs-gui/data/borders.txt from its country borders.
Usage:
curl -LO https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_50m_land.geojson
scripts/natural-earth-land.py ne_50m_land.geojson crates/pdfs-gui/data/land.txt
curl -LO https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_50m_admin_0_boundary_lines_land.geojson
scripts/natural-earth-land.py ne_50m_admin_0_boundary_lines_land.geojson crates/pdfs-gui/data/borders.txt
Natural Earth is in the public domain; the About dialog credits it anyway.
The output is one ring per line, as space-separated "longitude,latitude"
pairs rounded to two decimals (about a kilometre). Each ring is simplified
with Ramer-Douglas-Peucker to TOLERANCE degrees, which is finer than the map
can show at its closest zoom, and rings left with fewer than four points
(islets smaller than the tolerance) are dropped. Holes (lakes) are dropped
too: the map fills the land and draws nothing on top of it. Borders are lines
rather than rings, simplified the same way; a line needs two points.
"""
import json
import sys
TOLERANCE = 0.04
def point_line_distance(p, a, b):
(x, y), (x1, y1), (x2, y2) = p, a, b
dx, dy = x2 - x1, y2 - y1
if dx == 0 and dy == 0:
return ((x - x1) ** 2 + (y - y1) ** 2) ** 0.5
t = max(0, min(1, ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy)))
return ((x - x1 - t * dx) ** 2 + (y - y1 - t * dy) ** 2) ** 0.5
def simplify(points):
"""Ramer-Douglas-Peucker, iterative so long coastlines don't recurse deep."""
keep = [False] * len(points)
keep[0] = keep[-1] = True
stack = [(0, len(points) - 1)]
while stack:
first, last = stack.pop()
worst, index = 0.0, None
for i in range(first + 1, last):
d = point_line_distance(points[i], points[first], points[last])
if d > worst:
worst, index = d, i
if index is not None and worst > TOLERANCE:
keep[index] = True
stack.append((first, index))
stack.append((index, last))
return [p for p, k in zip(points, keep) if k]
def main(source, target):
features = json.load(open(source))["features"]
lines = []
for feature in features:
geometry = feature["geometry"]
kind = geometry["type"]
if kind in ("LineString", "MultiLineString"):
paths = (
geometry["coordinates"]
if kind == "MultiLineString"
else [geometry["coordinates"]]
)
shortest = 2
else:
polygons = (
geometry["coordinates"]
if kind == "MultiPolygon"
else [geometry["coordinates"]]
)
paths = [polygon[0] for polygon in polygons]
shortest = 4
for path in paths:
path = simplify(path)
if len(path) < shortest:
continue
lines.append(" ".join(f"{lon:.2f},{lat:.2f}" for lon, lat in path))
with open(target, "w") as out:
out.write("\n".join(lines) + "\n")
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2])