Files
MeshCore-Solo/tools/maps/pbf.py
T
JakubandClaude Opus 5.5 002f478231 feat(ui-lvgl): vector region packs, PBF input, region list
One .vpk file per region in /vmap (header, sorted index of tiles and
points, then the data), read in place with the index in PSRAM; loose
{dz}/{x}/{y}.vt/.vp files still work. osm_vector.py writes packs
(--pack/--name), reads OSM PBF extracts for a box (--pbf/--bbox, via
pyosmium) and picks label languages (--lang). Map tools > Vector regions
lists the packs on the card: show one on the map, delete it. No
on-device download: packs are made with the tool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:54:25 +02:00

66 lines
2.9 KiB
Python

"""OSM PBF extract -> the elements osm_vector.py works on (as Overpass gives
them with `out geom;`), for a box. Needs pyosmium (pip install osmium).
Two passes over the file: relations first (hiking routes, multipolygons: which
ways they need), then nodes and ways with their node locations. A way is kept
if it touches the box; a relation if one of its ways was kept.
"""
import sys
WAY_KEYS = ('highway', 'waterway', 'natural', 'landuse', 'building', 'tourism')
AREA_KEYS = ('natural', 'landuse', 'building', 'water')
NODE_KEYS = ('natural', 'tourism', 'amenity', 'place')
def read_pbf(path, box):
"""box: (lon0, lat0, lon1, lat1). Returns a list of element dicts."""
import osmium
lon0, lat0, lon1, lat1 = box
rels, want = [], {} # relations kept; way id -> [(relation index, role)]
for r in osmium.FileProcessor(path, osmium.osm.RELATION):
t = dict(r.tags)
if not (t.get('route') == 'hiking' or (t.get('type') == 'multipolygon' and any(k in t for k in AREA_KEYS))):
continue
i = len(rels)
rels.append({'type': 'relation', 'id': r.id, 'tags': t, 'members': []})
for m in r.members:
if m.type == 'w':
want.setdefault(m.ref, []).append((i, m.role))
print(f'PBF: {len(rels)} relations', file=sys.stderr)
elements, geoms = [], {}
# Untagged nodes (most of them) never reach Python; ways do, untagged ones
# can be a multipolygon's outline.
fp = osmium.FileProcessor(path, osmium.osm.NODE | osmium.osm.WAY).with_locations() \
.with_filter(osmium.filter.EmptyTagFilter().enable_for(osmium.osm.NODE))
for o in fp:
if o.is_node():
if not any(k in o.tags for k in NODE_KEYS):
continue
loc = o.location
if loc.valid() and lon0 <= loc.lon <= lon1 and lat0 <= loc.lat <= lat1:
elements.append({'type': 'node', 'id': o.id, 'lat': loc.lat, 'lon': loc.lon, 'tags': dict(o.tags)})
continue
in_rel = o.id in want
tagged = any(k in o.tags for k in WAY_KEYS)
if not in_rel and not tagged:
continue
g = [{'lat': n.lat, 'lon': n.lon} for n in o.nodes if n.location.valid()]
if len(g) < 2:
continue
if (max(p['lon'] for p in g) < lon0 or min(p['lon'] for p in g) > lon1 or
max(p['lat'] for p in g) < lat0 or min(p['lat'] for p in g) > lat1):
continue
if in_rel:
geoms[o.id] = g
if tagged:
elements.append({'type': 'way', 'id': o.id, 'tags': dict(o.tags), 'geometry': g})
for wid, uses in want.items():
if wid in geoms:
for i, role in uses:
rels[i]['members'].append({'type': 'way', 'ref': wid, 'role': role, 'geometry': geoms[wid]})
kept = [r for r in rels if r['members']]
print(f'PBF: {len(elements)} nodes / ways, {len(kept)} relations in the box', file=sys.stderr)
return elements + kept