Warum ist jedes 4-Wörter-Adress-Dekodierungsergebnis in meiner Flask-App die Ost-Antarktis, ATA?
Posted: 15 May 2025, 02:33
Ich arbeite an einem flaskbasierten Geolocation-Tool, das (LAT, LON) in vier Wörter koordiniert, wobei ein benutzerdefiniertes Netzsystem über Land und Wasser verwendet wird. Hier ist eine vereinfachte Version des Code: < /p>
Die Ausgabe
Die Funktion codode () gibt eine Liste von vier Wörtern zurück, basierend auf einem berechneten cell_id . Wenn ich diese vier Wörter jedoch mit Decode () dekodieren, unterscheiden sich die resultierenden (lat, lon) Koordinaten oft signifikant von den ursprünglichen -, manchmal um mehrere Kilometer oder mehr. /> Ich wende die Wortliste ein und erstelle ein sauberes Wort → Index Mapping. /> [*] testete sowohl das Legacy- als auch die aktuelle Dekodierungslogik: < /strong>
für IDs behandle ich sie als aktuelles Format; Andere greifen auf die Legacy Decodes Logic zurück. Noch kein Erfolg. /> Off-by-One-Fehler in der Gittermathematik < /li>
Basisumwandlung Fehlausrichtung (MSB /LSB-Bestellung, Überlauf) < /li>
Word-Index-Order-Ausgabe < /li>
< /ul>
< /li>
< /li>
< /ul>
Fragen < /h3>
Gibt es offensichtlich etwas nicht, wie ich cell_id < /code> → Wörter und zurück-Rücken umwandeln kann. Land/Wasser aufgeteilt, ohne die cell_id math?
Ist Decode () stillschweigend fehlschlagen und eine "Fallback" -Koordinate ohne Werfen? PrettyPrint-Override ">
Wenn der dekodierte Ort nicht nur wenige Meter vom Original entfernt ist, bricht etwas zusammen.
Code: Select all
import math
import csv
from typing import List, Tuple, Dict
import geopandas as gpd
from shapely.geometry import Point
from flask import Flask, request, render_template
from geopy.geocoders import ArcGIS
# ------------------------------------------------------------------------------
# FLASK APP INIT
# ------------------------------------------------------------------------------
app = Flask(__name__)
geolocator = ArcGIS()
# ------------------------------------------------------------------------------
# CONSTANTS
# ------------------------------------------------------------------------------
GRID_SIZE_LAND = 5 # metres
GRID_SIZE_WATER = 10 # metres
NUM_LAT_CELLS_LAND = int(180_000_000 / GRID_SIZE_LAND)
NUM_LON_CELLS_LAND = int(360_000_000 / GRID_SIZE_LAND)
NUM_LAT_CELLS_WATER = int(180_000_000 / GRID_SIZE_WATER)
NUM_LON_CELLS_WATER = int(360_000_000 / GRID_SIZE_WATER)
TOTAL_LAND_CELLS = NUM_LAT_CELLS_LAND * NUM_LON_CELLS_LAND
LEGACY_SPLIT = 10 ** 12 # old 4-digit prefix boundary
# ------------------------------------------------------------------------------
# WORD LIST
# ------------------------------------------------------------------------------
def load_words_from_csv(filename: str) -> Tuple[List[str], Dict[str, int]]:
"""Return a de-duplicated word list and lookup table."""
words: List[str] = []
lookup: Dict[str, int] = {}
with open(filename, newline="") as fh:
for row in csv.DictReader(fh):
w = row["word"].strip().lower()
if w and w not in lookup: # drop blanks & duplicates
lookup[w] = len(words)
words.append(w)
return words, lookup
WORDS, WORD_TO_INDEX = load_words_from_csv("data/fourwords_wordlist.csv")
BASE = len(WORDS)
# ------------------------------------------------------------------------------
# LAND / WATER TEST
# ------------------------------------------------------------------------------
land_union = gpd.read_file("data/ne_50m_land.shp").unary_union
def is_land(lat: float, lon: float) -> bool:
return land_union.covers(Point(lon, lat))
# ------------------------------------------------------------------------------
# ENCODING
# ------------------------------------------------------------------------------
def latlon_to_cell(lat: float, lon: float) -> int:
"""Current contiguous ID scheme (land first, then water)."""
grid = GRID_SIZE_LAND if is_land(lat, lon) else GRID_SIZE_WATER
if grid == GRID_SIZE_LAND:
lat_i = int((lat + 90) * 1e6 // GRID_SIZE_LAND)
lon_i = int((lon + 180) * 1e6 // GRID_SIZE_LAND)
return lat_i * NUM_LON_CELLS_LAND + lon_i
else: # water
lat_i = int((lat + 90) * 1e6 // GRID_SIZE_WATER)
lon_i = int((lon + 180) * 1e6 // GRID_SIZE_WATER)
return TOTAL_LAND_CELLS + lat_i * NUM_LON_CELLS_WATER + lon_i
def encode(lat: float, lon: float) -> List[str]:
cell_id = latlon_to_cell(lat, lon)
digits = [(cell_id // BASE ** i) % BASE for i in range(3, -1, -1)] # MSB→LSB
return [WORDS[d] for d in digits]
# ------------------------------------------------------------------------------
# DECODING
# ------------------------------------------------------------------------------
def _decode_current(cell_id: int) -> Tuple[float, float]:
"""Decode using the current contiguous ID scheme."""
if cell_id < TOTAL_LAND_CELLS:
grid = GRID_SIZE_LAND
lon_cells = NUM_LON_CELLS_LAND
core_id = cell_id
else:
grid = GRID_SIZE_WATER
lon_cells = NUM_LON_CELLS_WATER
core_id = cell_id - TOTAL_LAND_CELLS
lat_i = core_id // lon_cells
lon_i = core_id % lon_cells
lat = (lat_i * grid) / 1e6 - 90 + grid / (2 * 1e6)
lon = (lon_i * grid) / 1e6 - 180 + grid / (2 * 1e6)
return lat, lon
def _decode_legacy(cell_id: int) -> Tuple[float, float]:
"""Decode a legacy ID using the 10¹² prefix trick."""
prefix = cell_id // LEGACY_SPLIT
core_id = cell_id % LEGACY_SPLIT
if prefix not in (0, 1):
raise ValueError
grid = GRID_SIZE_LAND if prefix == 0 else GRID_SIZE_WATER
lon_cells = NUM_LON_CELLS_LAND if prefix == 0 else NUM_LON_CELLS_WATER
lat_i = core_id // lon_cells
lon_i = core_id % lon_cells
lat = (lat_i * grid) / 1e6 - 90 + grid / (2 * 1e6)
lon = (lon_i * grid) / 1e6 - 180 + grid / (2 * 1e6)
return lat, lon
def decode(words: List[str]) -> Tuple[float, float]:
"""Decode four words to (lat, lon)."""
try:
digits = [WORD_TO_INDEX[w] for w in words]
except KeyError as e:
raise ValueError(f"Unknown word in code: {e.args[0]}") from None
cell_id = 0
for d in digits:
cell_id = cell_id * BASE + d
lat, lon = _decode_current(cell_id)
if lat < -89.82: # likely bogus, near South Pole
try:
lat, lon = _decode_legacy(cell_id)
except ValueError:
pass
return lat, lon
# ------------------------------------------------------------------------------
# UTILITIES
# ------------------------------------------------------------------------------
def format_words(words: List[str]) -> str:
return ".".join(words)
def parse_words(code: str) -> List[str]:
return code.lower().strip().split(".")
def resolve_input(query: str):
query = query.strip()
if "." in query and len(query.split(".")) == 4:
return "decode", query.lower().split(".")
try:
lat, lon = map(float, query.split(","))
return "encode", (lat, lon)
except:
pass
location = geolocator.geocode(query)
if location:
return "encode", (location.latitude, location.longitude)
raise ValueError("Input could not be recognized.")`
Die Funktion codode () gibt eine Liste von vier Wörtern zurück, basierend auf einem berechneten cell_id . Wenn ich diese vier Wörter jedoch mit Decode () dekodieren, unterscheiden sich die resultierenden (lat, lon) Koordinaten oft signifikant von den ursprünglichen -, manchmal um mehrere Kilometer oder mehr. /> Ich wende die Wortliste ein und erstelle ein sauberes Wort → Index Mapping. /> [*] testete sowohl das Legacy- als auch die aktuelle Dekodierungslogik: < /strong>
für IDs behandle ich sie als aktuelles Format; Andere greifen auf die Legacy Decodes Logic zurück. Noch kein Erfolg. /> Off-by-One-Fehler in der Gittermathematik < /li>
Basisumwandlung Fehlausrichtung (MSB /LSB-Bestellung, Überlauf) < /li>
Word-Index-Order-Ausgabe < /li>
< /ul>
< /li>
< /li>
< /ul>
Fragen < /h3>
Gibt es offensichtlich etwas nicht, wie ich cell_id < /code> → Wörter und zurück-Rücken umwandeln kann. Land/Wasser aufgeteilt, ohne die cell_id math?
Ist Decode () stillschweigend fehlschlagen und eine "Fallback" -Koordinate ohne Werfen? PrettyPrint-Override ">
Code: Select all
lat, lon = 48.8584, 2.2945
words = encode(lat, lon)
decoded = decode(words)
print(f"Original: {lat}, {lon} → Encoded: {format_words(words)} → Decoded: {decoded}")