@nativescript-community/ui-mapbox 7.0.0-alpha.14.3191a7b → 7.0.1

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/index.ios.js CHANGED
@@ -1,529 +1,121 @@
1
- import { Color, File, Http, ImageSource, Trace, Utils, knownFolders, path } from '@nativescript/core';
2
- import { CLog, CLogTypes, ControlPosition, MapStyle, MapboxCommon, MapboxViewBase, telemetryProperty } from './common';
3
- import { Layer, LayerFactory } from './layers/layer-factory';
4
- import { ExpressionParser } from './expression/expression-parser';
5
- var MGLMapViewDelegateImpl = /** @class */ (function (_super) {
6
- __extends(MGLMapViewDelegateImpl, _super);
7
- function MGLMapViewDelegateImpl() {
8
- return _super !== null && _super.apply(this, arguments) || this;
9
- }
10
- MGLMapViewDelegateImpl.new = function () {
11
- return _super.new.call(this);
12
- };
13
- /**
14
- * initialize with the mapReady callback
15
- */
16
- MGLMapViewDelegateImpl.prototype.initWithCallback = function (mapLoadedCallback) {
17
- if (Trace.isEnabled()) {
18
- CLog(CLogTypes.info, 'MGLMapViewDelegateImpl::initWithCallback()');
19
- }
20
- this.mapLoadedCallback = mapLoadedCallback;
21
- return this;
22
- };
23
- /**
24
- * set a reference to the mapboxAPI instance
25
- */
26
- MGLMapViewDelegateImpl.prototype.setMapboxApi = function (api) {
27
- this.mapboxApi = api;
28
- };
29
- /**
30
- * set the user location click listener callback
31
- */
32
- MGLMapViewDelegateImpl.prototype.setUserLocationClickListener = function (callback) {
33
- this.userLocationClickListener = callback;
34
- };
35
- /**
36
- * set the user location click listener callback
37
- */
38
- MGLMapViewDelegateImpl.prototype.setUserLocationChangedistener = function (callback) {
39
- this.userLocationChangedListener = callback;
40
- };
41
- /**
42
- * set user location marker modes
43
- */
44
- MGLMapViewDelegateImpl.prototype.changeUserLocationRenderMode = function (userLocationRenderMode) {
45
- // nothing to do here
46
- };
47
- /**
48
- * set the camera changd listener callback
49
- */
50
- MGLMapViewDelegateImpl.prototype.setCameraChangedListener = function (callback) {
51
- this.cameraChangedListener = callback;
52
- };
53
- /**
54
- * set the camera idled listener callback
55
- */
56
- MGLMapViewDelegateImpl.prototype.setCameraIdledListener = function (callback) {
57
- this.cameraIdledListener = callback;
58
- };
59
- /**
60
- * set style loaded callback.
61
- *
62
- * set an optional callback to be invoked when a style set with
63
- * setMapStyle() is finished loading
64
- *
65
- * @param {function} callback function with loaded style as parameter.
66
- *
67
- * @see Mapbox:setMapStyle()
68
- */
69
- MGLMapViewDelegateImpl.prototype.setStyleLoadedCallback = function (callback) {
70
- this.styleLoadedCallback = callback;
71
- };
72
- /**
73
- * map ready callback
74
- */
75
- MGLMapViewDelegateImpl.prototype.mapViewDidFinishLoadingMap = function (mapView) {
76
- if (Trace.isEnabled()) {
77
- CLog(CLogTypes.info, 'MGLMapViewDelegateImpl:mapViewDidFinishLoadingMap(): top');
78
- }
79
- if (this.mapLoadedCallback !== undefined) {
80
- this.mapLoadedCallback(mapView);
81
- // this should be fired only once, but it's also fired when the style changes, so just remove the callback
82
- this.mapLoadedCallback = undefined;
83
- }
84
- };
85
- MGLMapViewDelegateImpl.prototype.mapViewDidFinishRenderingMapFullyRendered = function (mapView, fullyRendered) {
86
- if (Trace.isEnabled()) {
87
- CLog(CLogTypes.info, 'MGLMapViewDelegateImpl:mapViewDidFinishRenderingMapFullyRendered(): rendered is:', fullyRendered);
88
- }
89
- };
90
- /**
91
- * Callback when the style has been loaded.
92
- *
93
- * Based on my testing, it looks like this callback is invoked multiple times.
94
- *
95
- * @see Mapbox:setMapStyle()
96
- *
97
- * @link https://mapbox.github.io/mapbox-gl-native/macos/0.3.0/Protocols/MGLMapViewDelegate.html#/c:objc(pl)MGLMapViewDelegate(im)mapView:didFinishLoadingStyle:
98
- */
99
- MGLMapViewDelegateImpl.prototype.mapViewDidFinishLoadingStyle = function (mapView, style) {
100
- if (Trace.isEnabled()) {
101
- CLog(CLogTypes.info, 'MGLMapViewDelegateImpl:mapViewDidFinishLoadingStyle(): callback called.');
102
- }
103
- if (this.styleLoadedCallback !== undefined) {
104
- this.styleLoadedCallback(mapView, style);
105
- // to avoid multiple calls. This is only invoked from setMapStyle().
106
- this.styleLoadedCallback = undefined;
107
- }
108
- };
109
- /**
110
- * disable the default user location callout
111
- *
112
- * This took forever to find. The default iOS click handler for the user location
113
- * marker is about useless. It just displays "You Are Here". The examples do not
114
- * show how to disable it.
115
- */
116
- MGLMapViewDelegateImpl.prototype.mapViewAnnotationCanShowCallout = function (mapView, annotation) {
117
- if (annotation.isKindOfClass(MGLUserLocation.class())) {
118
- return false;
119
- }
120
- else {
121
- return true;
122
- }
123
- };
124
- MGLMapViewDelegateImpl.prototype.mapViewDidFailLoadingMapWithError = function (mapView, error) {
125
- if (Trace.isEnabled()) {
126
- CLog(CLogTypes.info, 'mapViewDidFailLoadingMapWithError: ' + error.localizedDescription);
127
- }
128
- };
129
- MGLMapViewDelegateImpl.prototype.mapViewDidChangeUserTrackingModeAnimated = function (mapView, mode, animated) {
130
- if (Trace.isEnabled()) {
131
- CLog(CLogTypes.info, 'mapViewDidChangeUserTrackingModeAnimated: ' + mode);
132
- }
133
- };
134
- /**
135
- * fired when the marker icon is about to be rendered - return null for the default icon
136
- */
137
- MGLMapViewDelegateImpl.prototype.mapViewImageForAnnotation = function (mapView, annotation) {
138
- var cachedMarker = this.getTappedMarkerDetails(annotation);
139
- if (cachedMarker) {
140
- if (cachedMarker.reuseIdentifier) {
141
- var reusedImage = mapView.dequeueReusableAnnotationImageWithIdentifier(cachedMarker.reuseIdentifier);
142
- if (reusedImage) {
143
- return reusedImage;
144
- }
145
- }
146
- // TODO try adding .rotatesToMatchCamera = true;
147
- // .. for instance in the mapViewDidDeselectAnnotationView / mapViewDidSelectAnnotationView / mapViewViewForAnnotation delegate
148
- if (cachedMarker.icon) {
149
- if (cachedMarker.icon.startsWith('res://')) {
150
- var resourceName = cachedMarker.icon.substring('res://'.length);
151
- var imageSource = ImageSource.fromResourceSync(resourceName);
152
- if (imageSource === null) {
153
- console.log("Unable to locate ".concat(resourceName));
154
- }
155
- else {
156
- cachedMarker.reuseIdentifier = cachedMarker.icon;
157
- return MGLAnnotationImage.annotationImageWithImageReuseIdentifier(imageSource.ios, cachedMarker.reuseIdentifier);
158
- }
159
- }
160
- else if (cachedMarker.icon.startsWith('http')) {
161
- if (cachedMarker.iconDownloaded !== null) {
162
- cachedMarker.reuseIdentifier = cachedMarker.icon;
163
- return MGLAnnotationImage.annotationImageWithImageReuseIdentifier(cachedMarker.iconDownloaded, cachedMarker.reuseIdentifier);
164
- }
165
- }
166
- else {
167
- if (Trace.isEnabled()) {
168
- CLog(CLogTypes.info, 'Please use res://resourceName, http(s)://imageUrl or iconPath to use a local path');
169
- }
170
- }
171
- }
172
- else if (cachedMarker.iconPath) {
173
- var appPath = knownFolders.currentApp().path;
174
- var iconFullPath = appPath + '/' + cachedMarker.iconPath.replace('~/', '');
175
- if (File.exists(iconFullPath)) {
176
- var image = ImageSource.fromFileSync(iconFullPath).ios;
177
- // perhaps add resize options for nice retina rendering (although you can now use the 'icon' param instead)
178
- cachedMarker.reuseIdentifier = cachedMarker.iconPath;
179
- return MGLAnnotationImage.annotationImageWithImageReuseIdentifier(image, cachedMarker.reuseIdentifier);
180
- }
181
- }
182
- }
183
- return null;
184
- };
185
- /**
186
- * fired when one of the callout's accessoryviews is tapped (not currently used)
187
- */
188
- MGLMapViewDelegateImpl.prototype.mapViewAnnotationCalloutAccessoryControlTapped = function (mapView, annotation, control) { };
189
- /**
190
- * fired when a marker is tapped
191
- */
192
- MGLMapViewDelegateImpl.prototype.mapViewDidSelectAnnotation = function (mapView, annotation) {
193
- if (Trace.isEnabled()) {
194
- CLog(CLogTypes.info, 'MGLMapViewDelegateImpl::mapViewDidSelectAnntation()');
195
- }
196
- if (annotation.isKindOfClass(MGLUserLocation.class())) {
197
- if (Trace.isEnabled()) {
198
- CLog(CLogTypes.info, 'MGLMapViewDelegateImpl::mapViewDidSelectAnnotation(): tapped the user location button');
199
- }
200
- if (typeof this.userLocationClickListener != 'undefined') {
201
- this.userLocationClickListener(annotation);
202
- return;
203
- }
204
- mapView.deselectAnnotationAnimated(annotation, false);
205
- }
206
- var cachedMarker = this.getTappedMarkerDetails(annotation);
207
- if (cachedMarker && cachedMarker.onTap) {
208
- cachedMarker.onTap(cachedMarker);
209
- }
210
- };
211
- /**
212
- * fired when a callout is tapped
213
- */
214
- MGLMapViewDelegateImpl.prototype.mapViewTapOnCalloutForAnnotation = function (mapView, annotation) {
215
- var cachedMarker = this.getTappedMarkerDetails(annotation);
216
- if (cachedMarker && cachedMarker.onCalloutTap) {
217
- cachedMarker.onCalloutTap(cachedMarker);
218
- }
219
- };
220
- MGLMapViewDelegateImpl.prototype.getTappedMarkerDetails = function (tapped) {
221
- _markers.forEach(function (cached) {
222
- // don't compare lat/lng types as they're not the same (same for (sub)title, they may be null vs undefined)
223
- if (
224
- // eslint-disable-next-line eqeqeq
225
- cached.lat == tapped.coordinate.latitude &&
226
- // eslint-disable-next-line eqeqeq
227
- cached.lng == tapped.coordinate.longitude &&
228
- // eslint-disable-next-line eqeqeq
229
- cached.title == tapped.title &&
230
- // eslint-disable-next-line eqeqeq
231
- cached.subtitle == tapped.subtitle) {
232
- return cached;
233
- }
234
- });
235
- };
236
- MGLMapViewDelegateImpl.prototype.mapViewRegionIsChangingWithReason = function (mapView, reason) {
237
- if (Trace.isEnabled()) {
238
- CLog(CLogTypes.info, 'MGLMapViewDelegateImpl::mapViewRegionIsChanging()');
239
- }
240
- if (this.cameraChangedListener) {
241
- this.cameraChangedListener(reason);
242
- }
243
- };
244
- MGLMapViewDelegateImpl.prototype.mapViewRegionDidChangeWithReasonAnimated = function (mapView, reason, animated) {
245
- if (Trace.isEnabled()) {
246
- CLog(CLogTypes.info, 'MGLMapViewDelegateImpl::mapViewRegionDidChangeAnimated()');
247
- }
248
- if (this.cameraChangedListener) {
249
- this.cameraChangedListener(reason, animated);
250
- }
251
- if (this.cameraIdledListener) {
252
- this.cameraIdledListener();
253
- }
254
- };
255
- MGLMapViewDelegateImpl.prototype.mapViewDidUpdateUserLocation = function (mapView, userLocation) {
256
- if (Trace.isEnabled()) {
257
- CLog(CLogTypes.info, 'MGLMapViewDelegateImpl::mapViewDidUpdateUserLocation()');
258
- }
259
- if (this.userLocationChangedListener) {
260
- this.userLocationChangedListener(_getLocation(userLocation));
261
- }
262
- };
263
- MGLMapViewDelegateImpl.ObjCProtocols = [MGLMapViewDelegate];
264
- return MGLMapViewDelegateImpl;
265
- }(NSObject));
266
- var MapTapHandlerImpl = /** @class */ (function (_super) {
267
- __extends(MapTapHandlerImpl, _super);
268
- function MapTapHandlerImpl() {
269
- return _super !== null && _super.apply(this, arguments) || this;
270
- }
271
- MapTapHandlerImpl.initWithOwnerAndListenerForMap = function (owner, listener, mapView) {
272
- var handler = MapTapHandlerImpl.new();
273
- handler._owner = owner;
274
- handler._listener = listener;
275
- handler._mapView = mapView;
276
- return handler;
277
- };
278
- MapTapHandlerImpl.prototype.tap = function (recognizer) {
279
- var tapPoint = recognizer.locationInView(this._mapView);
280
- var tapCoordinate = this._mapView.convertPointToCoordinateFromView(tapPoint, this._mapView);
281
- this._listener({
282
- lat: tapCoordinate.latitude,
283
- lng: tapCoordinate.longitude
284
- });
285
- };
286
- MapTapHandlerImpl.ObjCExposedMethods = {
287
- tap: { returns: interop.types.void, params: [interop.types.id] }
288
- };
289
- return MapTapHandlerImpl;
290
- }(NSObject));
291
- var MapLongPressHandlerImpl = /** @class */ (function (_super) {
292
- __extends(MapLongPressHandlerImpl, _super);
293
- function MapLongPressHandlerImpl() {
294
- return _super !== null && _super.apply(this, arguments) || this;
295
- }
296
- MapLongPressHandlerImpl.initWithOwnerAndListenerForMap = function (owner, listener, mapView) {
297
- var handler = MapLongPressHandlerImpl.new();
298
- handler._owner = owner;
299
- handler._listener = listener;
300
- handler._mapView = mapView;
301
- return handler;
302
- };
303
- MapLongPressHandlerImpl.prototype.longPress = function (recognizer) {
304
- var longPressPoint = recognizer.locationInView(this._mapView);
305
- var longPressCoordinate = this._mapView.convertPointToCoordinateFromView(longPressPoint, this._mapView);
306
- this._listener({
307
- lat: longPressCoordinate.latitude,
308
- lng: longPressCoordinate.longitude
309
- });
310
- };
311
- MapLongPressHandlerImpl.ObjCExposedMethods = {
312
- longPress: { returns: interop.types.void, params: [interop.types.id] }
313
- };
314
- return MapLongPressHandlerImpl;
315
- }(NSObject));
316
- var MapPanHandlerImpl = /** @class */ (function (_super) {
317
- __extends(MapPanHandlerImpl, _super);
318
- function MapPanHandlerImpl() {
1
+ // src/ui-mapbox/index.ios.ts
2
+ // Full iOS TypeScript bridge for MapboxBridge (MapboxMaps + TileStore).
3
+ // - Adds addGeoJsonClustered and addExtrusion (ported behavior).
4
+ // - querySourceFeatures now passes filter JSON to native bridge so the native SDK can apply filtering.
5
+ // - Uses LayerFactory and ExpressionParser TS shims (delegate to native helpers when available).
6
+ //
7
+ // Replace your existing file with this full implementation.
8
+ import { Application, Color, Http, ImageSource, Screen, Trace, Utils, View } from '@nativescript/core';
9
+ import { CLog, CLogTypes, MapboxCommon, MapboxViewBase, telemetryProperty } from './common';
10
+ import { Layer, LayerFactory } from './layers//layer-factory.ios';
11
+ import { createInfoWindowView } from './markers/Marker.common';
12
+ export * from './common';
13
+ // Notification names (must match Swift constants)
14
+ const MAPBOX_BRIDGE_MAP_LOADED = 'MapboxBridgeMapLoaded';
15
+ const MAPBOX_BRIDGE_STYLE_LOADED = 'MapboxBridgeStyleLoaded';
16
+ const MAPBOX_BRIDGE_MAP_CLICK = 'MapboxBridgeMapClick';
17
+ const MAPBOX_BRIDGE_MAP_LONGPRESS = 'MapboxBridgeMapLongPress';
18
+ const MAPBOX_BRIDGE_ANNOTATION_TAP = 'MapboxBridgeAnnotationTap';
19
+ const MAPBOX_BRIDGE_CAMERA_CHANGED = 'MapboxBridgeCameraChanged';
20
+ const MAPBOX_BRIDGE_CAMERA_IDLE = 'MapboxBridgeCameraIdle';
21
+ const MAPBOX_BRIDGE_MAP_SCROLL = 'MapboxBridgeMapScroll';
22
+ const MAPBOX_BRIDGE_MAP_MOVE_BEGIN = 'MapboxBridgeMapMoveBegin';
23
+ const MAPBOX_BRIDGE_MAP_MOVE_END = 'MapboxBridgeMapMoveEnd';
24
+ const MAPBOX_BRIDGE_MAP_FLING = 'MapboxBridgeMapFling';
25
+ const MAPBOX_BRIDGE_CAMERA_MOVE_CANCEL = 'MapboxBridgeCameraMoveCancel';
26
+ const MAPBOX_BRIDGE_OFFLINE_PROGRESS = 'MapboxBridgeOfflineProgress';
27
+ const MAPBOX_BRIDGE_OFFLINE_COMPLETE = 'MapboxBridgeOfflineComplete';
28
+ var UIViewAutoSizeUIViewAutoSize = /** @class */ (function (_super) {
29
+ __extends(UIViewAutoSizeUIViewAutoSize, _super);
30
+ function UIViewAutoSizeUIViewAutoSize() {
319
31
  return _super !== null && _super.apply(this, arguments) || this;
320
32
  }
321
- MapPanHandlerImpl.initWithOwnerAndListenerForMap = function (owner, listener, panState, mapView) {
322
- var handler = MapPanHandlerImpl.new();
323
- handler._owner = owner;
324
- handler._listener = new Map([[panState, listener]]);
325
- handler._mapView = mapView;
326
- return handler;
327
- };
328
- MapPanHandlerImpl.prototype.addListener = function (panState, listener) {
329
- this._listener.set(panState, listener);
330
- };
331
- MapPanHandlerImpl.prototype.pan = function (recognizer) {
332
- var panCoordinate = this.getCoordinates(recognizer);
333
- if (Trace.isEnabled()) {
334
- CLog(CLogTypes.info, 'MapPanHandlerImpl::pan(): top with state:', recognizer.state);
335
- }
336
- if (recognizer.state === UIGestureRecognizerState.Changed) {
337
- this.notifyListener(recognizer.state, panCoordinate.latitude, panCoordinate.longitude);
338
- }
339
- };
340
- MapPanHandlerImpl.prototype.panEnd = function (recognizer) {
341
- var panCoordinate = this.getCoordinates(recognizer);
342
- if (Trace.isEnabled()) {
343
- CLog(CLogTypes.info, 'MapPanHandlerImpl::panEnd(): top with state:', recognizer.state);
344
- }
345
- if (recognizer.state === UIGestureRecognizerState.Ended) {
346
- this.notifyListener(recognizer.state, panCoordinate.latitude, panCoordinate.longitude);
347
- }
348
- };
349
- MapPanHandlerImpl.prototype.panBegin = function (recognizer) {
350
- var panCoordinate = this.getCoordinates(recognizer);
351
- if (Trace.isEnabled()) {
352
- CLog(CLogTypes.info, 'MapPanHandlerImpl::panBegin(): top with state:', recognizer.state);
353
- }
354
- if (recognizer.state === UIGestureRecognizerState.Began) {
355
- this.notifyListener(recognizer.state, panCoordinate.latitude, panCoordinate.longitude);
356
- }
33
+ UIViewAutoSizeUIViewAutoSize.prototype.systemLayoutSizeFittingSize = function (boundsSize) {
34
+ var _a;
35
+ var view = (_a = this._view) === null || _a === void 0 ? void 0 : _a.get();
36
+ if (!view) {
37
+ return CGSizeZero;
38
+ }
39
+ var widthSpec = Utils.layout.makeMeasureSpec(Math.max(Screen.mainScreen.widthPixels, Utils.layout.toDevicePixels(boundsSize.width)), Utils.layout.AT_MOST);
40
+ var heighthSpec = Utils.layout.makeMeasureSpec(Math.max(Screen.mainScreen.widthPixels, Utils.layout.toDevicePixels(boundsSize.height)), Utils.layout.AT_MOST);
41
+ var measuredSize = View.measureChild(null, view, widthSpec, heighthSpec);
42
+ view.setMeasuredDimension(measuredSize.measuredWidth, measuredSize.measuredHeight);
43
+ var size = CGSizeMake(Utils.layout.toDeviceIndependentPixels(measuredSize.measuredWidth), Utils.layout.toDeviceIndependentPixels(measuredSize.measuredHeight));
44
+ return size;
357
45
  };
358
- MapPanHandlerImpl.prototype.getCoordinates = function (recognizer) {
359
- var panPoint = recognizer.locationInView(this._mapView);
360
- return this._mapView.convertPointToCoordinateFromView(panPoint, this._mapView);
361
- };
362
- MapPanHandlerImpl.prototype.notifyListener = function (panState, latitude, longitude) {
363
- if (this._listener.has(panState)) {
364
- this._listener.get(panState)({ lat: latitude, lng: longitude });
46
+ UIViewAutoSizeUIViewAutoSize.prototype.layoutSubviews = function () {
47
+ var _a;
48
+ var view = (_a = this._view) === null || _a === void 0 ? void 0 : _a.get();
49
+ if (!view) {
50
+ return;
365
51
  }
52
+ var frame = this.frame;
53
+ var size = this.frame.size;
54
+ View.layoutChild(null, view, 0, 0, Utils.layout.toDevicePixels(size.width), Utils.layout.toDevicePixels(size.height));
55
+ // this.frame = frame;
366
56
  };
367
- MapPanHandlerImpl.ObjCExposedMethods = {
368
- pan: { returns: interop.types.void, params: [interop.types.id] },
369
- panEnd: { returns: interop.types.void, params: [interop.types.id] },
370
- panBegin: { returns: interop.types.void, params: [interop.types.id] }
371
- };
372
- return MapPanHandlerImpl;
373
- }(NSObject));
374
- var MapSwipeHandlerImpl = /** @class */ (function (_super) {
375
- __extends(MapSwipeHandlerImpl, _super);
376
- function MapSwipeHandlerImpl() {
377
- return _super !== null && _super.apply(this, arguments) || this;
378
- }
379
- MapSwipeHandlerImpl.initWithOwnerAndListenerForMap = function (owner, listener, mapView) {
380
- var handler = MapSwipeHandlerImpl.new();
381
- handler._owner = owner;
382
- handler._listener = listener;
383
- handler._mapView = mapView;
384
- return handler;
385
- };
386
- MapSwipeHandlerImpl.prototype.swipe = function (recognizer) {
387
- var swipePoint = recognizer.locationInView(this._mapView);
388
- var swipeCoordinate = this._mapView.convertPointToCoordinateFromView(swipePoint, this._mapView);
389
- this._listener({
390
- lat: swipeCoordinate.latitude,
391
- lng: swipeCoordinate.longitude
392
- });
393
- };
394
- MapSwipeHandlerImpl.ObjCExposedMethods = {
395
- swipe: { returns: interop.types.void, params: [interop.types.id] }
396
- };
397
- return MapSwipeHandlerImpl;
398
- }(NSObject));
399
- // Export the enums for devs not using TS
400
- export * from './common';
57
+ return UIViewAutoSizeUIViewAutoSize;
58
+ }(UIView));
59
+ function createUIViewAutoSizeUIViewAutoSize(view) {
60
+ const self = UIViewAutoSizeUIViewAutoSize.new();
61
+ view['iosIgnoreSafeArea'] = true;
62
+ view._setupAsRootView({});
63
+ view.parent = Application.getRootView();
64
+ view._isAddedToNativeVisualTree = true;
65
+ view.callLoaded();
66
+ self._view = new WeakRef(view);
67
+ self.addSubview(view.nativeViewProtected);
68
+ view.nativeViewProtected.autoresizingMask = 2 /* UIViewAutoresizing.FlexibleWidth */ | 16 /* UIViewAutoresizing.FlexibleHeight */;
69
+ return self;
70
+ }
71
+ // Local caches and helpers
401
72
  let _markers = [];
402
- const _markerIconDownloadCache = [];
403
- const _setMapboxMapOptions = (mapView, settings) => {
404
- mapView.logoView.hidden = settings.hideLogo;
405
- mapView.logoViewPosition = _mapControlPositionToOrnamentPosition(settings.logoPosition);
406
- mapView.attributionButton.hidden = settings.hideAttribution;
407
- mapView.attributionButtonPosition = _mapControlPositionToOrnamentPosition(settings.attributionPosition);
408
- mapView.compassView.hidden = settings.hideCompass;
409
- mapView.compassViewPosition = _mapControlPositionToOrnamentPosition(settings.compassPosition);
410
- mapView.rotateEnabled = !settings.disableRotation;
411
- mapView.scrollEnabled = !settings.disableScroll;
412
- mapView.zoomEnabled = !settings.disableZoom;
413
- mapView.allowsTilting = !settings.disableTilt;
414
- if (settings.center && settings.center.lat && settings.center.lng) {
415
- const centerCoordinate = CLLocationCoordinate2DMake(settings.center.lat, settings.center.lng);
416
- mapView.setCenterCoordinateZoomLevelAnimated(centerCoordinate, settings.zoomLevel, false);
417
- }
418
- else {
419
- mapView.setZoomLevelAnimated(settings.zoomLevel, false);
420
- }
421
- mapView.showsUserLocation = settings.showUserLocation;
422
- mapView.autoresizingMask = 2 /* UIViewAutoresizing.FlexibleWidth */ | 16 /* UIViewAutoresizing.FlexibleHeight */;
423
- };
424
- const _mapControlPositionToOrnamentPosition = (position) => {
425
- switch (position) {
426
- case ControlPosition.TOP_LEFT:
427
- return 0 /* MGLOrnamentPosition.TopLeft */;
428
- case ControlPosition.TOP_RIGHT:
429
- return 1 /* MGLOrnamentPosition.TopRight */;
430
- case ControlPosition.BOTTOM_LEFT:
431
- return 2 /* MGLOrnamentPosition.BottomLeft */;
432
- case ControlPosition.BOTTOM_RIGHT:
433
- return 3 /* MGLOrnamentPosition.BottomRight */;
434
- }
435
- };
436
- const _getMapStyle = (input) => {
437
- if (input.startsWith('mapbox://styles') || input.startsWith('http://') || input.startsWith('https://')) {
438
- return NSURL.URLWithString(input);
439
- }
440
- else if (input.startsWith('~/')) {
441
- return NSURL.URLWithString('file://' + path.join(knownFolders.currentApp().path, input.replace('~/', '')));
442
- }
443
- else if (input === MapStyle.LIGHT) {
444
- return MGLStyle.lightStyleURL;
445
- }
446
- else if (input === MapStyle.DARK) {
447
- return MGLStyle.darkStyleURL;
448
- }
449
- else if (input === MapStyle.OUTDOORS) {
450
- return MGLStyle.outdoorsStyleURL;
451
- }
452
- else if (input === MapStyle.SATELLITE) {
453
- return MGLStyle.satelliteStyleURL;
454
- }
455
- else if (input === MapStyle.SATELLITE_STREETS) {
456
- return MGLStyle.satelliteStreetsStyleURL;
457
- }
458
- else if (input === MapStyle.TRAFFIC_DAY) {
459
- return NSURL.URLWithString('mapbox://styles/mapbox/traffic-day-v2');
460
- }
461
- else if (input === MapStyle.TRAFFIC_NIGHT) {
462
- return NSURL.URLWithString('mapbox://styles/mapbox/traffic-night-v2');
463
- }
464
- else {
465
- return MGLStyle.streetsStyleURL;
466
- }
467
- };
468
- function _getLocation(loc) {
469
- if (loc === null) {
470
- return null;
73
+ const _markerIconDownloadCache = {};
74
+ function getIosColor(color) {
75
+ const temp = color instanceof Color ? color : new Color(color);
76
+ if (Color.isValid(temp)) {
77
+ return temp.argb;
471
78
  }
472
79
  else {
473
- return {
474
- location: {
475
- lat: loc.coordinate.latitude,
476
- lng: loc.coordinate.longitude
477
- },
478
- speed: loc.location ? loc.location.speed : 0
479
- };
80
+ return new Color('black').argb;
480
81
  }
481
82
  }
482
- export function setLogLevel(level) {
483
- let loggingLevel;
484
- switch (level) {
485
- case 'none':
486
- loggingLevel = 0 /* MGLLoggingLevel.None */;
487
- break;
488
- case 'info':
489
- loggingLevel = 1 /* MGLLoggingLevel.Info */;
490
- break;
491
- case 'verbose':
492
- case 'debug':
493
- loggingLevel = 2 /* MGLLoggingLevel.Debug */;
494
- break;
495
- case 'error':
496
- loggingLevel = 3 /* MGLLoggingLevel.Error */;
497
- break;
498
- case 'fault':
499
- loggingLevel = 4 /* MGLLoggingLevel.Fault */;
500
- break;
501
- }
502
- MGLLoggingConfiguration.sharedConfiguration.loggingLevel = loggingLevel;
83
+ async function fetchImageIOS(imagePath) {
84
+ try {
85
+ if (!imagePath)
86
+ return null;
87
+ if (_markerIconDownloadCache[imagePath])
88
+ return _markerIconDownloadCache[imagePath];
89
+ const img = await Http.getImage(imagePath);
90
+ if (img && img.ios) {
91
+ _markerIconDownloadCache[imagePath] = img.ios;
92
+ return img.ios;
93
+ }
94
+ }
95
+ catch (e) {
96
+ // ignore
97
+ }
98
+ return null;
503
99
  }
504
- /**
505
- * Map View Class instantiated from XML
506
- *
507
- * This class is created by the NativeScript XML view parsing
508
- * code.
509
- */
100
+ function convertToJSON(data) {
101
+ return data ? JSON.parse(data.objectForKey?.('data') ?? data) : {};
102
+ }
103
+ // ---------------------- MapboxView ----------------------
510
104
  export class MapboxView extends MapboxViewBase {
511
105
  constructor() {
512
106
  super(...arguments);
513
107
  this.nativeMapView = null;
514
- this.delegate = null;
515
108
  this.settings = null;
516
109
  this.initialized = false;
517
- // see initMap. Count of how many times we've
518
- // tried to init the map.
519
110
  this.initCountHack = 50;
520
111
  }
521
- /**
522
- * programmatically include settings
523
- */
524
112
  setConfig(settings) {
525
- if (Trace.isEnabled()) {
526
- CLog(CLogTypes.info, 'setConfig(): settings:', settings);
113
+ if (settings.zoomLevel && !settings.center) {
114
+ // Eiffel tower, Paris
115
+ settings.center = {
116
+ lat: 48.858093,
117
+ lng: 2.294694
118
+ };
527
119
  }
528
120
  this.settings = settings;
529
121
  }
@@ -534,30 +126,11 @@ export class MapboxView extends MapboxViewBase {
534
126
  if (Trace.isEnabled()) {
535
127
  CLog(CLogTypes.info, 'createNativeView(): top');
536
128
  }
537
- const v = super.createNativeView();
538
- return v;
539
- }
540
- /**
541
- * init the native view.
542
- *
543
- * FIXME: It appears that the order of events is different between iOS and Android.
544
- * In the demo under Android, the main-page event handler is called first then the one
545
- * in the plugin. Under iOS it's the reverse.
546
- *
547
- * The symptom is that any properties that reference a binding aren't available
548
- * at the time this method is called. For example {{access_token}}.
549
- *
550
- * I'm sure there is something I do not understand about how this is supposed to work
551
- * and that the handstands below are not necessary.
552
- */
129
+ return super.createNativeView();
130
+ }
553
131
  onLoaded() {
554
132
  super.onLoaded();
555
- if (Trace.isEnabled()) {
556
- CLog(CLogTypes.info, 'initNativeView(): on - loaded');
557
- }
558
- if (this.telemetry === false) {
559
- NSUserDefaults.standardUserDefaults.setBoolForKey(false, 'MGLMapboxMetricsEnabled');
560
- }
133
+ // if (this.telemetry === false) NSUserDefaults.standardUserDefaults.setBoolForKey(false, 'MGLMapboxMetricsEnabled');
561
134
  if (!this.initialized) {
562
135
  this.initMap();
563
136
  this.initialized = true;
@@ -565,334 +138,282 @@ export class MapboxView extends MapboxViewBase {
565
138
  }
566
139
  initNativeView() {
567
140
  super.initNativeView();
141
+ if (Trace.isEnabled()) {
142
+ CLog(CLogTypes.info, 'initNativeView(): on - loaded');
143
+ }
568
144
  this.nativeView.owner = this;
569
145
  }
570
- /**
571
- * when the view is destroyed.
572
- *
573
- * This is called by the framework when the view is destroyed (made not visible).
574
- *
575
- * However, it does not seem to be called when the page is unloaded.
576
- *
577
- * @link https://docs.nativescript.org/plugins/ui-plugin-custom
578
- */
579
146
  async disposeNativeView() {
580
- if (Trace.isEnabled()) {
581
- CLog(CLogTypes.info, 'disposeNativeView(): top');
582
- }
583
147
  this.nativeView.owner = null;
584
- await this.mapbox.destroy();
585
- if (Trace.isEnabled()) {
586
- CLog(CLogTypes.info, 'disposeNativeView(): after mapbox.destroy()');
587
- }
148
+ if (this.mapbox)
149
+ await this.mapbox.destroy();
588
150
  super.disposeNativeView();
589
- if (Trace.isEnabled()) {
590
- CLog(CLogTypes.info, 'disposeNativeView(): bottom');
591
- }
592
151
  }
593
- /**
594
- * returns a reference to the class Mapbox API shim instance
595
- *
596
- * @see Mapbox
597
- */
598
152
  getMapboxApi() {
599
153
  return this.mapbox;
600
154
  }
601
- /**
602
- * initialize the map
603
- *
604
- * @see MGLMapViewDelegateImpl
605
- *
606
- * @todo FIXME: figure out why the accessToken property (which is using a binding in the demo XML) isn't set before we arrive here.
607
- */
608
155
  initMap() {
609
156
  if (Trace.isEnabled()) {
610
- CLog(CLogTypes.info, 'initMap() top with settings:', this.settings);
157
+ CLog(CLogTypes.info, 'initMap() top');
611
158
  }
612
- // FIXME: HACK: if we are arriving here because of an XML parse the property evaluations may not have
613
- // happened yet. This needs to be redone, but for the moment we'll assume the accessToken is done
614
- // via a property eval (since it really shouldn't be hard coded in XML).
615
- //
616
- // settings will only be set here if we are programmatically showing a map.
617
159
  if (!this.settings && !this.config.accessToken) {
618
- if (Trace.isEnabled()) {
619
- CLog(CLogTypes.info, 'initMap() no access token. Race condition on XML property evaluation?');
620
- }
621
- // If the user didn't specify an accessToken we don't want to loop forever
622
- if (this.initCountHack > 50) {
160
+ if (this.initCountHack > 50)
623
161
  return;
624
- }
625
- // FIXME: super ugly.
626
- setTimeout(() => {
627
- this.initMap();
628
- }, 50);
162
+ setTimeout(() => this.initMap(), 50);
629
163
  this.initCountHack++;
630
164
  return;
631
165
  }
632
- if (!this.settings) {
166
+ if (!this.settings)
633
167
  this.settings = Mapbox.merge(this.config, Mapbox.defaults);
634
- }
635
- else {
168
+ else
636
169
  this.settings = Mapbox.merge(this.settings, Mapbox.defaults);
637
- }
638
170
  if (!this.nativeMapView) {
639
171
  this.mapbox = new Mapbox(this);
640
- if (Trace.isEnabled()) {
641
- CLog(CLogTypes.info, 'initMap(): after new Mapbox()');
642
- }
643
- // called in a setTimeout call at the bottom.
644
- const drawMap = () => {
645
- MGLAccountManager.accessToken = this.settings.accessToken;
646
- this.nativeMapView = MGLMapView.alloc().initWithFrameStyleURL(CGRectMake(0, 0, this.nativeView.frame.size.width, this.nativeView.frame.size.height), _getMapStyle(this.settings.style));
647
- // this delegate class is defined later in this file and is where, in Obj-C land,
648
- // callbacks are delivered and handled.
649
- this.nativeMapView.delegate = this.delegate = MGLMapViewDelegateImpl.new().initWithCallback(() => {
650
- if (Trace.isEnabled()) {
651
- CLog(CLogTypes.info, 'initMap(): MLMapViewDeleteImpl onMapReady callback');
652
- }
653
- // FIXME: on the Android side the view is created in Mapbox::show(). On the iOS side it's created
654
- // here in MapboxView, however the mapbox api still needs a reference to it.
655
- this.mapbox.setMapboxViewInstance(this.nativeMapView);
656
- this.mapbox.initEventHandlerShim(this.settings, this.nativeMapView);
172
+ const options = {
173
+ parentView: this.nativeView,
174
+ onLocationPermissionGranted: (event) => {
657
175
  this.notify({
658
- eventName: MapboxViewBase.mapReadyEvent,
176
+ eventName: MapboxViewBase.locationPermissionGrantedEvent,
659
177
  object: this,
660
178
  map: this,
661
179
  ios: this.nativeMapView
662
180
  });
663
- // no permission required, but to align with Android we fire the event anyway
181
+ },
182
+ onLocationPermissionDenied: (event) => {
664
183
  this.notify({
665
- eventName: MapboxViewBase.locationPermissionGrantedEvent,
184
+ eventName: MapboxViewBase.locationPermissionDeniedEvent,
666
185
  object: this,
667
186
  map: this,
668
187
  ios: this.nativeMapView
669
188
  });
670
- });
671
- // this.delegate.setStyleLoadedCallback((map, style)=>{
672
- // this.delegate.setStyleLoadedCallback(null);
673
- // });
674
- _setMapboxMapOptions(this.nativeMapView, this.settings);
675
- _markers = [];
676
- this.nativeView.addSubview(this.nativeMapView);
677
- // this.notify will notify an event listener specified
678
- // in the XML, in this case (onMoveBegin)="..."
679
- this.mapbox.setOnMoveBeginListener((data) => {
189
+ },
190
+ onMapReady: (view) => {
191
+ this.nativeMapView = view;
192
+ // if (this.telemetry === false) {
193
+ // com.nativescript.mapbox.Telemetry.setUserTelemetryRequestState(this.nativeMapView, false);
194
+ // }
680
195
  if (Trace.isEnabled()) {
681
- CLog(CLogTypes.info, 'initMap(): onMoveBegin listener');
196
+ CLog(CLogTypes.info, 'initMap(): onMapReady event - calling notify with the MapboxViewBase.mapReadyEvent');
197
+ }
198
+ if (this.hasListeners(MapboxViewBase.mapReadyEvent)) {
199
+ if (Trace.isEnabled()) {
200
+ CLog(CLogTypes.info, 'initMap(): onMapReady has listeners.');
201
+ }
202
+ }
203
+ else {
204
+ if (Trace.isEnabled()) {
205
+ CLog(CLogTypes.info, 'initMap(): onMapReady DOES NOT HAVE listeners.');
206
+ }
682
207
  }
683
208
  this.notify({
684
- eventName: MapboxViewBase.moveBeginEvent,
209
+ eventName: MapboxViewBase.mapReadyEvent,
685
210
  object: this,
686
211
  map: this,
687
212
  ios: this.nativeMapView
688
213
  });
689
- }, this.nativeMapView);
690
- this.mapbox.setOnMoveEndListener((data) => {
214
+ },
215
+ onScrollEvent: (event) => {
691
216
  if (Trace.isEnabled()) {
692
- CLog(CLogTypes.info, 'initMap(): onMoveEnd listener');
217
+ CLog(CLogTypes.info, 'initMap(): onScrollEvent event:' + JSON.stringify(event));
693
218
  }
694
219
  this.notify({
695
- eventName: MapboxViewBase.moveEndEvent,
220
+ eventName: MapboxViewBase.scrollEvent,
696
221
  object: this,
222
+ event,
697
223
  map: this,
698
224
  ios: this.nativeMapView
699
225
  });
700
- }, this.nativeMapView);
701
- this.mapbox.setOnScrollListener((data) => {
226
+ },
227
+ onMoveBeginEvent: (event) => {
702
228
  if (Trace.isEnabled()) {
703
- CLog(CLogTypes.info, 'initMap(): onScroll listener');
229
+ CLog(CLogTypes.info, 'initMap(): onMoveBeginEvent event');
704
230
  }
705
231
  this.notify({
706
- eventName: MapboxViewBase.scrollEvent,
232
+ eventName: MapboxViewBase.moveBeginEvent,
233
+ object: this,
234
+ event,
235
+ map: this,
236
+ ios: this.nativeMapView
237
+ });
238
+ },
239
+ onMoveEndEvent: (event) => {
240
+ if (Trace.isEnabled()) {
241
+ CLog(CLogTypes.info, 'initMap(): onMoveEndEvent event');
242
+ }
243
+ this.notify({
244
+ eventName: MapboxViewBase.moveEndEvent,
707
245
  object: this,
246
+ event,
708
247
  map: this,
709
248
  ios: this.nativeMapView
710
249
  });
711
- }, this.nativeMapView);
250
+ }
251
+ };
252
+ this.settings = Mapbox.merge(this.settings, options);
253
+ const drawMap = () => {
254
+ if (Trace.isEnabled()) {
255
+ CLog(CLogTypes.info, 'drawMap()');
256
+ }
257
+ this.mapbox.show(this.settings);
258
+ // _setMapboxMapOptions(this.nativeMapView, this.settings);
259
+ // _markers = [];
260
+ // this.nativeView.addSubview(this.nativeMapView);
261
+ // this.mapbox.setMapboxViewInstance(this.nativeMapView);
712
262
  };
713
- // draw the map after a timeout
714
263
  setTimeout(drawMap, this.settings.delay ? this.settings.delay : 0);
715
264
  }
716
- } // end of initMap()
265
+ }
717
266
  onLayout(left, top, right, bottom) {
718
267
  super.onLayout(left, top, right, bottom);
719
- if (this.nativeMapView) {
268
+ if (this.nativeMapView)
720
269
  this.nativeMapView.layer.frame = this.ios.layer.bounds;
721
- }
722
270
  }
723
271
  [telemetryProperty.setNative](value) {
724
272
  NSUserDefaults.standardUserDefaults.setBoolForKey(false, 'MGLMapboxMetricsEnabled');
725
273
  }
726
274
  }
275
+ // ----------------------- Mapbox TS API -----------------------
727
276
  export class Mapbox extends MapboxCommon {
728
277
  constructor() {
729
- // reference to the native mapbox API
730
278
  super(...arguments);
731
279
  this.eventCallbacks = {};
280
+ this._markers = [];
281
+ this._observerTokens = [];
282
+ this._reusableCalloutView = null;
283
+ this._programmaticMapView = null;
284
+ // ---------------- Events wiring helpers ----------------
285
+ // Event listeners (setOnCameraChangeListener)
286
+ this.pushToken = (t) => this._observerTokens.push(t);
732
287
  }
733
- /**
734
- * set the mapboxViewInstance
735
- *
736
- * @see MapboxView::initMap();
737
- */
738
- setMapboxViewInstance(mapboxViewInstance) {
739
- this._mapboxViewInstance = mapboxViewInstance;
740
- }
741
- /**
742
- * event handler shim
743
- *
744
- * Initialize our event handler shim so that we can intercept events here.
745
- *
746
- * @param { MapboxView } mapboxView
747
- */
748
- initEventHandlerShim(settings, mapboxNativeViewInstance) {
749
- if (Trace.isEnabled()) {
750
- CLog(CLogTypes.info, 'Mapbox:initEventHandlerShim(): top');
751
- }
752
- this.setOnMapClickListener((point) => this.checkForClickEvent(point), mapboxNativeViewInstance);
288
+ setMapboxViewInstance(m) {
289
+ this._mapboxViewInstance = m;
753
290
  }
754
- /**
755
- * register a map event handler
756
- *
757
- * The NativeScript ContentView base class as on() and off() methods.
758
- */
759
291
  onMapEvent(eventName, id, callback, nativeMapView) {
760
- if (typeof this.eventCallbacks[eventName] == 'undefined') {
292
+ if (typeof this.eventCallbacks[eventName] === 'undefined')
761
293
  this.eventCallbacks[eventName] = [];
762
- }
763
- this.eventCallbacks[eventName].push({
764
- id,
765
- callback
766
- });
294
+ this.eventCallbacks[eventName].push({ id, callback });
767
295
  }
768
296
  offMapEvent(eventName, id, nativeMapView) {
769
- if (typeof this.eventCallbacks[eventName] == 'undefined') {
297
+ if (typeof this.eventCallbacks[eventName] === 'undefined')
770
298
  return;
771
- }
772
299
  this.eventCallbacks[eventName] = this.eventCallbacks[eventName].filter((entry) => entry.id !== id);
773
300
  }
774
- /**
775
- * If click events registered and a feature found for the event, then fire listener.
776
- */
777
301
  checkForClickEvent(point, nativeMap) {
778
302
  if (Trace.isEnabled()) {
779
- CLog(CLogTypes.info, 'Mapbox:checkForClickEvent(): got click event with point:', point);
303
+ CLog(CLogTypes.info, 'setOnMapClickListener(): click event at point:', point);
780
304
  }
781
305
  this.eventCallbacks['click'] &&
782
306
  this.eventCallbacks['click'].forEach((eventListener) => {
307
+ if (Trace.isEnabled()) {
308
+ CLog(CLogTypes.info, 'checkForClickEvent():', eventListener.id);
309
+ }
783
310
  this.queryRenderedFeatures({ point, layers: [eventListener.id] }, nativeMap)
784
311
  .then((response) => {
785
- if (response.length > 0) {
786
- eventListener.callback(response);
312
+ if (Trace.isEnabled()) {
313
+ CLog(CLogTypes.info, 'checkForClickEvent: queryRenderedFeatures:', response);
787
314
  }
315
+ if (response.length > 0)
316
+ eventListener.callback(response);
788
317
  })
789
- .catch((err) => {
790
- console.error('click error ', eventListener.id, err);
791
- });
318
+ .catch((err) => console.error('click error ', eventListener.id, err, err.stack));
792
319
  });
793
320
  this.view && this.view.notify({ eventName: 'mapClick', object: this.view, point });
794
321
  return false;
795
322
  }
796
- _addMarkers(markers, nativeMap) {
797
- if (!markers) {
323
+ // ---------------- lifecycle & programmatic ----------------
324
+ initEventHandlerShim(settings, mapboxNativeViewInstance) {
325
+ if (Trace.isEnabled()) {
326
+ CLog(CLogTypes.info, 'initEventHandlerShim(): top');
327
+ }
328
+ this.setOnMapClickListener((point) => {
329
+ if (this.selectedMarker) {
330
+ this.deselectMarker(this.selectedMarker);
331
+ return;
332
+ }
333
+ this.checkForClickEvent(point);
334
+ }, mapboxNativeViewInstance);
335
+ this.addNotificationCenterObserver(MAPBOX_BRIDGE_ANNOTATION_TAP, mapboxNativeViewInstance, (e) => this.onNativeAnnotationTap(e));
336
+ this.setOnMoveBeginListener((point) => {
798
337
  if (Trace.isEnabled()) {
799
- CLog(CLogTypes.info, 'No markers passed');
338
+ CLog(CLogTypes.info, 'initEventHandlerShim(): moveBegin:', point);
800
339
  }
801
- return;
802
- }
803
- if (!Array.isArray(markers)) {
340
+ if (typeof settings.onMoveBeginEvent != 'undefined') {
341
+ settings.onMoveBeginEvent(point);
342
+ }
343
+ }, mapboxNativeViewInstance);
344
+ this.setOnMoveEndListener((point) => {
804
345
  if (Trace.isEnabled()) {
805
- CLog(CLogTypes.info, "markers must be passed as an Array: [{title: 'foo'}]");
346
+ CLog(CLogTypes.info, 'initEventHandlerShim(): moveEnd:', point);
806
347
  }
807
- return;
808
- }
809
- const theMap = nativeMap || this._mapboxViewInstance;
810
- _downloadMarkerImages(markers).then((updatedMarkers) => {
811
- updatedMarkers.forEach((marker) => {
812
- const lat = marker.lat;
813
- const lng = marker.lng;
814
- const point = MGLPointAnnotation.new();
815
- point.coordinate = CLLocationCoordinate2DMake(lat, lng);
816
- point.title = marker.title;
817
- point.subtitle = marker.subtitle;
818
- // needs to be done before adding to the map, otherwise the delegate method 'mapViewImageForAnnotation' can't use it
819
- _markers.push(marker);
820
- theMap.addAnnotation(point);
821
- if (marker.selected) {
822
- theMap.selectAnnotationAnimated(point, false);
823
- }
824
- marker.ios = point;
825
- marker.update = (newSettings) => {
826
- _markers.forEach((_marker) => {
827
- if (marker.id === _marker.id) {
828
- if (newSettings.onTap !== undefined) {
829
- _marker.onTap = newSettings.onTap;
830
- }
831
- if (newSettings.onCalloutTap !== undefined) {
832
- _marker.onCalloutTap = newSettings.onCalloutTap;
833
- }
834
- if (newSettings.title !== undefined) {
835
- _marker.ios.title = _marker.title = newSettings.title;
836
- }
837
- if (newSettings.subtitle !== undefined) {
838
- _marker.ios.subtitle = _marker.subtitle = newSettings.subtitle;
839
- }
840
- if (newSettings.lat && newSettings.lng) {
841
- _marker.lat = newSettings.lat;
842
- _marker.lng = newSettings.lng;
843
- _marker.ios.coordinate = CLLocationCoordinate2DMake(newSettings.lat, newSettings.lng);
844
- }
845
- if (newSettings.selected) {
846
- theMap.selectAnnotationAnimated(_marker.ios, false);
847
- }
848
- }
849
- });
850
- };
851
- });
852
- });
348
+ if (typeof settings.onMoveEndEvent != 'undefined') {
349
+ settings.onMoveEndEvent(point);
350
+ }
351
+ }, mapboxNativeViewInstance);
352
+ this.setOnScrollListener((point) => {
353
+ if (Trace.isEnabled()) {
354
+ CLog(CLogTypes.info, 'initEventHandlerShim(): move:', point);
355
+ }
356
+ if (typeof settings.onScrollEvent != 'undefined') {
357
+ settings.onScrollEvent(point);
358
+ }
359
+ }, mapboxNativeViewInstance);
853
360
  }
854
- /**
855
- * create an display the map
856
- *
857
- * @todo FIXME: This method is not called. See MapboxView::initMap().
858
- */
859
- show(options) {
860
- if (Trace.isEnabled()) {
861
- CLog(CLogTypes.info, 'show(): top with options:', options);
862
- }
361
+ async show(options) {
362
+ // Implementation same as earlier merged file; create programmatic map instance
863
363
  return new Promise((resolve, reject) => {
864
364
  try {
365
+ if (Trace.isEnabled()) {
366
+ CLog(CLogTypes.info, 'show');
367
+ }
865
368
  const settings = Mapbox.merge(options, Mapbox.defaults);
866
- // let directions = MBDirections.alloc().initWithAccessToken(arg.accessToken);
867
- // alert("directions: " + directions);
868
- // if no accessToken was set the app may crash
869
- if (settings.accessToken === undefined) {
369
+ if (!settings.accessToken) {
870
370
  reject("Please set the 'accessToken' parameter");
871
371
  return;
872
372
  }
873
- // if already added, make sure it's removed first
874
- if (this._mapboxViewInstance) {
875
- this._mapboxViewInstance.removeFromSuperview();
876
- }
877
- const view = UIApplication.sharedApplication.keyWindow.rootViewController.view, frameRect = view.frame, mapFrame = CGRectMake(settings.margins.left, settings.margins.top, frameRect.size.width - settings.margins.left - settings.margins.right, frameRect.size.height - settings.margins.top - settings.margins.bottom), styleURL = _getMapStyle(settings.style);
878
- MGLAccountManager.accessToken = settings.accessToken;
879
- this._mapboxViewInstance = MGLMapView.alloc().initWithFrameStyleURL(mapFrame, styleURL);
880
- _setMapboxMapOptions(this._mapboxViewInstance, settings);
881
- this._mapboxViewInstance.delegate = MGLMapViewDelegateImpl.new().initWithCallback((mapView) => {
882
- resolve({
883
- ios: mapView
884
- });
885
- });
373
+ if (this._programmaticMapView) {
374
+ try {
375
+ this._programmaticMapView.removeFromSuperview();
376
+ }
377
+ catch (e) {
378
+ console.error(e, e.stack);
379
+ }
380
+ this._programmaticMapView = null;
381
+ }
382
+ const bridge = (this.bridgeInstance = MapboxBridge.alloc().init());
383
+ const view = options.parentView || UIApplication.sharedApplication.keyWindow.rootViewController.view;
384
+ const frameRect = view.frame;
385
+ const mapFrame = CGRectMake(settings.margins?.left ?? 0, settings.margins?.top ?? 0, frameRect.size.width - (settings.margins?.left ?? 0) - (settings.margins?.right ?? 0), frameRect.size.height - (settings.margins?.top ?? 0) - (settings.margins?.bottom ?? 0));
386
+ const style = typeof settings.style === 'string' ? settings.style : settings.style || 'streets';
387
+ const { center, disableRotation, disableScroll, disableTilt, disableZoom, hideAttribution, hideLogo, showUserLocation, zoomLevel, ...others } = settings;
388
+ const nativeMap = bridge.createMap(mapFrame.origin.x, mapFrame.origin.y, mapFrame.size.width, mapFrame.size.height, settings.accessToken, style, JSON.stringify({
389
+ zoomLevel,
390
+ center,
391
+ hideLogo,
392
+ hideAttribution,
393
+ disableRotation,
394
+ disableScroll,
395
+ disableZoom,
396
+ disableTilt,
397
+ showUserLocation
398
+ }));
399
+ nativeMap.autoresizingMask = 2 /* UIViewAutoresizing.FlexibleWidth */ | 16 /* UIViewAutoresizing.FlexibleHeight */;
886
400
  _markers = [];
887
- this._addMarkers(settings.markers);
888
- // wrapping in a little timeout since the map area tends to flash black a bit initially
889
- setTimeout(() => {
890
- view.addSubview(this._mapboxViewInstance);
891
- }, 500);
401
+ this.addMarkers(settings.markers);
402
+ // setTimeout(() => view.addSubview(nativeMap), 0);
403
+ view.addSubview(nativeMap);
404
+ this.setMapboxViewInstance(nativeMap);
405
+ if (settings.showUserLocation) {
406
+ this.showUserLocationMarker({});
407
+ }
408
+ this.initEventHandlerShim(settings, this._mapboxViewInstance);
409
+ if (settings.onMapReady) {
410
+ settings.onMapReady(this._mapboxViewInstance);
411
+ }
412
+ resolve({ ios: nativeMap });
892
413
  }
893
414
  catch (ex) {
894
415
  if (Trace.isEnabled()) {
895
- CLog(CLogTypes.info, 'Error in mapbox.show: ' + ex);
416
+ CLog(CLogTypes.error, 'show:', ex, ex.stack);
896
417
  }
897
418
  reject(ex);
898
419
  }
@@ -901,15 +422,12 @@ export class Mapbox extends MapboxCommon {
901
422
  hide() {
902
423
  return new Promise((resolve, reject) => {
903
424
  try {
904
- if (this._mapboxViewInstance) {
905
- this._mapboxViewInstance.removeFromSuperview();
425
+ if (this._programmaticMapView) {
426
+ this._programmaticMapView.hidden = true;
906
427
  }
907
428
  resolve();
908
429
  }
909
430
  catch (ex) {
910
- if (Trace.isEnabled()) {
911
- CLog(CLogTypes.info, 'Error in mapbox.hide: ' + ex);
912
- }
913
431
  reject(ex);
914
432
  }
915
433
  });
@@ -917,1640 +435,1292 @@ export class Mapbox extends MapboxCommon {
917
435
  unhide() {
918
436
  return new Promise((resolve, reject) => {
919
437
  try {
920
- if (this._mapboxViewInstance) {
921
- const view = UIApplication.sharedApplication.keyWindow.rootViewController.view;
922
- view.addSubview(this._mapboxViewInstance);
438
+ if (this._programmaticMapView) {
439
+ this._programmaticMapView.hidden = false;
923
440
  resolve();
924
441
  }
925
- else {
442
+ else
926
443
  reject('No map found');
927
- }
928
444
  }
929
445
  catch (ex) {
930
- if (Trace.isEnabled()) {
931
- CLog(CLogTypes.info, 'Error in mapbox.unhide: ' + ex);
932
- }
933
446
  reject(ex);
934
447
  }
935
448
  });
936
449
  }
937
450
  destroy(nativeMap) {
938
- return new Promise((resolve, reject) => {
939
- const theMap = nativeMap || this._mapboxViewInstance;
940
- if (theMap) {
941
- theMap.removeFromSuperview();
942
- theMap.delegate = null;
943
- }
944
- resolve();
945
- });
946
- }
947
- // ----------------------------------------
948
- // Life Cycle Hooks - Required on Android
949
- // ----------------------------------------
950
- onStart(nativeMap) {
951
- return Promise.resolve();
952
- }
953
- onResume(nativeMap) {
954
- return Promise.resolve();
955
- }
956
- onPause(nativeMap) {
957
- return Promise.resolve();
958
- }
959
- onStop(nativeMap) {
960
- return Promise.resolve();
961
- }
962
- onLowMemory(nativeMap) {
963
- return Promise.resolve();
964
- }
965
- onDestroy(nativeMap) {
966
- return Promise.resolve();
967
- }
968
- /**
969
- * explicitly set a map style
970
- */
971
- setMapStyle(style, nativeMap) {
972
451
  return new Promise((resolve, reject) => {
973
452
  try {
974
- const theMap = nativeMap || this._mapboxViewInstance;
975
- // the style takes some time to load so we have to set a callback
976
- // to wait for the style to finish loading
977
- const delegate = theMap.delegate;
978
- delegate.setStyleLoadedCallback((mapView) => {
979
- if (Trace.isEnabled()) {
980
- CLog(CLogTypes.info, 'Mapbox:setMapStyle(): style loaded callback returned.');
453
+ const theMap = nativeMap || this._mapboxViewInstance || this._programmaticMapView;
454
+ if (theMap) {
455
+ try {
456
+ theMap.removeFromSuperview();
457
+ }
458
+ catch (e) {
459
+ console.error(e, e.stack);
981
460
  }
982
- resolve();
983
- });
984
- theMap.styleURL = _getMapStyle(style);
985
- }
986
- catch (ex) {
987
- if (Trace.isEnabled()) {
988
- CLog(CLogTypes.info, 'Error in mapbox.setMapStyle: ' + ex);
989
461
  }
990
- reject(ex);
991
- }
992
- });
993
- }
994
- async getImage(imageId, nativeMap) {
995
- return new Promise((resolve, reject) => {
996
- const theMap = nativeMap || this._mapboxViewInstance;
997
- if (!theMap) {
998
- reject('No map has been loaded');
999
- return;
1000
- }
1001
- try {
1002
- const nativeImage = theMap.style.imageForName(imageId);
1003
- const img = new ImageSource(nativeImage);
1004
- resolve(img);
462
+ if (this._programmaticMapView) {
463
+ this._programmaticMapView = null;
464
+ }
465
+ const bridge = MapboxBridge.bridgeFor(theMap);
466
+ bridge.destroy();
467
+ if (bridge === this.bridgeInstance) {
468
+ this.bridgeInstance = null;
469
+ }
470
+ try {
471
+ this._observerTokens.forEach((t) => {
472
+ try {
473
+ NSNotificationCenter.defaultCenter.removeObserver(t);
474
+ }
475
+ catch (e) {
476
+ console.error(e, e.stack);
477
+ }
478
+ });
479
+ this._observerTokens = [];
480
+ }
481
+ catch (e) {
482
+ console.error(e, e.stack);
483
+ }
484
+ if (this._reusableCalloutView) {
485
+ this._reusableCalloutView._tearDownUI();
486
+ this._reusableCalloutView = null;
487
+ }
488
+ resolve();
1005
489
  }
1006
490
  catch (ex) {
1007
- reject('Error during getImage: ' + ex);
1008
- if (Trace.isEnabled()) {
1009
- CLog(CLogTypes.info, 'Error in mapbox.getImage: ' + ex);
1010
- }
1011
- throw ex;
491
+ reject(ex);
1012
492
  }
1013
493
  });
1014
494
  }
495
+ // ---------------- Images ----------------
1015
496
  async addImage(imageId, imagePath, nativeMap) {
1016
497
  return new Promise(async (resolve, reject) => {
1017
- const theMap = nativeMap || this._mapboxViewInstance;
1018
- if (!theMap) {
1019
- reject('No map has been loaded');
1020
- return;
1021
- }
1022
498
  try {
1023
- const imageSource = await this.fetchImageSource(imagePath);
1024
- theMap.style.setImageForName(imageSource.ios, imageId);
499
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
500
+ if (!b) {
501
+ reject('No bridge available');
502
+ return;
503
+ }
504
+ let imageSource = null;
505
+ try {
506
+ imageSource = ImageSource.fromFileOrResourceSync(imagePath);
507
+ }
508
+ catch (e) {
509
+ console.error('error adding image:', e, e.stack);
510
+ imageSource = null;
511
+ }
512
+ if (!imageSource) {
513
+ const httpImg = await Http.getImage(imagePath).catch(() => null);
514
+ if (!httpImg || !httpImg.ios) {
515
+ reject('Unable to fetch image');
516
+ return;
517
+ }
518
+ b.addImage(imageId, httpImg.ios);
519
+ resolve();
520
+ return;
521
+ }
522
+ b.addImage(imageId, imageSource.ios);
1025
523
  resolve();
1026
524
  }
1027
525
  catch (ex) {
1028
- reject('Error during addImage: ' + ex);
1029
- if (Trace.isEnabled()) {
1030
- CLog(CLogTypes.info, 'Error in mapbox.addImage: ' + ex);
1031
- }
1032
- throw ex;
526
+ reject(ex);
1033
527
  }
1034
528
  });
1035
529
  }
1036
530
  async removeImage(imageId, nativeMap) {
1037
531
  return new Promise((resolve, reject) => {
1038
- const theMap = nativeMap || this._mapboxViewInstance;
1039
- if (!theMap) {
1040
- reject('No map has been loaded');
1041
- return;
1042
- }
1043
532
  try {
1044
- theMap.style.removeImageForName(imageId);
533
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
534
+ if (!b) {
535
+ reject('No bridge available');
536
+ return;
537
+ }
538
+ b.removeImage(imageId);
1045
539
  resolve();
1046
540
  }
1047
541
  catch (ex) {
1048
- reject('Error during removeImage: ' + ex);
1049
- if (Trace.isEnabled()) {
1050
- CLog(CLogTypes.info, 'Error in mapbox.removeImage: ' + ex);
1051
- }
1052
- throw ex;
542
+ reject(ex);
1053
543
  }
1054
544
  });
1055
545
  }
1056
- addMarkers(markers, nativeMap) {
1057
- return new Promise((resolve, reject) => {
546
+ // ---------------- Markers ----------------
547
+ async addMarkers(markers, nativeMap) {
548
+ return new Promise(async (resolve, reject) => {
1058
549
  try {
1059
- const theMap = nativeMap || this._mapboxViewInstance;
1060
- this._addMarkers(markers, theMap);
550
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
551
+ if (!b) {
552
+ reject('No bridge available');
553
+ return;
554
+ }
555
+ const updated = await Promise.all(markers.map(async (m) => {
556
+ if (m.icon && typeof m.icon === 'string' && m.icon.startsWith('http')) {
557
+ m.iconDownloaded = await fetchImageIOS(m.icon);
558
+ }
559
+ return m;
560
+ }));
561
+ updated.forEach((m) => {
562
+ if (m.icon && typeof m.icon === 'string' && m.iconDownloaded) {
563
+ try {
564
+ b.addImage(m.icon, m.iconDownloaded);
565
+ delete m.iconDownloaded;
566
+ }
567
+ catch (e) {
568
+ console.error(e, e.stack);
569
+ }
570
+ }
571
+ });
572
+ let firstId = Date.now() * 1000;
573
+ let markerToSelect;
574
+ updated.forEach((marker) => {
575
+ marker.id = marker.id ?? firstId++;
576
+ if (marker.id && !this._markers.find((mm) => mm.id === marker.id))
577
+ this._markers.push(marker);
578
+ if (marker.selected) {
579
+ markerToSelect = marker;
580
+ }
581
+ marker.update = (newSettings) => {
582
+ const _marker = this._markers.find((m) => m.id === marker.id);
583
+ if (_marker) {
584
+ if (newSettings.onTap !== undefined) {
585
+ _marker.onTap = newSettings.onTap;
586
+ }
587
+ if (newSettings.onCalloutTap !== undefined) {
588
+ _marker.onCalloutTap = newSettings.onCalloutTap;
589
+ }
590
+ if (newSettings.title !== undefined) {
591
+ _marker.title = newSettings.title;
592
+ }
593
+ if (newSettings.subtitle !== undefined) {
594
+ _marker.subtitle = newSettings.subtitle;
595
+ }
596
+ if (newSettings.lat && newSettings.lng) {
597
+ _marker.lat = newSettings.lat;
598
+ _marker.lng = newSettings.lng;
599
+ b.updateMarkerPosition(_marker.id + '', newSettings.lat, newSettings.lng);
600
+ }
601
+ if (newSettings.selected) {
602
+ this.selectMarker(_marker);
603
+ }
604
+ }
605
+ };
606
+ });
607
+ b.addMarkers(JSON.stringify(updated));
608
+ if (markerToSelect) {
609
+ this.selectMarker(markerToSelect);
610
+ }
1061
611
  resolve();
1062
612
  }
1063
613
  catch (ex) {
1064
- if (Trace.isEnabled()) {
1065
- CLog(CLogTypes.error, 'Error in mapbox.addMarkers: ' + ex);
1066
- }
1067
614
  reject(ex);
1068
615
  }
1069
616
  });
1070
617
  }
1071
- removeMarkers(ids, nativeMap) {
1072
- return new Promise((resolve, reject) => {
618
+ async deselectMarker(marker) {
619
+ if (this.selectedMarker === marker || this.selectedMarker?.id === marker.id) {
620
+ this.hideCalloutForMarkerById(marker.id + '');
621
+ this.selectedMarker = null;
622
+ }
623
+ }
624
+ selectMarker(marker) {
625
+ return new Promise(async (resolve, reject) => {
1073
626
  try {
1074
- const theMap = nativeMap || this._mapboxViewInstance;
1075
- const markersToRemove = [];
1076
- _markers.forEach((marker) => {
1077
- if (!ids || (marker.id && ids.indexOf(marker.id) > -1)) {
1078
- markersToRemove.push(marker.ios);
1079
- }
1080
- });
1081
- // remove markers from cache
1082
- if (ids) {
1083
- _markers = _markers.filter((marker) => ids.indexOf(marker.id) < 0);
1084
- }
1085
- else {
1086
- _markers = [];
627
+ if (Trace.isEnabled()) {
628
+ CLog(CLogTypes.info, 'selectMarker():', marker.id);
1087
629
  }
1088
- if (markersToRemove.length > 0) {
1089
- theMap.removeAnnotations(markersToRemove);
630
+ if (this.selectedMarker) {
631
+ this.deselectMarker(this.selectedMarker);
1090
632
  }
633
+ await this.showCalloutForMarkerById(marker.id + '');
1091
634
  resolve();
1092
635
  }
1093
636
  catch (ex) {
1094
637
  if (Trace.isEnabled()) {
1095
- CLog(CLogTypes.info, 'Error in mapbox.removeMarkers: ' + ex);
638
+ CLog(CLogTypes.error, 'selectMarker():', ex, ex.stack);
1096
639
  }
1097
640
  reject(ex);
1098
641
  }
1099
642
  });
1100
643
  }
1101
- setCenter(options, nativeMap) {
644
+ removeMarkers(ids, nativeMap) {
1102
645
  return new Promise((resolve, reject) => {
1103
646
  try {
1104
- const theMap = nativeMap || this._mapboxViewInstance;
1105
- const animated = options.animated === undefined || options.animated;
1106
- const coordinate = CLLocationCoordinate2DMake(options.lat, options.lng);
1107
- theMap.setCenterCoordinateAnimated(coordinate, animated);
647
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
648
+ if (!b) {
649
+ reject('No bridge available');
650
+ return;
651
+ }
652
+ b.removeMarkers(ids ? JSON.stringify(ids.map((id) => id + '')) : null);
1108
653
  resolve();
1109
654
  }
1110
655
  catch (ex) {
1111
- if (Trace.isEnabled()) {
1112
- CLog(CLogTypes.info, 'Error in mapbox.setCenter: ' + ex);
1113
- }
1114
656
  reject(ex);
1115
657
  }
1116
658
  });
1117
659
  }
1118
- getCenter(nativeMap) {
660
+ updateMarkerPosition(markerId, lat, lng, nativeMap) {
1119
661
  return new Promise((resolve, reject) => {
1120
662
  try {
1121
- const theMap = nativeMap || this._mapboxViewInstance;
1122
- const coordinate = theMap.centerCoordinate;
1123
- resolve({
1124
- lat: coordinate.latitude,
1125
- lng: coordinate.longitude
1126
- });
663
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
664
+ if (!b) {
665
+ reject('No bridge available');
666
+ return;
667
+ }
668
+ const ok = b.updateMarkerPosition ? b.updateMarkerPosition(markerId, lat, lng) : false;
669
+ try {
670
+ if (b.updateViewAnnotationForMarker)
671
+ b.updateViewAnnotationForMarker(markerId, lat, lng);
672
+ }
673
+ catch { }
674
+ if (ok)
675
+ resolve();
676
+ else
677
+ reject('Marker not found or failed to update');
1127
678
  }
1128
679
  catch (ex) {
1129
- if (Trace.isEnabled()) {
1130
- CLog(CLogTypes.info, 'Error in mapbox.getCenter: ' + ex);
1131
- }
1132
680
  reject(ex);
1133
681
  }
1134
682
  });
1135
683
  }
1136
- setZoomLevel(options, nativeMap) {
684
+ // ---------------- Polylines ----------------
685
+ addPolyline(options, nativeMap) {
1137
686
  return new Promise((resolve, reject) => {
1138
687
  try {
1139
- const theMap = nativeMap || this._mapboxViewInstance;
1140
- const animated = options.animated === undefined || options.animated;
1141
- const level = options.level;
1142
- if (level >= 0 && level <= 20) {
1143
- theMap.setZoomLevelAnimated(level, animated);
1144
- resolve();
688
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
689
+ if (!b) {
690
+ reject('No bridge available');
691
+ return;
1145
692
  }
1146
- else {
1147
- reject('invalid ZoomLevel, use any double value from 0 to 20 (like 8.3)');
693
+ if (!options.points) {
694
+ reject("Please set the 'points' parameter");
695
+ return;
1148
696
  }
697
+ // const coords = options.points.map((p) => [p.lng, p.lat]);
698
+ const coordsJSON = JSON.stringify(options.points.map((p) => [p.lng, p.lat]));
699
+ const opts = { strokeColor: getIosColor(options.color), strokeWidth: options.width, strokeOpacity: options.opacity };
700
+ const id = (options.id ?? new Date().getTime()) + '';
701
+ const ok = b.addPolyline(id, coordsJSON, JSON.stringify(opts));
702
+ if (ok)
703
+ resolve();
704
+ else
705
+ reject('Failed to add polyline');
1149
706
  }
1150
707
  catch (ex) {
1151
- if (Trace.isEnabled()) {
1152
- CLog(CLogTypes.info, 'Error in mapbox.setZoomLevel: ' + ex);
1153
- }
1154
708
  reject(ex);
1155
709
  }
1156
710
  });
1157
711
  }
1158
- getZoomLevel(nativeMap) {
712
+ async addLinePoint(id, lnglat, sourceId, nativeMap) {
1159
713
  return new Promise((resolve, reject) => {
1160
714
  try {
1161
- const theMap = nativeMap || this._mapboxViewInstance;
1162
- resolve(theMap.zoomLevel);
715
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
716
+ if (!b) {
717
+ reject('No bridge available');
718
+ return;
719
+ }
720
+ const ok = b.addLinePoint(id, JSON.stringify(lnglat), sourceId);
721
+ if (ok)
722
+ resolve();
723
+ else
724
+ reject('Failed to add line point');
1163
725
  }
1164
726
  catch (ex) {
1165
- if (Trace.isEnabled()) {
1166
- CLog(CLogTypes.info, 'Error in mapbox.getZoomLevel: ' + ex);
1167
- }
1168
727
  reject(ex);
1169
728
  }
1170
729
  });
1171
730
  }
1172
- setTilt(options, nativeMap) {
731
+ removePolylines(ids, nativeMap) {
1173
732
  return new Promise((resolve, reject) => {
1174
733
  try {
1175
- const theMap = nativeMap || this._mapboxViewInstance;
1176
- const cam = theMap.camera;
1177
- cam.pitch = options.tilt;
1178
- const durationMs = options.duration ? options.duration : 5000;
1179
- theMap.setCameraWithDurationAnimationTimingFunction(cam, durationMs / 1000, CAMediaTimingFunction.functionWithName(kCAMediaTimingFunctionEaseInEaseOut));
1180
- setTimeout(() => {
734
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
735
+ if (!b || !b.removePolylines) {
736
+ reject('No bridge available');
737
+ return;
738
+ }
739
+ const ok = b.removePolylines(ids ? ids.map((s) => s + '') : null);
740
+ if (ok)
1181
741
  resolve();
1182
- }, durationMs);
742
+ else
743
+ reject('Failed to remove polylines');
1183
744
  }
1184
745
  catch (ex) {
1185
- if (Trace.isEnabled()) {
1186
- CLog(CLogTypes.info, 'Error in mapbox.setTilt: ' + ex);
1187
- }
1188
746
  reject(ex);
1189
747
  }
1190
748
  });
1191
749
  }
1192
- getTilt(nativeMap) {
750
+ // ---------------- Polygons ----------------
751
+ addPolygon(options, nativeMap) {
1193
752
  return new Promise((resolve, reject) => {
1194
753
  try {
1195
- const theMap = nativeMap || this._mapboxViewInstance;
1196
- resolve(theMap.camera.pitch);
1197
- }
1198
- catch (ex) {
754
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
755
+ if (!b) {
756
+ reject('No bridge available');
757
+ return;
758
+ }
759
+ if (!options.points) {
760
+ reject("Please set the 'points' parameter");
761
+ return;
762
+ }
763
+ // const coords = options.points.map((p) => [p.lng, p.lat]);
764
+ const coordsJSON = JSON.stringify(options.points.map((p) => [p.lng, p.lat]));
765
+ const opts = {
766
+ fillColor: getIosColor(options.fillColor),
767
+ fillOpacity: options.fillOpacity,
768
+ strokeColor: getIosColor(options.strokeColor),
769
+ strokeWidth: options.strokeWidth,
770
+ strokeOpacity: options.strokeOpacity
771
+ };
772
+ const id = (options.id ?? new Date().getTime()) + '';
1199
773
  if (Trace.isEnabled()) {
1200
- CLog(CLogTypes.info, 'Error in mapbox.getTilt: ' + ex);
774
+ CLog(CLogTypes.info, 'addPolygon:', id, coordsJSON, JSON.stringify(opts));
1201
775
  }
776
+ const ok = b.addPolygon(id, coordsJSON, JSON.stringify(opts));
777
+ if (ok)
778
+ resolve();
779
+ else
780
+ reject('Failed to add polygon');
781
+ }
782
+ catch (ex) {
1202
783
  reject(ex);
1203
784
  }
1204
785
  });
1205
786
  }
1206
- getUserLocation(nativeMap) {
787
+ // removePolygons / removePolylines (TS wrappers)
788
+ removePolygons(ids, nativeMap) {
1207
789
  return new Promise((resolve, reject) => {
1208
790
  try {
1209
- const theMap = nativeMap || this._mapboxViewInstance;
1210
- const loc = theMap.userLocation;
1211
- if (loc === null) {
1212
- reject('Location not available');
1213
- }
1214
- else {
1215
- resolve(_getLocation(loc));
791
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
792
+ if (!b) {
793
+ reject('No bridge available');
794
+ return;
1216
795
  }
796
+ const ok = b.removePolygons(ids ? ids.map((s) => s + '') : null);
797
+ if (ok)
798
+ resolve();
799
+ else
800
+ reject('Failed to remove polygons');
1217
801
  }
1218
802
  catch (ex) {
1219
- if (Trace.isEnabled()) {
1220
- CLog(CLogTypes.info, 'Error in mapbox.getUserLocation: ' + ex);
1221
- }
1222
803
  reject(ex);
1223
804
  }
1224
805
  });
1225
806
  }
1226
- /**
1227
- * convert string to camera mode constant.
1228
- *
1229
- * Supported modes on iOS are different than on Android.
1230
- *
1231
- * @todo come up with a reasonable set of cross platform defaults.
1232
- */
1233
- _stringToCameraMode(mode) {
1234
- switch (mode) {
1235
- case 'NONE':
1236
- return 0 /* MGLUserTrackingMode.None */;
1237
- case 'NONE_COMPASS':
1238
- console.log('MapboxView::_stringToCameraMode(): NONE_COMPASS unsupported on iOS');
1239
- return 0 /* MGLUserTrackingMode.None */;
1240
- case 'NONE_GPS':
1241
- console.log('MapboxView::_stringToCameraMode(): NONE_GPS unsupported on iOS');
1242
- return 0 /* MGLUserTrackingMode.None */;
1243
- case 'TRACKING':
1244
- return 1 /* MGLUserTrackingMode.Follow */;
1245
- case 'TRACKING_COMPASS':
1246
- return 2 /* MGLUserTrackingMode.FollowWithHeading */;
1247
- case 'TRACKING_GPS':
1248
- // a reasonable approximation.
1249
- return 1 /* MGLUserTrackingMode.Follow */;
1250
- case 'TRACKING_GPS_NORTH':
1251
- return 3 /* MGLUserTrackingMode.FollowWithCourse */;
1252
- default:
1253
- console.log(`_stringToCameraMode: invalid cameraMode: ${mode}`);
1254
- }
1255
- return 0 /* MGLUserTrackingMode.None */;
1256
- }
1257
- _stringToRenderMode(mode) {
1258
- let renderMode;
1259
- switch (mode) {
1260
- case 'NORMAL':
1261
- return 'NORMAL';
1262
- case 'COMPASS':
1263
- return 'COMPASS';
1264
- case 'GPS':
1265
- return 'GPS';
1266
- }
1267
- }
1268
- _convertCameraMode(mode) {
1269
- switch (mode) {
1270
- case 0 /* MGLUserTrackingMode.None */:
1271
- return 'NONE';
1272
- case 1 /* MGLUserTrackingMode.Follow */:
1273
- return 'TRACKING';
1274
- case 2 /* MGLUserTrackingMode.FollowWithHeading */:
1275
- return 'TRACKING_COMPASS';
1276
- case 3 /* MGLUserTrackingMode.FollowWithCourse */:
1277
- return 'TRACKING_GPS_NORTH';
1278
- }
1279
- }
1280
- /**
1281
- * show a user location marker
1282
- *
1283
- * This method must not be called before location permissions have been granted.
1284
- *
1285
- * Supported options under iOS are:
1286
- *
1287
- * - renderMode
1288
- * - cameraMode
1289
- * - clickListener
1290
- *
1291
- * Other options are ignored. Compare with the android version that supports a
1292
- * different set of options.
1293
- *
1294
- * @param {object} options
1295
- */
1296
- showUserLocationMarker(options, nativeMap) {
1297
- return new Promise((resolve, reject) => {
807
+ // ---------------- GeoJSON Clustered source helper ----------------
808
+ // Adds a clustered GeoJSON source + layers for clusters and unclustered points.
809
+ addGeoJsonClustered(options, nativeMap) {
810
+ return new Promise(async (resolve, reject) => {
1298
811
  try {
1299
- const theMap = nativeMap || this._mapboxViewInstance;
1300
- theMap.showsUserLocation = true;
1301
- theMap.userTrackingMode = this._stringToCameraMode(options.cameraMode);
1302
- theMap.showsUserHeadingIndicator = true;
1303
- this.userLocationRenderMode = this._stringToRenderMode(options.renderMode);
1304
- // the "delegate" needs to know the modes
1305
- const delegate = theMap.delegate;
1306
- // tell the delegate to tell the CustomerLocationAnnotationView to change the
1307
- // appearance of the marker.
1308
- delegate.changeUserLocationRenderMode(this.userLocationRenderMode);
1309
- if (typeof options.clickListener != 'undefined') {
1310
- delegate.setUserLocationClickListener(options.clickListener);
812
+ // options: id, data (geojson object or url), clusterRadius?, clusterMaxZoom?, clusterProperties?, layers styling...
813
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
814
+ if (!b) {
815
+ reject('No bridge available');
816
+ return;
1311
817
  }
818
+ if (!options.id) {
819
+ reject("Please provide 'id'");
820
+ return;
821
+ }
822
+ const sourceId = options.id;
823
+ let geojsonString = null;
824
+ if (options.data) {
825
+ geojsonString = typeof options.data === 'string' ? options.data : JSON.stringify(options.data);
826
+ }
827
+ else if (options.url) {
828
+ // pass URL string as source (bridge.addSourceGeoJSON expects geojson text or url; uses same param)
829
+ geojsonString = options.url;
830
+ }
831
+ else {
832
+ reject("Please pass 'data' or 'url' in addGeoJsonClustered options");
833
+ return;
834
+ }
835
+ // Build source JSON for clustering
836
+ const sourceObj = {
837
+ type: 'geojson',
838
+ data: options.data ? options.data : options.url,
839
+ cluster: true,
840
+ clusterRadius: typeof options.clusterRadius !== 'undefined' ? options.clusterRadius : 50,
841
+ clusterMaxZoom: typeof options.clusterMaxZoom !== 'undefined' ? options.clusterMaxZoom : 14
842
+ };
843
+ // use addSource (bridge.addSourceGeoJSON expects a geojson string, for clarity we'll add via bridge.addSourceGeoJSON)
844
+ // Some bridge implementations expect raw geojson string; if url, pass as string url
845
+ const sourceOk = b.addSourceGeoJSON(sourceId, JSON.stringify(sourceObj));
846
+ if (!sourceOk) {
847
+ // fallback: try addSource with just data
848
+ try {
849
+ const ok2 = b.addSourceGeoJSON(sourceId, geojsonString || '');
850
+ if (!ok2) {
851
+ reject('Failed to add clustered source');
852
+ return;
853
+ }
854
+ }
855
+ catch (e) {
856
+ reject('Failed to add clustered source: ' + e);
857
+ return;
858
+ }
859
+ }
860
+ // Create cluster layer (circle layer)
861
+ const clusterLayer = {
862
+ id: sourceId + '_clusters',
863
+ type: 'circle',
864
+ source: sourceId,
865
+ filter: ['has', 'point_count'],
866
+ paint: {
867
+ 'circle-color': ['step', ['get', 'point_count'], '#51bbd6', 100, '#f1f075', 750, '#f28cb1'],
868
+ 'circle-radius': ['step', ['get', 'point_count'], 20, 100, 30, 750, 40]
869
+ }
870
+ };
871
+ // Count label for clusters
872
+ const clusterCountLayer = {
873
+ id: sourceId + '_cluster-count',
874
+ type: 'symbol',
875
+ source: sourceId,
876
+ filter: ['has', 'point_count'],
877
+ layout: {
878
+ 'text-field': '{point_count_abbreviated}',
879
+ 'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'],
880
+ 'text-size': 12
881
+ }
882
+ };
883
+ // Unclustered points layer
884
+ const unclusteredLayer = {
885
+ id: sourceId + '_unclustered',
886
+ type: 'circle',
887
+ source: sourceId,
888
+ filter: ['!', ['has', 'point_count']],
889
+ paint: {
890
+ 'circle-color': options.pointColor || '#11b4da',
891
+ 'circle-radius': options.pointRadius || 6
892
+ }
893
+ };
894
+ // Add layers via LayerFactory / bridge
895
+ const mapboxView = b.getMapView();
896
+ if (!mapboxView)
897
+ return { x: 0, y: 0 };
898
+ // prefer native LayerFactory to create typed layers from JSON
899
+ await LayerFactory.createLayer(mapboxView, clusterLayer, null);
900
+ await LayerFactory.createLayer(mapboxView, clusterCountLayer, null);
901
+ await LayerFactory.createLayer(mapboxView, unclusteredLayer, null);
1312
902
  resolve();
1313
903
  }
1314
904
  catch (ex) {
1315
- if (Trace.isEnabled()) {
1316
- CLog(CLogTypes.info, 'Error in mapbox.getUserLocation: ' + ex);
1317
- }
1318
905
  reject(ex);
1319
906
  }
1320
907
  });
1321
908
  }
1322
- /**
1323
- * hide the user location marker
1324
- *
1325
- * @todo unfinished
1326
- */
1327
- hideUserLocationMarker(nativeMap) {
1328
- return new Promise((resolve, reject) => {
909
+ // ---------------- Extrusion (3D buildings) ----------------
910
+ // Ports the Android approach: add a FillExtrusion layer showing 3D buildings.
911
+ addExtrusion(options, nativeMap) {
912
+ return new Promise(async (resolve, reject) => {
1329
913
  try {
1330
- resolve();
914
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
915
+ // Build a layer JSON representing the fill-extrusion layer similar to Android sample
916
+ // Default values
917
+ const layerId = options.id || '3d-buildings';
918
+ const source = options.source || 'composite';
919
+ const sourceLayer = options.sourceLayer || 'building';
920
+ const minZoom = typeof options.minZoom !== 'undefined' ? options.minZoom : 15;
921
+ // Build layer JSON
922
+ const layerJson = {
923
+ id: layerId,
924
+ type: 'fill-extrusion',
925
+ source,
926
+ 'source-layer': sourceLayer,
927
+ minzoom: minZoom,
928
+ filter: ['==', ['get', 'extrude'], true],
929
+ paint: {
930
+ 'fill-extrusion-color': options.color || '#d3d3d3',
931
+ // Use a data-driven expression to read properties height/min_height
932
+ 'fill-extrusion-height': ['get', 'height'],
933
+ 'fill-extrusion-base': ['get', 'min_height'],
934
+ 'fill-extrusion-opacity': typeof options.opacity !== 'undefined' ? options.opacity : 0.6
935
+ }
936
+ };
937
+ // Use LayerFactory to apply properties / add layer if possible
938
+ try {
939
+ await LayerFactory.createLayer(b.getMapView(), layerJson, null);
940
+ resolve();
941
+ return;
942
+ }
943
+ catch (e) {
944
+ reject('Failed to add extrusion layer');
945
+ }
1331
946
  }
1332
947
  catch (ex) {
1333
- if (Trace.isEnabled()) {
1334
- CLog(CLogTypes.info, 'Error in mapbox.getUserLocation: ' + ex);
1335
- }
1336
948
  reject(ex);
1337
949
  }
1338
950
  });
1339
951
  }
1340
- /**
1341
- * Change the mode of the user location marker
1342
- *
1343
- * Used to change the camera tracking and render modes of an existing
1344
- * marker.
1345
- *
1346
- * The marker must be configured using showUserLocationMarker before this method
1347
- * can called.
1348
- */
1349
- changeUserLocationMarkerMode(renderModeString, cameraModeString, nativeMap) {
952
+ // ---------------- Camera ----------------
953
+ setCenter(options, nativeMap) {
1350
954
  return new Promise((resolve, reject) => {
1351
955
  try {
1352
- const theMap = nativeMap || this._mapboxViewInstance;
1353
- if (Trace.isEnabled()) {
1354
- CLog(CLogTypes.info, "Mapbox::changeUserLocationMarkerMode(): changing renderMode to '" + renderModeString + "' cameraMode '" + cameraModeString + "'");
956
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
957
+ if (!b) {
958
+ reject('No bridge available');
959
+ return;
1355
960
  }
1356
- theMap.userTrackingMode = this._stringToCameraMode(cameraModeString);
1357
- const delegate = theMap.delegate;
1358
- const renderMode = this._stringToRenderMode(renderModeString);
1359
- delegate.changeUserLocationRenderMode(renderMode);
961
+ const animated = options.animated === undefined || options.animated;
962
+ b.setCenter(options.lat, options.lng, animated);
963
+ resolve();
1360
964
  }
1361
965
  catch (ex) {
1362
- if (Trace.isEnabled()) {
1363
- CLog(CLogTypes.info, 'Error in mapbox.showUserLocationMarker: ' + ex);
1364
- }
1365
966
  reject(ex);
1366
967
  }
1367
968
  });
1368
969
  }
1369
- /**
1370
- * ignored on iOS
1371
- */
1372
- forceUserLocationUpdate(location, nativeMap) { }
1373
- queryRenderedFeatures(options, nativeMap) {
970
+ getCenter(nativeMap) {
1374
971
  return new Promise((resolve, reject) => {
1375
972
  try {
1376
- const theMap = nativeMap || this._mapboxViewInstance;
1377
- if (options.point === undefined) {
1378
- reject("Please set the 'point' parameter");
973
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
974
+ if (!b) {
975
+ reject('No bridge available');
1379
976
  return;
1380
977
  }
1381
- if (!options) {
1382
- options = {};
1383
- }
1384
- const { x, y } = theMap.convertCoordinateToPointToView({ latitude: options.point.lat, longitude: options.point.lng }, theMap);
1385
- const queryLayerIds = options.layers ? NSSet.setWithArray(Utils.ios.collections.jsArrayToNSArray(options.layers)) : null;
1386
- const queryFilter = options.filter ? ExpressionParser.parseJson(options.filter) : null;
1387
- const features = theMap.visibleFeaturesAtPointInStyleLayersWithIdentifiersPredicate({ x, y }, queryLayerIds, queryFilter);
1388
- const result = [];
1389
- for (let i = 0; i < features.count; i++) {
1390
- const feature = features.objectAtIndex(i);
1391
- const featureJson = NSJSONSerialization.dataWithJSONObjectOptionsError(feature.geoJSONDictionary(), 0);
1392
- result.push(JSON.parse(NSString.alloc().initWithDataEncoding(featureJson, NSUTF8StringEncoding)));
978
+ const c = b.getCenter();
979
+ if (!c) {
980
+ reject('No center found');
981
+ return;
1393
982
  }
1394
- resolve(result);
983
+ resolve(JSON.parse(c));
1395
984
  }
1396
985
  catch (ex) {
1397
- if (Trace.isEnabled()) {
1398
- CLog(CLogTypes.info, 'Error in mapbox.queryRenderedFeatures: ' + ex);
1399
- }
1400
986
  reject(ex);
1401
987
  }
1402
988
  });
1403
989
  }
1404
- querySourceFeatures(sourceId, options, nativeMap) {
990
+ setZoomLevel(options, nativeMap) {
1405
991
  return new Promise((resolve, reject) => {
1406
992
  try {
1407
- const theMap = nativeMap || this._mapboxViewInstance;
1408
- if (!options) {
1409
- options = {};
1410
- }
1411
- const source = theMap.style.sourceWithIdentifier(sourceId);
1412
- if (!source) {
1413
- throw new Error(`Source with id "${sourceId}" not found.`);
1414
- }
1415
- let features;
1416
- const queryFilter = options.filter ? ExpressionParser.parseJson(options.filter) : null;
1417
- if (source instanceof MGLShapeSource) {
1418
- features = source.featuresMatchingPredicate(queryFilter);
1419
- }
1420
- else if (source instanceof MGLVectorTileSource) {
1421
- if (!options.sourceLayer) {
1422
- throw new Error('The option "sourceLayer" is required for vector sources.');
1423
- }
1424
- const sourceLayerIds = options.sourceLayer ? NSSet.setWithArray(Utils.ios.collections.jsArrayToNSArray([options.sourceLayer])) : null;
1425
- features = source.featuresInSourceLayersWithIdentifiersPredicate(sourceLayerIds, queryFilter);
1426
- }
1427
- else {
1428
- throw new Error('Only sources from type "vector" or "geojson" are supported.');
1429
- }
1430
- const result = [];
1431
- for (let i = 0; i < features.count; i++) {
1432
- const feature = features.objectAtIndex(i);
1433
- const featureJson = NSJSONSerialization.dataWithJSONObjectOptionsError(feature.geoJSONDictionary(), 0);
1434
- result.push(JSON.parse(NSString.alloc().initWithDataEncoding(featureJson, NSUTF8StringEncoding)));
993
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
994
+ if (!b) {
995
+ reject('No bridge available');
996
+ return;
1435
997
  }
1436
- resolve(result);
998
+ const animated = options.animated === undefined || options.animated;
999
+ b.setZoom(options.level, animated);
1000
+ resolve();
1437
1001
  }
1438
1002
  catch (ex) {
1439
- if (Trace.isEnabled()) {
1440
- CLog(CLogTypes.info, 'Error in mapbox.querySourceFeatures: ' + ex);
1441
- }
1442
1003
  reject(ex);
1443
1004
  }
1444
1005
  });
1445
1006
  }
1446
- /**
1447
- * @deprecated
1448
- */
1449
- addPolygon(options, nativeMap) {
1007
+ getZoomLevel(nativeMap) {
1450
1008
  return new Promise((resolve, reject) => {
1451
- const theMap = nativeMap || this._mapboxViewInstance;
1452
- const points = options.points;
1453
- if (points === undefined) {
1454
- reject("Please set the 'points' parameter");
1455
- return;
1009
+ try {
1010
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1011
+ if (!b) {
1012
+ reject('No bridge available');
1013
+ return;
1014
+ }
1015
+ const z = b.getZoom();
1016
+ resolve(z);
1456
1017
  }
1457
- const coordinateArray = [];
1458
- points.forEach((point) => coordinateArray.push([point.lng, point.lat]));
1459
- const polygonID = `polygon_${options.id || new Date().getTime()}`;
1460
- if (theMap.style.sourceWithIdentifier(polygonID)) {
1461
- reject("Remove the polygon with this id first with 'removePolygons': " + polygonID);
1462
- return;
1018
+ catch (ex) {
1019
+ reject(ex);
1463
1020
  }
1464
- const geoJSON = `{
1465
- "type": "FeatureCollection",
1466
- "features": [
1467
- {
1468
- "id": ${JSON.stringify(polygonID)},
1469
- "type": "Feature",
1470
- "properties": {
1471
- },
1472
- "geometry": {
1473
- "type": "Polygon",
1474
- "coordinates": [${JSON.stringify(coordinateArray)}]
1475
- }
1476
- }
1477
- ]
1478
- }`;
1479
- const geoDataStr = NSString.stringWithString(geoJSON);
1480
- const geoData = geoDataStr.dataUsingEncoding(NSUTF8StringEncoding);
1481
- const geoDataBase64Enc = geoData.base64EncodedStringWithOptions(0);
1482
- const geo = NSData.alloc().initWithBase64EncodedStringOptions(geoDataBase64Enc, null);
1483
- const shape = MGLShape.shapeWithDataEncodingError(geo, NSUTF8StringEncoding);
1484
- const source = MGLShapeSource.alloc().initWithIdentifierShapeOptions(polygonID, shape, null);
1485
- theMap.style.addSource(source);
1486
- if (options.strokeColor || options.strokeWidth || options.strokeOpacity) {
1487
- const strokeLayer = MGLLineStyleLayer.alloc().initWithIdentifierSource(polygonID + '_stroke', source);
1488
- strokeLayer.lineColor = NSExpression.expressionForConstantValue(!options.strokeColor ? UIColor.blackColor : options.strokeColor instanceof Color ? options.strokeColor.ios : new Color(options.strokeColor).ios);
1489
- strokeLayer.lineWidth = NSExpression.expressionForConstantValue(options.strokeWidth || 5);
1490
- strokeLayer.lineOpacity = NSExpression.expressionForConstantValue(options.strokeOpacity === undefined ? 1 : options.strokeOpacity);
1491
- theMap.style.addLayer(strokeLayer);
1492
- }
1493
- const layer = MGLFillStyleLayer.alloc().initWithIdentifierSource(polygonID, source);
1494
- layer.fillColor = NSExpression.expressionForConstantValue(!options.fillColor ? UIColor.blackColor : options.fillColor instanceof Color ? options.fillColor.ios : new Color(options.fillColor).ios);
1495
- layer.fillOpacity = NSExpression.expressionForConstantValue(options.fillOpacity === undefined ? 1 : options.fillOpacity);
1496
- theMap.style.addLayer(layer);
1497
- resolve();
1498
1021
  });
1499
1022
  }
1500
- addPolyline(options, nativeMap) {
1023
+ setMapStyle(style, nativeMap) {
1501
1024
  return new Promise((resolve, reject) => {
1502
- const theMap = nativeMap || this._mapboxViewInstance;
1503
- const points = options.points;
1504
- if (points === undefined) {
1505
- reject("Please set the 'points' parameter");
1506
- return;
1025
+ try {
1026
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1027
+ if (!b) {
1028
+ reject('No bridge available');
1029
+ return;
1030
+ }
1031
+ const styleStr = typeof style === 'string' ? style : style.toString();
1032
+ b.setStyle(styleStr, (success, error) => {
1033
+ if (success)
1034
+ resolve();
1035
+ else
1036
+ reject(error && error.localizedDescription ? error.localizedDescription : error || 'Error setting style');
1037
+ });
1507
1038
  }
1508
- const coordinateArray = [];
1509
- points.forEach((point) => coordinateArray.push([point.lng, point.lat]));
1510
- const polylineID = 'polyline_' + (options.id || new Date().getTime());
1511
- // this would otherwise crash the app
1512
- if (theMap.style.sourceWithIdentifier(polylineID)) {
1513
- reject("Remove the polyline with this id first with 'removePolylines': " + polylineID);
1514
- return;
1039
+ catch (ex) {
1040
+ reject(ex);
1515
1041
  }
1516
- const geoJSON = `{"type": "FeatureCollection", "features": [{"type": "Feature","properties": {},"geometry": {"type": "LineString", "coordinates": ${JSON.stringify(coordinateArray)}}}]}`;
1517
- const geoDataStr = NSString.stringWithString(geoJSON);
1518
- const geoData = geoDataStr.dataUsingEncoding(NSUTF8StringEncoding);
1519
- const geoDataBase64Enc = geoData.base64EncodedStringWithOptions(0);
1520
- const geo = NSData.alloc().initWithBase64EncodedStringOptions(geoDataBase64Enc, null);
1521
- const shape = MGLShape.shapeWithDataEncodingError(geo, NSUTF8StringEncoding);
1522
- const source = MGLShapeSource.alloc().initWithIdentifierShapeOptions(polylineID, shape, null);
1523
- theMap.style.addSource(source);
1524
- const layer = MGLLineStyleLayer.alloc().initWithIdentifierSource(polylineID, source);
1525
- layer.lineColor = NSExpression.expressionForConstantValue(!options.color ? UIColor.blackColor : options.color instanceof Color ? options.color.ios : new Color(options.color).ios);
1526
- layer.lineWidth = NSExpression.expressionForConstantValue(options.width || 5);
1527
- layer.lineOpacity = NSExpression.expressionForConstantValue(options.opacity === undefined ? 1 : options.opacity);
1528
- theMap.style.addLayer(layer);
1529
- resolve();
1530
- });
1531
- }
1532
- removePolyById(theMap, id) {
1533
- let layer = theMap.style.layerWithIdentifier(id);
1534
- if (layer !== null) {
1535
- theMap.style.removeLayer(layer);
1536
- }
1537
- // polygons may have a 'stroke' layer
1538
- layer = theMap.style.layerWithIdentifier(id + '_stroke');
1539
- if (layer !== null) {
1540
- theMap.style.removeLayer(layer);
1541
- }
1542
- const source = theMap.style.sourceWithIdentifier(id);
1543
- if (source !== null) {
1544
- theMap.style.removeSource(source);
1545
- }
1546
- }
1547
- removePolys(polyType, ids, nativeMap) {
1548
- return new Promise((resolve) => {
1549
- const theMap = nativeMap || this._mapboxViewInstance;
1550
- ids.forEach((id) => this.removePolyById(theMap, polyType + id));
1551
- resolve();
1552
1042
  });
1553
1043
  }
1554
- removePolygons(ids, nativeMap) {
1555
- return this.removePolys('polygon_', ids, nativeMap);
1556
- }
1557
- removePolylines(ids, nativeMap) {
1558
- return this.removePolys('polyline_', ids, nativeMap);
1559
- }
1560
1044
  animateCamera(options, nativeMap) {
1561
1045
  return new Promise((resolve, reject) => {
1562
1046
  try {
1563
- const theMap = nativeMap || this._mapboxViewInstance;
1564
- let cam;
1565
- if (options.bounds) {
1566
- const padding = options.padding || 0;
1567
- // ensure padding is an object and assign default values
1568
- const { bottom = 0, left = 0, right = 0, top = 0 } = typeof padding === 'object' ? padding : { top: padding, left: padding, bottom: padding, right: padding };
1569
- // support defined padding
1570
- const insets = { top, left, bottom, right };
1571
- const bounds = {
1572
- sw: CLLocationCoordinate2DMake(options.bounds.south, options.bounds.west),
1573
- ne: CLLocationCoordinate2DMake(options.bounds.north, options.bounds.east)
1574
- };
1575
- cam = theMap.cameraThatFitsCoordinateBoundsEdgePadding(bounds, insets);
1576
- }
1577
- else {
1578
- const target = options.target;
1579
- if (target === undefined) {
1580
- reject("Please set the 'target' parameter");
1581
- return;
1582
- }
1583
- cam = theMap.camera;
1584
- cam.centerCoordinate = CLLocationCoordinate2DMake(target.lat, target.lng);
1585
- }
1586
- if (options.altitude) {
1587
- cam.altitude = options.altitude;
1588
- }
1589
- if (options.bearing) {
1590
- cam.heading = options.bearing;
1591
- }
1592
- if (options.tilt) {
1593
- cam.pitch = options.tilt;
1594
- }
1595
- if (options.zoomLevel && options.target) {
1596
- cam.altitude = MGLAltitudeForZoomLevel(options.zoomLevel, cam.pitch, options.target.lat, theMap.frame.size);
1047
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1048
+ if (!b) {
1049
+ reject('No bridge available');
1050
+ return;
1597
1051
  }
1598
- const durationMs = options.duration ? options.duration : 10000;
1599
- theMap.setCameraWithDurationAnimationTimingFunction(cam, durationMs / 1000, CAMediaTimingFunction.functionWithName(kCAMediaTimingFunctionEaseInEaseOut));
1052
+ options.duration = options.duration || 1000;
1053
+ b.animateCamera(JSON.stringify(options));
1600
1054
  setTimeout(() => {
1601
1055
  resolve();
1602
- }, durationMs);
1056
+ }, options.duration);
1603
1057
  }
1604
1058
  catch (ex) {
1059
+ reject(ex);
1060
+ }
1061
+ });
1062
+ }
1063
+ // ---------------- Queries ----------------
1064
+ queryRenderedFeatures(options, nativeMap) {
1065
+ return new Promise((resolve, reject) => {
1066
+ try {
1067
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1068
+ if (!b) {
1069
+ reject('No bridge available');
1070
+ return;
1071
+ }
1072
+ if (!options.point) {
1073
+ reject("Please set the 'point' parameter");
1074
+ return;
1075
+ }
1076
+ const screen = this.project({ lat: options.point.lat, lng: options.point.lng });
1605
1077
  if (Trace.isEnabled()) {
1606
- CLog(CLogTypes.info, 'Error in mapbox.animateCamera: ' + ex);
1078
+ CLog(CLogTypes.info, 'queryRenderedFeatures:', options.point, screen);
1607
1079
  }
1080
+ b.queryRenderedFeaturesAtPoint(JSON.stringify(screen), options.layers, (ret) => {
1081
+ if (ret) {
1082
+ resolve(JSON.parse(ret));
1083
+ }
1084
+ else {
1085
+ resolve([]);
1086
+ }
1087
+ });
1088
+ }
1089
+ catch (ex) {
1608
1090
  reject(ex);
1609
1091
  }
1610
1092
  });
1611
1093
  }
1612
- /**
1613
- * sets a map level click listener
1614
- *
1615
- */
1616
- setOnMapClickListener(listener, nativeMap) {
1094
+ // ---------------- Events, filters, queries ----------------
1095
+ // querySourceFeatures now calls native bridge querySourceFeatures which uses MapboxMaps native async API
1096
+ querySourceFeatures(sourceId, options, nativeMap) {
1617
1097
  return new Promise((resolve, reject) => {
1618
1098
  try {
1619
- const theMap = nativeMap || this._mapboxViewInstance;
1620
- if (!theMap) {
1621
- reject('No map has been loaded');
1099
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1100
+ if (!b) {
1101
+ reject('No bridge available');
1622
1102
  return;
1623
1103
  }
1624
- theMap['mapTapHandler'] = MapTapHandlerImpl.initWithOwnerAndListenerForMap(new WeakRef(this), listener, theMap);
1625
- const tapGestureRecognizer = UITapGestureRecognizer.alloc().initWithTargetAction(theMap['mapTapHandler'], 'tap');
1626
- for (let i = 0; i < theMap.gestureRecognizers.count; i++) {
1627
- const recognizer = theMap.gestureRecognizers.objectAtIndex(i);
1628
- if (recognizer instanceof UITapGestureRecognizer) {
1629
- tapGestureRecognizer.requireGestureRecognizerToFail(recognizer);
1104
+ if (Trace.isEnabled()) {
1105
+ CLog(CLogTypes.info, 'querySourceFeatures:', sourceId, JSON.stringify(options));
1106
+ }
1107
+ const payload = {};
1108
+ if (options && options.filter)
1109
+ payload.filter = options.filter;
1110
+ if (options && options.sourceLayer)
1111
+ payload.sourceLayer = options.sourceLayer;
1112
+ // call native async method which returns a Cancelable token
1113
+ const cancelable = b.querySourceFeatures(sourceId, JSON.stringify(payload), (retJson) => {
1114
+ if (!retJson) {
1115
+ resolve([]);
1116
+ return;
1630
1117
  }
1631
- }
1632
- theMap.addGestureRecognizer(tapGestureRecognizer);
1633
- resolve();
1118
+ try {
1119
+ const arr = JSON.parse(retJson);
1120
+ resolve(arr);
1121
+ }
1122
+ catch (err) {
1123
+ reject(err);
1124
+ }
1125
+ });
1126
+ // Optionally return cancelable to caller via resolved value? we keep the Promise focused on features.
1127
+ // If you want cancellation, you can expose the cancelable on another API or return { promise, cancelable }.
1634
1128
  }
1635
1129
  catch (ex) {
1636
- if (Trace.isEnabled()) {
1637
- CLog(CLogTypes.info, 'Error in mapbox.setOnMapClickListener: ' + ex);
1638
- }
1639
1130
  reject(ex);
1640
1131
  }
1641
1132
  });
1642
1133
  }
1643
- setOnMapLongClickListener(listener, nativeMap) {
1134
+ // ---------------- Sources / Layers / Geometry ----------------
1135
+ addSource(id, options, nativeMap) {
1644
1136
  return new Promise((resolve, reject) => {
1645
1137
  try {
1646
- const theMap = nativeMap || this._mapboxViewInstance;
1647
- if (!theMap) {
1648
- reject('No map has been loaded');
1138
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1139
+ if (!b) {
1140
+ reject('No bridge available');
1649
1141
  return;
1650
1142
  }
1651
- // adding the longPress handler to the map oject so it's not GC'd
1652
- theMap['mapLongPressHandler'] = MapLongPressHandlerImpl.initWithOwnerAndListenerForMap(new WeakRef(this), listener, theMap);
1653
- const longPressGestureRecognizer = UILongPressGestureRecognizer.alloc().initWithTargetAction(theMap['mapLongPressHandler'], 'longPress');
1654
- // cancel the default longPress handler
1655
- for (let i = 0; i < theMap.gestureRecognizers.count; i++) {
1656
- const recognizer = theMap.gestureRecognizers.objectAtIndex(i);
1657
- if (recognizer instanceof UILongPressGestureRecognizer) {
1658
- longPressGestureRecognizer.requireGestureRecognizerToFail(recognizer);
1143
+ if (Trace.isEnabled()) {
1144
+ CLog(CLogTypes.info, 'addSource:', id, JSON.stringify(options));
1145
+ }
1146
+ if (options.type === 'geojson') {
1147
+ const geojson = options.data ? JSON.stringify(options.data) : options.url;
1148
+ if (!geojson) {
1149
+ reject('geojson source requires data or url');
1150
+ return;
1659
1151
  }
1152
+ const ok = b.addSourceGeoJSON(id, geojson);
1153
+ if (ok)
1154
+ resolve();
1155
+ else
1156
+ reject('Failed to add source');
1660
1157
  }
1661
- theMap.addGestureRecognizer(longPressGestureRecognizer);
1662
- resolve();
1158
+ else
1159
+ reject('Only geojson source supported in bridge.addSource');
1663
1160
  }
1664
1161
  catch (ex) {
1665
- if (Trace.isEnabled()) {
1666
- CLog(CLogTypes.info, 'Error in mapbox.setOnMapClickListener: ' + ex);
1667
- }
1668
1162
  reject(ex);
1669
1163
  }
1670
1164
  });
1671
1165
  }
1672
- setOnScrollListener(listener, nativeMap) {
1166
+ updateSource(id, options, nativeMap) {
1673
1167
  return new Promise((resolve, reject) => {
1674
1168
  try {
1675
- const theMap = nativeMap || this._mapboxViewInstance;
1676
- if (!theMap) {
1677
- reject('No map has been loaded');
1169
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1170
+ if (!b) {
1171
+ reject('No bridge available');
1678
1172
  return;
1679
1173
  }
1680
- // adding the pan handler to the map oject so it's not GC'd
1681
- if (theMap['mapPanHandler'] === undefined) {
1682
- theMap['mapPanHandler'] = MapPanHandlerImpl.initWithOwnerAndListenerForMap(new WeakRef(this), listener, 2 /* UIGestureRecognizerState.Changed */, theMap);
1174
+ if (options.type === 'geojson') {
1175
+ const data = JSON.stringify(options.data);
1176
+ const ok = b.updateSourceGeoJSON(id, data);
1177
+ if (ok)
1178
+ resolve();
1179
+ else
1180
+ reject('Failed to update source');
1683
1181
  }
1684
- else {
1685
- theMap['mapPanHandler'].addListener(2 /* UIGestureRecognizerState.Changed */, listener);
1686
- }
1687
- // there's already a pan recognizer, so find it and attach a target action
1688
- for (let i = 0; i < theMap.gestureRecognizers.count; i++) {
1689
- const recognizer = theMap.gestureRecognizers.objectAtIndex(i);
1690
- if (recognizer instanceof UIPanGestureRecognizer) {
1691
- recognizer.addTargetAction(theMap['mapPanHandler'], 'pan');
1692
- break;
1693
- }
1182
+ else
1183
+ reject('Only geojson supported for updateSource');
1184
+ }
1185
+ catch (ex) {
1186
+ reject(ex);
1187
+ }
1188
+ });
1189
+ }
1190
+ removeSource(id, nativeMap) {
1191
+ return new Promise((resolve, reject) => {
1192
+ try {
1193
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1194
+ if (!b) {
1195
+ reject('No bridge available');
1196
+ return;
1694
1197
  }
1695
- resolve();
1198
+ const ok = b.removeSource(id);
1199
+ if (ok)
1200
+ resolve();
1201
+ else
1202
+ reject('Failed to remove source');
1696
1203
  }
1697
1204
  catch (ex) {
1698
- if (Trace.isEnabled()) {
1699
- CLog(CLogTypes.info, 'Error in mapbox.setOnScrollListener: ' + ex);
1205
+ reject(ex);
1206
+ }
1207
+ });
1208
+ }
1209
+ async addLayer(style, belowLayerId, nativeMap) {
1210
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1211
+ // delegate to LayerFactory (TS shim -> native when available)
1212
+ if (Trace.isEnabled()) {
1213
+ CLog(CLogTypes.info, 'addLayer:', belowLayerId, JSON.stringify(style));
1214
+ }
1215
+ await LayerFactory.createLayer(b.getMapView(), style, belowLayerId);
1216
+ }
1217
+ async removeLayer(id, nativeMap) {
1218
+ return new Promise((resolve, reject) => {
1219
+ try {
1220
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1221
+ if (!b) {
1222
+ reject('No bridge available');
1223
+ return;
1700
1224
  }
1225
+ const ok = b.removeLayer(id);
1226
+ if (ok)
1227
+ resolve();
1228
+ else
1229
+ reject('Failed to remove layer');
1230
+ }
1231
+ catch (ex) {
1701
1232
  reject(ex);
1702
1233
  }
1703
1234
  });
1704
1235
  }
1705
- /**
1706
- * simulates onMoveBegin single event callback
1707
- *
1708
- * This will call the listener provided once per pan akin to the way
1709
- * onMoveBegin on the Android side works.
1710
- */
1711
- setOnMoveBeginListener(listener, nativeMap) {
1236
+ getLayer(layerId, nativeMap) {
1237
+ return new Promise((resolve, reject) => {
1238
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1239
+ const found = NativeLayerFactory.getLayer(b.getMapView(), layerId);
1240
+ resolve(found ? new Layer(b.getMapView(), layerId) : null);
1241
+ });
1242
+ }
1243
+ getLayers(nativeMap) {
1712
1244
  return new Promise((resolve, reject) => {
1713
1245
  try {
1714
- const theMap = nativeMap || this._mapboxViewInstance;
1715
- if (!theMap) {
1716
- reject('No map has been loaded');
1717
- return;
1718
- }
1719
- // adding the pan handler to the map oject so it's not GC'd
1720
- if (theMap['mapPanHandler'] === undefined) {
1721
- theMap['mapPanHandler'] = MapPanHandlerImpl.initWithOwnerAndListenerForMap(new WeakRef(this), listener, 1 /* UIGestureRecognizerState.Began */, theMap);
1722
- }
1723
- else {
1724
- theMap['mapPanHandler'].addListener(1 /* UIGestureRecognizerState.Began */, listener);
1725
- }
1726
- // there's already a pan recognizer, so find it and attach a target action
1727
- for (let i = 0; i < theMap.gestureRecognizers.count; i++) {
1728
- const recognizer = theMap.gestureRecognizers.objectAtIndex(i);
1729
- if (recognizer instanceof UIPanGestureRecognizer) {
1730
- recognizer.addTargetAction(theMap['mapPanHandler'], 'panBegin');
1731
- break;
1732
- }
1733
- }
1734
- resolve();
1246
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1247
+ const layersIds = JSON.parse(NativeLayerFactory.getLayers(b.getMapView())) || [];
1248
+ resolve(layersIds.map((id) => new Layer(b.getMapView(), id)));
1735
1249
  }
1736
1250
  catch (ex) {
1737
- if (Trace.isEnabled()) {
1738
- CLog(CLogTypes.info, 'Error in mapbox.setOnMoveBeginListener: ' + ex);
1739
- }
1740
1251
  reject(ex);
1741
1252
  }
1742
1253
  });
1743
1254
  }
1744
- setOnMoveEndListener(listener, nativeMap) {
1255
+ // returns base64 PNG string for the image or null
1256
+ getImage(imageId, nativeMap) {
1745
1257
  return new Promise((resolve, reject) => {
1746
1258
  try {
1747
- const theMap = nativeMap || this._mapboxViewInstance;
1748
- if (!theMap) {
1749
- reject('No map has been loaded');
1259
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1260
+ if (!b || !b.getImage) {
1261
+ resolve(null);
1750
1262
  return;
1751
1263
  }
1752
- if (theMap['mapPanHandler'] === undefined) {
1753
- theMap['mapPanHandler'] = MapPanHandlerImpl.initWithOwnerAndListenerForMap(new WeakRef(this), listener, 3 /* UIGestureRecognizerState.Ended */, theMap);
1264
+ // The native bridge now returns a UIImage (native iOS object) or null.
1265
+ const nativeImage = b.getImage(imageId);
1266
+ if (!nativeImage) {
1267
+ resolve(null);
1268
+ return;
1754
1269
  }
1755
- else {
1756
- theMap['mapPanHandler'].addListener(3 /* UIGestureRecognizerState.Ended */, listener);
1757
- }
1758
- // there's already a pan recognizer, so find it and attach a target action
1759
- for (let i = 0; i < theMap.gestureRecognizers.count; i++) {
1760
- const recognizer = theMap.gestureRecognizers.objectAtIndex(i);
1761
- if (recognizer instanceof UIPanGestureRecognizer) {
1762
- recognizer.addTargetAction(theMap['mapPanHandler'], 'panEnd');
1763
- break;
1270
+ // Wrap the native UIImage into a NativeScript ImageSource
1271
+ try {
1272
+ // ImageSource.fromNativeSource accepts a native UIImage on iOS
1273
+ const imgSrc = new ImageSource(nativeImage);
1274
+ resolve(imgSrc || null);
1275
+ }
1276
+ catch (err) {
1277
+ // As a fallback, if the bridge returns base64 string (older fallback), try decode
1278
+ try {
1279
+ const maybeBase64 = nativeImage;
1280
+ if (typeof maybeBase64 === 'string') {
1281
+ const src = ImageSource.fromBase64Sync ? ImageSource.fromBase64Sync(maybeBase64) : ImageSource.fromBase64(maybeBase64);
1282
+ resolve(src || null);
1283
+ }
1284
+ else {
1285
+ resolve(null);
1286
+ }
1287
+ }
1288
+ catch (e) {
1289
+ resolve(null);
1764
1290
  }
1765
1291
  }
1766
- resolve();
1767
1292
  }
1768
1293
  catch (ex) {
1769
- if (Trace.isEnabled()) {
1770
- CLog(CLogTypes.info, 'Error in mapbox.setOnMoveEndListener: ' + ex);
1771
- }
1772
1294
  reject(ex);
1773
1295
  }
1774
1296
  });
1775
1297
  }
1776
- setOnFlingListener(listener, nativeMap) {
1777
- // there's no swipe event we can bind to
1778
- return Promise.reject("'setOnFlingListener' is not supported on iOS");
1779
- }
1780
- async setOnCameraChangeListener(listener, nativeMap) {
1781
- const theMap = nativeMap || this._mapboxViewInstance;
1782
- if (theMap) {
1783
- theMap.delegate.setCameraChangedListener(listener);
1298
+ // ---------------- Callouts ----------------
1299
+ createCalloutView(marker) {
1300
+ if (Trace.isEnabled()) {
1301
+ CLog(CLogTypes.info, 'createCalloutView():', marker.id, marker.title, !!this._reusableCalloutView);
1302
+ }
1303
+ if (this._reusableCalloutView) {
1304
+ const title = this._reusableCalloutView.getViewById('title');
1305
+ title.text = marker?.title || '';
1306
+ const subtitle = this._reusableCalloutView.getViewById('subtitle');
1307
+ subtitle.text = marker?.subtitle;
1308
+ subtitle.visibility = marker?.subtitle ? 'visible' : 'collapse';
1784
1309
  }
1785
1310
  else {
1786
- return Promise.reject('No map has been loaded');
1311
+ this._reusableCalloutView = createInfoWindowView(marker.title, marker.subtitle);
1312
+ }
1313
+ if (Trace.isEnabled()) {
1314
+ CLog(CLogTypes.info, 'createCalloutView1():', marker.id, marker.title, !!this._reusableCalloutView);
1787
1315
  }
1316
+ this._reusableCalloutView.removeEventListener('tap');
1317
+ return this._reusableCalloutView;
1788
1318
  }
1789
- setOnCameraMoveCancelListener(listener, nativeMap) {
1790
- return Promise.reject("'setOnCameraMoveCancelListener' not currently supported on iOS");
1319
+ async showCalloutForMarkerById(markerId) {
1320
+ const m = this._markers.find((x) => `${x.id}` === markerId);
1321
+ if (!m) {
1322
+ return;
1323
+ }
1324
+ if (Trace.isEnabled()) {
1325
+ CLog(CLogTypes.info, 'showCalloutForMarkerById():', typeof markerId, markerId);
1326
+ }
1327
+ if (this.bridgeInstance.hasViewAnnotationForMarker(markerId)) {
1328
+ return;
1329
+ }
1330
+ const callout = this.createCalloutView(m);
1331
+ callout.on('tap', () => {
1332
+ try {
1333
+ const res = m.onCalloutTap ? m.onCalloutTap(m) : undefined;
1334
+ if (res === false)
1335
+ this.deselectMarker(m);
1336
+ }
1337
+ catch (e) {
1338
+ console.error('callout tap handler error', e);
1339
+ }
1340
+ });
1341
+ try {
1342
+ const nativeView = createUIViewAutoSizeUIViewAutoSize(callout);
1343
+ if (Trace.isEnabled()) {
1344
+ CLog(CLogTypes.info, 'showCalloutForMarkerById1():', markerId, nativeView);
1345
+ }
1346
+ m.ios = nativeView;
1347
+ const ok = this.bridgeInstance.addViewAnnotationForMarker(markerId, nativeView, m.lat, m.lng);
1348
+ if (!ok) {
1349
+ console.warn('addViewAnnotationForMarker failed for', markerId);
1350
+ }
1351
+ this.selectedMarker = m;
1352
+ }
1353
+ catch (e) {
1354
+ console.error('Failed to add native view annotation', e);
1355
+ }
1791
1356
  }
1792
- async setOnMapIdleListener(listener, nativeMap) {
1793
- const theMap = nativeMap || this._mapboxViewInstance;
1794
- if (theMap) {
1795
- theMap.delegate.setCameraIdledListener(listener);
1357
+ hideCalloutForMarkerById(markerId) {
1358
+ const m = this._markers.find((x) => `${x.id}` === markerId);
1359
+ if (!m)
1360
+ return;
1361
+ try {
1362
+ this.bridgeInstance.removeViewAnnotationForMarker(markerId);
1363
+ }
1364
+ catch (e) {
1365
+ console.error(e, e.stack);
1366
+ }
1367
+ // const cv = this._calloutViews[m.id];
1368
+ // if (cv) {
1369
+ // cv.off('tap');
1370
+ // delete this._calloutViews[m.id];
1371
+ // }
1372
+ }
1373
+ toggleCalloutForMarkerById(markerId) {
1374
+ const m = this._markers.find((x) => `${x.id}` === markerId);
1375
+ if (!m || !m.id)
1376
+ return;
1377
+ const exists = this.bridgeInstance.hasViewAnnotationForMarker(markerId);
1378
+ if (exists)
1379
+ this.hideCalloutForMarkerById(markerId);
1380
+ else
1381
+ this.showCalloutForMarkerById(markerId);
1382
+ }
1383
+ onNativeAnnotationTap(userInfo) {
1384
+ if (Trace.isEnabled()) {
1385
+ CLog(CLogTypes.info, 'onNativeAnnotationTap:', JSON.stringify(userInfo));
1386
+ }
1387
+ const markerId = userInfo.id;
1388
+ if (!markerId)
1389
+ return;
1390
+ const marker = this._markers.find((m) => `${m.id}` === markerId);
1391
+ if (!marker)
1392
+ return;
1393
+ if (marker === this.selectedMarker) {
1394
+ this.deselectMarker(marker);
1796
1395
  }
1797
1396
  else {
1798
- return Promise.reject('No map has been loaded');
1397
+ this.selectMarker(marker);
1799
1398
  }
1800
1399
  }
1801
- getViewport(nativeMap) {
1400
+ addNotificationCenterObserver(event, map, callback) {
1401
+ this.pushToken(NSNotificationCenter.defaultCenter.addObserverForNameObjectQueueUsingBlock(event, map, NSOperationQueue.mainQueue, (notification) => {
1402
+ callback(convertToJSON(notification.userInfo));
1403
+ }));
1404
+ }
1405
+ setOnEventChangeListener(event, listener, nativeMap) {
1802
1406
  return new Promise((resolve, reject) => {
1803
1407
  try {
1804
- const theMap = nativeMap || this._mapboxViewInstance;
1805
- if (!theMap) {
1408
+ const map = nativeMap || this._mapboxViewInstance;
1409
+ if (!map) {
1806
1410
  reject('No map has been loaded');
1807
1411
  return;
1808
1412
  }
1809
- const visibleBounds = theMap.visibleCoordinateBounds;
1810
- const bounds = {
1811
- north: visibleBounds.ne.latitude,
1812
- east: visibleBounds.ne.longitude,
1813
- south: visibleBounds.sw.latitude,
1814
- west: visibleBounds.sw.longitude
1815
- };
1816
- resolve({
1817
- bounds,
1818
- zoomLevel: theMap.zoomLevel
1819
- });
1413
+ this.addNotificationCenterObserver(event, map, listener);
1414
+ resolve();
1820
1415
  }
1821
1416
  catch (ex) {
1822
- if (Trace.isEnabled()) {
1823
- CLog(CLogTypes.info, 'Error in mapbox.getViewport: ' + ex);
1824
- }
1825
1417
  reject(ex);
1826
1418
  }
1827
1419
  });
1828
1420
  }
1829
- setViewport(options, nativeMap) {
1421
+ setOnCameraChangeListener(listener, nativeMap) {
1422
+ return this.setOnEventChangeListener(MAPBOX_BRIDGE_CAMERA_CHANGED, listener, nativeMap);
1423
+ }
1424
+ setOnMapClickListener(listener, nativeMap) {
1425
+ return this.setOnEventChangeListener(MAPBOX_BRIDGE_MAP_CLICK, listener, nativeMap);
1426
+ }
1427
+ setOnMapLongClickListener(listener, nativeMap) {
1428
+ return this.setOnEventChangeListener(MAPBOX_BRIDGE_MAP_LONGPRESS, listener, nativeMap);
1429
+ }
1430
+ setOnScrollListener(listener, nativeMap) {
1431
+ return this.setOnEventChangeListener(MAPBOX_BRIDGE_MAP_SCROLL, listener, nativeMap);
1432
+ }
1433
+ setOnMoveBeginListener(listener, nativeMap) {
1434
+ return this.setOnEventChangeListener(MAPBOX_BRIDGE_MAP_MOVE_BEGIN, listener, nativeMap);
1435
+ }
1436
+ setOnMoveEndListener(listener, nativeMap) {
1437
+ return this.setOnEventChangeListener(MAPBOX_BRIDGE_MAP_MOVE_END, listener, nativeMap);
1438
+ }
1439
+ setOnFlingListener(listener, nativeMap) {
1440
+ return this.setOnEventChangeListener(MAPBOX_BRIDGE_MAP_FLING, listener, nativeMap);
1441
+ }
1442
+ setOnCameraMoveCancelListener(listener, nativeMap) {
1443
+ return this.setOnEventChangeListener(MAPBOX_BRIDGE_CAMERA_MOVE_CANCEL, listener, nativeMap);
1444
+ }
1445
+ setOnMapIdleListener(listener, nativeMap) {
1446
+ return this.setOnEventChangeListener(MAPBOX_BRIDGE_CAMERA_IDLE, listener, nativeMap);
1447
+ }
1448
+ // ---------------- Offline helpers ----------------
1449
+ downloadOfflineRegion(options, nativeMap) {
1830
1450
  return new Promise((resolve, reject) => {
1831
1451
  try {
1832
- const theMap = nativeMap || this._mapboxViewInstance;
1833
- if (!theMap) {
1834
- reject('No map has been loaded');
1452
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1453
+ if (!b) {
1454
+ reject('No bridge available');
1835
1455
  return;
1836
1456
  }
1837
- const bounds = {
1838
- sw: CLLocationCoordinate2DMake(options.bounds.south, options.bounds.west),
1839
- ne: CLLocationCoordinate2DMake(options.bounds.north, options.bounds.east)
1457
+ const progressCb = (info) => {
1458
+ options.onProgress?.(JSON.parse(info));
1840
1459
  };
1841
- const animated = options.animated === undefined || options.animated;
1842
- // define default padding
1843
- const defaultPadding = 25;
1844
- // check if padding is defined and whether it's an object or a single value
1845
- const padding = options.padding !== undefined
1846
- ? typeof options.padding === 'object'
1847
- ? {
1848
- top: options.padding.top ?? 0,
1849
- left: options.padding.left ?? 0,
1850
- bottom: options.padding.bottom ?? 0,
1851
- right: options.padding.right ?? 0
1852
- }
1853
- : {
1854
- top: options.padding,
1855
- left: options.padding,
1856
- bottom: options.padding,
1857
- right: options.padding
1858
- }
1859
- : {
1860
- top: defaultPadding,
1861
- left: defaultPadding,
1862
- bottom: defaultPadding,
1863
- right: defaultPadding
1864
- };
1865
- theMap.setVisibleCoordinateBoundsEdgePaddingAnimated(bounds, padding, animated);
1866
- resolve();
1460
+ b.downloadOfflineRegion(JSON.stringify(options), progressCb, (success, error) => {
1461
+ if (success)
1462
+ resolve();
1463
+ else {
1464
+ const msg = error && error.localizedDescription ? error.localizedDescription : error || 'Failed to download offline region';
1465
+ reject(msg);
1466
+ }
1467
+ });
1867
1468
  }
1868
1469
  catch (ex) {
1869
- if (Trace.isEnabled()) {
1870
- CLog(CLogTypes.info, 'Error in mapbox.setViewport: ' + ex);
1871
- }
1872
1470
  reject(ex);
1873
1471
  }
1874
1472
  });
1875
1473
  }
1876
- downloadOfflineRegion(options) {
1474
+ listOfflineRegions(options, nativeMap) {
1877
1475
  return new Promise((resolve, reject) => {
1878
1476
  try {
1879
- const styleURL = _getMapStyle(options.style);
1880
- const swCoordinate = CLLocationCoordinate2DMake(options.bounds.south, options.bounds.west);
1881
- const neCoordinate = CLLocationCoordinate2DMake(options.bounds.north, options.bounds.east);
1882
- const bounds = {
1883
- sw: swCoordinate,
1884
- ne: neCoordinate
1885
- };
1886
- const region = MGLTilePyramidOfflineRegion.alloc().initWithStyleURLBoundsFromZoomLevelToZoomLevel(styleURL, bounds, options.minZoom, options.maxZoom);
1887
- if (options.accessToken) {
1888
- MGLAccountManager.accessToken = options.accessToken;
1889
- }
1890
- // TODO there's more observers, see https://www.mapbox.com/ios-sdk/examples/offline-pack/
1891
- if (options.onProgress) {
1892
- _addObserver(MGLOfflinePackProgressChangedNotification, (notification) => {
1893
- const offlinePack = notification.object;
1894
- const offlinePackProgress = offlinePack.progress;
1895
- const userInfo = NSKeyedUnarchiver.unarchiveObjectWithData(offlinePack.context);
1896
- const complete = offlinePackProgress.countOfResourcesCompleted === offlinePackProgress.countOfResourcesExpected;
1897
- options.onProgress({
1898
- name: userInfo.objectForKey('name'),
1899
- completed: offlinePackProgress.countOfResourcesCompleted,
1900
- expected: offlinePackProgress.countOfResourcesExpected,
1901
- percentage: Math.round((offlinePackProgress.countOfResourcesCompleted / offlinePackProgress.countOfResourcesExpected) * 10000) / 100,
1902
- complete
1903
- });
1904
- if (complete) {
1905
- resolve();
1906
- }
1907
- });
1477
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1478
+ if (!b) {
1479
+ reject('No bridge available');
1480
+ return;
1908
1481
  }
1909
- _addObserver(MGLOfflinePackErrorNotification, (notification) => {
1910
- const offlinePack = notification.object;
1911
- const userInfo = NSKeyedUnarchiver.unarchiveObjectWithData(offlinePack.context);
1912
- const error = notification.userInfo[MGLOfflinePackUserInfoKeyError];
1913
- reject({
1914
- name: userInfo.objectForKey('name'),
1915
- error: 'Download error. ' + error
1916
- });
1917
- });
1918
- _addObserver(MGLOfflinePackMaximumMapboxTilesReachedNotification, (notification) => {
1919
- const offlinePack = notification.object;
1920
- const userInfo = NSKeyedUnarchiver.unarchiveObjectWithData(offlinePack.context);
1921
- const maximumCount = notification.userInfo[MGLOfflinePackUserInfoKeyMaximumCount];
1922
- console.log(`Offline region '${userInfo.objectForKey('name')}' reached the tile limit of ${maximumCount}`);
1923
- });
1924
- // Store some data for identification purposes alongside the downloaded resources.
1925
- const userInfo = { name: options.name };
1926
- const context = NSKeyedArchiver.archivedDataWithRootObject(userInfo);
1927
- // Create and register an offline pack with the shared offline storage object.
1928
- MGLOfflineStorage.sharedOfflineStorage.addPackForRegionWithContextCompletionHandler(region, context, (pack, error) => {
1929
- if (error) {
1930
- // The pack couldn’t be created for some reason.
1931
- reject(error.localizedFailureReason);
1932
- }
1933
- else {
1934
- // Start downloading.
1935
- pack.resume();
1936
- }
1482
+ b.listOfflineRegions((res) => {
1483
+ resolve(JSON.parse(res));
1937
1484
  });
1938
1485
  }
1939
1486
  catch (ex) {
1940
- if (Trace.isEnabled()) {
1941
- CLog(CLogTypes.info, 'Error in mapbox.downloadOfflineRegion: ' + ex);
1942
- }
1943
1487
  reject(ex);
1944
1488
  }
1945
1489
  });
1946
1490
  }
1947
- listOfflineRegions(options) {
1491
+ deleteOfflineRegion(options, nativeMap) {
1948
1492
  return new Promise((resolve, reject) => {
1949
1493
  try {
1950
- const packs = MGLOfflineStorage.sharedOfflineStorage.packs;
1951
- if (!packs) {
1952
- reject('No packs found or Mapbox not ready yet');
1494
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1495
+ if (!b) {
1496
+ reject('No bridge available');
1953
1497
  return;
1954
1498
  }
1955
- const regions = [];
1956
- for (let i = 0; i < packs.count; i++) {
1957
- const pack = packs.objectAtIndex(i);
1958
- const region = pack.region;
1959
- const userInfo = NSKeyedUnarchiver.unarchiveObjectWithData(pack.context);
1960
- regions.push({
1961
- name: userInfo.objectForKey('name'),
1962
- style: region.styleURL.absoluteString,
1963
- minZoom: region.minimumZoomLevel,
1964
- maxZoom: region.maximumZoomLevel,
1965
- bounds: {
1966
- north: region.bounds.ne.latitude,
1967
- east: region.bounds.ne.longitude,
1968
- south: region.bounds.sw.latitude,
1969
- west: region.bounds.sw.longitude
1970
- }
1971
- });
1499
+ const idOrName = options.id || options.name;
1500
+ if (!idOrName) {
1501
+ reject("Pass in the 'id' or 'name' param");
1502
+ return;
1972
1503
  }
1973
- resolve(regions);
1504
+ b.deleteOfflineRegion(idOrName);
1505
+ resolve();
1974
1506
  }
1975
1507
  catch (ex) {
1976
- if (Trace.isEnabled()) {
1977
- CLog(CLogTypes.info, 'Error in mapbox.listOfflineRegions: ' + ex);
1978
- }
1979
1508
  reject(ex);
1980
1509
  }
1981
1510
  });
1982
1511
  }
1983
- deleteOfflineRegion(options) {
1512
+ // ---------------- User location & tilt ----------------
1513
+ showUserLocationMarker(options) {
1984
1514
  return new Promise((resolve, reject) => {
1985
1515
  try {
1986
- if (!options || (!options.id && !options.name)) {
1987
- reject("Pass in the 'id' or 'name' param");
1516
+ const b = this.bridgeInstance;
1517
+ if (!b) {
1518
+ reject('No bridge available');
1988
1519
  return;
1989
1520
  }
1990
- const packs = MGLOfflineStorage.sharedOfflineStorage.packs;
1991
- let found = false;
1992
- for (let i = 0; i < packs.count; i++) {
1993
- const pack = packs.objectAtIndex(i);
1994
- const userInfo = NSKeyedUnarchiver.unarchiveObjectWithData(pack.context);
1995
- const regionId = options.id ? userInfo.objectForKey('id') : userInfo.objectForKey('name');
1996
- if (regionId === (options.id || options.name)) {
1997
- found = true;
1998
- MGLOfflineStorage.sharedOfflineStorage.removePackWithCompletionHandler(pack, (error) => {
1999
- if (error) {
2000
- // The pack couldn’t be deleted for some reason.
2001
- reject(error.localizedFailureReason);
2002
- }
2003
- else {
2004
- resolve();
2005
- // don't return, see note below
2006
- }
2007
- });
2008
- // don't break the loop as there may be multiple packs with the same name
2009
- }
2010
- }
2011
- if (!found) {
2012
- reject('Region not found');
2013
- }
1521
+ b.showUserLocationMarker(JSON.stringify(options));
1522
+ resolve();
2014
1523
  }
2015
1524
  catch (ex) {
2016
- if (Trace.isEnabled()) {
2017
- CLog(CLogTypes.info, 'Error in mapbox.deleteOfflineRegion: ' + ex);
2018
- }
2019
1525
  reject(ex);
2020
1526
  }
2021
1527
  });
2022
1528
  }
2023
- addExtrusion(options, nativeMap) {
1529
+ hideUserLocationMarker(nativeMap) {
2024
1530
  return new Promise((resolve, reject) => {
2025
1531
  try {
2026
- const theMap = nativeMap || this._mapboxViewInstance;
2027
- if (!theMap) {
2028
- reject('No map has been loaded');
1532
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1533
+ if (!b) {
1534
+ reject('No bridge available');
2029
1535
  return;
2030
1536
  }
1537
+ b.stopTrackingUser();
2031
1538
  resolve();
2032
1539
  }
2033
1540
  catch (ex) {
2034
- if (Trace.isEnabled()) {
2035
- CLog(CLogTypes.info, 'Error in mapbox.deleteOfflineRegion: ' + ex);
2036
- }
2037
1541
  reject(ex);
2038
1542
  }
2039
1543
  });
2040
1544
  }
2041
- /**
2042
- * update a geojson source
2043
- *
2044
- */
2045
- updateSource(id, options, nativeMap) {
1545
+ forceUserLocationUpdate(nativeMap) {
2046
1546
  return new Promise((resolve, reject) => {
2047
1547
  try {
2048
- const theMap = nativeMap || this._mapboxViewInstance;
2049
- if (!theMap) {
2050
- reject('No map has been loaded');
2051
- return;
2052
- }
2053
- const source = theMap.style.sourceWithIdentifier(id);
2054
- if (!source) {
2055
- reject('Source does not exists: ' + id);
1548
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1549
+ if (!b) {
1550
+ reject('No bridge available');
2056
1551
  return;
2057
1552
  }
2058
- switch (options.type) {
2059
- case 'geojson':
2060
- const content = NSString.stringWithString(JSON.stringify(options.data));
2061
- const nsData = content.dataUsingEncoding(NSUTF8StringEncoding);
2062
- const geoJsonShape = MGLShape.shapeWithDataEncodingError(nsData, NSUTF8StringEncoding);
2063
- source.shape = geoJsonShape;
2064
- resolve();
2065
- break;
2066
- default:
2067
- reject('Invalid source type: ' + options['type']);
2068
- return;
2069
- }
1553
+ const ok = b.forceUserLocationUpdate ? b.forceUserLocationUpdate() : false;
1554
+ resolve(!!ok);
2070
1555
  }
2071
1556
  catch (ex) {
2072
- if (Trace.isEnabled()) {
2073
- CLog(CLogTypes.info, 'Error in mapbox.addSource: ' + ex);
2074
- }
2075
1557
  reject(ex);
2076
1558
  }
2077
1559
  });
2078
1560
  }
2079
- /**
2080
- * add a vector or geojson source
2081
- *
2082
- * Add a source that can then be referenced in the style specification
2083
- * passed to addLayer().
2084
- *
2085
- * @link https://docs.mapbox.com/mapbox-gl-js/api/#map#addsource
2086
- */
2087
- addSource(id, options, nativeMap) {
1561
+ trackUser(options, nativeMap) {
2088
1562
  return new Promise((resolve, reject) => {
2089
1563
  try {
2090
- const theMap = nativeMap || this._mapboxViewInstance;
2091
- let source;
2092
- if (!theMap) {
2093
- reject('No map has been loaded');
1564
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1565
+ if (!b) {
1566
+ reject('No bridge available');
2094
1567
  return;
2095
1568
  }
2096
- if (theMap.style.sourceWithIdentifier(id)) {
2097
- reject('Source exists: ' + id);
2098
- return;
1569
+ if (Trace.isEnabled()) {
1570
+ CLog(CLogTypes.info, 'trackUser():', JSON.stringify(options));
2099
1571
  }
2100
- switch (options.type) {
2101
- case 'vector': {
2102
- if (options.url) {
2103
- source = MGLVectorTileSource.alloc().initWithIdentifierConfigurationURL(id, NSURL.URLWithString(options.url));
2104
- }
2105
- else {
2106
- const sourceOptions = {};
2107
- if (options.minzoom !== undefined) {
2108
- sourceOptions[MGLTileSourceOptionMinimumZoomLevel] = options.minzoom;
2109
- }
2110
- if (options.maxzoom !== undefined) {
2111
- sourceOptions[MGLTileSourceOptionMaximumZoomLevel] = options.maxzoom;
2112
- }
2113
- if (options.scheme) {
2114
- switch (options.scheme) {
2115
- case 'xyz':
2116
- sourceOptions[MGLTileSourceOptionTileCoordinateSystem] = 0 /* MGLTileCoordinateSystem.XYZ */;
2117
- break;
2118
- case 'tms':
2119
- sourceOptions[MGLTileSourceOptionTileCoordinateSystem] = 1 /* MGLTileCoordinateSystem.TMS */;
2120
- break;
2121
- default:
2122
- throw new Error('Unknown raster tile scheme.');
2123
- }
2124
- }
2125
- if (options.bounds) {
2126
- sourceOptions[MGLTileSourceOptionCoordinateBounds] = NSValue.valueWithMGLCoordinateBounds({
2127
- sw: CLLocationCoordinate2DMake(options.bounds[1], options.bounds[0]),
2128
- ne: CLLocationCoordinate2DMake(options.bounds[3], options.bounds[2])
2129
- });
2130
- }
2131
- source = MGLVectorTileSource.alloc().initWithIdentifierTileURLTemplatesOptions(id, options.tiles, sourceOptions);
2132
- }
2133
- break;
2134
- }
2135
- case 'geojson':
2136
- if (theMap.style.sourceWithIdentifier(id)) {
2137
- reject("Remove the layer with this id first with 'removeLayer': " + id);
2138
- return;
2139
- }
2140
- let geoJsonShape;
2141
- if (options.data) {
2142
- const content = NSString.stringWithString(JSON.stringify(options.data));
2143
- const nsData = content.dataUsingEncoding(NSUTF8StringEncoding);
2144
- geoJsonShape = MGLShape.shapeWithDataEncodingError(nsData, NSUTF8StringEncoding);
2145
- }
2146
- const sourceOptions = {};
2147
- if (options.minzoom !== undefined) {
2148
- sourceOptions[MGLShapeSourceOptionMinimumZoomLevel] = options.minzoom;
2149
- }
2150
- if (options.maxzoom !== undefined) {
2151
- sourceOptions[MGLShapeSourceOptionMaximumZoomLevel] = options.maxzoom;
2152
- }
2153
- if (options.lineMetrics !== undefined) {
2154
- sourceOptions[MGLShapeSourceOptionLineDistanceMetrics] = options.lineMetrics;
2155
- }
2156
- if (options.cluster) {
2157
- sourceOptions[MGLShapeSourceOptionClustered] = true;
2158
- sourceOptions[MGLShapeSourceOptionClusterRadius] = options.cluster.radius || 40;
2159
- sourceOptions[MGLShapeSourceOptionMaximumZoomLevelForClustering] = options.cluster.maxZoom || 13;
2160
- if (options.cluster.properties) {
2161
- const clusterProperties = {};
2162
- for (const property of Object.keys(options.cluster.properties)) {
2163
- // eslint-disable-next-line prefer-const
2164
- let [operator, operand] = options.cluster.properties[property];
2165
- if (!Array.isArray(operator)) {
2166
- operator = [operator];
2167
- }
2168
- const expressions = Utils.ios.collections.jsArrayToNSArray([ExpressionParser.parseJson(operator), ExpressionParser.parseJson(operand)]);
2169
- clusterProperties[property] = expressions;
2170
- }
2171
- sourceOptions[MGLShapeSourceOptionClusterProperties] = clusterProperties;
2172
- }
2173
- }
2174
- source = MGLShapeSource.alloc().initWithIdentifierShapeOptions(id, geoJsonShape, sourceOptions);
2175
- break;
2176
- case 'raster': {
2177
- const sourceOptions = {
2178
- [MGLTileSourceOptionTileSize]: options.tileSize || 256
2179
- };
2180
- if (options.minzoom !== undefined) {
2181
- sourceOptions[MGLTileSourceOptionMinimumZoomLevel] = options.minzoom;
2182
- }
2183
- if (options.maxzoom !== undefined) {
2184
- sourceOptions[MGLTileSourceOptionMaximumZoomLevel] = options.maxzoom;
2185
- }
2186
- if (options.scheme) {
2187
- switch (options.scheme || 'xyz') {
2188
- case 'xyz':
2189
- sourceOptions[MGLTileSourceOptionTileCoordinateSystem] = 0 /* MGLTileCoordinateSystem.XYZ */;
2190
- break;
2191
- case 'tms':
2192
- sourceOptions[MGLTileSourceOptionTileCoordinateSystem] = 1 /* MGLTileCoordinateSystem.TMS */;
2193
- break;
2194
- default:
2195
- throw new Error('Unknown raster tile scheme.');
2196
- }
2197
- }
2198
- if (options.bounds) {
2199
- sourceOptions[MGLTileSourceOptionCoordinateBounds] = NSValue.valueWithMGLCoordinateBounds({
2200
- sw: CLLocationCoordinate2DMake(options.bounds[1], options.bounds[0]),
2201
- ne: CLLocationCoordinate2DMake(options.bounds[3], options.bounds[2])
2202
- });
2203
- }
2204
- source = MGLRasterTileSource.alloc().initWithIdentifierTileURLTemplatesOptions(id, options.tiles, sourceOptions);
2205
- break;
2206
- }
2207
- default:
2208
- reject('Invalid source type: ' + options['type']);
2209
- return;
1572
+ const ok = b.showUserLocationMarker(JSON.stringify(options));
1573
+ if (ok) {
1574
+ resolve();
2210
1575
  }
2211
- if (!source) {
2212
- const ex = 'No source to add';
2213
- if (Trace.isEnabled()) {
2214
- CLog(CLogTypes.info, 'Error in mapbox.addSource: ' + ex);
2215
- }
2216
- reject(ex);
2217
- return;
1576
+ else {
1577
+ reject();
2218
1578
  }
2219
- theMap.style.addSource(source);
2220
- resolve();
2221
1579
  }
2222
1580
  catch (ex) {
2223
- if (Trace.isEnabled()) {
2224
- CLog(CLogTypes.info, 'Error in mapbox.addSource: ' + ex);
2225
- }
2226
1581
  reject(ex);
2227
1582
  }
2228
1583
  });
2229
1584
  }
2230
- /**
2231
- * remove source by id
2232
- */
2233
- removeSource(id, nativeMap) {
1585
+ getTilt(nativeMap) {
2234
1586
  return new Promise((resolve, reject) => {
2235
1587
  try {
2236
- const theMap = nativeMap || this._mapboxViewInstance;
2237
- if (!theMap) {
2238
- reject('No map has been loaded');
1588
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1589
+ if (!b) {
1590
+ reject('No bridge available');
2239
1591
  return;
2240
1592
  }
2241
- const source = theMap.style.sourceWithIdentifier(id);
2242
- if (!source) {
2243
- reject('Source does not exist');
1593
+ resolve(b.getTilt());
1594
+ }
1595
+ catch (ex) {
1596
+ reject(ex);
1597
+ }
1598
+ });
1599
+ }
1600
+ setTilt(options, nativeMap) {
1601
+ return new Promise((resolve, reject) => {
1602
+ try {
1603
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1604
+ if (!b) {
1605
+ reject('No bridge available');
2244
1606
  return;
2245
1607
  }
2246
- theMap.style.removeSource(source);
1608
+ const animated = options.animated === undefined ? true : options.animated;
1609
+ b.setTilt(options.tilt, animated);
2247
1610
  resolve();
2248
1611
  }
2249
1612
  catch (ex) {
2250
- if (Trace.isEnabled()) {
2251
- CLog(CLogTypes.info, 'Error in mapbox.removeSource: ' + ex);
2252
- }
2253
1613
  reject(ex);
2254
1614
  }
2255
1615
  });
2256
1616
  }
2257
- /**
2258
- * a rough analogue to the mapbox-gl-js addLayer() method
2259
- *
2260
- * It would be nice if this {N} API matched the mapbox-gl-js API which
2261
- * would make it much easier to share mapping applications between the web
2262
- * and {N} apps.
2263
- *
2264
- * This method accepts a Mapbox-GL-JS style specification JSON object with some
2265
- * limitations:
2266
- *
2267
- * - the source: must be a GeoJSON object.
2268
- * - only a subset of paint properties are available.
2269
- *
2270
- * @param {object} style - a style following the Mapbox style specification.
2271
- * @param {any} nativeMapView - native map view (com.mapbox.mapboxsdk.maps.MapView)
2272
- *
2273
- * @link https://docs.mapbox.com/mapbox-gl-js/style-spec/#layers
2274
- */
2275
- async addLayer(style, belowLayerId, nativeMapView) {
2276
- const theMap = nativeMapView || this._mapboxViewInstance;
2277
- let source = null;
2278
- if (typeof style.source != 'string') {
2279
- await this.addSource(style.id + '_source', style.source);
2280
- source = theMap.style.sourceWithIdentifier(style.id + '_source');
2281
- }
2282
- else {
2283
- source = theMap.style.sourceWithIdentifier(style.source);
2284
- }
2285
- const layer = await LayerFactory.createLayer(style, source);
2286
- if (belowLayerId) {
2287
- const belowlayer = theMap.style.layerWithIdentifier(belowLayerId);
2288
- if (belowlayer) {
2289
- theMap.style.insertLayerBelowLayer(layer.getNativeInstance(), belowlayer);
2290
- return;
2291
- }
2292
- }
2293
- theMap.style.addLayer(layer.getNativeInstance());
2294
- }
2295
- /**
2296
- * remove layer by ID
2297
- *
2298
- * Removes a layer given a layer id
2299
- *
2300
- * @param {string} id
2301
- */
2302
- async removeLayer(id, nativeMapViewInstance) {
2303
- const theMap = nativeMapViewInstance || this._mapboxViewInstance;
2304
- if (Trace.isEnabled()) {
2305
- CLog(CLogTypes.info, "Mapbox::removeLayer(): attempting to remove layer '" + id + "'");
2306
- }
2307
- const layer = theMap.style.layerWithIdentifier(id);
2308
- if (Trace.isEnabled()) {
2309
- CLog(CLogTypes.info, 'Mapbox:removeLayer(): got layer object: ', layer);
2310
- }
2311
- if (!layer) {
2312
- throw new Error("Layer '" + id + "' not found when attempting to remove it.");
2313
- }
2314
- theMap.style.removeLayer(layer);
2315
- if (Trace.isEnabled()) {
2316
- CLog(CLogTypes.info, 'Mapbox:removeLayer(): after removing layer ' + id);
2317
- }
2318
- }
2319
- /**
2320
- * @deprecated
2321
- * Add a point to a line
2322
- *
2323
- * This method appends a point to a line and is useful for drawing a users track.
2324
- *
2325
- * The process for adding a point to a line is different in the iOS sdk than in
2326
- * the Android java sdk.
2327
- *
2328
- * @param {id} id - id of line to add a point to.
2329
- * @param {array} lnglat - [lng,lat] to append to the line.
2330
- *
2331
- * @link https://github.com/mapbox/mapbox-gl-native/issues/13983
2332
- * @link https://docs.mapbox.com/ios/maps/examples/runtime-animate-line/
2333
- */
2334
- async addLinePoint(id, lnglat, sourceId, nativeMapView) {
2335
- const theMap = nativeMapView || this._mapboxViewInstance;
2336
- const sId = !!sourceId ? sourceId : id + '_source';
2337
- const lineSource = theMap.style.sourceWithIdentifier(sId);
2338
- if (!lineSource) {
2339
- throw new Error(`no source found with id: ${sId}`);
2340
- }
2341
- try {
2342
- const lineFeatures = lineSource.featuresMatchingPredicate(ExpressionParser.parseJson(['==', '$type', 'LineString']));
2343
- if (lineFeatures.count === 0) {
2344
- throw new Error('no line string feature found');
2345
- }
2346
- const lineFeature = lineFeatures.objectAtIndex(0);
2347
- const newCoord = CLLocationCoordinate2DMake(lnglat[1], lnglat[0]);
2348
- const newCoordPointer = new interop.Reference(newCoord);
2349
- lineFeature.appendCoordinatesCount(newCoordPointer, 1);
2350
- lineSource.shape = lineFeature;
2351
- }
2352
- catch (error) {
2353
- console.log(error);
2354
- throw error;
2355
- }
2356
- }
2357
- addGeoJsonClustered(options, nativeMapViewInstance) {
2358
- throw new Error('Method not implemented.');
2359
- // return new Promise((resolve, reject) => {
2360
- // const theMap: MGLMapView = nativeMapViewInstance || this._mapboxViewInstance;
2361
- // try {
2362
- // const source = MGLShapeSource.alloc().initWithIdentifierURLOptions(options.name, NSURL.URLWithString(options.data), null);
2363
- // theMap.style.addSource(source);
2364
- // const layers = [];
2365
- // if (options.clusters) {
2366
- // for (let i = 0; i < options.clusters.length; i++) {
2367
- // // TODO also allow Color object
2368
- // layers.push([options.clusters[i].points, new Color(options.clusters[i].color).ios]);
2369
- // }
2370
- // } else {
2371
- // layers.push([150, new Color('red').ios]);
2372
- // layers.push([20, new Color('green').ios]);
2373
- // layers.push([0, new Color('blue').ios]);
2374
- // }
2375
- // const unclustered = MGLCircleStyleLayer.alloc().initWithIdentifierSource(options.name, source);
2376
- // unclustered.circleColor = NSExpression.expressionWithFormatArgumentArray('%@', new Color('red').ios);
2377
- // unclustered.circleRadius = NSExpression.expressionWithFormatArgumentArray('16', null);
2378
- // unclustered.circleBlur = NSExpression.expressionWithFormatArgumentArray('0.2', null);
2379
- // // unclustered.setFilter(com.mapbox.mapboxsdk.style.expressions.Expression.neq(com.mapbox.mapboxsdk.style.expressions.Expression.get('cluster'), true));
2380
- // // theMap.style.addLayer(unclustered); // , "building");
2381
- // for (let i = 0; i < layers.length; i++) {
2382
- // // Add some nice circles
2383
- // const circles = MGLCircleStyleLayer.alloc().initWithIdentifierSource(options.name, source);
2384
- // const circles = new com.mapbox.mapboxsdk.style.layers.CircleLayer('cluster-' + i, options.name);
2385
- // // circles.setProperties([
2386
- // // // com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage("icon")
2387
- // // com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor(layers[i][1]),
2388
- // // com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleRadius(new java.lang.Float(22.0)),
2389
- // // com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleBlur(new java.lang.Float(0.2)),
2390
- // // ]);
2391
- // // const pointCount = com.mapbox.mapboxsdk.style.expressions.Expression.toNumber(com.mapbox.mapboxsdk.style.expressions.Expression.get('point_count'));
2392
- // // if (i === 0) {
2393
- // // circles.setFilter(
2394
- // // com.mapbox.mapboxsdk.style.expressions.Expression.gte(pointCount, com.mapbox.mapboxsdk.style.expressions.Expression.literal(java.lang.Integer.valueOf(layers[i][0])))
2395
- // // );
2396
- // // } else {
2397
- // // circles.setFilter(
2398
- // // com.mapbox.mapboxsdk.style.expressions.Expression.all([
2399
- // // com.mapbox.mapboxsdk.style.expressions.Expression.gte(pointCount, com.mapbox.mapboxsdk.style.expressions.Expression.literal(java.lang.Integer.valueOf(layers[i][0]))),
2400
- // // com.mapbox.mapboxsdk.style.expressions.Expression.lt(
2401
- // // pointCount,
2402
- // // com.mapbox.mapboxsdk.style.expressions.Expression.literal(java.lang.Integer.valueOf(layers[i - 1][0]))
2403
- // // ),
2404
- // // ])
2405
- // // );
2406
- // // }
2407
- // // this._mapboxMapInstance.getStyle().addLayer(circles); // , "building");
2408
- // }
2409
- // // // Add the count labels (note that this doesn't show.. #sad)
2410
- // // const count = new com.mapbox.mapboxsdk.style.layers.SymbolLayer('count', options.name);
2411
- // // count.setProperties([
2412
- // // com.mapbox.mapboxsdk.style.layers.PropertyFactory.textField(com.mapbox.mapboxsdk.style.expressions.Expression.get('point_count')),
2413
- // // com.mapbox.mapboxsdk.style.layers.PropertyFactory.textSize(new java.lang.Float(12.0)),
2414
- // // com.mapbox.mapboxsdk.style.layers.PropertyFactory.textColor(new Color('white').android),
2415
- // // ]);
2416
- // // this._mapboxMapInstance.getStyle().addLayer(count);
2417
- // resolve();
2418
- // } catch (ex) {
2419
- // if (Trace.isEnabled()) {
2420
- // CLog(CLogTypes.info, 'Error in mapbox.addGeoJsonClustered: ' + ex);
2421
- // }
2422
- // reject(ex);
2423
- // }
2424
- // });
2425
- }
2426
- trackUser(options, nativeMap) {
1617
+ getUserLocation(nativeMap) {
2427
1618
  return new Promise((resolve, reject) => {
2428
1619
  try {
2429
- const theMap = nativeMap || this._mapboxViewInstance;
2430
- if (!theMap) {
2431
- reject('No map has been loaded');
1620
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1621
+ if (!b) {
1622
+ reject('No bridge available');
2432
1623
  return;
2433
1624
  }
2434
- if (!theMap.showsUserLocation) {
2435
- reject('The map is not currently showing the user location');
1625
+ const loc = b.getUserLocation();
1626
+ if (!loc) {
1627
+ reject('No user location');
2436
1628
  return;
2437
1629
  }
2438
- theMap.setUserTrackingModeAnimated(this._stringToCameraMode(options.cameraMode), options.animated !== false);
2439
- resolve();
1630
+ resolve(JSON.parse(loc));
2440
1631
  }
2441
1632
  catch (ex) {
2442
- if (Trace.isEnabled()) {
2443
- CLog(CLogTypes.info, 'Error in mapbox.trackUser: ' + ex);
2444
- }
2445
1633
  reject(ex);
2446
1634
  }
2447
1635
  });
2448
1636
  }
2449
- getLayer(name, nativeMap) {
1637
+ // ---------------- Viewport ----------------
1638
+ setViewport(options, nativeMap) {
2450
1639
  return new Promise((resolve, reject) => {
2451
1640
  try {
2452
- const theMap = nativeMap || this._mapboxViewInstance;
2453
- if (!theMap) {
2454
- reject('No map has been loaded');
1641
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1642
+ if (!b || !b.setViewport) {
1643
+ reject('No bridge available');
2455
1644
  return;
2456
1645
  }
2457
- const layer = theMap.style.layerWithIdentifier(name);
2458
- resolve(layer ? new Layer(layer) : null);
1646
+ const payload = typeof options === 'string' ? options : JSON.stringify(options);
1647
+ const ok = b.setViewport(payload);
1648
+ resolve(!!ok);
2459
1649
  }
2460
1650
  catch (ex) {
2461
- if (Trace.isEnabled()) {
2462
- CLog(CLogTypes.info, 'Error in mapbox.getLayer: ' + ex);
2463
- }
2464
1651
  reject(ex);
2465
1652
  }
2466
1653
  });
2467
1654
  }
2468
- getLayers(nativeMap) {
1655
+ getViewport(nativeMap) {
2469
1656
  return new Promise((resolve, reject) => {
2470
1657
  try {
2471
- const theMap = nativeMap || this._mapboxViewInstance;
2472
- if (!theMap) {
2473
- reject('No map has been loaded');
1658
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1659
+ if (!b || !b.getViewport) {
1660
+ resolve(null);
2474
1661
  return;
2475
1662
  }
2476
- const layers = theMap.style.layers;
2477
- const result = [];
2478
- for (let i = 0; i < layers.count; i++) {
2479
- result.push(new Layer(layers[i]));
1663
+ const vp = b.getViewport();
1664
+ if (!vp) {
1665
+ reject('viewport could not be determined');
1666
+ return;
2480
1667
  }
2481
- resolve(result);
1668
+ resolve(JSON.parse(vp));
2482
1669
  }
2483
1670
  catch (ex) {
2484
- if (Trace.isEnabled()) {
2485
- CLog(CLogTypes.info, 'Error in mapbox.getLayers: ' + ex);
2486
- }
2487
1671
  reject(ex);
2488
1672
  }
2489
1673
  });
2490
1674
  }
2491
- project(data) {
2492
- const theMap = this._mapboxViewInstance;
2493
- const { x, y } = theMap.convertCoordinateToPointToView({ latitude: data.lat, longitude: data.lng }, theMap);
2494
- return { x, y };
2495
- }
2496
- projectBack(screenCoordinate) {
2497
- const theMap = this._mapboxViewInstance;
2498
- const cgPoint = {
2499
- x: screenCoordinate.x,
2500
- y: screenCoordinate.y
2501
- };
2502
- const coordinate = theMap.convertPointToCoordinateFromView(cgPoint, theMap);
2503
- return {
2504
- lat: coordinate.latitude,
2505
- lng: coordinate.longitude
2506
- };
2507
- }
2508
- getUserLocationCameraMode(nativeMap) {
2509
- const theMap = nativeMap || this._mapboxViewInstance;
2510
- if (!theMap) {
2511
- return 'NONE';
1675
+ // ---------------- Project helpers ----------------
1676
+ project(data, nativeMap) {
1677
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1678
+ if (!b)
1679
+ return { x: 0, y: 0 };
1680
+ try {
1681
+ if (Trace.isEnabled()) {
1682
+ CLog(CLogTypes.info, 'project:', JSON.stringify(data));
1683
+ }
1684
+ const pt = b.coordinateToPoint(data.lat, data.lng);
1685
+ return JSON.parse(pt);
2512
1686
  }
2513
- return this._convertCameraMode(theMap.userTrackingMode);
2514
- }
2515
- }
2516
- const _addObserver = (eventName, callback) => NSNotificationCenter.defaultCenter.addObserverForNameObjectQueueUsingBlock(eventName, null, NSOperationQueue.mainQueue, callback);
2517
- function _downloadImage(marker) {
2518
- return new Promise((resolve, reject) => {
2519
- if (Trace.isEnabled()) {
2520
- CLog(CLogTypes.info, '>> _downloadImage');
1687
+ catch (e) {
1688
+ return { x: 0, y: 0 };
2521
1689
  }
2522
- // to cache..
2523
- if (_markerIconDownloadCache[marker.icon]) {
2524
- marker.iconDownloaded = _markerIconDownloadCache[marker.icon];
1690
+ }
1691
+ projectBack(screenCoordinate, nativeMap) {
1692
+ const b = nativeMap ? MapboxBridge.bridgeFor(nativeMap) : this.bridgeInstance;
1693
+ if (!b)
1694
+ return { lat: 0, lng: 0 };
1695
+ try {
2525
1696
  if (Trace.isEnabled()) {
2526
- CLog(CLogTypes.info, '>> marker.iconDownloaded: ' + marker.iconDownloaded);
1697
+ CLog(CLogTypes.info, 'projectBack:', JSON.stringify(screenCoordinate));
2527
1698
  }
2528
- resolve(marker);
2529
- return;
1699
+ const coord = b.pointToCoordinate(screenCoordinate.x, screenCoordinate.y);
1700
+ return JSON.parse(coord);
2530
1701
  }
2531
- // ..or not to cache
2532
- Http.getImage(marker.icon).then((output) => {
2533
- marker.iconDownloaded = output.ios;
2534
- _markerIconDownloadCache[marker.icon] = marker.iconDownloaded;
2535
- resolve(marker);
2536
- }, (ignoredError) => {
2537
- console.log(`Download failed for ${marker.icon} with error: ${ignoredError}`);
2538
- resolve(marker);
2539
- });
2540
- });
2541
- }
2542
- const _downloadMarkerImages = (markers) => {
2543
- const iterations = [];
2544
- const result = [];
2545
- markers.forEach((marker) => {
2546
- if (marker.icon && marker.icon.startsWith('http')) {
2547
- const p = _downloadImage(marker).then((mark) => result.push(mark));
2548
- iterations.push(p);
2549
- }
2550
- else {
2551
- result.push(marker);
1702
+ catch (e) {
1703
+ return { lat: 0, lng: 0 };
2552
1704
  }
2553
- });
2554
- return Promise.all(iterations).then(() => result);
2555
- };
1705
+ }
1706
+ // ---------------- Lifecycle stubs ----------------
1707
+ onStart(nativeMap) {
1708
+ return Promise.resolve();
1709
+ }
1710
+ onResume(nativeMap) {
1711
+ return Promise.resolve();
1712
+ }
1713
+ onPause(nativeMap) {
1714
+ return Promise.resolve();
1715
+ }
1716
+ onStop(nativeMap) {
1717
+ return Promise.resolve();
1718
+ }
1719
+ onLowMemory(nativeMap) {
1720
+ return Promise.resolve();
1721
+ }
1722
+ onDestroy(nativeMap) {
1723
+ return Promise.resolve();
1724
+ }
1725
+ }
2556
1726
  //# sourceMappingURL=index.ios.js.map