Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions api/android-for-maptiler.api
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ public final class com.mapconductor.maptiler.MapTilerOverlayStacking {
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 getPOLYGON_TILE();
public final com.mapconductor.core.controller.OverlayStacking$Key getPOLYLINE_TILE();
public final void add(com.maptiler.maptilersdk.map.style.MTStyle, com.maptiler.maptilersdk.map.style.layer.MTLayer, com.mapconductor.core.controller.OverlayStacking$Key);
public final void remove(com.maptiler.maptilersdk.map.style.MTStyle, java.lang.String);
public final void onIdle(com.maptiler.maptilersdk.map.style.MTStyle);
Expand Down Expand Up @@ -200,6 +201,23 @@ public final class com.mapconductor.maptiler.MapTilerViewStateImplKt {
}
public interface com.mapconductor.maptiler.MapTilerViewStateInterface extends com.mapconductor.core.map.MapViewStateInterface<com.mapconductor.maptiler.MapTilerMapDesignTypeInterface> {
}
public final class com.mapconductor.maptiler.PageLayerWait {
public static final com.mapconductor.maptiler.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<java.lang.String>);
public final int parse(java.lang.String);
public final java.lang.Object await(java.util.List<java.lang.String>, long, long, kotlin.jvm.functions.Function2<? super java.lang.String, ? super kotlin.coroutines.Continuation<? super java.lang.String>, ? extends java.lang.Object>, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
}
public final class com.mapconductor.maptiler.PageLayerWaitKt {
public static final java.lang.Object awaitPageLayers(com.maptiler.maptilersdk.map.MTMapViewController, java.util.List<java.lang.String>, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
}
final class com.mapconductor.maptiler.PageScript implements com.maptiler.maptilersdk.bridge.MTCommand {
public com.mapconductor.maptiler.PageScript(java.lang.String);
public boolean isPrimitiveReturnType();
public java.lang.String toJS();
}
public final class com.mapconductor.maptiler.circle.MapTilerCircleController extends com.mapconductor.core.circle.CircleController<com.mapconductor.maptiler.circle.MapTilerCircleHandle> {
public com.mapconductor.maptiler.circle.MapTilerCircleController(com.mapconductor.maptiler.circle.MapTilerCircleOverlayRenderer, com.mapconductor.core.circle.CircleManagerInterface<com.mapconductor.maptiler.circle.MapTilerCircleHandle>);
public com.mapconductor.maptiler.circle.MapTilerCircleController(com.mapconductor.maptiler.circle.MapTilerCircleOverlayRenderer, com.mapconductor.core.circle.CircleManagerInterface, int, kotlin.jvm.internal.DefaultConstructorMarker);
Expand Down Expand Up @@ -313,6 +331,10 @@ public final class com.mapconductor.maptiler.marker.MapTilerMarkerTileRenderer {
}
final class com.mapconductor.maptiler.marker.MapTilerMarkerTileRenderer$Companion {
}
public final class com.mapconductor.maptiler.marker.MarkerIdIndex {
public com.mapconductor.maptiler.marker.MarkerIdIndex();
public final synchronized boolean contains(java.util.List<com.mapconductor.core.marker.MarkerState>, java.lang.String);
}
public final class com.mapconductor.maptiler.polygon.MapTilerPolygonController extends com.mapconductor.core.polygon.PolygonController<com.mapconductor.maptiler.polygon.MapTilerPolygonHandle> {
public com.mapconductor.maptiler.polygon.MapTilerPolygonController(com.mapconductor.maptiler.polygon.MapTilerPolygonOverlayRenderer, com.mapconductor.core.polygon.PolygonManagerInterface<com.mapconductor.maptiler.polygon.MapTilerPolygonHandle>);
public com.mapconductor.maptiler.polygon.MapTilerPolygonController(com.mapconductor.maptiler.polygon.MapTilerPolygonOverlayRenderer, com.mapconductor.core.polygon.PolygonManagerInterface, int, kotlin.jvm.internal.DefaultConstructorMarker);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@ 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.maptiler.circle.MapTilerCircleController
import com.mapconductor.maptiler.circle.MapTilerCircleOverlayRenderer
import com.mapconductor.maptiler.groundimage.MapTilerGroundImageController
import com.mapconductor.maptiler.groundimage.MapTilerGroundImageOverlayRenderer
import com.mapconductor.maptiler.marker.MapTilerMarkerTileRenderer
import com.mapconductor.maptiler.marker.MarkerIdIndex
import com.mapconductor.maptiler.polygon.MapTilerPolygonController
import com.mapconductor.maptiler.polygon.MapTilerPolygonOverlayRenderer
import com.mapconductor.maptiler.polyline.MapTilerPolylineController
Expand Down Expand Up @@ -206,8 +210,11 @@ class MapTilerMapViewController(
}

override fun hasMarker(state: MarkerState): Boolean =
_markers.value.any { it.id == state.id } ||
(markerTileRenderer?.markers?.any { it.id == state.id } ?: false)
overlayMarkerIds.contains(_markers.value, state.id) ||
(markerTileRenderer?.markers?.let { tiledMarkerIds.contains(it, state.id) } ?: false)

private val tiledMarkerIds = MarkerIdIndex()
private val overlayMarkerIds = MarkerIdIndex()

// --- Marker clustering (android-marker-clustering) 連携 ---

Expand Down Expand Up @@ -332,6 +339,18 @@ class MapTilerMapViewController(
}
}
}

// Many polylines too: the same raster layer path, in the polyline band.
polylineController.tiler =
PolylineTiler().apply {
onTemplate = { template, tileSize ->
if (template != null) {
rasterLayerController.upsert(polylineTileRasterLayerState(template, tileSize))
} else {
rasterLayerController.removeById(POLYLINE_TILE_LAYER_ID)
}
}
}
// ホルダーの同期投影にカメラを供給する。ホルダーはコントローラより先に作られるので
// ここで繋ぐ。これが無いと toScreenOffset が常に null を返し、InfoBubble と
// マーカー追従が理由も出ずに落ちる(android-for-longdo と同じ)。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ internal object MapTilerOverlayStacking {
/** ポリゴンのタイル: ポリゴンの帯の一番下。 */
val POLYGON_TILE = key(OverlayKind.Polygon, Int.MIN_VALUE)

/** ポリラインのタイル(ラスター): ポリラインの帯の一番下(サークルの上、ネイティブのポリラインの下)。 */
val POLYLINE_TILE = key(OverlayKind.Polyline, Int.MIN_VALUE)

fun add(
style: MTStyle,
layer: MTLayer,
Expand Down
124 changes: 124 additions & 0 deletions src/main/java/com/mapconductor/maptiler/PageLayerWait.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.mapconductor.maptiler

import com.maptiler.maptilersdk.bridge.MTBridgeReturnType
import com.maptiler.maptilersdk.bridge.MTCommand
import com.maptiler.maptilersdk.map.MTMapViewController
import android.util.Log
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeoutOrNull

/**
* Waits until the page's map has the layers an overlay was just given.
*
* The MapTiler SDK hands a layer to the page later than `MTStyle.addLayer`
* returns: the call launches a coroutine, and a layer whose source has not
* loaded yet waits in the SDK's queue until the map's next `idle`.
* 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 15 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"

/**
* 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>): 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<ids.length;i++){if(!m.getLayer(ids[i]))return String(i);}" +
"return String(ids.length);}catch(e){return '0';}})()"
}

/** Reads [script]'s answer from what the bridge returned (it may come back JSON-quoted). */
fun parse(result: String?): Int = result?.trim()?.trim('"')?.toIntOrNull() ?: 0

/**
* Returns once the map has every one of [ids], asking through [evaluate]
* every [intervalMs]. Gives up after [timeoutMs] (returns false and logs):
* a layer the page refused never shows up, and the overlay is reported
* then rather than never.
*/
suspend fun await(
ids: List<String>,
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 MTMapViewController.awaitPageLayers(ids: List<String>): Boolean =
PageLayerWait.await(ids) { js ->
when (val result = execute(PageScript(js))) {
is MTBridgeReturnType.StringValue -> result.value
is MTBridgeReturnType.DoubleValue -> result.value.toInt().toString()
else -> null
}
}

/** A script whose (string) value comes back. */
private class PageScript(
private val js: String,
) : MTCommand {
override val isPrimitiveReturnType: Boolean = true

override fun toJS(): String = js
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.mapconductor.core.controller.OverlayKind
import com.mapconductor.core.geometry.OverlayGeoJson
import com.mapconductor.core.geometry.circleToRing
import com.mapconductor.maptiler.MapTilerOverlayStacking
import com.mapconductor.maptiler.awaitPageLayers
import com.mapconductor.maptiler.removeLayerAndForget
import com.mapconductor.maptiler.removeSourceAndForget
import com.maptiler.maptilersdk.map.MTMapViewController
Expand All @@ -34,9 +35,19 @@ import android.util.Log
class MapTilerCircleOverlayRenderer(
private val mtController: MTMapViewController,
) : CircleOverlayRendererInterface<MapTilerCircleHandle> {
/**
* Returns once the layers are on the page's map, so the circles'
* `onAdded` comes after they are (see [com.mapconductor.maptiler.PageLayerWait]).
*/
override suspend fun onAdd(
data: List<CircleOverlayRendererInterface.AddParamsInterface>,
): List<MapTilerCircleHandle?> = data.map { addCircle(it.state) }
): List<MapTilerCircleHandle?> {
val handles = data.map { addCircle(it.state) }
if (mtController.style != null) {
mtController.awaitPageLayers(handles.filterNotNull().flatMap { listOf(it.fillLayerId, it.lineLayerId) })
}
return handles
}

override suspend fun onChange(
data: List<CircleOverlayRendererInterface.ChangeParamsInterface<MapTilerCircleHandle>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.mapconductor.core.groundimage.GroundImageEntityInterface
import com.mapconductor.core.groundimage.GroundImageOverlayRendererInterface
import com.mapconductor.core.groundimage.GroundImageState
import com.mapconductor.maptiler.MapTilerOverlayStacking
import com.mapconductor.maptiler.awaitPageLayers
import com.mapconductor.maptiler.removeLayerAndForget
import com.mapconductor.maptiler.removeSourceAndForget
import com.maptiler.maptilersdk.map.LngLat
Expand Down Expand Up @@ -36,9 +37,17 @@ import android.util.Log
class MapTilerGroundImageOverlayRenderer(
private val mtController: MTMapViewController,
) : GroundImageOverlayRendererInterface<MapTilerGroundImageHandle> {
/**
* Returns once the layers are on the page's map, so the ground images'
* `onAdded` comes after they are (see [com.mapconductor.maptiler.PageLayerWait]).
*/
override suspend fun onAdd(
data: List<GroundImageOverlayRendererInterface.AddParamsInterface>,
): List<MapTilerGroundImageHandle?> = data.map { addGroundImage(it.state) }
): List<MapTilerGroundImageHandle?> {
val handles = data.map { addGroundImage(it.state) }
if (mtController.style != null) mtController.awaitPageLayers(handles.mapNotNull { it?.layerId })
return handles
}

override suspend fun onChange(
data: List<GroundImageOverlayRendererInterface.ChangeParamsInterface<MapTilerGroundImageHandle>>,
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/com/mapconductor/maptiler/marker/MarkerIdIndex.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.mapconductor.maptiler.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<MarkerState>? = null
private var ids: Set<String> = emptySet()

@Synchronized
fun contains(
markers: List<MarkerState>,
id: String,
): Boolean {
if (markers !== list) {
ids = markers.mapTo(HashSet(markers.size * 2)) { it.id }
list = markers
}
return id in ids
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.mapconductor.core.polygon.PolygonOverlayRendererInterface
import com.mapconductor.core.polygon.PolygonState
import com.mapconductor.core.polygon.unionHoles
import com.mapconductor.maptiler.MapTilerOverlayStacking
import com.mapconductor.maptiler.awaitPageLayers
import com.mapconductor.maptiler.removeLayerAndForget
import com.mapconductor.maptiler.removeSourceAndForget
import com.maptiler.maptilersdk.map.MTMapViewController
Expand All @@ -34,9 +35,19 @@ import android.util.Log
class MapTilerPolygonOverlayRenderer(
private val mtController: MTMapViewController,
) : PolygonOverlayRendererInterface<MapTilerPolygonHandle> {
/**
* Returns once the layers are on the page's map, so the polygons'
* `onAdded` comes after they are (see [com.mapconductor.maptiler.PageLayerWait]).
*/
override suspend fun onAdd(
data: List<PolygonOverlayRendererInterface.AddParamsInterface>,
): List<MapTilerPolygonHandle?> = data.map { addPolygon(it.state) }
): List<MapTilerPolygonHandle?> {
val handles = data.map { addPolygon(it.state) }
if (mtController.style != null) {
mtController.awaitPageLayers(handles.filterNotNull().flatMap { listOf(it.fillLayerId, it.lineLayerId) })
}
return handles
}

override suspend fun onChange(
data: List<PolygonOverlayRendererInterface.ChangeParamsInterface<MapTilerPolygonHandle>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class MapTilerPolylineController(
* 既知のポリラインを再適用する(地図 `ready` 後の復元用)。
*/
fun reapply() {
renderer.reapply(polylineManager.allEntities().map { it.polyline })
// タイルで描く線(ハンドル無し)は飛ばす。
renderer.reapply(polylineManager.allEntities().mapNotNull { it.polyline as MapTilerPolylineHandle? })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.mapconductor.core.polyline.PolylineEntityInterface
import com.mapconductor.core.polyline.PolylineOverlayRendererInterface
import com.mapconductor.core.polyline.PolylineState
import com.mapconductor.maptiler.MapTilerOverlayStacking
import com.mapconductor.maptiler.awaitPageLayers
import com.mapconductor.maptiler.removeLayerAndForget
import com.mapconductor.maptiler.removeSourceAndForget
import com.maptiler.maptilersdk.map.MTMapViewController
Expand All @@ -32,9 +33,17 @@ import android.util.Log
class MapTilerPolylineOverlayRenderer(
private val mtController: MTMapViewController,
) : PolylineOverlayRendererInterface<MapTilerPolylineHandle> {
/**
* Returns once the layers are on the page's map, so the polylines'
* `onAdded` comes after they are (see [com.mapconductor.maptiler.PageLayerWait]).
*/
override suspend fun onAdd(
data: List<PolylineOverlayRendererInterface.AddParamsInterface>,
): List<MapTilerPolylineHandle?> = data.map { addLine(it.state) }
): List<MapTilerPolylineHandle?> {
val handles = data.map { addLine(it.state) }
if (mtController.style != null) mtController.awaitPageLayers(handles.mapNotNull { it?.layerId })
return handles
}

override suspend fun onChange(
data: List<PolylineOverlayRendererInterface.ChangeParamsInterface<MapTilerPolylineHandle>>,
Expand Down
Loading
Loading