Add zoom-aware symbology, fix globe popovers, expand geo coverage (#34)

* Add zoom-aware symbology, fix globe popovers, expand geo coverage

Map rendering improvements based on user feedback:
- Globe markers now scale with camera altitude (onZoom hook)
- Priority-based visibility culls low-priority markers at world view
- Globe popovers use getScreenCoords for accurate positioning
- Flat map labels hidden at low zoom, revealed progressively
- Default globe altitude lowered from 2.5 to 1.8 for better fill
- Americas region zoom tightened to CONUS focus

Geographic coverage expansion:
- 4 new OpenSky air theaters: Caribbean, Gulf of Guinea, Cape Route, Horn of Africa
- Flight corridors now span Americas and Africa
- NOAA alerts extract centroid lat/lon from GeoJSON geometry
- EPA RadNet stations geocoded with hardcoded coords for 10 US cities
- ISS + Tiangong positions estimated from TLE orbital elements
- GDELT geoEvents() now called in briefing for mapped event points
- New legend entries: Weather Alert, EPA RadNet, Space Station, GDELT Event

* Fix null-safe coordinate checks and remove injected data blob

- Use `!= null` instead of truthy checks for lat/lon in noaa.mjs
  and inject.mjs so valid 0-coordinates (equator/prime meridian)
  are not silently dropped
- Reset jarvis.html `let D` back to null placeholder so generated
  runtime data is not part of the PR diff

* Remove re-injected data blob from jarvis.html

Reset let D back to null — previous commit was correct but
inject.mjs build verification re-injected the payload.
This commit is contained in:
calesthio
2026-03-17 19:34:08 -07:00
committed by GitHub
parent d63c69bb05
commit a8682c50d0
6 changed files with 234 additions and 58 deletions

View File

@@ -14,16 +14,16 @@ const RADNET_AUX = `${BASE}/RADNET_AUX`;
// Key US cities with RadNet monitoring stations
const MONITORING_STATIONS = {
washingtonDC: { label: 'Washington, DC', state: 'DC' },
newYork: { label: 'New York, NY', state: 'NY' },
losAngeles: { label: 'Los Angeles, CA', state: 'CA' },
chicago: { label: 'Chicago, IL', state: 'IL' },
seattle: { label: 'Seattle, WA', state: 'WA' },
denver: { label: 'Denver, CO', state: 'CO' },
honolulu: { label: 'Honolulu, HI', state: 'HI' },
anchorage: { label: 'Anchorage, AK', state: 'AK' },
miami: { label: 'Miami, FL', state: 'FL' },
sanFrancisco: { label: 'San Francisco, CA', state: 'CA' },
washingtonDC: { label: 'Washington, DC', state: 'DC', lat: 38.9, lon: -77.0 },
newYork: { label: 'New York, NY', state: 'NY', lat: 40.7, lon: -74.0 },
losAngeles: { label: 'Los Angeles, CA', state: 'CA', lat: 34.1, lon: -118.2 },
chicago: { label: 'Chicago, IL', state: 'IL', lat: 41.9, lon: -87.6 },
seattle: { label: 'Seattle, WA', state: 'WA', lat: 47.6, lon: -122.3 },
denver: { label: 'Denver, CO', state: 'CO', lat: 39.7, lon: -105.0 },
honolulu: { label: 'Honolulu, HI', state: 'HI', lat: 21.3, lon: -157.9 },
anchorage: { label: 'Anchorage, AK', state: 'AK', lat: 61.2, lon: -149.9 },
miami: { label: 'Miami, FL', state: 'FL', lat: 25.8, lon: -80.2 },
sanFrancisco: { label: 'San Francisco, CA', state: 'CA', lat: 37.8, lon: -122.4 },
};
// Analyte types that indicate concerning radiation
@@ -76,8 +76,15 @@ export async function getResultsByAnalyte(analyte, opts = {}) {
);
}
// Lookup coords by city name or state
const CITY_COORDS = Object.fromEntries(
Object.values(MONITORING_STATIONS).map(s => [s.label.split(',')[0].toUpperCase(), s])
);
// Compact a reading for briefing output
function compactReading(r) {
const city = (r.ANA_CITY || r.LOCATION || '').toUpperCase().trim();
const station = CITY_COORDS[city];
return {
location: r.ANA_CITY || r.LOCATION || 'Unknown',
state: r.ANA_STATE || r.STATE || null,
@@ -86,6 +93,8 @@ function compactReading(r) {
unit: r.RESULT_UNIT || r.ANA_UNIT || null,
collectDate: r.COLLECT_DATE || r.SAMPLE_DATE || null,
medium: r.SAMPLE_TYPE || r.MEDIUM || null,
lat: station?.lat || null,
lon: station?.lon || null,
};
}

View File

@@ -104,11 +104,26 @@ export async function briefing() {
keywords.some(k => a.title?.toLowerCase().includes(k))
);
// Geo events — get mapped event locations (separate API, respects rate limit)
await delay(5500);
let geoPoints = [];
try {
const geo = await geoEvents('conflict OR military OR protest OR crisis', { maxPoints: 30, timespan: '24h' });
geoPoints = (geo?.features || []).filter(f => f.geometry?.coordinates).map(f => ({
lat: f.geometry.coordinates[1],
lon: f.geometry.coordinates[0],
name: f.properties?.name || f.properties?.html || '',
count: f.properties?.count || 1,
type: f.properties?.type || 'event',
}));
} catch (e) { /* geo endpoint optional — don't break briefing */ }
return {
source: 'GDELT',
timestamp: new Date().toISOString(),
totalArticles: articles.length,
allArticles: articles,
geoPoints,
conflicts: categorize(['military', 'conflict', 'war', 'strike', 'missile', 'attack', 'bomb', 'troops']),
economy: categorize(['economy', 'recession', 'inflation', 'market', 'sanctions', 'tariff', 'trade', 'gdp']),
health: categorize(['pandemic', 'outbreak', 'epidemic', 'disease', 'virus', 'health']),

View File

@@ -57,15 +57,33 @@ export async function briefing() {
wildfires: fire.length,
other: other.length,
},
topAlerts: features.slice(0, 15).map(f => ({
event: f.properties?.event,
severity: f.properties?.severity,
urgency: f.properties?.urgency,
headline: f.properties?.headline,
areas: f.properties?.areaDesc,
onset: f.properties?.onset,
expires: f.properties?.expires,
})),
topAlerts: features.slice(0, 15).map(f => {
// Extract centroid from GeoJSON geometry
let lat = null, lon = null;
const geo = f.geometry;
if (geo?.type === 'Polygon' && geo.coordinates?.[0]?.length) {
const coords = geo.coordinates[0];
lat = coords.reduce((s, c) => s + c[1], 0) / coords.length;
lon = coords.reduce((s, c) => s + c[0], 0) / coords.length;
} else if (geo?.type === 'MultiPolygon' && geo.coordinates?.length) {
const coords = geo.coordinates[0][0];
lat = coords.reduce((s, c) => s + c[1], 0) / coords.length;
lon = coords.reduce((s, c) => s + c[0], 0) / coords.length;
} else if (geo?.type === 'Point') {
[lon, lat] = geo.coordinates;
}
return {
event: f.properties?.event,
severity: f.properties?.severity,
urgency: f.properties?.urgency,
headline: f.properties?.headline,
areas: f.properties?.areaDesc,
onset: f.properties?.onset,
expires: f.properties?.expires,
lat: lat != null ? +lat.toFixed(3) : null,
lon: lon != null ? +lon.toFixed(3) : null,
};
}),
};
}

View File

@@ -57,6 +57,10 @@ const HOTSPOTS = {
baltics: { lamin: 53, lomin: 19, lamax: 60, lomax: 29, label: 'Baltic Region' },
southChinaSea: { lamin: 5, lomin: 105, lamax: 23, lomax: 122, label: 'South China Sea' },
koreanPeninsula: { lamin: 33, lomin: 124, lamax: 43, lomax: 132, label: 'Korean Peninsula' },
caribbean: { lamin: 18, lomin: -90, lamax: 30, lomax: -72, label: 'Caribbean' },
gulfOfGuinea: { lamin: -2, lomin: -5, lamax: 8, lomax: 10, label: 'Gulf of Guinea' },
capeRoute: { lamin: -38, lomin: 12, lamax: -28, lomax: 24, label: 'Cape Route' },
hornOfAfrica: { lamin: 5, lomin: 40, lamax: 15, lomax: 55, label: 'Horn of Africa' },
};
// Briefing — check hotspot regions for flight activity