From 2481945af0671aaa5035191e2516231d46e737f1 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Sat, 7 Nov 2026 23:55:27 -0800 Subject: [PATCH] Stack overlays by band inside the Longdo renderer Every layer was appended with addLayer() in the order it arrived, so the raster (added last in a re-send) covered everything and ground images and polygons followed add order. LongdoOverlayStacking generates the JS that records each layer's band and zIndex on map.Renderer and inserts it before the lowest SDK layer with a higher key; rasters also stay below the basemap's first label. Stacking order (all providers, all platforms), bottom to top: raster layers < ground images < polygons < circles < polylines < markers. Needs android-sdk-core with OverlayStacking (fix/overlay-stacking-order). --- api/android-for-longdo.api | 6 ++++ .../LongdoMapViewControllerMarkerTiles.kt | 6 ++-- .../longdo/LongdoMapViewControllerRaster.kt | 31 +++++++++++++--- .../longdo/LongdoOverlayStacking.kt | 35 +++++++++++++++++++ .../circle/LongdoCircleOverlayRenderer.kt | 13 +++++-- .../LongdoGroundImageOverlayRenderer.kt | 6 +++- .../polygon/LongdoPolygonOverlayRenderer.kt | 14 ++++++-- .../polyline/LongdoPolylineOverlayRenderer.kt | 10 +++++- 8 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/mapconductor/longdo/LongdoOverlayStacking.kt diff --git a/api/android-for-longdo.api b/api/android-for-longdo.api index b8b24f5..1678d7a 100644 --- a/api/android-for-longdo.api +++ b/api/android-for-longdo.api @@ -151,6 +151,7 @@ public final class com.mapconductor.longdo.LongdoMapViewControllerRasterKt { public static final java.lang.String rasterSourceSpec(com.mapconductor.longdo.LongdoMapViewController, com.mapconductor.core.raster.RasterLayerSource); public static final java.lang.String rasterLayerSpec(com.mapconductor.longdo.LongdoMapViewController, java.lang.String, java.lang.String, float, boolean); public static final java.lang.String addRasterJs(com.mapconductor.longdo.LongdoMapViewController, java.lang.String, java.lang.String, java.lang.String, java.lang.String); + public static final java.lang.String addRasterJs(com.mapconductor.longdo.LongdoMapViewController, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String); public static final java.lang.String readdRastersAfterStyleSwapJs(com.mapconductor.longdo.LongdoMapViewController, java.util.List); public static final java.lang.String removeRasterJs(com.mapconductor.longdo.LongdoMapViewController, java.lang.String); public static final java.lang.String setRasterOpacityJs(com.mapconductor.longdo.LongdoMapViewController, java.lang.String, float); @@ -190,6 +191,11 @@ final class com.mapconductor.longdo.LongdoMapViewSaver$Companion { public final class com.mapconductor.longdo.LongdoMapViewScope extends com.mapconductor.compose.MapViewScope { public com.mapconductor.longdo.LongdoMapViewScope(); } +public final class com.mapconductor.longdo.LongdoOverlayStacking { + public static final com.mapconductor.longdo.LongdoOverlayStacking INSTANCE; + public final java.lang.String addLayerJs(java.lang.String, com.mapconductor.core.controller.OverlayKind, int); + public final java.lang.String addMarkerTileLayerJs(java.lang.String); +} public final class com.mapconductor.longdo.LongdoOverlayState { public com.mapconductor.longdo.LongdoOverlayState(); public final androidx.compose.runtime.snapshots.SnapshotStateMap getRenderPx(); diff --git a/src/main/java/com/mapconductor/longdo/LongdoMapViewControllerMarkerTiles.kt b/src/main/java/com/mapconductor/longdo/LongdoMapViewControllerMarkerTiles.kt index 05e9187..b3855b8 100644 --- a/src/main/java/com/mapconductor/longdo/LongdoMapViewControllerMarkerTiles.kt +++ b/src/main/java/com/mapconductor/longdo/LongdoMapViewControllerMarkerTiles.kt @@ -71,10 +71,12 @@ internal fun LongdoMapViewController.applyMarkerTileRaster(state: RasterLayerSta val prev = markerTileState val layerSpec = rasterLayerSpec(layerId, srcId, state.opacity, state.visible) when { - prev == null -> runRasterJs(addRasterJs(srcId, layerId, spec, layerSpec)) + // マーカーのタイルはマーカーの帯(ポリライン等より上)。 + prev == null -> + runRasterJs(addRasterJs(srcId, layerId, spec, layerSpec, LongdoOverlayStacking.addMarkerTileLayerJs(layerSpec))) prev.source != state.source -> { runRasterJs(removeRasterJs(state.id)) - runRasterJs(addRasterJs(srcId, layerId, spec, layerSpec)) + runRasterJs(addRasterJs(srcId, layerId, spec, layerSpec, LongdoOverlayStacking.addMarkerTileLayerJs(layerSpec))) } } markerTileState = state diff --git a/src/main/java/com/mapconductor/longdo/LongdoMapViewControllerRaster.kt b/src/main/java/com/mapconductor/longdo/LongdoMapViewControllerRaster.kt index 5b1cdaa..5940967 100644 --- a/src/main/java/com/mapconductor/longdo/LongdoMapViewControllerRaster.kt +++ b/src/main/java/com/mapconductor/longdo/LongdoMapViewControllerRaster.kt @@ -1,5 +1,6 @@ package com.mapconductor.longdo +import com.mapconductor.core.controller.OverlayKind import com.mapconductor.core.raster.RasterHeaderRuleSet import com.mapconductor.core.raster.RasterLayerSource import com.mapconductor.core.raster.RasterLayerState @@ -23,10 +24,10 @@ internal fun LongdoMapViewController.applyRaster(state: RasterLayerState) { val layerSpec = rasterLayerSpec(layerId, srcId, state.opacity, state.visible) when { prev == null -> - runRasterJs(addRasterJs(srcId, layerId, spec, layerSpec)) - prev.source != state.source -> { + runRasterJs(addRasterJs(srcId, layerId, spec, layerSpec, rasterAddLayerJs(layerSpec, state))) + prev.source != state.source || prev.zIndex != state.zIndex -> { runRasterJs(removeRasterJs(state.id)) - runRasterJs(addRasterJs(srcId, layerId, spec, layerSpec)) + runRasterJs(addRasterJs(srcId, layerId, spec, layerSpec, rasterAddLayerJs(layerSpec, state))) } else -> { if (prev.opacity != state.opacity) runRasterJs(setRasterOpacityJs(layerId, state.opacity)) @@ -36,6 +37,11 @@ internal fun LongdoMapViewController.applyRaster(state: RasterLayerState) { appliedRasters[state.id] = state } +private fun rasterAddLayerJs( + layerSpec: String, + state: RasterLayerState, +): String = LongdoOverlayStacking.addLayerJs(layerSpec, OverlayKind.RasterLayer, state.zIndex) + internal fun LongdoMapViewController.runRasterJs(js: String) { runCatching { longdoMap.run(js) {} } } @@ -79,6 +85,15 @@ internal fun LongdoMapViewController.addRasterJs( layerId: String, sourceSpec: String, layerSpec: String, +): String = addRasterJs(sourceId, layerId, sourceSpec, layerSpec, LongdoOverlayStacking.addLayerJs(layerSpec, OverlayKind.RasterLayer)) + +/** [addLayer] は帯の順で層を足す JS([LongdoOverlayStacking.addLayerJs])。 */ +internal fun LongdoMapViewController.addRasterJs( + sourceId: String, + layerId: String, + sourceSpec: String, + layerSpec: String, + addLayer: String, ): String = """ (function(){var m=map.Renderer; if(!m)return; @@ -86,7 +101,7 @@ internal fun LongdoMapViewController.addRasterJs( function apply(){ try{ if(!m.getSource('$sourceId'))m.addSource('$sourceId',$sourceSpec); - if(!m.getLayer('$layerId'))m.addLayer($layerSpec); + if(!m.getLayer('$layerId'))$addLayer }catch(e){ if(tries++<30)setTimeout(apply,150); } } apply(); @@ -107,8 +122,14 @@ internal fun LongdoMapViewController.readdRastersAfterStyleSwapJs(rasters: List< val srcId = rasterSourceId(state.id) val layerId = rasterLayerId(state.id) val layerSpec = rasterLayerSpec(layerId, srcId, state.opacity, state.visible) + val addLayer = + if (state.id == markerTileState?.id) { + LongdoOverlayStacking.addMarkerTileLayerJs(layerSpec) + } else { + LongdoOverlayStacking.addLayerJs(layerSpec, OverlayKind.RasterLayer, state.zIndex) + } "try{if(!m.getSource('$srcId'))m.addSource('$srcId',$spec);" + - "if(!m.getLayer('$layerId'))m.addLayer($layerSpec);}catch(e){}" + "if(!m.getLayer('$layerId'))$addLayer}catch(e){}" }.joinToString("") return "(function(){var m=map.Renderer;if(!m)return;function re(){$adds}" + "m.once('style.load',function(){setTimeout(re,0);});setTimeout(re,1500);})()" diff --git a/src/main/java/com/mapconductor/longdo/LongdoOverlayStacking.kt b/src/main/java/com/mapconductor/longdo/LongdoOverlayStacking.kt new file mode 100644 index 0000000..20d3f90 --- /dev/null +++ b/src/main/java/com/mapconductor/longdo/LongdoOverlayStacking.kt @@ -0,0 +1,35 @@ +package com.mapconductor.longdo + +import com.mapconductor.core.controller.OverlayKind +import com.mapconductor.core.controller.OverlayStacking + +/** + * 重なり順([OverlayStacking]: ラスター < GroundImage < ポリゴン < サークル < ポリライン < マーカー)。 + * + * Longdo の地図は WebView の中の MapLibre GL JS(`map.Renderer`)で、レイヤーは + * `addLayer` した順に上へ積まれていた(追加・送り直し・スタイル差し替えの順に左右される)。 + * ここで作る JS は、足す層の帯と zIndex を `m.__mcStack` に記録し、今のスタイルの並びから + * 「鍵が自分より大きい一番下の SDK の層」を beforeId に選んで `addLayer` する。 + * ラスターだけはベースマップのラベル(SDK の層でない最初の symbol 層)の下に入れる。 + */ +internal object LongdoOverlayStacking { + private const val FN = + "function(m,s,b,z){var K=m.__mcStack||(m.__mcStack={});K[s.id]=[b,z];" + + "if(m.getLayer(s.id))return;var L=m.getStyle().layers,i,k,bi=-1;" + + "for(i=0;ib||(k[0]==b&&k[1]>z))){bi=i;break;}}" + + "if(b==0){for(i=0;i() + private fun addCircle(state: CircleState): LongdoCircleHandle? { val feature = buildGeoJson(state) ?: return null + zIndexByLayer["circle-fill-${state.id}"] = state.zIndex ?: 0 val fillArgb = state.fillColor.toArgb() val strokeArgb = state.strokeColor.toArgb() val handle = @@ -137,6 +143,9 @@ class LongdoCircleOverlayRenderer( .put("line-opacity", handle.strokeOpacity) .put("line-width", handle.strokeWidth), ).toString() + // サークルの帯。輪郭は塗りの直後。 + val fillAdd = LongdoOverlayStacking.addLayerJs(fillSpec, OverlayKind.Circle, zIndexByLayer[handle.fillLayerId] ?: 0) + val lineAdd = LongdoOverlayStacking.addLayerJs(lineSpec, OverlayKind.Circle, zIndexByLayer[handle.fillLayerId] ?: 0) return """ (function(){var m=map.Renderer; if(!m)return; var tries=0; @@ -144,8 +153,8 @@ class LongdoCircleOverlayRenderer( try{ if(!m.getSource('${handle.sourceId}')) m.addSource('${handle.sourceId}',{type:'geojson',data:${handle.featureJson}}); - if(!m.getLayer('${handle.fillLayerId}'))m.addLayer($fillSpec); - if(!m.getLayer('${handle.lineLayerId}'))m.addLayer($lineSpec); + if(!m.getLayer('${handle.fillLayerId}'))$fillAdd + if(!m.getLayer('${handle.lineLayerId}'))$lineAdd }catch(e){ if(tries++<30)setTimeout(apply,150); } } apply(); diff --git a/src/main/java/com/mapconductor/longdo/groundimage/LongdoGroundImageOverlayRenderer.kt b/src/main/java/com/mapconductor/longdo/groundimage/LongdoGroundImageOverlayRenderer.kt index 0157ef3..f1fc141 100644 --- a/src/main/java/com/mapconductor/longdo/groundimage/LongdoGroundImageOverlayRenderer.kt +++ b/src/main/java/com/mapconductor/longdo/groundimage/LongdoGroundImageOverlayRenderer.kt @@ -1,10 +1,12 @@ package com.mapconductor.longdo.groundimage import com.longdo.sdk3.LongdoMap +import com.mapconductor.core.controller.OverlayKind import com.mapconductor.core.features.GeoRectBounds import com.mapconductor.core.groundimage.GroundImageEntityInterface import com.mapconductor.core.groundimage.GroundImageOverlayRendererInterface import com.mapconductor.core.groundimage.GroundImageState +import com.mapconductor.longdo.LongdoOverlayStacking import org.json.JSONObject import java.io.ByteArrayOutputStream import android.graphics.Bitmap @@ -146,6 +148,8 @@ class LongdoGroundImageOverlayRenderer( .put("paint", JSONObject().put("raster-opacity", handle.opacity)) .put("layout", JSONObject().put("visibility", "visible")) .toString() + // GroundImage の帯(ラスターの上、ポリゴンの下)。 + val layerAdd = LongdoOverlayStacking.addLayerJs(layerSpec, OverlayKind.GroundImage) return """ (function(){var m=map.Renderer; if(!m)return; var tries=0; @@ -154,7 +158,7 @@ class LongdoGroundImageOverlayRenderer( if(!m.getSource('${handle.sourceId}')) m.addSource('${handle.sourceId}', {type:'image',url:'${handle.imageUrl}',coordinates:${handle.coordinatesJson}}); - if(!m.getLayer('${handle.layerId}'))m.addLayer($layerSpec); + if(!m.getLayer('${handle.layerId}'))$layerAdd }catch(e){ if(tries++<30)setTimeout(apply,150); } } apply(); diff --git a/src/main/java/com/mapconductor/longdo/polygon/LongdoPolygonOverlayRenderer.kt b/src/main/java/com/mapconductor/longdo/polygon/LongdoPolygonOverlayRenderer.kt index 7bf277f..1f7b81d 100644 --- a/src/main/java/com/mapconductor/longdo/polygon/LongdoPolygonOverlayRenderer.kt +++ b/src/main/java/com/mapconductor/longdo/polygon/LongdoPolygonOverlayRenderer.kt @@ -2,12 +2,14 @@ package com.mapconductor.longdo.polygon import androidx.compose.ui.graphics.toArgb import com.longdo.sdk3.LongdoMap +import com.mapconductor.core.controller.OverlayKind import com.mapconductor.core.geometry.OverlayGeoJson import com.mapconductor.core.geometry.buildUnwrappedPolygonRings import com.mapconductor.core.polygon.PolygonEntityInterface import com.mapconductor.core.polygon.PolygonOverlayRendererInterface import com.mapconductor.core.polygon.PolygonState import com.mapconductor.core.polygon.unionHoles +import com.mapconductor.longdo.LongdoOverlayStacking import org.json.JSONObject /** @@ -56,8 +58,12 @@ class LongdoPolygonOverlayRenderer( handles.forEach { runJs(addPolygonJs(it)) } } + /** 層の id → zIndex(帯の中の順)。handle は公開型なので持たせずここで覚える。 */ + private val zIndexByLayer = java.util.concurrent.ConcurrentHashMap() + private fun addPolygon(state: PolygonState): LongdoPolygonHandle? { val feature = buildGeoJson(state) ?: return null + zIndexByLayer["polygon-fill-${state.id}"] = state.zIndex val fillArgb = state.fillColor.toArgb() val strokeArgb = state.strokeColor.toArgb() val handle = @@ -140,6 +146,10 @@ class LongdoPolygonOverlayRenderer( .put("line-opacity", handle.strokeOpacity) .put("line-width", handle.strokeWidth), ).toString() + // ポリゴンの帯。帯の中は zIndex の順、輪郭は塗りの直後。 + val z = zIndexByLayer[handle.fillLayerId] ?: 0 + val fillAdd = LongdoOverlayStacking.addLayerJs(fillSpec, OverlayKind.Polygon, z) + val lineAdd = LongdoOverlayStacking.addLayerJs(lineSpec, OverlayKind.Polygon, z) return """ (function(){var m=map.Renderer; if(!m)return; var tries=0; @@ -147,8 +157,8 @@ class LongdoPolygonOverlayRenderer( try{ if(!m.getSource('${handle.sourceId}')) m.addSource('${handle.sourceId}',{type:'geojson',data:${handle.featureJson}}); - if(!m.getLayer('${handle.fillLayerId}'))m.addLayer($fillSpec); - if(!m.getLayer('${handle.lineLayerId}'))m.addLayer($lineSpec); + if(!m.getLayer('${handle.fillLayerId}'))$fillAdd + if(!m.getLayer('${handle.lineLayerId}'))$lineAdd }catch(e){ if(tries++<30)setTimeout(apply,150); } } apply(); diff --git a/src/main/java/com/mapconductor/longdo/polyline/LongdoPolylineOverlayRenderer.kt b/src/main/java/com/mapconductor/longdo/polyline/LongdoPolylineOverlayRenderer.kt index b4e08ea..bce5e88 100644 --- a/src/main/java/com/mapconductor/longdo/polyline/LongdoPolylineOverlayRenderer.kt +++ b/src/main/java/com/mapconductor/longdo/polyline/LongdoPolylineOverlayRenderer.kt @@ -2,11 +2,13 @@ package com.mapconductor.longdo.polyline import androidx.compose.ui.graphics.toArgb import com.longdo.sdk3.LongdoMap +import com.mapconductor.core.controller.OverlayKind import com.mapconductor.core.geometry.OverlayGeoJson import com.mapconductor.core.geometry.buildUnwrappedPolylinePath import com.mapconductor.core.polyline.PolylineEntityInterface import com.mapconductor.core.polyline.PolylineOverlayRendererInterface import com.mapconductor.core.polyline.PolylineState +import com.mapconductor.longdo.LongdoOverlayStacking import org.json.JSONObject /** @@ -52,8 +54,12 @@ class LongdoPolylineOverlayRenderer( handles.forEach { runJs(addLineJs(it)) } } + /** 層の id → zIndex(帯の中の順)。 */ + private val zIndexByLayer = java.util.concurrent.ConcurrentHashMap() + private fun addLine(state: PolylineState): LongdoPolylineHandle? { val feature = buildGeoJson(state) ?: return null + zIndexByLayer["polyline-layer-${state.id}"] = state.zIndex val argb = state.strokeColor.toArgb() val handle = LongdoPolylineHandle( @@ -122,6 +128,8 @@ class LongdoPolylineOverlayRenderer( .put("line-opacity", handle.opacity) .put("line-width", handle.width), ).toString() + // ポリラインの帯。 + val layerAdd = LongdoOverlayStacking.addLayerJs(layerSpec, OverlayKind.Polyline, zIndexByLayer[handle.layerId] ?: 0) return """ (function(){var m=map.Renderer; if(!m)return; var tries=0; @@ -129,7 +137,7 @@ class LongdoPolylineOverlayRenderer( try{ if(!m.getSource('${handle.sourceId}')) m.addSource('${handle.sourceId}',{type:'geojson',data:${handle.featureJson}}); - if(!m.getLayer('${handle.layerId}'))m.addLayer($layerSpec); + if(!m.getLayer('${handle.layerId}'))$layerAdd }catch(e){ if(tries++<30)setTimeout(apply,150); } } apply();