From 273a2c2fab393c24c8d54ede7cc08cb74944b278 Mon Sep 17 00:00:00 2001 From: Naftali Beder Date: Mon, 11 Apr 2022 16:39:32 -0500 Subject: [PATCH 01/36] Enable setting camera bounds (iOS) --- ios/RCTMGL-v10/RCTMGLCamera.swift | 62 +++++++++++++++++++------------ 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/ios/RCTMGL-v10/RCTMGLCamera.swift b/ios/RCTMGL-v10/RCTMGLCamera.swift index eb78331ef..dd87956a2 100644 --- a/ios/RCTMGL-v10/RCTMGLCamera.swift +++ b/ios/RCTMGL-v10/RCTMGLCamera.swift @@ -60,7 +60,7 @@ class RCTMGLCamera : RCTMGLMapComponentBase, LocationConsumer { map.location.addLocationConsumer(newConsumer: self) } } - + @objc func setStop(_ dictionary: [String:Any]?) { guard let dictionary = dictionary else { // Seems to be normal when followUserLocation is set @@ -80,39 +80,55 @@ class RCTMGLCamera : RCTMGLMapComponentBase, LocationConsumer { default: fatalError("Unexpected geometry: \(String(describing: centerFeature?.geometry))") } - //camera.center = centerFeature } - var duration : Double? = nil - var zoom : Double? = nil - var pitch : Double? = nil - var bearing : Double? = nil - var heading : Double? = nil - - if let durationParam = dictionary["duration"] as? Double { - duration = durationParam + if let feature : String = dictionary["bounds"] as? String { + let collection : Turf.FeatureCollection? = try! + JSONDecoder().decode(Turf.FeatureCollection.self, from: feature.data(using: .utf8)!) + let features = collection?.features + + let ne: CLLocationCoordinate2D + switch features?.first?.geometry { + case .point(let point): + ne = point.coordinates + default: + fatalError("Unexpected geometry: \(String(describing: features?.first?.geometry))") + } + + let sw: CLLocationCoordinate2D + switch features?.last?.geometry { + case .point(let point): + sw = point.coordinates + default: + fatalError("Unexpected geometry: \(String(describing: features?.last?.geometry))") + } + + withMapView { map in + let bounds = CoordinateBounds(southwest: sw, northeast: ne) + let c = map.mapboxMap.camera(for: bounds, padding: .zero, bearing: 0, pitch: 0) + camera.center = c.center + camera.zoom = c.zoom + } } - - if let zoomParam = dictionary["zoom"] as? Double { - zoom = zoomParam; - camera.zoom = CGFloat(zoomParam) + + if let zoom = dictionary["zoom"] as? Double { + camera.zoom = CGFloat(zoom) } - if let pitchParam = dictionary["pitch"] as? Double { - pitch = pitchParam - camera.pitch = CGFloat(pitchParam) + if let pitch = dictionary["pitch"] as? Double { + camera.pitch = CGFloat(pitch) } - if let headingParam = dictionary["heading"] as? Double { - heading = headingParam - camera.bearing = CLLocationDirection(headingParam) + if let heading = dictionary["heading"] as? Double { + camera.bearing = CLLocationDirection(heading) } - if let bearingParam = dictionary["bearing"] as? Double { - bearing = bearingParam - camera.bearing = CLLocationDirection(bearingParam) + if let bearing = dictionary["bearing"] as? Double { + camera.bearing = CLLocationDirection(bearing) } + let duration = dictionary["duration"] as? Double + withMapView { map in if let duration = duration { map.camera.fly(to: camera, duration: self.toTimeInterval(duration)) From 8f51b009970ed83d5c2d8164b8524eda4633e714 Mon Sep 17 00:00:00 2001 From: Naftali Beder Date: Tue, 12 Apr 2022 10:13:11 -0500 Subject: [PATCH 02/36] Implement camera easing, organize dict value extraction (iOS) --- ios/RCTMGL-v10/RCTMGLCamera.swift | 42 +++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/ios/RCTMGL-v10/RCTMGLCamera.swift b/ios/RCTMGL-v10/RCTMGLCamera.swift index dd87956a2..503c5c450 100644 --- a/ios/RCTMGL-v10/RCTMGLCamera.swift +++ b/ios/RCTMGL-v10/RCTMGLCamera.swift @@ -1,5 +1,6 @@ import MapboxMaps import Turf +import Foundation protocol RCTMGLMapComponent { func addToMap(_ map: RCTMGLMapView) @@ -39,6 +40,11 @@ open class RCTMGLMapComponentBase : UIView, RCTMGLMapComponent { } class RCTMGLCamera : RCTMGLMapComponentBase, LocationConsumer { + // See MGLModule.swift:constantsToExport. + enum Mode: String, CaseIterable { + case flight, move, ease, linear + } + @objc var followUserLocation : Bool = false { didSet { @@ -55,16 +61,15 @@ class RCTMGLCamera : RCTMGLMapComponentBase, LocationConsumer { if let locationModule = RCTMGLLocationModule.shared { map.location.overrideLocationProvider(with: locationModule.locationProvider) } - map.location map.location.locationProvider.requestWhenInUseAuthorization() map.location.addLocationConsumer(newConsumer: self) } } - + @objc func setStop(_ dictionary: [String:Any]?) { guard let dictionary = dictionary else { // Seems to be normal when followUserLocation is set - //return Logger.log(level: .error, message: "stop called with nil") + // return Logger.log(level: .error, message: "stop called with nil") return } @@ -127,13 +132,34 @@ class RCTMGLCamera : RCTMGLMapComponentBase, LocationConsumer { camera.bearing = CLLocationDirection(bearing) } - let duration = dictionary["duration"] as? Double + let duration: TimeInterval? = { + if let d = dictionary["duration"] as? Double { + return self.toTimeInterval(d) + } + return nil + }() + + let mode: Mode = { + if let m = dictionary["mode"] as? String, let m = Mode(rawValue: m) { + return m + } + return .flight + }() withMapView { map in - if let duration = duration { - map.camera.fly(to: camera, duration: self.toTimeInterval(duration)) - } else { - map.camera.fly(to: camera) + switch mode { + case .flight: + if let duration = duration { + map.camera.fly(to: camera, duration: duration) + } else { + map.camera.fly(to: camera) + } + case .move: + map.camera.ease(to: camera, duration: duration ?? 0, curve: .easeInOut, completion: nil) + case .ease: + map.camera.ease(to: camera, duration: duration ?? 0, curve: .easeInOut, completion: nil) + case .linear: + map.camera.ease(to: camera, duration: duration ?? 0, curve: .linear, completion: nil) } } } From 3188450d7aa734482d24f74f7504914ac4d08d04 Mon Sep 17 00:00:00 2001 From: Naftali Beder Date: Tue, 12 Apr 2022 14:59:28 -0500 Subject: [PATCH 03/36] Add remaining camera animation mode types (iOS) --- ios/RCTMGL-v10/MGLModule.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ios/RCTMGL-v10/MGLModule.swift b/ios/RCTMGL-v10/MGLModule.swift index ac8a74f49..475aa58aa 100644 --- a/ios/RCTMGL-v10/MGLModule.swift +++ b/ios/RCTMGL-v10/MGLModule.swift @@ -35,6 +35,9 @@ class MGLModule : NSObject { ["Update": RCT_MAPBOX_USER_LOCATION_UPDATE], "CameraModes": [ + "Flight": "flight", + "None": "none", + "Linear": "linear", "Ease": "ease", ], "EventTypes": From 015ed222af4315e7a27b3f06167cdbe748bd3035 Mon Sep 17 00:00:00 2001 From: Naftali Beder Date: Tue, 12 Apr 2022 15:03:33 -0500 Subject: [PATCH 04/36] Clarify comment --- ios/RCTMGL-v10/RCTMGLCamera.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RCTMGL-v10/RCTMGLCamera.swift b/ios/RCTMGL-v10/RCTMGLCamera.swift index 503c5c450..2c427f403 100644 --- a/ios/RCTMGL-v10/RCTMGLCamera.swift +++ b/ios/RCTMGL-v10/RCTMGLCamera.swift @@ -40,7 +40,7 @@ open class RCTMGLMapComponentBase : UIView, RCTMGLMapComponent { } class RCTMGLCamera : RCTMGLMapComponentBase, LocationConsumer { - // See MGLModule.swift:constantsToExport. + // See MGLModule.swift:constantsToExport:CameraModes. enum Mode: String, CaseIterable { case flight, move, ease, linear } From f856b4625b6870464e60e2f46638a00b5181173b Mon Sep 17 00:00:00 2001 From: Naftali Beder Date: Tue, 12 Apr 2022 15:03:56 -0500 Subject: [PATCH 05/36] Add camera animation example --- example/src/examples/V10/CameraAnimation.js | 84 +++++++++++++++++++++ example/src/scenes/Home.js | 2 + 2 files changed, 86 insertions(+) create mode 100644 example/src/examples/V10/CameraAnimation.js diff --git a/example/src/examples/V10/CameraAnimation.js b/example/src/examples/V10/CameraAnimation.js new file mode 100644 index 000000000..a5ab71474 --- /dev/null +++ b/example/src/examples/V10/CameraAnimation.js @@ -0,0 +1,84 @@ +import React, {useState} from 'react'; +import {Button, SafeAreaView, StyleSheet} from 'react-native'; +import { + MapView, + Camera, + ShapeSource, + CircleLayer, + Logger, +} from '@rnmapbox/maps'; + +import Page from '../common/Page'; +import colors from '../../styles/colors'; + +Logger.setLogLevel('verbose'); + +const styles = { + map: { + flex: 1, + }, + circle: { + circleRadius: 6, + circleColor: colors.primary.blue, + }, + buttonRow: { + flex: 0, + flexDirection: 'row', + justifyContent: 'space-between', + }, +}; + +const CameraAnimation = props => { + const initialCoordinates = { + latitude: 40.759211, + longitude: -73.984638, + }; + + const [animationMode, setAnimationMode] = useState('flyTo'); + const [coordinates, setCoordinates] = useState(initialCoordinates); + + const onPress = _animationMode => { + setAnimationMode(_animationMode); + + const offset = Math.random() * 0.2; + setCoordinates({ + latitude: initialCoordinates.latitude + offset, + longitude: initialCoordinates.longitude + offset, + }); + }; + + const position = [coordinates.longitude, coordinates.latitude]; + + const shape = { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: position, + }, + }; + + return ( + + + + + + + + + + +