Skip to content

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

Open
Ahmadhamdan47 wants to merge 6 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 6 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

greptile-apps bot commented Mar 28, 2026

Greptile Summary

This PR introduces a config-driven conflict KML overlay system for the map: a new ⚔ Conflicts dropdown lets users fly to a conflict zone and load a live KML file rendered natively via GeoJsonLayer (WebGL). KML is parsed client-side using DOMParser with no new npm dependencies, and a new /api/gmaps-kml edge function proxies CORS-blocked requests.

All previous review concerns have been resolved in this iteration:

  • Rate limiting via checkRateLimit is applied on the edge function
  • GET-only guard is present
  • https: protocol is enforced alongside the hostname allowlist
  • The Vite dev proxy now has a matching allowlist and protocol check, eliminating the local/production parity gap
  • The tooltip ellipsis correctly compares cleanDesc.length (post-HTML-strip) rather than raw desc.length
  • folderTranslations has been moved from a private static on DeckGLMap into ConflictSceneConfig, making the system genuinely config-driven

Remaining finding:

  • The JSDoc on folderTranslations in src/config/conflicts.ts incorrectly states "Only folders listed here are rendered; all others are hidden." In reality, createConflictOverlayLayer renders all non-Point geometry regardless of folder membership — folderTranslations controls only the legend panel entries. The PR description itself has the correct description. This is a one-line doc fix to prevent confusion for future contributors.

Confidence Score: 5/5

Safe to merge — all prior P0/P1 concerns have been addressed; the one remaining finding is a misleading JSDoc comment with no runtime impact.

Every previously flagged issue (rate limiting, method guard, protocol validation, dev proxy allowlist, ellipsis logic, folderTranslations coupling) has been correctly resolved. The only outstanding finding is an incorrect documentation comment on folderTranslations that does not affect runtime behavior — it cannot cause a bug on the changed path and is therefore a P2.

src/config/conflicts.ts — the folderTranslations JSDoc should be corrected before the config pattern is used by contributors unfamiliar with the rendering behavior.

Important Files Changed

Filename Overview
api/gmaps-kml.js New CORS proxy edge function for Google My Maps KML exports. Rate limiting, GET-only guard, HTTPS enforcement, and hostname allowlist are all present and correct. Previous review concerns fully addressed.
src/config/conflicts.ts New config file for conflict scenes. Clean interface design with folderTranslations moved here (addressing prior feedback), but the JSDoc for folderTranslations incorrectly describes rendering behavior ("hidden") when in reality all non-Point features still render.
src/components/DeckGLMap.ts KML fetch/parse pipeline with in-flight version cancellation, GeoJsonLayer renderer using KML-sourced colors, and conflict legend panel. Tooltip ellipsis logic correctly uses cleaned-text length. Style resolution via StyleMap indirection is solid.
vite.config.ts Dev proxy now includes hostname allowlist and HTTPS protocol check matching the production edge function, addressing prior parity gap.
src/components/MapContainer.ts Thin delegation of activateConflictScene to DeckGLMap. Correctly guarded by useDeckGL flag.
src/app/event-handlers.ts Wires conflictSelect change event using optional chaining on a nullable element reference; passes null on blank selection to clear the overlay correctly.
src/app/panel-layout.ts Injects the Conflicts dropdown into the header HTML using a conditional on CONFLICT_SCENES.length; uses scene.id as option value safely.
src-tauri/sidecar/local-api-server.test.mjs Updated test accurately reflects the new sidecar behavior: browser origin is replaced with the trusted worldmonitor.app origin rather than stripped to null.
tests/edge-functions.test.mjs Registers gmaps-kml.js in the edge-function allowlist test. No issues.

Sequence Diagram

sequenceDiagram
    participant User
    participant ConflictSelect as ⚔ Conflicts Dropdown
    participant DeckGLMap
    participant Browser as Browser fetch()
    participant Proxy as /api/gmaps-kml (Edge Fn)
    participant Google as www.google.com

    User->>ConflictSelect: select scene
    ConflictSelect->>DeckGLMap: activateConflictScene(id)
    DeckGLMap->>DeckGLMap: flyTo(lat, lon, zoom)
    DeckGLMap->>Browser: fetch(kmlUrl) direct
    alt CORS allowed
        Browser->>Google: GET kmlUrl
        Google-->>Browser: KML text
        Browser-->>DeckGLMap: text
    else CORS blocked (TypeError)
        Browser-->>DeckGLMap: throws
        DeckGLMap->>Proxy: GET /api/gmaps-kml?url=…
        Note over Proxy: checkRateLimit, GET-only,<br/>hostname + https allowlist
        Proxy->>Google: GET kmlUrl
        Google-->>Proxy: KML text
        Proxy-->>DeckGLMap: KML text (CORS headers added)
    end
    DeckGLMap->>DeckGLMap: parseKmlToGeoJson(text)
    DeckGLMap->>DeckGLMap: deriveConflictLegendEntries()
    DeckGLMap->>DeckGLMap: createConflictOverlayLayer() → GeoJsonLayer
    DeckGLMap->>DeckGLMap: updateLegend() → conflict-kml-legend panel
Loading

Reviews (2): Last reviewed commit: "fix(tests): register gmaps-kml.js in leg..." | Re-trigger Greptile

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

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