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
14 changes: 9 additions & 5 deletions api/android-sdk-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -852,14 +852,18 @@ public final class com.mapconductor.core.map.LocalMapOverlayRegistryKt {
}
public final class com.mapconductor.core.map.MapCameraPosition implements com.mapconductor.core.map.MapCameraPositionInterface {
public static final com.mapconductor.core.map.MapCameraPosition$Companion Companion;
public com.mapconductor.core.map.MapCameraPosition(com.mapconductor.core.features.GeoPointInterface, double, double, double, com.mapconductor.core.map.MapPaddingsInterface, com.mapconductor.core.map.VisibleRegion);
public com.mapconductor.core.map.MapCameraPosition(com.mapconductor.core.features.GeoPointInterface, double, double, double, com.mapconductor.core.map.MapPaddingsInterface, com.mapconductor.core.map.VisibleRegion, int, kotlin.jvm.internal.DefaultConstructorMarker);
public double getZoom();
public double getBearing();
public double getTilt();
public com.mapconductor.core.map.MapCameraPosition(com.mapconductor.core.features.GeoPointInterface, java.lang.Double, java.lang.Double, java.lang.Double, com.mapconductor.core.map.MapPaddingsInterface, com.mapconductor.core.map.VisibleRegion);
public com.mapconductor.core.map.MapCameraPosition(com.mapconductor.core.features.GeoPointInterface, java.lang.Double, java.lang.Double, java.lang.Double, com.mapconductor.core.map.MapPaddingsInterface, com.mapconductor.core.map.VisibleRegion, int, kotlin.jvm.internal.DefaultConstructorMarker);
public com.mapconductor.core.map.MapPaddingsInterface getPaddings();
public com.mapconductor.core.map.VisibleRegion getVisibleRegion();
public com.mapconductor.core.features.GeoPoint getPosition();
public double getZoom();
public double getBearing();
public double getTilt();
public final boolean isZoomSpecified();
public final boolean isBearingSpecified();
public final boolean isTiltSpecified();
public final com.mapconductor.core.map.MapCameraPosition resolvedOnto(com.mapconductor.core.map.MapCameraPositionInterface);
public final boolean equals(com.mapconductor.core.map.MapCameraPositionInterface);
public final com.mapconductor.core.map.MapCameraPosition copy(com.mapconductor.core.features.GeoPointInterface, java.lang.Double, java.lang.Double, java.lang.Double, com.mapconductor.core.map.MapPaddingsInterface, com.mapconductor.core.map.VisibleRegion);
public int hashCode();
Expand Down
61 changes: 52 additions & 9 deletions src/main/java/com/mapconductor/core/map/MapCameraPositionBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,32 +32,75 @@ interface MapCameraPositionInterface {

class MapCameraPosition(
position: GeoPointInterface,
override val zoom: Double = 0.0,
override val bearing: Double = 0.0,
override val tilt: Double = 0.0,
zoom: Double? = null,
bearing: Double? = null,
tilt: Double? = null,
override val paddings: MapPaddingsInterface? = MapPaddings.Companion.Zeros,
override val visibleRegion: VisibleRegion? = null,
) : MapCameraPositionInterface {
override val position: GeoPoint = GeoPoint.from(position)

// 指定された値。null は未指定。
private val givenZoom: Double? = zoom
private val givenBearing: Double? = bearing
private val givenTilt: Double? = tilt

/** 指定されなかったときは 0。指定されたかどうかは [isZoomSpecified] で区別する。 */
override val zoom: Double = zoom ?: 0.0

/** 指定されなかったときは 0。指定されたかどうかは [isBearingSpecified] で区別する。 */
override val bearing: Double = bearing ?: 0.0

/** 指定されなかったときは 0。指定されたかどうかは [isTiltSpecified] で区別する。 */
override val tilt: Double = tilt ?: 0.0

/**
* `zoom` / `bearing` / `tilt` が明示されたか。
*
* 明示された 0(zoom 0 = 世界全体、真北、真上から)と未指定を区別するために持つ。
* [MapViewState.moveCameraTo] は未指定のプロパティだけを現在のカメラから
* 引き継ぐ([resolvedOnto])。
*/
val isZoomSpecified: Boolean get() = givenZoom != null
val isBearingSpecified: Boolean get() = givenBearing != null
val isTiltSpecified: Boolean get() = givenTilt != null

/**
* 未指定の `zoom` / `bearing` / `tilt` を [current] の値で埋めたカメラを返す。
*
* 明示されたプロパティは、0 であってもそのまま使う。
*/
fun resolvedOnto(current: MapCameraPositionInterface): MapCameraPosition {
if (isZoomSpecified && isBearingSpecified && isTiltSpecified) return this
return MapCameraPosition(
position = position,
zoom = givenZoom ?: current.zoom,
bearing = givenBearing ?: current.bearing,
tilt = givenTilt ?: current.tilt,
paddings = paddings,
visibleRegion = visibleRegion,
)
}

fun equals(other: MapCameraPositionInterface): Boolean =
this.position.equals(other = other.position) &&
this.zoomEquals(other) &&
this.bearingEquals(other) &&
this.tiltEquals(other)

// 渡されなかったプロパティは、未指定だったならコピー後も未指定のまま。
fun copy(
position: GeoPointInterface? = this.position,
zoom: Double? = this.zoom,
bearing: Double? = this.bearing,
tilt: Double? = this.tilt,
zoom: Double? = this.givenZoom,
bearing: Double? = this.givenBearing,
tilt: Double? = this.givenTilt,
paddings: MapPaddingsInterface? = this.paddings,
visibleRegion: VisibleRegion? = this.visibleRegion,
) = MapCameraPosition(
position = position ?: this.position,
zoom = zoom ?: this.zoom,
bearing = bearing ?: this.bearing,
tilt = tilt ?: this.tilt,
zoom = zoom ?: this.givenZoom,
bearing = bearing ?: this.givenBearing,
tilt = tilt ?: this.givenTilt,
paddings = paddings ?: this.paddings,
visibleRegion = visibleRegion ?: this.visibleRegion,
)
Expand Down
11 changes: 7 additions & 4 deletions src/main/java/com/mapconductor/core/map/MapViewState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -169,22 +169,25 @@ abstract class MapViewState<ActualMapDesignType>(
cameraPosition: MapCameraPosition,
durationMillis: Long?,
) {
// 明示されたプロパティは 0 でもそのまま使い、未指定のものだけ現在のカメラから
// 引き継ぐ。`MapCameraPosition(position = ...)` なら位置だけが動く。
val resolved = cameraPosition.resolvedOnto(_cameraPosition)
val ctrl = attachedMapController
if (ctrl == null) {
// まだ地図が無い。接続時に attachController がこの位置へ移動する。
_cameraPosition = cameraPosition
_cameraPosition = resolved
return
}
if (optimisticCameraUpdate) {
_cameraPosition = cameraPosition
_cameraPosition = resolved
}
// 引数は既に MapCameraPosition なので、プロバイダごとの
// `MapCameraPosition.Companion.from` は恒等変換になる(どの実装も
// `is MapCameraPosition -> position` で素通しする)。ここでは呼ばない。
if (durationMillis == null || durationMillis == 0L) {
ctrl.moveCamera(cameraPosition)
ctrl.moveCamera(resolved)
} else {
ctrl.animateCamera(cameraPosition, durationMillis)
ctrl.animateCamera(resolved, durationMillis)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.mapconductor.core.map

import com.mapconductor.core.features.GeoPoint
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

/**
* 未指定の zoom / bearing / tilt だけが現在のカメラから引き継がれ、
* 明示された 0 はそのまま使われることを確認する。
*
* ios-sdk の `CameraPositionResolveTests`、js-sdk の `CameraPositionResolve.test.mjs` と同じ観点。
*/
class CameraPositionResolveTest {
private val tokyo = GeoPoint(latitude = 35.6812, longitude = 139.7671)
private val osaka = GeoPoint(latitude = 34.7025, longitude = 135.4959)
private val current = MapCameraPosition(position = tokyo, zoom = 12.0, bearing = 30.0, tilt = 45.0)

private fun MapCameraPosition.zbt() = listOf(zoom, bearing, tilt)

@Test
fun positionOnlyKeepsEverythingElse() {
val resolved = MapCameraPosition(position = osaka).resolvedOnto(current)
assertEquals(osaka.latitude, resolved.position.latitude, 0.0)
assertEquals(listOf(12.0, 30.0, 45.0), resolved.zbt())
}

@Test
fun explicitZeroIsHonoured() {
val resolved =
MapCameraPosition(position = osaka, zoom = 0.0, bearing = 0.0, tilt = 0.0)
.resolvedOnto(current)
assertEquals(listOf(0.0, 0.0, 0.0), resolved.zbt())
}

@Test
fun eachPropertyIsInheritedOnItsOwn() {
assertEquals(
listOf(0.0, 30.0, 45.0),
MapCameraPosition(position = osaka, zoom = 0.0).resolvedOnto(current).zbt(),
)
assertEquals(
listOf(12.0, 0.0, 45.0),
MapCameraPosition(position = osaka, bearing = 0.0).resolvedOnto(current).zbt(),
)
assertEquals(
listOf(12.0, 30.0, 0.0),
MapCameraPosition(position = osaka, tilt = 0.0).resolvedOnto(current).zbt(),
)
}

@Test
fun unsetReadsAsZeroBeforeResolving() {
val camera = MapCameraPosition(position = osaka)
assertEquals(0.0, camera.zoom, 0.0)
assertFalse(camera.isZoomSpecified)
assertFalse(camera.isBearingSpecified)
assertFalse(camera.isTiltSpecified)
assertTrue(MapCameraPosition.Default.isZoomSpecified)
}

@Test
fun copyKeepsUnspecifiedUnlessGiven() {
assertFalse(MapCameraPosition(position = osaka).copy(position = tokyo).isZoomSpecified)

val withZoom = MapCameraPosition(position = osaka).copy(zoom = 0.0)
assertTrue(withZoom.isZoomSpecified)
assertFalse(withZoom.isBearingSpecified)
}

private class TestState(
initial: MapCameraPosition,
) : MapViewState<String>(initial) {
override val id: String = "test"
override var mapDesignType: String = "design"
}

@Test
fun mapViewStateMergesUnsetPropertiesFromCurrentCamera() {
val state = TestState(current)

state.moveCameraTo(MapCameraPosition(position = osaka, bearing = 0.0), null)
assertEquals(listOf(12.0, 0.0, 45.0), state.cameraPosition.zbt())

state.moveCameraTo(MapCameraPosition(position = osaka, zoom = 0.0), null)
assertEquals(0.0, state.cameraPosition.zoom, 0.0)
}
}
Loading