@react-native-kakao/map 2.2.6 → 3.1.0-next.2
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 +14 -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,1018 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import KakaoMapsSDK
|
|
3
|
+
|
|
4
|
+
final class KakaoMapView: ExpoView, MapControllerDelegate, KakaoMapEventDelegate {
|
|
5
|
+
let onMapReady = EventDispatcher()
|
|
6
|
+
let onError = EventDispatcher()
|
|
7
|
+
let onCameraMoveStart = EventDispatcher()
|
|
8
|
+
let onCameraIdle = EventDispatcher()
|
|
9
|
+
let onMapClick = EventDispatcher()
|
|
10
|
+
let onMapLongClick = EventDispatcher()
|
|
11
|
+
let onTerrainClick = EventDispatcher()
|
|
12
|
+
let onViewportClick = EventDispatcher()
|
|
13
|
+
let onVisibilityChanged = EventDispatcher()
|
|
14
|
+
let onFocusChanged = EventDispatcher()
|
|
15
|
+
let onPoiClick = EventDispatcher()
|
|
16
|
+
let onCompassClick = EventDispatcher()
|
|
17
|
+
let onMapTypeChanged = EventDispatcher()
|
|
18
|
+
let onPaddingChanged = EventDispatcher()
|
|
19
|
+
let onViewportChanged = EventDispatcher()
|
|
20
|
+
let onLabelClick = EventDispatcher()
|
|
21
|
+
let onLabelAnimationStopped = EventDispatcher()
|
|
22
|
+
let onLabelsChanged = EventDispatcher()
|
|
23
|
+
let onShapeAnimationStop = EventDispatcher()
|
|
24
|
+
let onShapesChanged = EventDispatcher()
|
|
25
|
+
let onRoutesChanged = EventDispatcher()
|
|
26
|
+
let onRouteProgress = EventDispatcher()
|
|
27
|
+
let onRouteAnimationStop = EventDispatcher()
|
|
28
|
+
let onGuiEvent = EventDispatcher()
|
|
29
|
+
let onDimScreenEvent = EventDispatcher()
|
|
30
|
+
let onCameraOperation = EventDispatcher()
|
|
31
|
+
private var labels: MapLabels?
|
|
32
|
+
private var shapes: MapShapes?
|
|
33
|
+
private var routes: MapRoutes?
|
|
34
|
+
private var gui: MapGui?
|
|
35
|
+
private var dimScreen: MapDimScreen?
|
|
36
|
+
private var utilities: MapUtilities?
|
|
37
|
+
private var cameraCommands: MapCameraCommands?
|
|
38
|
+
private var imageLoader: MapImageLoader?
|
|
39
|
+
private var pendingLabels: MapLabelSceneRecord?
|
|
40
|
+
private var pendingShapes: [String: Any]?
|
|
41
|
+
private var hasUsedShapes = false
|
|
42
|
+
private var pendingRoutes: [String: Any]?
|
|
43
|
+
private var hasUsedRoutes = false
|
|
44
|
+
private var routeGeneration = 0
|
|
45
|
+
private var labelGeneration = 0
|
|
46
|
+
var pendingInitialCamera: CameraOptions?
|
|
47
|
+
var camera: CameraOptions?
|
|
48
|
+
private var initialCamera: CameraOptions?
|
|
49
|
+
private var lastCameraValues: [Double]?
|
|
50
|
+
private var pendingOptions: KakaoMapViewOptions?
|
|
51
|
+
private var appliedOptions = KakaoMapViewOptions()
|
|
52
|
+
private var semanticOverlays: Set<String> = []
|
|
53
|
+
private var customOverlays: Set<String> = []
|
|
54
|
+
private var currentViewInfoName = "map"
|
|
55
|
+
private var currentLanguage = "ko"
|
|
56
|
+
private var hasInitialProps = false
|
|
57
|
+
private var container: KMViewContainer?
|
|
58
|
+
private var controller: KMController?
|
|
59
|
+
private var map: KakaoMap?
|
|
60
|
+
private var lastEmittedViewportSize: CGSize?
|
|
61
|
+
private var isStarted = false
|
|
62
|
+
private var isDestroyed = false
|
|
63
|
+
private var isAuthenticated = false
|
|
64
|
+
private var isReady = false
|
|
65
|
+
private let viewCreationError = "ERR_KAKAO_MAP_VIEW_CREATION"
|
|
66
|
+
|
|
67
|
+
required init(appContext: AppContext? = nil) {
|
|
68
|
+
super.init(appContext: appContext)
|
|
69
|
+
NotificationCenter.default.addObserver(
|
|
70
|
+
self,
|
|
71
|
+
selector: #selector(pause),
|
|
72
|
+
name: UIApplication.willResignActiveNotification,
|
|
73
|
+
object: nil
|
|
74
|
+
)
|
|
75
|
+
NotificationCenter.default.addObserver(
|
|
76
|
+
self,
|
|
77
|
+
selector: #selector(resume),
|
|
78
|
+
name: UIApplication.didBecomeActiveNotification,
|
|
79
|
+
object: nil
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func updateProps() {
|
|
84
|
+
if !hasInitialProps {
|
|
85
|
+
hasInitialProps = true
|
|
86
|
+
initialCamera = pendingInitialCamera
|
|
87
|
+
}
|
|
88
|
+
if isReady, camera?.values != lastCameraValues {
|
|
89
|
+
if let camera {
|
|
90
|
+
applyCameraProp(camera)
|
|
91
|
+
}
|
|
92
|
+
lastCameraValues = camera?.values
|
|
93
|
+
}
|
|
94
|
+
applyPendingOptionsIfReady()
|
|
95
|
+
applyPendingShapesIfReady()
|
|
96
|
+
applyPendingRoutesIfReady()
|
|
97
|
+
applyPendingLabelsIfReady()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
func queueShapes(_ scene: [String: Any]?) {
|
|
101
|
+
if scene == nil, !hasUsedShapes {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
hasUsedShapes = true
|
|
105
|
+
pendingShapes = scene ?? [:]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func queueLabels(_ scene: MapLabelSceneRecord?) {
|
|
109
|
+
if scene == nil, labels == nil {
|
|
110
|
+
pendingLabels = nil
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
pendingLabels = scene ?? MapLabelSceneRecord()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func queueRoutes(_ scene: [String: Any]?) {
|
|
117
|
+
if scene == nil, !hasUsedRoutes {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
hasUsedRoutes = true
|
|
121
|
+
pendingRoutes = scene ?? [:]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func queueOptions(_ options: KakaoMapViewOptions?) {
|
|
125
|
+
guard let options else { return }
|
|
126
|
+
guard options.mapType == nil || options.viewInfo == nil else {
|
|
127
|
+
onError([
|
|
128
|
+
"code": invalidCameraArgument,
|
|
129
|
+
"message": "mapType and viewInfo are mutually exclusive."
|
|
130
|
+
])
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
let value = pendingOptions ?? KakaoMapViewOptions()
|
|
134
|
+
value.merge(options)
|
|
135
|
+
pendingOptions = value
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
override func layoutSubviews() {
|
|
139
|
+
super.layoutSubviews()
|
|
140
|
+
if container?.frame != bounds {
|
|
141
|
+
container?.frame = bounds
|
|
142
|
+
}
|
|
143
|
+
startIfNeeded()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
override func didMoveToWindow() {
|
|
147
|
+
super.didMoveToWindow()
|
|
148
|
+
guard window != nil else {
|
|
149
|
+
// The SDK retains its event delegate; detach it so an unmounted view can deinitialize.
|
|
150
|
+
map?.eventDelegate = nil
|
|
151
|
+
pause()
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
map?.eventDelegate = self
|
|
155
|
+
startIfNeeded()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private func startIfNeeded() {
|
|
159
|
+
guard !isDestroyed, window != nil, bounds.width > 0, bounds.height > 0 else { return }
|
|
160
|
+
if !isStarted {
|
|
161
|
+
isStarted = true
|
|
162
|
+
guard KakaoMapModule.appKey != nil else {
|
|
163
|
+
onError([
|
|
164
|
+
"code": mapNotInitialized,
|
|
165
|
+
"message": "Call initializeKakaoMapSDK before mounting the map."
|
|
166
|
+
])
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
// Creating a zero-sized container prevents the SDK engine from preparing.
|
|
170
|
+
let container = KMViewContainer(frame: bounds)
|
|
171
|
+
self.container = container
|
|
172
|
+
addSubview(container)
|
|
173
|
+
let controller = KMController(viewContainer: container)
|
|
174
|
+
self.controller = controller
|
|
175
|
+
controller.delegate = self
|
|
176
|
+
guard controller.prepareEngine() else {
|
|
177
|
+
onError([
|
|
178
|
+
"code": viewCreationError,
|
|
179
|
+
"message": "Could not prepare the map engine."
|
|
180
|
+
])
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
resume()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@objc private func pause() {
|
|
188
|
+
controller?.pauseEngine()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@objc private func resume() {
|
|
192
|
+
if !isDestroyed, window != nil, UIApplication.shared.applicationState == .active,
|
|
193
|
+
controller?.isEnginePrepared == true, controller?.isEngineActive == false
|
|
194
|
+
{
|
|
195
|
+
controller?.activateEngine()
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
func addViews() {
|
|
200
|
+
guard !isDestroyed else { return }
|
|
201
|
+
controller?.addView(MapviewInfo(
|
|
202
|
+
viewName: "kakao-map",
|
|
203
|
+
viewInfoName: "map",
|
|
204
|
+
defaultPosition: MapPoint(longitude: 127.108678, latitude: 37.402001),
|
|
205
|
+
defaultLevel: 15
|
|
206
|
+
))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
func addViewSucceeded(_ viewName: String, viewInfoName: String) {
|
|
210
|
+
guard !isDestroyed, let map = controller?.getView(viewName) as? KakaoMap else { return }
|
|
211
|
+
self.map = map
|
|
212
|
+
currentViewInfoName = viewInfoName
|
|
213
|
+
let supportedLanguages = map.getSupportedLanguages()
|
|
214
|
+
if !supportedLanguages.contains(currentLanguage) {
|
|
215
|
+
currentLanguage = supportedLanguages.contains("ko") ? "ko" : supportedLanguages.first ?? ""
|
|
216
|
+
}
|
|
217
|
+
if !currentLanguage.isEmpty {
|
|
218
|
+
map.setLanguage(currentLanguage)
|
|
219
|
+
}
|
|
220
|
+
map.eventDelegate = window != nil ? self : nil
|
|
221
|
+
containerDidResized(bounds.size)
|
|
222
|
+
notifyReady()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private func notifyReady() {
|
|
226
|
+
guard !isDestroyed, !isReady, isAuthenticated, map != nil else { return }
|
|
227
|
+
isReady = true
|
|
228
|
+
if let camera = camera ?? initialCamera {
|
|
229
|
+
applyCameraProp(camera)
|
|
230
|
+
}
|
|
231
|
+
lastCameraValues = camera?.values
|
|
232
|
+
applyPendingOptionsIfReady()
|
|
233
|
+
applyPendingShapesIfReady()
|
|
234
|
+
applyPendingRoutesIfReady()
|
|
235
|
+
applyPendingLabelsIfReady()
|
|
236
|
+
onMapReady([:])
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
func addViewFailed(_ viewName: String, viewInfoName: String) {
|
|
240
|
+
guard !isDestroyed else { return }
|
|
241
|
+
onError(["code": viewCreationError, "message": "Could not create the map view."])
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
func authenticationFailed(_ errorCode: Int, desc: String) {
|
|
245
|
+
guard !isDestroyed else { return }
|
|
246
|
+
releaseMapObjects()
|
|
247
|
+
map?.eventDelegate = nil
|
|
248
|
+
map = nil
|
|
249
|
+
isReady = false
|
|
250
|
+
isAuthenticated = false
|
|
251
|
+
onError(["code": String(errorCode), "message": desc])
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
func authenticationSucceeded() {
|
|
255
|
+
isAuthenticated = true
|
|
256
|
+
resume()
|
|
257
|
+
notifyReady()
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
func containerDidResized(_ size: CGSize) {
|
|
261
|
+
guard let map, map.viewRect.size != size else { return }
|
|
262
|
+
map.viewRect = CGRect(origin: .zero, size: size)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
func moveCamera(_ camera: CameraOptions, duration: Double) throws {
|
|
266
|
+
let map = try requireMap()
|
|
267
|
+
try dispatchCameraUpdate(camera.update(for: map), on: map, duration: duration)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private func dispatchCameraUpdate(
|
|
271
|
+
_ update: CameraUpdate,
|
|
272
|
+
on map: KakaoMap,
|
|
273
|
+
duration: Double
|
|
274
|
+
) throws {
|
|
275
|
+
guard duration.isFinite, duration >= 0, duration <= Double(Int32.max),
|
|
276
|
+
duration.rounded() == duration
|
|
277
|
+
else {
|
|
278
|
+
throw KakaoMapException(
|
|
279
|
+
invalidCameraArgument,
|
|
280
|
+
"Duration must be an integer between 0 and 2147483647 milliseconds."
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
cameraCommands?.supersedePendingOperations()
|
|
284
|
+
if duration == 0 {
|
|
285
|
+
map.moveCamera(update)
|
|
286
|
+
} else {
|
|
287
|
+
map.animateCamera(
|
|
288
|
+
cameraUpdate: update,
|
|
289
|
+
options: CameraAnimationOptions(
|
|
290
|
+
autoElevation: false,
|
|
291
|
+
consecutive: false,
|
|
292
|
+
durationInMillis: UInt(duration)
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
func getCamera() throws -> [String: Any] {
|
|
299
|
+
try cameraPayload(requireMap())
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
func updateCamera(_ options: CameraUpdateOptions, duration: Double) throws {
|
|
303
|
+
let map = try requireMap()
|
|
304
|
+
try dispatchCameraUpdate(options.update(for: map), on: map, duration: duration)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
func getMapInfo() throws -> [String: Any] {
|
|
308
|
+
let map = try requireMap()
|
|
309
|
+
return [
|
|
310
|
+
"mapType": currentViewInfoName == "skyview" ? "skyview" : "normal",
|
|
311
|
+
"viewInfoName": currentViewInfoName,
|
|
312
|
+
"zoomRange": ["min": map.cameraMinLevel, "max": map.cameraMaxLevel],
|
|
313
|
+
"padding": [
|
|
314
|
+
"top": map.margins.top,
|
|
315
|
+
"right": map.margins.right,
|
|
316
|
+
"bottom": map.margins.bottom,
|
|
317
|
+
"left": map.margins.left
|
|
318
|
+
],
|
|
319
|
+
"size": ["width": map.viewRect.width, "height": map.viewRect.height],
|
|
320
|
+
"viewport": [
|
|
321
|
+
"x": map.viewRect.origin.x,
|
|
322
|
+
"y": map.viewRect.origin.y,
|
|
323
|
+
"width": map.viewRect.width,
|
|
324
|
+
"height": map.viewRect.height
|
|
325
|
+
],
|
|
326
|
+
"language": currentLanguage,
|
|
327
|
+
"supportedLanguages": map.getSupportedLanguages(),
|
|
328
|
+
"capabilities": ["coordinateToScreen": false]
|
|
329
|
+
]
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
func screenToCoordinate(_ point: MapScreenPoint) throws -> [String: Double] {
|
|
333
|
+
let map = try requireMap()
|
|
334
|
+
guard point.x.isFinite, point.y.isFinite,
|
|
335
|
+
point.x >= 0, point.y >= 0,
|
|
336
|
+
point.x <= map.viewRect.width, point.y <= map.viewRect.height
|
|
337
|
+
else {
|
|
338
|
+
throw KakaoMapException(
|
|
339
|
+
invalidCameraArgument,
|
|
340
|
+
"Screen point must be finite and inside the map view."
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
let coordinate = map.getPosition(CGPoint(x: point.x, y: point.y)).wgsCoord
|
|
344
|
+
return ["latitude": coordinate.latitude, "longitude": coordinate.longitude]
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
func clearCache() throws {
|
|
348
|
+
_ = try requireUtilities().command(["type": "clearCache", "kind": "all"])
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
func requireUtilities() throws -> MapUtilities {
|
|
352
|
+
let map = try requireMap()
|
|
353
|
+
if let utilities {
|
|
354
|
+
return utilities
|
|
355
|
+
}
|
|
356
|
+
guard let controller else {
|
|
357
|
+
throw KakaoMapException("ERR_KAKAO_MAP_NOT_READY", "The map controller is unavailable.")
|
|
358
|
+
}
|
|
359
|
+
let manager = MapUtilities(map: map, controller: controller)
|
|
360
|
+
utilities = manager
|
|
361
|
+
return manager
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
func executeCameraCommand(_ command: [String: Any]) throws -> [String: Any] {
|
|
365
|
+
let map = try requireMap()
|
|
366
|
+
if let cameraCommands {
|
|
367
|
+
return try cameraCommands.command(command)
|
|
368
|
+
}
|
|
369
|
+
let manager = MapCameraCommands(map: map) { [weak self] payload in
|
|
370
|
+
guard let self, !isDestroyed else { return }
|
|
371
|
+
onCameraOperation(payload)
|
|
372
|
+
}
|
|
373
|
+
cameraCommands = manager
|
|
374
|
+
return try manager.command(command)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
func runLabelCommand(_ command: MapLabelCommandRecord) throws {
|
|
378
|
+
try requireLabels().command(command)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
func getLabelCapabilities() throws -> [String: Any] {
|
|
382
|
+
try requireLabels().capabilities()
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
func getLabelState() throws -> [String: Any] {
|
|
386
|
+
try requireLabels().state()
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
func applyShapes(_ scene: [String: Any]) throws -> [String: Any] {
|
|
390
|
+
pendingShapes = nil
|
|
391
|
+
let counts = try requireShapes().apply(scene)
|
|
392
|
+
hasUsedShapes = true
|
|
393
|
+
if !isDestroyed {
|
|
394
|
+
onShapesChanged(counts)
|
|
395
|
+
}
|
|
396
|
+
return counts
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
func executeShapeCommand(_ command: [String: Any]) throws -> [String: Any] {
|
|
400
|
+
try requireShapes().command(command)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private func requireShapes() throws -> MapShapes {
|
|
404
|
+
let map = try requireMap()
|
|
405
|
+
if let shapes {
|
|
406
|
+
return shapes
|
|
407
|
+
}
|
|
408
|
+
let shapes = MapShapes(map: map) { [weak self] event, payload in
|
|
409
|
+
guard let self, !isDestroyed else { return }
|
|
410
|
+
if event == "onShapeAnimationStop" {
|
|
411
|
+
onShapeAnimationStop(payload)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
self.shapes = shapes
|
|
415
|
+
return shapes
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private func requireLabels() throws -> MapLabels {
|
|
419
|
+
let map = try requireMap()
|
|
420
|
+
if let labels {
|
|
421
|
+
return labels
|
|
422
|
+
}
|
|
423
|
+
let loader = requireImageLoader()
|
|
424
|
+
let labels = MapLabels(
|
|
425
|
+
map: map,
|
|
426
|
+
imageLoader: loader,
|
|
427
|
+
emitClick: { [weak self] event in
|
|
428
|
+
guard let self, !isDestroyed else { return }
|
|
429
|
+
onLabelClick(event)
|
|
430
|
+
},
|
|
431
|
+
emitAnimationStopped: { [weak self] event in
|
|
432
|
+
guard let self, !isDestroyed else { return }
|
|
433
|
+
onLabelAnimationStopped(event)
|
|
434
|
+
},
|
|
435
|
+
resolveShape: { [weak self] id in
|
|
436
|
+
self?.shapes?.resolveShape(id)
|
|
437
|
+
}
|
|
438
|
+
)
|
|
439
|
+
self.labels = labels
|
|
440
|
+
return labels
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
func applyRoutes(_ scene: [String: Any], promise: Promise) throws {
|
|
444
|
+
pendingRoutes = nil
|
|
445
|
+
hasUsedRoutes = true
|
|
446
|
+
routeGeneration += 1
|
|
447
|
+
let generation = routeGeneration
|
|
448
|
+
try requireRoutes().apply(scene) { [weak self] result in
|
|
449
|
+
switch result {
|
|
450
|
+
case let .success(counts):
|
|
451
|
+
if let self, !isDestroyed, generation == routeGeneration {
|
|
452
|
+
onRoutesChanged(counts)
|
|
453
|
+
}
|
|
454
|
+
promise.resolve(counts)
|
|
455
|
+
case let .failure(error):
|
|
456
|
+
promise.reject(error)
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
func executeRouteCommand(_ command: [String: Any]) throws -> [String: Any] {
|
|
462
|
+
try requireRoutes().command(command)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private func requireRoutes() throws -> MapRoutes {
|
|
466
|
+
let map = try requireMap()
|
|
467
|
+
if let routes {
|
|
468
|
+
return routes
|
|
469
|
+
}
|
|
470
|
+
let manager = MapRoutes(
|
|
471
|
+
map: map,
|
|
472
|
+
imageLoader: requireImageLoader()
|
|
473
|
+
) { [weak self] event, payload in
|
|
474
|
+
guard let self, !isDestroyed else { return }
|
|
475
|
+
switch event {
|
|
476
|
+
case "onRouteProgress": onRouteProgress(payload)
|
|
477
|
+
case "onRouteAnimationStop": onRouteAnimationStop(payload)
|
|
478
|
+
default: break
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
routes = manager
|
|
482
|
+
return manager
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
func requireGui() throws -> MapGui {
|
|
486
|
+
let map = try requireMap()
|
|
487
|
+
if let gui {
|
|
488
|
+
return gui
|
|
489
|
+
}
|
|
490
|
+
let manager = MapGui(map: map, imageLoader: requireImageLoader()) { [weak self] payload in
|
|
491
|
+
guard let self, !isDestroyed else { return }
|
|
492
|
+
onGuiEvent(payload)
|
|
493
|
+
}
|
|
494
|
+
gui = manager
|
|
495
|
+
return manager
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
func requireDimScreen() throws -> MapDimScreen {
|
|
499
|
+
let map = try requireMap()
|
|
500
|
+
if let dimScreen {
|
|
501
|
+
return dimScreen
|
|
502
|
+
}
|
|
503
|
+
let manager = MapDimScreen(map: map) { [weak self] payload in
|
|
504
|
+
guard let self, !isDestroyed else { return }
|
|
505
|
+
onDimScreenEvent(payload)
|
|
506
|
+
}
|
|
507
|
+
dimScreen = manager
|
|
508
|
+
return manager
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
private func requireImageLoader() -> MapImageLoader {
|
|
512
|
+
if let imageLoader {
|
|
513
|
+
return imageLoader
|
|
514
|
+
}
|
|
515
|
+
let loader = MapImageLoader(appContext: appContext)
|
|
516
|
+
imageLoader = loader
|
|
517
|
+
return loader
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private func applyPendingRoutesIfReady() {
|
|
521
|
+
guard isReady, let scene = pendingRoutes else { return }
|
|
522
|
+
pendingRoutes = nil
|
|
523
|
+
routeGeneration += 1
|
|
524
|
+
let generation = routeGeneration
|
|
525
|
+
do {
|
|
526
|
+
try requireRoutes().apply(scene) { [weak self] result in
|
|
527
|
+
guard let self, !isDestroyed, generation == routeGeneration else { return }
|
|
528
|
+
switch result {
|
|
529
|
+
case let .success(counts): onRoutesChanged(counts)
|
|
530
|
+
case let .failure(error): reportObjectError(error, fallbackCode: "ERR_KAKAO_MAP_ROUTE")
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
} catch {
|
|
534
|
+
reportObjectError(error, fallbackCode: "ERR_KAKAO_MAP_ROUTE")
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
private func reportObjectError(_ error: Error, fallbackCode: String) {
|
|
539
|
+
if let error = error as? Exception {
|
|
540
|
+
onError(["code": error.code, "message": error.reason])
|
|
541
|
+
} else {
|
|
542
|
+
onError(["code": fallbackCode, "message": error.localizedDescription])
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private func applyPendingLabelsIfReady() {
|
|
547
|
+
guard isReady, let scene = pendingLabels else { return }
|
|
548
|
+
pendingLabels = nil
|
|
549
|
+
let previousGeneration = labelGeneration
|
|
550
|
+
labelGeneration += 1
|
|
551
|
+
let generation = labelGeneration
|
|
552
|
+
do {
|
|
553
|
+
try requireLabels().apply(scene) { [weak self] result in
|
|
554
|
+
guard let self, !isDestroyed, generation == labelGeneration else { return }
|
|
555
|
+
switch result {
|
|
556
|
+
case .success:
|
|
557
|
+
if let labels {
|
|
558
|
+
onLabelsChanged(labels.state())
|
|
559
|
+
}
|
|
560
|
+
case let .failure(error):
|
|
561
|
+
reportLabelError(error)
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
} catch {
|
|
565
|
+
if labelGeneration == generation {
|
|
566
|
+
labelGeneration = previousGeneration
|
|
567
|
+
}
|
|
568
|
+
reportLabelError(error)
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
private func reportLabelError(_ error: Error) {
|
|
573
|
+
if let error = error as? Exception {
|
|
574
|
+
onError(["code": error.code, "message": error.reason])
|
|
575
|
+
} else {
|
|
576
|
+
onError(["code": "ERR_KAKAO_MAP_LABEL", "message": error.localizedDescription])
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
private func applyPendingShapesIfReady() {
|
|
581
|
+
guard isReady, let scene = pendingShapes else { return }
|
|
582
|
+
pendingShapes = nil
|
|
583
|
+
do {
|
|
584
|
+
_ = try applyShapes(scene)
|
|
585
|
+
} catch {
|
|
586
|
+
reportShapeError(error)
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
private func reportShapeError(_ error: Error) {
|
|
591
|
+
if let error = error as? Exception {
|
|
592
|
+
onError(["code": error.code, "message": error.reason])
|
|
593
|
+
} else {
|
|
594
|
+
onError(["code": "ERR_KAKAO_MAP_SHAPE", "message": error.localizedDescription])
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
private func releaseMapObjects() {
|
|
599
|
+
labelGeneration += 1
|
|
600
|
+
routeGeneration += 1
|
|
601
|
+
utilities?.dispose()
|
|
602
|
+
utilities = nil
|
|
603
|
+
cameraCommands?.dispose()
|
|
604
|
+
cameraCommands = nil
|
|
605
|
+
labels?.dispose()
|
|
606
|
+
labels = nil
|
|
607
|
+
gui?.dispose()
|
|
608
|
+
gui = nil
|
|
609
|
+
dimScreen?.dispose()
|
|
610
|
+
dimScreen = nil
|
|
611
|
+
routes?.dispose()
|
|
612
|
+
routes = nil
|
|
613
|
+
shapes?.dispose()
|
|
614
|
+
shapes = nil
|
|
615
|
+
imageLoader?.dispose()
|
|
616
|
+
imageLoader = nil
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
private func applyPendingOptionsIfReady() {
|
|
620
|
+
guard isReady, let map, let pendingOptions else { return }
|
|
621
|
+
self.pendingOptions = nil
|
|
622
|
+
let target = appliedOptions.copy()
|
|
623
|
+
target.merge(pendingOptions)
|
|
624
|
+
do {
|
|
625
|
+
try validate(target, for: map)
|
|
626
|
+
apply(target.difference(from: appliedOptions), to: map)
|
|
627
|
+
appliedOptions = target
|
|
628
|
+
} catch let error as Exception {
|
|
629
|
+
onError(["code": error.code, "message": error.reason])
|
|
630
|
+
} catch {
|
|
631
|
+
onError(["code": invalidCameraArgument, "message": error.localizedDescription])
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
private func validate(_ options: KakaoMapViewOptions, for map: KakaoMap) throws {
|
|
636
|
+
if options.mapType != nil, options.viewInfo != nil {
|
|
637
|
+
throw KakaoMapException(
|
|
638
|
+
invalidCameraArgument,
|
|
639
|
+
"mapType and viewInfo are mutually exclusive."
|
|
640
|
+
)
|
|
641
|
+
}
|
|
642
|
+
if let mapType = options.mapType, mapType != "normal", mapType != "skyview" {
|
|
643
|
+
throw KakaoMapException(invalidCameraArgument, "Invalid map type.")
|
|
644
|
+
}
|
|
645
|
+
if let viewInfo = options.viewInfo {
|
|
646
|
+
guard !viewInfo.name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
|
|
647
|
+
viewInfo.appName?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty != true
|
|
648
|
+
else {
|
|
649
|
+
throw KakaoMapException(invalidCameraArgument, "View info names must not be empty.")
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if let overlays = options.overlays {
|
|
653
|
+
for overlay in overlays {
|
|
654
|
+
_ = try overlayID(overlay)
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if let overlays = options.customOverlays,
|
|
658
|
+
overlays.contains(where: { $0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty })
|
|
659
|
+
{
|
|
660
|
+
throw KakaoMapException(invalidCameraArgument, "Custom overlay IDs must not be empty.")
|
|
661
|
+
}
|
|
662
|
+
if let language = options.language, !map.getSupportedLanguages().contains(language) {
|
|
663
|
+
throw KakaoMapException(
|
|
664
|
+
invalidCameraArgument,
|
|
665
|
+
"The map does not support language '\(language)'."
|
|
666
|
+
)
|
|
667
|
+
}
|
|
668
|
+
if let scale = options.poi?.scale {
|
|
669
|
+
_ = try poiScale(scale)
|
|
670
|
+
}
|
|
671
|
+
if let scale = options.buildingScale,
|
|
672
|
+
!scale.isFinite || !(0 ... 1).contains(scale)
|
|
673
|
+
{
|
|
674
|
+
throw KakaoMapException(invalidCameraArgument, "Building scale must be between 0 and 1.")
|
|
675
|
+
}
|
|
676
|
+
if let padding = options.padding {
|
|
677
|
+
let values = [padding.top, padding.right, padding.bottom, padding.left]
|
|
678
|
+
guard values.allSatisfy({ $0.isFinite && $0 >= 0 }) else {
|
|
679
|
+
throw KakaoMapException(
|
|
680
|
+
invalidCameraArgument,
|
|
681
|
+
"Map padding must contain non-negative values."
|
|
682
|
+
)
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if let range = options.zoomRange {
|
|
686
|
+
guard range.min.isFinite, range.max.isFinite,
|
|
687
|
+
range.min.rounded() == range.min, range.max.rounded() == range.max,
|
|
688
|
+
range.min >= Double(map.minLevel), range.max <= Double(map.maxLevel),
|
|
689
|
+
range.min <= range.max
|
|
690
|
+
else {
|
|
691
|
+
throw KakaoMapException(invalidCameraArgument, "Invalid map zoom range.")
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if let gestures = options.gestures {
|
|
695
|
+
for gesture in gestures.keys {
|
|
696
|
+
_ = try gestureType(gesture)
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
_ = try options.compass?.position?.values()
|
|
700
|
+
_ = try options.scaleBar?.position?.values()
|
|
701
|
+
_ = try options.logo?.values()
|
|
702
|
+
if let fade = options.scaleBar?.fade {
|
|
703
|
+
_ = try duration(fade.inDuration)
|
|
704
|
+
_ = try duration(fade.outDuration)
|
|
705
|
+
_ = try duration(fade.retentionDuration)
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
private func apply(_ options: KakaoMapViewOptions, to map: KakaoMap) {
|
|
710
|
+
if let viewInfo = options.viewInfo {
|
|
711
|
+
map.changeViewInfo(appName: viewInfo.appName ?? "openmap", viewInfoName: viewInfo.name)
|
|
712
|
+
} else if let mapType = options.mapType {
|
|
713
|
+
map.changeViewInfo(
|
|
714
|
+
appName: "openmap",
|
|
715
|
+
viewInfoName: mapType == "skyview" ? "skyview" : "map"
|
|
716
|
+
)
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
let previousOverlays = semanticOverlays.union(customOverlays)
|
|
720
|
+
if let overlays = options.overlays {
|
|
721
|
+
semanticOverlays = Set(overlays.compactMap { try? overlayID($0) })
|
|
722
|
+
}
|
|
723
|
+
if let overlays = options.customOverlays {
|
|
724
|
+
customOverlays = Set(overlays)
|
|
725
|
+
}
|
|
726
|
+
let nextOverlays = semanticOverlays.union(customOverlays)
|
|
727
|
+
for overlay in previousOverlays.subtracting(nextOverlays) {
|
|
728
|
+
map.hideOverlay(overlay)
|
|
729
|
+
}
|
|
730
|
+
for overlay in nextOverlays.subtracting(previousOverlays) {
|
|
731
|
+
map.showOverlay(overlay)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if let language = options.language {
|
|
735
|
+
map.setLanguage(language)
|
|
736
|
+
currentLanguage = language
|
|
737
|
+
}
|
|
738
|
+
if let visible = options.poi?.visible {
|
|
739
|
+
map.setPoiEnabled(visible)
|
|
740
|
+
}
|
|
741
|
+
if let clickable = options.poi?.clickable {
|
|
742
|
+
map.poiClickable = clickable
|
|
743
|
+
}
|
|
744
|
+
if let scale = options.poi?.scale, let value = try? poiScale(scale) {
|
|
745
|
+
map.poiScale = value
|
|
746
|
+
}
|
|
747
|
+
if let scale = options.buildingScale {
|
|
748
|
+
map.buildingScale = Float(scale)
|
|
749
|
+
}
|
|
750
|
+
if let padding = options.padding {
|
|
751
|
+
map.setMargins(UIEdgeInsets(
|
|
752
|
+
top: padding.top,
|
|
753
|
+
left: padding.left,
|
|
754
|
+
bottom: padding.bottom,
|
|
755
|
+
right: padding.right
|
|
756
|
+
))
|
|
757
|
+
}
|
|
758
|
+
if let range = options.zoomRange {
|
|
759
|
+
let minLevel = Int(range.min)
|
|
760
|
+
let maxLevel = Int(range.max)
|
|
761
|
+
if minLevel > map.cameraMaxLevel {
|
|
762
|
+
map.cameraMaxLevel = maxLevel
|
|
763
|
+
} else if maxLevel < map.cameraMinLevel {
|
|
764
|
+
map.cameraMinLevel = minLevel
|
|
765
|
+
}
|
|
766
|
+
map.cameraMinLevel = minLevel
|
|
767
|
+
map.cameraMaxLevel = maxLevel
|
|
768
|
+
}
|
|
769
|
+
if let gestures = options.gestures {
|
|
770
|
+
for (gesture, isEnabled) in gestures {
|
|
771
|
+
if let value = try? gestureType(gesture) {
|
|
772
|
+
map.setGestureEnable(type: value, enable: isEnabled)
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if let compass = options.compass {
|
|
777
|
+
if let visible = compass.visible {
|
|
778
|
+
visible ? map.showCompass() : map.hideCompass()
|
|
779
|
+
}
|
|
780
|
+
if let position = compass.position, let (origin, offset) = try? position.values() {
|
|
781
|
+
map.setCompassPosition(origin: origin, position: offset)
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if let scaleBar = options.scaleBar {
|
|
785
|
+
if let visible = scaleBar.visible {
|
|
786
|
+
visible ? map.showScaleBar() : map.hideScaleBar()
|
|
787
|
+
}
|
|
788
|
+
if let position = scaleBar.position, let (origin, offset) = try? position.values() {
|
|
789
|
+
map.setScaleBarPosition(origin: origin, position: offset)
|
|
790
|
+
}
|
|
791
|
+
if let autoHide = scaleBar.autoHide {
|
|
792
|
+
map.setScaleBarAutoDisappear(autoHide)
|
|
793
|
+
}
|
|
794
|
+
if let fade = scaleBar.fade,
|
|
795
|
+
let fadeIn = try? duration(fade.inDuration),
|
|
796
|
+
let fadeOut = try? duration(fade.outDuration),
|
|
797
|
+
let retention = try? duration(fade.retentionDuration)
|
|
798
|
+
{
|
|
799
|
+
map.setScaleBarFadeInOutOption(FadeInOutOptions(
|
|
800
|
+
fadeInTime: fadeIn,
|
|
801
|
+
fadeOutTime: fadeOut,
|
|
802
|
+
retentionTime: retention
|
|
803
|
+
))
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if let logo = options.logo, let (origin, offset) = try? logo.values() {
|
|
807
|
+
map.setLogoPosition(origin: origin, position: offset)
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
private func overlayID(_ overlay: String) throws -> String {
|
|
812
|
+
switch overlay {
|
|
813
|
+
case "roadviewLine": "roadview_line"
|
|
814
|
+
case "hillShading": "hill_shading"
|
|
815
|
+
case "bicycleRoad": "bicycle_road"
|
|
816
|
+
case "hybrid": "hybrid"
|
|
817
|
+
default: throw KakaoMapException(invalidCameraArgument, "Invalid map overlay.")
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
private func poiScale(_ scale: String) throws -> PoiScaleType {
|
|
822
|
+
switch scale {
|
|
823
|
+
case "small": .small
|
|
824
|
+
case "regular": .regular
|
|
825
|
+
case "large": .large
|
|
826
|
+
case "xLarge": .xLarge
|
|
827
|
+
default: throw KakaoMapException(invalidCameraArgument, "Invalid map POI scale.")
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
private func gestureType(_ gesture: String) throws -> GestureType {
|
|
832
|
+
switch gesture {
|
|
833
|
+
case "doubleTapZoomIn": .doubleTapZoomIn
|
|
834
|
+
case "twoFingerTapZoomOut": .twoFingerTapZoomOut
|
|
835
|
+
case "pan": .pan
|
|
836
|
+
case "rotate": .rotate
|
|
837
|
+
case "zoom": .zoom
|
|
838
|
+
case "tilt": .tilt
|
|
839
|
+
case "longTapAndDrag": .longTapAndDrag
|
|
840
|
+
case "rotateZoom": .rotateZoom
|
|
841
|
+
case "oneFingerZoom": .oneFingerZoom
|
|
842
|
+
default: throw KakaoMapException(invalidCameraArgument, "Invalid map gesture.")
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
private func duration(_ value: Double) throws -> UInt32 {
|
|
847
|
+
guard value.isFinite, value >= 0, value <= Double(UInt32.max), value.rounded() == value else {
|
|
848
|
+
throw KakaoMapException(
|
|
849
|
+
invalidCameraArgument,
|
|
850
|
+
"Widget durations must be integer milliseconds between 0 and \(UInt32.max)."
|
|
851
|
+
)
|
|
852
|
+
}
|
|
853
|
+
return UInt32(value)
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
private func requireMap() throws -> KakaoMap {
|
|
857
|
+
if isDestroyed {
|
|
858
|
+
throw KakaoMapException(
|
|
859
|
+
"ERR_KAKAO_MAP_VIEW_DESTROYED",
|
|
860
|
+
"The map view was destroyed."
|
|
861
|
+
)
|
|
862
|
+
}
|
|
863
|
+
if KakaoMapModule.appKey == nil {
|
|
864
|
+
throw KakaoMapException(
|
|
865
|
+
mapNotInitialized,
|
|
866
|
+
"Initialize the map SDK first."
|
|
867
|
+
)
|
|
868
|
+
}
|
|
869
|
+
guard isReady, let map else { throw KakaoMapException(
|
|
870
|
+
"ERR_KAKAO_MAP_NOT_READY",
|
|
871
|
+
"Wait for onMapReady before calling map methods."
|
|
872
|
+
) }
|
|
873
|
+
return map
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
private func applyCameraProp(_ camera: CameraOptions) {
|
|
877
|
+
do {
|
|
878
|
+
try moveCamera(camera, duration: 0)
|
|
879
|
+
} catch let error as Exception {
|
|
880
|
+
onError(["code": error.code, "message": error.reason])
|
|
881
|
+
} catch {
|
|
882
|
+
onError(["code": "ERR_KAKAO_MAP_CAMERA", "message": error.localizedDescription])
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
private func cameraPayload(_ map: KakaoMap) -> [String: Any] {
|
|
887
|
+
let margins = map.margins
|
|
888
|
+
let point = map.getPosition(CGPoint(
|
|
889
|
+
x: (margins.left + map.viewRect.width - margins.right) / 2,
|
|
890
|
+
y: (margins.top + map.viewRect.height - margins.bottom) / 2
|
|
891
|
+
))
|
|
892
|
+
let rotation = map.rotationAngle * 180 / .pi
|
|
893
|
+
return [
|
|
894
|
+
"latitude": point.wgsCoord.latitude,
|
|
895
|
+
"longitude": point.wgsCoord.longitude,
|
|
896
|
+
"zoomLevel": map.zoomLevel,
|
|
897
|
+
"tilt": map.tiltAngle * 180 / .pi,
|
|
898
|
+
"rotation": (rotation.truncatingRemainder(dividingBy: 360) + 360)
|
|
899
|
+
.truncatingRemainder(dividingBy: 360)
|
|
900
|
+
]
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
func cameraWillMove(kakaoMap: KakaoMap, by: MoveBy) {
|
|
904
|
+
if !isDestroyed {
|
|
905
|
+
if by != .notUserAction {
|
|
906
|
+
cameraCommands?.supersedePendingOperations()
|
|
907
|
+
}
|
|
908
|
+
onCameraMoveStart(["reason": reason(by)])
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
func cameraDidStopped(kakaoMap: KakaoMap, by: MoveBy) {
|
|
913
|
+
if !isDestroyed {
|
|
914
|
+
onCameraIdle(["reason": reason(by), "camera": cameraPayload(kakaoMap)])
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
func kakaoMapDidTapped(kakaoMap: KakaoMap, point: CGPoint) {
|
|
919
|
+
guard !isDestroyed else { return }
|
|
920
|
+
let coordinate = kakaoMap.getPosition(point).wgsCoord
|
|
921
|
+
onMapClick([
|
|
922
|
+
"latitude": coordinate.latitude,
|
|
923
|
+
"longitude": coordinate.longitude,
|
|
924
|
+
"x": point.x,
|
|
925
|
+
"y": point.y
|
|
926
|
+
])
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
func terrainDidTapped(kakaoMap: KakaoMap, position: MapPoint) {
|
|
930
|
+
guard !isDestroyed else { return }
|
|
931
|
+
let point = position.wgsCoord
|
|
932
|
+
onTerrainClick(["latitude": point.latitude, "longitude": point.longitude])
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
func kakaoMapFocusDidChanged(kakaoMap: KakaoMap, focus: Bool) {
|
|
936
|
+
guard !isDestroyed else { return }
|
|
937
|
+
onFocusChanged(["focused": focus])
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
func terrainDidLongPressed(kakaoMap: KakaoMap, position: MapPoint) {
|
|
941
|
+
guard !isDestroyed else { return }
|
|
942
|
+
onMapLongClick([
|
|
943
|
+
"latitude": position.wgsCoord.latitude,
|
|
944
|
+
"longitude": position.wgsCoord.longitude
|
|
945
|
+
])
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
func poiDidTapped(kakaoMap: KakaoMap, layerID: String, poiID: String, position: MapPoint) {
|
|
949
|
+
guard !isDestroyed else { return }
|
|
950
|
+
if labels?.handlesPoiTap(layerId: layerID, id: poiID) == true {
|
|
951
|
+
return
|
|
952
|
+
}
|
|
953
|
+
onPoiClick([
|
|
954
|
+
"latitude": position.wgsCoord.latitude,
|
|
955
|
+
"longitude": position.wgsCoord.longitude,
|
|
956
|
+
"id": poiID,
|
|
957
|
+
"layerId": layerID
|
|
958
|
+
])
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
func kakaoMapMarginDidUpdated(_ kakaoMap: KakaoMap) {
|
|
962
|
+
guard !isDestroyed else { return }
|
|
963
|
+
onPaddingChanged([
|
|
964
|
+
"top": kakaoMap.margins.top,
|
|
965
|
+
"right": kakaoMap.margins.right,
|
|
966
|
+
"bottom": kakaoMap.margins.bottom,
|
|
967
|
+
"left": kakaoMap.margins.left
|
|
968
|
+
])
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
func kakaoMapDidResized(_ kakaoMap: KakaoMap) {
|
|
972
|
+
guard !isDestroyed else { return }
|
|
973
|
+
let size = kakaoMap.viewRect.size
|
|
974
|
+
guard lastEmittedViewportSize != size else { return }
|
|
975
|
+
lastEmittedViewportSize = size
|
|
976
|
+
onViewportChanged([
|
|
977
|
+
"x": kakaoMap.viewRect.origin.x,
|
|
978
|
+
"y": kakaoMap.viewRect.origin.y,
|
|
979
|
+
"width": size.width,
|
|
980
|
+
"height": size.height
|
|
981
|
+
])
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
func compassDidTapped(kakaoMap: KakaoMap) {
|
|
985
|
+
if !isDestroyed {
|
|
986
|
+
onCompassClick([:])
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
func onViewInfoChanged(kakaoMap: KakaoMap, viewInfoName: String) {
|
|
991
|
+
guard !isDestroyed else { return }
|
|
992
|
+
currentViewInfoName = viewInfoName
|
|
993
|
+
onMapTypeChanged(["viewInfoName": viewInfoName])
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
func onViewInfoChangeFailure(kakaoMap: KakaoMap, viewInfoName: String) {
|
|
997
|
+
if !isDestroyed {
|
|
998
|
+
onError([
|
|
999
|
+
"code": "ERR_KAKAO_MAP_VIEW_INFO_CHANGE",
|
|
1000
|
+
"message": "Could not change the map view info to '\(viewInfoName)'."
|
|
1001
|
+
])
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
private func reason(_ by: MoveBy) -> String {
|
|
1006
|
+
by == .notUserAction ? "developer" : "gesture"
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
deinit {
|
|
1010
|
+
isDestroyed = true
|
|
1011
|
+
releaseMapObjects()
|
|
1012
|
+
NotificationCenter.default.removeObserver(self)
|
|
1013
|
+
map?.eventDelegate = nil
|
|
1014
|
+
controller?.delegate = nil
|
|
1015
|
+
controller?.pauseEngine()
|
|
1016
|
+
controller?.resetEngine()
|
|
1017
|
+
}
|
|
1018
|
+
}
|