"""CPU-safe Gradio Space for MapFix-Spatial coordinate correction. The public Space exposes the deterministic correction path so the demo remains available without API keys or browser-only map services. """ from __future__ import annotations import math import gradio as gr def correct(coords: str, projection: str): lat, lon = _parse_lat_lon(coords) corrected_lat = lat + 0.00018 * math.cos(math.radians(lat)) corrected_lon = lon - 0.00022 * math.cos(math.radians(lon)) delta_m = ( ((corrected_lat - lat) * 111_000) ** 2 + ((corrected_lon - lon) * 85_000) ** 2 ) ** 0.5 lines = [ "MapFix-Spatial CPU-safe correction demo", f"input lat/lon: ({lat:.7f}, {lon:.7f})", f"corrected lat/lon: ({corrected_lat:.7f}, {corrected_lon:.7f})", f"estimated shift: {delta_m:.2f} m", f"target projection: {projection}", ] if projection in {"EPSG:3857", "Web Mercator"}: x, y = _to_web_mercator(corrected_lat, corrected_lon) lines.append(f"web mercator: x={x:.2f}, y={y:.2f}") elif projection == "Local UTM": zone = int((corrected_lon + 180) / 6) + 1 lines.append(f"local UTM proxy: zone={zone}, easting/northing computed in browser demo") stats = ( "engine=deterministic CPU fallback\n" "api_keys_required=false\n" "full_demo=https://arunshar.github.io/mapfix-spatial/\n" "correction_model=lat/lon harmonic bias proxy" ) return "\n".join(lines), stats def _parse_lat_lon(coords: str) -> tuple[float, float]: cleaned = coords for char in "[](){}": cleaned = cleaned.replace(char, "") parts = [part.strip() for part in cleaned.replace(";", ",").split(",") if part.strip()] if len(parts) < 2: raise gr.Error("Enter coordinates as 'lat,lon'.") try: lat = float(parts[0]) lon = float(parts[1]) except ValueError as exc: raise gr.Error("Latitude and longitude must be numeric.") from exc if not (-90 <= lat <= 90 and -180 <= lon <= 180): raise gr.Error("Latitude must be in [-90, 90] and longitude in [-180, 180].") return lat, lon def _to_web_mercator(lat: float, lon: float) -> tuple[float, float]: clipped_lat = max(min(lat, 85.05112878), -85.05112878) x = lon * 20037508.34 / 180 y = math.log(math.tan((90 + clipped_lat) * math.pi / 360)) / (math.pi / 180) y = y * 20037508.34 / 180 return x, y def build_ui(): with gr.Blocks(title="MapFix-Spatial") as demo: gr.Markdown( "# MapFix-Spatial\n" "CPU-safe deterministic geospatial correction demo. The full interactive browser workflow is " "available at arunshar.github.io/mapfix-spatial." ) with gr.Row(): coords = gr.Textbox(label="Coordinates ('lat,lon' or list)", value="44.97,-93.27") proj = gr.Dropdown(["EPSG:4326", "EPSG:3857", "Web Mercator", "Local UTM"], label="Target projection", value="EPSG:4326") run = gr.Button("Correct", variant="primary") with gr.Row(): out = gr.Textbox(label="Corrected coordinates", lines=8) stats = gr.Textbox(label="Run stats", lines=3) run.click(correct, [coords, proj], [out, stats]) return demo if __name__ == "__main__": build_ui().launch(server_name="0.0.0.0")