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:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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']),
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user