Skip to content

feat(map): add conflict KML overlay system with native WebGL rendering#2452

Open
Ahmadhamdan47 wants to merge 7 commits intokoala73:mainfrom
Ahmadhamdan47:feat/conflict-kml-overlay
Open

feat(map): add conflict KML overlay system with native WebGL rendering#2452
Ahmadhamdan47 wants to merge 7 commits intokoala73:mainfrom
Ahmadhamdan47:feat/conflict-kml-overlay

Conversation

@Ahmadhamdan47
Copy link
Copy Markdown
Contributor

@Ahmadhamdan47 Ahmadhamdan47 commented Mar 28, 2026

Summary

Introduces a fully data-driven conflict overlay system on the map. A new ⚔ Conflicts dropdown in the map header (next to the regional selector) lets users fly to a conflict area and load a live KML overlay rendered natively in WebGL via GeoJsonLayer — no new npm dependencies added; KML is parsed entirely client-side using the built-in DOMParser.

Geometry is rendered using the source map's own colors — lines for front lines, polygons for control zones. A dedicated conflict legend panel (bottom-right) appears when an overlay is active, showing only curated, translated folder entries. Selecting the blank option clears the overlay and hides the legend.

A standalone /api/gmaps-kml Edge Function proxies KML requests that are CORS-blocked in the browser, currently allowlisted to www.google.com only.

The initial scene — Israeli invasion of South Lebanon / Gaza — is sourced from [ArabOSINT](https://x.com/Arabosint's public Google My Maps. Credit and thanks to ArabOSINT for making this data openly available.

Adding future conflict scenes requires only a single entry in src/config/conflicts.ts — no other code changes needed.


Type of change

  • Bug fix
  • New feature
  • New data source / feed
  • New map layer
  • Refactor / code cleanup
  • Documentation
  • CI / Build / Infrastructure

Affected areas

  • Map / Globe
  • News panels / RSS feeds
  • AI Insights / World Brief
  • Market Radar / Crypto
  • Desktop app (Tauri)
  • API endpoints (/api/*) — new /api/gmaps-kml CORS proxy
  • Config / Settings — new src/config/conflicts.ts
  • Other

Files changed

File Change
src/config/conflicts.ts NewConflictSceneConfig interface + CONFLICT_SCENES array
api/gmaps-kml.js New — Edge Function CORS proxy, allowlisted to www.google.com
src/components/DeckGLMap.ts KML fetch/parse pipeline, GeoJsonLayer renderer, conflict legend, activateConflictScene() public method
src/components/MapContainer.ts Proxies activateConflictScene() through the unified map API
src/app/panel-layout.ts Conflicts dropdown injected into header HTML
src/app/event-handlers.ts Event wiring for the conflicts dropdown
src/config/index.ts Re-exports CONFLICT_SCENES / ConflictSceneConfig
src/styles/main.css Conflict legend panel + header selector styles
vite.config.ts Dev proxy for /api/gmaps-kml
docs/data-sources.mdx Documents new ArabOSINT data source

Maintainer guide — adding or changing conflict sources

The system is fully config-driven. All scenes live in one file; no other code changes are needed when adding new entries.

1. Add a scene to src/config/conflicts.ts

export const CONFLICT_SCENES: ConflictSceneConfig[] = [
  {
    id: 'south-lebanon',                 // unique key (URL-safe)
    label: 'Israeli invasion of South Lebanon / Gaza',  // shown in dropdown
    lat: 33.19923,                       // map centre latitude
    lon: 35.57413,                       // map centre longitude
    zoom: 11,                            // zoom level to fly to
    kmlUrl: 'https://www.google.com/maps/d/kml?mid=1rSOCMJ8VTxNOl6wWCMByZE93qcKqDD4&forcekml=1',
  },
];

2. Getting the KML URL from a Google My Maps

  1. Open the Google My Maps
  2. Click Share → copy the link (https://www.google.com/maps/d/u/0/viewer?mid=XXXX&...)
  3. Extract the mid= value
  4. Build the export URL: https://www.google.com/maps/d/kml?mid=XXXX&forcekml=1
  5. Paste as kmlUrl

3. Using any other public KML URL

Any publicly accessible KML URL works — not just Google My Maps:

{
  id: 'ukraine-frontline',
  label: 'Russia–Ukraine front line',
  lat: 48.5,
  lon: 32.0,
  zoom: 6,
  kmlUrl: 'https://example.com/ukraine-frontline.kml',
}

If the URL is CORS-blocked, the proxy handles it automatically. For non-Google domains, extend ALLOWED_HOSTNAMES in api/gmaps-kml.js.

4. Controlling legend entries

Add a folderTranslations map to the scene entry in src/config/conflicts.ts.
Only folders listed there appear in the legend. Unlisted folders still render on
the map — they are simply omitted from the legend panel.

{
  id: 'south-lebanon',
  ...
  folderTranslations: {
    'مناطق تقدم\\تمركز الجيش الإسرائيلي': 'IDF Advance / Deployment Areas',
    'Your KML Folder Name': 'English Translation',
  },
}
---

## Checklist

- [x] Tested on [[worldmonitor.app](https://worldmonitor.app/)](https://worldmonitor.app) variant
- [ ] Tested on [[tech.worldmonitor.app](https://tech.worldmonitor.app/)](https://tech.worldmonitor.app) variant (if applicable)
- [ ] New RSS feed domains added to `api/rss-proxy.js` allowlist (if adding feeds)
- [x] No API keys or secrets committed
- [x] TypeScript compiles without errors (`npm run typecheck`)

---

## Test plan

- [x] `npm run typecheck` passes with no errors
- [x] `npm run dev`   Conflicts dropdown visible in header, next to regional selector
- [x] Selecting "Israeli invasion of South Lebanon / Gaza" flies the camera to the area (zoom 11, Lebanon)
- [x] KML loads  lines (front lines) and polygons (control zones) render in WebGL using source map colors; no black dots visible
- [x] Conflict legend appears bottom-right with correct color swatches and English labels
- [x] Selecting the blank option clears the overlay and hides the legend
- [x] Dark/light theme toggle  overlay colors remain correct (sourced from KML, not hardcoded)


---


https://github.com/user-attachments/assets/b7c083af-f40a-40cf-9b8e-e3c83d4bee7b

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 28, 2026

@Ahmadhamdan47 is attempting to deploy a commit to the Elie Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions bot added the trust:safe Brin: contributor trust score safe label Mar 28, 2026
@Ahmadhamdan47 Ahmadhamdan47 marked this pull request as draft March 28, 2026 20:33
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 28, 2026

Greptile Summary

This PR introduces a config-driven conflict KML overlay system, letting users fly to a conflict zone and render live KML geometry natively via DeckGL's GeoJsonLayer. A new /api/gmaps-kml Edge Function acts as a CORS proxy for Google My Maps exports.

Previously-flagged concerns have all been addressed in this revision:

  • Rate limiting via checkRateLimit is wired in
  • GET-only enforcement is in place
  • parsed.protocol !== 'https:' check is enforced
  • Dev-proxy allowlist in vite.config.ts now mirrors the production allowlist
  • folderTranslations moved into ConflictSceneConfig so the legend is fully config-driven without touching DeckGLMap.ts

Key changes:

  • src/config/conflicts.ts — new ConflictSceneConfig interface + initial Israel/Lebanon/Gaza scene
  • api/gmaps-kml.js — Edge Function CORS proxy, rate-limited, allowlisted to https://www.google.com and https://maps.google.com only
  • src/components/DeckGLMap.ts — KML parser (DOMParser + StyleMap resolution), GeoJsonLayer renderer, conflict legend panel, and public activateConflictScene() method
  • src/app/panel-layout.ts / event-handlers.ts — header dropdown injection and event wiring

One P1 issue remains: conflictLegendEntries is not cleared at the start of loadConflictKml. If a previous scene loaded successfully and the next scene's KML fetch fails, a subsequent layer toggle triggers updateLegend(), which renders the old scene's color swatches under the new scene's title. The fix is a single line (see inline comment on DeckGLMap.ts).

Confidence Score: 4/5

Safe to merge after the one-line fix resetting conflictLegendEntries at the top of loadConflictKml

All previously-flagged concerns have been addressed. One P1 remains: stale legend swatches appear when a KML fetch fails mid-session and the user subsequently toggles a map layer. The fix is trivial (add a single reset line). Everything else is clean: the CORS proxy is well-guarded, the config-driven architecture works correctly, and the KML parser handles the Google My Maps format as tested.

src/components/DeckGLMap.ts — loadConflictKml method (lines 3702-3705)

Important Files Changed

Filename Overview
api/gmaps-kml.js New CORS proxy edge function; rate-limited, GET-only, allowlisted to HTTPS Google domains
src/config/conflicts.ts New ConflictSceneConfig interface and CONFLICT_SCENES array with one initial scene and folderTranslations
src/components/DeckGLMap.ts KML parse pipeline and GeoJsonLayer renderer added; conflictLegendEntries not reset at start of loadConflictKml causes stale legend swatches on failure
src/app/event-handlers.ts Minimal change: wires conflictSelect dropdown change event to activateConflictScene
src/app/panel-layout.ts Injects Conflicts dropdown into map header HTML when CONFLICT_SCENES is non-empty
src/components/MapContainer.ts Proxies activateConflictScene through the unified MapContainer public API
vite.config.ts Dev proxy for /api/gmaps-kml with hostname and HTTPS protocol allowlist matching production edge function
tests/edge-functions.test.mjs Adds gmaps-kml.js to the legacy endpoint allowlist so CI does not reject the new edge function
src/config/index.ts Re-exports CONFLICT_SCENES and ConflictSceneConfig from the new conflicts module

Sequence Diagram

sequenceDiagram
  actor User
  participant Dropdown as Conflicts Dropdown
  participant DeckGLMap
  participant DirectFetch as Direct fetch (browser)
  participant Proxy as /api/gmaps-kml (Edge Fn)
  participant Google as Google My Maps

  User->>Dropdown: Select conflict scene
  Dropdown->>DeckGLMap: activateConflictScene(id)
  DeckGLMap->>DeckGLMap: flyTo(lat/lon/zoom)
  DeckGLMap->>DirectFetch: fetch(kmlUrl)
  alt CORS blocked — TypeError thrown
    DirectFetch-->>DeckGLMap: throw TypeError
    DeckGLMap->>Proxy: GET /api/gmaps-kml?url=...
    Proxy->>Proxy: Validate hostname + https: protocol
    Proxy->>Proxy: checkRateLimit(req)
    Proxy->>Google: fetch(kmlUrl, User-Agent)
    Google-->>Proxy: KML text
    Proxy-->>DeckGLMap: 200 KML (CORS + Cache-Control headers)
  else Direct fetch succeeds
    DirectFetch-->>DeckGLMap: KML text
  end
  DeckGLMap->>DeckGLMap: parseKmlToGeoJson(text)
  DeckGLMap->>DeckGLMap: deriveConflictLegendEntries(...)
  DeckGLMap->>DeckGLMap: updateLegend() + render()
Loading

Greploops — Automatically fix all review issues by running /greploops in Claude Code. It iterates: fix, push, re-review, repeat until 5/5 confidence.
Use the Greptile plugin for Claude Code to query reviews, search comments, and manage custom context directly from your terminal.

Reviews (3): Last reviewed commit: "merge(main): resolve DeckGLMap.ts confli..." | Re-trigger Greptile

Comment thread api/gmaps-kml.js
Comment thread src/components/DeckGLMap.ts
Comment thread src/components/DeckGLMap.ts
Comment thread api/gmaps-kml.js
Comment thread vite.config.ts
Ahmadhamdan47 and others added 3 commits March 28, 2026 22:56
- api/gmaps-kml.js: reject non-GET methods (405), add rate limiting via
  shared _rate-limit.js helper, enforce https: protocol on allowlisted URLs
- vite.config.ts: mirror production allowlist (hostname + https: guard) in
  dev proxy so disallowed URLs fail locally as they would in production
- src/config/conflicts.ts: add folderTranslations to ConflictSceneConfig so
  legend mappings live alongside other scene data; move Arabic->English
  translations from DeckGLMap into the south-lebanon entry
- src/components/DeckGLMap.ts: remove CONFLICT_FOLDER_TRANSLATIONS static;
  thread folderTranslations through loadConflictKml -> deriveConflictLegendEntries;
  fix ellipsis check to use cleanDesc.length instead of raw HTML desc.length
@Ahmadhamdan47 Ahmadhamdan47 marked this pull request as ready for review March 28, 2026 21:12
SebastienMelki
SebastienMelki previously approved these changes Mar 30, 2026
Copy link
Copy Markdown
Collaborator

@SebastienMelki SebastienMelki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid implementation — config-driven KML overlay system with WebGL rendering via GeoJsonLayer, no new npm deps. Greptile 5/5. One nit: the JSDoc on folderTranslations in src/config/conflicts.ts says folders not listed are hidden, but actually all non-Point geometry is rendered regardless — folderTranslations only controls legend panel entries. Please fix that comment before merge. LGTM otherwise.

@Ahmadhamdan47
Copy link
Copy Markdown
Contributor Author

One nit: the JSDoc on folderTranslations in src/config/conflicts.ts says folders not listed are hidden, but actually all non-Point geometry is rendered regardless — folderTranslations only controls legend panel entries. Please fix that comment before merge. LGTM otherwise.

done

@Ahmadhamdan47 Ahmadhamdan47 marked this pull request as draft April 5, 2026 21:57
@Ahmadhamdan47 Ahmadhamdan47 force-pushed the feat/conflict-kml-overlay branch from 5dc099c to 05ac620 Compare April 5, 2026 22:07
@Ahmadhamdan47 Ahmadhamdan47 marked this pull request as ready for review April 5, 2026 22:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

trust:safe Brin: contributor trust score safe

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants