From 2a46b29058a3738b030f243ff23adac55773a5bb Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Sat, 7 Nov 2026 08:35:35 -0800 Subject: [PATCH 1/4] hasMarker: look ids up in a set instead of scanning the list MarkerState.onAdded asks hasMarker once per marker after every composition; a linear scan made that quadratic (1e5 markers, 1e10 comparisons). --- .../longdo/LongdoMapViewController.kt | 14 +++++++--- .../longdo/marker/MarkerIdIndex.kt | 28 +++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/mapconductor/longdo/marker/MarkerIdIndex.kt diff --git a/src/main/java/com/mapconductor/longdo/LongdoMapViewController.kt b/src/main/java/com/mapconductor/longdo/LongdoMapViewController.kt index f168b3e..b43131d 100644 --- a/src/main/java/com/mapconductor/longdo/LongdoMapViewController.kt +++ b/src/main/java/com/mapconductor/longdo/LongdoMapViewController.kt @@ -31,6 +31,7 @@ import com.mapconductor.longdo.circle.LongdoCircleOverlayRenderer import com.mapconductor.longdo.groundimage.LongdoGroundImageController import com.mapconductor.longdo.groundimage.LongdoGroundImageOverlayRenderer import com.mapconductor.longdo.marker.LongdoMarkerTileRenderer +import com.mapconductor.longdo.marker.MarkerIdIndex import com.mapconductor.longdo.polygon.LongdoPolygonController import com.mapconductor.longdo.polygon.LongdoPolygonOverlayRenderer import com.mapconductor.longdo.polyline.LongdoPolylineController @@ -285,12 +286,17 @@ class LongdoMapViewController( } } - override fun hasMarker(state: MarkerState): Boolean = - if (markerTileRenderer?.markers?.isNotEmpty() == true) { - markerTileRenderer?.markers?.any { it.id == state.id } ?: false + override fun hasMarker(state: MarkerState): Boolean { + val tiled = markerTileRenderer?.markers + return if (tiled?.isNotEmpty() == true) { + tiledMarkerIds.contains(tiled, state.id) } else { - _markers.value.any { it.id == state.id } + overlayMarkerIds.contains(_markers.value, state.id) } + } + + private val tiledMarkerIds = MarkerIdIndex() + private val overlayMarkerIds = MarkerIdIndex() @Deprecated("Use MarkerState.onDragStart instead.") override fun setOnMarkerDragStart(listener: OnMarkerEventHandler?) = Unit diff --git a/src/main/java/com/mapconductor/longdo/marker/MarkerIdIndex.kt b/src/main/java/com/mapconductor/longdo/marker/MarkerIdIndex.kt new file mode 100644 index 0000000..65d7804 --- /dev/null +++ b/src/main/java/com/mapconductor/longdo/marker/MarkerIdIndex.kt @@ -0,0 +1,28 @@ +package com.mapconductor.longdo.marker + +import com.mapconductor.core.marker.MarkerState + +/** + * The ids of a marker list, rebuilt only when the list itself is replaced. + * + * `hasMarker` is asked once per marker after every composition (that is how + * `MarkerState.onAdded` finds out what is on the map) and on every in-place + * change. A linear search of the list made the first of those quadratic: 1e5 + * markers, 1e10 comparisons. + */ +internal class MarkerIdIndex { + private var list: List? = null + private var ids: Set = emptySet() + + @Synchronized + fun contains( + markers: List, + id: String, + ): Boolean { + if (markers !== list) { + ids = markers.mapTo(HashSet(markers.size * 2)) { it.id } + list = markers + } + return id in ids + } +} From 536443bf40b97ef85480551005692b8ee14b57e2 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Sat, 7 Nov 2026 16:23:33 -0800 Subject: [PATCH 2/4] Hold onAdded until the layers are on the page's map Longdo is a page in a WebView: LongdoMap.run queues the script and returns, and an add that finds the style not ready retries from the page. So onAdded came before the map had the overlay: Boston's 604 downtown polygons reported it 0.1 s after the hand-over and were all on the map 1.4 s later. The renderers' onAdd (polygon, polyline, circle, ground image), the app's raster layers, the marker tiles and the polygon tiles (now applied, not launched) return once the page's map has their layers (PageLayerWait). --- .../longdo/LongdoMapViewController.kt | 32 ++++- .../com/mapconductor/longdo/PageLayerWait.kt | 113 ++++++++++++++++++ .../circle/LongdoCircleOverlayRenderer.kt | 11 +- .../LongdoGroundImageOverlayRenderer.kt | 11 +- .../polygon/LongdoPolygonOverlayRenderer.kt | 11 +- .../polyline/LongdoPolylineOverlayRenderer.kt | 11 +- .../mapconductor/longdo/PageLayerWaitTest.kt | 109 +++++++++++++++++ 7 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/mapconductor/longdo/PageLayerWait.kt create mode 100644 src/test/java/com/mapconductor/longdo/PageLayerWaitTest.kt diff --git a/src/main/java/com/mapconductor/longdo/LongdoMapViewController.kt b/src/main/java/com/mapconductor/longdo/LongdoMapViewController.kt index b43131d..4f08217 100644 --- a/src/main/java/com/mapconductor/longdo/LongdoMapViewController.kt +++ b/src/main/java/com/mapconductor/longdo/LongdoMapViewController.kt @@ -42,7 +42,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * MapConductor コアと Longdo Map API3 SDK([LongdoMap])を橋渡しするマップコントローラ。 @@ -115,7 +117,9 @@ class LongdoMapViewController( PolygonTiler().apply { onTemplate = { template, tileSize -> if (template != null) { - mountRasterLayer(polygonTileRasterLayerState(template, tileSize)) + // Returns once the tile layer is on the page's map: the tiled + // polygons' onAdded is called on the return. + mountRasterLayerOnMap(polygonTileRasterLayerState(template, tileSize)) } else { unmountRasterLayer(POLYGON_TILE_LAYER_ID) } @@ -272,6 +276,8 @@ class LongdoMapViewController( override suspend fun compositionMarkers(data: List) { if (shouldUseMarkerLayer(data.size)) { renderTiledMarkers(data) + // The tiled markers' onAdded waits for their tile layer (see [PageLayerWait]). + markerTileState?.let { longdoMap.awaitPageLayers(listOf(rasterLayerId(it.id))) } } else { _markers.value = data } @@ -330,6 +336,9 @@ class LongdoMapViewController( appliedRasters.remove(id) } data.forEach { applyRaster(it) } + // Returns once the layers are on the page's map, so their onAdded comes after + // (see [PageLayerWait]). A source Longdo cannot show is not applied, nor waited for. + longdoMap.awaitPageLayers(data.filter { it.id in appliedRasters }.map { rasterLayerId(it.id) }) } /** @@ -369,6 +378,27 @@ class LongdoMapViewController( mainCoroutine.launch { applyRaster(state) } } + /** + * [mountRasterLayer] that returns once the layer is on the page's map, for + * the polygon tiles: the tiled polygons' `onAdded` is called on the return. + * [mountRasterLayer] only queues the script (and before `ready` only keeps + * the layer), so they were reported before the map had their tiles' layer. + * Applied here rather than launched, so that a replaced source is gone + * before the layer is looked for. + */ + internal suspend fun mountRasterLayerOnMap(state: RasterLayerState) { + withContext(Dispatchers.Main.immediate) { + if (mapReady) { + mountedRasterIds.add(state.id) + applyRaster(state) + } else { + mountRasterLayer(state) + mapLoaded.first { it } + } + } + longdoMap.awaitPageLayers(listOf(rasterLayerId(state.id))) + } + override fun unmountRasterLayer(id: String) { mountedRasterIds.remove(id) pendingMountedRasters.remove(id) diff --git a/src/main/java/com/mapconductor/longdo/PageLayerWait.kt b/src/main/java/com/mapconductor/longdo/PageLayerWait.kt new file mode 100644 index 0000000..8ca9e01 --- /dev/null +++ b/src/main/java/com/mapconductor/longdo/PageLayerWait.kt @@ -0,0 +1,113 @@ +package com.mapconductor.longdo + +import com.longdo.sdk3.LongdoMap +import android.util.Log +import kotlin.coroutines.resume +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.delay +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeoutOrNull + +/** + * Waits until the page's map has the layers an overlay was just given. + * + * Longdo is a page in a WebView: `LongdoMap.run` queues the script and returns, + * and an add that finds the style not ready retries from the page with + * `setTimeout`. + * So a renderer's `onAdd` returned before its layers were on the map, and + * the overlay's `onAdded` (asked for right after the hand-over) came while + * the map still showed nothing. Boston's 604 downtown polygons reported + * `onAdded` 0.1 s after the hand-over; the last of their layers reached the + * map 1.4 s after it (Lenovo TB520FU). + * Renderers call [await] at the end of `onAdd`, so `compositionXxx` + * returns, and `onAdded` is called, once the layers are there. + * + * Only that the layers are in the style is waited for, not their tiles or + * images (same as every other provider). + */ +internal object PageLayerWait { + /** The page's map, or a falsy value while there is none. */ + const val MAP_EXPR = "window.map && map.Renderer" + + /** + * Only a guard against a layer the page refused, which never shows up: + * a few hundred layers can take the page well over 10 s. + */ + const val TIMEOUT_MS = 60_000L + const val INTERVAL_MS = 100L + + /** + * Script that answers how many of [ids], from the front, the map has + * (as text, e.g. `"3"`); `"0"` while there is no map. + */ + fun script(ids: List): String { + val list = ids.joinToString(",", "[", "]") { quote(it) } + return "(function(){try{var m=$MAP_EXPR;if(!m)return '0';var ids=$list;" + + "for(var i=0;i, + timeoutMs: Long = TIMEOUT_MS, + intervalMs: Long = INTERVAL_MS, + evaluate: suspend (String) -> String?, + ): Boolean { + if (ids.isEmpty()) return true + var missing = ids + val done = + withTimeoutOrNull(timeoutMs) { + while (missing.isNotEmpty()) { + val present = + try { + parse(evaluate(script(missing))) + } catch (e: CancellationException) { + throw e + } catch (_: Exception) { + 0 + } + // Layers mostly arrive in the order they were added: ask only for the rest. + missing = missing.drop(present) + if (missing.isNotEmpty()) delay(intervalMs) + } + true + } ?: false + if (!done) { + runCatching { Log.w(TAG, "${missing.size} of ${ids.size} layers not on the map after $timeoutMs ms") } + } + return done + } + + private fun quote(text: String): String = + buildString { + append('"') + for (c in text) { + when { + c == '"' -> append("\\\"") + c == '\\' -> append("\\\\") + c < ' ' || c == '\u2028' || c == '\u2029' -> append(String.format("\\u%04x", c.code)) + else -> append(c) + } + } + append('"') + } + + private const val TAG = "PageLayerWait" +} + +/** [PageLayerWait.await] against this map's page. */ +internal suspend fun LongdoMap.awaitPageLayers(ids: List): Boolean = + PageLayerWait.await(ids) { js -> + suspendCancellableCoroutine { continuation -> + run(js) { result -> if (continuation.isActive) continuation.resume(result) } + } + } diff --git a/src/main/java/com/mapconductor/longdo/circle/LongdoCircleOverlayRenderer.kt b/src/main/java/com/mapconductor/longdo/circle/LongdoCircleOverlayRenderer.kt index fb4a939..41e173e 100644 --- a/src/main/java/com/mapconductor/longdo/circle/LongdoCircleOverlayRenderer.kt +++ b/src/main/java/com/mapconductor/longdo/circle/LongdoCircleOverlayRenderer.kt @@ -7,6 +7,7 @@ import com.mapconductor.core.circle.CircleOverlayRendererInterface import com.mapconductor.core.circle.CircleState import com.mapconductor.core.geometry.OverlayGeoJson import com.mapconductor.core.geometry.circleToRing +import com.mapconductor.longdo.awaitPageLayers import org.json.JSONObject /** @@ -21,9 +22,17 @@ import org.json.JSONObject class LongdoCircleOverlayRenderer( private val longdoMap: LongdoMap, ) : CircleOverlayRendererInterface { + /** + * Returns once the layers are on the page's map, so the circles' + * `onAdded` comes after they are (see [com.mapconductor.longdo.PageLayerWait]). + */ override suspend fun onAdd( data: List, - ): List = data.map { addCircle(it.state) } + ): List { + val handles = data.map { addCircle(it.state) } + longdoMap.awaitPageLayers(handles.filterNotNull().flatMap { listOf(it.fillLayerId, it.lineLayerId) }) + return handles + } override suspend fun onChange( data: List>, diff --git a/src/main/java/com/mapconductor/longdo/groundimage/LongdoGroundImageOverlayRenderer.kt b/src/main/java/com/mapconductor/longdo/groundimage/LongdoGroundImageOverlayRenderer.kt index 0157ef3..23dda0b 100644 --- a/src/main/java/com/mapconductor/longdo/groundimage/LongdoGroundImageOverlayRenderer.kt +++ b/src/main/java/com/mapconductor/longdo/groundimage/LongdoGroundImageOverlayRenderer.kt @@ -5,6 +5,7 @@ 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.awaitPageLayers import org.json.JSONObject import java.io.ByteArrayOutputStream import android.graphics.Bitmap @@ -29,9 +30,17 @@ import android.util.Base64 class LongdoGroundImageOverlayRenderer( private val longdoMap: LongdoMap, ) : GroundImageOverlayRendererInterface { + /** + * Returns once the layers are on the page's map, so the ground images' + * `onAdded` comes after they are (see [com.mapconductor.longdo.PageLayerWait]). + */ override suspend fun onAdd( data: List, - ): List = data.map { addGroundImage(it.state) } + ): List { + val handles = data.map { addGroundImage(it.state) } + longdoMap.awaitPageLayers(handles.mapNotNull { it?.layerId }) + return handles + } override suspend fun onChange( data: List>, diff --git a/src/main/java/com/mapconductor/longdo/polygon/LongdoPolygonOverlayRenderer.kt b/src/main/java/com/mapconductor/longdo/polygon/LongdoPolygonOverlayRenderer.kt index 7bf277f..28ad852 100644 --- a/src/main/java/com/mapconductor/longdo/polygon/LongdoPolygonOverlayRenderer.kt +++ b/src/main/java/com/mapconductor/longdo/polygon/LongdoPolygonOverlayRenderer.kt @@ -8,6 +8,7 @@ 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.awaitPageLayers import org.json.JSONObject /** @@ -21,9 +22,17 @@ import org.json.JSONObject class LongdoPolygonOverlayRenderer( private val longdoMap: LongdoMap, ) : PolygonOverlayRendererInterface { + /** + * Returns once the layers are on the page's map, so the polygons' + * `onAdded` comes after they are (see [com.mapconductor.longdo.PageLayerWait]). + */ override suspend fun onAdd( data: List, - ): List = data.map { addPolygon(it.state) } + ): List { + val handles = data.map { addPolygon(it.state) } + longdoMap.awaitPageLayers(handles.filterNotNull().flatMap { listOf(it.fillLayerId, it.lineLayerId) }) + return handles + } override suspend fun onChange( data: List>, diff --git a/src/main/java/com/mapconductor/longdo/polyline/LongdoPolylineOverlayRenderer.kt b/src/main/java/com/mapconductor/longdo/polyline/LongdoPolylineOverlayRenderer.kt index b4e08ea..afef2db 100644 --- a/src/main/java/com/mapconductor/longdo/polyline/LongdoPolylineOverlayRenderer.kt +++ b/src/main/java/com/mapconductor/longdo/polyline/LongdoPolylineOverlayRenderer.kt @@ -7,6 +7,7 @@ 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.awaitPageLayers import org.json.JSONObject /** @@ -19,9 +20,17 @@ import org.json.JSONObject class LongdoPolylineOverlayRenderer( private val longdoMap: LongdoMap, ) : PolylineOverlayRendererInterface { + /** + * Returns once the layers are on the page's map, so the polylines' + * `onAdded` comes after they are (see [com.mapconductor.longdo.PageLayerWait]). + */ override suspend fun onAdd( data: List, - ): List = data.map { addLine(it.state) } + ): List { + val handles = data.map { addLine(it.state) } + longdoMap.awaitPageLayers(handles.mapNotNull { it?.layerId }) + return handles + } override suspend fun onChange( data: List>, diff --git a/src/test/java/com/mapconductor/longdo/PageLayerWaitTest.kt b/src/test/java/com/mapconductor/longdo/PageLayerWaitTest.kt new file mode 100644 index 0000000..c8884ea --- /dev/null +++ b/src/test/java/com/mapconductor/longdo/PageLayerWaitTest.kt @@ -0,0 +1,109 @@ +package com.mapconductor.longdo + +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * [PageLayerWait] returns only once the page's map has every layer, which is + * what holds an overlay's `onAdded` until it is really on the map. + * + * The page is faked: [FakePage] answers the script the way the real one does, + * from a set of layer ids that tests add to between polls. + */ +class PageLayerWaitTest { + /** Answers [PageLayerWait.script] from [layers], counting the calls. */ + private class FakePage { + val layers = mutableSetOf() + val asked = mutableListOf>() + var onAsk: (Int) -> Unit = {} + + fun evaluate(js: String): String { + val ids = + Regex("var ids=\\[(.*?)\\];") + .find(js)!! + .groupValues[1] + .split(',') + .filter { it.isNotEmpty() } + .map { it.trim('"') } + asked += ids + onAsk(asked.size) + val present = ids.takeWhile { it in layers }.size + // evaluateJavascript hands a string value back JSON-quoted. + return "\"$present\"" + } + } + + @Test + fun returnsAtOnceForNoLayers() = + runBlocking { + var calls = 0 + assertTrue( + PageLayerWait.await(emptyList()) { + calls++ + "0" + }, + ) + assertEquals(0, calls) + } + + @Test + fun waitsUntilEveryLayerIsOnTheMap() = + runBlocking { + val page = FakePage() + // The layers arrive over three polls, as the SDK's queue empties. + page.onAsk = { n -> + if (n == 2) page.layers += "a" + if (n == 3) page.layers += listOf("b", "c") + } + val done = PageLayerWait.await(listOf("a", "b", "c"), intervalMs = 1) { page.evaluate(it) } + assertTrue(done) + assertEquals(listOf("a", "b", "c"), page.layers.sorted()) + // Asks again only for the layers still missing. + assertEquals(listOf(listOf("a", "b", "c"), listOf("a", "b", "c"), listOf("b", "c")), page.asked) + } + + @Test + fun givesUpAfterTheTimeout() = + runBlocking { + val page = FakePage() + page.layers += "a" + val done = PageLayerWait.await(listOf("a", "never"), timeoutMs = 100, intervalMs = 5) { page.evaluate(it) } + assertFalse(done) + assertTrue(page.asked.size > 1) + } + + @Test + fun aFailedAskCountsAsNothingYet() = + runBlocking { + var calls = 0 + val done = + PageLayerWait.await(listOf("a"), intervalMs = 1) { + calls++ + when (calls) { + 1 -> throw IllegalStateException("bridge not loaded") + 2 -> null + else -> "\"1\"" + } + } + assertTrue(done) + assertEquals(3, calls) + } + + @Test + fun parseReadsTheCountQuotedOrNot() { + assertEquals(3, PageLayerWait.parse("\"3\"")) + assertEquals(3, PageLayerWait.parse("3")) + assertEquals(0, PageLayerWait.parse(null)) + assertEquals(0, PageLayerWait.parse("null")) + } + + @Test + fun idsAreQuotedForTheScript() { + val js = PageLayerWait.script(listOf("polygon-fill-a\"b", "x\\y")) + assertTrue(js, js.contains("[\"polygon-fill-a\\\"b\",\"x\\\\y\"]")) + assertTrue(js.contains(PageLayerWait.MAP_EXPR)) + } +} From 9a148abfea4b6d5217f1fce94950623761ef457a Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Mon, 9 Nov 2026 06:59:45 -0800 Subject: [PATCH 3/4] Update the API baseline Additions only (this branch had not run apiDump); in core, one member moved. --- api/android-for-longdo.api | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/api/android-for-longdo.api b/api/android-for-longdo.api index bbab706..c590a2d 100644 --- a/api/android-for-longdo.api +++ b/api/android-for-longdo.api @@ -234,6 +234,18 @@ public final class com.mapconductor.longdo.NativeCamera { public int hashCode(); public boolean equals(java.lang.Object); } +public final class com.mapconductor.longdo.PageLayerWait { + public static final com.mapconductor.longdo.PageLayerWait INSTANCE; + public static final java.lang.String MAP_EXPR; + public static final long TIMEOUT_MS; + public static final long INTERVAL_MS; + public final java.lang.String script(java.util.List); + public final int parse(java.lang.String); + public final java.lang.Object await(java.util.List, long, long, kotlin.jvm.functions.Function2, ? extends java.lang.Object>, kotlin.coroutines.Continuation); +} +public final class com.mapconductor.longdo.PageLayerWaitKt { + public static final java.lang.Object awaitPageLayers(com.longdo.sdk3.LongdoMap, java.util.List, kotlin.coroutines.Continuation); +} public final class com.mapconductor.longdo.circle.LongdoCircleController extends com.mapconductor.core.circle.CircleController { public com.mapconductor.longdo.circle.LongdoCircleController(com.mapconductor.longdo.circle.LongdoCircleOverlayRenderer, com.mapconductor.core.circle.CircleManagerInterface); public com.mapconductor.longdo.circle.LongdoCircleController(com.mapconductor.longdo.circle.LongdoCircleOverlayRenderer, com.mapconductor.core.circle.CircleManagerInterface, int, kotlin.jvm.internal.DefaultConstructorMarker); @@ -354,6 +366,10 @@ public final class com.mapconductor.longdo.marker.LongdoMarkerTileRenderer { } final class com.mapconductor.longdo.marker.LongdoMarkerTileRenderer$Companion { } +public final class com.mapconductor.longdo.marker.MarkerIdIndex { + public com.mapconductor.longdo.marker.MarkerIdIndex(); + public final synchronized boolean contains(java.util.List, java.lang.String); +} public final class com.mapconductor.longdo.polygon.LongdoPolygonController extends com.mapconductor.core.polygon.PolygonController { public com.mapconductor.longdo.polygon.LongdoPolygonController(com.mapconductor.longdo.polygon.LongdoPolygonOverlayRenderer, com.mapconductor.core.polygon.PolygonManagerInterface); public com.mapconductor.longdo.polygon.LongdoPolygonController(com.mapconductor.longdo.polygon.LongdoPolygonOverlayRenderer, com.mapconductor.core.polygon.PolygonManagerInterface, int, kotlin.jvm.internal.DefaultConstructorMarker); From 2c722a80f1d58175d240404636a837bdda333439 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Mon, 9 Nov 2026 09:38:06 -0800 Subject: [PATCH 4/4] Draw large polyline sets as raster tiles (PolylineTiler) The polyline controller gets a PolylineTiler, shown through the same raster layer path as the polygon tiles. Stacking key (Polyline, Int.MIN_VALUE): over circles, under native polylines and markers. Whatever redraws from the polyline manager skips tiled entities (no native line). --- api/android-for-longdo.api | 1 + .../longdo/LongdoMapViewController.kt | 17 +++++++++++++++++ .../longdo/LongdoMapViewControllerRaster.kt | 3 +++ .../longdo/LongdoOverlayStacking.kt | 3 +++ .../longdo/polyline/LongdoPolylineController.kt | 4 ++-- 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/api/android-for-longdo.api b/api/android-for-longdo.api index c590a2d..9a4b484 100644 --- a/api/android-for-longdo.api +++ b/api/android-for-longdo.api @@ -195,6 +195,7 @@ 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 addPolygonTileLayerJs(java.lang.String); + public final java.lang.String addPolylineTileLayerJs(java.lang.String); public final java.lang.String addMarkerTileLayerJs(java.lang.String); } public final class com.mapconductor.longdo.LongdoOverlayState { diff --git a/src/main/java/com/mapconductor/longdo/LongdoMapViewController.kt b/src/main/java/com/mapconductor/longdo/LongdoMapViewController.kt index 0a72e7d..3ea96ad 100644 --- a/src/main/java/com/mapconductor/longdo/LongdoMapViewController.kt +++ b/src/main/java/com/mapconductor/longdo/LongdoMapViewController.kt @@ -22,8 +22,11 @@ import com.mapconductor.core.polygon.PolygonCapableInterface import com.mapconductor.core.polygon.PolygonTiler import com.mapconductor.core.polygon.polygonTileRasterLayerState import com.mapconductor.core.polyline.OnPolylineEventHandler +import com.mapconductor.core.polyline.POLYLINE_TILE_LAYER_ID import com.mapconductor.core.polyline.PolylineCapableInterface import com.mapconductor.core.polyline.PolylineState +import com.mapconductor.core.polyline.PolylineTiler +import com.mapconductor.core.polyline.polylineTileRasterLayerState import com.mapconductor.core.raster.RasterLayerCapableInterface import com.mapconductor.core.raster.RasterLayerState import com.mapconductor.longdo.circle.LongdoCircleController @@ -125,6 +128,20 @@ class LongdoMapViewController( } } } + + // Many polylines too: the same raster layer path, in the polyline band. + polylineController.tiler = + PolylineTiler().apply { + onTemplate = { template, tileSize -> + if (template != null) { + // Returns once the tile layer is on the page's map: the tiled + // polylines' onAdded is called on the return. + mountRasterLayerOnMap(polylineTileRasterLayerState(template, tileSize)) + } else { + unmountRasterLayer(POLYLINE_TILE_LAYER_ID) + } + } + } // ホルダーの投影にカメラを供給する。ホルダーはコントローラより先に作られるので // ここで繋ぐ。これが無いと toScreenOffset が常に null を返し、InfoBubble と // マーカー追従が理由も出ずに落ちる。 diff --git a/src/main/java/com/mapconductor/longdo/LongdoMapViewControllerRaster.kt b/src/main/java/com/mapconductor/longdo/LongdoMapViewControllerRaster.kt index 2429862..0e84dad 100644 --- a/src/main/java/com/mapconductor/longdo/LongdoMapViewControllerRaster.kt +++ b/src/main/java/com/mapconductor/longdo/LongdoMapViewControllerRaster.kt @@ -2,6 +2,7 @@ package com.mapconductor.longdo import com.mapconductor.core.controller.OverlayKind import com.mapconductor.core.polygon.POLYGON_TILE_LAYER_ID +import com.mapconductor.core.polyline.POLYLINE_TILE_LAYER_ID import com.mapconductor.core.raster.RasterHeaderRuleSet import com.mapconductor.core.raster.RasterLayerSource import com.mapconductor.core.raster.RasterLayerState @@ -44,6 +45,8 @@ private fun rasterAddLayerJs( ): String = if (state.id == POLYGON_TILE_LAYER_ID) { LongdoOverlayStacking.addPolygonTileLayerJs(layerSpec) + } else if (state.id == POLYLINE_TILE_LAYER_ID) { + LongdoOverlayStacking.addPolylineTileLayerJs(layerSpec) } else { LongdoOverlayStacking.addLayerJs(layerSpec, OverlayKind.RasterLayer, state.zIndex) } diff --git a/src/main/java/com/mapconductor/longdo/LongdoOverlayStacking.kt b/src/main/java/com/mapconductor/longdo/LongdoOverlayStacking.kt index b561c02..1be98d4 100644 --- a/src/main/java/com/mapconductor/longdo/LongdoOverlayStacking.kt +++ b/src/main/java/com/mapconductor/longdo/LongdoOverlayStacking.kt @@ -33,6 +33,9 @@ internal object LongdoOverlayStacking { /** ポリゴンのタイル: ポリゴンの帯の一番下(画像になったポリゴンであって、地図の画像ではない)。 */ fun addPolygonTileLayerJs(layerSpec: String): String = addLayerJs(layerSpec, OverlayKind.Polygon, Int.MIN_VALUE) + /** ポリラインのタイル: ポリラインの帯の一番下(サークルの上、ネイティブのポリラインの下)。 */ + fun addPolylineTileLayerJs(layerSpec: String): String = addLayerJs(layerSpec, OverlayKind.Polyline, Int.MIN_VALUE) + /** マーカーのタイル: マーカーの帯の一番下。 */ fun addMarkerTileLayerJs(layerSpec: String): String = addLayerJs(layerSpec, OverlayKind.Marker, Int.MIN_VALUE) } diff --git a/src/main/java/com/mapconductor/longdo/polyline/LongdoPolylineController.kt b/src/main/java/com/mapconductor/longdo/polyline/LongdoPolylineController.kt index e503349..dff3eef 100644 --- a/src/main/java/com/mapconductor/longdo/polyline/LongdoPolylineController.kt +++ b/src/main/java/com/mapconductor/longdo/polyline/LongdoPolylineController.kt @@ -14,8 +14,8 @@ class LongdoPolylineController( override val renderer: LongdoPolylineOverlayRenderer, polylineManager: PolylineManagerInterface = PolylineManager(), ) : PolylineController(polylineManager, renderer) { - /** 既知のポリラインを再適用する(地図 `ready` 後の復元用)。 */ + /** 既知のポリラインを再適用する(地図 `ready` 後の復元用)。タイルで描く線(ハンドル無し)は飛ばす。 */ fun reapply() { - renderer.reapply(polylineManager.allEntities().map { it.polyline }) + renderer.reapply(polylineManager.allEntities().mapNotNull { it.polyline as LongdoPolylineHandle? }) } }