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
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,12 @@ internal class MapTilerStyleMutationTarget(

override fun removeLayer(layerId: String) {
val target = style() ?: return
// `removeLayerById` reaches the page without being checked against
// anything, and removing an absent layer there is a no-op.
target.removeLayerById(layerId)
// Remove it from MTStyle's record too: `removeLayerById` alone leaves
// the id recorded, and adding the same background layer again throws
// LayerAlreadyExists. A layer MTStyle never recorded falls back to
// `removeLayerById`, which reaches the page unchecked (a no-op when the
// layer is absent). See MapTilerStyleRemoval.kt.
target.removeLayerAndForget(MTBackgroundLayer(layerId))
own(target) -= layerId
}

Expand Down
31 changes: 31 additions & 0 deletions src/main/java/com/mapconductor/maptiler/MapTilerStyleRemoval.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.mapconductor.maptiler

import com.mapconductor.core.InternalMapConductorApi
import com.maptiler.maptilersdk.map.style.MTStyle
import com.maptiler.maptilersdk.map.style.layer.MTLayer
import com.maptiler.maptilersdk.map.style.source.MTSource

/*
* MTStyle からレイヤ・ソースを外すときは、id ではなくオブジェクトで外す。
*
* MTStyle は addLayer / addSource したものを id で覚えていて、同じ id をもう一度足そうとすると
* LayerAlreadyExists / SourceAlreadyExists を投げる。ところが removeLayerById / removeSourceById は
* ページ(MapLibre GL JS)から消すだけで、この記録を消さない。id で外したポリゴン・円・ラスター等を
* 同じ id(= 同じ state.id)で戻すと add が投げて、地図に戻らなかった。
* removeLayer(layer) / removeSource(source) は記録も消してからページへ渡す。
*
* 記録に無いとき(add の記録は SDK のコルーチンで遅れて入る)は NotFound を投げるので、
* そのときは id でページから消す。
*/

@InternalMapConductorApi
internal fun MTStyle.removeLayerAndForget(layer: MTLayer) {
runCatching { removeLayer(layer) }
.onFailure { runCatching { removeLayerById(layer.identifier) } }
}

@InternalMapConductorApi
internal fun MTStyle.removeSourceAndForget(source: MTSource) {
runCatching { removeSource(source) }
.onFailure { runCatching { removeSourceById(source.identifier) } }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ 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.maptiler.removeLayerAndForget
import com.mapconductor.maptiler.removeSourceAndForget
import com.maptiler.maptilersdk.map.MTMapViewController
import com.maptiler.maptilersdk.map.style.dsl.PropertyValue
import com.maptiler.maptilersdk.map.style.layer.MTLayerVisibility
Expand Down Expand Up @@ -141,9 +143,10 @@ class MapTilerCircleOverlayRenderer(
private fun removeCircle(entity: CircleEntityInterface<MapTilerCircleHandle>) {
val style = mtController.style ?: return
val handle = entity.circle
runCatching { style.removeLayerById(handle.lineLayerId) }
runCatching { style.removeLayerById(handle.fillLayerId) }
runCatching { style.removeSourceById(handle.sourceId) }
// id で外すと MTStyle の記録が残り、同じ id で戻せなくなる(MapTilerStyleRemoval.kt)。
style.removeLayerAndForget(handle.lineLayer)
style.removeLayerAndForget(handle.fillLayer)
style.removeSourceAndForget(handle.source)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ 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.maptiler.removeLayerAndForget
import com.mapconductor.maptiler.removeSourceAndForget
import com.maptiler.maptilersdk.map.LngLat
import com.maptiler.maptilersdk.map.MTMapViewController
import com.maptiler.maptilersdk.map.style.dsl.PropertyValue
Expand Down Expand Up @@ -132,8 +134,9 @@ class MapTilerGroundImageOverlayRenderer(
private fun removeGroundImage(entity: GroundImageEntityInterface<MapTilerGroundImageHandle>) {
val style = mtController.style ?: return
val handle = entity.groundImage
runCatching { style.removeLayerById(handle.layerId) }
runCatching { style.removeSourceById(handle.sourceId) }
// id で外すと MTStyle の記録が残り、同じ id で戻せなくなる(MapTilerStyleRemoval.kt)。
style.removeLayerAndForget(handle.layer)
style.removeSourceAndForget(handle.source)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ 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.maptiler.removeLayerAndForget
import com.mapconductor.maptiler.removeSourceAndForget
import com.maptiler.maptilersdk.map.MTMapViewController
import com.maptiler.maptilersdk.map.style.dsl.PropertyValue
import com.maptiler.maptilersdk.map.style.layer.MTLayerVisibility
Expand Down Expand Up @@ -164,9 +166,10 @@ class MapTilerPolygonOverlayRenderer(
private fun removePolygon(entity: PolygonEntityInterface<MapTilerPolygonHandle>) {
val style = mtController.style ?: return
val handle = entity.polygon
runCatching { style.removeLayerById(handle.lineLayerId) }
runCatching { style.removeLayerById(handle.fillLayerId) }
runCatching { style.removeSourceById(handle.sourceId) }
// id で外すと MTStyle の記録が残り、同じ id で戻せなくなる(MapTilerStyleRemoval.kt)。
style.removeLayerAndForget(handle.lineLayer)
style.removeLayerAndForget(handle.fillLayer)
style.removeSourceAndForget(handle.source)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ 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.maptiler.removeLayerAndForget
import com.mapconductor.maptiler.removeSourceAndForget
import com.maptiler.maptilersdk.map.MTMapViewController
import com.maptiler.maptilersdk.map.style.dsl.PropertyValue
import com.maptiler.maptilersdk.map.style.layer.MTLayerVisibility
Expand Down Expand Up @@ -141,8 +143,9 @@ class MapTilerPolylineOverlayRenderer(
private fun removeLine(entity: PolylineEntityInterface<MapTilerPolylineHandle>) {
val style = mtController.style ?: return
val handle = entity.polyline
runCatching { style.removeLayerById(handle.layerId) }
runCatching { style.removeSourceById(handle.sourceId) }
// id で外すと MTStyle の記録が残り、同じ id で戻せなくなる(MapTilerStyleRemoval.kt)。
style.removeLayerAndForget(handle.layer)
style.removeSourceAndForget(handle.source)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.mapconductor.core.raster.RasterLayerOverlayRendererInterface
import com.mapconductor.core.raster.RasterLayerSource
import com.mapconductor.core.raster.RasterLayerState
import com.mapconductor.core.raster.TileScheme
import com.mapconductor.maptiler.removeLayerAndForget
import com.mapconductor.maptiler.removeSourceAndForget
import com.maptiler.maptilersdk.map.MTMapViewController
import com.maptiler.maptilersdk.map.style.MTTileScheme
import com.maptiler.maptilersdk.map.style.dsl.PropertyValue
Expand Down Expand Up @@ -121,8 +123,10 @@ class MapTilerRasterLayerOverlayRenderer(
private fun removeLayer(entity: RasterLayerEntityInterface<MapTilerRasterLayerHandle>) {
val style = mtController.style ?: return
val handle = entity.layer
runCatching { style.removeLayerById(handle.layerId) }
runCatching { style.removeSourceById(handle.sourceId) }
// id で外すと MTStyle の記録が残り、同じ id で戻せなくなる(MapTilerStyleRemoval.kt)。
// ソースを替えるとき(onChange)も同じ id で足し直すので、ここで記録まで消す。
style.removeLayerAndForget(handle.layer)
style.removeSourceAndForget(handle.source)
}

private fun buildSource(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.mapconductor.maptiler

import com.mapconductor.core.InternalMapConductorApi
import com.maptiler.maptilersdk.bridge.MTBridge
import com.maptiler.maptilersdk.bridge.MTBridgeReturnType
import com.maptiler.maptilersdk.bridge.MTCommand
import com.maptiler.maptilersdk.bridge.MTCommandExecutable
import com.maptiler.maptilersdk.map.style.MTMapReferenceStyle
import com.maptiler.maptilersdk.map.style.MTStyle
import com.maptiler.maptilersdk.map.style.MTStyleError
import com.maptiler.maptilersdk.map.style.source.MTGeoJSONSource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

/**
* 外したソースを同じ id で足し直せること(ポリゴン・円・ラスター等が「一度外すと戻らない」不具合)。
*
* 実物の [MTStyle] に、ページ(WebView)の代わりにコマンドを記録するだけの実行器をつないで確かめる。
*/
@OptIn(InternalMapConductorApi::class)
class MapTilerStyleRemovalTest {
private val commands = mutableListOf<String>()
private lateinit var style: MTStyle

@Before
fun setUp() {
val executor =
object : MTCommandExecutable {
override suspend fun execute(command: MTCommand): MTBridgeReturnType {
commands += command.javaClass.simpleName
return MTBridgeReturnType.BoolValue(true)
}
}
style = MTStyle(MTMapReferenceStyle.STREETS)
// initWorker は SDK の internal。地図の ready で SDK 自身が呼ぶものを、ここでは反射で呼ぶ。
MTStyle::class.java
.declaredMethods
.single { it.name.startsWith("initWorker") }
.apply { isAccessible = true }
.invoke(style, MTBridge(executor), CoroutineScope(Dispatchers.Unconfined))
}

private fun source() = MTGeoJSONSource.fromJsonString("polygon-source-PG", """{"type":"FeatureCollection","features":[]}""")

/** 不具合の原因: id で外しても MTStyle の記録が残り、同じ id の add が投げる。 */
@Test
fun removeSourceById_leavesTheIdRecorded() {
style.addSource(source())
style.removeSourceById("polygon-source-PG")
assertThrows(MTStyleError.SourceAlreadyExists::class.java) { style.addSource(source()) }
}

@Test
fun removeSourceAndForget_letsTheSameIdBeAddedAgain() {
val first = source()
style.addSource(first)
style.removeSourceAndForget(first)
style.addSource(source())
assertEquals(listOf("AddSource", "RemoveSource", "AddSource"), commands.filter { it.contains("Source") })
}

/** 記録に無いもの(足していない・記録が遅れている)は id でページから消す。 */
@Test
fun removeSourceAndForget_fallsBackToTheIdWhenNotRecorded() {
style.removeSourceAndForget(source())
assertTrue(commands.toString(), commands.contains("RemoveSourceById"))
}
}
Loading