@@ -74,6 +74,7 @@ import type { GaussianSplatControl, GaussianSplatLayerAdapter } from "maplibre-g
7474import type { LidarControlEventHandler , PointCloudInfo } from "maplibre-gl-lidar" ;
7575import type { GeoLibreAppAPI , GeoLibreMapControlPosition , GeoLibrePlugin } from "../types" ;
7676import { ensureMercatorProjection } from "./map-projection-utils" ;
77+ import { ensureSharedDeckOverlay , setSharedDeckLayers } from "./shared-deck-overlay" ;
7778import { attachTerrainMeasure , measurePanelElement , type TerrainMapLike } from "./terrain-measure" ;
7879import { INTERNAL_HELPER_LAYER_PATTERNS } from "./internal-layers" ;
7980import {
@@ -732,6 +733,12 @@ let bookmarkControl: BookmarkControl | null = null;
732733let minimapControl : MinimapControl | null = null ;
733734let viewStateControl : ViewStateControl | null = null ;
734735let stacSearchControl : StacSearchControl | null = null ;
736+ // The host API the STAC Search control was opened with, kept so its deck.gl COG
737+ // layers can reach the shared interleaved overlay from the patched hooks below.
738+ let stacSearchApp : GeoLibreAppAPI | null = null ;
739+ // Store-derived `beforeId` per STAC Search deck layer id, pushed in by
740+ // `applyStacSearchLayerOrder`. See `renderStacSearchDeckLayers`.
741+ const stacSearchBeforeIds = new Map < string , string | undefined > ( ) ;
735742let zarrControl : ZarrLayerControl | null = null ;
736743let colorbarControl : ColorbarGuiControl | null = null ;
737744let legendControl : LegendGuiControl | null = null ;
@@ -3124,6 +3131,7 @@ async function openStandaloneViewStateControl(app: GeoLibreAppAPI): Promise<bool
31243131async function openStandaloneStacSearchControl ( app : GeoLibreAppAPI ) : Promise < boolean > {
31253132 const { StacSearchControl : StacSearchControlClass } = await getComponentsConstructors ( ) ;
31263133
3134+ stacSearchApp = app ;
31273135 stacSearchControl ??= createStacSearchControl ( StacSearchControlClass ) ;
31283136
31293137 if ( ! stacSearchControlMounted ) {
@@ -4320,6 +4328,10 @@ function teardownStacSearchControl(app: GeoLibreAppAPI): void {
43204328 }
43214329 stacSearchControl = null ;
43224330 stacSearchControlMounted = false ;
4331+ stacSearchApp = null ;
4332+ // Its deck layers live in the shared overlay, which outlives this control.
4333+ stacSearchBeforeIds . clear ( ) ;
4334+ setSharedDeckLayers ( "stac-search" , [ ] ) ;
43234335}
43244336
43254337function hideSearchControl ( ) : void {
@@ -5432,6 +5444,11 @@ function createStacSearchStoreLayer(
54325444 metadata : {
54335445 collectionId,
54345446 customLayerType : "raster" ,
5447+ // The COG variant renders as a deck.gl layer with no MapLibre style layer
5448+ // to move, so layer-sync must hand its computed `beforeId` to the control
5449+ // instead of calling `moveLayer` (#1718). The raster-tile variant is a
5450+ // real style layer and reorders normally.
5451+ ...( rasterLayerInfo ? { } : { externalDeckLayer : true } ) ,
54355452 externalNativeLayer : true ,
54365453 identifiable : false ,
54375454 nativeLayerIds,
@@ -5597,6 +5614,10 @@ function patchStacSearchRemoveLayer(control: StacSearchControl): void {
55975614 mutableControl . _removeLayer = ( id ?: string ) => {
55985615 const layerIds = id ? [ id ] : Array . from ( mutableControl . _cogLayers ?. keys ( ) ?? [ ] ) ;
55995616 removeLayer ( id ) ;
5617+ // Upstream repaints its own overlay, which GeoLibre bypasses, so drop the
5618+ // removed layers from the shared interleaved overlay here (#1718).
5619+ for ( const layerId of layerIds ) stacSearchBeforeIds . delete ( layerId ) ;
5620+ renderStacSearchDeckLayers ( ) ;
56005621 const store = useAppStore . getState ( ) ;
56015622 for ( const layerId of layerIds ) {
56025623 const layer = store . layers . find ( ( item ) => item . id === layerId ) ;
@@ -5628,7 +5649,10 @@ function patchStacSearchCogLayer(control: StacSearchControl): void {
56285649
56295650 mutableControl . _addCogLayer = async ( url : string , item : StacSearchItem , assetKey : string ) => {
56305651 ensureMercatorProjection ( mutableControl . _map ) ;
5631- await mutableControl . _ensureOverlay ?.( ) ;
5652+ // Deliberately NOT `_ensureOverlay()`: that builds the control's own
5653+ // non-interleaved overlay, which can never be ordered against the style.
5654+ // The shared interleaved overlay renders these layers instead (#1718).
5655+ if ( stacSearchApp ) await ensureSharedDeckOverlay ( stacSearchApp ) ;
56325656 const selectedAsset = getStacSearchSelectedAsset ( mutableControl , item , {
56335657 key : assetKey ,
56345658 url,
@@ -5655,9 +5679,7 @@ function patchStacSearchCogLayer(control: StacSearchControl): void {
56555679 ...renderProps ,
56565680 } ) ;
56575681 mutableControl . _cogLayers ?. set ( id , layer as unknown as Layer ) ;
5658- mutableControl . _deckOverlay ?. setProps ( {
5659- layers : Array . from ( mutableControl . _cogLayers ?. values ( ) ?? [ ] ) as Layer [ ] ,
5660- } ) ;
5682+ renderStacSearchDeckLayers ( ) ;
56615683 if ( mutableControl . _state ) {
56625684 mutableControl . _state . hasLayer = true ;
56635685 mutableControl . _state . layerCount = mutableControl . _cogLayers ?. size ?? 0 ;
@@ -6008,9 +6030,7 @@ function setStacSearchControlLayerState(id: string, visible: boolean, opacity: n
60086030 id ,
60096031 layer . clone ( { opacity : appliedOpacity } ) as StacSearchRenderableLayer ,
60106032 ) ;
6011- mutableControl ?. _deckOverlay ?. setProps ( {
6012- layers : getStacSearchDeckLayers ( mutableControl ) ,
6013- } ) ;
6033+ renderStacSearchDeckLayers ( ) ;
60146034}
60156035
60166036function getStacSearchDeckLayers ( control : MutableStacSearchControl ) : Layer [ ] {
@@ -6019,6 +6039,50 @@ function getStacSearchDeckLayers(control: MutableStacSearchControl): Layer[] {
60196039 ) ;
60206040}
60216041
6042+ /**
6043+ * Pushes the STAC Search control's deck.gl COG layers into GeoLibre's shared
6044+ * interleaved overlay, each carrying the `beforeId` derived from the store's
6045+ * layer order.
6046+ *
6047+ * Upstream renders them through the control's own non-interleaved
6048+ * `MapboxOverlay`, which owns a separate canvas stacked above the entire
6049+ * MapLibre style — so STAC imagery covered every vector layer no matter where
6050+ * the user placed it in the Layers panel (opengeos/GeoLibre#1718). Interleaved
6051+ * layers are drawn inside the style instead, at the depth their `beforeId`
6052+ * selects, which is what makes panel order mean anything for them.
6053+ */
6054+ function renderStacSearchDeckLayers ( ) : void {
6055+ const control = stacSearchControl as unknown as MutableStacSearchControl | null ;
6056+ if ( ! control ) return ;
6057+ const layers = getStacSearchDeckLayers ( control ) . map ( ( layer ) => {
6058+ const beforeId = stacSearchBeforeIds . get ( layer . id ) ;
6059+ if ( ( layer . props as { beforeId ?: string } ) . beforeId === beforeId ) return layer ;
6060+ return layer . clone ( { beforeId } as unknown as Partial < Layer [ "props" ] > ) ;
6061+ } ) ;
6062+ setSharedDeckLayers ( "stac-search" , layers ) ;
6063+ }
6064+
6065+ /**
6066+ * Applies a store-derived draw order to a STAC Search deck.gl COG layer.
6067+ *
6068+ * Registered by the app shell as part of the external deck-layer order handler:
6069+ * such a layer is not a real MapLibre style layer, so `moveLayer` cannot reorder
6070+ * it and layer-sync forwards the computed `beforeId` here instead.
6071+ *
6072+ * @param layerId - The store layer id, which doubles as the deck layer id.
6073+ * @param beforeId - The style layer to draw beneath, or undefined for the top.
6074+ * @returns True when the id belongs to the STAC Search control.
6075+ */
6076+ export function applyStacSearchLayerOrder ( layerId : string , beforeId : string | undefined ) : boolean {
6077+ const control = stacSearchControl as unknown as MutableStacSearchControl | null ;
6078+ const layer = control ?. _cogLayers ?. get ( layerId ) ;
6079+ if ( ! layer || getStacSearchRasterLayerInfo ( layer ) ) return false ;
6080+ if ( stacSearchBeforeIds . get ( layerId ) === beforeId ) return true ;
6081+ stacSearchBeforeIds . set ( layerId , beforeId ) ;
6082+ renderStacSearchDeckLayers ( ) ;
6083+ return true ;
6084+ }
6085+
60226086function getStacSearchRasterLayerInfo (
60236087 layer : StacSearchRenderableLayer ,
60246088) : { layerId : string ; sourceId : string ; tileUrl ?: string } | null {
0 commit comments