File size: 3,387 Bytes
6f1f64c
 
 
 
 
68d8285
 
6f1f64c
 
68d8285
 
 
 
6f1f64c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68d8285
6f1f64c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68d8285
 
 
 
6f1f64c
 
 
 
 
68d8285
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"""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")