Files
gods-eye-view/src/layers/streetLevel/selection.js
T
German KhokhlovandClaude Opus 5.5 6390a3346f Merge origin/main: Google Street View, KartaView and Panoramax on the slim Street Level
Main gained three imagery providers (Google Street View, KartaView,
Panoramax), shared line-coverage, cone and photo-viewer modules, one source
lit at a time and the translated interface. Their shared modules still
carried what this branch removes, so the merge ports them onto it:

- lineCoverage.js is this branch's coverage.js made provider-neutral
  (draped lines near the ground only, the selection drawn over them, key
  and rate-limit blocking); sequenceCones.js is sequences.js the same way.
  Mapillary, KartaView and Panoramax all build on them; Mapillary moves
  back to providers/mapillary/ beside the others.
- No ground casting, mesh sampling, surface modes, FOLLOW, legend, prewarm,
  orbit overview points (Panoramax's density dots included), stale-tile
  swaps or water-filled point budget come back.
- Kept from main because the new providers need them: requiresMapStack and
  attachMapStackController (Street View on Google 3D only), ground clicks,
  openNearest with an optional provider nearestImage, openImage {frame},
  reportError, the view-cone marker (now an SVG glyph), filters /
  coverageLabel / sinceBasis, OPEN STREET VIEW, keyAlternatives and i18n.
- photoViewer.js treats the viewer as expanded when its wrap is full screen
  or the iPhone popover, as EXPAND now is.
- The fixture browser gate keeps its 8 steps and adds one each for
  KartaView and Panoramax; catalogs, docs, boundaries and format scope
  follow.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-19 13:51:45 +04:00

93 lines
3.1 KiB
JavaScript

import * as Cesium from 'cesium';
import { CLICK_PICK_WINDOW_PX, STREET_LEVEL_LAYER_ID } from './policy.js';
/** One click handler for every provider's lines and cones, and the marker. */
export function createSelection({ state, parts }) {
const { picking, input } = state.services;
/** The id carries a provider's prefix: that provider handles the pick. */
function onClick(click) {
const viewer = state.viewer;
if (!viewer || !state.enabled) return;
if (input?.isPointerFree && !input.isPointerFree()) return;
const picked = viewer.scene.pick(
click.position,
CLICK_PICK_WINDOW_PX,
CLICK_PICK_WINDOW_PX,
);
const id = picking?.resolvePickId
? picking.resolvePickId(picked)
: picked?.id;
const route = parts.router.resolve(id);
// Not ours: the ground handler decides whether it is the map itself.
if (!route) {
parts.openAtGround?.(click.position, picked);
return;
}
// The position marker is the core's own: nothing to select.
if (!route.instance) return;
route.instance.handlePick(route.id, {
groundPoint: () => groundPointAt(viewer, click.position),
});
}
/** Lon/lat of the ground under a screen point, or null (sky, no picker). */
function groundPointAt(viewer, screenPosition) {
const position = state.services.scenePick?.pickGroundPosition?.(
viewer,
screenPosition,
);
const carto = position && Cesium.Cartographic.fromCartesian(position);
return carto
? {
lon: Cesium.Math.toDegrees(carto.longitude),
lat: Cesium.Math.toDegrees(carto.latitude),
}
: null;
}
/**
* Esc clears the selected sequence, but only once nothing closer to the
* user wants it: an expanded viewer, an open panel or a text field.
*/
function onKeyDown(event) {
if (event.key !== 'Escape' || event.defaultPrevented) return;
// A text field keeps its Esc, rich-text editors (contenteditable) included.
const target = event.target;
if (
target?.isContentEditable === true ||
target?.closest?.(
'.panel-collapsible, [role="dialog"], input, textarea, select',
)
)
return;
if (!parts.hasSelectedSequence()) return;
event.preventDefault();
parts.clearSequences();
}
function install(viewer) {
if (state.clickHandler) return;
state.clickHandler = new Cesium.ScreenSpaceEventHandler(
viewer.scene.canvas,
);
state.clickHandler.setInputAction(
onClick,
Cesium.ScreenSpaceEventType.LEFT_CLICK,
);
document.addEventListener('keydown', onKeyDown);
picking?.registerPickOwner?.(STREET_LEVEL_LAYER_ID, parts.router.ownsPick);
}
function uninstall() {
// Nothing to undo for a layer that was never enabled.
if (!state.clickHandler) return;
if (!state.clickHandler.isDestroyed()) state.clickHandler.destroy();
state.clickHandler = null;
document.removeEventListener('keydown', onKeyDown);
picking?.unregisterPickOwner?.(STREET_LEVEL_LAYER_ID);
}
return { install, uninstall, ownsPick: parts.router.ownsPick };
}