// Road cams layer (Wave 6.2) — official public traffic cameras from keyless // government APIs. The "511" platform is a North-America standard, so one // adapter covers many regions; today: Alberta + Ontario (~1,100 cams). London // has its own dedicated TfL layer. JSON comes via proxy/cams/ (no CORS // upstream); snapshot IMAGES load directly in the InfoBox (no CORS needed). // // Add a region = one entry in REGIONS here + serve.py CAMS_REGIONS + a prod // nginx location. Several more 511s (Georgia, Nevada, New England…) just need // their free API keys. const REGIONS = [ { id: 'alberta', name: 'Alberta' }, { id: 'ontario', name: 'Ontario' }, ]; export default function create(ctx) { const { viewer, Cesium, lib, ui } = ctx; const ds = new Cesium.CustomDataSource('roadcams'); ds.show = false; viewer.dataSources.add(ds); let enabled = false; let loaded = false; ui.addLayer('roadcams', 'Road cams (511 network)', false, (on) => { enabled = on; ds.show = on; if (on && !loaded) load(); else if (on) ui.setStatus('roadcams', statusText(), 'ok'); else ui.setStatus('roadcams', '', 'ok'); }, 'Context'); const okRegions = []; function statusText() { return `${ds.entities.values.length} cams · ${okRegions.join(' · ') || '—'}`; } async function load() { ui.setStatus('roadcams', 'loading cameras…', 'warn'); const results = await Promise.allSettled(REGIONS.map(async (r) => { const res = await fetch(`proxy/cams/${r.id}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); return { region: r, cams: await res.json() }; })); for (const rr of results) { if (rr.status !== 'fulfilled') continue; const { region, cams } = rr.value; let n = 0; for (const c of Array.isArray(cams) ? cams : []) { if (!isFinite(c.Latitude) || !isFinite(c.Longitude)) continue; const views = (c.Views || []).filter((v) => v && v.Url && v.Status !== 'Disabled'); if (!views.length) continue; ds.entities.add({ position: Cesium.Cartesian3.fromDegrees(c.Longitude, c.Latitude), point: { pixelSize: 5, color: lib.cz('#ffb84d'), outlineColor: Cesium.Color.BLACK, outlineWidth: 1, disableDepthTestDistance: 50000, }, // CallbackProperty: HTML is built the moment the InfoBox opens, so the // snapshot cache-buster is always current — no stale images, no rebuilds. description: new Cesium.CallbackProperty(() => { const cb = Math.floor(Date.now() / 120000); // 2-min buckets const imgs = views.slice(0, 2).map((v) => `

traffic cam` + (v.Description ? `
${lib.escapeHtml(v.Description)}` : '') + `

`).join(''); return `

${lib.escapeHtml(c.Location || c.Roadway || 'Camera')}

` + imgs + `

${lib.escapeHtml([c.Roadway, region.name].filter(Boolean).join(' · '))} · 511 open data

`; }, false), }); n++; } if (n) okRegions.push(region.name); } loaded = true; const anyFail = results.some((r) => r.status !== 'fulfilled'); if (enabled) ui.setStatus('roadcams', statusText() + (anyFail ? ' · some regions down' : ''), anyFail ? 'warn' : 'ok'); } return { id: 'roadcams' }; }