diff --git a/api/js-sdk-core.api.d.ts b/api/js-sdk-core.api.d.ts index 206f03d..5dc40c9 100644 --- a/api/js-sdk-core.api.d.ts +++ b/api/js-sdk-core.api.d.ts @@ -418,7 +418,7 @@ declare const MapUISettingsDiagnostics: { * かといってコンストラクタを持たせると、状態型がプロバイダ固有になったときに破綻する。 * 種別という別の軸で持つ。 * - * **描画順(zIndex)とは別軸**であることに注意。現状 polygon(3) > groundImage(2) だが、 + * **描画順({@link OVERLAY_STACK_ORDER})とは別軸**であることに注意。重なりは groundImage が polygon の下だが、 * クリックの探索は groundImage が先。 * * android-sdk / ios-sdk にも同名・同じ並びで置く。 @@ -5620,5 +5620,5 @@ declare function segmentIntersectsRegion({ start, end, region, geodesic, }: { declare function splitByMeridian(points: GeoPoint[], geodesic: boolean): GeoPoint[][]; -export { AbstractCircleOverlayRenderer, AbstractGroundImageOverlayRenderer, AbstractMarkerController, AbstractMarkerIcon, AbstractMarkerOverlayRenderer, AbstractMarkerRenderingStrategy, AbstractPolygonOverlayRenderer, AbstractPolylineOverlayRenderer, AbstractViewportStrategy, AbstractZoomAltitudeConverter, BLANK_MAP_STYLE, BLANK_MAP_STYLE_URL, BaseMapViewController, CANONICAL_ORDER, CircleController, CircleManager, CircleOverlay, CollectorMarkerOverlayRenderer, ColorDefaultIcon, DEFAULT_CIRCLE_SEGMENTS, DEFAULT_RASTER_LAYER_USER_AGENT, DefaultMarkerEventController, DefaultMarkerIcon, Direction6, Direction6Delta, DriverConformanceViolation, Earth, EmptyMapServiceRegistry, GROUND_IMAGE_DEFAULT_TILE_SIZE, GeoGridIndex, GeoPoint, GroundImageController, GroundImageManager, GroundImageOverlay, GroundScaleZoomAltitudeConverter, HexGeocellImpl, IconImageCache, ImageDefaultIcon, ImageIcon, InitState, LocalTileServer, MAP_CAPABILITIES, MARKER_HIT_RADIUS_MOUSE_PX, MARKER_HIT_RADIUS_TOUCH_PX, MARKER_HIT_SEARCH_RADIUS_PX, MARKER_RENDER_BATCH_SIZE, MAX_TILE_PIXEL_RATIO, MapCameraPosition, MapCapabilityStatus, MapDiagnostics, MapDriverConformance, MapOverlayRegistry, MapPaddings, MapProjection, MapProvider, MapProviderType, MapServiceRegistrations, MapUISettings, MapUISettingsDiagnostics, MapViewBase, MapViewHolderBase, MapViewState, MapViewStyleHost, MarkerAnimation, MarkerGrid, MarkerIconSize, MarkerManager, MarkerOverlay, MarkerRenderingSupportKey, MarkerTileRenderer, MarkerTilingOptions, MutableMapServiceRegistry, Mutex, NoCameraRestriction, OVERLAY_KINDS, OverlayCollector, OverlayGeoJson, OverlayHitResolver, Planar, PolygonController, PolygonManager, PolygonOverlay, PolylineController, PolylineManager, PolylineOverlay, RasterHeaderRuleSet, RasterLayerController, RasterLayerManager, RasterLayerOverlay, RasterLayerSource, RasterTilePreferenceKey, ScreenProjectionRequirement, Settings, Spherical, StrategyMarkerController, StyleMutationStore, TRANSPARENT_TILE_PNG, TileRenderFailedError, TileScheme, TileServerRegistry, VectorStyleAsDesign, VectorStyleMutationSupportKey, VectorStyleSupportKey, WEB_MERCATOR_MAX_EXTENT_METERS, WEB_MERCATOR_MAX_LATITUDE, WGS84, WGS84Geodesic, WebMercator, WebMercatorZoomAltitudeConverter, applyGlMapUISettings, applyStyleMutations, bearingFromNativeHeading, bearingFromNativeRotation, bounceInterpolation, bridgeHolesIntoSingleRing, bridgeHolesIntoSingleRingWrapAware, buildPolygonRings, buildPolylineSegments, buildUnwrappedPolygonRings, buildUnwrappedPolylinePath, buildVisibleRegion, calculateMetersPerPixel, capabilityOfGesture, circleToRing, closeRing, closestPointOnSegment, combineHash, computeArea$1 as computeArea, computeDistanceBetween$1 as computeDistanceBetween, computeFitBoundsCameraPosition, computeHeading$1 as computeHeading, computeLength$1 as computeLength, computeOffset$1 as computeOffset, computeOffsetOrigin$1 as computeOffsetOrigin, computeSignedArea$1 as computeSignedArea, computeDistanceBetween as computeWGS84DistanceBetween, createCircleEntity, createCircleState, createCollectorMarkerRenderingSupport, createDefaultIcon, createFingerPrint, createGeoPoint, createGeoRectBounds, createGroundImageEntity, createGroundImageState, createHexCell, createHexCoord, createIdentifiedHexCell, createMapCameraPosition, createMapServiceKey, createMarkerEntity, createMarkerFingerPrint, createMarkerState, createOffset, createOppositeMeridianPoint, createOverlayHit, createPolygonEntity, createPolygonState, createPolylineEntity, createPolylineState, createRandomId, createRasterLayerEntity, createRasterLayerState, createSegmentBounds, createSubject, createTileWorkerHandler, densifyAndNormalize, densifyPolygonRing, describeStyleMutations, expandBounds, fingerPrintEquals, fromGeoPoint, fromLatLng, fromLatLong, fromLngLat, fromLongLat, generateIdFromHashes, getNeighbors, hashBool, hashDefaultMarkerIcon, hashGeoPoint, hashNullable, hashNum, hashObj, hashStr, hexCellToIdPrefix, hexCoordToString, ingestMarkers, interpolate$1 as interpolate, interpolateAtMeridianGeodesic, interpolateAtMeridianLinear, interpolate as interpolateWGS84, isEmptyCameraRestriction, isFullySupported, isKnownUnsupported, isNativeDescribableStyle, isNativeMapExtensionCapable, isSlottedOverlayController, isUsable, longHashCode, mapCapabilityFromId, mapViewStateInternal, markerHitShape, markerIconExtentPx, markerIconHashCode, markerSearchBounds, normalizeLngDegrees, parseStyleMutations, parseTilePixelRatio, pickMarkerHit, pickMarkerIndex, preferMarkerHit, resolveAttributionRules, resolveCameraRestriction, resolveHoles, resolveMapUISettings, resolveRasterAttributions, reverseStyleMutation, segmentIntersectsRegion, splitByMeridian, splitRingByMeridian, styleMutationLayerId, styleMutationProperty, styleMutationTarget, tilePixelRatio, tilePixelRatioSuffix, toDegrees, toInt, toNativeHeading, toNativeRotation, toRadians, unionHoleRings, unionHoles, unionHolesInPlace, vectorStyleKey, viewportSizeOf, withRasterHeaderTransform, wrapClickedPoint }; -export type { AddParams, AnyMarkerRenderingSupport, AttributionRule, BitmapIcon, CameraOptions, CameraRestriction, ChangeParams, ChangeParamsInterface, CircleAddParams, CircleCapable, CircleChangeParams, CircleEntity, CircleEvent, CircleFingerPrint, CircleManagerInterface, CircleOptions, CircleOverlayRenderer, CircleState, CircleStateCopyParams, CreateMarkerStateParams, DefaultMarkerIconOptions, FitBoundsCameraResult, GeoPointInterface, GeoRectBounds, GlGestureHandler, GlGestureHandlers, GlMarkerDragMap, GlTouchZoomRotateHandler, GroundImageAddParams, GroundImageCapable, GroundImageChangeParams, GroundImageEntity, GroundImageEvent, GroundImageFingerPrint, GroundImageManagerInterface, GroundImageOverlayRenderer, GroundImageState, GroundImageStateCopyParams, GroundOverlayOptions, HexCell, HexCoord, HexGeocell, HexGeocell as HexGeocellInterface, IdentifiedHexCell, ImageDefaultIconOptions, ImageIconOptions, ImageSource, InfoBubbleEntry, MapCameraPositionCopyParams, MapCameraPositionInterface, MapCapability, MapConfig, MapDesignTypeInterface, MapDiagnosticLevel, MapDiagnosticsSink, MapGesture, MapOverlayInterface, MapPaddingsInterface, MapServiceKey, MapServiceRegistration, MapServiceRegistry, MapStyleHost, MapStyleInstallation, MapViewBaseProps, MapViewControllerInterface, MapViewHolder, MapViewStateInterface, MapViewStateInternal, MapViewStateOptions, MapViewStyle, MarkerAnimationOverlayEntry, MarkerAnimationOverlayHost, MarkerCapable, MarkerEntity, MarkerEventController, MarkerEventHost, MarkerFingerPrint, MarkerHit, MarkerHitRect, MarkerIcon, MarkerIngestionResult, MarkerLabelHorizontalPosition, MarkerLabelPosition, MarkerLabelVerticalPosition, MarkerManagerStats, MarkerOptions, MarkerOverlayRenderer, MarkerRenderingStrategy, MarkerRenderingSupport, MarkerState, MarkerStateCopyParams, NativeDescribableStyle, NativeMapExtensionCapable, NativeMapExtensionDescriptor, NativeMapExtensionEvent, NativeMapExtensionEventHandler, NativeStyleDescriptor, Offset, OnCameraMoveHandler, OnCircleEventHandler, OnGroundImageEventHandler, OnMapEventHandler, OnMapInitializedHandler, OnMapLoadedHandler, OnMarkerEventHandler, OnPolygonEventHandler, OnPolylineEventHandler, OnRasterLayerEventHandler, OverlayController, OverlayControllerLike, OverlayHit, OverlayKind, OverlayRendererInterface, PolygonAddParams, PolygonCapable, PolygonChangeParams, PolygonEntity, PolygonEvent, PolygonFingerPrint, PolygonManagerInterface, PolygonOptions, PolygonOverlayRenderer, PolygonRings, PolygonState, PolygonStateCopyParams, PolylineAddParams, PolylineCapable, PolylineChangeParams, PolylineEntity, PolylineEvent, PolylineFingerPrint, PolylineHitResult, PolylineManagerInterface, PolylineOptions, PolylineOverlayRenderer, PolylineState, PolylineStateCopyParams, PreparedLabel, PreparedMarker, Projection, RasterAttributionRule, RasterHeaderRule, RasterHeaderSupport, RasterLayerAddParams, RasterLayerCapable, RasterLayerChangeParams, RasterLayerEntity, RasterLayerEvent, RasterLayerFingerPrint, RasterLayerManagerInterface, RasterLayerMounting, RasterLayerOverlayRenderer, RasterLayerState, RasterLayerStateCopyParams, RasterRequestParameters, RasterTilePreference, RasterTransformRequest, ScreenOffsetResult, Serializable, SlottedOverlayController, StyleMutation, StylePatchableMap, TileOutcome, TileProvider, TileRenderRequest, TileRenderResponse, TileRequest, VectorStyleMutationSupport, VectorStyleSupport, ViewportSize, VisibleRegion }; +export { AbstractCircleOverlayRenderer, AbstractGroundImageOverlayRenderer, AbstractMarkerController, AbstractMarkerIcon, AbstractMarkerOverlayRenderer, AbstractMarkerRenderingStrategy, AbstractPolygonOverlayRenderer, AbstractPolylineOverlayRenderer, AbstractViewportStrategy, AbstractZoomAltitudeConverter, BLANK_MAP_STYLE, BLANK_MAP_STYLE_URL, BaseMapViewController, CANONICAL_ORDER, CircleController, CircleManager, CircleOverlay, CollectorMarkerOverlayRenderer, ColorDefaultIcon, DEFAULT_CIRCLE_SEGMENTS, DEFAULT_RASTER_LAYER_USER_AGENT, DefaultMarkerEventController, DefaultMarkerIcon, Direction6, Direction6Delta, DriverConformanceViolation, Earth, EmptyMapServiceRegistry, GROUND_IMAGE_DEFAULT_TILE_SIZE, GeoGridIndex, GeoPoint, GroundImageController, GroundImageManager, GroundImageOverlay, GroundScaleZoomAltitudeConverter, HexGeocellImpl, IconImageCache, ImageDefaultIcon, ImageIcon, InitState, LocalTileServer, MAP_CAPABILITIES, MARKER_HIT_RADIUS_MOUSE_PX, MARKER_HIT_RADIUS_TOUCH_PX, MARKER_HIT_SEARCH_RADIUS_PX, MARKER_RENDER_BATCH_SIZE, MAX_TILE_PIXEL_RATIO, MapCameraPosition, MapCapabilityStatus, MapDiagnostics, MapDriverConformance, MapOverlayRegistry, MapPaddings, MapProjection, MapProvider, MapProviderType, MapServiceRegistrations, MapUISettings, MapUISettingsDiagnostics, MapViewBase, MapViewHolderBase, MapViewState, MapViewStyleHost, MarkerAnimation, MarkerGrid, MarkerIconSize, MarkerManager, MarkerOverlay, MarkerRenderingSupportKey, MarkerTileRenderer, MarkerTilingOptions, MutableMapServiceRegistry, Mutex, NoCameraRestriction, OVERLAY_KINDS, OVERLAY_STACK_ORDER, OverlayCollector, OverlayGeoJson, OverlayHitResolver, Planar, PolygonController, PolygonManager, PolygonOverlay, PolylineController, PolylineManager, PolylineOverlay, RasterHeaderRuleSet, RasterLayerController, RasterLayerManager, RasterLayerOverlay, RasterLayerSource, RasterTilePreferenceKey, ScreenProjectionRequirement, Settings, Spherical, StrategyMarkerController, StyleMutationStore, TRANSPARENT_TILE_PNG, TileRenderFailedError, TileScheme, TileServerRegistry, VectorStyleAsDesign, VectorStyleMutationSupportKey, VectorStyleSupportKey, WEB_MERCATOR_MAX_EXTENT_METERS, WEB_MERCATOR_MAX_LATITUDE, WGS84, WGS84Geodesic, WebMercator, WebMercatorZoomAltitudeConverter, applyGlMapUISettings, applyStyleMutations, bearingFromNativeHeading, bearingFromNativeRotation, bounceInterpolation, bridgeHolesIntoSingleRing, bridgeHolesIntoSingleRingWrapAware, buildPolygonRings, buildPolylineSegments, buildUnwrappedPolygonRings, buildUnwrappedPolylinePath, buildVisibleRegion, calculateMetersPerPixel, capabilityOfGesture, circleToRing, closeRing, closestPointOnSegment, combineHash, compareOverlayStackKeys, computeArea$1 as computeArea, computeDistanceBetween$1 as computeDistanceBetween, computeFitBoundsCameraPosition, computeHeading$1 as computeHeading, computeLength$1 as computeLength, computeOffset$1 as computeOffset, computeOffsetOrigin$1 as computeOffsetOrigin, computeSignedArea$1 as computeSignedArea, computeDistanceBetween as computeWGS84DistanceBetween, createCircleEntity, createCircleState, createCollectorMarkerRenderingSupport, createDefaultIcon, createFingerPrint, createGeoPoint, createGeoRectBounds, createGroundImageEntity, createGroundImageState, createHexCell, createHexCoord, createIdentifiedHexCell, createMapCameraPosition, createMapServiceKey, createMarkerEntity, createMarkerFingerPrint, createMarkerState, createOffset, createOppositeMeridianPoint, createOverlayHit, createPolygonEntity, createPolygonState, createPolylineEntity, createPolylineState, createRandomId, createRasterLayerEntity, createRasterLayerState, createSegmentBounds, createSubject, createTileWorkerHandler, densifyAndNormalize, densifyPolygonRing, describeStyleMutations, expandBounds, fingerPrintEquals, fromGeoPoint, fromLatLng, fromLatLong, fromLngLat, fromLongLat, generateIdFromHashes, getNeighbors, hashBool, hashDefaultMarkerIcon, hashGeoPoint, hashNullable, hashNum, hashObj, hashStr, hexCellToIdPrefix, hexCoordToString, ingestMarkers, interpolate$1 as interpolate, interpolateAtMeridianGeodesic, interpolateAtMeridianLinear, interpolate as interpolateWGS84, isEmptyCameraRestriction, isFullySupported, isKnownUnsupported, isNativeDescribableStyle, isNativeMapExtensionCapable, isSlottedOverlayController, isUsable, longHashCode, mapCapabilityFromId, mapViewStateInternal, markerHitShape, markerIconExtentPx, markerIconHashCode, markerSearchBounds, normalizeLngDegrees, overlayStackBand, overlayStackBeforeId, overlayStackZ, parseStyleMutations, parseTilePixelRatio, pickMarkerHit, pickMarkerIndex, preferMarkerHit, resolveAttributionRules, resolveCameraRestriction, resolveHoles, resolveMapUISettings, resolveRasterAttributions, reverseStyleMutation, segmentIntersectsRegion, splitByMeridian, splitRingByMeridian, styleMutationLayerId, styleMutationProperty, styleMutationTarget, tilePixelRatio, tilePixelRatioSuffix, toDegrees, toInt, toNativeHeading, toNativeRotation, toRadians, unionHoleRings, unionHoles, unionHolesInPlace, vectorStyleKey, viewportSizeOf, withRasterHeaderTransform, wrapClickedPoint }; +export type { AddParams, AnyMarkerRenderingSupport, AttributionRule, BitmapIcon, CameraOptions, CameraRestriction, ChangeParams, ChangeParamsInterface, CircleAddParams, CircleCapable, CircleChangeParams, CircleEntity, CircleEvent, CircleFingerPrint, CircleManagerInterface, CircleOptions, CircleOverlayRenderer, CircleState, CircleStateCopyParams, CreateMarkerStateParams, DefaultMarkerIconOptions, FitBoundsCameraResult, GeoPointInterface, GeoRectBounds, GlGestureHandler, GlGestureHandlers, GlMarkerDragMap, GlTouchZoomRotateHandler, GroundImageAddParams, GroundImageCapable, GroundImageChangeParams, GroundImageEntity, GroundImageEvent, GroundImageFingerPrint, GroundImageManagerInterface, GroundImageOverlayRenderer, GroundImageState, GroundImageStateCopyParams, GroundOverlayOptions, HexCell, HexCoord, HexGeocell, HexGeocell as HexGeocellInterface, IdentifiedHexCell, ImageDefaultIconOptions, ImageIconOptions, ImageSource, InfoBubbleEntry, MapCameraPositionCopyParams, MapCameraPositionInterface, MapCapability, MapConfig, MapDesignTypeInterface, MapDiagnosticLevel, MapDiagnosticsSink, MapGesture, MapOverlayInterface, MapPaddingsInterface, MapServiceKey, MapServiceRegistration, MapServiceRegistry, MapStyleHost, MapStyleInstallation, MapViewBaseProps, MapViewControllerInterface, MapViewHolder, MapViewStateInterface, MapViewStateInternal, MapViewStateOptions, MapViewStyle, MarkerAnimationOverlayEntry, MarkerAnimationOverlayHost, MarkerCapable, MarkerEntity, MarkerEventController, MarkerEventHost, MarkerFingerPrint, MarkerHit, MarkerHitRect, MarkerIcon, MarkerIngestionResult, MarkerLabelHorizontalPosition, MarkerLabelPosition, MarkerLabelVerticalPosition, MarkerManagerStats, MarkerOptions, MarkerOverlayRenderer, MarkerRenderingStrategy, MarkerRenderingSupport, MarkerState, MarkerStateCopyParams, NativeDescribableStyle, NativeMapExtensionCapable, NativeMapExtensionDescriptor, NativeMapExtensionEvent, NativeMapExtensionEventHandler, NativeStyleDescriptor, Offset, OnCameraMoveHandler, OnCircleEventHandler, OnGroundImageEventHandler, OnMapEventHandler, OnMapInitializedHandler, OnMapLoadedHandler, OnMarkerEventHandler, OnPolygonEventHandler, OnPolylineEventHandler, OnRasterLayerEventHandler, OverlayController, OverlayControllerLike, OverlayHit, OverlayKind, OverlayRendererInterface, OverlayStackKey, PolygonAddParams, PolygonCapable, PolygonChangeParams, PolygonEntity, PolygonEvent, PolygonFingerPrint, PolygonManagerInterface, PolygonOptions, PolygonOverlayRenderer, PolygonRings, PolygonState, PolygonStateCopyParams, PolylineAddParams, PolylineCapable, PolylineChangeParams, PolylineEntity, PolylineEvent, PolylineFingerPrint, PolylineHitResult, PolylineManagerInterface, PolylineOptions, PolylineOverlayRenderer, PolylineState, PolylineStateCopyParams, PreparedLabel, PreparedMarker, Projection, RasterAttributionRule, RasterHeaderRule, RasterHeaderSupport, RasterLayerAddParams, RasterLayerCapable, RasterLayerChangeParams, RasterLayerEntity, RasterLayerEvent, RasterLayerFingerPrint, RasterLayerManagerInterface, RasterLayerMounting, RasterLayerOverlayRenderer, RasterLayerState, RasterLayerStateCopyParams, RasterRequestParameters, RasterTilePreference, RasterTransformRequest, ScreenOffsetResult, Serializable, SlottedOverlayController, StyleMutation, StylePatchableMap, TileOutcome, TileProvider, TileRenderRequest, TileRenderResponse, TileRequest, VectorStyleMutationSupport, VectorStyleSupport, ViewportSize, VisibleRegion }; diff --git a/src/circle/CircleController.ts b/src/circle/CircleController.ts index 5715370..357f2a6 100644 --- a/src/circle/CircleController.ts +++ b/src/circle/CircleController.ts @@ -30,7 +30,8 @@ function fingerPrintsEqual(a: ReturnType, b: ReturnT export abstract class CircleController implements SlottedOverlayController, OverlayController, CircleEvent> { - readonly zIndex: number = 3; + // Above the polygons (3), under the polylines (5): OVERLAY_STACK_ORDER. + readonly zIndex: number = 4; public readonly circleManager: CircleManagerInterface; public readonly renderer: CircleOverlayRenderer; public clickListener: OnCircleEventHandler | null; diff --git a/src/controller/OverlayKind.ts b/src/controller/OverlayKind.ts index 2a50f41..2fcde33 100644 --- a/src/controller/OverlayKind.ts +++ b/src/controller/OverlayKind.ts @@ -8,7 +8,7 @@ * かといってコンストラクタを持たせると、状態型がプロバイダ固有になったときに破綻する。 * 種別という別の軸で持つ。 * - * **描画順(zIndex)とは別軸**であることに注意。現状 polygon(3) > groundImage(2) だが、 + * **描画順({@link OVERLAY_STACK_ORDER})とは別軸**であることに注意。重なりは groundImage が polygon の下だが、 * クリックの探索は groundImage が先。 * * android-sdk / ios-sdk にも同名・同じ並びで置く。 diff --git a/src/overlay/OverlayStacking.ts b/src/overlay/OverlayStacking.ts new file mode 100644 index 0000000..f939fce --- /dev/null +++ b/src/overlay/OverlayStacking.ts @@ -0,0 +1,113 @@ +import type { OverlayKind } from '../controller/OverlayKind'; + +/** + * オーバーレイの重なり順(下から上)。すべてのプロバイダ・3 プラットフォームで同じ。 + * + * 1. ラスターレイヤー(アプリが足すタイル) + * 2. GroundImage + * 3. ポリゴン(ネイティブのポリゴンと、SDK のポリゴンのタイルレイヤーは同じ帯) + * 4. サークル + * 5. ポリライン + * 6. マーカー(ネイティブのマーカーと、SDK のマーカーのタイルレイヤーは同じ帯) + * + * 追加した順・送り直した順(`clearOverlays()` の後など)・デザイン変更に左右されない。 + * 帯の中は zIndex、同じ zIndex なら渡された順(後のものが上)。 + * + * android-sdk / ios-sdk にも同じ並びで置く。クリックの探索順({@link CANONICAL_ORDER})とは別軸。 + * + * @internal ドライバー実装点。 + */ +export const OVERLAY_STACK_ORDER: readonly OverlayKind[] = [ + 'rasterLayer', + 'groundImage', + 'polygon', + 'circle', + 'polyline', + 'marker', +] as const; + +/** + * 帯の番号。0 が一番下(ラスター)、5 が一番上(マーカー)。 + * + * @internal ドライバー実装点。 + */ +export function overlayStackBand(kind: OverlayKind): number { + return OVERLAY_STACK_ORDER.indexOf(kind); +} + +/** + * 1 つの描画物(GL のレイヤー、ペインなど)の重なり順を決める鍵。 + * + * - `kind`: どの帯か。 + * - `zIndex`: 帯の中の順。 + * - `sequence`: 同じ zIndex の中の順。渡された順の番号(大きいほど上)。 + * 1 つの描画物が複数の SDK レイヤーを持つ(塗りと輪郭など)ときは `sub` で並べる。 + * + * @internal ドライバー実装点。 + */ +export interface OverlayStackKey { + readonly kind: OverlayKind; + readonly zIndex?: number; + readonly sequence?: number; + readonly sub?: number; +} + +/** + * 重なり順の比較。負なら `a` が下。 + * + * @internal ドライバー実装点。 + */ +export function compareOverlayStackKeys(a: OverlayStackKey, b: OverlayStackKey): number { + return ( + overlayStackBand(a.kind) - overlayStackBand(b.kind) || + (a.zIndex ?? 0) - (b.zIndex ?? 0) || + (a.sequence ?? 0) - (b.sequence ?? 0) || + (a.sub ?? 0) - (b.sub ?? 0) + ); +} + +/** + * レイヤーを順に積む SDK(MapLibre GL 系の `addLayer(layer, beforeId)` など)で、 + * 新しいレイヤーを `target` の位置に入れるための `beforeId`。 + * + * `order` は今の地図のレイヤー id(下から上)。`keyOf` は自分たちのレイヤーなら鍵を、 + * それ以外(ベースマップ、拡張が足したもの)なら null を返す。 + * 自分たちのレイヤーのうち `target` より上に来るべき最初のものの id を返す。無ければ + * undefined(一番上に積む)。 + * + * **追加した順に依存しない**: 毎回この関数で入れれば、どの順で足しても、スタイルの差し替え後に + * どの順で戻しても、同じ並びになる。 + * + * @internal ドライバー実装点。 + */ +export function overlayStackBeforeId( + order: readonly string[], + keyOf: (layerId: string) => OverlayStackKey | null | undefined, + target: OverlayStackKey, +): string | undefined { + for (const id of order) { + const key = keyOf(id); + if (key == null) continue; + if (compareOverlayStackKeys(key, target) > 0) return id; + } + return undefined; +} + +/** + * 数値で重なりを決める SDK(ペインの CSS z-index、deck.gl のレイヤー順、Cesium の地表 zIndex、 + * OpenLayers のレイヤー zIndex など)向けの値。 + * + * `base + 帯 * bandSize + zIndex` を返す。zIndex は帯からはみ出さないよう + * `±(bandSize / 2 - 1)` に丸める(帯をまたいで逆転させないため)。 + * + * @internal ドライバー実装点。 + */ +export function overlayStackZ( + kind: OverlayKind, + zIndex: number | null | undefined = 0, + { base = 0, bandSize = 1000 }: { base?: number; bandSize?: number } = {}, +): number { + const limit = Math.floor(bandSize / 2) - 1; + const z = Math.max(-limit, Math.min(limit, Math.round(zIndex ?? 0))); + return base + overlayStackBand(kind) * bandSize + z; +} diff --git a/src/overlay/index.ts b/src/overlay/index.ts index 4d65c56..30cc40d 100644 --- a/src/overlay/index.ts +++ b/src/overlay/index.ts @@ -1,3 +1,4 @@ export * from './OverlayStates'; export * from './OverlayCollector'; export * from '../controller/OverlayRendererInterface'; +export * from './OverlayStacking'; diff --git a/test/OverlayStacking.test.mjs b/test/OverlayStacking.test.mjs new file mode 100644 index 0000000..323cce4 --- /dev/null +++ b/test/OverlayStacking.test.mjs @@ -0,0 +1,104 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import { + OVERLAY_STACK_ORDER, + compareOverlayStackKeys, + overlayStackBand, + overlayStackBeforeId, + overlayStackZ, +} from '../dist/index.mjs'; + +/** + * The overlay stack, bottom to top, on every provider (user decision, 2026-11-07): + * raster < groundImage < polygon < circle < polyline < marker. zIndex orders within a band; + * equal zIndexes keep the order they were handed over in. Same order on android-sdk-core and + * ios-sdk-core. + */ + +test('the band order', () => { + assert.deepEqual([...OVERLAY_STACK_ORDER], ['rasterLayer', 'groundImage', 'polygon', 'circle', 'polyline', 'marker']); + assert.ok(overlayStackBand('rasterLayer') < overlayStackBand('groundImage')); + assert.ok(overlayStackBand('polygon') < overlayStackBand('circle')); + assert.ok(overlayStackBand('circle') < overlayStackBand('polyline')); +}); + +test('band beats zIndex; zIndex beats the order handed over; then sub', () => { + assert.ok(compareOverlayStackKeys({ kind: 'polygon', zIndex: 1000 }, { kind: 'circle', zIndex: -1000 }) < 0); + assert.ok(compareOverlayStackKeys({ kind: 'polygon', zIndex: 2, sequence: 1 }, { kind: 'polygon', zIndex: 1, sequence: 9 }) > 0); + assert.ok(compareOverlayStackKeys({ kind: 'polygon', sequence: 1, sub: 1 }, { kind: 'polygon', sequence: 2, sub: 0 }) < 0); + assert.equal(compareOverlayStackKeys({ kind: 'marker' }, { kind: 'marker', zIndex: 0, sequence: 0, sub: 0 }), 0); +}); + +/** A layer stack like MapLibre GL's: addLayer(layer, beforeId). */ +function glMap(basemap) { + const order = [...basemap]; + return { + order, + add(id, beforeId) { + const at = beforeId ? order.indexOf(beforeId) : -1; + if (at >= 0) order.splice(at, 0, id); else order.push(id); + }, + }; +} + +function* permutations(items) { + if (items.length <= 1) { yield items; return; } + for (let i = 0; i < items.length; i++) { + for (const rest of permutations([...items.slice(0, i), ...items.slice(i + 1)])) yield [items[i], ...rest]; + } +} + +const LAYERS = { + raster: { kind: 'rasterLayer' }, + gimg: { kind: 'groundImage' }, + 'polygon-fill': { kind: 'polygon', sub: 0 }, + 'polygon-outline': { kind: 'polygon', sub: 1 }, + circle: { kind: 'circle' }, + polyline: { kind: 'polyline' }, + marker: { kind: 'marker' }, +}; +const BASEMAP = ['background', 'water', 'road', 'place-label']; + +test('inserting with overlayStackBeforeId gives the same stack for every insertion order', () => { + const want = [...BASEMAP, 'raster', 'gimg', 'polygon-fill', 'polygon-outline', 'circle', 'polyline', 'marker']; + let n = 0; + for (const perm of permutations(Object.keys(LAYERS))) { + const map = glMap(BASEMAP); + for (const id of perm) map.add(id, overlayStackBeforeId(map.order, (x) => LAYERS[x] ?? null, LAYERS[id])); + assert.deepEqual(map.order, want, perm.join(',')); + n++; + } + assert.equal(n, 5040); +}); + +test('within a band: zIndex first, then the order handed over', () => { + const map = glMap(BASEMAP); + const keys = { + a: { kind: 'groundImage', zIndex: 0, sequence: 1 }, + b: { kind: 'groundImage', zIndex: 0, sequence: 2 }, + c: { kind: 'groundImage', zIndex: -1, sequence: 3 }, + p: { kind: 'polygon' }, + }; + for (const id of ['p', 'b', 'c', 'a']) map.add(id, overlayStackBeforeId(map.order, (x) => keys[x] ?? null, keys[id])); + assert.deepEqual(map.order.slice(BASEMAP.length), ['c', 'a', 'b', 'p']); +}); + +test('layers that are not ours are skipped', () => { + // Something an extension appended on top must not stop a polygon from going under our circle. + const order = [...BASEMAP, 'circle', 'geojson-ext']; + assert.equal(overlayStackBeforeId(order, (x) => LAYERS[x] ?? null, LAYERS['polygon-fill']), 'circle'); + assert.equal(overlayStackBeforeId(order, (x) => LAYERS[x] ?? null, LAYERS.marker), undefined); +}); + +test('overlayStackZ keeps the bands apart whatever the zIndex', () => { + for (let i = 0; i + 1 < OVERLAY_STACK_ORDER.length; i++) { + const lowTop = overlayStackZ(OVERLAY_STACK_ORDER[i], 1e9); + const highBottom = overlayStackZ(OVERLAY_STACK_ORDER[i + 1], -1e9); + assert.ok(lowTop < highBottom, `${OVERLAY_STACK_ORDER[i]} ${lowTop} vs ${OVERLAY_STACK_ORDER[i + 1]} ${highBottom}`); + } + assert.ok(overlayStackZ('polygon', 1) > overlayStackZ('polygon', 0)); + assert.equal(overlayStackZ('rasterLayer', 0, { base: 100, bandSize: 20 }), 100); + assert.equal(overlayStackZ('marker', 0, { base: 100, bandSize: 20 }), 200); + assert.equal(overlayStackZ('marker', 50, { base: 100, bandSize: 20 }), 209); +});