From 89f1fa79ee6e88993360bc449bbd10d678ddc9f6 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 7 Sep 2017 12:10:01 -0700 Subject: [PATCH] Added support for zoomTo --- .../components/AbstractEventEmitter.java | 2 +- .../rctmgl/components/AbstractMapFeature.java | 19 +++++++ .../components/camera/CameraUpdateItem.java | 1 - .../components/camera/CameraUpdateQueue.java | 7 +++ .../components/mapview/RCTMGLMapView.java | 3 ++ example/src/App.js | 2 + example/src/components/YoYo.js | 50 +++++++++++++++++++ ios/RCTMGL/CameraUpdateItem.m | 6 ++- ios/RCTMGL/CameraUpdateQueue.h | 3 +- ios/RCTMGL/CameraUpdateQueue.m | 5 ++ ios/RCTMGL/RCTMGLMapView.h | 6 +++ ios/RCTMGL/RCTMGLMapView.m | 39 +++++++++++++++ ios/RCTMGL/RCTMGLMapViewManager.m | 19 ++----- ios/RCTMGL/RCTMGLUtils.h | 1 + ios/RCTMGL/RCTMGLUtils.m | 5 ++ ios/RCTMGL/ViewManager.m | 4 +- javascript/components/MapView.js | 11 ++++ 17 files changed, 162 insertions(+), 21 deletions(-) create mode 100644 android/rctmgl/src/main/java/com/mapbox/rctmgl/components/AbstractMapFeature.java create mode 100644 example/src/components/YoYo.js diff --git a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/AbstractEventEmitter.java b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/AbstractEventEmitter.java index 9b75b56..566dd34 100644 --- a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/AbstractEventEmitter.java +++ b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/AbstractEventEmitter.java @@ -19,7 +19,7 @@ import com.mapbox.rctmgl.events.IEvent; */ abstract public class AbstractEventEmitter extends ViewGroupManager { - private static final double BRIDGE_TIMEOUT_MS = 100; + private static final double BRIDGE_TIMEOUT_MS = 10; private Map mRateLimitedEvents; private ReactApplicationContext mRCTAppContext; diff --git a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/AbstractMapFeature.java b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/AbstractMapFeature.java new file mode 100644 index 0000000..a06f8fe --- /dev/null +++ b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/AbstractMapFeature.java @@ -0,0 +1,19 @@ +package com.mapbox.rctmgl.components; + +import android.content.Context; + +import com.facebook.react.views.view.ReactViewGroup; +import com.mapbox.rctmgl.components.mapview.RCTMGLMapView; + +/** + * Created by nickitaliano on 9/6/17. + */ + +public abstract class AbstractMapFeature extends ReactViewGroup { + public AbstractMapFeature(Context context) { + super(context); + } + + public abstract void addToMap(RCTMGLMapView mapView); + public abstract void removeFromMap(RCTMGLMapView mapView); +} diff --git a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/camera/CameraUpdateItem.java b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/camera/CameraUpdateItem.java index 065b7d0..2d8d5f4 100644 --- a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/camera/CameraUpdateItem.java +++ b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/camera/CameraUpdateItem.java @@ -51,7 +51,6 @@ public class CameraUpdateItem { } } - private void handleCallbackResponse(OnCameraCompleteListener listener, boolean isCancel) { listener.onComplete(); diff --git a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/camera/CameraUpdateQueue.java b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/camera/CameraUpdateQueue.java index b4d79b8..0d9055a 100644 --- a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/camera/CameraUpdateQueue.java +++ b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/camera/CameraUpdateQueue.java @@ -2,6 +2,7 @@ package com.mapbox.rctmgl.components.camera; import com.mapbox.mapboxsdk.maps.MapboxMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; @@ -33,6 +34,12 @@ public class CameraUpdateQueue { return mQueue.isEmpty(); } + public void flush() { + while (!mQueue.isEmpty()) { + mQueue.remove(); + } + } + public void setOnCompleteAllListener(OnCompleteAllListener listener) { mCompleteListener = listener; } diff --git a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.java b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.java index 0916f71..92d207b 100644 --- a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.java +++ b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.java @@ -285,6 +285,9 @@ public class RCTMGLMapView extends MapView implements final IEvent event = new MapChangeEvent(this, EventTypes.SET_CAMERA_COMPLETE); final SimpleEventCallback callback = new SimpleEventCallback(mManager, event); + // remove any current camera updates + mCameraUpdateQueue.flush(); + if (args.hasKey("stops")) { ReadableArray stops = args.getArray("stops"); diff --git a/example/src/App.js b/example/src/App.js index 55ceeb1..d118896 100644 --- a/example/src/App.js +++ b/example/src/App.js @@ -32,6 +32,7 @@ import FlyTo from './components/FlyTo'; import FitBounds from './components/FitBounds'; import SetUserTrackingModes from './components/SetUserTrackingModes'; import ShowRegionDidChange from './components/ShowRegionDidChange'; +import YoYo from './components/YoYo'; const styles = StyleSheet.create({ header: { @@ -78,6 +79,7 @@ const Examples = [ new ExampleItem('Fit Bounds', FitBounds), new ExampleItem('Set User Tracking Modes', SetUserTrackingModes), new ExampleItem('Show Region Did Change', ShowRegionDidChange), + new ExampleItem('Yo Yo Camera', YoYo), ]; class App extends React.Component { diff --git a/example/src/components/YoYo.js b/example/src/components/YoYo.js new file mode 100644 index 0000000..6ea7ef2 --- /dev/null +++ b/example/src/components/YoYo.js @@ -0,0 +1,50 @@ +import React from 'react'; +import MapboxGL from 'react-native-mapbox-gl'; + +import BaseExamplePropTypes from './common/BaseExamplePropTypes'; +import Page from './common/Page'; + +import sheet from '../styles/sheet'; +import { DEFAULT_CENTER_COORDINATE } from '../utils'; + +class YoYo extends React.Component { + static propTypes = { + ...BaseExamplePropTypes, + }; + + constructor (props) { + super(props); + + this.state = { + zoomLevel: 16, + }; + + this.onUpdateZoomLevel = this.onUpdateZoomLevel.bind(this); + } + + componentDidMount () { + this.map.zoomTo(this.state.zoomLevel, 4000); + } + + onUpdateZoomLevel () { + const nextZoomLevel = this.state.zoomLevel === 16 ? 1 : 16; + this.setState({ zoomLevel: nextZoomLevel }); + this.map.zoomTo(nextZoomLevel, 4000); + } + + render () { + return ( + + this.map = ref} + style={sheet.matchParent} + styleURL={MapboxGL.StyleURL.Outside} /> + + ); + } +} + +export default YoYo; diff --git a/ios/RCTMGL/CameraUpdateItem.m b/ios/RCTMGL/CameraUpdateItem.m index b2b033b..8773caa 100644 --- a/ios/RCTMGL/CameraUpdateItem.m +++ b/ios/RCTMGL/CameraUpdateItem.m @@ -87,10 +87,14 @@ nextCamera.heading = [_cameraStop.heading floatValue]; } + if (_cameraStop.zoom != nil) { + nextCamera.altitude = [mapView altitudeFromZoom:[_cameraStop.zoom doubleValue]]; + } + if ([self _isCoordValid:_cameraStop.coordinate]) { nextCamera.centerCoordinate = _cameraStop.coordinate; } - + return nextCamera; } diff --git a/ios/RCTMGL/CameraUpdateQueue.h b/ios/RCTMGL/CameraUpdateQueue.h index c8c77e6..78734c5 100644 --- a/ios/RCTMGL/CameraUpdateQueue.h +++ b/ios/RCTMGL/CameraUpdateQueue.h @@ -13,7 +13,8 @@ @interface CameraUpdateQueue : NSObject - (void)enqueue:(CameraStop* _Nonnull)cameraUpdateItem; -- (CameraStop* _Nonnull)pop; +- (CameraStop* _Nonnull)dequeue; +- (void)flush; - (BOOL)isEmpty; - (void)execute:(RCTMGLMapView* _Nonnull)mapView withCompletionHandler:(nullable void (^)(void))completionHandler; diff --git a/ios/RCTMGL/CameraUpdateQueue.m b/ios/RCTMGL/CameraUpdateQueue.m index 320a28e..6cc0552 100644 --- a/ios/RCTMGL/CameraUpdateQueue.m +++ b/ios/RCTMGL/CameraUpdateQueue.m @@ -37,6 +37,11 @@ return stop; } +- (void)flush +{ + [queue removeAllObjects]; +} + - (BOOL)isEmpty { return queue.count == 0; diff --git a/ios/RCTMGL/RCTMGLMapView.h b/ios/RCTMGL/RCTMGLMapView.h index f1689b7..bf0be76 100644 --- a/ios/RCTMGL/RCTMGLMapView.h +++ b/ios/RCTMGL/RCTMGLMapView.h @@ -9,8 +9,11 @@ #import @import Mapbox; +@class CameraUpdateQueue; @interface RCTMGLMapView : MGLMapView +@property (nonatomic, strong) CameraUpdateQueue *cameraUpdateQueue; + @property (nonatomic, assign) BOOL animated; @property (nonatomic, assign) BOOL reactScrollEnabled; @property (nonatomic, assign) BOOL reactPitchEnabled; @@ -32,4 +35,7 @@ @property (nonatomic, copy) RCTBubblingEventBlock onMapChange; @property (nonatomic, copy) RCTBubblingEventBlock onUserLocationChange; +- (CLLocationDistance)getMetersPerPixelAtLatitude:(double)latitude withZoom:(double)zoomLevel; +- (CLLocationDistance)altitudeFromZoom:(double)zoomLevel; + @end diff --git a/ios/RCTMGL/RCTMGLMapView.m b/ios/RCTMGL/RCTMGLMapView.m index 0467fd8..f084d36 100644 --- a/ios/RCTMGL/RCTMGLMapView.m +++ b/ios/RCTMGL/RCTMGLMapView.m @@ -7,11 +7,26 @@ // #import "RCTMGLMapView.h" +#import "CameraUpdateQueue.h" #import "RCTMGLUtils.h" #import "UIView+React.h" @implementation RCTMGLMapView +static double const DEG2RAD = M_PI / 180; +static double const LAT_MAX = 85.051128779806604; +static double const TILE_SIZE = 256; +static double const EARTH_RADIUS_M = 6378137; +static double const M2PI = M_PI * 2; + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + _cameraUpdateQueue = [[CameraUpdateQueue alloc] init]; + } + return self; +} + - (void)setReactScrollEnabled:(BOOL)reactScrollEnabled { _reactScrollEnabled = reactScrollEnabled; @@ -78,6 +93,30 @@ [self setUserTrackingMode:(NSUInteger)_reactUserTrackingMode animated:_animated]; } +#pragma mark - methods + +- (CLLocationDistance)getMetersPerPixelAtLatitude:(double)latitude withZoom:(double)zoomLevel +{ + double constrainedZoom = [[RCTMGLUtils clamp:[NSNumber numberWithDouble:zoomLevel] + min:[NSNumber numberWithDouble:self.minimumZoomLevel] + max:[NSNumber numberWithDouble:self.maximumZoomLevel]] doubleValue]; + + double constrainedLatitude = [[RCTMGLUtils clamp:[NSNumber numberWithDouble:latitude] + min:[NSNumber numberWithDouble:-LAT_MAX] + max:[NSNumber numberWithDouble:LAT_MAX]] doubleValue]; + + double constrainedScale = pow(2.0, constrainedZoom); + return cos(constrainedLatitude * DEG2RAD) * M2PI * EARTH_RADIUS_M / (constrainedScale * TILE_SIZE); +} + +- (CLLocationDistance)altitudeFromZoom:(double)zoomLevel +{ + CLLocationDistance metersPerPixel = [self getMetersPerPixelAtLatitude:self.camera.centerCoordinate.latitude withZoom:zoomLevel]; + CLLocationDistance metersTall = metersPerPixel * self.frame.size.height; + CLLocationDistance altitude = metersTall / 2 / tan(MGLRadiansFromDegrees(30) / 2.0); + return altitude * sin(M_PI_2 - MGLRadiansFromDegrees(self.camera.pitch)) / sin(M_PI_2); +} + - (NSURL*)_getStyleURLFromKey:(NSString *)styleURL { return [NSURL URLWithString:styleURL]; diff --git a/ios/RCTMGL/RCTMGLMapViewManager.m b/ios/RCTMGL/RCTMGLMapViewManager.m index fde1178..c32130e 100644 --- a/ios/RCTMGL/RCTMGLMapViewManager.m +++ b/ios/RCTMGL/RCTMGLMapViewManager.m @@ -22,9 +22,6 @@ @implementation RCTMGLMapViewManager -{ - CameraUpdateQueue *cameraUpdateQueue; -} // prevents SDK from crashing and cluttering logs // since we don't have access to the frame right away @@ -32,15 +29,6 @@ static CGRect const RCT_MAPBOX_MIN_MAP_FRAME = { { 0.0f, 0.0f }, { 64.0f, 64.0f RCT_EXPORT_MODULE() -- (instancetype)init -{ - if (self = [super init]) { - cameraUpdateQueue = [[CameraUpdateQueue alloc] init]; - } - - return self; -} - - (UIView *)view { RCTMGLMapView *mapView = [[RCTMGLMapView alloc] initWithFrame:RCT_MAPBOX_MIN_MAP_FRAME]; @@ -92,18 +80,19 @@ RCT_EXPORT_METHOD(setCamera:(nonnull NSNumber*)reactTag } __weak RCTMGLMapView *reactMapView = (RCTMGLMapView*)view; + [reactMapView.cameraUpdateQueue flush]; // remove any curreny camera updates if (config[@"stops"]) { NSArray *stops = (NSArray*)config[@"stops"]; for (int i = 0; i < stops.count; i++) { - [cameraUpdateQueue enqueue:[CameraStop fromDictionary:stops[i]]]; + [reactMapView.cameraUpdateQueue enqueue:[CameraStop fromDictionary:stops[i]]]; } } else { - [cameraUpdateQueue enqueue:[CameraStop fromDictionary:config]]; + [reactMapView.cameraUpdateQueue enqueue:[CameraStop fromDictionary:config]]; } - [cameraUpdateQueue execute:reactMapView withCompletionHandler:^{ + [reactMapView.cameraUpdateQueue execute:reactMapView withCompletionHandler:^{ [self reactMapDidChange:reactMapView eventType:RCT_MAPBOX_SET_CAMERA_COMPLETE]; }]; }]; diff --git a/ios/RCTMGL/RCTMGLUtils.h b/ios/RCTMGL/RCTMGLUtils.h index 17c6410..b5a361e 100644 --- a/ios/RCTMGL/RCTMGLUtils.h +++ b/ios/RCTMGL/RCTMGLUtils.h @@ -14,5 +14,6 @@ + (CLLocationCoordinate2D)fromFeature:(NSString*)json; + (MGLCoordinateBounds)fromFeatureCollection:(NSString*)json; + (NSTimeInterval)fromMS:(NSNumber*)number; ++ (NSNumber*)clamp:(NSNumber*)value min:(NSNumber*)min max:(NSNumber*)max; @end diff --git a/ios/RCTMGL/RCTMGLUtils.m b/ios/RCTMGL/RCTMGLUtils.m index 8f57654..0857390 100644 --- a/ios/RCTMGL/RCTMGLUtils.m +++ b/ios/RCTMGL/RCTMGLUtils.m @@ -36,4 +36,9 @@ static double const MS_TO_S = 0.001; return [number doubleValue] * MS_TO_S; } ++ (NSNumber*)clamp:(NSNumber *)value min:(NSNumber *)min max:(NSNumber *)max +{ + return MAX(MIN(value, max), min); +} + @end diff --git a/ios/RCTMGL/ViewManager.m b/ios/RCTMGL/ViewManager.m index 3dd79fc..347512a 100644 --- a/ios/RCTMGL/ViewManager.m +++ b/ios/RCTMGL/ViewManager.m @@ -13,7 +13,7 @@ NSMutableDictionary *eventTimestampCache; } -static NSTimeInterval EVENT_THROTTLE_S = 0.1; +static NSTimeInterval EVENT_THROTTLE_S = 0.01; - (instancetype)init { @@ -26,7 +26,7 @@ static NSTimeInterval EVENT_THROTTLE_S = 0.1; - (void)fireEvent:(RCTMGLEvent*)event withCallback:(RCTBubblingEventBlock)callback { - if (![self _shouldDropEvent:event]) { + if (YES || ![self _shouldDropEvent:event]) { NSString *cacheKey = [self _getCacheKey:event]; NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; [eventTimestampCache setObject:[NSNumber numberWithDouble:now] forKey:cacheKey]; diff --git a/javascript/components/MapView.js b/javascript/components/MapView.js index 04edfb6..58a69d0 100644 --- a/javascript/components/MapView.js +++ b/javascript/components/MapView.js @@ -225,6 +225,17 @@ class MapView extends React.Component { }); } + zoomTo (zoomLevel, duration = 2000) { + if (!this._nativeRef) { + return; + } + this.setCamera({ + zoom: zoomLevel, + duration: duration, + mode: MapboxGL.CameraModes.Flight, + }); + } + setCamera (config = {}) { if (!this._nativeRef) { return;