@react-native-kakao/map 2.2.6 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +5 -38
- package/RNCKakaoMap.podspec +16 -52
- package/android/build.gradle +12 -107
- package/android/consumer-rules.pro +2 -0
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/java/net/mjstudio/rnkakao/map/CameraOptions.kt +61 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/KakaoMapModule.kt +131 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/KakaoMapView.kt +1257 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapCameraCommands.kt +306 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapDimScreen.kt +431 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapGui.kt +899 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapImageLoader.kt +232 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapLabels.kt +1572 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapOptions.kt +360 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapRoutes.kt +805 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapShapes.kt +1096 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapUtilities.kt +495 -0
- package/expo-module.config.json +5 -0
- package/ios/CameraOptions.swift +41 -0
- package/ios/KakaoMapModule.swift +204 -0
- package/ios/KakaoMapView.swift +1018 -0
- package/ios/MapCameraCommands.swift +451 -0
- package/ios/MapDimScreen.swift +463 -0
- package/ios/MapGui.swift +1099 -0
- package/ios/MapImageLoader.swift +177 -0
- package/ios/MapLabels.swift +1676 -0
- package/ios/MapOptions.swift +374 -0
- package/ios/MapRoutes.swift +903 -0
- package/ios/MapShapes.swift +1294 -0
- package/ios/MapUnits.swift +20 -0
- package/ios/MapUtilities.swift +365 -0
- package/lib/commonjs/index.js +180 -29
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +18 -0
- package/lib/commonjs/index.web.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/types-assets.js +2 -0
- package/lib/commonjs/types-assets.js.map +1 -0
- package/lib/commonjs/types-camera.js +6 -0
- package/lib/commonjs/types-camera.js.map +1 -0
- package/lib/commonjs/types-dim.js +6 -0
- package/lib/commonjs/types-dim.js.map +1 -0
- package/lib/commonjs/types-gui.js +6 -0
- package/lib/commonjs/types-gui.js.map +1 -0
- package/lib/commonjs/types-labels.js +6 -0
- package/lib/commonjs/types-labels.js.map +1 -0
- package/lib/commonjs/types-routes.js +6 -0
- package/lib/commonjs/types-routes.js.map +1 -0
- package/lib/commonjs/types-shapes.js +6 -0
- package/lib/commonjs/types-shapes.js.map +1 -0
- package/lib/commonjs/types-utilities.js +6 -0
- package/lib/commonjs/types-utilities.js.map +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/index.js +180 -22
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +13 -0
- package/lib/module/index.web.js.map +1 -0
- package/lib/module/types-assets.js +2 -0
- package/lib/module/types-assets.js.map +1 -0
- package/lib/module/types-camera.js +4 -0
- package/lib/module/types-camera.js.map +1 -0
- package/lib/module/types-dim.js +4 -0
- package/lib/module/types-dim.js.map +1 -0
- package/lib/module/types-gui.js +4 -0
- package/lib/module/types-gui.js.map +1 -0
- package/lib/module/types-labels.js +4 -0
- package/lib/module/types-labels.js.map +1 -0
- package/lib/module/types-routes.js +4 -0
- package/lib/module/types-routes.js.map +1 -0
- package/lib/module/types-shapes.js +4 -0
- package/lib/module/types-shapes.js.map +1 -0
- package/lib/module/types-utilities.js +4 -0
- package/lib/module/types-utilities.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/src/index.d.ts +59 -9
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types-assets.d.ts +9 -0
- package/lib/typescript/src/types-assets.d.ts.map +1 -0
- package/lib/typescript/src/types-camera.d.ts +123 -0
- package/lib/typescript/src/types-camera.d.ts.map +1 -0
- package/lib/typescript/src/types-dim.d.ts +54 -0
- package/lib/typescript/src/types-dim.d.ts.map +1 -0
- package/lib/typescript/src/types-gui.d.ts +189 -0
- package/lib/typescript/src/types-gui.d.ts.map +1 -0
- package/lib/typescript/src/types-labels.d.ts +246 -0
- package/lib/typescript/src/types-labels.d.ts.map +1 -0
- package/lib/typescript/src/types-routes.d.ts +162 -0
- package/lib/typescript/src/types-routes.d.ts.map +1 -0
- package/lib/typescript/src/types-shapes.d.ts +253 -0
- package/lib/typescript/src/types-shapes.d.ts.map +1 -0
- package/lib/typescript/src/types-utilities.d.ts +130 -0
- package/lib/typescript/src/types-utilities.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +222 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +24 -77
- package/src/index.tsx +322 -0
- package/src/index.web.tsx +46 -0
- package/src/types-assets.ts +8 -0
- package/src/types-camera.ts +97 -0
- package/src/types-dim.ts +54 -0
- package/src/types-gui.ts +171 -0
- package/src/types-labels.ts +207 -0
- package/src/types-routes.ts +118 -0
- package/src/types-shapes.ts +244 -0
- package/src/types-utilities.ts +115 -0
- package/src/types.ts +223 -0
- package/android/gradle.properties +0 -1
- package/android/src/main/AndroidManifestNew.xml +0 -2
- package/android/src/main/java/net/mjstudio/rnkakao/map/RNCKakaoMapModule.kt +0 -25
- package/android/src/main/java/net/mjstudio/rnkakao/map/RNCKakaoMapPackage.kt +0 -35
- package/android/src/newarch/KakaoMapSpec.kt +0 -7
- package/android/src/oldarch/KakaoMapSpec.kt +0 -14
- package/ios/Component/Map/RNCKakaoMapView.h +0 -23
- package/ios/Component/Map/RNCKakaoMapView.mm +0 -209
- package/ios/Component/Map/RNCKakaoMapViewManager.mm +0 -19
- package/ios/NativeModule/RNCKakaoMap.h +0 -7
- package/ios/NativeModule/RNCKakaoMap.mm +0 -19
- package/ios/NativeModule/RNCKakaoMapManager.swift +0 -7
- package/ios/Util/Util.h +0 -97
- package/lib/commonjs/component/KakaoMapView.js +0 -54
- package/lib/commonjs/component/KakaoMapView.js.map +0 -1
- package/lib/commonjs/spec/NativeKakaoMap.js +0 -9
- package/lib/commonjs/spec/NativeKakaoMap.js.map +0 -1
- package/lib/commonjs/spec/RNCKakaoMapViewNativeComponent.js +0 -15
- package/lib/commonjs/spec/RNCKakaoMapViewNativeComponent.js.map +0 -1
- package/lib/commonjs/type/type.js +0 -2
- package/lib/commonjs/type/type.js.map +0 -1
- package/lib/commonjs/util/Const.js +0 -14
- package/lib/commonjs/util/Const.js.map +0 -1
- package/lib/commonjs/util/Util.js +0 -15
- package/lib/commonjs/util/Util.js.map +0 -1
- package/lib/module/component/KakaoMapView.js +0 -48
- package/lib/module/component/KakaoMapView.js.map +0 -1
- package/lib/module/spec/NativeKakaoMap.js +0 -3
- package/lib/module/spec/NativeKakaoMap.js.map +0 -1
- package/lib/module/spec/RNCKakaoMapViewNativeComponent.js +0 -10
- package/lib/module/spec/RNCKakaoMapViewNativeComponent.js.map +0 -1
- package/lib/module/type/type.js +0 -2
- package/lib/module/type/type.js.map +0 -1
- package/lib/module/util/Const.js +0 -8
- package/lib/module/util/Const.js.map +0 -1
- package/lib/module/util/Util.js +0 -9
- package/lib/module/util/Util.js.map +0 -1
- package/lib/typescript/src/component/KakaoMapView.d.ts +0 -25
- package/lib/typescript/src/component/KakaoMapView.d.ts.map +0 -1
- package/lib/typescript/src/spec/NativeKakaoMap.d.ts +0 -7
- package/lib/typescript/src/spec/NativeKakaoMap.d.ts.map +0 -1
- package/lib/typescript/src/spec/RNCKakaoMapViewNativeComponent.d.ts +0 -32
- package/lib/typescript/src/spec/RNCKakaoMapViewNativeComponent.d.ts.map +0 -1
- package/lib/typescript/src/type/type.d.ts +0 -8
- package/lib/typescript/src/type/type.d.ts.map +0 -1
- package/lib/typescript/src/util/Const.d.ts +0 -8
- package/lib/typescript/src/util/Const.d.ts.map +0 -1
- package/lib/typescript/src/util/Util.d.ts +0 -3
- package/lib/typescript/src/util/Util.d.ts.map +0 -1
- package/src/component/KakaoMapView.tsx +0 -82
- package/src/index.ts +0 -43
- package/src/spec/NativeKakaoMap.ts +0 -8
- package/src/spec/RNCKakaoMapViewNativeComponent.ts +0 -38
- package/src/type/type.ts +0 -7
- package/src/util/Const.ts +0 -7
- package/src/util/Util.ts +0 -11
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import CoreFoundation
|
|
2
|
+
import CoreGraphics
|
|
3
|
+
import Foundation
|
|
4
|
+
import KakaoMapsSDK
|
|
5
|
+
|
|
6
|
+
private let cameraUnsupported = "ERR_KAKAO_MAP_UNSUPPORTED_PLATFORM_FEATURE"
|
|
7
|
+
private let cameraDestroyed = "ERR_KAKAO_MAP_VIEW_DESTROYED"
|
|
8
|
+
private let maximumPendingCameraOperations = 64
|
|
9
|
+
|
|
10
|
+
final class MapCameraCommands {
|
|
11
|
+
private weak var map: KakaoMap?
|
|
12
|
+
private let emitOperation: ([String: Any]) -> Void
|
|
13
|
+
private var generation: UInt = 0
|
|
14
|
+
private var operations: [String: UInt] = [:]
|
|
15
|
+
private var nextToken: UInt = 0
|
|
16
|
+
private var disposed = false
|
|
17
|
+
|
|
18
|
+
init(map: KakaoMap, emitOperation: @escaping ([String: Any]) -> Void) {
|
|
19
|
+
self.map = map
|
|
20
|
+
self.emitOperation = emitOperation
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func command(_ command: [String: Any]) throws -> [String: Any] {
|
|
24
|
+
try requireMainThread()
|
|
25
|
+
let type = try command.string("type")
|
|
26
|
+
switch type {
|
|
27
|
+
case "getCapabilities": return capabilities()
|
|
28
|
+
case "getState": return try ["type": "state", "camera": state(requireMap())]
|
|
29
|
+
case "moveToHeight": return try moveToHeight(command)
|
|
30
|
+
case "transform": return try transform(command)
|
|
31
|
+
case "fit": return try fit(command)
|
|
32
|
+
case "canShow": return try canShow(command)
|
|
33
|
+
case "heightAtLevel": return try heightAtLevel(command)
|
|
34
|
+
case "resetOrientation": return try resetOrientation(command)
|
|
35
|
+
case "setBackToNorthDuringAutoElevation": return try setBackToNorth(command)
|
|
36
|
+
case "setReferencePointLock": return try setReferencePointLock(command)
|
|
37
|
+
default: throw invalidCamera("Unsupported advanced camera command: \(type).")
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func dispose() {
|
|
42
|
+
precondition(Thread.isMainThread, "MapCameraCommands.dispose() must run on the main thread.")
|
|
43
|
+
disposed = true
|
|
44
|
+
generation &+= 1
|
|
45
|
+
operations.removeAll()
|
|
46
|
+
map = nil
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func supersedePendingOperations() {
|
|
50
|
+
supersedeOperations(except: nil)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private func moveToHeight(_ command: [String: Any]) throws -> [String: Any] {
|
|
54
|
+
let camera = try command.object("camera")
|
|
55
|
+
let latitude = try camera.finiteNumber("latitude")
|
|
56
|
+
let longitude = try camera.finiteNumber("longitude")
|
|
57
|
+
let height = try camera.finiteNumber("height")
|
|
58
|
+
let tilt = try camera.finiteNumber("tilt")
|
|
59
|
+
let rotation = try camera.finiteNumber("rotation")
|
|
60
|
+
try validateCoordinate(latitude: latitude, longitude: longitude)
|
|
61
|
+
guard height > 0, (0 ... 90).contains(tilt) else {
|
|
62
|
+
throw invalidCamera(
|
|
63
|
+
"Camera height must be positive and tilt must be between 0 and 90 degrees."
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
let position = CameraPosition(
|
|
67
|
+
target: MapPoint(longitude: longitude, latitude: latitude), height: height,
|
|
68
|
+
rotation: rotation.normalizedDegrees * .pi / 180, tilt: tilt * .pi / 180
|
|
69
|
+
)
|
|
70
|
+
return try dispatch(CameraUpdate.make(cameraPosition: position), command: command)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private func transform(_ command: [String: Any]) throws -> [String: Any] {
|
|
74
|
+
let delta = try command.object("delta")
|
|
75
|
+
let supportedFields: Set = ["latitude", "longitude", "height", "rotation", "tilt"]
|
|
76
|
+
guard !delta.isEmpty, Set(delta.keys).isSubset(of: supportedFields) else {
|
|
77
|
+
throw invalidCamera("Camera transform delta must contain only supported transform fields.")
|
|
78
|
+
}
|
|
79
|
+
let transform = try CameraTransform(
|
|
80
|
+
deltaPos: CameraTransformDelta(
|
|
81
|
+
deltaLon: delta.optionalFiniteNumber("longitude") ?? 0,
|
|
82
|
+
deltaLat: delta.optionalFiniteNumber("latitude") ?? 0
|
|
83
|
+
),
|
|
84
|
+
deltaHeight: delta.optionalFiniteNumber("height") ?? 0,
|
|
85
|
+
deltaRotation: (delta.optionalFiniteNumber("rotation") ?? 0) * (.pi / 180),
|
|
86
|
+
deltaTilt: (delta.optionalFiniteNumber("tilt") ?? 0) * (.pi / 180)
|
|
87
|
+
)
|
|
88
|
+
return try dispatch(CameraUpdate.make(transform: transform), command: command)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private func fit(_ command: [String: Any]) throws -> [String: Any] {
|
|
92
|
+
let points = try command.coordinates("coordinates")
|
|
93
|
+
if let padding = try command.optionalFiniteNumber("padding"), padding != 0 {
|
|
94
|
+
throw unsupportedCamera("Fit padding is not exposed by Kakao Maps iOS SDK 2.12.19.")
|
|
95
|
+
}
|
|
96
|
+
let map = try requireMap()
|
|
97
|
+
let levelLimit = try command.optionalInteger("levelLimit")
|
|
98
|
+
if let levelLimit, !(map.cameraMinLevel ... map.cameraMaxLevel).contains(levelLimit) {
|
|
99
|
+
throw invalidCamera("Camera fit levelLimit is outside the configured camera range.")
|
|
100
|
+
}
|
|
101
|
+
return try dispatch(
|
|
102
|
+
CameraUpdate.make(area: AreaRect(points: points), levelLimit: levelLimit ?? -1),
|
|
103
|
+
command: command
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private func canShow(_ command: [String: Any]) throws -> [String: Any] {
|
|
108
|
+
let map = try requireMap()
|
|
109
|
+
let level = try command.integer("zoomLevel")
|
|
110
|
+
guard (map.cameraMinLevel ... map.cameraMaxLevel).contains(level) else {
|
|
111
|
+
throw invalidCamera("canShow zoomLevel is outside the configured camera range.")
|
|
112
|
+
}
|
|
113
|
+
let rotation = try command.optionalFiniteNumber("rotation") ?? 0
|
|
114
|
+
let weight = try command.optionalFiniteNumber("weight") ?? 1
|
|
115
|
+
guard weight > 0, weight <= Double(Float.greatestFiniteMagnitude) else {
|
|
116
|
+
throw invalidCamera("canShow weight must be a positive finite Float value.")
|
|
117
|
+
}
|
|
118
|
+
let rect: CGRect? = try command["rect"].map { raw in
|
|
119
|
+
guard let value = raw as? [String: Any]
|
|
120
|
+
else { throw invalidCamera("rect must be an object.") }
|
|
121
|
+
let x = try value.finiteNumber("x")
|
|
122
|
+
let y = try value.finiteNumber("y")
|
|
123
|
+
let width = try value.finiteNumber("width")
|
|
124
|
+
let height = try value.finiteNumber("height")
|
|
125
|
+
guard x >= 0, y >= 0, width >= 0, height >= 0,
|
|
126
|
+
x + width <= map.viewRect.width, y + height <= map.viewRect.height
|
|
127
|
+
else {
|
|
128
|
+
throw invalidCamera("rect must be inside the map viewport.")
|
|
129
|
+
}
|
|
130
|
+
return CGRect(x: x, y: y, width: width, height: height)
|
|
131
|
+
}
|
|
132
|
+
return try [
|
|
133
|
+
"type": "canShow",
|
|
134
|
+
"value": map.canShow(
|
|
135
|
+
mapPoints: command.coordinates("coordinates"), atLevel: level,
|
|
136
|
+
rotationAngle: rotation.normalizedDegrees * .pi / 180, weight: Float(weight), inRect: rect
|
|
137
|
+
)
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private func heightAtLevel(_ command: [String: Any]) throws -> [String: Any] {
|
|
142
|
+
let map = try requireMap()
|
|
143
|
+
let level = try command.integer("zoomLevel")
|
|
144
|
+
guard (map.minLevel ... map.maxLevel).contains(level) else {
|
|
145
|
+
throw invalidCamera("heightAtLevel zoomLevel is outside the SDK map range.")
|
|
146
|
+
}
|
|
147
|
+
return ["type": "heightAtLevel", "height": map.heightAtLevel(level)]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private func resetOrientation(_ command: [String: Any]) throws -> [String: Any] {
|
|
151
|
+
let map = try requireMap()
|
|
152
|
+
let parsed = try animation(command)
|
|
153
|
+
let operation = try beginOperation(command, animation: parsed)
|
|
154
|
+
if parsed.options.consecutive.boolValue == false {
|
|
155
|
+
supersedeOperations(except: operation?.id)
|
|
156
|
+
}
|
|
157
|
+
map.resetCameraOrientation(parsed.options) { [weak self] in
|
|
158
|
+
self?.complete(operation)
|
|
159
|
+
}
|
|
160
|
+
return dispatched(operation?.id)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private func setBackToNorth(_ command: [String: Any]) throws -> [String: Any] {
|
|
164
|
+
try requireMap().setBackToNorthDuringAutoElevation(command.bool("enabled"))
|
|
165
|
+
return ["type": "applied"]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private func setReferencePointLock(_ command: [String: Any]) throws -> [String: Any] {
|
|
169
|
+
let map = try requireMap()
|
|
170
|
+
let gesture = try referenceGesture(command.string("gesture"))
|
|
171
|
+
if try command.bool("locked") {
|
|
172
|
+
map.lockReferencePoint(forGesture: gesture)
|
|
173
|
+
} else {
|
|
174
|
+
map.unlockReferencePoint(forGesture: gesture)
|
|
175
|
+
}
|
|
176
|
+
return ["type": "applied"]
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private func dispatch(_ update: CameraUpdate, command: [String: Any]) throws -> [String: Any] {
|
|
180
|
+
let map = try requireMap()
|
|
181
|
+
let parsed = try animation(command)
|
|
182
|
+
let operation = try beginOperation(command, animation: parsed)
|
|
183
|
+
if parsed.options.durationInMillis == 0 || parsed.options.consecutive.boolValue == false {
|
|
184
|
+
supersedeOperations(except: operation?.id)
|
|
185
|
+
}
|
|
186
|
+
let callback: () -> Void = { [weak self] in
|
|
187
|
+
self?.complete(operation)
|
|
188
|
+
}
|
|
189
|
+
if parsed.options.durationInMillis == 0 {
|
|
190
|
+
map.moveCamera(update, callback: callback)
|
|
191
|
+
} else {
|
|
192
|
+
map.animateCamera(cameraUpdate: update, options: parsed.options, callback: callback)
|
|
193
|
+
}
|
|
194
|
+
return dispatched(operation?.id)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private func animation(_ command: [String: Any]) throws -> (
|
|
198
|
+
options: CameraAnimationOptions,
|
|
199
|
+
supplied: Bool
|
|
200
|
+
) {
|
|
201
|
+
guard let raw = command["animation"] else {
|
|
202
|
+
return (
|
|
203
|
+
CameraAnimationOptions(
|
|
204
|
+
autoElevation: ObjCBool(false),
|
|
205
|
+
consecutive: ObjCBool(false),
|
|
206
|
+
durationInMillis: 0
|
|
207
|
+
),
|
|
208
|
+
false
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
guard let value = raw as? [String: Any] else {
|
|
212
|
+
throw invalidCamera("animation must be an object.")
|
|
213
|
+
}
|
|
214
|
+
let duration = try value.integer("duration")
|
|
215
|
+
guard (0 ... Int(Int32.max)).contains(duration) else {
|
|
216
|
+
throw invalidCamera(
|
|
217
|
+
"Animation duration must be an integer between 0 and 2147483647 milliseconds."
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
return try (
|
|
221
|
+
CameraAnimationOptions(
|
|
222
|
+
autoElevation: ObjCBool(value.optionalBool("autoElevation") ?? false),
|
|
223
|
+
consecutive: ObjCBool(value.optionalBool("consecutive") ?? false),
|
|
224
|
+
durationInMillis: UInt(duration)
|
|
225
|
+
),
|
|
226
|
+
true
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private func beginOperation(
|
|
231
|
+
_ command: [String: Any],
|
|
232
|
+
animation: (options: CameraAnimationOptions, supplied: Bool)
|
|
233
|
+
) throws -> (id: String, token: UInt, generation: UInt)? {
|
|
234
|
+
guard let rawID = command["operationId"] else { return nil }
|
|
235
|
+
guard let id = rawID as? String, !id.isEmpty else {
|
|
236
|
+
throw invalidCamera("operationId must be a non-empty string.")
|
|
237
|
+
}
|
|
238
|
+
guard animation.supplied, animation.options.durationInMillis > 0 else {
|
|
239
|
+
throw invalidCamera("operationId requires an animation with a positive duration.")
|
|
240
|
+
}
|
|
241
|
+
guard operations[id] == nil else {
|
|
242
|
+
throw invalidCamera("operationId must be unique while pending.")
|
|
243
|
+
}
|
|
244
|
+
guard operations.count < maximumPendingCameraOperations else {
|
|
245
|
+
throw KakaoMapException(
|
|
246
|
+
"ERR_KAKAO_MAP_CAMERA_OPERATION_LIMIT",
|
|
247
|
+
"Too many camera operations are pending."
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
nextToken &+= 1
|
|
251
|
+
operations[id] = nextToken
|
|
252
|
+
return (id, nextToken, generation)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private func supersedeOperations(except preservedID: String?) {
|
|
256
|
+
for id in Array(operations.keys) where id != preservedID {
|
|
257
|
+
operations[id] = nil
|
|
258
|
+
emitOperation(["operationId": id, "status": "superseded"])
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private func complete(_ operation: (id: String, token: UInt, generation: UInt)?) {
|
|
263
|
+
guard let operation else { return }
|
|
264
|
+
let body = { [weak self] in
|
|
265
|
+
guard let self, !self.disposed, generation == operation.generation,
|
|
266
|
+
operations[operation.id] == operation.token else { return }
|
|
267
|
+
operations[operation.id] = nil
|
|
268
|
+
guard let map else { return }
|
|
269
|
+
emitOperation([
|
|
270
|
+
"operationId": operation.id,
|
|
271
|
+
"status": "completed",
|
|
272
|
+
"camera": state(map)
|
|
273
|
+
])
|
|
274
|
+
}
|
|
275
|
+
if Thread.isMainThread {
|
|
276
|
+
body()
|
|
277
|
+
} else {
|
|
278
|
+
DispatchQueue.main.async(execute: body)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private func state(_ map: KakaoMap) -> [String: Any] {
|
|
283
|
+
let margins = map.margins
|
|
284
|
+
let center = CGPoint(
|
|
285
|
+
x: (margins.left + map.viewRect.width - margins.right) / 2,
|
|
286
|
+
y: (margins.top + map.viewRect.height - margins.bottom) / 2
|
|
287
|
+
)
|
|
288
|
+
let coordinate = map.getPosition(center).wgsCoord
|
|
289
|
+
return [
|
|
290
|
+
"latitude": coordinate.latitude,
|
|
291
|
+
"longitude": coordinate.longitude,
|
|
292
|
+
"zoomLevel": map.zoomLevel,
|
|
293
|
+
"height": map.cameraHeight,
|
|
294
|
+
"tilt": map.tiltAngle * 180 / .pi,
|
|
295
|
+
"rotation": (map.rotationAngle * 180 / .pi).normalizedDegrees
|
|
296
|
+
]
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private func capabilities() -> [String: Any] {
|
|
300
|
+
[
|
|
301
|
+
"type": "capabilities",
|
|
302
|
+
"capabilities": [
|
|
303
|
+
"transform": true,
|
|
304
|
+
"absoluteHeight": true,
|
|
305
|
+
"heightAtLevel": true,
|
|
306
|
+
"fit": ["padding": false, "levelLimit": true],
|
|
307
|
+
"canShow": ["basic": true, "rotation": true, "weight": true, "rect": true],
|
|
308
|
+
"resetOrientation": true,
|
|
309
|
+
"backToNorthDuringAutoElevation": true,
|
|
310
|
+
"animation": [
|
|
311
|
+
"autoElevation": true,
|
|
312
|
+
"consecutive": true,
|
|
313
|
+
"operationCompletion": true
|
|
314
|
+
],
|
|
315
|
+
"referencePoint": ["supported": true, "gestures": referenceGestures]
|
|
316
|
+
]
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private func requireMap() throws -> KakaoMap {
|
|
321
|
+
guard !disposed, let map else {
|
|
322
|
+
throw KakaoMapException(cameraDestroyed, "The map view was destroyed.")
|
|
323
|
+
}
|
|
324
|
+
return map
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private let referenceGestures = ["rotate", "zoom", "tilt", "rotateZoom", "oneFingerZoom"]
|
|
329
|
+
|
|
330
|
+
private func referenceGesture(_ value: String) throws -> GestureType {
|
|
331
|
+
switch value {
|
|
332
|
+
case "rotate": .rotate
|
|
333
|
+
case "zoom": .zoom
|
|
334
|
+
case "tilt": .tilt
|
|
335
|
+
case "rotateZoom": .rotateZoom
|
|
336
|
+
case "oneFingerZoom": .oneFingerZoom
|
|
337
|
+
default: throw invalidCamera("Unsupported reference-point gesture: \(value).")
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private func dispatched(_ operationID: String?) -> [String: Any] {
|
|
342
|
+
var payload: [String: Any] = ["type": "dispatched"]
|
|
343
|
+
if let operationID {
|
|
344
|
+
payload["operationId"] = operationID
|
|
345
|
+
}
|
|
346
|
+
return payload
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private func validateCoordinate(latitude: Double, longitude: Double) throws {
|
|
350
|
+
guard (-90 ... 90).contains(latitude), (-180 ... 180).contains(longitude) else {
|
|
351
|
+
throw invalidCamera("Coordinate latitude or longitude is outside its valid range.")
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private extension [String: Any] {
|
|
356
|
+
func string(_ name: String) throws -> String {
|
|
357
|
+
guard let value = self[name] as? String else {
|
|
358
|
+
throw invalidCamera("\(name) must be a string.")
|
|
359
|
+
}
|
|
360
|
+
return value
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
func bool(_ name: String) throws -> Bool {
|
|
364
|
+
guard let value = self[name] as? NSNumber, CFGetTypeID(value) == CFBooleanGetTypeID() else {
|
|
365
|
+
throw invalidCamera("\(name) must be a boolean.")
|
|
366
|
+
}
|
|
367
|
+
return value.boolValue
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
func optionalBool(_ name: String) throws -> Bool? {
|
|
371
|
+
guard let raw = self[name] else { return nil }
|
|
372
|
+
guard let value = raw as? NSNumber, CFGetTypeID(value) == CFBooleanGetTypeID() else {
|
|
373
|
+
throw invalidCamera("\(name) must be a boolean.")
|
|
374
|
+
}
|
|
375
|
+
return value.boolValue
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
func object(_ name: String) throws -> [String: Any] {
|
|
379
|
+
guard let value = self[name] as? [String: Any] else {
|
|
380
|
+
throw invalidCamera("\(name) must be an object.")
|
|
381
|
+
}
|
|
382
|
+
return value
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
func finiteNumber(_ name: String) throws -> Double {
|
|
386
|
+
guard let value = try optionalFiniteNumber(name) else {
|
|
387
|
+
throw invalidCamera("\(name) must be a finite number.")
|
|
388
|
+
}
|
|
389
|
+
return value
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
func optionalFiniteNumber(_ name: String) throws -> Double? {
|
|
393
|
+
guard let raw = self[name] else { return nil }
|
|
394
|
+
guard let number = raw as? NSNumber, CFGetTypeID(number) != CFBooleanGetTypeID() else {
|
|
395
|
+
throw invalidCamera("\(name) must be a finite number.")
|
|
396
|
+
}
|
|
397
|
+
let value = number.doubleValue
|
|
398
|
+
guard value.isFinite else { throw invalidCamera("\(name) must be a finite number.") }
|
|
399
|
+
return value
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
func integer(_ name: String) throws -> Int {
|
|
403
|
+
guard let value = try optionalInteger(name) else {
|
|
404
|
+
throw invalidCamera("\(name) must be an integer.")
|
|
405
|
+
}
|
|
406
|
+
return value
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
func optionalInteger(_ name: String) throws -> Int? {
|
|
410
|
+
guard let value = try optionalFiniteNumber(name) else { return nil }
|
|
411
|
+
guard let integer = Int(exactly: value) else {
|
|
412
|
+
throw invalidCamera("\(name) must be an integer.")
|
|
413
|
+
}
|
|
414
|
+
return integer
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
func coordinates(_ name: String) throws -> [MapPoint] {
|
|
418
|
+
guard let values = self[name] as? [[String: Any]], !values.isEmpty else {
|
|
419
|
+
throw invalidCamera("\(name) must contain at least one coordinate.")
|
|
420
|
+
}
|
|
421
|
+
return try values.map { coordinate in
|
|
422
|
+
let latitude = try coordinate.finiteNumber("latitude")
|
|
423
|
+
let longitude = try coordinate.finiteNumber("longitude")
|
|
424
|
+
try validateCoordinate(latitude: latitude, longitude: longitude)
|
|
425
|
+
return MapPoint(longitude: longitude, latitude: latitude)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
private extension Double {
|
|
431
|
+
var normalizedDegrees: Double {
|
|
432
|
+
(truncatingRemainder(dividingBy: 360) + 360).truncatingRemainder(dividingBy: 360)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private func requireMainThread() throws {
|
|
437
|
+
guard Thread.isMainThread else {
|
|
438
|
+
throw KakaoMapException(
|
|
439
|
+
"ERR_KAKAO_MAP_WRONG_THREAD",
|
|
440
|
+
"Advanced camera commands must run on the main thread."
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private func invalidCamera(_ message: String) -> KakaoMapException {
|
|
446
|
+
KakaoMapException(invalidCameraArgument, message)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private func unsupportedCamera(_ message: String) -> KakaoMapException {
|
|
450
|
+
KakaoMapException(cameraUnsupported, message)
|
|
451
|
+
}
|