From 5c7da9f1b561f82492e02643d3497a9c34da1ecd Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Sat, 7 Nov 2026 23:55:27 -0800 Subject: [PATCH] Give rasters, ground images and marker tiles their own index bands Rasters and ground images shared one index range filled from freed indices, so their order followed add/re-send order; marker tiles were in the same range, below polygons. Rasters, ground images and marker tiles now get separate index bands (raster < ground image < polygon/circle/polyline < marker tiles < icons). Polygons, circles and polylines are handed to their layer in zIndex order. 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-openmobilemaps.api | 14 ++++ .../openmobilemaps/OpenMobileMapsLayers.kt | 83 +++++++++++++++---- .../openmobilemaps/OpenMobileMapsOverlays.kt | 20 +++-- 3 files changed, 96 insertions(+), 21 deletions(-) diff --git a/api/android-for-openmobilemaps.api b/api/android-for-openmobilemaps.api index 5e95939..f1d5491 100644 --- a/api/android-for-openmobilemaps.api +++ b/api/android-for-openmobilemaps.api @@ -121,8 +121,12 @@ public final class com.mapconductor.openmobilemaps.OpenMobileMapsGroundImageOver } public final class com.mapconductor.openmobilemaps.OpenMobileMapsLayers { public static final int DESIGN_INDEX; + public static final int RASTER_INDEX_FIRST; public static final int BELOW_INDEX_FIRST; + public static final int GROUND_IMAGE_INDEX_FIRST; public static final int FIXED_INDEX_FIRST; + public static final int MARKER_TILE_INDEX_FIRST; + public static final int ICON_INDEX; public com.mapconductor.openmobilemaps.OpenMobileMapsLayers(); public final io.openmobilemaps.mapscore.shared.map.layers.polygon.PolygonLayerInterface getPolygonFillLayer(); public final io.openmobilemaps.mapscore.shared.map.layers.line.LineLayerInterface getPolygonOutlineLayer(); @@ -133,9 +137,19 @@ public final class com.mapconductor.openmobilemaps.OpenMobileMapsLayers { public final void attachTo(io.openmobilemaps.mapscore.shared.map.MapInterface); public final void setDesignLayer(io.openmobilemaps.mapscore.shared.map.MapInterface, io.openmobilemaps.mapscore.shared.map.LayerInterface); public final io.openmobilemaps.mapscore.shared.map.LayerInterface insertBelowOverlays(io.openmobilemaps.mapscore.shared.map.MapInterface, io.openmobilemaps.mapscore.shared.map.LayerInterface); + public final io.openmobilemaps.mapscore.shared.map.LayerInterface insertGroundImage(io.openmobilemaps.mapscore.shared.map.MapInterface, io.openmobilemaps.mapscore.shared.map.LayerInterface); + public final io.openmobilemaps.mapscore.shared.map.LayerInterface insertMarkerTile(io.openmobilemaps.mapscore.shared.map.MapInterface, io.openmobilemaps.mapscore.shared.map.LayerInterface); public final void remove(io.openmobilemaps.mapscore.shared.map.MapInterface, io.openmobilemaps.mapscore.shared.map.LayerInterface); public final void clearAll(); } +final class com.mapconductor.openmobilemaps.OpenMobileMapsLayers$Band { + public final int getFirst(); + public final int getEnd(); + public final java.util.TreeSet getFree(); + public final int getNext(); + public final void setNext(int); + public final int take(); +} final class com.mapconductor.openmobilemaps.OpenMobileMapsLayers$Companion { } public interface com.mapconductor.openmobilemaps.OpenMobileMapsMapDesignTypeInterface extends com.mapconductor.core.map.MapDesignTypeInterface { diff --git a/src/main/java/com/mapconductor/openmobilemaps/OpenMobileMapsLayers.kt b/src/main/java/com/mapconductor/openmobilemaps/OpenMobileMapsLayers.kt index 1cbd05a..47f2f66 100644 --- a/src/main/java/com/mapconductor/openmobilemaps/OpenMobileMapsLayers.kt +++ b/src/main/java/com/mapconductor/openmobilemaps/OpenMobileMapsLayers.kt @@ -23,6 +23,11 @@ import io.openmobilemaps.mapscore.shared.map.layers.polygon.PolygonLayerInterfac * → 円の塗り → 円の輪郭 → ポリライン → マーカー * ``` * + * すべてのプロバイダ共通の決まり([com.mapconductor.core.controller.OverlayStacking])。 + * ラスターとグラウンドイメージ、マーカーのタイルは数が変わるので、帯ごとに索引の区間を分けて + * 割り当てる(以前はラスターとグラウンドイメージが同じ区間を空き番号から使い回していたため、 + * 追加・送り直しの順で上下が入れ替わった)。 + * * 他プロバイダのクリックのカスケード(marker → circle → groundImage → polyline → * polygon → map)と**上下が逆に見える**が、これは正しい。カスケードは「上にあるものから * 順に当たり判定する」ので、描画の重ね順を上から読むとカスケード順に一致する。 @@ -56,18 +61,30 @@ class OpenMobileMapsLayers { /** 地図デザインとオーバーレイ群の間に入っているレイヤ(ラスター / グラウンドイメージ)と、その索引。 */ private val belowOverlayIndices = mutableMapOf() - /** 外したレイヤの索引。使い回さないと、タイルの張り替えを繰り返すうちに索引が尽きる。 */ - private val freeBelowIndices = sortedSetOf() - private var nextBelowIndex = BELOW_INDEX_FIRST + /** 帯ごとの索引の割り当て。外したレイヤの索引は使い回す(張り替えを繰り返すうちに尽きないよう)。 */ + private class Band( + val first: Int, + val end: Int, + ) { + val free = sortedSetOf() + var next = first + + fun take(): Int = free.pollFirst() ?: next++.also { check(it < end) { "layer index band exhausted" } } + } + + private val rasterBand = Band(RASTER_INDEX_FIRST, GROUND_IMAGE_INDEX_FIRST) + private val groundImageBand = Band(GROUND_IMAGE_INDEX_FIRST, FIXED_INDEX_FIRST) + private val markerTileBand = Band(MARKER_TILE_INDEX_FIRST, ICON_INDEX) + private val bandOfLayer = mutableMapOf() - private val fixedLayers: List by lazy { + private val fixedLayers: List> by lazy { listOf( - polygonFillLayer.asLayerInterface(), - polygonOutlineLayer.asLayerInterface(), - circleFillLayer.asLayerInterface(), - circleOutlineLayer.asLayerInterface(), - polylineLayer.asLayerInterface(), - iconLayer.asLayerInterface(), + polygonFillLayer.asLayerInterface() to FIXED_INDEX_FIRST, + polygonOutlineLayer.asLayerInterface() to FIXED_INDEX_FIRST + 1, + circleFillLayer.asLayerInterface() to FIXED_INDEX_FIRST + 2, + circleOutlineLayer.asLayerInterface() to FIXED_INDEX_FIRST + 3, + polylineLayer.asLayerInterface() to FIXED_INDEX_FIRST + 4, + iconLayer.asLayerInterface() to ICON_INDEX, ) } @@ -90,8 +107,8 @@ class OpenMobileMapsLayers { polylineLayer.setLayerClickable(false) iconLayer.setLayerClickable(false) - fixedLayers.forEachIndexed { offset, layer -> - map.insertLayerAt(layer, FIXED_INDEX_FIRST + offset) + fixedLayers.forEach { (layer, index) -> + map.insertLayerAt(layer, index) } } @@ -114,9 +131,28 @@ class OpenMobileMapsLayers { fun insertBelowOverlays( map: MapInterface, layer: LayerInterface, + ): LayerInterface = insertInBand(map, layer, rasterBand) + + /** グラウンドイメージ: ラスターレイヤの上、ポリゴンの下。 */ + fun insertGroundImage( + map: MapInterface, + layer: LayerInterface, + ): LayerInterface = insertInBand(map, layer, groundImageBand) + + /** マーカーのタイル: ポリラインの上、マーカー(アイコン)の下。 */ + fun insertMarkerTile( + map: MapInterface, + layer: LayerInterface, + ): LayerInterface = insertInBand(map, layer, markerTileBand) + + private fun insertInBand( + map: MapInterface, + layer: LayerInterface, + band: Band, ): LayerInterface { - val index = freeBelowIndices.pollFirst() ?: nextBelowIndex++ + val index = band.take() belowOverlayIndices[layer] = index + bandOfLayer[layer] = band map.insertLayerAt(layer, index) return layer } @@ -125,7 +161,9 @@ class OpenMobileMapsLayers { map: MapInterface, layer: LayerInterface, ) { - belowOverlayIndices.remove(layer)?.let { freeBelowIndices.add(it) } + val index = belowOverlayIndices.remove(layer) + val band = bandOfLayer.remove(layer) + if (index != null && band != null) band.free.add(index) map.removeLayer(layer) } @@ -142,10 +180,23 @@ class OpenMobileMapsLayers { /** 地図デザイン。一番下。 */ const val DESIGN_INDEX = 0 - /** ラスターレイヤ・グラウンドイメージ。地図デザインとオーバーレイ群の間。 */ - const val BELOW_INDEX_FIRST = 1 + /** ラスターレイヤ。地図デザインの上。 */ + const val RASTER_INDEX_FIRST = 1 + + /** 以前の名前(ラスターとグラウンドイメージが共用していた区間の先頭)。 */ + @Suppress("unused") + const val BELOW_INDEX_FIRST = RASTER_INDEX_FIRST + + /** グラウンドイメージ。ラスターの上、ポリゴンの下。 */ + const val GROUND_IMAGE_INDEX_FIRST = 500 /** 固定のオーバーレイ 6 種。下の枠を使い切らないよう十分離す。 */ const val FIXED_INDEX_FIRST = 1_000 + + /** マーカーのタイル。ポリラインの上、マーカーの下。 */ + const val MARKER_TILE_INDEX_FIRST = 1_100 + + /** マーカー(アイコン)。一番上。 */ + const val ICON_INDEX = 2_000 } } diff --git a/src/main/java/com/mapconductor/openmobilemaps/OpenMobileMapsOverlays.kt b/src/main/java/com/mapconductor/openmobilemaps/OpenMobileMapsOverlays.kt index 51886c4..b9cbe13 100644 --- a/src/main/java/com/mapconductor/openmobilemaps/OpenMobileMapsOverlays.kt +++ b/src/main/java/com/mapconductor/openmobilemaps/OpenMobileMapsOverlays.kt @@ -406,7 +406,10 @@ class OpenMobileMapsPolylineOverlayRenderer( override suspend fun removePolyline(entity: PolylineEntityInterface) = Unit override suspend fun onPostProcess() { - lineLayer.setLines(polylineManager.allEntities().flatMapTo(ArrayList()) { it.polyline.orEmpty() }) + // 帯の中は zIndex の順(同じなら渡された順)。 + lineLayer.setLines( + polylineManager.allEntities().sortedBy { it.state.zIndex }.flatMapTo(ArrayList()) { it.polyline.orEmpty() }, + ) } } @@ -475,7 +478,8 @@ class OpenMobileMapsPolygonOverlayRenderer( override suspend fun removePolygon(entity: PolygonEntityInterface) = Unit override suspend fun onPostProcess() { - val entities = polygonManager.allEntities() + // 帯の中は zIndex の順(同じなら渡された順)。 + val entities = polygonManager.allEntities().sortedBy { it.state.zIndex } // add / addAll ではなく setPolygons を使うこと。4.0 の PolygonLayer は // 原点(第 2 引数)を持つようになっていて、setPolygons を一度も通っていない // レイヤに add すると**何も描かれない**(例外も警告も出ない)。 @@ -530,7 +534,7 @@ class OpenMobileMapsCircleOverlayRenderer( override suspend fun removeCircle(entity: CircleEntityInterface) = Unit override suspend fun onPostProcess() { - val entities = circleManager.allEntities() + val entities = circleManager.allEntities().sortedBy { it.state.zIndex ?: 0 } fillLayer.setPolygons(entities.flatMapTo(ArrayList()) { it.circle.fills }, RENDER_ORIGIN) outlineLayer.setLines(entities.flatMapTo(ArrayList()) { it.circle.outlines }) } @@ -583,7 +587,7 @@ class OpenMobileMapsGroundImageOverlayRenderer( return OpenMobileMapsActualGroundImage( layer = layer, - layerInterface = layers.insertBelowOverlays(holder.map, layer.asLayerInterface()), + layerInterface = layers.insertGroundImage(holder.map, layer.asLayerInterface()), ) } @@ -685,7 +689,13 @@ class OpenMobileMapsRasterLayerOverlayRenderer( } return OpenMobileMapsActualRasterLayer( layer = layer, - layerInterface = layers.insertBelowOverlays(holder.map, layer.asLayerInterface()), + layerInterface = + if (state.id.startsWith("marker-tile-")) { + // マーカーのタイルはマーカーの帯(ポリラインの上)。 + layers.insertMarkerTile(holder.map, layer.asLayerInterface()) + } else { + layers.insertBelowOverlays(holder.map, layer.asLayerInterface()) + }, ) } }