feat(geoportal): panorama viewer with street-view navigation - #718
Open
helllth wants to merge 61 commits into
Open
feat(geoportal): panorama viewer with street-view navigation#718helllth wants to merge 61 commits into
helllth wants to merge 61 commits into
Conversation
helllth
commented
Jun 25, 2026
Member
Demonstrate a panorama "photobox" driven by a `panorama` field emitted from the vector-style infoBoxMapping, independent of the react-cismap photo lightbox (no react-cismap changes). - PanoramaPreview: inline draggable 360° preview in the infobox (250x160), with an expand control - PanoramaLightBox: fullscreen pannellum viewer portaled to document.body so it renders above the map; zoom and fullscreen controls hidden, mobile device-orientation (compass) control kept; close via close button / Esc - wire the panorama branch into geoportal FeatureInfoBox and the shared portals FeatureInfobox - add pannellum 2.5.7 dependency and a minimal type shim Note: includes temporary [PANORAMA] debug logging to verify the field handover.
PanoramaPreview: - full width on small screens / fixed thumbnail on desktop, using an explicit pixel width (the infobox places each secondary element in a shrink-to-fit cell, so a percentage width collapses to zero) - init pannellum once and call viewer.resize() on width change to refit without reloading the image - clean dark loading placeholder and an enlarged loading spinner - 5px gap to the infobox header, matching the photo preview PanoramaLightBox: - render via a portal to document.body so it appears above the map - hide zoom and fullscreen controls, keep the mobile device-orientation control
The responsive-design refactor extracted the preview's inline styles into PanoramaPreview.css but dropped the expand button rule. Without absolute positioning the button fell into normal flow below the full-height viewer and was clipped by the container's overflow: hidden, so the fullscreen control was invisible. Re-add the .carma-panorama-preview__expand rule.
…ction The map's selection-arrow now tracks the live look-direction inside the 360° panorama viewer (Street-View style), instead of only showing the photo's fixed capture heading. - PanoramaPreview / PanoramaLightBox emit an onYawChange(yaw) callback, driven by a requestAnimationFrame loop polling viewer.getYaw() (pannellum has no continuous view-change event). The callback is held in a ref so it never re-inits the viewer / reloads the image. - FeatureInfoBox resolves the selected layer's maplibre map via getMaplibreMaps (keyed by selectedFeature.id) and sets selection-arrow's icon-rotate to heading + yaw. Since that layer only renders the selected feature, the layer-wide constant rotates just the visible arrow; the data-driven ['get','heading'] is restored on deselect/unmount. - Only one viewer drives at a time: the inline preview yields to the fullscreen lightbox while it is open. - Tunable PANORAMA_YAW_SIGN / PANORAMA_YAW_OFFSET constants for calibration.
Temporary [PANORAMA] debug log of the raw native maplibre features (e.hits) in onSelectionChanged, before carma maps them into the redux selectedFeature, plus a window.__panoramaHits handle for interactive inspection. Strip before merge.
Add Street-View-style navigation between panoramas plus the viewer-orientation handling needed to make it usable: - PanoramaLightBox: accept hotspots (new PanoramaHotspot type) and render them as clickable arrow-discs (PanoramaLightBox.css); clicking one hops to a neighbouring pano. - FeatureInfoBox: find surrounding panos at runtime via querySourceFeatures using each feature's proj_x/proj_y/proj_z, place a hotspot per neighbour at its bearing with pitch refined from horizontal distance + altitude difference, and declutter by distance + angular separation. Navigation rebuilds the neighbour's carma feature via createFeature and selects it, so viewer, infobox and map arrow all follow the tour. - Keep the map selection highlight a pure function of the redux selection (covers programmatic hops) and snap the arrow to the new heading on selection to avoid a stale-direction jump. - Open the viewer facing forward (initialYaw 180; the image centre faces the back of the survey car) and gate yaw reporting on isLoaded() so the arrow does not briefly flip 180° before pannellum applies the initial yaw.
The selection-arrow's icon-rotate was overridden with a precomputed scalar (selectedHeading + yaw). On selection change the cismap path highlights the new feature one frame before the snap effect runs, so for that frame the arrow used the previous feature's heading — a full heading error (~90° at a T-junction). Write icon-rotate as an expression on the feature's own heading (['+', ['get','heading'], yaw]) in the live follow, the on-select snap and the reset. The visible (selected) arrow is then always rotated by its own heading, so it is correct the instant the selection moves; only the small yaw delta can lag, and that is zero when facing forward.
Pannellum shows a spinner and a load-progress box while a panorama loads. For a cached or fast-decoding image this only flashes briefly, which reads as a glitch. Hide the load indicator (pannellum sets its display inline, so !important is required) and keep a dark #2a2a2a placeholder to cover the short decode time, in both the inline preview and the fullscreen lightbox. Tag the lightbox viewer container with a carma-panorama-lightbox__viewer class so the CSS can target it.
Make the panorama tour work against a fully client-side geojson style (e.g. oelberg_panorama/styleG.json) in addition to the vector-tile style: - resolvePanoSource now falls back to a geojson source (which has no source-layer) when no vector source is present, so hotspots, the selection highlight and tour navigation resolve correctly for both style types. Feature ids are used as-is: vector tiles bake id=fid and the geojson dataset carries id=fid natively, so the style must not set generateId (which would override the data's ids). - Preload the reachable neighbours' panorama images on selection (Image + decode, held in a ref so the in-flight downloads are not GC'd) so a tour hop is near-instant. The hotspot memo now also surfaces the kept neighbours' file names; each neighbour URL is derived from the current panorama URL by swapping the file name. - Guard the selection-arrow cleanup against an already-removed layer: getLayer/setLayoutProperty throw once the layer's map is torn down, which crashed when removing a layer while a panorama was selected.
Pannellum rewrites each hotspot host element's inline transform every frame to reposition it as the user pans. The hotspot carried its own transition: transform, so it animated toward each new position instead of snapping there, making the arrows swim/lag and "settle" when panning stopped. Move the disc and its hover-scale onto a ::before pseudo-element (which pannellum never touches) and leave the host element transform-free, so it stays locked to its bearing while hover still animates smoothly.
Each navigation hotspot now points toward the panorama it leads to, Street-View style, instead of always pointing up. pannellum's createTooltipFunc gives a handle to each rendered hotspot element; the per-frame yaw poll sets a --pano-hotspot-dir CSS variable on each to the on-screen angle from view-centre to that neighbour (its panorama yaw minus the current view yaw), and the chevron is rotated by it. A neighbour you face points up; one off to the side points that way.
Load a pannellum multiresolution panorama (tiled cube faces) when the selected feature carries a panoramaMultiResConfig URL, falling back to the equirectangular jpg otherwise. Multires paints a low-res cube immediately and refines, so first view is far faster than pulling a single large equirectangular image. - new resolvePanoramaScene() fetches the generated config.json and gives it a basePath (its own directory) so pannellum can resolve the relative tile paths, which the generated config omits; on any failure (e.g. a pano not yet tiled -> 404) it falls back to the equirectangular image. - PanoramaPreview and PanoramaLightBox take a multiResConfigUrl prop and resolve the scene asynchronously (guarded against teardown mid-fetch). - FeatureInfoBox passes properties.panoramaMultiResConfig to both.
The lightbox used to destroy and recreate the whole pannellum WebGL viewer on every tour hop, paying context teardown/setup plus a full decode each time. Init the viewer once in tour mode (default + scenes) and, on a hop, addScene + loadScene to crossfade to the new pano, reusing the WebGL context; the fade also masks the decode. - The viewer is created for the first scene and destroyed only on unmount (separate []-effect); the src/multiResConfigUrl effect now either creates it (first scene) or addScene+loadScene (subsequent). - The yaw poll runs for the viewer's whole life across scene loads; the current scene's hotspot elements are swapped into a ref each hop so arrow re-aiming tracks whatever is on screen. - Scenes are still built by resolvePanoramaScene, so multires and the jpg fallback work per scene. - PannellumViewer gains addScene/loadScene; window.pannellum uses it.
Free pannellum's arrow keys in the fullscreen viewer (disableKeyboardCtrl) and bind Up/Down to a tour hop: Up jumps to the neighbour nearest the current view direction, Down to the one behind it. - PanoramaLightBox takes onNext/onPrevious and fires them on ArrowUp/ ArrowDown (preventing page scroll); dragging still pans. - FeatureInfoBox captures the live view yaw from the viewer poll and picks the neighbour whose bearing is nearest the view (Up) or its opposite (Down), reusing the existing hotspot hop (loadScene crossfade). Only fires within a 90° hemisphere of the target, so Up never jumps to a pano clearly behind you.
- discover neighbours from the baked `nb` tile property (viewport-independent), falling back to querySourceFeatures where the tiles do not carry it yet - render neighbours as faint, ground-pinned rings that recede down the street (per-cross ground tilt from pitch + distance scale), replacing the chevrons - click anywhere jumps to the nearest cross; hover highlights that same cross; capture-phase listeners so the click beats pannellum's canvas drag handler - keep arrow-key tour navigation; dev [PANORAMA] neighbour-source log remains for now (strip before merge)
…ading - add a ground-pinned cursor arrow to the panorama lightbox: it follows the mouse and aims along the nearest cross's street (its heading), using a pinhole world->screen projection of the current view so it reads as lying on the road - read each neighbour's heading from the baked `nb` (5th value) or, when the deployed tiles predate that field, from the live map source, so the aim works before a redeploy; resolve the heading's 180deg ambiguity toward the target - add getPitch/getHfov/mouseEventToCoords to the pannellum viewer type Dev [PANORAMA] logs still present; to be stripped before merge.
When navigating between panoramas in the fullscreen viewer, pan the Leaflet base map to the pano being jumped to (keeping the current zoom), so closing fullscreen leaves the just-visited point centered in the map.
The panorama behind the current view was only reachable with the ArrowDown key or by panning 180°. Add an invisible hit zone pinned to the bottom centre of the lightbox (over the survey vehicle) that, on click, calls onPrevious (same as ArrowDown). The zone is not drawn; over it the native cursor is replaced by a downward chevron drawn as a DOM element that follows the pointer, matching the ground cursor arrow's look (an SVG image cursor renders blank in Chrome).
Double the size of both the ground cursor arrow and the back-navigation chevron (46->92px) and render them at 40% opacity for a subtler look.
Previously every tour hop reset the panorama to the forward (initial) yaw. Now a hop keeps the user's current view direction and pitch, so they can follow e.g. a sidewalk from pano to pano (the yaw frame is street-relative, so the relative angle is preserved). Exception: a direct click on a cross dot still resets to the forward, level view.
|
View your CI Pipeline Execution ↗ for commit e4ff911
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
🚀 Deployed
|
🚀 Deployed
|
🚀 Deployed
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.