diff --git a/api/android-for-mappls.api b/api/android-for-mappls.api index 8fe3338..c052daa 100644 --- a/api/android-for-mappls.api +++ b/api/android-for-mappls.api @@ -108,6 +108,15 @@ public final class com.mapconductor.mappls.MapplsMapViewSaver extends com.mapcon public final class com.mapconductor.mappls.MapplsMapViewScope extends com.mapconductor.compose.MapViewScope { public com.mapconductor.mappls.MapplsMapViewScope(); } +public final class com.mapconductor.mappls.MapplsOverlayStacking { + public static final com.mapconductor.mappls.MapplsOverlayStacking INSTANCE; + public final com.mapconductor.core.controller.OverlayLayerStack stackOf(com.mappls.sdk.maps.MapplsMap); + public final com.mapconductor.core.controller.OverlayStacking$Key key(com.mapconductor.core.controller.OverlayKind, int); + public final com.mapconductor.core.controller.OverlayStacking$Key getMARKER_TILE(); + public final com.mapconductor.core.controller.OverlayStacking$Key getMARKER_DRAG(); + public final void add(com.mappls.sdk.maps.MapplsMap, com.mappls.sdk.maps.Style, com.mappls.sdk.maps.style.layers.Layer, com.mapconductor.core.controller.OverlayStacking$Key); + public final void remove(com.mappls.sdk.maps.MapplsMap, com.mappls.sdk.maps.Style, java.lang.String); +} public final class com.mapconductor.mappls.MapplsPolyUtilsKt { public static final java.util.List createMapplsLines-rXlOB7A(java.lang.String, java.util.List, boolean, long, float, int); public static final java.lang.String toMapplsColorString-8_81llA(long); diff --git a/src/main/java/com/mapconductor/mappls/MapplsOverlayStacking.kt b/src/main/java/com/mapconductor/mappls/MapplsOverlayStacking.kt new file mode 100644 index 0000000..047d039 --- /dev/null +++ b/src/main/java/com/mapconductor/mappls/MapplsOverlayStacking.kt @@ -0,0 +1,86 @@ +package com.mapconductor.mappls + +import android.util.Log +import com.mapconductor.core.controller.OverlayKind +import com.mapconductor.core.controller.OverlayLayerStack +import com.mapconductor.core.controller.OverlayStacking +import com.mappls.sdk.maps.MapplsMap +import com.mappls.sdk.maps.Style +import com.mappls.sdk.maps.style.layers.Layer +import com.mappls.sdk.maps.style.layers.SymbolLayer +import java.util.WeakHashMap + +/** + * オーバーレイの層をスタイルのどこへ入れるか([OverlayStacking] の帯の順)。 + * + * 下から: ラスター < GroundImage < ポリゴン < サークル < ポリライン < マーカー + * (マーカーのタイルはマーカーの帯の一番下、ドラッグ中の層は一番上)。 + * + * 位置は毎回スタイルの今の並びから決めるので、追加した順・送り直し・ + * スタイルの読み直し(デザイン変更)に左右されない。 + * + * ラスターだけはベースマップのラベル(SDK の層でない最初の symbol 層)の下に入れる。 + * それ以外の帯はラベルの上に乗る(従来どおり)。 + */ +internal object MapplsOverlayStacking { + private val stacks = WeakHashMap() + + fun stackOf(map: MapplsMap): OverlayLayerStack = synchronized(stacks) { stacks.getOrPut(map) { OverlayLayerStack() } } + + fun key( + kind: OverlayKind, + zIndex: Int = 0, + ) = OverlayStacking.Key(kind, zIndex) + + /** マーカーのタイル(ラスター): マーカーの帯の一番下。 */ + val MARKER_TILE = key(OverlayKind.Marker, Int.MIN_VALUE) + + /** ドラッグ中のマーカーの層: マーカーの帯の一番上。 */ + val MARKER_DRAG = key(OverlayKind.Marker, Int.MAX_VALUE) + + /** + * [layer] を [key] の位置へ足す。同じ id の層が既にあれば何もしない + * (位置を変えたいときは先に [remove] する)。 + */ + fun add( + map: MapplsMap, + style: Style, + layer: Layer, + key: OverlayStacking.Key, + ) { + val stack = stackOf(map) + stack.put(layer.id, key) + if (style.getLayer(layer.id) != null) return + val layers = style.layers + val ids = layers.map { it.id } + var before = stack.beforeId(ids, key) + if (key.kind == OverlayKind.RasterLayer) { + val label = layers.firstOrNull { it is SymbolLayer && stack.keyOf(it.id) == null }?.id + if (label != null && (before == null || ids.indexOf(label) < ids.indexOf(before))) { + before = label + } + } + try { + if (before != null) style.addLayerBelow(layer, before) else style.addLayer(layer) + } catch (e: Exception) { + if (style.getLayer(layer.id) != null) return + try { + style.addLayer(layer) + } catch (e2: Exception) { + Log.w("Mappls", "Failed to add layer: ${layer.id} (${e2.message})") + } + } + } + + fun remove( + map: MapplsMap, + style: Style?, + layerId: String, + ) { + stackOf(map).remove(layerId) + try { + style?.removeLayer(layerId) + } catch (_: Exception) { + } + } +} diff --git a/src/main/java/com/mapconductor/mappls/MapplsViewController.kt b/src/main/java/com/mapconductor/mappls/MapplsViewController.kt index c7791b2..f05951c 100644 --- a/src/main/java/com/mapconductor/mappls/MapplsViewController.kt +++ b/src/main/java/com/mapconductor/mappls/MapplsViewController.kt @@ -337,19 +337,14 @@ class MapplsViewController( styleInstance?.let { style -> renderer.ensureDefaultIcon(style) ensureGeoJsonSource(style, renderer.markerLayer.sourceId) - addLayerAboveSafely( - style = style, - layer = renderer.markerLayer.layer, - layerId = renderer.markerLayer.layerId, - aboveId = polylineController.renderer.layer.layerId, + MapplsOverlayStacking.add( + holder.map, + style, + renderer.markerLayer.layer, + MapplsOverlayStacking.key(com.mapconductor.core.controller.OverlayKind.Marker), ) ensureGeoJsonSource(style, renderer.dragLayer.sourceId) - addLayerAboveSafely( - style = style, - layer = renderer.dragLayer.layer, - layerId = renderer.dragLayer.layerId, - aboveId = renderer.markerLayer.layerId, - ) + MapplsOverlayStacking.add(holder.map, style, renderer.dragLayer.layer, MapplsOverlayStacking.MARKER_DRAG) renderer.redraw() renderer.drawDragLayer() } diff --git a/src/main/java/com/mapconductor/mappls/MapplsViewControllerStyle.kt b/src/main/java/com/mapconductor/mappls/MapplsViewControllerStyle.kt index 74b8d03..177c03f 100644 --- a/src/main/java/com/mapconductor/mappls/MapplsViewControllerStyle.kt +++ b/src/main/java/com/mapconductor/mappls/MapplsViewControllerStyle.kt @@ -1,5 +1,6 @@ package com.mapconductor.mappls +import com.mapconductor.core.controller.OverlayKind import com.mapconductor.mappls.marker.MapplsMarkerOverlayRenderer import com.mappls.sdk.maps.Style import com.mappls.sdk.maps.style.expressions.Expression @@ -17,7 +18,9 @@ import kotlinx.coroutines.launch * * Mappls はスタイルが読み込み直されるたびにレイヤーが全部消えるため、ここは * 何度呼ばれても同じ状態になるように書いてある(`ensureGeoJsonSource` / - * `addLayerSafely` はいずれも既存があれば作らない)。 + * [MapplsOverlayStacking.add] はいずれも既存があれば作らない)。層の位置は + * [MapplsOverlayStacking] が帯の順(ラスター < GroundImage < ポリゴン < サークル < + * ポリライン < マーカー)で決める。 * * ポリゴンの z 順は**レイヤーを z 値ごとに用意して**表現する。Mappls には * フィーチャー単位の z 指定が無く、レイヤーの並び順だけが順序を決めるため。 @@ -34,6 +37,7 @@ internal fun MapplsViewController.ensureGeoJsonSource( } } +// 層の位置は MapplsOverlayStacking が決める。以下 2 つは公開 API の互換のために残している(SDK 内では未使用)。 internal fun MapplsViewController.addLayerSafely( style: Style, layer: Layer, @@ -84,51 +88,28 @@ internal fun MapplsViewController.setupStyle(style: Style) { ensureGeoJsonSource(style, polygonController.renderer.outline.layer.sourceId) ensureGeoJsonSource(style, polygonController.renderer.fill.layer.sourceId) - // Circle cts as anchor above polygons + // 層の位置は MapplsOverlayStacking が帯の順で決める(追加の順に依らない)。 + val stacking = MapplsOverlayStacking + val map = holder.map + val markerRenderer = markerController.renderer as MapplsMarkerOverlayRenderer + ensureGeoJsonSource(style, circleController.renderer.layer.sourceId) - addLayerSafely( - style = style, - layer = circleController.renderer.layer.layer, - layerId = circleController.renderer.layer.layerId, - ) - // Circle stroke (LineLayer) directly above the circle fill - addLayerAboveSafely( - style = style, - layer = circleController.renderer.layer.strokeLayer, - layerId = circleController.renderer.layer.strokeLayerId, - aboveId = circleController.renderer.layer.layerId, - ) + stacking.add(map, style, circleController.renderer.layer.layer, stacking.key(OverlayKind.Circle)) + // 線は塗りの直後(同じ鍵なら後から入れたものが上)。 + stacking.add(map, style, circleController.renderer.layer.strokeLayer, stacking.key(OverlayKind.Circle)) - // Polyline (general) acts as anchor above circles ensureGeoJsonSource(style, polylineController.renderer.layer.sourceId) - addLayerSafely( - style = style, - layer = polylineController.renderer.layer.layer, - layerId = polylineController.renderer.layer.layerId, - ) + stacking.add(map, style, polylineController.renderer.layer.layer, stacking.key(OverlayKind.Polyline)) - // Add z-indexed polygon layers below general polylines ensurePolygonZLayers(style) - // Marker - add source and layer at the top - ensureGeoJsonSource(style, (markerController.renderer as MapplsMarkerOverlayRenderer).markerLayer.sourceId) - addLayerAboveSafely( - style = style, - layer = (markerController.renderer as MapplsMarkerOverlayRenderer).markerLayer.layer, - layerId = (markerController.renderer as MapplsMarkerOverlayRenderer).markerLayer.layerId, - aboveId = polylineController.renderer.layer.layerId, - ) - (markerController.renderer as MapplsMarkerOverlayRenderer).redraw() + ensureGeoJsonSource(style, markerRenderer.markerLayer.sourceId) + stacking.add(map, style, markerRenderer.markerLayer.layer, stacking.key(OverlayKind.Marker)) + markerRenderer.redraw() - // Drag layer above marker layer - ensureGeoJsonSource(style, (markerController.renderer as MapplsMarkerOverlayRenderer).dragLayer.sourceId) - addLayerAboveSafely( - style = style, - layer = (markerController.renderer as MapplsMarkerOverlayRenderer).dragLayer.layer, - layerId = (markerController.renderer as MapplsMarkerOverlayRenderer).dragLayer.layerId, - aboveId = (markerController.renderer as MapplsMarkerOverlayRenderer).markerLayer.layerId, - ) - (markerController.renderer as MapplsMarkerOverlayRenderer).redraw() + ensureGeoJsonSource(style, markerRenderer.dragLayer.sourceId) + stacking.add(map, style, markerRenderer.dragLayer.layer, stacking.MARKER_DRAG) + markerRenderer.redraw() markerEventControllers .map { it.renderer as MapplsMarkerOverlayRenderer } @@ -136,19 +117,9 @@ internal fun MapplsViewController.setupStyle(style: Style) { .forEach { renderer -> renderer.ensureDefaultIcon(style) ensureGeoJsonSource(style, renderer.markerLayer.sourceId) - addLayerAboveSafely( - style = style, - layer = renderer.markerLayer.layer, - layerId = renderer.markerLayer.layerId, - aboveId = polylineController.renderer.layer.layerId, - ) + stacking.add(map, style, renderer.markerLayer.layer, stacking.key(OverlayKind.Marker)) ensureGeoJsonSource(style, renderer.dragLayer.sourceId) - addLayerAboveSafely( - style = style, - layer = renderer.dragLayer.layer, - layerId = renderer.dragLayer.layerId, - aboveId = renderer.markerLayer.layerId, - ) + stacking.add(map, style, renderer.dragLayer.layer, stacking.MARKER_DRAG) renderer.redraw() renderer.drawDragLayer() } @@ -167,7 +138,6 @@ internal fun MapplsViewController.setupStyle(style: Style) { internal fun MapplsViewController.ensurePolygonZLayers(style: Style) { val fillSourceId = polygonController.renderer.fill.layer.sourceId val outlineSourceId = polygonController.renderer.outline.layer.sourceId - val anchorId = polylineController.renderer.layer.layerId val zSet = polygonController.polygonManager @@ -180,14 +150,8 @@ internal fun MapplsViewController.ensurePolygonZLayers(style: Style) { toRemove.forEach { z -> val fillId = "polygon-fill-layer-$z" val outlineId = "polygon-outline-layer-$z" - try { - style.removeLayer(outlineId) - } catch (_: Exception) { - } - try { - style.removeLayer(fillId) - } catch (_: Exception) { - } + MapplsOverlayStacking.remove(holder.map, style, outlineId) + MapplsOverlayStacking.remove(holder.map, style, fillId) } val zList = zSet.toList().sorted() @@ -213,11 +177,7 @@ internal fun MapplsViewController.ensurePolygonZLayers(style: Style) { ), ) } - try { - style.addLayerBelow(fill, anchorId) - } catch (_: Exception) { - style.addLayer(fill) - } + MapplsOverlayStacking.add(holder.map, style, fill, MapplsOverlayStacking.key(OverlayKind.Polygon, z)) } if (style.getLayer(outlineId) == null) { @@ -246,11 +206,8 @@ internal fun MapplsViewController.ensurePolygonZLayers(style: Style) { ), ) } - try { - style.addLayerAbove(outline, fillId) - } catch (_: Exception) { - style.addLayer(outline) - } + // 塗りの直後(同じ鍵なら後から入れたものが上)。 + MapplsOverlayStacking.add(holder.map, style, outline, MapplsOverlayStacking.key(OverlayKind.Polygon, z)) } } polygonZLayers.clear() diff --git a/src/main/java/com/mapconductor/mappls/groundimage/MapplsGroundImageOverlayRenderer.kt b/src/main/java/com/mapconductor/mappls/groundimage/MapplsGroundImageOverlayRenderer.kt index 0dcc9eb..1b7059e 100644 --- a/src/main/java/com/mapconductor/mappls/groundimage/MapplsGroundImageOverlayRenderer.kt +++ b/src/main/java/com/mapconductor/mappls/groundimage/MapplsGroundImageOverlayRenderer.kt @@ -1,5 +1,6 @@ package com.mapconductor.mappls.groundimage +import com.mapconductor.core.controller.OverlayKind import com.mapconductor.core.features.GeoRectBounds import com.mapconductor.core.groundimage.AbstractGroundImageOverlayRenderer import com.mapconductor.core.groundimage.GroundImageEntityInterface @@ -7,6 +8,7 @@ import com.mapconductor.core.groundimage.GroundImageFingerPrint import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.mappls.MapplsActualGroundImage import com.mapconductor.mappls.MapplsMapViewHolderInterface +import com.mapconductor.mappls.MapplsOverlayStacking import com.mappls.sdk.maps.Style import com.mappls.sdk.maps.geometry.LatLng import com.mappls.sdk.maps.geometry.LatLngQuad @@ -104,25 +106,20 @@ class MapplsGroundImageOverlayRenderer( Log.w("Mappls", "Failed to add ground image source: ${e.message}") } - try { - style.addLayerBelow(layer, BELOW_LAYER_ID) - } catch (_: Exception) { - try { - style.addLayer(layer) - } catch (e: Exception) { - Log.w("Mappls", "Failed to add ground image layer: ${e.message}") - } - } + // GroundImage の帯(ラスターの上、ポリゴンの下)。送り直しても同じ位置。 + MapplsOverlayStacking.add( + holder.map, + style, + layer, + MapplsOverlayStacking.key(OverlayKind.GroundImage), + ) } private fun removeSourceAndLayerIfExists( style: Style, handle: MapplsGroundImageHandle, ) { - try { - style.removeLayer(handle.layerId) - } catch (_: Exception) { - } + MapplsOverlayStacking.remove(holder.map, style, handle.layerId) try { style.removeSource(handle.sourceId) } catch (_: Exception) { @@ -185,7 +182,6 @@ class MapplsGroundImageOverlayRenderer( opacity = opacity, ) - companion object { - private const val BELOW_LAYER_ID = "polyline-layer" - } + // 公開 API(Companion)の互換のために残している。 + companion object } diff --git a/src/main/java/com/mapconductor/mappls/raster/MapplsRasterLayerOverlayRenderer.kt b/src/main/java/com/mapconductor/mappls/raster/MapplsRasterLayerOverlayRenderer.kt index 60512e3..fc2c8ac 100644 --- a/src/main/java/com/mapconductor/mappls/raster/MapplsRasterLayerOverlayRenderer.kt +++ b/src/main/java/com/mapconductor/mappls/raster/MapplsRasterLayerOverlayRenderer.kt @@ -1,11 +1,13 @@ package com.mapconductor.mappls.raster +import com.mapconductor.core.controller.OverlayKind import com.mapconductor.core.raster.RasterLayerEntityInterface import com.mapconductor.core.raster.RasterLayerOverlayRendererInterface import com.mapconductor.core.raster.RasterLayerSource import com.mapconductor.core.raster.RasterLayerState import com.mapconductor.core.raster.TileScheme import com.mapconductor.mappls.MapplsMapViewHolderInterface +import com.mapconductor.mappls.MapplsOverlayStacking import com.mappls.sdk.maps.style.layers.Property import com.mappls.sdk.maps.style.layers.PropertyFactory import com.mappls.sdk.maps.style.layers.RasterLayer @@ -93,38 +95,26 @@ class MapplsRasterLayerOverlayRenderer( } catch (e: Exception) { Log.w("Mappls", "Failed to add raster source: ${e.message}") } - try { - if (isMarkerTileRaster(state)) { - addLayerForMarkerTile(style, layer) - } else { - addBelowBasemapLabels(style, layer) - } - } catch (e: Exception) { - Log.w("Mappls", "Failed to add raster layer: ${e.message}") - } + addToStack(style, layer, state) return handle } - private fun addLayerForMarkerTile( + /** + * 帯の順([MapplsOverlayStacking])で足す。アプリのラスターはラスターの帯 + * (一番下、ベースマップのラベルの下)、マーカーのタイルはマーカーの帯の一番下。 + */ + private fun addToStack( style: com.mappls.sdk.maps.Style, layer: RasterLayer, + state: RasterLayerState, ) { - // Insert the raster tiles below the marker symbol layer so they don't cover markers, - // but remain above vector overlays that are anchored below markers (polyline/circle/etc). - try { - style.addLayerBelow(layer, MARKERS_LAYER_ID) - return - } catch (_: Exception) { - } - - // Best-effort fallback: place above polylines if marker layer isn't present yet. - try { - style.addLayerAbove(layer, POLYLINE_LAYER_ID) - return - } catch (_: Exception) { - } - - style.addLayer(layer) + val key = + if (isMarkerTileRaster(state)) { + MapplsOverlayStacking.MARKER_TILE + } else { + MapplsOverlayStacking.key(OverlayKind.RasterLayer, state.zIndex) + } + MapplsOverlayStacking.add(holder.map, style, layer, key) } private fun updateLayer( @@ -145,10 +135,7 @@ class MapplsRasterLayerOverlayRenderer( private fun removeLayer(entity: RasterLayerEntityInterface) { val style = holder.map.style ?: return val handle = entity.layer - try { - style.removeLayer(handle.layerId) - } catch (_: Exception) { - } + MapplsOverlayStacking.remove(holder.map, style, handle.layerId) try { style.removeSource(handle.sourceId) } catch (_: Exception) { @@ -164,11 +151,10 @@ class MapplsRasterLayerOverlayRenderer( .mapNotNull { state -> handleById[state.id]?.let { handle -> state to handle } } .toList() + // zIndex が変わったときのために、いったん外して zIndex の順に入れ直す + // (同じ zIndex なら渡された順)。位置は帯の順で決まる。 ordered.forEach { (_, handle) -> - try { - style.removeLayer(handle.layerId) - } catch (_: Exception) { - } + MapplsOverlayStacking.remove(holder.map, style, handle.layerId) } ordered.forEach { (state, handle) -> @@ -180,59 +166,10 @@ class MapplsRasterLayerOverlayRenderer( if (state.visible) Property.VISIBLE else Property.NONE, ), ) - try { - addBelowBasemapLabels(style, layer) - } catch (_: Exception) { - } - } - } - - /** - * Puts a raster layer above the basemap's geometry but **below its labels**. - * - * Appended at the top of the style instead, a raster overlay covers the - * place names, road names and shields the backend draws -- a vector tile - * layer's own roads run straight through them, which is what "the labels - * are under the lines" looks like. Every raster overlay we add has the - * same problem, so the rule lives here rather than in each of them. - * - * Inserting successive layers below the same anchor keeps their order: - * each one lands directly below the anchor, which is directly above the - * one inserted before it. - * - * Anything of ours is skipped when looking for the anchor. Markers are a - * symbol layer too, and anchoring to them would put the raster back above - * the labels -- with the added twist of only doing so once a marker exists. - */ - private fun addBelowBasemapLabels( - style: com.mappls.sdk.maps.Style, - layer: com.mappls.sdk.maps.style.layers.Layer, - ) { - val anchor = - style.layers.firstOrNull { - it is com.mappls.sdk.maps.style.layers.SymbolLayer && !isOursById(it.id) - } - if (anchor == null) { - style.addLayer(layer) - return - } - try { - style.addLayerBelow(layer, anchor.id) - } catch (_: Exception) { - style.addLayer(layer) + addToStack(style, layer, state) } } - /** True for layers this SDK adds, as opposed to the design's own. */ - private fun isOursById(id: String): Boolean = - id.startsWith("raster-layer-") || - id.startsWith("org.maplibre.annotations") || - id.startsWith("com.mappls.annotations") || - id.startsWith(MARKERS_LAYER_ID) || - id.startsWith("marker-drag-layer") || - id.startsWith(POLYLINE_LAYER_ID) || - id.startsWith("circle-layer") - private fun buildSource( sourceId: String, source: RasterLayerSource, @@ -260,8 +197,6 @@ class MapplsRasterLayerOverlayRenderer( private companion object { private const val MARKER_TILE_RASTER_ID_PREFIX = "marker-tile-" - private const val MARKERS_LAYER_ID = "markers-layer" - private const val POLYLINE_LAYER_ID = "polyline-layer" } }