mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-04 00:47:00 +01:00
Photos now know where they were taken. proton-drive-rs 0.7.0 reads the Location from a photo's extended attributes, and the timeline refresh stores it with the photo (schema v33). The migration marks every photo unresolved once, so the next refresh backfills locations from node metadata without downloading anything. Towns are looked up offline: pdfs-core bundles GeoNames' cities15000 table, trimmed by scripts/geonames-cities.py, and files each photo under the nearest town within 50 km. The new PhotoPlaces and PlacePhotos requests list the towns and page one town's photos. The Photos page gets a third view, Places, with a card per town (newest photo, town, country, count). A place opens in the gallery like an album. Country names come translated from the iso-codes catalogs, now an optional dependency. The About dialog credits GeoNames (CC BY 4.0).
87 lines
3.0 KiB
Python
Executable File
87 lines
3.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Regenerate crates/pdfs-core/data/cities15000.tsv from a GeoNames dump.
|
|
|
|
Usage:
|
|
curl -LO https://download.geonames.org/export/dump/cities15000.zip
|
|
unzip cities15000.zip
|
|
scripts/geonames-cities.py cities15000.txt crates/pdfs-core/data/cities15000.tsv
|
|
|
|
GeoNames publishes the dump under CC BY 4.0; the About dialog credits it.
|
|
|
|
The output keeps id, name, latitude, longitude and country code, sorted by id.
|
|
Two kinds of rows are dropped, so that a photo lands in the town people would
|
|
name rather than in a part of it:
|
|
|
|
- Feature codes for sections of a city (PPLX, such as Kreuzberg) and for
|
|
places that no longer exist (PPLH, PPLQ, PPLW).
|
|
- Districts that GeoNames lists as towns of their own but that are named after
|
|
a town at least five times their size within 20 km in the same country,
|
|
such as "Paris 15 Vaugirard" or "Marseille 01".
|
|
"""
|
|
|
|
import math
|
|
import sys
|
|
|
|
SKIPPED_FEATURES = {"PPLX", "PPLH", "PPLQ", "PPLW"}
|
|
DISTRICT_RADIUS_KM = 20
|
|
DISTRICT_RATIO = 5
|
|
|
|
|
|
def distance_km(a, b):
|
|
lat1, lat2 = math.radians(a[0]), math.radians(b[0])
|
|
dlon = math.radians(b[1] - a[1])
|
|
h = (
|
|
math.sin((lat2 - lat1) / 2) ** 2
|
|
+ math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
|
|
)
|
|
return 2 * 6371 * math.asin(min(1, math.sqrt(h)))
|
|
|
|
|
|
def main(source, target):
|
|
towns = []
|
|
with open(source, encoding="utf-8") as dump:
|
|
for line in dump:
|
|
f = line.rstrip("\n").split("\t")
|
|
if f[7] in SKIPPED_FEATURES:
|
|
continue
|
|
towns.append(
|
|
{
|
|
"id": int(f[0]),
|
|
"name": f[1],
|
|
"at": (float(f[4]), float(f[5])),
|
|
"country": f[8],
|
|
"population": int(f[14] or 0),
|
|
}
|
|
)
|
|
|
|
cells = {}
|
|
for town in towns:
|
|
key = (math.floor(town["at"][0]), math.floor(town["at"][1]))
|
|
cells.setdefault(key, []).append(town)
|
|
|
|
def is_district(town):
|
|
row, column = math.floor(town["at"][0]), math.floor(town["at"][1])
|
|
for dlat in (-1, 0, 1):
|
|
for dlon in (-1, 0, 1):
|
|
for other in cells.get((row + dlat, column + dlon), []):
|
|
if (
|
|
other is not town
|
|
and other["country"] == town["country"]
|
|
and town["name"].startswith(other["name"] + " ")
|
|
and other["population"] >= DISTRICT_RATIO * town["population"]
|
|
and distance_km(town["at"], other["at"]) <= DISTRICT_RADIUS_KM
|
|
):
|
|
return True
|
|
return False
|
|
|
|
kept = sorted((t for t in towns if not is_district(t)), key=lambda t: t["id"])
|
|
with open(target, "w", encoding="utf-8") as out:
|
|
for t in kept:
|
|
lat, lon = t["at"]
|
|
out.write(f"{t['id']}\t{t['name']}\t{lat:.4f}\t{lon:.4f}\t{t['country']}\n")
|
|
print(f"{len(kept)} towns written", file=sys.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1], sys.argv[2])
|