diff --git a/api/android-for-tomtom.api b/api/android-for-tomtom.api index 1983248..940e2ea 100644 --- a/api/android-for-tomtom.api +++ b/api/android-for-tomtom.api @@ -86,6 +86,8 @@ public final class com.mapconductor.tomtom.TomTomMapViewController extends com.m public void setMapDesignTypeChangeListener(kotlin.jvm.functions.Function1, kotlin.Unit>); public java.lang.Object compositionRasterLayers(java.util.List, kotlin.coroutines.Continuation); public java.lang.Object updateRasterLayer(com.mapconductor.core.raster.RasterLayerState, kotlin.coroutines.Continuation); + public void mountRasterLayer(com.mapconductor.core.raster.RasterLayerState); + public void unmountRasterLayer(java.lang.String); public boolean hasRasterLayer(com.mapconductor.core.raster.RasterLayerState); public java.lang.Object upsertRasterLayer(java.lang.String, java.lang.String, double, int, java.lang.Integer, java.lang.Integer, kotlin.coroutines.Continuation); public java.lang.Object removeRasterLayer(java.lang.String, kotlin.coroutines.Continuation); @@ -150,7 +152,7 @@ public final class com.mapconductor.tomtom.TomTomMapViewHolder implements com.ma public java.lang.Object getMap(); } public final class com.mapconductor.tomtom.TomTomMapViewKt { - public static final void TomTomMapView(com.mapconductor.tomtom.TomTomMapViewState, androidx.compose.ui.Modifier, com.mapconductor.core.marker.MarkerTilingOptions, com.mapconductor.core.map.CameraRestriction, kotlin.jvm.functions.Function2, ? extends java.lang.Object>, kotlin.jvm.functions.Function1, kotlin.Unit>, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer, int, int, int); + public static final void TomTomMapView(com.mapconductor.tomtom.TomTomMapViewState, androidx.compose.ui.Modifier, com.mapconductor.core.marker.MarkerTilingOptions, com.mapconductor.core.map.CameraRestriction, kotlin.jvm.functions.Function2, ? extends java.lang.Object>, kotlin.jvm.functions.Function1, kotlin.Unit>, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, com.mapconductor.core.map.MapViewStyle, kotlin.jvm.functions.Function1, kotlin.Unit>, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer, int, int, int); public static final com.mapconductor.tomtom.TomTomMapViewController createTomTomMapViewController(com.mapconductor.tomtom.TomTomMapViewHolder, com.mapconductor.core.marker.MarkerTilingOptions, com.mapconductor.core.map.MutableMapServiceRegistry); } public final class com.mapconductor.tomtom.TomTomMapViewSaver extends com.mapconductor.compose.map.BaseMapViewSaver { diff --git a/src/main/java/com/mapconductor/tomtom/TomTomMapDesign.kt b/src/main/java/com/mapconductor/tomtom/TomTomMapDesign.kt index b0022ef..372ae98 100644 --- a/src/main/java/com/mapconductor/tomtom/TomTomMapDesign.kt +++ b/src/main/java/com/mapconductor/tomtom/TomTomMapDesign.kt @@ -1,11 +1,11 @@ package com.mapconductor.tomtom import com.mapconductor.core.map.AttributionRule +import com.mapconductor.core.map.BlankMapStyle import com.mapconductor.core.map.MapDesignTypeInterface import com.tomtom.sdk.map.display.style.StandardStyles -import android.net.Uri -import com.mapconductor.core.map.BlankMapStyle import com.tomtom.sdk.map.display.style.StyleDescriptor +import android.net.Uri typealias TomTomMapDesignType = MapDesignTypeInterface diff --git a/src/main/java/com/mapconductor/tomtom/TomTomMapView.kt b/src/main/java/com/mapconductor/tomtom/TomTomMapView.kt index 9b38301..cd55210 100644 --- a/src/main/java/com/mapconductor/tomtom/TomTomMapView.kt +++ b/src/main/java/com/mapconductor/tomtom/TomTomMapView.kt @@ -18,6 +18,7 @@ import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapCameraPositionInterface import com.mapconductor.core.map.MapCapability import com.mapconductor.core.map.MapCapabilityStatus +import com.mapconductor.core.map.MapViewStyle import com.mapconductor.core.map.MutableMapServiceRegistry import com.mapconductor.core.marker.MarkerEventControllerInterface import com.mapconductor.core.marker.MarkerOverlayRendererInterface @@ -54,6 +55,15 @@ fun TomTomMapView( onCameraMoveStart: OnCameraMoveHandler? = null, onCameraMove: OnCameraMoveHandler? = null, onCameraMoveEnd: OnCameraMoveHandler? = null, + /** + * How the map looks, when the app states it rather than naming a design. + * + * A vector style *is* the basemap. `com.mapconductor:vectorstyle` builds + * one; what happens underneath depends on this backend and the app does + * not have to know. + */ + style: MapViewStyle? = null, + onStyleDiagnostics: ((List) -> Unit)? = null, content: (@Composable TomTomMapViewScope.() -> Unit)? = null, ) { val scope = remember { TomTomMapViewScope() } @@ -182,6 +192,8 @@ fun TomTomMapView( // 地図が生成されない)。初期化が必要なのは検索/ルーティング等のナビ機能のみ。 sdkInitialize?.invoke(context) ?: true }, + style = style, + onStyleDiagnostics = onStyleDiagnostics, content = content, ) } diff --git a/src/main/java/com/mapconductor/tomtom/TomTomMapViewController.kt b/src/main/java/com/mapconductor/tomtom/TomTomMapViewController.kt index d14057f..e8dd877 100644 --- a/src/main/java/com/mapconductor/tomtom/TomTomMapViewController.kt +++ b/src/main/java/com/mapconductor/tomtom/TomTomMapViewController.kt @@ -364,6 +364,23 @@ class TomTomMapViewController internal constructor( applyPublicRasterLayer(state) } + /** + * content の外から載せるラスタ(マーカータイル、ベクタースタイルのラスタ化)。 + * + * 他プロバイダは [com.mapconductor.core.raster.RasterLayerController] を + * オーバーレイコントローラとして登録してあるので基底の実装で足りるが、 + * TomTom はラスタを自前の合成スタイルへ畳み込むのでそれが無い。 + * **`publicRasterLayerIds` に入れないこと** — 入れると次の composition が + * 「アプリが宣言していないレイヤ」として掃除してしまう。 + */ + override fun mountRasterLayer(state: RasterLayerState) { + mainCoroutine.launch { applyPublicRasterLayer(state) } + } + + override fun unmountRasterLayer(id: String) { + mainCoroutine.launch { removeRasterLayer(id) } + } + override fun hasRasterLayer(state: RasterLayerState): Boolean = composedRasterLayers.containsKey(state.id) override suspend fun upsertRasterLayer( diff --git a/src/main/java/com/mapconductor/tomtom/raster/TomTomStyleComposer.kt b/src/main/java/com/mapconductor/tomtom/raster/TomTomStyleComposer.kt index 8e30de1..ae86578 100644 --- a/src/main/java/com/mapconductor/tomtom/raster/TomTomStyleComposer.kt +++ b/src/main/java/com/mapconductor/tomtom/raster/TomTomStyleComposer.kt @@ -47,6 +47,13 @@ object TomTomStyleComposer { /** 可視ベースに使う TomTom ラスタ地図タイル(basic/main)テンプレート(%s = API キー)。 */ private const val BASE_RASTER_TILES = "https://api.tomtom.com/map/1/tile/basic/main/{z}/{x}/{y}.png?key=%s" + /** + * The browsing style's skeleton, kept so a map with no network still has + * the SDK layers it insists on. Private cache only: the JSON carries the + * API key. + */ + private const val BASE_STYLE_CACHE_FILE = "tomtom_base_style.json" + private const val TILE_SIZE = 256 /** 合成スタイルに載せる自前ラスタレイヤー1枚分の指定。 */ @@ -79,11 +86,22 @@ object TomTomStyleComposer { withoutBasemap: Boolean = false, ): Uri? = withContext(Dispatchers.IO) { + // The SDK will not take a style that has none of its own layers + // (it reverts to its default), so every composed style is built + // on the browsing style's skeleton -- which lives on + // api.tomtom.com. Keeping the last copy on disk is what lets the + // map come up at all with no network: the skeleton is reused and + // only the raster layers on top, which are served from the + // device, are drawn. + val cachedBase = File(cacheDir, BASE_STYLE_CACHE_FILE) val baseJson = - fetch(String.format(BROWSING_STYLE_URL, apiKey)) ?: run { - Log.e(TAG, "Failed to fetch base style JSON") - return@withContext null - } + fetch(String.format(BROWSING_STYLE_URL, apiKey)) + ?.also { runCatching { cachedBase.writeText(it) } } + ?: cachedBase.takeIf { it.isFile }?.let { runCatching { it.readText() }.getOrNull() } + ?: run { + Log.e(TAG, "Failed to fetch base style JSON and no cached copy") + return@withContext null + } val root = try { @@ -114,16 +132,23 @@ object TomTomStyleComposer { when (type) { "fill" -> paint.put("fill-opacity", 0) "line" -> paint.put("line-opacity", 0) - "symbol" -> { paint.put("text-opacity", 0); paint.put("icon-opacity", 0) } + "symbol" -> { + paint.put("text-opacity", 0) + paint.put("icon-opacity", 0) + } "raster" -> paint.put("raster-opacity", 0) "fill-extrusion" -> paint.put("fill-extrusion-opacity", 0) - "circle" -> { paint.put("circle-opacity", 0); paint.put("circle-stroke-opacity", 0) } + "circle" -> { + paint.put("circle-opacity", 0) + paint.put("circle-stroke-opacity", 0) + } "heatmap" -> paint.put("heatmap-opacity", 0) "hillshade" -> paint.put("hillshade-exaggeration", 0) } } } else { - sources.put("mc-base-raster", rasterSource(String.format(BASE_RASTER_TILES, apiKey), TILE_SIZE, null, null)) + sources + .put("mc-base-raster", rasterSource(String.format(BASE_RASTER_TILES, apiKey), TILE_SIZE, null, null)) // 可視ベース地図(ラスタ)は最下層付近("background" の直後)に挿入し、 // ラベル/オーバーレイ/マーカーがその上に来るようにする。