Skip to content

Commit 6607f1b

Browse files
Map UX: layer naming, honest gmaps links, tooltip labels + border
- Legend renamed Tier->Layer; the two network entries are now distinguishable: 'Layer 2 - Network sites (towers & antennas)' (points) vs 'Layer 2 - Network paths (routes)' (arcs) - Google Maps links are precision-aware: coordinate deep link only for exact/approximate pins; name+city text search for city_level pins (their coords are centroids, not the site); none for symbolic pins - Tooltips show human labels (Site, Operator / venue, Power (MW), Pin precision, Source URL...) instead of raw column names; NaN values cleaned; popover gets a border - Flobecq (T2-013) precision upgraded to exact: user report of 'a farm' checked — OSM surveyed node 3646963462 ('Ancien station de relais US/OTAN') sits 7m from the pin; the tower stands in open fields on the Pottelberg ridge, pin is correct Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 9a63ef0 commit 6607f1b

3 files changed

Lines changed: 64 additions & 37 deletions

File tree

build_map.py

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import argparse
1717
import re
18+
import urllib.parse
1819
from pathlib import Path
1920

2021
import pandas as pd
@@ -40,6 +41,10 @@
4041
FULLSCREEN_SNIPPET = """<style>
4142
html, body { width: 100%; height: 100%; }
4243
.keplergl-widget-container { width: 100vw !important; height: 100vh !important; }
44+
.map-popover {
45+
border: 1px solid rgba(255, 255, 255, 0.28) !important;
46+
border-radius: 6px !important;
47+
}
4348
</style>
4449
<script>
4550
window.addEventListener('load', function () {
@@ -49,9 +54,34 @@
4954
"""
5055

5156
TIER_LABELS = {
52-
"execution": "Tier 1 — Execution (exchange colo)",
53-
"network": "Tier 2 — Network (microwave/shortwave)",
54-
"research": "Tier 3 — Research (ML compute)",
57+
"execution": "Layer 1 — Execution (exchange colo)",
58+
"network": "Layer 2 — Network sites (towers & antennas)",
59+
"research": "Layer 3 — Research (ML compute)",
60+
}
61+
62+
# Column names -> human labels shown in the kepler tooltip (kepler displays
63+
# raw column names, so the display DataFrames are renamed before add_data).
64+
SITE_DISPLAY = {
65+
"site_name": "Site",
66+
"operator_or_venue": "Operator / venue",
67+
"firms_linked": "Firms linked",
68+
"capacity_note": "Notes",
69+
"power_mw": "Power (MW)",
70+
"status": "Status",
71+
"coord_precision": "Pin precision",
72+
"confidence": "Confidence",
73+
"evidence_note": "Evidence",
74+
"evidence_url": "Source URL",
75+
"gmaps_url": "Google Maps",
76+
}
77+
PATH_DISPLAY = {
78+
"route": "Route",
79+
"operator": "Operator",
80+
"medium": "Medium",
81+
"status": "Status",
82+
"confidence": "Confidence",
83+
"evidence_note": "Evidence",
84+
"evidence_url": "Source URL",
5585
}
5686

5787
# Hex colors per tier, referenced in the kepler config below.
@@ -61,8 +91,8 @@
6191
"research": [70, 130, 240], # blue — the compute layer
6292
}
6393

64-
PATHS_LABEL = "Tier 2 — Paths (arcs)"
65-
PATHS_COLOR = [250, 190, 60] # amber, matching the network tier
94+
PATHS_LABEL = "Layer 2 — Network paths (routes)"
95+
PATHS_COLOR = [250, 190, 60] # amber, matching the network layer
6696

6797

6898
def load_sites(csv_path: Path) -> pd.DataFrame:
@@ -73,21 +103,31 @@ def load_sites(csv_path: Path) -> pd.DataFrame:
73103
if missing:
74104
raise ValueError(f"CSV missing required columns: {missing}")
75105

76-
# Google Maps deep link per pin — the "zoom in for detail" handoff.
77-
df["gmaps_url"] = (
78-
"https://www.google.com/maps/search/?api=1&query="
79-
+ df["lat"].round(6).astype(str)
80-
+ ","
81-
+ df["lon"].round(6).astype(str)
82-
)
106+
# Google Maps handoff, honest about pin precision: coordinate deep link
107+
# only where the pin is placed (exact/approximate); a name+city text
108+
# search for city-level pins (the coords are a centroid, not the site);
109+
# nothing for symbolic pins (they mark a firm, not a place).
110+
def gmaps(row):
111+
if row.coord_precision in ("exact", "approximate"):
112+
return ("https://www.google.com/maps/search/?api=1&query="
113+
f"{row.lat:.6f},{row.lon:.6f}")
114+
if row.coord_precision == "city_level":
115+
q = urllib.parse.quote_plus(f"{row.site_name} {row.city}")
116+
return f"https://www.google.com/maps/search/?api=1&query={q}"
117+
return ""
118+
119+
df["gmaps_url"] = df.apply(gmaps, axis=1)
83120
df["tier_label"] = df["tier"].map(TIER_LABELS).fillna(df["tier"])
84121

85122
# Fail loudly on bad coordinates rather than silently dropping pins.
86123
bad = df[(df["lat"].abs() > 90) | (df["lon"].abs() > 180) | df["lat"].isna()]
87124
if not bad.empty:
88125
raise ValueError(f"Bad coordinates for: {bad['site_id'].tolist()}")
89126

90-
return df
127+
# Tooltip hygiene: no NaN in display fields, no trailing ".0" on power.
128+
df["power_mw"] = df["power_mw"].map(
129+
lambda v: "" if pd.isna(v) else f"{v:g}")
130+
return df.fillna("")
91131

92132

93133
def load_paths(paths_csv: Path, sites: pd.DataFrame) -> pd.DataFrame | None:
@@ -162,26 +202,8 @@ def kepler_config(df: pd.DataFrame, with_paths: bool = False) -> dict:
162202
}
163203
)
164204

165-
tooltip_fields = [
166-
{"name": f}
167-
for f in [
168-
"site_name",
169-
"operator_or_venue",
170-
"firms_linked",
171-
"capacity_note",
172-
"power_mw",
173-
"status",
174-
"confidence",
175-
"evidence_note",
176-
"evidence_url",
177-
"gmaps_url",
178-
]
179-
]
180-
paths_tooltip = [
181-
{"name": f}
182-
for f in ["route", "operator", "medium", "status", "confidence",
183-
"evidence_note", "evidence_url"]
184-
]
205+
tooltip_fields = [{"name": label} for label in SITE_DISPLAY.values()]
206+
paths_tooltip = [{"name": label} for label in PATH_DISPLAY.values()]
185207
fields_to_show = {t: tooltip_fields for t in TIER_COLORS}
186208
if with_paths:
187209
fields_to_show["paths"] = paths_tooltip
@@ -220,9 +242,10 @@ def build(csv_path: Path, out_path: Path, paths_csv: Path | None = None) -> None
220242
for tier in TIER_COLORS:
221243
subset = df[df["tier"] == tier].reset_index(drop=True)
222244
if not subset.empty:
223-
m.add_data(data=subset, name=tier)
245+
m.add_data(data=subset.rename(columns=SITE_DISPLAY), name=tier)
224246
if paths is not None:
225-
m.add_data(data=paths, name="paths")
247+
m.add_data(data=paths.fillna("").rename(columns=PATH_DISPLAY),
248+
name="paths")
226249

227250
m.save_to_html(file_name=str(out_path), read_only=False)
228251

data/sites_seed.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ T2-009,Tour de Reuze (Dunkerque),network,"Rooftop antenna site on Tour de Reuze
4141
T2-010,Dunkerque port grain elevator (FR),network,"Grain elevator, Port of Dunkerque",McKay Brothers,51.03,2.34,city_level,Dunkerque,France,"McKay Brothers dishes on a port grain elevator for the Channel crossing, documented 2016. Current tenancy unverified.",,active,press,Sniper in Mahwah (2016): McKay Brothers dishes on a Dunkerque port grain elevator; exact silo not identified — city-level pin,https://sniperinmahwah.wordpress.com/2016/01/26/hft-in-the-banana-land/,reported,
4242
T2-011,Oostduinkerke rooftop relay (BE),network,"±40-meter residential building, Oostduinkerke",Vigilant Global (DRW),51.114,2.674,city_level,Oostduinkerke (Koksijde),Belgium,"Belgian end of Vigilant's PROPOSED direct Channel crossing toward Richborough (Ofcom path data, licenses April 2015). The UK-side 322m mast was refused in January 2017, so the crossing as proposed was never completed.",,historical,press,"Sniper in Mahwah (2016): Belgian end of Vigilant's proposed direct crossing to Richborough (Ofcom path data, Apr 2015 licenses); UK mast refused — historical",https://sniperinmahwah.wordpress.com/2016/01/26/hft-in-the-banana-land/,reported,
4343
T2-012,"Ramsgate relay cluster (Kent, UK)",network,Multiple masts/structures around Ramsgate (per planning applications),Optiver; Jump Trading; McKay Brothers,51.333,1.416,city_level,Ramsgate,United Kingdom,"Preferred UK landing area for shorter Channel crossings from Houtem/Dunkerque, documented via planning applications 2014-2016. Individual mast locations not pinned in fetched sources.",,active,press,"Sniper in Mahwah / Visionscarto: preferred UK landing area for shorter Channel crossings; documented via 2014-2016 planning applications, individual masts not singly located",https://sniperinmahwah.wordpress.com/2016/01/26/hft-in-the-banana-land/,reported,
44-
T2-013,"Flobecq DCS tower (Hainaut, BE)",network,Former US Army Defense Communication System tower,Jump Trading; Flow Traders; Optiver,50.763892,3.744626,approximate,Flobecq,Belgium,Old US Army DCS microwave tower on the London-Frankfurt line; HFT dish colocations documented 2014-2015 via Belgian antenna cadastre dossiers. Current tenancy unverified.,,active,press,Sniper in Mahwah V: ex-US Army DCS tower on the London-Frankfurt line; HFT dish colocations documented 2014-2015 via Belgian cadastre dossiers,https://sniperinmahwah.wordpress.com/2015/01/14/hft-in-my-backyard-v/,reported,
44+
T2-013,"Flobecq DCS tower (Hainaut, BE)",network,Former US Army Defense Communication System tower,Jump Trading; Flow Traders; Optiver,50.763892,3.744626,exact,Flobecq,Belgium,Old US Army DCS microwave tower on the London-Frankfurt line; HFT dish colocations documented 2014-2015 via Belgian antenna cadastre dossiers. Current tenancy unverified.,,active,press,Sniper in Mahwah V (ex-DEB technician coords) + OSM surveyed node 3646963462 'Ancien station de relais US/OTAN' 7m away — two independent sources within 10m,https://sniperinmahwah.wordpress.com/2015/01/14/hft-in-my-backyard-v/,reported,
4545
T2-014,Sint-Pieters-Leeuw tower (BE),network,"Norkring broadcast tower, Sint-Pieters-Leeuw",McKay Brothers,50.782,4.244,city_level,Sint-Pieters-Leeuw,Belgium,Norkring broadcast tower described by the source as the second tallest building in Belgium; McKay Brothers dishes documented February 2015 via Belgian antenna cadastre dossier. Current tenancy unverified.,,active,press,"Sniper in Mahwah ('the last tower'): McKay dishes on the Norkring broadcast tower, documented Feb 2015",https://sniperinmahwah.wordpress.com/2015/02/03/hft-in-my-backyard-the-last-tower/,reported,
4646
T2-015,Wavre tower (BE),network,Wavre broadcast tower (third tallest structure in Belgium),Jump Trading,50.717,4.601,city_level,Wavre,Belgium,"Three Jump-owned dishes documented/photographed on the tower in 2014, two pointing to Hannut. Current tenancy unverified.",,active,press,"Sniper in Mahwah II: three Jump-owned dishes photographed 2014, two pointing to Hannut",https://sniperinmahwah.wordpress.com/2014/09/25/hft-in-my-backyard-ii/,reported,
4747
T2-016,Hannut tower (BE),network,"Relay tower, Hannut",Jump Trading; Optiver,50.671,5.079,city_level,Hannut,Belgium,Hop on Jump's Belgian chain toward Frankfurt; Jump dishes and Optiver colocation documented 2014 via cadastre dossiers. Current tenancy unverified.,,active,press,Sniper in Mahwah II: hop on Jump's Belgian chain toward Frankfurt; Jump dishes + Optiver colocation via cadastre dossiers (2014),https://sniperinmahwah.wordpress.com/2014/09/25/hft-in-my-backyard-ii/,reported,

docs/index.html

Lines changed: 5 additions & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)