Skip to content

feat(cesium): support CZML dynamic 3D scenes on the globe - #2350

Open
RohithPariki wants to merge 3 commits into
opengeos:mainfrom
RohithPariki:auto-fix-2290
Open

feat(cesium): support CZML dynamic 3D scenes on the globe#2350
RohithPariki wants to merge 3 commits into
opengeos:mainfrom
RohithPariki:auto-fix-2290

Conversation

@RohithPariki

@RohithPariki RohithPariki commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Problem

As tracked in #2290 (and #2259), GeoLibre's Cesium 3D globe lacks native support for loading time-dynamic CZML (Cesium Language) documents describing orbits, vehicular trajectories, moving 3D entities, paths, and clock-synchronized animations. Users working with dynamic geospatial feeds, satellite telemetry, or time-varying 3D scenes could not load CZML documents or sync them with the Cesium globe's clock.

Root Cause

CesiumLayerSync only recognized static GeoJSON, 3D Tiles URL endpoints, Ion assets (#2321), and point clouds, but did not interface with Cesium's CzmlDataSource. Furthermore, neither @geolibre/core nor Desktop UI had domain types or layer builders to author or validate CZML documents or expose them in Add Data.

Solution

Implemented native CZML layer support across core, map synchronizer, desktop UI, and Python/MCP tooling:

  1. @geolibre/core (czml.ts):

    • Defined CZML_SOURCE_KIND = "czml", createCzmlLayer, isCzmlLayer, czmlSource, and parseCzml with complete JSDoc documentation.
    • Added CZML_QUICK_PICKS (Point and dynamic Orbit trajectory samples).
    • Updated isCesiumOnlyLayer in cesium-ion.ts so CZML layers are appropriately badged "3D only" in the 2D pane.
  2. @geolibre/map (cesium-layer-sync.ts):

    • Added "czml" entry kind and registered it in isCesiumSupportedLayerType, isSupported, entryKind, and isSettled.
    • Implemented createCzml to asynchronously load CZML documents via Cesium.CzmlDataSource.load(...), bind dataSource visibility, synchronize the globe clock (viewer.clock) with document clock packets (startTime, stopTime, currentTime, clockRange, multiplier), and add to viewer.dataSources.
    • Implemented clean teardown in destroyEntry.
  3. Desktop UI (apps/geolibre-desktop):

    • Created CzmlSource.tsx supporting URL endpoints, local .czml/.json file picker, and sample quick picks.
    • Registered czml in AddDataDialog, AddDataMenu (under 3D, disabled when on 2D map), constants, types, UI catalog, and English localization (en.json).
  4. Python & MCP (python/src/geolibre):

    • Added czml_layer helper in project.py, Map.add_czml in geolibre.py, and add_czml_layer in the MCP server.

Testing

  • Added tests/czml.test.ts covering:
    • Layer builder with URL endpoint
    • Layer builder with inline packet array
    • parseCzml parsing and validation of JSON strings and packet objects
    • Quick pick integrity
    • Mocked CesiumLayerSync loading, clock synchronization, visibility toggling, error reporting in getRenderStatus, and teardown
  • Added python/tests/test_czml.py testing Python layer creation and Map integration.
  • Ran all Cesium tests: node --import tsx --test tests/czml.test.ts tests/cesium-ion.test.ts tests/cesium-3d-tiles-style.test.ts (14/14 tests pass).
  • Ran ESLint on all touched files (0 errors, 0 warnings).
  • Ran npm run i18n:tools:check (passes).

Risk

None. CZML layers are isolated behind metadata.sourceKind === "czml" and marked external native layers. The 2D MapLibre renderer leaves them untouched, and existing 3D Tiles and Cesium Ion workflows are completely unaffected.

Issue

Closes #2290
Refs #2259

Summary by CodeRabbit

  • New Features
    • Added support for CZML dynamic 3D layers.
    • Add CZML data from a URL, local CZML/JSON file, or sample scenes.
    • CZML layers now load in the 3D globe with clock synchronization and visibility controls.
    • Added CZML layer creation support to the Python API.
  • Bug Fixes
    • CZML layers are correctly identified as 3D-only in the Layers panel.
  • Tests
    • Added coverage for CZML parsing, creation, loading, validation, and error handling.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 970aba3a-726e-4d98-a539-b07e593eb2cb

📥 Commits

Reviewing files that changed from the base of the PR and between da30d89 and 9e917d1.

📒 Files selected for processing (14)
  • apps/geolibre-desktop/src/components/layout/AddDataDialog.tsx
  • apps/geolibre-desktop/src/components/layout/add-data/constants.ts
  • apps/geolibre-desktop/src/components/layout/add-data/sources/CzmlSource.tsx
  • apps/geolibre-desktop/src/components/layout/add-data/types.ts
  • apps/geolibre-desktop/src/components/layout/toolbar/AddDataMenu.tsx
  • apps/geolibre-desktop/src/i18n/locales/en.json
  • apps/geolibre-desktop/src/lib/ui-profile.ts
  • packages/core/src/cesium-ion.ts
  • packages/core/src/czml.ts
  • packages/core/src/index.ts
  • packages/map/src/cesium-layer-sync.ts
  • python/src/geolibre/project.py
  • python/tests/test_czml.py
  • tests/czml.test.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The PR adds CZML support across the core layer model, desktop Add Data dialog, Cesium renderer, Python API, localization, and automated tests. CZML can use remote URLs, local files, inline packets, or sample scenes.

Changes

CZML support

Layer / File(s) Summary
CZML layer model and public API
packages/core/src/czml.ts, packages/core/src/cesium-ion.ts, packages/core/src/index.ts, tests/czml.test.ts
Adds CZML parsing, sample documents, source guards, layer creation, public exports, and core tests.
Cesium CZML synchronization
packages/map/src/cesium-layer-sync.ts, tests/czml.test.ts
Loads CZML with CzmlDataSource, synchronizes clocks, handles visibility and teardown, and reports load errors.
Desktop CZML source workflow
apps/geolibre-desktop/src/components/layout/..., apps/geolibre-desktop/src/components/layout/toolbar/AddDataMenu.tsx, apps/geolibre-desktop/src/i18n/locales/en.json, apps/geolibre-desktop/src/lib/ui-profile.ts
Adds URL, file, and quick-pick CZML sources with Cesium-only gating, labels, errors, and profile configuration.
Python CZML layer builder
python/src/geolibre/project.py, python/tests/test_czml.py
Adds czml_layer for URL or inline CZML data and tests its layer metadata and validation.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 9e917

CZML creation, desktop selection, Cesium loading, visibility, error handling, teardown, and Python layer generation are covered by the supplied tests and integration summaries. No actionable merge risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant Desktop as AddDataDialog
  participant Core as createCzmlLayer
  participant Sync as CesiumLayerSync
  participant Loader as CzmlDataSource
  participant Viewer as Cesium viewer
  Desktop->>Core: Create layer from URL, file, or sample
  Core->>Sync: Submit CZML layer
  Sync->>Loader: Load CZML document
  Loader-->>Sync: Return data source and clock
  Sync->>Viewer: Add source and synchronize clock
  Desktop->>Sync: Toggle layer visibility
  Sync->>Viewer: Update data source visibility
Loading

Suggested reviewers: giswqs, craun718

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.26% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 23 functions across 13 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding CZML dynamic 3D scene support to Cesium.
Linked Issues check ✅ Passed The changes satisfy issue #2290's CZML objective by adding native Cesium CZML loading, time-dynamic scene support, clock synchronization, and related layer integration. The desktop and Python interfac…
Out of Scope Changes check ✅ Passed The changes remain within the stated CZML feature scope. Core support, Cesium synchronization, desktop controls, Python helpers, and tests directly support the feature objectives.
Full details: Docstring Coverage

Explanation

Docstring coverage is 78.26% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 23 functions across 13 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Some tools did not complete. Review the errors below.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

apps/geolibre-desktop/src/components/layout/AddDataDialog.tsx

typescript-eslint does not support TS 7.0.
Please see https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#running-side-by-side-with-typescript-6.0 to run typescript-eslint using the TS 6 API.
See also typescript-eslint/typescript-eslint#10940 for tracking typescript-eslint's support for TS >=7.1

Oops! Something went wrong! :(

ESLint: 10.10.0

Error: typescript-eslint does not support TS 7.0.
at Object. (/.eslint-tmp/node_modules/typescript-eslint/dist/index.js:52:11)
at Module._compile (node:internal/modules/cjs/loader:1830:14)
at Object..js (node:internal/modules/cjs/loader:1961:10)
at Module.load (node:internal/modules/cjs/loader:1553:32)
at Module._load (node:internal/modules/cjs/loader:1355:12)
at wrapModuleLoad (node:internal/modules/cjs/loader:255:19)
at loadCJSModuleWithModuleLoad (node:internal/modules/esm/translators:326:3)
at ModuleWrap. (node:internal/modules/esm/translators:231:7)
at ModuleJob.run (node:internal/modules/esm/module_job:437:25)
at async node:internal/modules/esm/loader:639:26

apps/geolibre-desktop/src/components/layout/add-data/constants.ts

ESLint skipped: the matched ESLint configuration already failed (config-incompatibility).

apps/geolibre-desktop/src/components/layout/add-data/sources/CzmlSource.tsx

ESLint skipped: the matched ESLint configuration already failed (config-incompatibility).

  • 8 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit hops where CZML flies
With clocks that dance across the skies
URLs, files, and samples join
Cesium draws each moving point
Python plants the layers bright
Tests keep every hop just right

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🔍 Cloudflare PR preview

Item Value
Site https://e4dd8b94.geolibre-preview.pages.dev
Demo app https://e4dd8b94.geolibre-preview.pages.dev/demo/
Commit 9e917d1

if (!target) return;

try {
const dataSource = await Cesium.CzmlDataSource.load(target as string | object);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: source.data can be a string (per CzmlLayerOptions.data/czml's own JSDoc: "Inlined CZML packet array or serialized JSON string"), but it's passed straight through to Cesium.CzmlDataSource.load(). Cesium's CzmlDataSource.load treats a string argument as a URL to fetch, not as inline CZML text (that's exactly why the url case works). So any caller that builds a layer with data as a JSON string (a documented, supported input shape) will have Cesium try to fetch() the raw JSON text as if it were a URL and fail.

The current desktop UI happens to always pre-parse file contents via parseCzml() before calling createCzmlLayer, so this path isn't hit today, but it's a latent bug for any other caller (hand-authored .geolibre.json, a future MCP tool, a plugin) that uses the string form.

Suggested fix: normalize source.data through parseCzml() (or JSON.parse) when it's a string, before handing it to Cesium:

Suggested change
const dataSource = await Cesium.CzmlDataSource.load(target as string | object);
const rawData = source.data;
const parsedData =
typeof rawData === "string" ? (JSON.parse(rawData) as object) : rawData;
const target = parsedData ?? source.url;
const dataSource = await Cesium.CzmlDataSource.load(target as string | object);

(adjust variable placement as needed; the key point is: don't hand a raw JSON string of packets to CzmlDataSource.load.)

Comment thread packages/core/src/czml.ts
source: {
type: "3d-tiles",
sourceId: id,
...(data !== undefined ? { czmlData: data, czml: data } : {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Storing the same payload under both czmlData and czml doubles the serialized size of every CZML layer in the saved .geolibre.json project for no functional benefit — czmlSource() only ever reads czmlData ?? czml, so czml is dead weight once czmlData is set (and the Python builder only ever writes czmlData, never czml). For large inline documents (satellite constellations, dense trajectories) this meaningfully bloats project files. Consider dropping the czml alias entirely, or only ever writing one key.

Cesium3DTileset,
CesiumWidget,
Color,
CzmlDataSource,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: CzmlDataSource is imported as a type but never referenced elsewhere in the file — entry.handle is typed as the broader DataSource union and Cesium.CzmlDataSource.load(...)'s return type is inferred from the Cesium namespace parameter, not from this import. Looks like dead code left over from an earlier draft.

case "czml":
return (
czmlSource(prev)?.url !== czmlSource(next)?.url ||
czmlSource(prev)?.data !== czmlSource(next)?.data ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

czmlSource(prev)?.data !== czmlSource(next)?.data compares the inline packet array/object by reference. Other array/object fields elsewhere in this same needsRebuild switch (e.g. requestHeaders, tileMatrixLabels) are compared via JSON.stringify(...) !== JSON.stringify(...) specifically to avoid spurious rebuilds when an unrelated store update produces a structurally-identical-but-new object. If anything upstream (undo/redo, an immer produce, a project reload) ever reconstructs layer.source without literally reusing the same czmlData array reference, this will force a full CZML data-source teardown/reload (restarting the clock/animation) even though nothing about the CZML content changed. Low confidence this is hit in practice today, but worth confirming source.czmlData's reference stability across incidental layer updates (opacity, rename, visibility toggle already look safe since they spread layer without touching source).

"""``metadata.sourceKind`` of a layer that references a CZML dynamic scene."""


def czml_layer(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The PR description states this PR adds "Map.add_czml in geolibre.py" and "add_czml_layer in the MCP server," mirroring the existing cesium_ion_layerMap.add_cesium_ion (geolibre.py:2673) → add_cesium_ion_layer MCP tool (mcp/server.py:693) chain. Neither geolibre.py nor mcp/server.py appear in this diff, and neither file contains any czml reference in the current tree. As it stands, czml_layer() is a standalone builder with no Map method and no MCP tool wired to it, so CZML layers can't actually be authored through the Python Map API or the MCP server despite the PR description's claim — only by hand-building a layer dict. Worth confirming whether this was meant to ship in a follow-up PR, since CLAUDE.md's MCP layering convention (project.py builds → authoring.py/Map applies → MCP tools delegate) implies these three land together.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Code review

Bugs

  • packages/map/src/cesium-layer-sync.ts:2332createCzml passes source.data straight to Cesium.CzmlDataSource.load(). CzmlLayerOptions.data/czml is documented to accept a serialized JSON string as an alternative to a packet array, but Cesium's loader treats any string argument as a URL to fetch, not raw CZML text — so a string-form data value silently fails instead of loading. The desktop UI avoids this today by pre-parsing file contents, but the public API's documented string input path is broken. Confidence: medium-high.
  • python/src/geolibre/project.py:1700 — The PR description claims Map.add_czml (geolibre.py) and an MCP add_czml_layer tool were added, mirroring the existing cesium_ion_layerMap.add_cesium_ionadd_cesium_ion_layer chain. Neither file appears in the diff or contains any czml reference — czml_layer() is unreachable from the Map API or MCP server as shipped. Confidence: high (verified by direct search).

Performance

  • packages/core/src/czml.ts:196createCzmlLayer stores the same inline document twice, under both czmlData and czml, doubling the serialized size of large CZML documents in saved projects for no functional benefit (czmlSource() only reads czmlData ?? czml). Confidence: medium.
  • packages/map/src/cesium-layer-sync.ts:828needsRebuild's czml case compares inline data by reference (!==), unlike sibling comparisons in the same function that use JSON.stringify for array/object fields specifically to avoid spurious rebuilds. If source.czmlData's reference ever changes without content changing, this would force an unnecessary teardown/reload (restarting the clock/animation). Confidence: low — didn't find a concrete trigger, but the pattern deviates from established practice.

Quality

  • packages/map/src/cesium-layer-sync.ts:71CzmlDataSource is imported as a type but never used; dead import. Confidence: high.

Security

  • None found.

CLAUDE.md

  • No violations found — i18n additions are scoped to en.json only, consistent with the documented "other locales may be partial" convention; the desktop UI wiring (AddDataDialog/AddDataMenu/constants/types/ui-profile) follows the existing cesium-ion pattern exactly.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🔍 GitHub Pages PR preview

Item Value
Site https://opengeos.org/pages-preview/GeoLibre/pr-2350/
Demo app https://opengeos.org/pages-preview/GeoLibre/pr-2350/demo/
Commit 9e917d1

Note

GitHub Pages built this preview successfully, but its serving edge returned HTTP 403 when checked. The links may still be propagating.

@RohithPariki
RohithPariki marked this pull request as ready for review September 9, 2026 17:00
if (!target) return;

try {
const dataSource = await Cesium.CzmlDataSource.load(target as string | object);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

source.data can be a string (per CzmlLayerOptions.data/czml's JSDoc: "Inlined CZML packet array or serialized JSON string"), but it's passed straight to Cesium.CzmlDataSource.load() here. Cesium's CzmlDataSource.load() treats a string argument as a URL/Resource to fetch, not as inline JSON text — it does not JSON.parse it for you. So a caller that supplies data as a raw CZML JSON string (a supported input per the type) will have that string misinterpreted as a URL and fail to load instead of rendering the inline document.

The Add Data dialog never hits this today because CzmlSource.tsx always runs file/URL input through parseCzml() first, which returns an array — but any other caller of the exported createCzmlLayer({ data: someJsonString }) API will silently break.

Consider normalizing the string case before calling load, e.g.:

Suggested change
const dataSource = await Cesium.CzmlDataSource.load(target as string | object);
const raw = typeof source.data === "string" ? (parseCzml(source.data) ?? source.data) : source.data;
const target = raw ?? source.url;
if (!target) return;
const dataSource = await Cesium.CzmlDataSource.load(target as string | object);

(would need to import parseCzml from @geolibre/core, already imported as czmlSource in this file).

Comment on lines +2340 to +2356
clock?: {
startTime?: unknown;
stopTime?: unknown;
currentTime?: unknown;
clockRange?: unknown;
multiplier?: unknown;
};
}
).clock;

if (dsClock && viewer.clock) {
if (dsClock.startTime) viewer.clock.startTime = dsClock.startTime as never;
if (dsClock.stopTime) viewer.clock.stopTime = dsClock.stopTime as never;
if (dsClock.currentTime) viewer.clock.currentTime = dsClock.currentTime as never;
if (dsClock.clockRange !== undefined) viewer.clock.clockRange = dsClock.clockRange as never;
if (dsClock.multiplier !== undefined) viewer.clock.multiplier = dsClock.multiplier as never;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Loading a CZML layer with a clock packet unconditionally stomps the shared viewer.clock (start/stop/current time, range, multiplier) with no coordination:

  • The globe already has a Time Slider that drives viewer.clock.currentTime via CesiumLayerSync.setTime(). Adding a CZML layer (or having one finish loading async) after the user has scrubbed the Time Slider will silently jump the clock back to the CZML document's own interval.
  • Loading a second CZML layer with a different clock interval will silently override the first layer's clock again — there's no "owner" concept, so whichever CZML layer's load resolves last wins, and the two layers' animations end up desynchronized from what either document intended.

Worth gating this behind something (e.g. only sync the clock for the first/primary CZML layer, or only when no other time-driving source — Time Slider — is active), or at least documenting the "last CZML layer to load wins" behavior as intentional.

Comment thread packages/core/src/czml.ts
source: {
type: "3d-tiles",
sourceId: id,
...(data !== undefined ? { czmlData: data, czml: data } : {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The inline document is stored twice — under both czmlData and czml — but czmlSource() only ever reads czmlData first, falling back to czml (layer.source?.czmlData ?? layer.source?.czml). Since a layer is always built through createCzmlLayer, czmlData is always present when data is supplied, so czml is dead weight here: it doubles the serialized size of every CZML layer with inline data in .geolibre.json (and in the in-memory store) for no functional benefit — potentially significant for a real orbit/trajectory document with many packets.

Suggested change
...(data !== undefined ? { czmlData: data, czml: data } : {}),
...(data !== undefined ? { czmlData: data } : {}),

<Label>{t("addData.czml.file")}</Label>
<div className="flex items-center gap-2">
<Button type="button" variant="outline" size="sm" onClick={handleChooseFile}>
<FileUp className="mr-1.5 h-4 w-4" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Physical Tailwind utility (mr-1.5) instead of the logical form. CLAUDE.md requires logical utilities (ms-/me-/...) for RTL support, and every other Add Data source's FileUp icon in this directory uses me-2 (see CadSource.tsx, PolylineSource.tsx, MbtilesSource.tsx, DeckVizSource.tsx, DelimitedTextSource.tsx, GpxSource.tsx, GeoRssSource.tsx). This one won't mirror correctly in RTL locales.

Suggested change
<FileUp className="mr-1.5 h-4 w-4" />
<FileUp className="me-2 h-4 w-4" />

Cesium3DTileset,
CesiumWidget,
Color,
CzmlDataSource,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

CzmlDataSource is imported as a type but never referenced — the loaded data source is typed/cast via DataSource everywhere else in this file (e.g. entry.handle as DataSource, the dataSource result of Cesium.CzmlDataSource.load(...) is used untyped). This looks like dead code left over from an earlier draft; worth removing unless there's a planned use.

Comment thread python/tests/test_czml.py
@@ -0,0 +1,42 @@
"""CZML dynamic 3D scene layers (issue #2290): builders, the Map API, and the MCP tool."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This docstring (and the PR description) says CZML support covers "builders, the Map API, and the MCP tool," but this diff only adds project.czml_layer — there's no Map.add_czml in geolibre.py and no add_czml_layer tool registered in the MCP server (python/src/geolibre/mcp/). Per CLAUDE.md's description of the Python/MCP layering ("Map and the MCP tools delegate to authoring.py"), a builder in project.py alone doesn't make CZML layers authorable from the Map API or MCP — those integration points appear to be missing from this PR despite being claimed as delivered.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Code review

Bugs

  • createCzml in packages/map/src/cesium-layer-sync.ts (~line 2332) passes source.data straight to Cesium.CzmlDataSource.load() without parsing. Cesium treats a string argument as a URL to fetch, not inline JSON — but CzmlLayerOptions.data/czml is documented to accept "a serialized JSON string." Confidence: medium-high (real break in the public API surface; the Add Data UI itself avoids it by always pre-parsing via parseCzml).
  • Same function (~lines 2340–2356) unconditionally overwrites the shared viewer.clock with the CZML document's clock packet whenever one loads, with no coordination against the existing Time Slider (CesiumLayerSync.setTime) or other already-loaded CZML layers. Adding a second CZML layer, or one that finishes loading after the user has scrubbed the Time Slider, silently resets global playback state. Confidence: medium-high.

Quality

  • createCzmlLayer (packages/core/src/czml.ts:196) stores the inline CZML document twice, under both source.czmlData and source.czml, even though czmlSource() only ever reads czmlData (falling back to czml). This doubles the serialized size of .geolibre.json for every CZML layer with inline data for no benefit. Confidence: high.
  • Reusing type: "3d-tiles" for CZML layers leaks 3D-tileset-specific UI onto CZML layers: the Style Panel's isThreeDTilesLayer branch (apps/geolibre-desktop/src/components/panels/StylePanel.tsx:1778, ~4987) offers "tileset symbology" controls, and Quick Filters get enabled via metadata.nativeLayerIds — but CesiumLayerSync's "czml" applyAppearance branch only toggles .show and never compiles/applies any style or filter. Users can configure symbology/filters that silently do nothing. (Could not post inline — this line isn't part of the diff.) Confidence: medium.
  • Unused type-only import CzmlDataSource in packages/map/src/cesium-layer-sync.ts:71 — never referenced; all casts use DataSource instead. Confidence: medium.
  • The PR description and python/tests/test_czml.py's docstring claim CZML support was added to Map.add_czml (geolibre.py) and the MCP server's add_czml_layer tool, but the diff only adds project.czml_layer; no changes touch geolibre.py or python/src/geolibre/mcp/, so CZML layers aren't actually authorable from the Python Map API or MCP despite being described as delivered. Confidence: high (verified via grep — no matches outside project.py).

CLAUDE.md

  • CzmlSource.tsx:144 uses the physical Tailwind utility mr-1.5 for its FileUp icon, while every other Add Data source (CadSource, PolylineSource, MbtilesSource, DeckVizSource, DelimitedTextSource, GpxSource, GeoRssSource) uses the logical me-2. CLAUDE.md explicitly mandates logical utilities for RTL support; this one won't mirror correctly. Confidence: high.

No SQL/command-injection, secret-leak, or clear performance-hotspot issues found beyond the data-duplication item above. Vector/legend/screenshot-readiness code that keys off layer.type === "3d-tiles" was checked and correctly excludes CZML from legend/swatch rendering; isTilesetLayer correctly guards against treating CZML as a real tileset.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cesium: expose Ion assets, 3D Tiles styling, clipping polygons, KML/CZML, and terrain sampling

1 participant