Report: crossing readings from traceroutes and cable owners; flat-fit section removed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+43
-1
@@ -157,6 +157,19 @@ tg = {}
|
||||
for f in glob.glob(P("raw/tg/cables/*.json")):
|
||||
c = json.load(open(f))
|
||||
tg[c["id"]] = c
|
||||
CCN = {"NZ": "New Zealand", "AU": "Australia", "AO": "Angola", "BR": "Brazil", "NC": "New Caledonia", "SG": "Singapore", "US": "United States"}
|
||||
anc_cc = {x["fqdn"].split(".")[0]: x["country"] for x in json.load(open(P("raw/ripe_anchors_all.json")))}
|
||||
toks_of = lambda cid: {w for own in ((tg.get(cid) or {}).get("owners") or "").split(",") for w in re.findall(r"[a-z0-9]+", own.lower()) if w not in STOP and len(w) > 1}
|
||||
owns = lambda cid, o: o is not None and any(re.search(rf"(^|[^a-z0-9]){t}([^a-z0-9]|$)", (asname(o) or "").lower()) for t in toks_of(cid))
|
||||
|
||||
|
||||
def short(o):
|
||||
"""'VOCUS-BACKBONE-AS Vocus Connect International Backbone, AU' -> 'Vocus Connect International Backbone'."""
|
||||
n = re.sub(r",\s*[A-Z]{2}$", "", asname(o) or "")
|
||||
m = re.match(r"^(\S+)\s+(?:-\s+)?(.+)$", n)
|
||||
return (m.group(2) if m else n).strip()
|
||||
|
||||
|
||||
xing = []
|
||||
for c in crossings:
|
||||
hops = c["hops"]
|
||||
@@ -172,7 +185,36 @@ for c in crossings:
|
||||
owners = (tg.get(c["cable"]) or {}).get("owners") or ""
|
||||
toks = {w for own in owners.split(",") for w in re.findall(r"[a-z0-9]+", own.lower()) if w not in STOP and len(w) > 1}
|
||||
is_owner = lambda o: o is not None and any(re.search(rf"(^|[^a-z0-9]){t}([^a-z0-9]|$)", (asname(o) or "").lower()) for t in toks)
|
||||
xing.append(dict(src=c["src"], dst=c["dst"], cable=c["cable"], owners=owners or None, jump_ms=c["jump"],
|
||||
# hops around the jump: last announced hop before it, exchange points, first announced hop after it
|
||||
ib = next(i for i in range(c["before"], -1, -1) if hops[i]["ip"] and look.get(hops[i]["ip"], (None,))[0])
|
||||
ia = next((i for i in range(c["after"], len(hops)) if hops[i]["ip"] and look.get(hops[i]["ip"], (None,))[0]), len(hops) - 1)
|
||||
path, seen = [], set()
|
||||
for i in range(ib, ia + 1):
|
||||
ip = hops[i]["ip"]
|
||||
o = look.get(ip, (None,))[0] if ip else None
|
||||
lab = f"AS{o} {short(o)}" if o else (ixp_of[ip] + " exchange" if ip in ixp_of else None)
|
||||
if lab and (not path or path[-1] != lab):
|
||||
path.append(lab)
|
||||
if i == c["before"]:
|
||||
path.append(f"{c['jump']} ms")
|
||||
ca, cb = anc_cc.get(c["src"]), anc_cc.get(c["dst"])
|
||||
ixp_after = next((ixp_of[hops[i]["ip"]] for i in range(c["after"], ia + 1) if hops[i]["ip"] in ixp_of), None)
|
||||
if ca == cb:
|
||||
reading = f"Overland within {CCN.get(ca, ca)}; {short(ob)} at both ends."
|
||||
elif owners and is_owner(ob) and is_owner(oa):
|
||||
reading = f"Carried end to end by {short(ob)}, the cable's owner."
|
||||
elif owners and is_owner(ob):
|
||||
reading = f"Carried across by {short(ob)}, the cable's owner, and handed over to {short(oa)}" + (f" at the {ixp_after.split(' (')[0]} exchange." if ixp_after else ".")
|
||||
else:
|
||||
cands = sorted(cid for cid, t in tg.items() if not t.get("is_planned") and (t.get("rfs_year") or 0) <= 2026
|
||||
and {CCN.get(ca), CCN.get(cb)} <= {lp.get("country") for lp in t.get("landing_points", [])})
|
||||
owned = [cid for cid in cands if owns(cid, ob) or owns(cid, oa)]
|
||||
cnames = ", ".join(tg[cid]["name"].replace(" Cable", "") for cid in cands)
|
||||
carrier = short(ob) if ob == oa else f"{short(ob)} and {short(oa)}"
|
||||
reading = (f"{carrier} operates both ends and owns none of the {len(cands)} cables between {CCN.get(ca)} and {CCN.get(cb)}"
|
||||
+ (f" ({cnames})" if len(cands) <= 5 else "") + "; the crossing runs on capacity held on one of them, the usual arrangement for carriers.") if not owned else \
|
||||
f"{carrier} operates both ends and is an owner of {', '.join(tg[cid]['name'] for cid in owned)}."
|
||||
xing.append(dict(src=c["src"], dst=c["dst"], cable=c["cable"], owners=owners or None, jump_ms=c["jump"], path=path, reading=reading,
|
||||
before=dict(hop=hb, asn=ob, name=asname(ob), owner=is_owner(ob) if owners else None),
|
||||
after=dict(hop=ha, asn=oa, name=asname(oa), owner=is_owner(oa) if owners else None)))
|
||||
|
||||
|
||||
+1
-1
@@ -204,7 +204,7 @@ def nets():
|
||||
ends.sort(key=lambda e: (e["kind"], e["city"]))
|
||||
cname = {c["id"]: c["name"].replace(" Cable", "") for c in json.load(open(P("data/cables.json")))["cables"]}
|
||||
xs = [dict(src_city=city(x["src"].split(":")[-1]), dst_city=city(x["dst"].split(":")[-1]), cable_name=cname.get(x["cable"]) if x["cable"] else None,
|
||||
owners=x["owners"], before=x["before"], after=x["after"]) for x in a["crossings"]]
|
||||
owners=x["owners"], path=x["path"], reading=x["reading"], assumed=x["cable"] == "tasman-global-access-tga-cable") for x in a["crossings"]]
|
||||
return dict(summary=a["summary"], traces=a["traces"], endpoints=ends, gp=sorted(gp.values(), key=lambda x: -x["probes"]),
|
||||
transit=a["transit"], ixps=a["ixps"], crossings=xs)
|
||||
|
||||
|
||||
+64
-15
@@ -74,6 +74,12 @@
|
||||
"cable": "tasman-global-access-tga-cable",
|
||||
"owners": "One NZ, Spark New Zealand, Telstra",
|
||||
"jump_ms": 23.35,
|
||||
"path": [
|
||||
"AS4826 Vocus Connect International Backbone",
|
||||
"23.35 ms",
|
||||
"AS4826 Vocus Connect International Backbone"
|
||||
],
|
||||
"reading": "Vocus Connect International Backbone operates both ends and owns none of the 4 cables between New Zealand and Australia (Hawaiki, Southern Cross Network (SCCN), Southern Cross NEXT, Tasman Global Access (TGA)); the crossing runs on capacity held on one of them, the usual arrangement for carriers.",
|
||||
"before": {
|
||||
"hop": 2,
|
||||
"asn": "4826",
|
||||
@@ -93,6 +99,12 @@
|
||||
"cable": "tasman-global-access-tga-cable",
|
||||
"owners": "One NZ, Spark New Zealand, Telstra",
|
||||
"jump_ms": 23.2,
|
||||
"path": [
|
||||
"AS4826 Vocus Connect International Backbone",
|
||||
"23.2 ms",
|
||||
"AS4826 Vocus Connect International Backbone"
|
||||
],
|
||||
"reading": "Vocus Connect International Backbone operates both ends and owns none of the 4 cables between Australia and New Zealand (Hawaiki, Southern Cross Network (SCCN), Southern Cross NEXT, Tasman Global Access (TGA)); the crossing runs on capacity held on one of them, the usual arrangement for carriers.",
|
||||
"before": {
|
||||
"hop": 1,
|
||||
"asn": "4826",
|
||||
@@ -112,6 +124,12 @@
|
||||
"cable": "south-atlantic-cable-system-sacs",
|
||||
"owners": "Angola Cables",
|
||||
"jump_ms": 60.59,
|
||||
"path": [
|
||||
"AS37468 ANGOLA-CABLES",
|
||||
"60.59 ms",
|
||||
"AS37468 ANGOLA-CABLES"
|
||||
],
|
||||
"reading": "Carried end to end by ANGOLA-CABLES, the cable's owner.",
|
||||
"before": {
|
||||
"hop": 3,
|
||||
"asn": "37468",
|
||||
@@ -131,6 +149,12 @@
|
||||
"cable": "south-atlantic-cable-system-sacs",
|
||||
"owners": "Angola Cables",
|
||||
"jump_ms": 60.79,
|
||||
"path": [
|
||||
"AS37468 ANGOLA-CABLES",
|
||||
"60.79 ms",
|
||||
"AS37468 ANGOLA-CABLES"
|
||||
],
|
||||
"reading": "Carried end to end by ANGOLA-CABLES, the cable's owner.",
|
||||
"before": {
|
||||
"hop": 1,
|
||||
"asn": "37468",
|
||||
@@ -150,6 +174,13 @@
|
||||
"cable": "gondwana-1",
|
||||
"owners": "OPT",
|
||||
"jump_ms": 21.39,
|
||||
"path": [
|
||||
"AS18200 Office des Postes et Telecommunications New-Caledonia",
|
||||
"21.39 ms",
|
||||
"Equinix Sydney (Sydney, AU) exchange",
|
||||
"AS4826 Vocus Connect International Backbone"
|
||||
],
|
||||
"reading": "Carried across by Office des Postes et Telecommunications New-Caledonia, the cable's owner, and handed over to Vocus Connect International Backbone at the Equinix Sydney exchange.",
|
||||
"before": {
|
||||
"hop": 3,
|
||||
"asn": "18200",
|
||||
@@ -169,6 +200,12 @@
|
||||
"cable": "australia-singapore-cable-asc",
|
||||
"owners": "Vocus Communications",
|
||||
"jump_ms": 50.57,
|
||||
"path": [
|
||||
"AS4826 Vocus Connect International Backbone",
|
||||
"50.57 ms",
|
||||
"AS4826 Vocus Connect International Backbone"
|
||||
],
|
||||
"reading": "Carried end to end by Vocus Connect International Backbone, the cable's owner.",
|
||||
"before": {
|
||||
"hop": 1,
|
||||
"asn": "4826",
|
||||
@@ -188,6 +225,12 @@
|
||||
"cable": null,
|
||||
"owners": null,
|
||||
"jump_ms": 45.28,
|
||||
"path": [
|
||||
"AS4826 Vocus Connect International Backbone",
|
||||
"45.28 ms",
|
||||
"AS4826 Vocus Connect International Backbone"
|
||||
],
|
||||
"reading": "Overland within Australia; Vocus Connect International Backbone at both ends.",
|
||||
"before": {
|
||||
"hop": 1,
|
||||
"asn": "4826",
|
||||
@@ -207,6 +250,12 @@
|
||||
"cable": null,
|
||||
"owners": null,
|
||||
"jump_ms": 148.88,
|
||||
"path": [
|
||||
"AS6939 Hurricane Electric LLC",
|
||||
"148.88 ms",
|
||||
"AS6939 Hurricane Electric LLC"
|
||||
],
|
||||
"reading": "Hurricane Electric LLC operates both ends and owns none of the 4 cables between Australia and United States (Hawaiki, Southern Cross Network (SCCN), Southern Cross NEXT, Telstra Endeavour); the crossing runs on capacity held on one of them, the usual arrangement for carriers.",
|
||||
"before": {
|
||||
"hop": 8,
|
||||
"asn": "6939",
|
||||
@@ -4025,19 +4074,14 @@
|
||||
"name": "LEVEL3 - Level 3 Parent, LLC, US",
|
||||
"traces": 33
|
||||
},
|
||||
{
|
||||
"asn": "45177",
|
||||
"name": "DEVOLI-AS-AP Devoli, NZ",
|
||||
"traces": 24
|
||||
},
|
||||
{
|
||||
"asn": "38064",
|
||||
"name": "NZ-AS-NS1-AP ASN for anycast peering of, NZ",
|
||||
"traces": 24
|
||||
},
|
||||
{
|
||||
"asn": "18200",
|
||||
"name": "OPT-NC-AS-AP Office des Postes et Telecommunications New-Caledonia, NC",
|
||||
"asn": "45177",
|
||||
"name": "DEVOLI-AS-AP Devoli, NZ",
|
||||
"traces": 24
|
||||
},
|
||||
{
|
||||
@@ -4045,19 +4089,24 @@
|
||||
"name": "NAUTILE-NC-AS-AP Nautile, NC",
|
||||
"traces": 24
|
||||
},
|
||||
{
|
||||
"asn": "18200",
|
||||
"name": "OPT-NC-AS-AP Office des Postes et Telecommunications New-Caledonia, NC",
|
||||
"traces": 24
|
||||
},
|
||||
{
|
||||
"asn": "64112",
|
||||
"name": "PIT CHILE SP, CL",
|
||||
"traces": 24
|
||||
},
|
||||
{
|
||||
"asn": "12956",
|
||||
"name": "TELXIUS TELEFONICA GLOBAL SOLUTIONS SL, ES",
|
||||
"asn": "4270",
|
||||
"name": "Red de Interconexion Universitaria, AR",
|
||||
"traces": 24
|
||||
},
|
||||
{
|
||||
"asn": "4270",
|
||||
"name": "Red de Interconexion Universitaria, AR",
|
||||
"asn": "12956",
|
||||
"name": "TELXIUS TELEFONICA GLOBAL SOLUTIONS SL, ES",
|
||||
"traces": 24
|
||||
},
|
||||
{
|
||||
@@ -4071,13 +4120,13 @@
|
||||
"traces": 24
|
||||
},
|
||||
{
|
||||
"asn": "55685",
|
||||
"name": "JLM-AS-ID PT Jala Lintas Media, ID",
|
||||
"asn": "4622",
|
||||
"name": "ID-NIC Indonesia Network Information Center, ID",
|
||||
"traces": 24
|
||||
},
|
||||
{
|
||||
"asn": "4622",
|
||||
"name": "ID-NIC Indonesia Network Information Center, ID",
|
||||
"asn": "55685",
|
||||
"name": "JLM-AS-ID PT Jala Lintas Media, ID",
|
||||
"traces": 24
|
||||
},
|
||||
{
|
||||
|
||||
+13
-55
File diff suppressed because one or more lines are too long
+12
-54
@@ -304,21 +304,11 @@ summary{cursor:pointer;font:600 14px var(--display);text-transform:uppercase;let
|
||||
<p class="note" id="netnote"></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="sec">Best attainable flat arrangements</h2>
|
||||
<p class="lede">Latency bounds distances only from above, so a free flat arrangement can satisfy any set of measurements by shrinking. The informative question is how far a given flat map would have to be corrected. Each city may move at most the stated distance from where the map places it; the arrangement shown minimises the total excess over all measured pairs at the speed of light in fibre, using straight lines (the shortest possible cable). The final setting is the smallest correction at which no route remains inconsistent. The problem is convex, so this minimum is global.</p>
|
||||
<div class="ffctl"><label for="ffb">Maximum correction per city</label><input id="ffb" type="range" min="0" max="6" step="1" value="0"><output id="ffbv" for="ffb"></output></div>
|
||||
<div class="ff" id="ff"></div>
|
||||
<div class="ffkey"><span><i class="o"></i>position on the map</span><span><i class="f"></i>corrected position</span><span><i style="background:var(--dim)"></i>correction</span><span><i style="background:var(--fib)"></i>faster than light in fibre</span><span><i style="background:var(--bad)"></i>faster than light in vacuum</span></div>
|
||||
<div class="tw" style="margin-top:16px"><table class="mini" id="fftab"></table></div>
|
||||
<p class="note" id="ffnote"></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="sec">Independent verification</h2>
|
||||
<p class="lede">To reproduce a measurement, ping one of the anchors from any computer and enter the minimum round-trip time. Local network and access delays can only increase the measured value.</p>
|
||||
<form id="byo" class="byo" autocomplete="off">
|
||||
<label for="byoA">Location<input id="byoA" list="byocities" placeholder="City, or lat, lon"></label>
|
||||
<label for="byoA">Location<input id="byoA" list="byocities" placeholder="Type a city, or lat, lon"></label>
|
||||
<label for="byoB">Anchor<select id="byoB"></select></label>
|
||||
<label for="byoT">Minimum RTT, ms<input id="byoT" type="number" min="0" step="0.1"></label>
|
||||
<button class="go" type="submit">Evaluate</button>
|
||||
@@ -704,7 +694,7 @@ function setSource(src){
|
||||
const worst=LP.slice().sort((x,y)=>(rt(y).fl-y.T)-(rt(x).fl-x.T))[0];
|
||||
S.sel=(LP.find(p=>p.a+"-"+p.b===key)||LP.find(p=>(p.a===ia&&p.b===ib)||(p.a===ib&&p.b===ia))||worst).idx;
|
||||
document.getElementById("srcsel").value=src;
|
||||
bgKey="";readout();renderCard();renderScore();drawSM();drawSpeed();renderLadder();renderEx();renderMethod();renderFlat();frame();
|
||||
bgKey="";readout();renderCard();renderScore();drawSM();drawSpeed();renderLadder();renderEx();renderMethod();frame();
|
||||
}
|
||||
document.getElementById("srcsel").onchange=e=>setSource(e.target.value);
|
||||
function setMode(md){S.mode=md;document.querySelectorAll("#mode button").forEach(b=>b.classList.toggle("on",b.dataset.mode===md));legend();readout();renderCard();renderScore();drawSM();drawSpeed();frame()}
|
||||
@@ -895,6 +885,8 @@ function byoInit(){
|
||||
const sel=document.getElementById("byoB");sel.innerHTML=BA.map((a,i)=>`<option value="${i}">${esc(a[0])}, ${a[1]} · ${a[4]}</option>`).join("");
|
||||
sel.value=String(BA.findIndex(a=>a[0]==="Sydney"));document.getElementById("byoA").value="Auckland, NZ";document.getElementById("byoT").value="25.2";
|
||||
sel.onchange=byoCmd;document.getElementById("byo").addEventListener("submit",byoRun);byoCmd();byoRun();
|
||||
/* the example location clears on first focus, so the full city list appears while typing */
|
||||
document.getElementById("byoA").addEventListener("focus",e=>{if(byoExample&&e.target.value==="Auckland, NZ")e.target.value=""},{once:true});
|
||||
}
|
||||
|
||||
/* ---------- raw records, code and data ---------- */
|
||||
@@ -907,7 +899,7 @@ function renderCode(){
|
||||
const gp=D.live.runs.filter(r=>!r.source),vps=[...new Set(D.live.cells.filter(c=>/Vultr/.test(c[5])).map(c=>c[6]))].sort();
|
||||
const f=(p,t)=>lnk(SRCV+"src/"+p,t||p),py=[["models.py","distance on each model, constants"],["measure.py","Globalping measurements"],["vps.py","Vultr servers: create, measure, collect, delete"],["vps_analyze.py","Vultr logs to measurements"],
|
||||
["analyze_wn.py","WonderNetwork archive: statistics, mislocated servers"],["cables.py","published cable lengths on each model"],["network.py","cable-and-land routing on each model"],["trace.py","RIPE Atlas sea crossings"],
|
||||
["flatfit.py","best attainable flat arrangements"],["asmap.py","network attribution from public BGP data"],["pingmap.py","shape fit without positions"],["build.py","this page"],["export.py","JSON data hand-off"]];
|
||||
["asmap.py","network attribution from public BGP data"],["pingmap.py","shape fit without positions"],["build.py","this page"],["export.py","JSON data hand-off"]];
|
||||
document.getElementById("code").innerHTML=`
|
||||
<div>Repository</div><div>${lnk(GIT,GIT.replace("https://",""))}</div>
|
||||
<div>Data hand-off</div><div>${lnk(RAWV+"cable_latency_survey_data_v2.json","cable_latency_survey_data_v2.json")} (every measurement, site, route and cable path) · ${lnk(RAWV+"cable_latency_survey_data.json","version 1")}</div>
|
||||
@@ -916,14 +908,13 @@ function renderCode(){
|
||||
<div>Residential connection</div><div>${f("data/home/ping.log","ping log")}</div>
|
||||
<div>RIPE Atlas</div><div>${f("data/trace.json","sea-crossing traceroutes")} (hops, timings, measurement and probe IDs)</div>
|
||||
<div>WonderNetwork</div><div>${lnk("https://wp-public-data.s3.amazonaws.com/pings/pings-2020-07-19-2020-07-20.csv.gz","original archive")} · ${f("data/wn_clean.json","per-pair statistics")}</div>
|
||||
<div>Derived</div><div>${f("data/network.json","routes")} · ${f("data/cables.json","cables")} · ${f("data/flatfit.json","flat arrangements")} · ${f("data/asmap.json","networks")} · ${f("data/pingmap.json","shape fit")}</div>
|
||||
<div>Derived</div><div>${f("data/network.json","routes")} · ${f("data/cables.json","cables")} · ${f("data/asmap.json","networks")} · ${f("data/pingmap.json","shape fit")}</div>
|
||||
<div>Pipeline</div><div>${py.map(([p,t])=>`${f(p)} ${t}`).join("<br>")}</div>
|
||||
<div>Licences</div><div>${f("../LICENSE","MIT")} (code) · ${f("../LICENSE-DATA.md","CC BY 4.0 and third-party terms")} (data)</div>`;
|
||||
document.getElementById("repro").textContent=`git clone ${GIT}.git
|
||||
cd cable-latency-survey/src
|
||||
./fetch_raw.sh # third-party inputs that are not redistributed
|
||||
python3 build.py # this page, from the files in data/
|
||||
python3 flatfit.py # flat arrangements (requires cvxpy)
|
||||
python3 export.py # the JSON data hand-off`;
|
||||
}
|
||||
|
||||
@@ -940,44 +931,11 @@ function renderNet(){
|
||||
document.getElementById("netanc").innerHTML=`<thead><tr><th>Anchor</th><th>Host, per RIPE Atlas</th><th>BGP origin</th><th>RPKI</th></tr></thead><tbody>`+anc.map(e=>`<tr><td>${esc(e.city)}<br><span style="color:var(--dim)">${esc(e.id)}</span></td><td>${/^\d*$/.test(e.company)?"not stated":esc(e.company)} (${asn(e.claimed_asn)})</td><td class="${e.match?"":"fib"}">${asn(e.bgp_origin)} ${esc(e.name)}<br><span style="color:var(--dim)">${esc(e.prefix)}</span></td><td>${e.rpki.join(", ")}</td></tr>`).join("")+"</tbody>";
|
||||
document.getElementById("nettr").innerHTML=`<thead><tr><th>Network</th><th>Traceroutes</th></tr></thead><tbody>`+N.transit.map(x=>`<tr><td>${asn(x.asn)} ${esc(x.name)}</td><td class="num">${x.traces}</td></tr>`).join("")+"</tbody>";
|
||||
document.getElementById("netix").innerHTML=`<thead><tr><th>Exchange point (PeeringDB)</th><th>Hop addresses</th></tr></thead><tbody>`+N.ixps.map(x=>`<tr><td>${esc(x.name)}</td><td class="num">${x.addresses}</td></tr>`).join("")+"</tbody>";
|
||||
const side=s=>s.asn?`${asn(s.asn)} ${esc(s.name)}${s.owner===true?' · <span class="ok">cable owner</span>':s.owner===false?' · <span class="fib">not a listed owner</span>':""}`:"—";
|
||||
document.getElementById("netx").innerHTML=`<thead><tr><th>Crossing</th><th>Cable and owners (TeleGeography)</th><th>Last announced hop before the jump</th><th>First announced hop after it</th></tr></thead><tbody>`+
|
||||
N.crossings.map(x=>`<tr><td>${esc(x.src_city)} → ${esc(x.dst_city)}</td><td>${x.cable_name?`${esc(x.cable_name)}: ${esc(x.owners||"—")}`:"not identified"}</td><td>${side(x.before)}</td><td>${side(x.after)}</td></tr>`).join("")+"</tbody>";
|
||||
document.getElementById("netnote").innerHTML=`AS names are the registrants' designations. The two anchors announced by a different AS than their name states are both hosted by DigiCert, which operates both numbers. RPKI "unknown" means the address holder has published no route origin authorisation; no announcement was invalid. Ownership tokens are matched against AS names, so a network that leases capacity on a cable appears as "not a listed owner". Code: ${lnk(SRCV+"src/asmap.py","asmap.py")}; results: ${lnk(SRCV+"src/data/asmap.json","asmap.json")}, ${lnk(SRCV+"src/data/rpki.json","rpki.json")}.`;
|
||||
document.getElementById("netx").innerHTML=`<thead><tr><th>Crossing</th><th>Cable and owners (TeleGeography)</th><th>Networks across the jump</th><th>Reading</th></tr></thead><tbody>`+
|
||||
N.crossings.map(x=>`<tr><td>${esc(x.src_city)} → ${esc(x.dst_city)}</td><td>${x.cable_name?(x.assumed?`not confirmed; ${esc(x.cable_name)} assumed for its length`:`${esc(x.cable_name)}: ${esc(x.owners||"—")}`):"not identified"}</td><td>${x.path.map(esc).join(" → ")}</td><td>${esc(x.reading)}</td></tr>`).join("")+"</tbody>";
|
||||
document.getElementById("netnote").innerHTML=`AS names are the registrants' designations. The two anchors announced by a different AS than their name states are both hosted by DigiCert, which operates both numbers. RPKI "unknown" means the address holder has published no route origin authorisation; no announcement was invalid. Cable owners are from TeleGeography; a network that operates both ends of a crossing without owning the cable holds capacity on it, which is how most carriers use submarine cables. Code: ${lnk(SRCV+"src/asmap.py","asmap.py")}; results: ${lnk(SRCV+"src/data/asmap.json","asmap.json")}, ${lnk(SRCV+"src/data/rpki.json","rpki.json")}.`;
|
||||
}
|
||||
|
||||
/* ---------- best attainable flat arrangements ---------- */
|
||||
let FFB=0;
|
||||
const FFM=[["ae","North-pole AE","ae"],["dp","Dual-pole AE","dualpole"],["merc","Mercator","mercator"]];
|
||||
function renderFlat(){
|
||||
if(!D.flat)return;
|
||||
const box=document.getElementById("ff"),F=D.flat.fits[S.src],bud=D.flat.budgets_km;
|
||||
if(!box.children.length)box.innerHTML=FFM.map(([m,n])=>`<div><h3>${n}</h3><canvas id="ff-${m}" aria-label="Corrected ${n} arrangement"></canvas><p id="ffp-${m}"></p></div>`).join("");
|
||||
document.getElementById("ffbv").textContent=FFB===0?"0 km (as mapped)":FFB<bud.length?`${fint(bud[FFB])} km`:"minimum for full consistency";
|
||||
for(const [m,n,k] of FFM){
|
||||
const f=F[k],row=f.budgets[Math.min(FFB,f.budgets.length-1)],c=document.getElementById("ff-"+m),r=c.getBoundingClientRect(),w=r.width,h=r.height;if(!w)continue;
|
||||
c.width=Math.round(w*DPR);c.height=Math.round(h*DPR);const g=c.getContext("2d");g.setTransform(DPR,0,0,DPR,0,0);
|
||||
const rad=w/2-8,proj=baseProj(m).scale(rad/Math.PI).translate([w/2,h/2]),path=d3.geoPath(proj,g);
|
||||
g.fillStyle="#050a12";g.fillRect(0,0,w,h);
|
||||
g.beginPath();if(m==="merc")g.rect(w/2-rad,h/2-rad,2*rad,2*rad);else g.arc(w/2,h/2,rad,0,2*Math.PI);g.fillStyle=C.deep;g.fill();g.strokeStyle="#1a2a40";g.lineWidth=1;g.stroke();
|
||||
g.beginPath();path(grat);g.strokeStyle=C.grat;g.stroke();g.beginPath();path(landGeo);g.fillStyle=C.land;g.fill();
|
||||
const at=f.sites.map(s=>proj([LS[s].lon,LS[s].lat])),to=row.pos.map(q=>proj(q)),ix=Object.fromEntries(f.sites.map((s,i)=>[s,i]));
|
||||
g.lineWidth=1.5;for(const [a,b,t] of row.inconsistent){const p1=to[ix[a]],p2=to[ix[b]];g.strokeStyle=t===2?C.bad:C.fib;g.beginPath();g.moveTo(p1[0],p1[1]);g.lineTo(p2[0],p2[1]);g.stroke()}
|
||||
g.strokeStyle=C.dim;g.lineWidth=1;for(let i=0;i<at.length;i++){if(Math.hypot(at[i][0]-to[i][0],at[i][1]-to[i][1])<1)continue;g.beginPath();g.moveTo(at[i][0],at[i][1]);g.lineTo(to[i][0],to[i][1]);g.stroke()}
|
||||
for(let i=0;i<at.length;i++){g.beginPath();g.arc(at[i][0],at[i][1],3.5,0,2*Math.PI);g.strokeStyle=C.dim;g.lineWidth=1.2;g.stroke();g.beginPath();g.arc(to[i][0],to[i][1],2.6,0,2*Math.PI);g.fillStyle=C.sel;g.fill()}
|
||||
const mv=f.sites.map((s,i)=>[Math.hypot(at[i][0]-to[i][0],at[i][1]-to[i][1]),i]).filter(x=>x[0]>6).sort((x,y)=>y[0]-x[0]).slice(0,4);
|
||||
g.font=`11px ${FONT()}`;g.textAlign="left";const boxes=[];for(const [,i] of mv){const t=LS[f.sites[i]].name.replace(/ \(.*\)$/,""),tw=g.measureText(t).width,x=Math.max(4,Math.min(to[i][0]+6,w-6-tw)),y=to[i][1]-6,bx=[x-2,y-11,x+tw+2,y+3];if(boxes.some(q=>bx[0]<q[2]&&bx[2]>q[0]&&bx[1]<q[3]&&bx[3]>q[1]))continue;boxes.push(bx);g.lineWidth=3;g.strokeStyle="#050a12";g.strokeText(t,x,y);g.fillStyle=C.ink;g.fillText(t,x,y)}
|
||||
const nb=row.inconsistent.length,nv=row.inconsistent.filter(x=>x[2]===2).length;
|
||||
document.getElementById("ffp-"+m).innerHTML=`<b class="num ${nv?"bad":nb?"fib":"ok"}">${nb}</b> of ${f.routes} routes inconsistent${nb?` (${nv} faster than light in vacuum)`:""}${row.moved_max_km>0.5?` · largest correction <b class="num">${fint(row.moved_max_km)} km</b>`:""}`;
|
||||
}
|
||||
const one=(f,m,sp)=>{const b=f.one_pair_bound_km[sp];if(!(b.km>0))return"none needed";const [i,j]=b.pair,p=LP.find(q=>(q.a===Math.min(i,j)&&q.b===Math.max(i,j))),U=(sp==="fibre"?CF:CV)*p.T/2000+25,X=dist(m,LS[i],LS[j]);
|
||||
return`${fint(b.km)} km: ${esc(LS[i].name)} ↔ ${esc(LS[j].name)} are ${fint(X)} km apart on the map; ${fmt(p.T)} ms allows at most ${fint(U)} km`};
|
||||
document.getElementById("fftab").innerHTML=`<thead><tr><th>Map</th><th>Minimum correction, light in fibre</th><th>Minimum correction, light in vacuum</th><th>Bound from a single pair (no solver), fibre</th></tr></thead><tbody>`+
|
||||
FFM.map(([m,n,k])=>{const f=F[k];return`<tr><td>${n}</td><td class="num">${fint(f.min_correction_km.fibre)} km</td><td class="num">${fint(f.min_correction_km.vacuum)} km</td><td>${one(f,m,"fibre")}</td></tr>`}).join("")+"</tbody>";
|
||||
document.getElementById("ffnote").innerHTML=`Routes measured by ${SRC[S.src].label}; ${D.flat.slack_km} km is allowed at each end, as elsewhere on this page. A single pair bounds the correction from below without any solver: its two cities can approach each other by at most twice the correction. The minimum itself is computed with an interior-point solver (cvxpy, Clarabel); the arrangement it returns satisfies every measured pair. Code: ${lnk(SRCV+"src/flatfit.py","flatfit.py")}; results: ${lnk(SRCV+"src/data/flatfit.json","flatfit.json")}.`;
|
||||
}
|
||||
document.getElementById("ffb").addEventListener("input",e=>{FFB=+e.target.value;renderFlat()});
|
||||
|
||||
/* ---------- method ---------- */
|
||||
function renderMethod(){
|
||||
const best=rows=>rows.reduce((b,r)=>r[1]<b[1]?r:b);
|
||||
@@ -1014,7 +972,7 @@ ${vr?`<div>Vultr servers</div><div>Virtual servers rented for this survey in ${v
|
||||
}
|
||||
|
||||
/* ---------- boot ---------- */
|
||||
function layoutAll(){resize();fitKm();drawSM();renderEx();renderFlat();drawSpeed();frame();viewBtns()}
|
||||
function layoutAll(){resize();fitKm();drawSM();renderEx();drawSpeed();frame();viewBtns()}
|
||||
let rszT=0;window.addEventListener("resize",()=>{clearTimeout(rszT);rszT=setTimeout(layoutAll,120)});
|
||||
document.querySelectorAll(".models button").forEach(b=>b.classList.toggle("on",b.dataset.v===S.view));
|
||||
const ia=LS.findIndex(s=>s.name==="Sydney"),ib=LS.findIndex(s=>s.name==="Auckland");
|
||||
@@ -1024,6 +982,6 @@ document.getElementById("srcsel").value=S.src;
|
||||
{document.getElementById("eyeb").textContent=`${LS.length} sites · ${Object.keys(D.netlines).length} cables · 4 models`;
|
||||
document.getElementById("step1").textContent=`Globalping probes, rented Vultr servers and a residential connection measured round-trip times to RIPE Atlas anchors and to each other across ${LS.length} sites. The minimum for each pair is used.`}
|
||||
legend();renderScore();renderLadder();buildSM();renderX();renderNet();byoInit();renderMethod();renderCode();
|
||||
readout();renderCard();resize();renderEx();renderFlat();drawSM();drawSpeed();kick();frame();
|
||||
document.fonts&&document.fonts.ready.then(()=>{resize();drawSM();renderEx();renderFlat();drawSpeed();frame();viewBtns()});
|
||||
readout();renderCard();resize();renderEx();drawSM();drawSpeed();kick();frame();
|
||||
document.fonts&&document.fonts.ready.then(()=>{resize();drawSM();renderEx();drawSpeed();frame();viewBtns()});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user