"""Routing graph node ids (docs/offroad/PLAN.md Appendix E.7, R10.1a). The id is an exact integer encoding of the coordinate snapped to a 1e-5 degree grid (about 1.1 m): round((lat + 90) * 1e5) * 36_000_001 + round((lon + 180) * 1e5) It can't collide, stays below 2^53 (so JavaScript numbers hold it exactly: the device's twin is nodeId.ts, R10.4a), is the same in the official and the OSM graph, and doesn't depend on tiles. Two nodes in one grid cell are one node. """ LON_CELLS = 36_000_001 # (180 - -180) * 1e5 + 1: every longitude cell, -180 and 180 included MAX_ID = 2 ** 53 - 1 def node_id(lon: float, lat: float) -> int: if not (-180.0 <= lon <= 180.0 and -90.0 <= lat <= 90.0): raise ValueError(f'coordinate out of range: {lon}, {lat}') return round((lat + 90) * 1e5) * LON_CELLS + round((lon + 180) * 1e5) def cell(node: int) -> tuple[float, float]: """The (lon, lat) of a node id's grid cell centre.""" lat_cell, lon_cell = divmod(node, LON_CELLS) return lon_cell / 1e5 - 180, lat_cell / 1e5 - 90