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 @@ -151,6 +151,8 @@ abstract class CircleController<ActualCircle>(
semaphore.withPermit {
val entities: List<CircleEntityInterface<ActualCircle>> = circleManager.allEntities()
renderer.onRemove(entities)
// manager を空にしてから onPostProcess を呼ぶ。GL 系の描画担当は remove では何もせず、
// onPostProcess で manager の全部を描き直すので、逆の順だと clear() 後も地図に残る。
circleManager.clear()
renderer.onPostProcess()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,10 @@ abstract class GroundImageController<ActualGroundImage>(
semaphore.withPermit {
val entities: List<GroundImageEntityInterface<ActualGroundImage>> = groundImageManager.allEntities()
renderer.onRemove(entities)
renderer.onPostProcess()
// manager を空にしてから onPostProcess を呼ぶ。GL 系の描画担当は remove では何もせず、
// onPostProcess で manager の全部を描き直すので、逆の順だと clear() 後も地図に残る。
groundImageManager.clear()
renderer.onPostProcess()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,9 @@ abstract class AbstractMarkerController<ActualMarker>(
renderer.onRemove(entities)
markerManager.clear()
draggingMarkerIds.clear()
// 他の 5 種類と同じく、manager を空にしてから onPostProcess を呼ぶ。GL 系の描画担当は
// onRemove では地図に触らず、onPostProcess で manager の中身から描き直す。
renderer.onPostProcess()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ abstract class PolygonController<ActualPolygon>(
semaphore.withPermit {
val entities: List<PolygonEntityInterface<ActualPolygon>> = polygonManager.allEntities()
renderer.onRemove(entities)
// manager を空にしてから onPostProcess を呼ぶ。GL 系の描画担当は remove では何もせず、
// onPostProcess で manager の全部を描き直すので、逆の順だと clear() 後も地図に残る。
polygonManager.clear()
renderer.onPostProcess()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,10 @@ abstract class PolylineController<ActualPolyline>(
semaphore.withPermit {
val entities: List<PolylineEntityInterface<ActualPolyline>> = polylineManager.allEntities()
renderer.onRemove(entities)
renderer.onPostProcess()
// manager を空にしてから onPostProcess を呼ぶ。GL 系の描画担当は remove では何もせず、
// onPostProcess で manager の全部を描き直すので、逆の順だと clear() 後も地図に残る。
polylineManager.clear()
renderer.onPostProcess()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,10 @@ abstract class RasterLayerController<ActualLayer : Any>(
upsertedIds.clear()
val entities: List<RasterLayerEntityInterface<ActualLayer>> = rasterLayerManager.allEntities()
renderer.onRemove(entities)
renderer.onPostProcess()
// manager を空にしてから onPostProcess を呼ぶ。GL 系の描画担当は remove では何もせず、
// onPostProcess で manager の全部を描き直すので、逆の順だと clear() 後も地図に残る。
rasterLayerManager.clear()
renderer.onPostProcess()
}
}
}
Expand Down
302 changes: 302 additions & 0 deletions src/test/java/com/mapconductor/core/OverlayClearOrderTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
package com.mapconductor.core

import com.mapconductor.core.circle.CircleController
import com.mapconductor.core.circle.CircleEntityInterface
import com.mapconductor.core.circle.CircleManager
import com.mapconductor.core.circle.CircleOverlayRendererInterface
import com.mapconductor.core.circle.CircleState
import com.mapconductor.core.features.GeoPoint
import com.mapconductor.core.features.GeoRectBounds
import com.mapconductor.core.groundimage.GroundImageController
import com.mapconductor.core.groundimage.GroundImageEntityInterface
import com.mapconductor.core.groundimage.GroundImageManager
import com.mapconductor.core.groundimage.GroundImageOverlayRendererInterface
import com.mapconductor.core.groundimage.GroundImageState
import com.mapconductor.core.map.MapViewHolderInterface
import com.mapconductor.core.marker.AbstractMarkerController
import com.mapconductor.core.marker.MarkerEntity
import com.mapconductor.core.marker.MarkerEntityInterface
import com.mapconductor.core.marker.MarkerManager
import com.mapconductor.core.marker.MarkerOverlayRendererInterface
import com.mapconductor.core.marker.MarkerState
import com.mapconductor.core.marker.OnMarkerEventHandler
import com.mapconductor.core.polygon.PolygonController
import com.mapconductor.core.polygon.PolygonEntityInterface
import com.mapconductor.core.polygon.PolygonManager
import com.mapconductor.core.polygon.PolygonOverlayRendererInterface
import com.mapconductor.core.polygon.PolygonState
import com.mapconductor.core.polyline.PolylineController
import com.mapconductor.core.polyline.PolylineEntityInterface
import com.mapconductor.core.polyline.PolylineManager
import com.mapconductor.core.polyline.PolylineOverlayRendererInterface
import com.mapconductor.core.polyline.PolylineState
import com.mapconductor.core.raster.RasterLayerController
import com.mapconductor.core.raster.RasterLayerEntityInterface
import com.mapconductor.core.raster.RasterLayerManager
import com.mapconductor.core.raster.RasterLayerOverlayRendererInterface
import com.mapconductor.core.raster.RasterLayerSource
import com.mapconductor.core.raster.RasterLayerState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Test

/**
* clear() の後、地図に何も残らないこと。
*
* MapLibre / Mapbox / Mappls / OMM などの GL 系の描画担当は、remove では何もせず、
* onPostProcess で manager の全部を描き直す。ここの偽レンダラも同じ作りにしてあり、
* `drawn` は最後の onPostProcess で描いたもの(=地図に見えているもの)を表す。
*
* clear() が manager を空にする前に onPostProcess を呼ぶと、全部を描き直して残る。
* 呼ばなければ、前に描いたものが残る。
*/
class OverlayClearOrderTest {
private val a = GeoPoint(35.0, 139.0)
private val b = GeoPoint(35.1, 139.1)
private val c = GeoPoint(35.0, 139.1)

/** GL 系の描画担当の作り: onPostProcess で [all] の全部を描き直す。 */
private class Drawn(
private val all: () -> List<String>,
) {
var ids: List<String> = emptyList()
private set

fun redraw() {
ids = all().sorted()
}
}

// ── Polygon ─────────────────────────────────────────────────────────

private class PolygonGl(
manager: PolygonManager<String>,
) : PolygonOverlayRendererInterface<String> {
val drawn = Drawn { manager.allEntities().map { it.state.id } }

override suspend fun onAdd(data: List<PolygonOverlayRendererInterface.AddParamsInterface>) = data.map { it.state.id }

override suspend fun onChange(data: List<PolygonOverlayRendererInterface.ChangeParamsInterface<String>>) =
data.map { it.current.state.id }

override suspend fun onRemove(data: List<PolygonEntityInterface<String>>) = Unit

override suspend fun onPostProcess() = drawn.redraw()
}

@Test
fun `polygon の clear 後は何も描かれていない`() =
runTest {
val manager = PolygonManager<String>()
val renderer = PolygonGl(manager)
val controller = object : PolygonController<String>(manager, renderer) {}
controller.add(listOf(PolygonState(points = listOf(a, b, c), id = "p1"), PolygonState(points = listOf(a, b, c), id = "p2")))
assertEquals(listOf("p1", "p2"), renderer.drawn.ids)

controller.clear()

assertEquals(emptyList<String>(), renderer.drawn.ids)
}

// ── Polyline ────────────────────────────────────────────────────────

private class PolylineGl(
manager: PolylineManager<String>,
) : PolylineOverlayRendererInterface<String> {
val drawn = Drawn { manager.allEntities().map { it.state.id } }

override suspend fun onAdd(data: List<PolylineOverlayRendererInterface.AddParamsInterface>) = data.map { it.state.id }

override suspend fun onChange(data: List<PolylineOverlayRendererInterface.ChangeParamsInterface<String>>) =
data.map { it.current.state.id }

override suspend fun onRemove(data: List<PolylineEntityInterface<String>>) = Unit

override suspend fun onPostProcess() = drawn.redraw()
}

@Test
fun `polyline の clear 後は何も描かれていない`() =
runTest {
val manager = PolylineManager<String>()
val renderer = PolylineGl(manager)
val controller = object : PolylineController<String>(manager, renderer) {}
controller.add(listOf(PolylineState(points = listOf(a, b), id = "l1"), PolylineState(points = listOf(b, c), id = "l2")))
assertEquals(listOf("l1", "l2"), renderer.drawn.ids)

controller.clear()

assertEquals(emptyList<String>(), renderer.drawn.ids)
}

// ── Circle ──────────────────────────────────────────────────────────

private class CircleGl(
manager: CircleManager<String>,
) : CircleOverlayRendererInterface<String> {
val drawn = Drawn { manager.allEntities().map { it.state.id } }

override suspend fun onAdd(data: List<CircleOverlayRendererInterface.AddParamsInterface>) = data.map { it.state.id }

override suspend fun onChange(data: List<CircleOverlayRendererInterface.ChangeParamsInterface<String>>) =
data.map { it.current.state.id }

override suspend fun onRemove(data: List<CircleEntityInterface<String>>) = Unit

override suspend fun onPostProcess() = drawn.redraw()
}

@Test
fun `circle の clear 後は何も描かれていない`() =
runTest {
val manager = CircleManager<String>()
val renderer = CircleGl(manager)
val controller = object : CircleController<String>(manager, renderer) {}
controller.add(
listOf(CircleState(center = a, radiusMeters = 100.0, id = "c1"), CircleState(center = b, radiusMeters = 50.0, id = "c2")),
)
assertEquals(listOf("c1", "c2"), renderer.drawn.ids)

controller.clear()

assertEquals(emptyList<String>(), renderer.drawn.ids)
}

// ── GroundImage ─────────────────────────────────────────────────────

private class GroundImageGl(
manager: GroundImageManager<String>,
) : GroundImageOverlayRendererInterface<String> {
val drawn = Drawn { manager.allEntities().map { it.state.id } }

override suspend fun onAdd(data: List<GroundImageOverlayRendererInterface.AddParamsInterface>) = data.map { it.state.id }

override suspend fun onChange(data: List<GroundImageOverlayRendererInterface.ChangeParamsInterface<String>>) =
data.map { it.current.state.id }

override suspend fun onRemove(data: List<GroundImageEntityInterface<String>>) = Unit

override suspend fun onPostProcess() = drawn.redraw()
}

/** GroundImageState は Drawable を要求するが、ここでは中身を使わない。 */
private class FakeDrawable : android.graphics.drawable.Drawable() {
override fun draw(canvas: android.graphics.Canvas) = Unit

override fun setAlpha(alpha: Int) = Unit

override fun setColorFilter(colorFilter: android.graphics.ColorFilter?) = Unit

@Deprecated("Deprecated in Java")
override fun getOpacity(): Int = android.graphics.PixelFormat.OPAQUE
}

@Test
fun `groundImage の clear 後は何も描かれていない`() =
runTest {
val manager = GroundImageManager<String>()
val renderer = GroundImageGl(manager)
val controller = object : GroundImageController<String>(manager, renderer) {}
val bounds =
GeoRectBounds().apply {
extend(a)
extend(b)
}
controller.add(
listOf(
GroundImageState(bounds = bounds, image = FakeDrawable(), id = "g1"),
GroundImageState(bounds = bounds, image = FakeDrawable(), id = "g2"),
),
)
assertEquals(listOf("g1", "g2"), renderer.drawn.ids)

controller.clear()

assertEquals(emptyList<String>(), renderer.drawn.ids)
}

// ── RasterLayer ─────────────────────────────────────────────────────

private class RasterGl(
manager: RasterLayerManager<String>,
override val coroutine: CoroutineScope,
) : RasterLayerOverlayRendererInterface<String> {
val drawn = Drawn { manager.allEntities().map { it.state.id } }

override suspend fun onAdd(data: List<RasterLayerOverlayRendererInterface.AddParamsInterface>) = data.map { it.state.id }

override suspend fun onChange(data: List<RasterLayerOverlayRendererInterface.ChangeParamsInterface<String>>) =
data.map { it.current.state.id }

override suspend fun onRemove(data: List<RasterLayerEntityInterface<String>>) = Unit

override suspend fun onPostProcess() = drawn.redraw()
}

@Test
fun `rasterLayer の clear 後は何も描かれていない`() =
runTest {
val manager = RasterLayerManager<String>()
val renderer = RasterGl(manager, CoroutineScope(StandardTestDispatcher(testScheduler)))
val controller = object : RasterLayerController<String>(manager, renderer) {}
controller.add(
listOf(
RasterLayerState(source = RasterLayerSource.UrlTemplate("https://example.com/a/{z}/{x}/{y}.png"), id = "r1"),
RasterLayerState(source = RasterLayerSource.UrlTemplate("https://example.com/b/{z}/{x}/{y}.png"), id = "r2"),
),
)
assertEquals(listOf("r1", "r2"), renderer.drawn.ids)

controller.clear()

assertEquals(emptyList<String>(), renderer.drawn.ids)
}

// ── Marker ──────────────────────────────────────────────────────────

/**
* MapLibre / Mapbox / Mappls のマーカーの描画担当と同じ作り: onRemove はアイコンの参照数を
* 減らすだけで、地図のソースは onPostProcess が manager の中身から描き直す。
*/
private class MarkerGl(
manager: MarkerManager<String>,
) : MarkerOverlayRendererInterface<String> {
val drawn = Drawn { manager.allEntities().map { it.state.id } }
override var animateStartListener: OnMarkerEventHandler? = null
override var animateEndListener: OnMarkerEventHandler? = null
override val holder: MapViewHolderInterface<*, *>
get() = throw UnsupportedOperationException("not used")

override suspend fun onAdd(data: List<MarkerOverlayRendererInterface.AddParamsInterface>) = data.map { it.state.id }

override suspend fun onChange(data: List<MarkerOverlayRendererInterface.ChangeParamsInterface<String>>) =
data.map { it.current.state.id }

override suspend fun onRemove(data: List<MarkerEntityInterface<String>>) = Unit

override suspend fun onAnimate(entity: MarkerEntityInterface<String>) = Unit

override suspend fun onPostProcess() = drawn.redraw()
}

@Test
fun `marker の clear 後は何も描かれていない`() =
runTest {
val manager = MarkerManager.defaultManager<String>()
val renderer = MarkerGl(manager)
val controller = object : AbstractMarkerController<String>(manager, renderer) {}
// add() は既定アイコン(Android の Bitmap)を焼くので素の JVM では呼べない。
// 描かれた状態は manager へ直接登録して作る。
listOf("m1", "m2").forEach { id ->
manager.registerEntity(MarkerEntity(marker = id, state = MarkerState(position = a, id = id), isRendered = true))
}
renderer.onPostProcess()
assertEquals(listOf("m1", "m2"), renderer.drawn.ids)

controller.clear()

assertEquals(emptyList<String>(), renderer.drawn.ids)
}
}
Loading