| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat(mcp): add read-only map previews with MCP Apps (#3097) | 5 天前 | |
fix: restore saved legend/colorbar panels and add set_map_legend (#2720) * fix: restore saved legend/colorbar panels and add set_map_legend A project that carries a Components legend, colorbar, or HTML panel lost it on reopen. Those panels restore from settings without activating the Components plugin and mount only after a dynamic import resolves, but the restore-scoped app refused any control added after the synchronous turn from an inactive plugin. Restore scopes now stay valid until the next restore (a project load or renderer swap) supersedes them; the activation-generation check still rejects a deactivated plugin. Add set_map_legend to the Python package, Map, and the MCP server. It writes the project-level legend behind Controls > Legend, whose rows come from each layer's symbology, so a classified layer gets a matching legend without restating its colors. * Address review feedback - set_map_legend: visible/collapsed default to None and keep the saved panel state, so a rename no longer re-expands a collapsed legend; a project with no legend still opens it (Claude review). - Plugin manager: a scope also goes stale when the renderer changes or the plugin is re-registered, closing the window before the next restore bumps the pass (CodeRabbit). | 18 天前 | |
feat(bookmarks): save bookmarks in the project (#3006) * feat(bookmarks): save bookmarks in the project Bookmarks lived only in the browser's localStorage, so they did not travel with a .geolibre.json and the Python package and MCP server could not author them. - Project format: optional `bookmarks` and `bookmarkGroups` (folders), normalized on load (camera range checks, duplicate ids, bearing wrap, dangling folder ids dropped) and omitted when empty. - Store: `bookmarks`, `bookmarkGroups` and `setBookmarks`; saved, autosaved and shared in collaboration like other project sections. - Bookmarks panel: no longer persists to localStorage. It opens with the project's bookmarks, writes every edit (including host-dialog imports) back to the project, and rebuilds when a project load or a collaborator replaces them. Bookmarks saved by older versions move into the first project opened that has none, once. - Python `Map.add_bookmark` / `remove_bookmark` and MCP tools `add_bookmark` / `remove_bookmark` (camera from the saved view by default, folder by id or name, layers to show), plus bookmarks in `describe_project`. The Python round-trip fixture now carries bookmarks through the app's parseProject. - Docs: project format, Python, MCP, user guide, and the agent skill. Part of #2869 * Address review feedback - Trim a bookmark's groupId before matching it to the (trimmed) folder ids, so a hand-edited id with spaces is not silently un-filed (Claude review). - Legacy migration no longer deletes the old localStorage bookmarks: a marker key records the one-time copy, so bookmarks copied into the wrong project stay recoverable from browser storage (Claude review). - Python add_bookmark refuses a 501st bookmark before changing the project, matching the app's MAX_PROJECT_BOOKMARKS (500), with a drift test that pins the two together (CodeRabbit). * Address review feedback - Write the legacy-bookmark migration marker only once the project that received them is saved; a project discarded or replaced unsaved leaves it unset so the next empty project gets the copy (CodeRabbit). - Correct the user guide: the old browser copy is left in place but no longer updated, not kept as a backup (Claude). - Move the bookmarkGroups normalization above the print-layout comment so the comment stays attached to its code (Claude). | 9 天前 | |
feat(map): Cesium Ion assets by id on the globe (#2321) * feat(map): Cesium Ion assets by id on the globe A 3D Tiles tileset or an imagery layer can now be referenced by its Cesium Ion asset id (issue #2290). The globe resolves the asset through an IonResource / IonImageryProvider with the app's Ion token, read at load time so a token added in Settings reaches the next sync; a missing token is a layer error rather than a silent blank. - `@geolibre/core` `cesium-ion.ts`: `createCesiumIonLayer`, the asset-id parser, `isCesiumOnlyLayer`, and the one-click picks (Cesium OSM Buildings, Bing Aerial through Ion). - Add Data → 3D → Cesium Ion Asset: asset id, tileset/imagery, altitude offset, quick picks, and a token hint; enabled when the globe is primary. - The Layers panel and the pane layer menus badge these layers "3D only" on the 2D map, the mirror of the existing "2D only" badge. - Python `add_3d_tiles(ion_asset_id=…)` / `add_cesium_ion(...)`, the MCP `add_3d_tiles_layer(ion_asset_id=…)` / `add_cesium_ion_layer` tools, and the docs and agent skill references. Refs #2290, #2259. * style: auto-format (ruff + oxfmt) [pre-commit.ci] * test(python): assert Ion asset ids survive the MCP save; document Ion imagery in the format table * fix: Ion asset follow-ups - three_d_tiles_layer rejects request_headers together with ion_asset_id instead of dropping them silently. - An Ion asset id counts as a restorable source, so Ion layers can be saved to My Data. - The Add Data form reports an unparseable altitude offset instead of coercing it to zero. * fix(core): name the Bing Aerial Ion asset for what it is; parse restorable Ion ids the globe's way * fix(core): an Ion asset id is restorable only under the cesium-ion source kind --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> | 1 个月前 | |
feat(cesium): native KML, terrain profiles, and control events (#2356) * fix(cesium): restore startup and forward control events * feat(cesium): load native KML and sample elevation profiles * fix(cesium): fade KML labels and align Python tool coverage * fix(cesium): avoid MapLibre placeholder warnings on native layers * Address review feedback - KmlSource: route the URL field label and the default layer name through t(), and add addData.kml.url / addData.kml.defaultName to all 19 locale catalogs, matching the sibling Add Data sources (Claude). - LayerPanel: scope the placeholder-warning suppression to the layer kinds Cesium actually draws, so a kind the globe cannot render (duckdb-query) keeps its message while the globe is primary (Claude). - cesium-control-host: convert hit positions through scene.globe?.ellipsoid with a WGS84 fallback -- pickGlobeHit answers a globe-less scene with an ellipsoid pick, which the old dereference would have thrown on (CodeRabbit); skip the pick entirely when no control listens for the event, so mousemove no longer pays for a terrain ray per pointer frame that nothing consumes (Claude). - cesium-layer-sync: re-append KML ScreenOverlay containers in store order during sync(), so a panel reorder restacks overlapping overlays instead of leaving them in load order (CodeRabbit). - ElevationProfileControl: keep the native sampler's own error messages ("the terrain source changed", "the globe was closed") instead of replacing them with the generic HTTP-path fallback (CodeRabbit). - Tests: regression coverage for the globe-less pointer conversion, the unlistened-event pick skip, and BOM-prefixed inline KML. | 1 个月前 | |
feat(python): local raster, marker/cluster, and choropleth APIs (#348) (#360) * feat(python): local raster, marker/cluster, and choropleth APIs (#348) - add_raster/add_cog accept a kernel-side local GeoTIFF path, served by the bundled static server with HTTP Range support (geotiff.js needs ranges) - add_marker/add_markers/add_circle_markers/add_marker_cluster build point GeoJSON layers; cluster enables the point cluster renderer - add_choropleth/add_data compute graduated symbology from a numeric column via a new color_ramp module mirroring the app's color-ramp helpers * Address CodeRabbit review feedback - docs/python.md: clarify local-raster paths only work where the browser can reach the kernel's localhost (remote setups need a hosted URL) - docs/python.md: add **style to the add_cog/add_raster table signatures - color_ramp.get_color_ramp: return a copy so callers can't mutate the shared VECTOR_COLOR_RAMPS entries - add_markers: reject non-Point/MultiPoint GeoJSON inputs instead of silently rendering polygons/lines through a point-only markers layer - add_choropleth: raise when the column has no numeric value (was falling back to misleading index-based stops) - test_server: shut down the static server on fixture teardown to avoid leaking threads/sockets across the suite - tests: cover the two new validation errors * Address Claude review feedback - _server: add do_OPTIONS so a CORS preflight on the ranged COG fetch returns 200 (Range is safelisted in modern browsers, but older browsers/proxies that preflight would otherwise hit the stdlib 501 and fail to render local rasters) - _server.register_local_file: reuse the existing token for an already-registered file so repeated add_raster calls don't grow the registry unbounded - color_ramp.graduated_stops: cap class_count at 12 (mirrors clampClassCount in StylePanel.tsx); use math.isfinite; drop the unreachable break/color mismatch branch (both schemes always return `count` breaks) - add_choropleth: cap stored vectorStyleClassCount at 12; use math.isfinite - tests: cover the class-count cap, ramp-copy immutability, token reuse, and the OPTIONS preflight | 3 个月前 | |
fix(build): keep build-machine credentials out of redistributable builds (#2130) * fix(build): keep build-machine credentials out of redistributable builds An artifact we hand to someone else -- the Jupyter wheel above all -- must carry no credential of ours. Three properties of this build made that easy to get wrong: 1. vite.config.ts bridges bare shell vars into their VITE_ names (GOOGLE_MAPS_API_KEY, MAPBOX_TOKEN, CESIUM_TOKEN), so the build machine's shell is build input. 2. Something in the graph reads `import.meta.env` as a whole object, so Vite stops replacing per key and inlines the entire env record into every chunk that read reaches. Ours was packages/core/src/runtime-env.ts; the one we cannot fix is @clerk/shared's getEnvVariable.mjs, which does `import.meta.env[name]` with a computed name -- so the inlined record lands in ClerkGate-*.js. 3. hatch_build.py skips the JS build when static/app already exists and GEOLIBRE_FORCE_JS_BUILD is unset, so `python -m build` packages whatever an earlier `npm run build:embed` left staged, with no JavaScript running at all. Changes: - vite.config.ts gains BUILD_ENV_KEYS (allowlist of VITE_ names that may reach a bundle) and CREDENTIAL_ENV_KEYS (the subset that bills to whoever built). pruneBuildEnv() deletes unrecognized VITE_ vars from process.env and blanks credentials in redistributable builds, before Vite reads it -- so a whole-object read has nothing sensitive to inline, including Clerk's. The web deploy is unaffected and keeps its own referrer-restricted keys. - runtime-env.ts reads the allowlisted __GEOLIBRE_BUILD_ENV__ define instead of import.meta.env, and exports getBuildEnvironment() for the five call sites that took a whole-object dependency. - scripts/scan-credentials.mjs verifies build OUTPUT, wired into build-embed.mjs (before staging) and hatch_build.py (before packaging any wheel/sdist, needing no Node -- the path (3) above that no JS-side guard can cover). Both read scripts/credential-patterns.json so they cannot drift; it is force-included into the sdist so sdist -> wheel is gated too. - CesiumJS's own public default Ion token is hash-allowlisted so the guard does not fire on every release. Verified: an embed build on a machine with those shell vars set now emits VITE_*:"" for every credential and no raw token shapes; the web build is unchanged; a planted key makes `python -m build` refuse. Stripped credentials still resolve at runtime through getRuntimeEnvironment()'s Settings -> Environment variables overlay. * Address review feedback - pruneBuildEnv() now covers `.env*` files, not just `process.env`. Only 4 of the 11 credential keys have a bare->prefixed shell bridge; the other 7 are documented in getting-started.md as `.env.local` entries. A key set only in a file was never in `process.env` at prune time, so the loop skipped it and Vite's own loadEnv then resolved the raw value into `import.meta.env` -- reaching the @clerk/shared computed lookup in a redistributable build. Confirmed by planting a file-only VITE_STADIA_API_KEY: it was inlined verbatim, and only the output scan caught it. The sweep now unions process.env and FILE_ENV keys, and blanks rather than deletes file-backed ones, because process.env wins over a .env value only when the key is present (including as ""). The __GEOLIBRE_BUILD_ENV__ record falls back to FILE_ENV too, so a .env.local-configured var still reaches getBuildEnvironment(). - CREDENTIAL_ENV_KEYS is now read from scripts/credential-patterns.json instead of being a second hand-maintained copy, so the name this config strips and the name the scanners look for cannot drift. - The Google API key pattern ends with a lookahead instead of `\b`. Its charset includes `-`, and `\b` needs a word character on the left, so a key ending in `-` did not match before a quote and both scanners missed it. Verified in both JavaScript RegExp and Python re; regression fixtures added on both sides. - _load_patterns() raises instead of returning None. It previously made scan_for_credentials return [] and report "Credential scan clean.", which is indistinguishable in the build log from a genuine clean scan -- failing open, the opposite of the documented intent. - Added python/tests/test_credential_scan.py (9 tests), including one that runs both scanners over the same fixture and asserts identical output. The Python scanner previously had no coverage. The hatchling import in hatch_build.py is now guarded so the scanner is importable standalone. | 1 个月前 | |
feat(plugins): God's Eye View — USGS earthquakes + CelesTrak satellites as live CZML (issue #2462) (#2504) * feat(plugins): add God's Eye View Tier A feeds (issue #2462) Add live USGS earthquake and CelesTrak satellite feeds as inline, clock-driven CZML layers with a translated right panel and restore-safe refresh lifecycle. Test with npm run test:frontend, npm run lint, and npm run build. * fix(gods-eye-view): resolve PR #2504 review findings + broken orbits & Identify Reviewer (claude-review / CodeRabbit, PR #2504): - F1 orbits: root cause was Cesium path availability, not the time format. Cesium draws a path over currentTime +/- lead/trail clipped to entity availability, and the LOOP_STOP clock rewinds to interval start, so a fixed 30-min lead/trail (< one LEO revolution) with no pre-sampled samples rendered stubs/broken arcs. Now each satellite pre-samples one (capped) revolution ahead of the window and gets lead/trail of half its own period, so the ring is closed at every clock wrap. (Verified with a real @cesium/engine repro: relative-seconds + ISO epoch IS the valid format, so no time-format change was needed.) - F3: export reattachGodsEyeView and wire it in DesktopShell alongside reattachSun/FlightSimulator/RouteAnimation so a renderer swap re-binds the Cesium handle instead of pushing feeds at a torn-down viewer. - F4: background refresh now patches only name/source/metadata, so a user's visibility/opacity/style choice survives the 10-min cycle. - F5: getProjectState/applyProjectState persist the per-feed toggles and activate() no longer unconditionally re-enables feeds the user disabled. - F7 (CodeRabbit): usgsGeoJsonToCzml now tolerates a missing/null features array and null entries instead of throwing (test added). User-reported defects from the preview: - Identify now answers for the feed layers: createCzmlLayer defaults to identifiable:true and CesiumLayerSync gains a CZML branch that maps a picked entity to its name + custom properties (magnitude, depthKm, place, time for earthquakes; catalog number, inclination, period for satellites); tests/czml.test.ts expectation updated to match the new default. - Feed packets carry those properties via CZML 'properties' so the Identify popup has something useful to show; depth stays pinned to the surface (underground depth is hidden by terrain) and is surfaced through properties instead. Verified: npm run test:frontend (8850 pass/0 fail), lint 0 errors, build clean, e2e core suite green (two Style-panel visibility flakes in one serial run pass in isolation; same code passed on the first run). * fix(gods-eye-view): complete satellite rendering and selection * style: auto-format (ruff + oxfmt) [pre-commit.ci] * Address review feedback - reattachGodsEyeView: compare the underlying `viewer` instead of the `CesiumSceneHandle`. `getCesiumScene()` mints a fresh object literal on every call, so the old `next === cesiumRef` guard never short-circuited while Cesium was live: every project load restarted the refresh timer, re-fetched both public feeds, and re-forced `clock.shouldAnimate` over a pause the user had made. Matches the `flight-simulator` / `maplibre-sun` idiom. - Add `tests/gods-eye-view-plugin.test.ts`, a regression guard for that path (fresh handle per call, unchanged viewer re-fetches nothing and leaves the clock alone, a new viewer rebinds) — the plugin lifecycle file had no coverage. - python/src/geolibre/project.py::czml_layer: flip `identifiable` to True so the hand-maintained mirror matches `createCzmlLayer`; an MCP-authored project no longer diverges from the app on Identify. - Add an end-to-end CZML Identify test driving `CesiumLayerSync.resolveCzmlFeature`: layer/entity ids, the property bag sampled at the viewer's current time, `geometry: null`, and the hidden-entity / hidden-layer / zero-opacity exclusions. - Satellite labels: raise the fleet `distanceDisplayCondition` from 8,000,000 m to 11,300,000 m (the zoom-3 distance times √2) so names appear from zoom 2.5 instead of zoom 3. * Address CodeRabbit review feedback - cesium-engine `highlightFeature`: fit a multi-feature selection over the shortest longitude interval. A pair at 179° and -179° made `Math.min`/ `Math.max` hand Cesium a 358° rectangle, framing almost the whole globe instead of the two-degree cluster. New `shortestLongitudeInterval` unwraps onto the branch nearest the first longitude and returns the repo's crossing-rectangle form (`west` > `east`). - cesium-layer-sync `createCzml`: replay the retained highlight after `viewer.dataSources.add`, as `createGeoJson` already does. The entities exist only once the document loads, so a selection applied while it was loading — every ten-minute feed refresh re-applies one — painted nothing and stayed unpainted until the selection changed. - tests/cesium-basemap: assert the keyless fallback actually constructs OpenStreetMapImageryProvider with the keyless URL and makes no Ion request, instead of only checking that some Promise was passed to `fromProviderAsync`. - Both fixes come with tests that fail against the old code. * feat(gods-eye-view): make the clock speed a setting, default real time The feeds used to hard-code a 60x clock multiplier, so the ISS lapped the planet in about ninety seconds — an animation rather than a view of where the satellite is now. The globe now runs at real time by default, with a Speed control in the panel offering 1x, 10x, 60x and 600x. - `GodsEyeViewProjectState` carries `speed` alongside the feed toggles, so a project remembers it; `normalizeProjectState` honours only an offered step, defaulting a hand-edited value back to real time. - Changing the speed writes `clock.multiplier` directly and refetches nothing: the CZML documents carry the multiplier only so a fresh load starts at the right speed, and `electCzmlClockOwner` leaves an unchanged owner's clock alone. `activate`, `reattach` and `applyProjectState` each re-time the globe for the same reason. - `documentPacket`'s fallback multiplier drops from 60 to 1 so the feed helpers default the way the plugin does. - The panel carries `geolibre-gods-eye-view-panel` and index.css themes its native select, as for the graticule and timelapse panels. - `panel.godsEyeView.speed` / `speedRealTime` / `speedMultiple` added to all 19 locale catalogs, inserted in en.json order. - Tests cover the default, the select re-timing the live clock without a refetch, persistence, and the hand-edited-value fallback. * fix(gods-eye-view): crisp globe text and one standing satellite label Both label complaints trace to the same difference from the reference app (bilawalsidhu/gods-eye-view): it does not draw satellite names with Cesium at all. `src/overlays/worldOverlay.js` paints them on a separate 2D canvas sized to `devicePixelRatio`, over the WebGL globe, with a priority-based collision allocator deciding which survive. - `CesiumCanvas`: pass `useBrowserRecommendedResolution: false`. Cesium defaults it to `true`, which pins the drawing buffer to CSS pixels and leaves the browser to upscale it, so on a HiDPI display the globe softens and glyph-atlas text shows it first. The camera maths is unaffected: every zoom conversion reads `clientWidth`/`clientHeight`, with `canvas.width` only as a fallback. - Only the ISS keeps a standing label, as upstream does. Cesium labels perform no collision avoidance, so naming the fleet piled hundreds of names over each other — and over the ISS's — as soon as the camera came in. Every other satellite names itself on hover and Identify, where its catalogue number, inclination and period already are. - Feed layers ship `popup.hover`, so hovering names a satellite or a quake without a click. It is set at layer creation only, so turning it off survives a refresh like the other user-owned fields. * feat(cesium): fall back to Esri World Imagery without an Ion token A keyless globe fell back to OpenStreetMap street tiles, which under a 3D globe mostly show empty ocean. Esri World Imagery needs no key and shows the Earth, and is what the God's Eye View reference app defaults to without a key (`src/maps/defaultSources.js`: `defaultId: googleTileset ? 'photoreal' : 'esri-imagery'`), for the same reason. OpenStreetMap stays as the last resort: if the Esri service cannot be reached the provider promise is caught and street tiles stand in, mirroring upstream's `constructionFallback`. With an Ion token the Bing Aerial asset is unchanged. No Tauri CSP change is needed — `img-src`/`connect-src` already allow `https:`. * feat(cesium): name and trace the selected CZML entity A time-dynamic document is a crowd, and Cesium will not declutter labels, so the document itself labels only what deserves a standing name — the ISS alone, for the satellite feed. Selecting an entity is the user asking which one that is, so the selection now answers: - An entity with a name but no label of its own gets one while selected. - An entity whose position is sampled over time gets a path, lead and trail set to half its own `orbitalPeriodMinutes` where the packet reports one (a satellite does), so the ring closes instead of being cut to an arbitrary length. Both are pushed onto the existing highlight restorers, so clearing the selection leaves the document exactly as its author wrote it. * Address Claude review feedback - `refreshFeed`: drop the `!controller.signal.aborted` guard from the catch. The 20s watchdog aborts the very request whose generation still matches, so the guard swallowed exactly the failure worth reporting and left the panel on a stale timestamp. Every deliberate cancellation bumps the generation, which the remaining check already excludes. - `upsertLayer`: fall back to the flag search when the remembered layer id misses. A project switch replaces `store.layers` wholesale while the plugin stays active, so the old id matched nothing and a second layer was added beside the new project's own, orphaning it. - `startRefreshing`: stop forcing. It runs on every project load for a plugin that stays active, and CelesTrak asks not to be re-read every few minutes. The interval now also requires the feed to still have a layer, so a feed toggled off and on rebuilds immediately instead of being spared a fetch it needs — recent data nobody can see is no data at all. - `shortestLongitudeInterval`: take the complement of the widest gap between neighbouring longitudes. Measuring from one member found the minimum only while the answer was under 180° wide, reporting 340° for a three-point spread whose true enclosing arc is 190°. - `parseTle`: treat a line as a name only when line 1 really follows, and resynchronize by one line after an unusable triplet, so a stray line in a truncated response damages one record rather than every record after it. - `normalizeProjectPreferences`: read the default from `DEFAULT_PROJECT_PREFERENCES` instead of repeating the literal. That surfaced a latent bug — `createEmptyProject` handed back the shared constant, so a caller editing the new project's preferences rewrote the app-wide defaults — which is fixed here too, with a test. * Address Claude review feedback Clamp the selected entity's path to the arc the document actually sampled. Lead and trail were half the satellite's own period, but only the ISS gets orbit padding — every other satellite is sampled over the plugin's three-hour window. A sampled position does not extrapolate, so a 24-hour GEO or 12-hour GPS orbit drew a line that stopped dead at the edge of the data instead of the ring the comment promised. The entity's availability is exactly the span its samples cover, so lead and trail are now the smaller of half a period and the sampled distance either side of the clock. The ISS, padded half an orbit past both window ends, is unchanged. * Address Claude review feedback - Move the globe's clock window forward with every refresh. `electCzmlClockOwner` writes a document's clock only when the owning layer changes, and `upsertLayer` reuses the layer id, so the viewer kept the very first window it was given. With `LOOP_STOP` the clock then rewound to a three-hour-old start and looped there while each refresh anchored its entities to the real now — at the panel's 600x option, every eighteen seconds. The refresh now sets `startTime`/`stopTime` itself, and reclaims `currentTime` only when the old instant fell outside the new window, so a Time Slider scrub inside it survives. - Restore `clock.multiplier` in `deactivate`, symmetrically with `shouldAnimate`. Picking 600x and then switching the plugin off used to leave the globe racing, taking the Sun's day/night cycle and the Time Slider with it. * fix(gods-eye-view): render complete selected orbits * style: auto-format (ruff + oxfmt) [pre-commit.ci] * feat(gods-eye-view): add dense satellite catalog * Address review feedback: propagate the core catalog with SGP4 - `sampleSatellitePosition` now propagates with SGP4 through satellite.js, which the dense catalog added as a dependency, closing the accuracy finding CodeRabbit raised. TLE elements are mean elements fitted to that model: reading them as plain Keplerian elements froze the secular drift they encode (J2 nodal regression of roughly five degrees a day for a low orbit, plus drag), so a ground track slid visibly within hours of an epoch already hours old when fetched. A per-record satrec is memoized in a WeakMap, since a three-hour arc samples the same satellite ninety times. The two-body solution stays as the fallback for an element set SGP4 will not propagate, where an approximate orbit beats dropping the satellite. - Dense catalog: widen the propagation guard to reject a non-vector position, matching `describeSelectedCzmlEntity`. On the installed satellite.js this is defensive rather than a fix — `propagate` returns null for a decayed orbit, which the existing falsy check already caught — but its types still describe the older `position: false` shape. - Tests: a day of drift separates the SGP4 and two-body paths by more than 100 km, and a catalog SGP4 cannot propagate yields no points and a clean failure rather than NaN coordinates. * Address Claude review feedback - Remove the orphaned "lightweight two-body propagation ... dependency-free" docblock. It described the model that `bcadd4c2` replaced with SGP4 and sat above the satrec cache, contradicting the accurate docstring below it. - Scope the Attribute Table's materialized-rows branch to CZML rather than any layer carrying a truthy `geojson`. Only CZML produces one today, but nothing said so, and a future layer stashing one for its own reasons would have quietly acquired the table. Covered by a test. * Address Claude review feedback - Sample the fleet in slices, yielding between them. A three-hour arc is ninety-odd SGP4 propagations per satellite, so a full core catalog is tens of thousands in one task: measured at ~90 ms for 840 satellites here, and several dropped frames on slower hardware. The dense catalog already chunked its own build for this reason; the core path now does too, 150 satellites at a time. A test covers the seam — every satellite arrives exactly once, under a single document packet. - Dense catalog: rebuild when the core catalog's membership changes. The early return compared only the viewer, so the exclusion set from the first load was kept forever; a satellite entering or leaving a core group would have been drawn twice or not at all until the shell was toggled by hand. An unchanged set still returns early, which is the every-refresh case. - `resolveFeature`: try the `featureRefs` WeakMap before the CZML scan. This runs per hovered entity on every mouse move, not only on click, so the constant-time lookup belongs first; the CZML docstring said "not per frame", which was wrong, and now says what actually drives it. * Fix dense satellite catalog interactions * style: auto-format (ruff + oxfmt) [pre-commit.ci] * Address Claude review feedback Leave a legacy project's Cesium globe basemap alone on load. `normalizeProjectPreferences` fell back to the new-project default when a saved project omitted `cesiumBasemap`, so every project written before the field existed switched its globe to Bing Aerial the next time it opened on a machine with an Ion token. Five review threads flagged it; the deciding argument is that the same function documents the opposite policy four lines above, for `mapboxStyleUrl`: "Missing means follow the saved project basemap. Do not reapply the new-project Streets default after a user has selected a shared style." `arcgisBasemap` follows that too — `cesiumBasemap` was the only one reapplying its default. A missing field now normalizes to `project` again. New projects are unaffected: they take the default from `DEFAULT_PROJECT_PREFERENCES` and save it explicitly, and the render-time `availableCesiumBasemap` gate still prefers Ion imagery when nothing is stored at all. This reverses behaviour a test previously asserted ("defaults new and legacy projects to Bing Aerial"); that test now pins the split instead. * Address Claude review feedback - Mark the feed layers' attribute rows transient. `prepareLayerForSave` strips `geojson` only for a layer that says so, and `upsertLayer` never did — unlike `ensureDenseLayer` — so every save and autosave embedded the whole materialized row model, up to 2,000 satellites, refreshed on each fetch. Exactly what the flag and the snapshot-ceiling comment exist to prevent. - Refresh a feed whose rows were stripped. The interval may only spare a fetch when the layer still has its table, or a project reopened inside the interval would show an empty Attribute Table until it elapsed. - Give the Ion branch the same fallback chain the keyless one got: Ion imagery, then keyless Esri, then street tiles. A revoked or expired token now leaves a drawn globe rather than a bare one. * Address Claude review feedback `fetchCelestrakTleText` fell through to the direct CelesTrak read only when the proxy answered with a non-OK status. A hop that throws — the edge worker unreachable, DNS, CORS — escaped the loop, so the last-resort read the docstring promises was never attempted in exactly the case it exists for. The fetch is now wrapped per URL, with an abort left fatal since that is the caller's decision rather than a failed hop. Tests cover both: a thrown first hop falls through to the direct URL and returns its text, and an aborted read rejects instead of trying the next. * Address Claude review feedback Declare every renderer in the plugin's `engines`. The list gates whether the Plugins menu entry can be toggled at all, not which engine the plugin draws on, so omitting Mapbox and ArcGIS greyed the entry out on those renderers and put the "Live globe feeds render on the Cesium globe. Switch to the 3D globe to view them." note behind the very door it was written to open. The feeds still render on the globe alone — `activate` already handles having no primary Cesium scene — which is the same split `flight-simulator` declares with `["maplibre", "mapbox", "cesium"]`. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> | 25 天前 | |
feat: add DashMap support for Plotly Dash (#2136) * Add DashMap component * feat: add DashMap support * style: auto-format (ruff + oxfmt) [pre-commit.ci] * Address CodeRabbit review feedback - DashMap front end: the app posts "geolibre:ready" only once per iframe load, so a project change on a mounted iframe never reached it. Track readiness in a ref and push the latest project when `project` changes after ready, mirroring the anywidget front end's ready + onProjectChange pattern. Message handlers now read the latest props through a ref (so a changed setProps is never stale) and each load carries an incrementing seq instead of a constant 1. - Add tests/dash-component-frontend.test.ts covering the ready handshake, the project-change-after-ready push (fails without the fix), the before-ready case, cross-origin/foreign-window rejection, and the click -> clickData mapping. - Install Dash in the publish-python test job and add it to the `dev` extra, so test_dashmap_requires_dash_or_has_component exercises the installed-Dash branch instead of only the ImportError fallback. Claude-Session: https://claude.ai/code/session_01J6hyqLp3Cfym5WGZjsxYTT --------- Co-authored-by: arich <arich@scwa.ca.gov> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: giswqs <giswqs@gmail.com> | 1 个月前 | |
fix(jupyter): serve embedded app via Jupyter Server extension on managed JupyterHub (#247) (#250) * fix(jupyter): serve embedded app via Jupyter Server extension on hubs (#247) The widget reached the bundled app on JupyterHub through jupyter-server-proxy's raw-port route ({base_url}proxy/{port}/index.html). On managed/shared hubs that route 404s because jupyter-server-proxy is often absent or raw-port proxying is disabled, so the iframe stayed blank. Ship a Jupyter Server extension in the wheel that serves the bundled app from the notebook server's own origin at {base_url}geolibre/app/. It auto-enables on `pip install geolibre` via an etc/jupyter/jupyter_server_config.d drop-in, needs no jupyter-server-proxy and no extra port, and works on locked-down hubs. The widget uses this route under JupyterHub (auto) or server_proxy=True, keeping the localhost path for local Jupyter/VS Code and Colab's port proxy unchanged. The front-end HEAD-probes the route and shows an actionable message if the extension is not active. Drops the now-unused [hub] extra. Adds extension serving/auto-enable tests. * Address review feedback - _extension.py: import tornado lazily inside load_jupyter_server_extension so `pytest python/tests` collects without tornado (CI installs only anywidget/traitlets/pytest); fixes the failing Test Python package jobs. - _extension.py: log a warning when the bundled app dir is absent so a dev checkout (no `npm run build:embed`) gets a clear diagnostic instead of silent 404s; rewrite handler docstring to describe its actual role. - _server.py: validate the bundle only when booting the singleton, so a later serve_app() call with a missing/different path reuses the running server instead of raising FileNotFoundError. - _frontend.js: abort the reachability HEAD probe after 5s so a stalled request cannot hang widget rendering; add "then re-run this cell" recovery hint. - python/README.md: note that an already-running Jupyter server must be restarted for the auto-enabled extension to take effect. - tests: split tornado-dependent route tests into test_extension_routes.py (guarded by importorskip) and add a JupyterHub-style base_url=/user/alice/ case alongside the root case. * Update Python version support and increment package version * Address review feedback - _frontend.js: show a "connecting…" placeholder while the reachability probe runs (the cell was blank during the await), and raise the HEAD-probe abort deadline from 5s to 15s so cold/loaded hubs are not flagged spuriously; mount the iframe with replaceChildren to clear the placeholder. - _extension.py: correct the _AppStaticHandler docstring — a plain tornado handler sits outside Jupyter Server's per-request auth pipeline, so the public, data-free bundle is served unauthenticated by design (verified no "Unprotected handler" warning on jupyter_server 2.17). - test_extension_routes.py: add a HEAD-method assertion per base_url so a GET-only regression of the probe path is caught. * Update Python version requirement to 3.11 in pyproject.toml * Address review feedback - publish-python.yml: align the matrix comment with the raised minimum (requires-python >= 3.11); the matrix already starts at 3.11. - _frontend.js: cache positive reachability results per base URL so a re-render skips the redundant HEAD probe; leave negatives uncached so re-running the cell after enabling the extension still re-probes and recovers. - _server.py: clarify the first-boot-only validation comment (a later missing/different path 404s rather than raising, given the singleton always serves _STATIC_APP). * Address review feedback - _extension.py: when the bundled app dir is absent, emit only the warning and skip the 'Serving the bundled app at ...' info line, so the log no longer shows two contradictory messages. * Address review feedback - geolibre.py: fix a regression where Map(server_proxy=True) on Google Colab would switch to the server-extension route (which does not exist on Colab) and fail. _resolve_remote_mode now detects Colab and keeps the localhost + port-proxy path even when the extension is forced, so Colab works regardless of server_proxy. Adds tests for the Colab and non-Colab branches. | 4 个月前 | |
fix(jupyter): serve embedded app via Jupyter Server extension on managed JupyterHub (#247) (#250) * fix(jupyter): serve embedded app via Jupyter Server extension on hubs (#247) The widget reached the bundled app on JupyterHub through jupyter-server-proxy's raw-port route ({base_url}proxy/{port}/index.html). On managed/shared hubs that route 404s because jupyter-server-proxy is often absent or raw-port proxying is disabled, so the iframe stayed blank. Ship a Jupyter Server extension in the wheel that serves the bundled app from the notebook server's own origin at {base_url}geolibre/app/. It auto-enables on `pip install geolibre` via an etc/jupyter/jupyter_server_config.d drop-in, needs no jupyter-server-proxy and no extra port, and works on locked-down hubs. The widget uses this route under JupyterHub (auto) or server_proxy=True, keeping the localhost path for local Jupyter/VS Code and Colab's port proxy unchanged. The front-end HEAD-probes the route and shows an actionable message if the extension is not active. Drops the now-unused [hub] extra. Adds extension serving/auto-enable tests. * Address review feedback - _extension.py: import tornado lazily inside load_jupyter_server_extension so `pytest python/tests` collects without tornado (CI installs only anywidget/traitlets/pytest); fixes the failing Test Python package jobs. - _extension.py: log a warning when the bundled app dir is absent so a dev checkout (no `npm run build:embed`) gets a clear diagnostic instead of silent 404s; rewrite handler docstring to describe its actual role. - _server.py: validate the bundle only when booting the singleton, so a later serve_app() call with a missing/different path reuses the running server instead of raising FileNotFoundError. - _frontend.js: abort the reachability HEAD probe after 5s so a stalled request cannot hang widget rendering; add "then re-run this cell" recovery hint. - python/README.md: note that an already-running Jupyter server must be restarted for the auto-enabled extension to take effect. - tests: split tornado-dependent route tests into test_extension_routes.py (guarded by importorskip) and add a JupyterHub-style base_url=/user/alice/ case alongside the root case. * Update Python version support and increment package version * Address review feedback - _frontend.js: show a "connecting…" placeholder while the reachability probe runs (the cell was blank during the await), and raise the HEAD-probe abort deadline from 5s to 15s so cold/loaded hubs are not flagged spuriously; mount the iframe with replaceChildren to clear the placeholder. - _extension.py: correct the _AppStaticHandler docstring — a plain tornado handler sits outside Jupyter Server's per-request auth pipeline, so the public, data-free bundle is served unauthenticated by design (verified no "Unprotected handler" warning on jupyter_server 2.17). - test_extension_routes.py: add a HEAD-method assertion per base_url so a GET-only regression of the probe path is caught. * Update Python version requirement to 3.11 in pyproject.toml * Address review feedback - publish-python.yml: align the matrix comment with the raised minimum (requires-python >= 3.11); the matrix already starts at 3.11. - _frontend.js: cache positive reachability results per base URL so a re-render skips the redundant HEAD probe; leave negatives uncached so re-running the cell after enabling the extension still re-probes and recovers. - _server.py: clarify the first-boot-only validation comment (a later missing/different path 404s rather than raising, given the singleton always serves _STATIC_APP). * Address review feedback - _extension.py: when the bundled app dir is absent, emit only the warning and skip the 'Serving the bundled app at ...' info line, so the log no longer shows two contradictory messages. * Address review feedback - geolibre.py: fix a regression where Map(server_proxy=True) on Google Colab would switch to the server-extension route (which does not exist on Colab) and fail. _resolve_remote_mode now detects Colab and keeps the localhost + port-proxy path even when the extension is forced, so Colab works regardless of server_proxy. Adds tests for the Colab and non-Colab branches. | 4 个月前 | |
feat(layers): editable layer metadata with STAC Item export (#2904) * feat(layers): editable layer metadata with STAC Item export Add an optional `descriptiveMetadata` block to GeoLibreLayer (title, abstract, keywords, license, attribution, contact, lineage, temporal extent, links). It is normalized on load and save, so old projects load unchanged and an empty block is never written. The field is named apart from the existing internal `metadata` record the renderers and plugins key off. The layer Metadata dialog becomes an editable form above the read-only source details, with email, URL and date validation. Save commits the draft through setLayerDescriptiveMetadata, one undoable store update. "Export as STAC Item" writes a STAC 1.0 Item (extent, datetime or start/end, providers, processing:lineage, links, a data asset for a remote source URL). GeoParquet exports carry the block in the Parquet key-value metadata under `geolibre:metadata`. The layer library captures and restores the block, and the Python package and MCP server gain set_layer_metadata / clear_layer_metadata, pinned by the cross-language round-trip fixture. Part of #2858 * Address review feedback - MCP set_layer_metadata: merge temporal_start and temporal_end one at a time, so passing only one bound keeps the stored other bound. - isRealDate: use setUTCFullYear instead of Date.UTC, which maps years 0-99 to 1900-1999 and rejected valid dates such as 0099-01-01. - metadataDateToRfc3339: normalize a compact +HHMM offset to +HH:MM before parsing; the compact form is not an ECMAScript date-time. - Python link validation error names every accepted URL scheme. * Address Claude review feedback - Metadata dialog status line shows "Unsaved changes" alongside a save or STAC export note, so an export note cannot hide draft edits that the export (built from the stored record) did not include. - STAC footprint of a box degenerate on one axis is a LineString, not a zero-area Polygon. * Address Claude review feedback - metadataDateToRfc3339 upper-cases the date-time before parsing: the validator is case-insensitive, but ECMAScript only promises to read an upper-case T and Z, so a lower-case z could export the export time. - The Python mirror upper-cases too, covering a lower-case t separator. * Address Claude review feedback - A {z}/{x}/{y} tile template is exported as a `tiles` asset (role `tiles`, no guessed media type) instead of a `data` asset, since it cannot be fetched as-is. Documented in project-format.md. | 11 天前 | |
feat: 3D polylines, polygons and keypoints for point cloud annotation (#2765) * feat: 3D polylines, polygons and keypoints for point cloud annotation A 3D vectors section draws polylines (V), 3D polygons and keypoints (K) whose vertices snap to the nearest drawn point and take its elevation. They are saved with the project, exported as 3D GeoJSON and a Segments.ai pointcloud-vector label in the source CRS, and read by the Python package and MCP. A dblclick listener now finishes vectors and closes selection polygons, since pointer events carry no click count. * Address review feedback - Mark the project as changed on every annotation edit (point labels, instances, custom classes, 3D boxes and vectors), so closing an edited project prompts to save; these live in plugin state, not store layers. - Backspace on the last vertex ends the draft and releases the camera. - Switching vector kinds mid-draw reports that the draft was discarded. - Cover signed-URL redaction of vectors in get_point_cloud_annotations. * Address Claude review feedback - Clamp the vertex snap loop to the points the live buffers cover, like the selection and highlight readers, since a streamed cloud's point count can run ahead of its buffers. | 15 天前 | |
feat(python): author filters, labels, plugin state and story maps; live processing in MCP (#2892) * feat(python): author filters, labels, plugin state and story maps Add first-class authoring for layer filters, labels, plugin state and story map chapters to geolibre.authoring, with thin wrappers on Map and new MCP tools (set_layer_filter, set_labels, set_plugin_state, set_story_map, add/remove/move_story_chapter). project.py builds the pieces (labels object, filter expression, chapter) mirroring the TypeScript defaults and vocabularies, and repo drift tests compare them with types.ts and the plugin sources. Add live_list_algorithms and live_run_algorithm MCP tools over the desktop relay; processing runs in the app, so it is live-only. A pinned fixture written by the authoring functions is loaded through the app's parseProject in a new frontend test, so the produced JSON is proven to round-trip unchanged. Bookmarks are not authored: the app keeps them in browser storage, not in the project file. Part of #2858 * Address review feedback - set_labels (MCP): also refuse field/expression/enabled inside options, which previously crashed with a TypeError instead of a readable tool error. - filter_expression: case/match/coalesce/let are accepted only when every branch they can return is boolean, so a numeric case no longer slips past the pre-check and gets dropped by the app on load. - set_plugin_state: clearing settings (state=None) no longer activates the plugin or moves its control as a side effect; documented. - set_labels: enabled now defaults to None and keeps the current state, so a restyle no longer re-shows labels hidden with enabled=False. - docs/python.md: state must be plain JSON. - test_mcp_live: import ToolError after the SDK skip guard. * Address review feedback - set_plugin_state: an omitted state now keeps the stored settings, so a call that only repositions or (de)activates a plugin cannot wipe them; removing them takes an explicit clear=True (Map and MCP too). - Expressions: a RecursionError from a deeply nested expression is now the documented ValueError, for filters and label expressions. - set_labels: a stored labels object without an enabled key counts as on, so labels added over a hand-edited {} are visible. - label_style: document that data-defined expressions are checked for shape, not result type. | 11 天前 | |
feat(mcp): add read-only map previews with MCP Apps (#3097) | 5 天前 | |
feat(mcp): add read-only map previews with MCP Apps (#3097) | 5 天前 | |
feat: headless point cloud pre-labels and labelled full-file rewrites (#2771) * feat: headless point cloud pre-labels and labelled full-file rewrites geolibre.pointcloud maps COPC node keys to file order, encodes labels like the app, pre-labels a local copy of a LiDAR layer with the app's Whitebox classifiers (whitebox-workflows) and streams a LAS/LAZ/COPC file with the saved labels and instance ids applied. MCP gains prelabel_point_cloud and write_labeled_point_cloud; Map gains the same. The sidecar gets a /pointcloud job (laspy[lazrs] installed into the conversion runtime on first use), and the annotator a "Full file with labels" export that runs it. * Address review feedback - Sidecar decoder matches the Python reader: a decoded-entry cap alongside the byte budget, a bad node skipped rather than failing the job, and instance ids above 32 bits rejected. - The legacy-to-1.4 upgrade (Python and sidecar) keeps the extra-bytes dimensions, identifiers and creation date, and rewrites the CRS as WKT. - Remove the label payload when a job cannot start. - Retry a failed job poll a few times; recheck sidecar availability at each session start. - Map.write_labeled_point_cloud checks the URL is a LiDAR layer. - Pre-labelling reads only classification codes, chunk by chunk. * Drop an unused laspy binding in prelabel_point_cloud * Address Claude review feedback - Install point cloud support under the conversion runtime's own setup lock, since it mutates the venv that lock guards. - Charge a failed label record's full cap to the budget, as the Python reader does, so malformed records cannot bypass it. * Address Claude review feedback - Keep job label payloads in their own temp folder and remove any older than an hour when a job is prepared, so a job that dies before its script runs cannot leave them behind. - Upgrade the waveform formats 4 and 5 to 9 and 10 (not 6 and 7), so their waveform fields carry over; in both the sidecar and the module. * Fix the waveform-format test fixture and wrap a docstring - The test fixture writes format 4/5 as LAS 1.3, which those formats require (1.2 only allows 0-3). - Wrap an over-long docstring line. * Address review feedback - write_labeled_point_cloud refuses to write over its input, which it would truncate mid-read (the MCP tool and sidecar already refuse). - Use a private payload folder when the shared one in the temp directory is owned by someone else or writable by others, so no other local user can swap a job's labels. * Address Claude review feedback - Refuse COPC hierarchy pages that lie outside the file and cap the hierarchy walk at the file size, in the module and the sidecar, so a crafted page size cannot force a huge read. | 15 天前 | |
feat: Encoded Polyline Encoder/Decoder Implementation (#2030) * feat: Encoded Polyline Encoder/Decoder Implementation Implemented a comprehensive Google Encoded Polyline codec supporting standard precision 5 (Google, OSRM) and precision 6 (Valhalla, Mapbox), unescaping of backslashes, batch parsing with custom delimiters, interactive preview and coordinate inspector in Add Data UI, processing toolbox tools, layer export, and full Python package support with zero external dependencies. * style: auto-format (ruff + oxfmt) [pre-commit.ci] * fix: Sequence was used in the type annotation of add_polyline but was never imported Sequence was used in the type annotation of add_polyline() at line 1203, but was never imported. * fix(polyline): resolve review findings across codec, export, and processing tools for Encoding Polyline #2029 - PolylineSource: use translated previewInfo string and reset delimiters on sample select - tauri-io: deduplicate polyline decoding and validate coordinate ranges for precision detection - vector-export / UI: thread precision option and add distinct precision 5 & 6 export menu actions - core & python: prevent unsafe conversion of literal \n, \r, \t in unescapePolyline - processing: validate polyline ASCII character range and handle MultiLineString roundtrips symmetrically - python: import Sequence from collections.abc and align delta rounding to Math.round() behavior * style: auto-format (ruff + oxfmt) [pre-commit.ci] * fix(polyline): localize UI labels and harden coordinate validation in processing and Python API #2029 - LayerPanel & AttributeTable: localize Encoded Polyline export options via dynamic precision catalog key - PolylineSource: localize sample route labels and metric distance units via t() - vector-tools: add bounds validation ([-180, 180], [-90, 90]) for decoded coordinates in decodePolylineTool - tests: add null assertion on decoded geometry and test for out-of-bounds polyline input - python/polyline: validate coordinate bounds in encode_polyline, check character byte range in decode_polyline, and default unescape to False * style: auto-format (ruff + oxfmt) [pre-commit.ci] * fix(polyline): enhance decoder validation, geometry export restrictions, and UI formatting #2029 - packages/core: export `decodePolylineDetailed` to indicate whether polylines were fully consumed without truncation. - packages/processing: update `decodePolylineTool` to skip features with incomplete coordinate pairs using `decodePolylineDetailed`. - apps/geolibre-desktop: - Format distance preview using locale-invariant SI units (`m` / `km`). - Sanitize `baseProperties.source` to file basename instead of full local path. - Strict line-compatibility check in `layerSupportsPolylineExport` to reject mixed-geometry layers. - python: validate cumulative lat/lon bounds `[-90, 90]` and `[-180, 180]` in `decode_polyline`. - tests: add regression tests for truncated coordinate decoding, mixed geometry exports, and Python coordinate bounds. * style: auto-format (ruff + oxfmt) [pre-commit.ci] * fix(polyline): reject overlong varint shifts and harden geometry export restrictions #2029 - packages/core: guard polyline varint decoding loops against shifts exceeding 30 bits or 32-bit payload overflows, returning incomplete status immediately. - apps/geolibre-desktop: ensure layerSupportsPolylineExport strictly requires all feature geometries to be line-compatible. - tests: add regression tests for the overlong "_______?" varint chunk in decodePolyline and decodePolylineTool. - python: add tests covering coordinate bounds validation on latitude and longitude axes. * style: auto-format (ruff + oxfmt) [pre-commit.ci] * fix(polyline): correct sample escaped data and harden coordinate validation #2029 - apps/geolibre-desktop: - Update `sampleEscaped` sample polyline in `PolylineSource.tsx` with a valid string containing a properly escapable backslash varint sequence. - Harden `hasValidPolylineCoordinates` in `tauri-io.ts` to reject empty coordinate lists and enforce non-empty total point counts. - Document precision 5 vs 6 auto-detection heuristics and bounds handling during dropped-file ingestion. * style: auto-format (ruff + oxfmt) [pre-commit.ci] * fix(polyline): enforce complete decoding in batch decoder and clean up imports #2029 - packages/core: require `complete === true` from `decodePolylineDetailed` in `batchDecodePolylines` before accepting features. - apps/geolibre-desktop: remove unused `Info` and `MapPin` imports from `PolylineSource.tsx`. - tests: add test verifying `batchDecodePolylines` skips lines with invalid characters or truncated varints. * style: auto-format (ruff + oxfmt) [pre-commit.ci] * Address review feedback - Fix the "Escaped backslashes (JSON string format)" sample, which failed to decode at all ("No valid polyline coordinates could be decoded"). The old value was not a real polyline: it carried one stray backslash that truncated a varint, and even ignoring that it decoded to coordinates off the planet (lat 130, lon 774). Replaced with a San Francisco -> Monterey route whose encoding genuinely contains a backslash, written doubled the way a JSON payload carries it, so the dialog's unescape step is what makes it decode. - Move SAMPLE_POLYLINES out of PolylineSource.tsx into a leaf module, lib/polyline-samples.ts, so the samples can be decoded in a test rather than only in the UI, and add tests/polyline-samples.test.ts covering every sample and the escaped one's with/without-unescape behaviour. - Document in parsePolylineFileLayers that the precision 5 -> 6 bounds check cannot resolve the ambiguous cases: the two decodes differ by exactly 10x, so in-bounds at precision 5 implies in-bounds at precision 6, and precision-6 data near the prime meridian/equator still imports 10x too large. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: giswqs <giswqs@gmail.com> | 1 个月前 | |
feat: 3D polylines, polygons and keypoints for point cloud annotation (#2765) * feat: 3D polylines, polygons and keypoints for point cloud annotation A 3D vectors section draws polylines (V), 3D polygons and keypoints (K) whose vertices snap to the nearest drawn point and take its elevation. They are saved with the project, exported as 3D GeoJSON and a Segments.ai pointcloud-vector label in the source CRS, and read by the Python package and MCP. A dblclick listener now finishes vectors and closes selection polygons, since pointer events carry no click count. * Address review feedback - Mark the project as changed on every annotation edit (point labels, instances, custom classes, 3D boxes and vectors), so closing an edited project prompts to save; these live in plugin state, not store layers. - Backspace on the last vertex ends the draft and releases the camera. - Switching vector kinds mid-draw reports that the draft was discarded. - Cover signed-URL redaction of vectors in get_point_cloud_annotations. * Address Claude review feedback - Clamp the vertex snap loop to the points the live buffers cover, like the selection and highlight readers, since a streamed cloud's point count can run ahead of its buffers. | 15 天前 | |
feat(desktop): keep project-file credentials in the OS keychain (2/2) (#2728) * feat(desktop): keep project-file credentials in the OS keychain Geocoding API keys, secret environment variables, and layer request headers are stored device-wide by name in the OS keychain on desktop and left out of the saved project file (#1667). Environment variables gain a per-row `secret` flag; rows marked `secret: false` stay in the file and survive share/export. The web build keeps today's keep/strip save prompt. * fix(desktop): keep Settings secrets until stored, guard the credential index - Settings keeps the edited plaintext in the store and writes only changed entries to the keychain, so a failed write can no longer lose a value; the next project save strips it or falls back to the keep/strip prompt. commitPreferenceCredentials becomes changedPreferenceCredentials. - rememberProjectCredentials skips names the credential store rejects (over 512 bytes, control characters) so one bad name cannot make the device-wide index unreadable. - Seed the Settings credential ref from the same overlaid preferences as the draft. * fix: preserve ambiguous secrets and show accurate keychain warnings * feat: reference environment variables in request headers instead of storing them by layer ID Layer request headers may now contain ${NAME}, resolved from the project's enabled Environment Variables when the request is made (keychain-backed secret values on desktop). The template is saved and shared as typed. This replaces the device-wide project.layer.<id>.requestHeaders keychain entries, which any opened project reusing a layer ID could have received. Redaction (JS and Python) keeps header values that are only an optional scheme word plus one reference. The 3D Tiles panel and Settings explain the syntax. * fix(desktop): clearing a project-file override keeps the shared keychain value Settings deleted the device-wide geocoding key (or secret variable) whenever a field was cleared, even when the field showed a plaintext override from the opened project. Other projects then lost the shared value. A cleared field now deletes the stored value only when it showed that value; clearing a differing override just drops it, and the shared value applies again. * Update apps/geolibre-desktop/src/lib/project-credentials.ts Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix: address review findings on credential saves and header references - Undo a save's keychain writes when the save never writes the project (cancelled prompt or picker, failed write, project switched), so an abandoned save cannot replace a device-wide value. - Rebuild Cesium imagery/3D Tiles and Mapbox 3D Tiles when a variable a ${NAME} header references changes. - Honour "Fly to tileset after load" for templated MapLibre tilesets. - Drop the duplicated index comment in project-credentials.ts. * fix: keep 3D Tiles credentials on HTTPS, finish save rollback, rebuild MapLibre tilesets on variable change - Request headers go only to HTTPS (or loopback) tileset URLs on Cesium, Mapbox/deck.gl and the MapLibre panel. Cesium and deck.gl also refuse plaintext child-tile requests (Cesium via a resource proxy, deck.gl via a fetch function). The shared check moves to @geolibre/core. - Await an abandoned save's keychain rollback before the save guard is released, and report a failed restore. - Rebuild native MapLibre tilesets whose ${NAME} headers resolve differently after an environment variable change. * fix: undo partial keychain writes after a Keep save, prune resolved 3D Tiles headers - When only some credentials reached the keychain and the user keeps the rest in the file, undo the writes that landed. After Strip they stay, as the keychain is then the only copy. - Forget the resolved headers of a removed MapLibre tileset when the layer leaves the store, not only on the next variable change. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> | 15 天前 | |
fix: proxy local raster URLs in Colab (#2108) * fix: proxy local raster URLs in Colab Route session-scoped raster files through Colab's kernel proxy so xarray layers can load remotely. Always strip outputs from every tracked notebook during pre-commit runs. * Address Claude review feedback - Support local raster rewriting through jupyter-server-proxy as well as Colab. - Rewrite sourcePath independently when it references a registered local file. - Preserve git stderr when notebook discovery fails. * Address CodeRabbit review feedback - Terminate nbstripout option parsing before passing tracked notebook paths. * fix: rebase stale Colab raster proxy URLs Colab gives separate widget views distinct proxy origins. Rebase tokenized raster URLs from an older view onto the current view before restoring layers. * fix(python): preserve raster range requests through Colab proxy * style: auto-format (ruff + oxfmt) [pre-commit.ci] * fix(python): tunnel partial raster responses through Colab * style: auto-format (ruff + oxfmt) [pre-commit.ci] * fix(python): render local rasters as XYZ tiles in Colab * Address review feedback - _server.py: gate the Content-Range/X-GeoLibre-Content-Range headers on a newly parsed range rather than on the presence of a Range header, so a non-bytes unit (e.g. "items=0-1") no longer emits a spec-invalid Content-Range alongside a full-file 200. - _server.py: send Access-Control-Expose-Headers for X-GeoLibre-Content-Range, without which a cross-origin fetch cannot read it and the app leaves the response at 200 instead of rebuilding the 206. - _frontend.js: require an empty port on the Colab proxy hostname, so a look-alike host on a foreign network port does not enter the rewrite path. - _frontend.js: extract the transport gate into an exported canProxyLocalFiles(base, port) and cover it in tests, so a future proxyBase/resolveBase refactor cannot silently stop the rewrite. - geolibre.py: resolve the local raster path once and hand the resolved path to both register_raster_tiles and rasterio.open — GDAL does not expand "~", so a "~/dem.tif" source silently lost its computed bounds. - kernel-proxy-range.ts: export KERNEL_CONTENT_RANGE_HEADER and cross-reference the three mirrored literals from _frontend.js and _server.py; a new test asserts both Python sources against the exported constants. - python-api.md: fix the duplicated "JupyterHub when" clause, matching the wording in docs/python.md. - tests: cover the malformed-range 200, the exposed range header, and both the populated and None bounds paths of add_cog's Colab branch. * Address Claude review feedback - add_cog: the `url:` docstring still claimed a local raster is only reachable when the browser runs on the kernel's host, which this PR made stale. Restate it the way docs/python.md now does: direct reads in local Jupyter/VS Code, kernel-rendered PNG XYZ tiles in Colab, the kernel port via jupyter-server-proxy on JupyterHub, and a hosted URL for an extension-only deployment. * Address Claude review feedback - Remove the Colab range-proxy bridge, which the PNG-tile path in 45599eff superseded and left unreachable: `register_local_file` is called only from `_resolve_raster_source`, which only `add_cog`'s non-Colab branch reaches, so no `/_geolibre_local/` URL is ever emitted under Colab and nothing ever set the `__geolibre_range_proxy` marker the bridge keys on. Deleted `kernel-proxy-range.ts` and its global `fetch` wrapper (installed in `main.tsx` for every GeoLibre user), the marker logic in `_frontend.js`, and the `__geolibre_range` / `X-GeoLibre-Content-Range` transport in `_server.py`, along with their tests. Ordinary `Range` header support — what local Jupyter, VS Code, and jupyter-server-proxy actually use — is unchanged, including the `range_matched` fix from 8af3e91c. - Document the notebook hook's blast radius in docs/contributing.md and expand the comment in .pre-commit-config.yaml: it rewrites tracked notebooks in place, so unstaged locally-executed outputs are erased by any commit, not merely excluded from it. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> | 1 个月前 | |
feat: ICESat-2 ATL03 photons, read lazily for the map view (#3091) * feat: ICESat-2 ATL03 photons, read lazily for the map view ATL03 granules hold every photon of an orbit segment (1-7 GB), so they are never loaded whole. A Web Worker opens the granule lazily and reads only what the map view needs: each beam's 20 m geolocation segments (found by a binary search on latitude, shared across the six beams), then just the photon index range inside the view. - Local files: the dialog picks a File (a file input in every build) and the worker mounts it with WORKERFS, so reads are FileReaderSync slices. - NASA granules: Earthaccess "Open" on an ATL03 granule hands the dialog the Earthdata relay URL; the worker reads it with synchronous ranged GETs (custom lazy device: size from a ranged GET, the bearer token, exact reads for whole chunks, an LRU block cache), split across up to six workers by beam, with live MB progress. Photons are merged and thinned evenly once. - Add Data → ICESat-2 / GEDI shows ground tracks with photon counts, surface type, lowest signal confidence and a photon cap; the layer carries h_ph, signal_conf, time and distance_km and is tagged for the along-track profile, which gains a "Photon heights" dot scatter. - Matches a brute-force h5py scan of every photon exactly (per beam) on two real granules (264 MB and 528 MB). Also follow-ups from #3087 / #3089 review: an integer dataset's _FillValue is read as an exact int (uint64 shot numbers), and the profile resets a beam or preset a layer edit made invalid and uses useId() for its clip path. Part of #3078 (Phase 2: ATL03 photon-level data). * Address review feedback - atl03-client: drop a helper worker that fails to start (handled, so no unhandled rejection) so the next read retries it; each worker caps its own part at maxPoints and the merge thins over what arrived, so a dense view does not clone millions of photons only to discard them. - atl03.ts: refuse a server that ignores Range for any start (the probe at 0 included) instead of reading a granule whole; keep ~32 MB of blocks a worker; accept views reported unwrapped across the antimeridian (east > 180). - atl03.worker: clear the previous file and disposer on re-open. - SpaceborneAtl03Options: measure an antimeridian-crossing view's width correctly, and keep existing layer metadata when tagging the layer. - AddSpaceborneLidarDialog: attach the file input while picking. - docs: list ATL03 among the popular datasets. - spaceborne_lidar.py: ignore a malformed integer _FillValue instead of failing the read. * Address review feedback - atl03-client: refuse a read after close() up front, and close helpers spawned during a read that close() interrupted. - AddSpaceborneLidarDialog: clear a stale error or status when the Earthaccess handoff opens an ATL03 granule. - atl03.ts: never read the segment arrays past the dataset's end when the granule's last segment is in view; correct the merge's doc. | 6 天前 | |
feat(python): save set_identify/show_control state; identify a layer list (#2689) * feat(python): save set_identify/show_control state; identify a layer list set_identify and show_control only lived in the widget's synced _ui trait, so save_project, to_project and to_html dropped them and a shared map opened with Identify off and the default controls (#2688). Projects gain an optional `interaction` block (identify target and control visibility). The Python Map writes it from _ui on export and reads it back in load_project. The app arms Identify from it in loadProject and shows or hides the named controls and panels once per loaded project through the same setControlVisible command the widget replays, and writes the block back unchanged on save. set_identify (and the setIdentify command) also take a list of layers. The store keeps it as the all-layers Identify mode restricted by a new identifyLayerIds field, honoured by the MapLibre, Mapbox, ArcGIS and Cesium identify paths and the DuckDB pick bridge. Removing a listed layer narrows the list; the in-app Identify buttons clear it. Fixes #2688 * fix(python): reject a non-list set_identify argument with ValueError * fix(identify): honour the layer list in the Layers panel; keep it on group ungroup A row outside a restricted Identify list no longer counts as owning map clicks, so editing or selecting on it does not disarm Identify. Deleting a group but keeping its layers leaves them in the Identify target. | 19 天前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 5 天前 | ||
| 18 天前 | ||
| 9 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 3 个月前 | ||
| 1 个月前 | ||
| 25 天前 | ||
| 1 个月前 | ||
| 4 个月前 | ||
| 4 个月前 | ||
| 11 天前 | ||
| 15 天前 | ||
| 11 天前 | ||
| 5 天前 | ||
| 5 天前 | ||
| 15 天前 | ||
| 1 个月前 | ||
| 15 天前 | ||
| 15 天前 | ||
| 1 个月前 | ||
| 6 天前 | ||
| 19 天前 |