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
51 changes: 42 additions & 9 deletions Sources/MapConductorCore/map/MapCameraPositionBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,61 @@ public protocol MapCameraPositionProtocol {

public final class MapCameraPosition: MapCameraPositionProtocol {
public var position: GeoPointProtocol { positionValue }
/// 指定されなかったときは 0。指定されたかどうかは ``isZoomSpecified`` で区別する。
public let zoom: Double
/// 指定されなかったときは 0。指定されたかどうかは ``isBearingSpecified`` で区別する。
public let bearing: Double
/// 指定されなかったときは 0。指定されたかどうかは ``isTiltSpecified`` で区別する。
public let tilt: Double
public let paddings: MapPaddingsProtocol?
public let visibleRegion: VisibleRegion?
private let positionValue: GeoPoint

/// `zoom` / `bearing` / `tilt` が明示されたか。
///
/// 明示された 0(zoom 0 = 世界全体、真北、真上から)と未指定を区別するために持つ。
/// ``MapViewState/moveCameraTo(cameraPosition:durationMillis:)`` は未指定の
/// プロパティだけを現在のカメラから引き継ぐ(``resolved(onto:)``)。
public let isZoomSpecified: Bool
public let isBearingSpecified: Bool
public let isTiltSpecified: Bool

public init(
position: GeoPointProtocol,
zoom: Double = 0.0,
bearing: Double = 0.0,
tilt: Double = 0.0,
zoom: Double? = nil,
bearing: Double? = nil,
tilt: Double? = nil,
paddings: MapPaddingsProtocol? = MapPaddings.Zeros,
visibleRegion: VisibleRegion? = nil
) {
self.positionValue = GeoPoint.from(position: position)
self.zoom = zoom
self.bearing = bearing
self.tilt = tilt
self.zoom = zoom ?? 0.0
self.bearing = bearing ?? 0.0
self.tilt = tilt ?? 0.0
self.isZoomSpecified = zoom != nil
self.isBearingSpecified = bearing != nil
self.isTiltSpecified = tilt != nil
self.paddings = paddings
self.visibleRegion = visibleRegion
}

/// 未指定の `zoom` / `bearing` / `tilt` を `current` の値で埋めたカメラを返す。
///
/// 明示されたプロパティは、0 であってもそのまま使う。
public func resolved(onto current: MapCameraPositionProtocol) -> MapCameraPosition {
if isZoomSpecified && isBearingSpecified && isTiltSpecified {
return self
}
return MapCameraPosition(
position: positionValue,
zoom: isZoomSpecified ? zoom : current.zoom,
bearing: isBearingSpecified ? bearing : current.bearing,
tilt: isTiltSpecified ? tilt : current.tilt,
paddings: paddings,
visibleRegion: visibleRegion
)
}

public func equals(other: MapCameraPositionProtocol) -> Bool {
positionValue == GeoPoint.from(position: other.position) &&
zoomEquals(other) &&
Expand All @@ -92,11 +124,12 @@ public final class MapCameraPosition: MapCameraPositionProtocol {
paddings: MapPaddingsProtocol? = nil,
visibleRegion: VisibleRegion? = nil
) -> MapCameraPosition {
// 渡されなかったプロパティは、未指定だったならコピー後も未指定のまま。
MapCameraPosition(
position: position ?? self.positionValue,
zoom: zoom ?? self.zoom,
bearing: bearing ?? self.bearing,
tilt: tilt ?? self.tilt,
zoom: zoom ?? (isZoomSpecified ? self.zoom : nil),
bearing: bearing ?? (isBearingSpecified ? self.bearing : nil),
tilt: tilt ?? (isTiltSpecified ? self.tilt : nil),
paddings: paddings ?? self.paddings,
visibleRegion: visibleRegion ?? self.visibleRegion
)
Expand Down
6 changes: 3 additions & 3 deletions Sources/MapConductorCore/map/MapViewState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,9 @@ open class MapViewState<ActualMapDesignType>: ObservableObject, MapViewStateProt
}

open func moveCameraTo(cameraPosition: MapCameraPosition, durationMillis: Long? = 0) {
// カメラを渡されたらそのまま使う。「位置だけ動かして縮尺は保つ」は
// `moveCameraTo(position:)` のオーバーロードが担う。
let resolved = cameraPosition
// 明示されたプロパティは 0 でもそのまま使い、未指定のものだけ現在のカメラから
// 引き継ぐ。`MapCameraPosition(position:)` なら位置だけが動く。
let resolved = cameraPosition.resolved(onto: storedCameraPosition)
guard let controller = attachedMapController else {
// まだ地図が無い。接続時に attachController がこの位置へ移動する。
storedCameraPosition = resolved
Expand Down
77 changes: 77 additions & 0 deletions Tests/MapConductorCoreTests/CameraPositionResolveTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import XCTest

@testable import MapConductorCore

/// 未指定の zoom / bearing / tilt だけが現在のカメラから引き継がれ、
/// 明示された 0 はそのまま使われることを確認する。
final class CameraPositionResolveTests: XCTestCase {
private let tokyo = GeoPoint(latitude: 35.6812, longitude: 139.7671)
private let osaka = GeoPoint(latitude: 34.7025, longitude: 135.4959)

private var current: MapCameraPosition {
MapCameraPosition(position: tokyo, zoom: 12, bearing: 30, tilt: 45)
}

func testPositionOnlyKeepsEverythingElse() {
let resolved = MapCameraPosition(position: osaka).resolved(onto: current)
XCTAssertEqual(GeoPoint.from(position: resolved.position), osaka)
XCTAssertEqual(resolved.zoom, 12)
XCTAssertEqual(resolved.bearing, 30)
XCTAssertEqual(resolved.tilt, 45)
}

func testExplicitZeroIsHonoured() {
let resolved = MapCameraPosition(position: osaka, zoom: 0, bearing: 0, tilt: 0)
.resolved(onto: current)
XCTAssertEqual(resolved.zoom, 0)
XCTAssertEqual(resolved.bearing, 0)
XCTAssertEqual(resolved.tilt, 0)
}

func testEachPropertyIsInheritedOnItsOwn() {
let zoomOnly = MapCameraPosition(position: osaka, zoom: 0).resolved(onto: current)
XCTAssertEqual(zoomOnly.zoom, 0)
XCTAssertEqual(zoomOnly.bearing, 30)
XCTAssertEqual(zoomOnly.tilt, 45)

let bearingOnly = MapCameraPosition(position: osaka, bearing: 0).resolved(onto: current)
XCTAssertEqual(bearingOnly.zoom, 12)
XCTAssertEqual(bearingOnly.bearing, 0)
XCTAssertEqual(bearingOnly.tilt, 45)

let tiltOnly = MapCameraPosition(position: osaka, tilt: 0).resolved(onto: current)
XCTAssertEqual(tiltOnly.zoom, 12)
XCTAssertEqual(tiltOnly.bearing, 30)
XCTAssertEqual(tiltOnly.tilt, 0)
}

func testUnspecifiedReadsAsZeroBeforeResolving() {
let camera = MapCameraPosition(position: osaka)
XCTAssertEqual(camera.zoom, 0)
XCTAssertFalse(camera.isZoomSpecified)
XCTAssertFalse(camera.isBearingSpecified)
XCTAssertFalse(camera.isTiltSpecified)
XCTAssertTrue(MapCameraPosition.Default.isZoomSpecified)
}

func testCopyKeepsUnspecifiedUnlessGiven() {
let copied = MapCameraPosition(position: osaka).copy(position: tokyo)
XCTAssertFalse(copied.isZoomSpecified)

let withZoom = MapCameraPosition(position: osaka).copy(zoom: 0)
XCTAssertTrue(withZoom.isZoomSpecified)
XCTAssertFalse(withZoom.isBearingSpecified)
}

func testMapViewStateMergesUnsetPropertiesFromCurrentCamera() {
let state = MapViewState<Int>(initialCameraPosition: current)

state.moveCameraTo(cameraPosition: MapCameraPosition(position: osaka, bearing: 0))
XCTAssertEqual(state.cameraPosition.zoom, 12)
XCTAssertEqual(state.cameraPosition.bearing, 0)
XCTAssertEqual(state.cameraPosition.tilt, 45)

state.moveCameraTo(cameraPosition: MapCameraPosition(position: osaka, zoom: 0))
XCTAssertEqual(state.cameraPosition.zoom, 0)
}
}
6 changes: 5 additions & 1 deletion api/MapConductorCore.api.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1204,7 +1204,11 @@ final public class MapCameraPosition : MapConductorCore::MapCameraPositionProtoc
final public let tilt: Swift::Double
final public let paddings: (any MapConductorCore::MapPaddingsProtocol)?
final public let visibleRegion: MapConductorCore::VisibleRegion?
public init(position: any MapConductorCore::GeoPointProtocol, zoom: Swift::Double = 0.0, bearing: Swift::Double = 0.0, tilt: Swift::Double = 0.0, paddings: (any MapConductorCore::MapPaddingsProtocol)? = MapPaddings.Zeros, visibleRegion: MapConductorCore::VisibleRegion? = nil)
final public let isZoomSpecified: Swift::Bool
final public let isBearingSpecified: Swift::Bool
final public let isTiltSpecified: Swift::Bool
public init(position: any MapConductorCore::GeoPointProtocol, zoom: Swift::Double? = nil, bearing: Swift::Double? = nil, tilt: Swift::Double? = nil, paddings: (any MapConductorCore::MapPaddingsProtocol)? = MapPaddings.Zeros, visibleRegion: MapConductorCore::VisibleRegion? = nil)
final public func resolved(onto current: any MapConductorCore::MapCameraPositionProtocol) -> MapConductorCore::MapCameraPosition
final public func equals(other: any MapConductorCore::MapCameraPositionProtocol) -> Swift::Bool
final public func copy(position: (any MapConductorCore::GeoPointProtocol)? = nil, zoom: Swift::Double? = nil, bearing: Swift::Double? = nil, tilt: Swift::Double? = nil, paddings: (any MapConductorCore::MapPaddingsProtocol)? = nil, visibleRegion: MapConductorCore::VisibleRegion? = nil) -> MapConductorCore::MapCameraPosition
final public func hashCode() -> Swift::Int
Expand Down
Loading