@maplibre/maplibre-react-native 10.0.0-alpha.6 → 10.0.0-alpha.7

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.
Files changed (51) hide show
  1. package/.eslintrc.js +3 -1
  2. package/.yarn/sdks/eslint/bin/eslint.js +8 -1
  3. package/.yarn/sdks/eslint/lib/api.js +8 -1
  4. package/.yarn/sdks/eslint/lib/unsupported-api.js +8 -1
  5. package/.yarn/sdks/prettier/bin/prettier.cjs +8 -1
  6. package/.yarn/sdks/prettier/index.cjs +8 -1
  7. package/.yarn/sdks/typescript/bin/tsc +8 -1
  8. package/.yarn/sdks/typescript/bin/tsserver +8 -1
  9. package/.yarn/sdks/typescript/lib/tsc.js +8 -1
  10. package/.yarn/sdks/typescript/lib/tsserver.js +20 -6
  11. package/.yarn/sdks/typescript/lib/tsserverlibrary.js +20 -6
  12. package/.yarn/sdks/typescript/lib/typescript.js +8 -1
  13. package/CHANGELOG.md +4 -0
  14. package/CONTRIBUTING.md +10 -9
  15. package/docs/Camera.md +3 -3
  16. package/docs/MapView.md +9 -33
  17. package/docs/UserLocation.md +10 -2
  18. package/docs/docs.json +16 -31
  19. package/javascript/Maplibre.ts +5 -1
  20. package/javascript/components/BackgroundLayer.tsx +27 -20
  21. package/javascript/components/Callout.tsx +40 -40
  22. package/javascript/components/Camera.tsx +421 -478
  23. package/javascript/components/CircleLayer.tsx +29 -22
  24. package/javascript/components/FillExtrusionLayer.tsx +23 -23
  25. package/javascript/components/FillLayer.tsx +22 -19
  26. package/javascript/components/HeatmapLayer.tsx +21 -19
  27. package/javascript/components/ImageSource.tsx +25 -32
  28. package/javascript/components/Images.tsx +36 -35
  29. package/javascript/components/Light.tsx +20 -47
  30. package/javascript/components/LineLayer.tsx +23 -20
  31. package/javascript/components/MapView.tsx +604 -554
  32. package/javascript/components/MarkerView.tsx +23 -38
  33. package/javascript/components/NativeUserLocation.tsx +3 -5
  34. package/javascript/components/PointAnnotation.tsx +111 -87
  35. package/javascript/components/RasterLayer.tsx +21 -18
  36. package/javascript/components/RasterSource.tsx +39 -42
  37. package/javascript/components/ShapeSource.tsx +287 -239
  38. package/javascript/components/Style.tsx +1 -1
  39. package/javascript/components/SymbolLayer.tsx +34 -28
  40. package/javascript/components/UserLocation.tsx +164 -151
  41. package/javascript/components/VectorSource.tsx +128 -117
  42. package/javascript/components/annotations/Annotation.tsx +105 -79
  43. package/javascript/{components/AbstractLayer.tsx → hooks/useAbstractLayer.ts} +54 -37
  44. package/javascript/hooks/useAbstractSource.ts +34 -0
  45. package/javascript/hooks/useNativeBridge.ts +125 -0
  46. package/javascript/hooks/useNativeRef.ts +13 -0
  47. package/javascript/hooks/useOnce.ts +12 -0
  48. package/javascript/utils/Logger.ts +3 -3
  49. package/package.json +2 -1
  50. package/javascript/components/AbstractSource.tsx +0 -27
  51. package/javascript/components/NativeBridgeComponent.tsx +0 -117
@@ -1,14 +1,18 @@
1
- import {toJSONString, existenceChange} from '../utils';
1
+ import {toJSONString} from '../utils';
2
2
  import * as geoUtils from '../utils/geoUtils';
3
3
  import {MaplibreGLEvent} from '../types';
4
-
5
- import {
6
- NativeMethods,
7
- NativeModules,
8
- requireNativeComponent,
9
- ViewProps,
10
- } from 'react-native';
11
- import React, {Component, MutableRefObject, ReactElement} from 'react';
4
+ import {useNativeRef} from '../hooks/useNativeRef';
5
+
6
+ import {NativeModules, requireNativeComponent, ViewProps} from 'react-native';
7
+ import React, {
8
+ memo,
9
+ RefObject,
10
+ useEffect,
11
+ useImperativeHandle,
12
+ useMemo,
13
+ useRef,
14
+ useCallback,
15
+ } from 'react';
12
16
 
13
17
  const MapLibreGL = NativeModules.MLNModule;
14
18
 
@@ -47,6 +51,15 @@ export interface CameraRef {
47
51
  animationDuration?: number,
48
52
  ) => void;
49
53
  zoomTo: (zoomLevel: number, animationDuration?: number) => void;
54
+
55
+ _defaultCamera: RefObject<NativeCameraStop | null>;
56
+ _getMaxBounds: () => string | null;
57
+ _getNativeCameraMode: (config: CameraStop) => NativeAnimationMode;
58
+ _createStopConfig: (
59
+ config: CameraStop,
60
+ ignoreFollowUserLocation?: boolean,
61
+ ) => NativeCameraStop | null;
62
+ _createDefaultCamera: () => NativeCameraStop | null;
50
63
  }
51
64
 
52
65
  export interface CameraPadding {
@@ -186,494 +199,424 @@ interface NativeProps extends Omit<CameraProps, 'maxBounds'> {
186
199
  defaultStop: NativeCameraStop | null;
187
200
  }
188
201
 
189
- class Camera extends React.Component<CameraProps> {
190
- static defaultProps = {
191
- allowUpdates: true,
192
- animationMode: 'easeTo',
193
- animationDuration: 2000,
194
- };
195
-
196
- defaultCamera: NativeCameraStop | null = null;
197
- cameraRef: MutableRefObject<
198
- (Component<NativeProps> & Readonly<NativeMethods>) | null
199
- >;
200
-
201
- constructor(props: CameraProps) {
202
- super(props);
203
- this.cameraRef = React.createRef<
204
- Component<NativeProps> & Readonly<NativeMethods>
205
- >();
206
- }
207
-
208
- UNSAFE_componentWillReceiveProps(nextProps: CameraProps): void {
209
- this._handleCameraChange(this.props, nextProps);
210
- }
211
-
212
- shouldComponentUpdate(): boolean {
213
- return false;
214
- }
215
-
216
- _handleCameraChange(
217
- currentCamera: CameraProps,
218
- nextCamera: CameraProps,
219
- ): void {
220
- const c = currentCamera;
221
- const n = nextCamera;
222
-
223
- if (!n.allowUpdates) {
224
- return;
225
- }
226
-
227
- const hasCameraChanged = this._hasCameraChanged(c, n);
228
- if (!hasCameraChanged) {
229
- return;
230
- }
231
-
232
- if (c.followUserLocation && !n.followUserLocation) {
233
- this.cameraRef.current?.setNativeProps({followUserLocation: false});
234
- return;
235
- }
236
- if (!c.followUserLocation && n.followUserLocation) {
237
- this.cameraRef.current?.setNativeProps({followUserLocation: true});
238
- }
239
-
240
- if (n.followUserLocation) {
241
- this.cameraRef.current?.setNativeProps({
242
- followUserMode: n.followUserMode,
243
- followPitch: n.followPitch || n.pitch,
244
- followHeading: n.followHeading || n.heading,
245
- followZoomLevel: n.followZoomLevel || n.zoomLevel,
246
- });
247
- return;
248
- }
249
-
250
- if (n.maxBounds) {
251
- this.cameraRef.current?.setNativeProps({
252
- maxBounds: this._getMaxBounds(),
253
- });
254
- }
255
- if (n.minZoomLevel) {
256
- this.cameraRef.current?.setNativeProps({
257
- minZoomLevel: this.props.minZoomLevel,
258
- });
259
- }
260
- if (n.maxZoomLevel) {
261
- this.cameraRef.current?.setNativeProps({
262
- maxZoomLevel: this.props.maxZoomLevel,
263
- });
264
- }
265
-
266
- const cameraConfig: CameraStop = {
267
- bounds: undefined,
268
- centerCoordinate: undefined,
269
- padding: n.padding,
270
- zoomLevel: n.zoomLevel,
271
- pitch: n.pitch,
272
- heading: n.heading,
273
- animationMode: n.animationMode,
274
- animationDuration: n.animationDuration,
275
- };
276
-
277
- const boundsChanged = this._hasBoundsChanged(c.bounds, n.bounds);
278
- const centerCoordinateChanged = this._hasCenterCoordinateChanged(
279
- c.centerCoordinate,
280
- n.centerCoordinate,
281
- );
282
- const paddingChanged = this._hasPaddingChanged(c.padding, n.padding);
283
- const zoomChanged = this._hasNumberChanged(c.zoomLevel, n.zoomLevel);
284
- const pitchChanged = this._hasNumberChanged(c.pitch, n.pitch);
285
- const headingChanged = this._hasNumberChanged(c.heading, n.heading);
286
-
287
- let shouldUpdate = false;
288
-
289
- if (n.bounds && boundsChanged) {
290
- cameraConfig.bounds = n.bounds;
291
- shouldUpdate = true;
292
- } else if (n.centerCoordinate && centerCoordinateChanged) {
293
- cameraConfig.centerCoordinate = n.centerCoordinate;
294
- shouldUpdate = true;
295
- }
296
-
297
- if (paddingChanged || zoomChanged || pitchChanged || headingChanged) {
298
- shouldUpdate = true;
299
- }
202
+ const Camera = memo(
203
+ React.forwardRef<CameraRef, CameraProps>(
204
+ (
205
+ {
206
+ allowUpdates = true,
207
+ animationMode = 'easeTo',
208
+ animationDuration = 2000,
209
+ ...rest
210
+ }: CameraProps,
211
+ ref,
212
+ ) => {
213
+ const props = useMemo(() => {
214
+ return {
215
+ allowUpdates,
216
+ animationMode,
217
+ animationDuration,
218
+ ...rest,
219
+ };
220
+ }, [allowUpdates, animationMode, animationDuration, rest]);
221
+
222
+ useImperativeHandle(
223
+ ref,
224
+ (): CameraRef => ({
225
+ /**
226
+ * Map camera transitions to fit provided bounds
227
+ *
228
+ * @example
229
+ * this.camera.fitBounds([lng, lat], [lng, lat])
230
+ * this.camera.fitBounds([lng, lat], [lng, lat], 20, 1000) // padding for all sides
231
+ * this.camera.fitBounds([lng, lat], [lng, lat], [verticalPadding, horizontalPadding], 1000)
232
+ * this.camera.fitBounds([lng, lat], [lng, lat], [top, right, bottom, left], 1000)
233
+ *
234
+ * @param {Array<Number>} northEastCoordinates - North east coordinate of bound
235
+ * @param {Array<Number>} southWestCoordinates - South west coordinate of bound
236
+ * @param {Number|Array<Number>|undefined} padding - Padding for the bounds
237
+ * @param {Number=} animationDuration - Duration of camera animation
238
+ * @return {void}
239
+ */
240
+ fitBounds,
241
+ /**
242
+ * Map camera will fly to new coordinate
243
+ *
244
+ * @example
245
+ * this.camera.flyTo([lng, lat])
246
+ * this.camera.flyTo([lng, lat], 12000)
247
+ *
248
+ * @param {Array<Number>} coordinates - Coordinates that map camera will jump too
249
+ * @param {Number=} animationDuration - Duration of camera animation
250
+ * @return {void}
251
+ */
252
+ flyTo,
253
+ /**
254
+ * Map camera will move to new coordinate at the same zoom level
255
+ *
256
+ * @example
257
+ * this.camera.moveTo([lng, lat], 200) // eases camera to new location based on duration
258
+ * this.camera.moveTo([lng, lat]) // snaps camera to new location without any easing
259
+ *
260
+ * @param {Array<Number>} coordinates - Coordinates that map camera will move too
261
+ * @param {Number=} animationDuration - Duration of camera animation
262
+ * @return {void}
263
+ */
264
+ moveTo,
265
+ /**
266
+ * Map camera will zoom to specified level
267
+ *
268
+ * @example
269
+ * this.camera.zoomTo(16)
270
+ * this.camera.zoomTo(16, 100)
271
+ *
272
+ * @param {Number} zoomLevel - Zoom level that the map camera will animate too
273
+ * @param {Number=} animationDuration - Duration of camera animation
274
+ * @return {void}
275
+ */
276
+ zoomTo,
277
+ /**
278
+ * Map camera will perform updates based on provided config. Advanced use only!
279
+ *
280
+ * @example
281
+ * this.camera.setCamera({
282
+ * centerCoordinate: [lng, lat],
283
+ * zoomLevel: 16,
284
+ * animationDuration: 2000,
285
+ * })
286
+ *
287
+ * this.camera.setCamera({
288
+ * stops: [
289
+ * { pitch: 45, animationDuration: 200 },
290
+ * { heading: 180, animationDuration: 300 },
291
+ * ]
292
+ * })
293
+ *
294
+ * @param {Object} config - Camera configuration
295
+ */
296
+ setCamera,
297
+ _defaultCamera,
298
+ _getMaxBounds,
299
+ _getNativeCameraMode,
300
+ _createStopConfig,
301
+ _createDefaultCamera,
302
+ }),
303
+ );
300
304
 
301
- if (shouldUpdate) {
302
- this._setCamera(cameraConfig);
303
- }
304
- }
305
-
306
- _hasCameraChanged(
307
- currentCamera: CameraProps,
308
- nextCamera: CameraProps,
309
- ): boolean {
310
- const c = currentCamera;
311
- const n = nextCamera;
312
-
313
- const hasDefaultPropsChanged =
314
- c.heading !== n.heading ||
315
- this._hasCenterCoordinateChanged(
316
- c?.centerCoordinate,
317
- n?.centerCoordinate,
318
- ) ||
319
- this._hasBoundsChanged(c.bounds, n.bounds) ||
320
- this._hasPaddingChanged(c.padding, n.padding) ||
321
- c.pitch !== n.pitch ||
322
- c.zoomLevel !== n.zoomLevel ||
323
- c.triggerKey !== n.triggerKey;
324
-
325
- const hasFollowPropsChanged =
326
- c.followUserLocation !== n.followUserLocation ||
327
- c.followUserMode !== n.followUserMode ||
328
- c.followZoomLevel !== n.followZoomLevel ||
329
- c.followHeading !== n.followHeading ||
330
- c.followPitch !== n.followPitch;
331
-
332
- const hasAnimationPropsChanged =
333
- c.animationMode !== n.animationMode ||
334
- c.animationDuration !== n.animationDuration;
335
-
336
- const hasNavigationConstraintsPropsChanged =
337
- this._hasBoundsChanged(c.maxBounds, n.maxBounds) ||
338
- c.minZoomLevel !== n.minZoomLevel ||
339
- c.maxZoomLevel !== n.maxZoomLevel;
340
-
341
- return (
342
- hasDefaultPropsChanged ||
343
- hasFollowPropsChanged ||
344
- hasAnimationPropsChanged ||
345
- hasNavigationConstraintsPropsChanged
346
- );
347
- }
348
-
349
- _hasCenterCoordinateChanged(
350
- cC?: GeoJSON.Position,
351
- nC?: GeoJSON.Position,
352
- ): boolean {
353
- if (!cC && !nC) {
354
- return false;
355
- }
305
+ const _defaultCamera = useRef<NativeCameraStop | null>(null);
306
+
307
+ const cameraRef = useNativeRef<NativeProps>();
308
+
309
+ const _createStopConfig = useCallback(
310
+ (
311
+ config: CameraStop = {},
312
+ ignoreFollowUserLocation = false,
313
+ ): NativeCameraStop | null => {
314
+ if (props.followUserLocation && !ignoreFollowUserLocation) {
315
+ return null;
316
+ }
317
+
318
+ const stopConfig: NativeCameraStop = {
319
+ mode: _getNativeCameraMode(config),
320
+ pitch: config.pitch,
321
+ heading: config.heading,
322
+ duration: config.animationDuration || 0,
323
+ zoom: config.zoomLevel,
324
+ paddingTop:
325
+ config.padding?.paddingTop || config.bounds?.paddingTop || 0,
326
+ paddingRight:
327
+ config.padding?.paddingRight || config.bounds?.paddingRight || 0,
328
+ paddingBottom:
329
+ config.padding?.paddingBottom ||
330
+ config.bounds?.paddingBottom ||
331
+ 0,
332
+ paddingLeft:
333
+ config.padding?.paddingLeft || config.bounds?.paddingLeft || 0,
334
+ };
335
+
336
+ if (config.centerCoordinate) {
337
+ stopConfig.centerCoordinate = toJSONString(
338
+ geoUtils.makePoint(config.centerCoordinate),
339
+ );
340
+ }
341
+
342
+ if (config.bounds && config.bounds.ne && config.bounds.sw) {
343
+ const {ne, sw} = config.bounds;
344
+ stopConfig.bounds = toJSONString(geoUtils.makeLatLngBounds(ne, sw));
345
+ }
346
+
347
+ return stopConfig;
348
+ },
349
+ [props.followUserLocation],
350
+ );
356
351
 
357
- if (existenceChange(!!cC, !!nC)) {
358
- return true;
359
- }
352
+ const _setCamera = useCallback(
353
+ (config: CameraStop | CameraStops = {}): void => {
354
+ if ('stops' in config) {
355
+ let nativeStops: NativeCameraStop[] = [];
356
+
357
+ for (const stop of config.stops) {
358
+ const nativeStop = _createStopConfig(stop);
359
+ if (nativeStop) {
360
+ nativeStops = [...nativeStops, nativeStop];
361
+ }
362
+ }
363
+ cameraRef.current?.setNativeProps({stop: {stops: nativeStops}});
364
+ } else {
365
+ const nativeStop = _createStopConfig(config);
366
+
367
+ if (nativeStop) {
368
+ cameraRef.current?.setNativeProps({stop: nativeStop});
369
+ }
370
+ }
371
+ },
372
+ [cameraRef.current, _createStopConfig],
373
+ );
360
374
 
361
- const isLngDiff = cC?.[0] !== nC?.[0];
362
- const isLatDiff = cC?.[1] !== nC?.[1];
363
- return isLngDiff || isLatDiff;
364
- }
365
-
366
- _hasBoundsChanged(
367
- cB?: CameraBoundsWithPadding,
368
- nB?: CameraBoundsWithPadding,
369
- ): boolean {
370
- if (!cB && !nB) {
371
- return false;
372
- }
375
+ const _getMaxBounds = useCallback((): string | null => {
376
+ const bounds = props.maxBounds;
377
+ if (!bounds || !bounds.ne || !bounds.sw) {
378
+ return null;
379
+ }
380
+ return toJSONString(geoUtils.makeLatLngBounds(bounds.ne, bounds.sw));
381
+ }, [props.maxBounds]);
373
382
 
374
- if (existenceChange(!!cB, !!nB)) {
375
- return true;
376
- }
383
+ useEffect(() => {
384
+ if (!props.allowUpdates) {
385
+ return;
386
+ }
377
387
 
378
- return (
379
- cB?.ne[0] !== nB?.ne[0] ||
380
- cB?.ne[1] !== nB?.ne[1] ||
381
- cB?.sw[0] !== nB?.sw[0] ||
382
- cB?.sw[1] !== nB?.sw[1] ||
383
- cB?.paddingTop !== nB?.paddingTop ||
384
- cB?.paddingLeft !== nB?.paddingLeft ||
385
- cB?.paddingRight !== nB?.paddingRight ||
386
- cB?.paddingBottom !== nB?.paddingBottom
387
- );
388
- }
389
-
390
- _hasPaddingChanged(cP?: CameraPadding, nP?: CameraPadding): boolean {
391
- if (!cP && !nP) {
392
- return false;
393
- }
388
+ cameraRef.current?.setNativeProps({
389
+ followUserLocation: props.followUserLocation,
390
+ });
391
+ }, [cameraRef.current, props.followUserLocation]);
394
392
 
395
- if (existenceChange(!!cP, !!nP)) {
396
- return true;
397
- }
393
+ useEffect(() => {
394
+ if (!props.maxBounds || !props.allowUpdates) {
395
+ return;
396
+ }
398
397
 
399
- return (
400
- cP?.paddingTop !== nP?.paddingTop ||
401
- cP?.paddingLeft !== nP?.paddingLeft ||
402
- cP?.paddingRight !== nP?.paddingRight ||
403
- cP?.paddingBottom !== nP?.paddingBottom
404
- );
405
- }
406
-
407
- _hasNumberChanged(prev?: number, next?: number): boolean {
408
- if (existenceChange(!!prev, !!next)) {
409
- return true;
410
- }
398
+ cameraRef.current?.setNativeProps({
399
+ maxBounds: _getMaxBounds(),
400
+ });
401
+ }, [cameraRef.current, props.maxBounds, _getMaxBounds]);
411
402
 
412
- if (!prev && !next) {
413
- return false;
414
- }
415
-
416
- return prev !== next;
417
- }
403
+ useEffect(() => {
404
+ if (!props.minZoomLevel || !props.allowUpdates) {
405
+ return;
406
+ }
418
407
 
419
- /**
420
- * Map camera transitions to fit provided bounds
421
- *
422
- * @example
423
- * this.camera.fitBounds([lng, lat], [lng, lat])
424
- * this.camera.fitBounds([lng, lat], [lng, lat], 20, 1000) // padding for all sides
425
- * this.camera.fitBounds([lng, lat], [lng, lat], [verticalPadding, horizontalPadding], 1000)
426
- * this.camera.fitBounds([lng, lat], [lng, lat], [top, right, bottom, left], 1000)
427
- *
428
- * @param {Array<Number>} northEastCoordinates - North east coordinate of bound
429
- * @param {Array<Number>} southWestCoordinates - South west coordinate of bound
430
- * @param {Number|Array<Number>=} padding - Padding for the bounds
431
- * @param {Number=} animationDuration - Duration of camera animation
432
- * @return {void}
433
- */
434
- fitBounds(
435
- northEastCoordinates: number[],
436
- southWestCoordinates: number[],
437
- padding: number | number[] | null | undefined = 0,
438
- animationDuration: number | null | undefined = 0.0,
439
- ): void {
440
- const pad = {
441
- paddingLeft: 0,
442
- paddingRight: 0,
443
- paddingTop: 0,
444
- paddingBottom: 0,
445
- };
446
-
447
- if (Array.isArray(padding)) {
448
- if (padding.length === 2) {
449
- pad.paddingTop = padding[0];
450
- pad.paddingBottom = padding[0];
451
- pad.paddingLeft = padding[1];
452
- pad.paddingRight = padding[1];
453
- } else if (padding.length === 4) {
454
- pad.paddingTop = padding[0];
455
- pad.paddingRight = padding[1];
456
- pad.paddingBottom = padding[2];
457
- pad.paddingLeft = padding[3];
458
- }
459
- } else {
460
- pad.paddingLeft = padding;
461
- pad.paddingRight = padding;
462
- pad.paddingTop = padding;
463
- pad.paddingBottom = padding;
464
- }
408
+ cameraRef.current?.setNativeProps({
409
+ minZoomLevel: props.minZoomLevel,
410
+ });
411
+ }, [cameraRef.current, props.minZoomLevel]);
465
412
 
466
- this.setCamera({
467
- bounds: {
468
- ne: northEastCoordinates,
469
- sw: southWestCoordinates,
470
- },
471
- padding: pad,
472
- animationDuration,
473
- animationMode: 'easeTo',
474
- });
475
- }
413
+ useEffect(() => {
414
+ if (!props.maxZoomLevel || !props.allowUpdates) {
415
+ return;
416
+ }
476
417
 
477
- /**
478
- * Map camera will fly to new coordinate
479
- *
480
- * @example
481
- * this.camera.flyTo([lng, lat])
482
- * this.camera.flyTo([lng, lat], 12000)
483
- *
484
- * @param {Array<Number>} coordinates - Coordinates that map camera will jump too
485
- * @param {Number=} animationDuration - Duration of camera animation
486
- * @return {void}
487
- */
488
- flyTo(coordinates: GeoJSON.Position, animationDuration = 2000): void {
489
- this.setCamera({
490
- centerCoordinate: coordinates,
491
- animationDuration,
492
- animationMode: 'flyTo',
493
- });
494
- }
418
+ cameraRef.current?.setNativeProps({
419
+ maxZoomLevel: props.maxZoomLevel,
420
+ });
421
+ }, [cameraRef.current, props.maxZoomLevel]);
495
422
 
496
- /**
497
- * Map camera will move to new coordinate at the same zoom level
498
- *
499
- * @example
500
- * this.camera.moveTo([lng, lat], 200) // eases camera to new location based on duration
501
- * this.camera.moveTo([lng, lat]) // snaps camera to new location without any easing
502
- *
503
- * @param {Array<Number>} coordinates - Coordinates that map camera will move too
504
- * @param {Number=} animationDuration - Duration of camera animation
505
- * @return {void}
506
- */
507
- moveTo(coordinates: GeoJSON.Position, animationDuration = 0): void {
508
- this.setCamera({
509
- centerCoordinate: coordinates,
510
- animationDuration,
511
- });
512
- }
423
+ useEffect(() => {
424
+ if (!props.allowUpdates) {
425
+ return;
426
+ }
513
427
 
514
- /**
515
- * Map camera will zoom to specified level
516
- *
517
- * @example
518
- * this.camera.zoomTo(16)
519
- * this.camera.zoomTo(16, 100)
520
- *
521
- * @param {Number} zoomLevel - Zoom level that the map camera will animate too
522
- * @param {Number=} animationDuration - Duration of camera animation
523
- * @return {void}
524
- */
525
- zoomTo(zoomLevel: number, animationDuration = 2000): void {
526
- this.setCamera({
527
- zoomLevel,
528
- animationDuration,
529
- animationMode: 'flyTo',
530
- });
531
- }
428
+ if (!props.followUserLocation) {
429
+ return;
430
+ }
532
431
 
533
- /**
534
- * Map camera will perform updates based on provided config. Advanced use only!
535
- *
536
- * @example
537
- * this.camera.setCamera({
538
- * centerCoordinate: [lng, lat],
539
- * zoomLevel: 16,
540
- * animationDuration: 2000,
541
- * })
542
- *
543
- * this.camera.setCamera({
544
- * stops: [
545
- * { pitch: 45, animationDuration: 200 },
546
- * { heading: 180, animationDuration: 300 },
547
- * ]
548
- * })
549
- *
550
- * @param {Object} config - Camera configuration
551
- */
552
- setCamera(config: CameraStop | CameraStops = {}): void {
553
- this._setCamera(config);
554
- }
555
-
556
- _setCamera(config: CameraStop | CameraStops = {}): void {
557
- if ('stops' in config) {
558
- let nativeStops: NativeCameraStop[] = [];
559
-
560
- for (const stop of config.stops) {
561
- const nativeStop = this._createStopConfig(stop);
562
- if (nativeStop) {
563
- nativeStops = [...nativeStops, nativeStop];
432
+ cameraRef.current?.setNativeProps({
433
+ followUserMode: props.followUserMode,
434
+ followPitch: props.followPitch || props.pitch,
435
+ followHeading: props.followHeading || props.heading,
436
+ followZoomLevel: props.followZoomLevel || props.zoomLevel,
437
+ });
438
+ }, [
439
+ cameraRef.current,
440
+ props.allowUpdates,
441
+ props.followUserLocation,
442
+ props.followUserMode,
443
+ props.followPitch,
444
+ props.pitch,
445
+ props.followHeading,
446
+ props.heading,
447
+ props.followZoomLevel,
448
+ props.zoomLevel,
449
+ ]);
450
+
451
+ const cameraConfig: CameraStop = useMemo(() => {
452
+ return {
453
+ bounds: props.bounds,
454
+ centerCoordinate: props.centerCoordinate,
455
+ padding: props.padding,
456
+ zoomLevel: props.zoomLevel,
457
+ minZoomLevel: props.minZoomLevel,
458
+ maxZoomLevel: props.maxZoomLevel,
459
+ pitch: props.pitch,
460
+ heading: props.heading,
461
+ animationMode: props.animationMode,
462
+ animationDuration: props.animationDuration,
463
+ };
464
+ }, [
465
+ props.bounds,
466
+ props.centerCoordinate,
467
+ props.padding,
468
+ props.zoomLevel,
469
+ props.minZoomLevel,
470
+ props.maxZoomLevel,
471
+ props.pitch,
472
+ props.heading,
473
+ props.animationMode,
474
+ props.animationDuration,
475
+ ]);
476
+
477
+ useEffect(() => {
478
+ if (!props.allowUpdates) {
479
+ return;
564
480
  }
565
- }
566
- this.cameraRef.current?.setNativeProps({stop: {stops: nativeStops}});
567
- } else {
568
- const nativeStop = this._createStopConfig(config);
569
- if (nativeStop) {
570
- this.cameraRef.current?.setNativeProps({stop: nativeStop});
571
- }
572
- }
573
- }
574
481
 
575
- _createDefaultCamera(): NativeCameraStop | null {
576
- if (this.defaultCamera) {
577
- return this.defaultCamera;
578
- }
579
- if (!this.props.defaultSettings) {
580
- return null;
581
- }
482
+ _setCamera(cameraConfig);
483
+ }, [_setCamera, cameraConfig]);
484
+
485
+ const fitBounds = (
486
+ northEastCoordinates: GeoJSON.Position,
487
+ southWestCoordinates: GeoJSON.Position,
488
+ padding: number | number[] = 0,
489
+ animationDuration: number = 0.0,
490
+ ): void => {
491
+ const pad = {
492
+ paddingLeft: 0,
493
+ paddingRight: 0,
494
+ paddingTop: 0,
495
+ paddingBottom: 0,
496
+ };
497
+
498
+ if (Array.isArray(padding)) {
499
+ if (padding.length === 2) {
500
+ pad.paddingTop = padding[0];
501
+ pad.paddingBottom = padding[0];
502
+ pad.paddingLeft = padding[1];
503
+ pad.paddingRight = padding[1];
504
+ } else if (padding.length === 4) {
505
+ pad.paddingTop = padding[0];
506
+ pad.paddingRight = padding[1];
507
+ pad.paddingBottom = padding[2];
508
+ pad.paddingLeft = padding[3];
509
+ }
510
+ } else {
511
+ pad.paddingLeft = padding;
512
+ pad.paddingRight = padding;
513
+ pad.paddingTop = padding;
514
+ pad.paddingBottom = padding;
515
+ }
582
516
 
583
- this.defaultCamera = this._createStopConfig(
584
- {
585
- ...this.props.defaultSettings,
586
- animationMode: 'moveTo',
587
- },
588
- true,
589
- );
590
- return this.defaultCamera;
591
- }
592
-
593
- _createStopConfig(
594
- config: CameraStop = {},
595
- ignoreFollowUserLocation = false,
596
- ): NativeCameraStop | null {
597
- if (this.props.followUserLocation && !ignoreFollowUserLocation) {
598
- return null;
599
- }
517
+ setCamera({
518
+ bounds: {
519
+ ne: northEastCoordinates,
520
+ sw: southWestCoordinates,
521
+ },
522
+ padding: pad,
523
+ animationDuration: animationDuration,
524
+ animationMode: 'easeTo',
525
+ });
526
+ };
527
+
528
+ const flyTo = (
529
+ coordinates: GeoJSON.Position,
530
+ animationDuration = 2000,
531
+ ): void => {
532
+ setCamera({
533
+ centerCoordinate: coordinates,
534
+ animationDuration,
535
+ animationMode: 'flyTo',
536
+ });
537
+ };
538
+
539
+ const moveTo = (
540
+ coordinates: GeoJSON.Position,
541
+ animationDuration = 0,
542
+ ): void => {
543
+ setCamera({
544
+ centerCoordinate: coordinates,
545
+ animationDuration,
546
+ });
547
+ };
548
+
549
+ const zoomTo = (zoomLevel: number, animationDuration = 2000): void => {
550
+ setCamera({
551
+ zoomLevel,
552
+ animationDuration,
553
+ animationMode: 'flyTo',
554
+ });
555
+ };
556
+
557
+ const setCamera = (config: CameraStop | CameraStops = {}): void => {
558
+ _setCamera(config);
559
+ };
560
+
561
+ const _createDefaultCamera = (): NativeCameraStop | null => {
562
+ if (_defaultCamera.current) {
563
+ return _defaultCamera.current;
564
+ }
565
+ if (!props.defaultSettings) {
566
+ return null;
567
+ }
600
568
 
601
- const stopConfig: NativeCameraStop = {
602
- mode: this._getNativeCameraMode(config),
603
- pitch: config.pitch,
604
- heading: config.heading,
605
- duration: config.animationDuration || 0,
606
- zoom: config.zoomLevel,
607
- paddingTop: config.padding?.paddingTop || config.bounds?.paddingTop || 0,
608
- paddingRight:
609
- config.padding?.paddingRight || config.bounds?.paddingRight || 0,
610
- paddingBottom:
611
- config.padding?.paddingBottom || config.bounds?.paddingBottom || 0,
612
- paddingLeft:
613
- config.padding?.paddingLeft || config.bounds?.paddingLeft || 0,
614
- };
615
-
616
- if (config.centerCoordinate) {
617
- stopConfig.centerCoordinate = toJSONString(
618
- geoUtils.makePoint(config.centerCoordinate),
569
+ _defaultCamera.current = _createStopConfig(
570
+ {
571
+ ...props.defaultSettings,
572
+ animationMode: 'moveTo',
573
+ },
574
+ true,
575
+ );
576
+ return _defaultCamera.current;
577
+ };
578
+
579
+ const _getNativeCameraMode = (
580
+ config: CameraStop,
581
+ ): NativeAnimationMode => {
582
+ switch (config.animationMode) {
583
+ case 'flyTo':
584
+ return MapLibreGL.CameraModes.Flight;
585
+ case 'moveTo':
586
+ return MapLibreGL.CameraModes.None;
587
+ case 'linearTo':
588
+ return MapLibreGL.CameraModes.Linear;
589
+ default:
590
+ return MapLibreGL.CameraModes.Ease;
591
+ }
592
+ };
593
+
594
+ const nativeProps = Object.assign({}, props);
595
+
596
+ const callbacks = {
597
+ onUserTrackingModeChange: nativeProps.onUserTrackingModeChange,
598
+ };
599
+
600
+ return (
601
+ <RCTMLNCamera
602
+ testID="Camera"
603
+ ref={cameraRef}
604
+ followUserLocation={props.followUserLocation}
605
+ followUserMode={props.followUserMode}
606
+ followPitch={props.followPitch}
607
+ followHeading={props.followHeading}
608
+ followZoomLevel={props.followZoomLevel}
609
+ stop={_createStopConfig(nativeProps)}
610
+ maxZoomLevel={props.maxZoomLevel}
611
+ minZoomLevel={props.minZoomLevel}
612
+ maxBounds={_getMaxBounds()}
613
+ defaultStop={_createDefaultCamera()}
614
+ {...callbacks}
615
+ />
619
616
  );
620
- }
621
-
622
- if (config.bounds && config.bounds.ne && config.bounds.sw) {
623
- const {ne, sw} = config.bounds;
624
- stopConfig.bounds = toJSONString(geoUtils.makeLatLngBounds(ne, sw));
625
- }
626
-
627
- return stopConfig;
628
- }
629
-
630
- _getNativeCameraMode(config: CameraStop): NativeAnimationMode {
631
- switch (config.animationMode) {
632
- case 'flyTo':
633
- return MapLibreGL.CameraModes.Flight;
634
- case 'moveTo':
635
- return MapLibreGL.CameraModes.None;
636
- case 'linearTo':
637
- return MapLibreGL.CameraModes.Linear;
638
- default:
639
- return MapLibreGL.CameraModes.Ease;
640
- }
641
- }
642
-
643
- _getMaxBounds(): string | null {
644
- const bounds = this.props.maxBounds;
645
- if (!bounds || !bounds.ne || !bounds.sw) {
646
- return null;
647
- }
648
- return toJSONString(geoUtils.makeLatLngBounds(bounds.ne, bounds.sw));
649
- }
650
-
651
- render(): ReactElement {
652
- const props = Object.assign({}, this.props);
653
-
654
- const callbacks = {
655
- onUserTrackingModeChange: props.onUserTrackingModeChange,
656
- };
657
-
658
- return (
659
- <RCTMLNCamera
660
- testID="Camera"
661
- ref={this.cameraRef}
662
- followUserLocation={this.props.followUserLocation}
663
- followUserMode={this.props.followUserMode}
664
- followPitch={this.props.followPitch}
665
- followHeading={this.props.followHeading}
666
- followZoomLevel={this.props.followZoomLevel}
667
- stop={this._createStopConfig(props)}
668
- maxZoomLevel={this.props.maxZoomLevel}
669
- minZoomLevel={this.props.minZoomLevel}
670
- maxBounds={this._getMaxBounds()}
671
- defaultStop={this._createDefaultCamera()}
672
- {...callbacks}
673
- />
674
- );
675
- }
676
- }
617
+ },
618
+ ),
619
+ );
677
620
 
678
621
  const RCTMLNCamera = requireNativeComponent<NativeProps>(NATIVE_MODULE_NAME);
679
622