diff --git a/api/android-for-mapbox.api b/api/android-for-mapbox.api index 7e1e793..86164e0 100644 --- a/api/android-for-mapbox.api +++ b/api/android-for-mapbox.api @@ -211,6 +211,15 @@ public final class com.mapconductor.mapbox.MapboxMapViewSaver extends com.mapcon public final class com.mapconductor.mapbox.MapboxMapViewScope extends com.mapconductor.compose.MapViewScope { public com.mapconductor.mapbox.MapboxMapViewScope(); } +public final class com.mapconductor.mapbox.MapboxOverlayStacking { + public static final com.mapconductor.mapbox.MapboxOverlayStacking INSTANCE; + public final com.mapconductor.core.controller.OverlayLayerStack stackOf(com.mapbox.maps.MapboxMap); + 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.mapbox.maps.MapboxMap, com.mapbox.maps.Style, com.mapbox.maps.extension.style.layers.Layer, com.mapconductor.core.controller.OverlayStacking$Key); + public final void remove(com.mapbox.maps.MapboxMap, com.mapbox.maps.Style, java.lang.String); +} public final class com.mapconductor.mapbox.MapboxPaddings extends com.mapconductor.core.map.MapPaddings implements com.mapconductor.mapbox.IMapboxPaddingsInterface { public static final com.mapconductor.mapbox.MapboxPaddings$Companion Companion; public com.mapconductor.mapbox.MapboxPaddings(double, double, double, double); diff --git a/src/main/java/com/mapconductor/mapbox/MapboxMapViewControllerStyle.kt b/src/main/java/com/mapconductor/mapbox/MapboxMapViewControllerStyle.kt index cbbc2d0..3f08602 100644 --- a/src/main/java/com/mapconductor/mapbox/MapboxMapViewControllerStyle.kt +++ b/src/main/java/com/mapconductor/mapbox/MapboxMapViewControllerStyle.kt @@ -1,9 +1,7 @@ package com.mapconductor.mapbox -import com.mapbox.maps.extension.style.layers.addLayer -import com.mapbox.maps.extension.style.layers.addLayerAbove -import com.mapbox.maps.extension.style.layers.addLayerBelow import com.mapbox.maps.extension.style.sources.addSource +import com.mapconductor.core.controller.OverlayKind import com.mapconductor.mapbox.marker.MapboxMarkerOverlayRenderer /** @@ -21,18 +19,12 @@ internal fun MapboxMapViewController.attachMarkerLayers( style.addSource(renderer.markerLayer.source) } catch (_: Exception) { } - try { - style.addLayer(renderer.markerLayer.layer) - } catch (_: Exception) { - } + MapboxOverlayStacking.add(holder.map, style, renderer.markerLayer.layer, MapboxOverlayStacking.key(OverlayKind.Marker)) try { style.addSource(renderer.dragLayer.source) } catch (_: Exception) { } - try { - style.addLayer(renderer.dragLayer.layer) - } catch (_: Exception) { - } + MapboxOverlayStacking.add(holder.map, style, renderer.dragLayer.layer, MapboxOverlayStacking.MARKER_DRAG) } internal fun MapboxMapViewController.attachOverlaySourcesAndLayers(style: com.mapbox.maps.Style) { @@ -42,14 +34,22 @@ internal fun MapboxMapViewController.attachOverlaySourcesAndLayers(style: com.ma // Circle fill (fill pass) then stroke (line pass) — both below general polyline style.addSource(circleController.renderer.layer.source) - style.addLayer(circleController.renderer.layer.fillLayer) - style.addLayer(circleController.renderer.layer.strokeLayer) + // 層の位置は MapboxOverlayStacking が帯の順で決める(追加の順に依らない)。 + // 線は塗りの直後(同じ鍵なら後から入れたものが上)。 + val circleKey = MapboxOverlayStacking.key(OverlayKind.Circle) + MapboxOverlayStacking.add(holder.map, style, circleController.renderer.layer.fillLayer, circleKey) + MapboxOverlayStacking.add(holder.map, style, circleController.renderer.layer.strokeLayer, circleKey) // Polyline (general) style.addSource(polylineController.renderer.layer.source) - style.addLayer(polylineController.renderer.layer.layer) + MapboxOverlayStacking.add( + holder.map, + style, + polylineController.renderer.layer.layer, + MapboxOverlayStacking.key(OverlayKind.Polyline), + ) - // Add z-indexed polygon layers below general polylines + // Polygon z-layers (polygon band: above ground images, below circles) ensurePolygonZLayers(style) // Marker + drag layers @@ -76,14 +76,8 @@ internal fun MapboxMapViewController.ensurePolygonZLayers(style: com.mapbox.maps toRemove.forEach { z -> val fillId = "polygon-fill-layer-$z" val outlineId = "polygon-outline-layer-$z" - try { - style.removeStyleLayer(outlineId) - } catch (_: Exception) { - } - try { - style.removeStyleLayer(fillId) - } catch (_: Exception) { - } + MapboxOverlayStacking.remove(holder.map, style, outlineId) + MapboxOverlayStacking.remove(holder.map, style, fillId) } val zList = zSet.toList().sorted() @@ -108,11 +102,7 @@ internal fun MapboxMapViewController.ensurePolygonZLayers(style: com.mapbox.maps .get("fillColor"), ) } - try { - style.addLayerBelow(layer, anchorId) - } catch (_: Exception) { - style.addLayer(layer) - } + MapboxOverlayStacking.add(holder.map, style, layer, MapboxOverlayStacking.key(OverlayKind.Polygon, z)) } // Outline layer above its fill @@ -138,11 +128,8 @@ internal fun MapboxMapViewController.ensurePolygonZLayers(style: com.mapbox.maps .get("strokeWidth"), ) } - try { - style.addLayerAbove(layer, fillId) - } catch (_: Exception) { - style.addLayer(layer) - } + // 塗りの直後(同じ鍵なら後から入れたものが上)。 + MapboxOverlayStacking.add(holder.map, style, layer, MapboxOverlayStacking.key(OverlayKind.Polygon, z)) } } // Update tracked set diff --git a/src/main/java/com/mapconductor/mapbox/MapboxOverlayStacking.kt b/src/main/java/com/mapconductor/mapbox/MapboxOverlayStacking.kt new file mode 100644 index 0000000..34757f5 --- /dev/null +++ b/src/main/java/com/mapconductor/mapbox/MapboxOverlayStacking.kt @@ -0,0 +1,86 @@ +package com.mapconductor.mapbox + +import android.util.Log +import com.mapbox.maps.MapboxMap +import com.mapbox.maps.Style +import com.mapbox.maps.extension.style.layers.Layer +import com.mapbox.maps.extension.style.layers.addLayer +import com.mapbox.maps.extension.style.layers.addLayerBelow +import com.mapconductor.core.controller.OverlayKind +import com.mapconductor.core.controller.OverlayLayerStack +import com.mapconductor.core.controller.OverlayStacking +import java.util.WeakHashMap + +/** + * オーバーレイの層をスタイルのどこへ入れるか([OverlayStacking] の帯の順)。 + * + * 下から: ラスター < GroundImage < ポリゴン < サークル < ポリライン < マーカー + * (マーカーのタイルはマーカーの帯の一番下、ドラッグ中の層は一番上)。 + * + * 位置は毎回スタイルの今の並びから決めるので、追加した順・送り直し・ + * スタイルの読み直し(デザイン変更)に左右されない。 + * + * ラスターだけはベースマップのラベル(SDK の層でない最初の symbol 層)の下に入れる。 + * それ以外の帯はラベルの上に乗る(従来どおり)。Standard スタイルのように + * ベースマップが import で、その層が `styleLayers` に出てこない場合は、 + * ラスターも含めてベースマップ全体の上に乗る。 + */ +internal object MapboxOverlayStacking { + private val stacks = WeakHashMap() + + fun stackOf(map: MapboxMap): 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 の層が既にあれば何もしない。 */ + fun add( + map: MapboxMap, + style: Style, + layer: Layer, + key: OverlayStacking.Key, + ) { + val stack = stackOf(map) + stack.put(layer.layerId, key) + if (style.styleLayerExists(layer.layerId)) return + val layers = style.styleLayers + val ids = layers.map { it.id } + var before = stack.beforeId(ids, key) + if (key.kind == OverlayKind.RasterLayer) { + val label = layers.firstOrNull { it.type == "symbol" && 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.styleLayerExists(layer.layerId)) return + try { + style.addLayer(layer) + } catch (e2: Exception) { + Log.w("Mapbox", "Failed to add layer: ${layer.layerId} (${e2.message})") + } + } + } + + fun remove( + map: MapboxMap, + style: Style?, + layerId: String, + ) { + stackOf(map).remove(layerId) + try { + style?.removeStyleLayer(layerId) + } catch (_: Exception) { + } + } +} diff --git a/src/main/java/com/mapconductor/mapbox/groundimage/MapboxGroundImageOverlayRenderer.kt b/src/main/java/com/mapconductor/mapbox/groundimage/MapboxGroundImageOverlayRenderer.kt index 34b8679..2a5c186 100644 --- a/src/main/java/com/mapconductor/mapbox/groundimage/MapboxGroundImageOverlayRenderer.kt +++ b/src/main/java/com/mapconductor/mapbox/groundimage/MapboxGroundImageOverlayRenderer.kt @@ -3,13 +3,12 @@ package com.mapconductor.mapbox.groundimage import com.mapbox.bindgen.DataRef import com.mapbox.maps.Image import com.mapbox.maps.Style -import com.mapbox.maps.extension.style.layers.addLayer -import com.mapbox.maps.extension.style.layers.addLayerBelow import com.mapbox.maps.extension.style.layers.generated.rasterLayer import com.mapbox.maps.extension.style.layers.properties.generated.Visibility import com.mapbox.maps.extension.style.sources.addSource import com.mapbox.maps.extension.style.sources.generated.imageSource import com.mapbox.maps.extension.style.utils.TypeUtils +import com.mapconductor.core.controller.OverlayKind import com.mapconductor.core.features.GeoRectBounds import com.mapconductor.core.groundimage.AbstractGroundImageOverlayRenderer import com.mapconductor.core.groundimage.GroundImageEntityInterface @@ -17,6 +16,7 @@ import com.mapconductor.core.groundimage.GroundImageFingerPrint import com.mapconductor.core.groundimage.GroundImageState import com.mapconductor.mapbox.MapboxActualGroundImage import com.mapconductor.mapbox.MapboxMapViewHolder +import com.mapconductor.mapbox.MapboxOverlayStacking import java.nio.ByteBuffer import android.annotation.SuppressLint import android.graphics.Bitmap @@ -134,25 +134,15 @@ class MapboxGroundImageOverlayRenderer( Log.w("Mapbox", "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("Mapbox", "Failed to add ground image layer: ${e.message}") - } - } + // GroundImage の帯(ラスターの上、ポリゴンの下)。送り直しても同じ位置。 + MapboxOverlayStacking.add(holder.map, style, layer, MapboxOverlayStacking.key(OverlayKind.GroundImage)) } private fun removeSourceAndLayerIfExists( style: Style, handle: MapboxGroundImageHandle, ) { - try { - style.removeStyleLayer(handle.layerId) - } catch (_: Exception) { - } + MapboxOverlayStacking.remove(holder.map, style, handle.layerId) try { style.removeStyleSource(handle.sourceId) } catch (_: Exception) { @@ -224,7 +214,6 @@ class MapboxGroundImageOverlayRenderer( opacity = opacity, ) - companion object { - private const val BELOW_LAYER_ID = "polyline-layer" - } + // 公開 API(Companion)の互換のために残している。 + companion object } diff --git a/src/main/java/com/mapconductor/mapbox/raster/MapboxRasterLayerOverlayRenderer.kt b/src/main/java/com/mapconductor/mapbox/raster/MapboxRasterLayerOverlayRenderer.kt index 54c73a1..9c36fdc 100644 --- a/src/main/java/com/mapconductor/mapbox/raster/MapboxRasterLayerOverlayRenderer.kt +++ b/src/main/java/com/mapconductor/mapbox/raster/MapboxRasterLayerOverlayRenderer.kt @@ -1,11 +1,9 @@ package com.mapconductor.mapbox.raster -import com.mapbox.maps.extension.style.layers.addLayer -import com.mapbox.maps.extension.style.layers.addLayerAbove -import com.mapbox.maps.extension.style.layers.addLayerBelow import com.mapbox.maps.extension.style.layers.generated.rasterLayer import com.mapbox.maps.extension.style.sources.addSource import com.mapbox.maps.extension.style.sources.generated.rasterSource +import com.mapconductor.core.controller.OverlayKind import com.mapconductor.core.raster.RasterHeaderRuleSet import com.mapconductor.core.raster.RasterLayerEntityInterface import com.mapconductor.core.raster.RasterLayerOverlayRendererInterface @@ -13,6 +11,7 @@ import com.mapconductor.core.raster.RasterLayerSource import com.mapconductor.core.raster.RasterLayerState import com.mapconductor.core.raster.TileScheme import com.mapconductor.mapbox.MapboxMapViewHolder +import com.mapconductor.mapbox.MapboxOverlayStacking import android.util.Log import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -99,15 +98,7 @@ class MapboxRasterLayerOverlayRenderer( } catch (e: Exception) { Log.w("Mapbox", "Failed to add raster source: ${e.message}") } - try { - if (isMarkerTileRaster(state)) { - addLayerForMarkerTile(style, layer) - } else { - addBelowBasemapLabels(style, layer) - } - } catch (e: Exception) { - Log.w("Mapbox", "Failed to add raster layer: ${e.message}") - } + addToStack(style, layer, state) return handle } @@ -116,10 +107,7 @@ class MapboxRasterLayerOverlayRenderer( state: RasterLayerState, ) { val style = holder.map.style ?: return - try { - style.removeStyleLayer(handle.layerId) - } catch (_: Exception) { - } + MapboxOverlayStacking.remove(holder.map, style, handle.layerId) val opacity = if (state.visible) { state.opacity.coerceIn(0.0f, 1.0f).toDouble() @@ -130,23 +118,13 @@ class MapboxRasterLayerOverlayRenderer( rasterLayer(handle.layerId, handle.sourceId) { rasterOpacity(opacity) } - try { - if (isMarkerTileRaster(state)) { - addLayerForMarkerTile(style, layer) - } else { - addBelowBasemapLabels(style, layer) - } - } catch (_: Exception) { - } + addToStack(style, layer, state) } private fun removeLayer(entity: RasterLayerEntityInterface) { val style = holder.map.style ?: return val handle = entity.layer - try { - style.removeStyleLayer(handle.layerId) - } catch (_: Exception) { - } + MapboxOverlayStacking.remove(holder.map, style, handle.layerId) try { style.removeStyleSource(handle.sourceId) } catch (_: Exception) { @@ -182,26 +160,22 @@ class MapboxRasterLayerOverlayRenderer( } } - private fun addLayerForMarkerTile( + /** + * 帯の順([MapboxOverlayStacking])で足す。アプリのラスターはラスターの帯 + * (一番下、ベースマップのラベルの下)、マーカーのタイルはマーカーの帯の一番下。 + */ + private fun addToStack( style: com.mapbox.maps.Style, layer: com.mapbox.maps.extension.style.layers.Layer, + 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)) { + MapboxOverlayStacking.MARKER_TILE + } else { + MapboxOverlayStacking.key(OverlayKind.RasterLayer, state.zIndex) + } + MapboxOverlayStacking.add(holder.map, style, layer, key) } private fun rebuildNonMarkerRasterLayers(style: com.mapbox.maps.Style) { @@ -215,10 +189,7 @@ class MapboxRasterLayerOverlayRenderer( // Remove and re-add non-marker raster layers in zIndex order to ensure deterministic stacking. ordered.forEach { (_, handle) -> - try { - style.removeStyleLayer(handle.layerId) - } catch (_: Exception) { - } + MapboxOverlayStacking.remove(holder.map, style, handle.layerId) } ordered.forEach { (state, handle) -> @@ -232,59 +203,12 @@ class MapboxRasterLayerOverlayRenderer( rasterLayer(handle.layerId, handle.sourceId) { rasterOpacity(opacity) } - try { - addBelowBasemapLabels(style, layer) - } catch (_: Exception) { - } + addToStack(style, layer, state) } } - /** - * 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.mapbox.maps.Style, - layer: com.mapbox.maps.extension.style.layers.Layer, - ) { - val anchor = - style.styleLayers.firstOrNull { it.type == "symbol" && !isOursById(it.id) } - if (anchor == null) { - style.addLayer(layer) - return - } - try { - style.addLayerBelow(layer, anchor.id) - } catch (_: Exception) { - style.addLayer(layer) - } - } - - /** 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(MARKERS_LAYER_ID) || - id.startsWith("marker-drag-layer") || - id.startsWith(POLYLINE_LAYER_ID) || - id.startsWith("circle-layer") - 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" } }