cables and maps

This commit is contained in:
2026-05-17 15:32:51 -07:00
parent 3f7875084d
commit eecfa49779
12 changed files with 231292 additions and 0 deletions

338
make_internet_cables_map.py Normal file
View File

@@ -0,0 +1,338 @@
#!/usr/bin/env python3
"""Render a Leaflet HTML map combining US data centers, submarine cables,
and city-level network-dominance points from PostGIS.
"""
import argparse
import json
import os
import psycopg2
DB_NAME = "data_centers"
DC_TABLE = "public.us_dc_sample_geocoded"
CABLES_TABLE = "public.internet_cables"
CITY_TABLE = "public.internet_city_dominance"
def connect():
return psycopg2.connect(
host=os.environ["PGWEB_HOST"],
port=os.environ["PGWEB_PORT"],
user=os.environ["PGWEB_USER"],
password=os.environ["PGWEB_PASSWORD"],
dbname=DB_NAME,
)
def load_data_centers(conn):
with conn.cursor() as cur:
cur.execute(
f"""
select
id,
coalesce(provider, ''),
coalesce(facility_name, ''),
coalesce(city, ''),
coalesce(state_code, ''),
longitude,
latitude,
coalesce(geocode_source, '')
from {DC_TABLE}
where longitude is not null and latitude is not null
"""
)
return [
{
"id": r[0],
"provider": r[1],
"facility_name": r[2],
"city": r[3],
"state_code": r[4],
"lon": float(r[5]),
"lat": float(r[6]),
"geocode_source": r[7],
}
for r in cur.fetchall()
]
def load_cables(conn):
with conn.cursor() as cur:
cur.execute(
f"""
select
feature_id,
coalesce(cable_id, ''),
coalesce(name, ''),
coalesce(color, '#888888'),
coalesce(owners, ''),
rfs_year,
decommission_year,
length_km,
coalesce(url, ''),
ST_AsGeoJSON(geom)
from {CABLES_TABLE}
where geom is not null
"""
)
features = []
for r in cur.fetchall():
features.append(
{
"type": "Feature",
"geometry": json.loads(r[9]),
"properties": {
"feature_id": r[0],
"cable_id": r[1],
"name": r[2],
"color": r[3],
"owners": r[4],
"rfs_year": r[5],
"decommission_year": r[6],
"length_km": float(r[7]) if r[7] is not None else None,
"url": r[8],
},
}
)
return {"type": "FeatureCollection", "features": features}
def load_cities(conn, us_only=False):
where = "where geom is not null"
if us_only:
where += " and country = 'US'"
with conn.cursor() as cur:
cur.execute(
f"""
select
id,
coalesce(city, ''),
coalesce(country, ''),
coalesce(country_name, ''),
coalesce(region, ''),
physical_capacity_tbps,
logical_dominance_ips,
longitude,
latitude
from {CITY_TABLE}
{where}
"""
)
return [
{
"id": r[0],
"city": r[1],
"country": r[2],
"country_name": r[3],
"region": r[4],
"tbps": float(r[5]) if r[5] is not None else None,
"ips": int(r[6]) if r[6] is not None else None,
"lon": float(r[7]),
"lat": float(r[8]),
}
for r in cur.fetchall()
]
def render_html(data_centers, cables_geojson, cities, output_path):
payload = json.dumps(
{
"data_centers": data_centers,
"cables": cables_geojson,
"cities": cities,
}
)
html = """<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>US Data Centers + Submarine Cables</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
html, body { height: 100%; margin: 0; font-family: system-ui, -apple-system, Segoe UI, sans-serif; }
#layout { display: grid; grid-template-columns: 300px 1fr; height: 100%; }
#panel { padding: 14px; border-right: 1px solid #ddd; overflow: auto; background: #f8fafb; font-size: 13px; }
#map { height: 100%; width: 100%; }
h1 { margin: 0 0 8px; font-size: 18px; }
h2 { margin: 14px 0 6px; font-size: 13px; text-transform: uppercase; color: #555; letter-spacing: 0.04em; }
.row { display: flex; justify-content: space-between; padding: 2px 0; }
.swatch { width: 12px; height: 12px; display: inline-block; margin-right: 8px; vertical-align: middle; border: 1px solid #ccc; }
label.toggle { display: block; padding: 3px 0; cursor: pointer; }
@media (max-width: 900px) {
#layout { grid-template-columns: 1fr; grid-template-rows: 220px 1fr; }
#panel { border-right: 0; border-bottom: 1px solid #ddd; }
}
</style>
</head>
<body>
<div id="layout">
<div id="panel">
<h1>Data Centers + Cables</h1>
<div class="row"><span>Data centers</span><strong id="dcCount"></strong></div>
<div class="row"><span>Submarine cables</span><strong id="cableCount"></strong></div>
<div class="row"><span>City dominance pts</span><strong id="cityCount"></strong></div>
<h2>Layers</h2>
<label class="toggle"><input type="checkbox" id="tDc" checked> Data centers</label>
<label class="toggle"><input type="checkbox" id="tCables" checked> Submarine cables</label>
<label class="toggle"><input type="checkbox" id="tCities" checked> City dominance</label>
<h2>Data center source</h2>
<div class="row"><span><span class="swatch" style="background:#1f77b4"></span>IM3_Existing_DataCenters</span></div>
<div class="row"><span><span class="swatch" style="background:#2ca02c"></span>US Census Geocoder</span></div>
<div class="row"><span><span class="swatch" style="background:#ff7f0e"></span>Nominatim/OpenStreetMap</span></div>
<div class="row"><span><span class="swatch" style="background:#7f7f7f"></span>Other</span></div>
<h2>City dominance</h2>
<div class="row"><span><span class="swatch" style="background:#9b59b6;border-radius:50%"></span>Sized by physical Tbps</span></div>
</div>
<div id="map"></div>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const DATA = __PAYLOAD__;
function colorForSource(source) {
if (source === 'IM3_Existing_DataCenters') return '#1f77b4';
if (source === 'US Census Geocoder') return '#2ca02c';
if (source === 'Nominatim/OpenStreetMap') return '#ff7f0e';
return '#7f7f7f';
}
function esc(v) {
return String(v == null ? '' : v)
.replaceAll('&','&amp;').replaceAll('<','&lt;').replaceAll('>','&gt;')
.replaceAll('"','&quot;').replaceAll("'", '&#39;');
}
const map = L.map('map', { preferCanvas: true, worldCopyJump: true }).setView([20, -40], 3);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; OpenStreetMap contributors'
}).addTo(map);
const cableLayer = L.geoJSON(DATA.cables, {
style: f => ({
color: f.properties.color || '#888',
weight: 1.4,
opacity: 0.75,
}),
onEachFeature: (feature, layer) => {
const p = feature.properties;
const yrs = [p.rfs_year, p.decommission_year].filter(Boolean).join(' ');
layer.bindPopup(`
<strong>${esc(p.name)}</strong><br>
${p.url ? `<a href="${esc(p.url)}" target="_blank" rel="noopener">${esc(p.url)}</a><br>` : ''}
Owners: ${esc(p.owners)}<br>
${yrs ? `Years: ${esc(yrs)}<br>` : ''}
${p.length_km ? `Length: ${esc(p.length_km.toLocaleString())} km<br>` : ''}
ID: ${esc(p.cable_id || p.feature_id)}
`);
},
}).addTo(map);
const cityLayer = L.layerGroup();
for (const c of DATA.cities) {
const tbps = c.tbps || 0;
const radius = Math.max(2, Math.min(18, Math.sqrt(tbps) * 1.6));
const m = L.circleMarker([c.lat, c.lon], {
radius,
color: '#6c2a86',
fillColor: '#9b59b6',
fillOpacity: 0.45,
weight: 0.8,
});
m.bindPopup(`
<strong>${esc(c.city)}</strong> (${esc(c.country)})<br>
Region: ${esc(c.region)}<br>
Physical capacity: ${esc(tbps.toFixed ? tbps.toFixed(2) : tbps)} Tbps<br>
Logical dominance IPs: ${esc(c.ips ? c.ips.toLocaleString() : '')}
`);
cityLayer.addLayer(m);
}
cityLayer.addTo(map);
const dcLayer = L.layerGroup();
const dcBounds = [];
for (const p of DATA.data_centers) {
const m = L.circleMarker([p.lat, p.lon], {
radius: 3,
color: colorForSource(p.geocode_source),
fillColor: colorForSource(p.geocode_source),
fillOpacity: 0.85,
weight: 0.8,
});
const title = p.facility_name || p.id;
const provider = p.provider || '(unknown provider)';
const cityState = [p.city, p.state_code].filter(Boolean).join(', ');
m.bindPopup(`
<strong>${esc(title)}</strong><br>
Provider: ${esc(provider)}<br>
Location: ${esc(cityState)}<br>
Source: ${esc(p.geocode_source)}
`);
dcLayer.addLayer(m);
dcBounds.push([p.lat, p.lon]);
}
dcLayer.addTo(map);
if (dcBounds.length) map.fitBounds(dcBounds, { padding: [30, 30], maxZoom: 5 });
function toggle(layer, on) {
if (on) { if (!map.hasLayer(layer)) layer.addTo(map); }
else { if (map.hasLayer(layer)) map.removeLayer(layer); }
}
document.getElementById('tDc').addEventListener('change', e => toggle(dcLayer, e.target.checked));
document.getElementById('tCables').addEventListener('change', e => toggle(cableLayer, e.target.checked));
document.getElementById('tCities').addEventListener('change', e => toggle(cityLayer, e.target.checked));
document.getElementById('dcCount').textContent = DATA.data_centers.length.toLocaleString();
document.getElementById('cableCount').textContent = DATA.cables.features.length.toLocaleString();
document.getElementById('cityCount').textContent = DATA.cities.length.toLocaleString();
</script>
</body>
</html>
"""
html = html.replace("__PAYLOAD__", payload)
with open(output_path, "w", encoding="utf-8") as f:
f.write(html)
def parse_args():
p = argparse.ArgumentParser(
description="Render a Leaflet map combining data centers, submarine cables, and city dominance."
)
p.add_argument("--output", default="data_centers_cables_map.html")
p.add_argument(
"--us-cities-only",
action="store_true",
help="Restrict the city-dominance layer to country='US'.",
)
return p.parse_args()
def main():
args = parse_args()
conn = connect()
try:
dcs = load_data_centers(conn)
cables = load_cables(conn)
cities = load_cities(conn, us_only=args.us_cities_only)
finally:
conn.close()
render_html(dcs, cables, cities, args.output)
print(
f"wrote {len(dcs)} data centers, "
f"{len(cables['features'])} cables, "
f"{len(cities)} city points -> {args.output}"
)
if __name__ == "__main__":
main()