Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get visible map bounding box #2571

Merged
merged 11 commits into from Nov 22, 2018
3 changes: 2 additions & 1 deletion docs/mapview.md
Expand Up @@ -76,6 +76,7 @@ To access event data, you will need to use `e.nativeEvent`. For example, `onPres
| `animateToCoordinate` | `coordinate: LatLng`, `duration: Number` |
| `animateToBearing` | `bearing: Number`, `duration: Number` |
| `animateToViewingAngle` | `angle: Number`, `duration: Number` |
| `getMapBoundaries` | | `Promise<{northEast: LatLng, southWest: LatLng}>`
| `setMapBoundaries` | `northEast: LatLng`, `southWest: LatLng` | `GoogleMaps only`
| `setIndoorActiveLevelIndex` | `levelIndex: Number` |
| `fitToElements` | `animated: Boolean` |
Expand Down Expand Up @@ -179,4 +180,4 @@ type IndoorLevel {
name: String,
shortName: String,
}
```
```
2 changes: 2 additions & 0 deletions example/App.js
Expand Up @@ -25,6 +25,7 @@ import DefaultMarkers from './examples/DefaultMarkers';
import CustomMarkers from './examples/CustomMarkers';
import CachedMap from './examples/CachedMap';
import LoadingMap from './examples/LoadingMap';
import MapBoundaries from './examples/MapBoundaries';
import TakeSnapshot from './examples/TakeSnapshot';
import FitToSuppliedMarkers from './examples/FitToSuppliedMarkers';
import FitToCoordinates from './examples/FitToCoordinates';
Expand Down Expand Up @@ -148,6 +149,7 @@ class App extends React.Component {
[TakeSnapshot, 'Take Snapshot', true, '(incomplete)'],
[CachedMap, 'Cached Map'],
[LoadingMap, 'Map with loading'],
[MapBoundaries, 'Get visible map boundaries', true],
[FitToSuppliedMarkers, 'Focus Map On Markers', true],
[FitToCoordinates, 'Fit Map To Coordinates', true],
[LiteMapView, 'Android Lite MapView'],
Expand Down
93 changes: 93 additions & 0 deletions example/examples/MapBoundaries.js
@@ -0,0 +1,93 @@
import React from 'react';
import { StyleSheet, View, Text, Dimensions } from 'react-native';

import MapView, { ProviderPropType } from 'react-native-maps';

const { width, height } = Dimensions.get('window');

const ASPECT_RATIO = width / height;
const LATITUDE = 37.78825;
const LONGITUDE = -122.4324;
const LATITUDE_DELTA = 0.0922;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;

class MapBoundaries extends React.Component {
constructor(props) {
super(props);

this.state = {
region: {
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
},
mapBoundaries: null,
};
}

async onRegionChangeComplete() {
this.setState({
mapBoundaries: await this.map.getMapBoundaries(),
});
}

render() {
return (
<View style={styles.container}>
<MapView
ref={ref => {
this.map = ref;
}}
provider={this.props.provider}
style={styles.map}
initialRegion={this.state.region}
onRegionChangeComplete={() => this.onRegionChangeComplete()}
/>
<View style={styles.buttonContainer}>
<View style={styles.bubble}>
<Text>{JSON.stringify(this.state.mapBoundaries)}</Text>
</View>
</View>
</View>
);
}
}

MapBoundaries.propTypes = {
provider: ProviderPropType,
};

const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
justifyContent: 'flex-end',
alignItems: 'center',
},
map: {
...StyleSheet.absoluteFillObject,
},
bubble: {
backgroundColor: 'rgba(255,255,255,0.7)',
paddingHorizontal: 18,
paddingVertical: 12,
borderRadius: 20,
},
latlng: {
width: 200,
alignItems: 'stretch',
},
button: {
width: 80,
paddingHorizontal: 12,
alignItems: 'center',
marginHorizontal: 10,
},
buttonContainer: {
flexDirection: 'row',
marginVertical: 20,
backgroundColor: 'transparent',
},
});

export default MapBoundaries;
Expand Up @@ -215,4 +215,43 @@ public void execute(NativeViewHierarchyManager nvhm)
}
});
}

@ReactMethod
public void getMapBoundaries(final int tag, final Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();

UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
uiManager.addUIBlock(new UIBlock()
{
@Override
public void execute(NativeViewHierarchyManager nvhm)
{
AirMapView view = (AirMapView) nvhm.resolveView(tag);
if (view == null) {
promise.reject("AirMapView not found");
return;
}
if (view.map == null) {
promise.reject("AirMapView.map is not valid");
return;
}

double[][] boundaries = view.getMapBoundaries();

WritableMap coordinates = new WritableNativeMap();
WritableMap northEastHash = new WritableNativeMap();
WritableMap southWestHash = new WritableNativeMap();

northEastHash.putDouble("longitude", boundaries[0][0]);
northEastHash.putDouble("latitude", boundaries[0][1]);
southWestHash.putDouble("longitude", boundaries[1][0]);
southWestHash.putDouble("latitude", boundaries[1][1]);

coordinates.putMap("northEast", northEastHash);
coordinates.putMap("southWest", southWestHash);

promise.resolve(coordinates);
}
});
}
}
Expand Up @@ -787,6 +787,17 @@ public void fitToCoordinates(ReadableArray coordinatesArray, ReadableMap edgePad
0); // Without this, the Google logo is moved up by the value of edgePadding.bottom
}

public double[][] getMapBoundaries() {
LatLngBounds bounds = map.getProjection().getVisibleRegion().latLngBounds;
LatLng northEast = bounds.northeast;
LatLng southWest = bounds.southwest;

return new double[][] {
{northEast.longitude, northEast.latitude},
{southWest.longitude, southWest.latitude}
};
}

public void setMapBoundaries(ReadableMap northEast, ReadableMap southWest) {
if (map == null) return;

Expand Down
14 changes: 14 additions & 0 deletions lib/components/MapView.js
Expand Up @@ -600,6 +600,20 @@ class MapView extends React.Component {
this._runCommand('fitToCoordinates', [coordinates, edgePadding, animated]);
}

/**
* Get visible boudaries
*
* @return Promise Promise with the bounding box ({ northEast: <LatLng>, southWest: <LatLng> })
*/
async getMapBoundaries() {
if (Platform.OS === 'android') {
return NativeModules.AirMapModule.getMapBoundaries(this._getHandle());
} else if (Platform.OS === 'ios') {
return this._runCommand('getMapBoundaries', []);
}
return Promise.reject('getMapBoundaries not supported on this platform');
}

setMapBoundaries(northEast, southWest) {
this._runCommand('setMapBoundaries', [northEast, southWest]);
}
Expand Down
1 change: 1 addition & 0 deletions lib/ios/AirGoogleMaps/AIRGoogleMap.h
Expand Up @@ -65,6 +65,7 @@
- (void)didChangeCameraPosition:(GMSCameraPosition *)position;
- (void)idleAtCameraPosition:(GMSCameraPosition *)position;
- (void)didTapPOIWithPlaceID:(NSString *)placeID name:(NSString *) name location:(CLLocationCoordinate2D) location;
- (NSArray *)getMapBoundaries;

+ (MKCoordinateRegion)makeGMSCameraPositionFromMap:(GMSMapView *)map andGMSCameraPosition:(GMSCameraPosition *)position;
+ (GMSCameraPosition*)makeGMSCameraPositionFromMap:(GMSMapView *)map andMKCoordinateRegion:(MKCoordinateRegion)region;
Expand Down
20 changes: 20 additions & 0 deletions lib/ios/AirGoogleMaps/AIRGoogleMap.m
Expand Up @@ -195,6 +195,26 @@ - (void)removeReactSubview:(id<RCTComponent>)subview {
}
#pragma clang diagnostic pop

- (NSArray *)getMapBoundaries
{
GMSVisibleRegion visibleRegion = self.projection.visibleRegion;
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithRegion:visibleRegion];

CLLocationCoordinate2D northEast = bounds.northEast;
CLLocationCoordinate2D southWest = bounds.southWest;

return @[
@[
[NSNumber numberWithDouble:northEast.longitude],
[NSNumber numberWithDouble:northEast.latitude]
],
@[
[NSNumber numberWithDouble:southWest.longitude],
[NSNumber numberWithDouble:southWest.latitude]
]
];
}

- (void)didMoveToWindow {
if (_didMoveToWindow) return;
_didMoveToWindow = true;
Expand Down
25 changes: 25 additions & 0 deletions lib/ios/AirGoogleMaps/AIRGoogleMapManager.m
Expand Up @@ -391,6 +391,31 @@ - (UIView *)view
}];
}

RCT_EXPORT_METHOD(getMapBoundaries:(nonnull NSNumber *)reactTag
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
} else {
NSArray *boundingBox = [view getMapBoundaries];

resolve(@{
@"northEast" : @{
@"longitude" : boundingBox[0][0],
@"latitude" : boundingBox[0][1]
},
@"southWest" : @{
@"longitude" : boundingBox[1][0],
@"latitude" : boundingBox[1][1]
}
});
}
}];
}

RCT_EXPORT_METHOD(setMapBoundaries:(nonnull NSNumber *)reactTag
northEast:(CLLocationCoordinate2D)northEast
southWest:(CLLocationCoordinate2D)southWest)
Expand Down
1 change: 1 addition & 0 deletions lib/ios/AirMaps/AIRMap.h
Expand Up @@ -64,5 +64,6 @@ extern const NSInteger AIRMapMaxZoomLevel;
- (void)cacheViewIfNeeded;
- (void)beginLoading;
- (void)finishLoading;
- (NSArray *)getMapBoundaries;

@end
19 changes: 19 additions & 0 deletions lib/ios/AirMaps/AIRMap.m
Expand Up @@ -223,6 +223,25 @@ - (NSTimeInterval)calloutView:(SMCalloutView *)calloutView delayForRepositionWit

#pragma mark Accessors

- (NSArray *)getMapBoundaries
{
MKMapRect mapRect = self.visibleMapRect;

CLLocationCoordinate2D northEast = MKCoordinateForMapPoint(MKMapPointMake(MKMapRectGetMaxX(mapRect), mapRect.origin.y));
CLLocationCoordinate2D southWest = MKCoordinateForMapPoint(MKMapPointMake(mapRect.origin.x, MKMapRectGetMaxY(mapRect)));

return @[
@[
[NSNumber numberWithDouble:northEast.longitude],
[NSNumber numberWithDouble:northEast.latitude]
],
@[
[NSNumber numberWithDouble:southWest.longitude],
[NSNumber numberWithDouble:southWest.latitude]
]
];
}

- (void)setShowsUserLocation:(BOOL)showsUserLocation
{
if (self.showsUserLocation != showsUserLocation) {
Expand Down
35 changes: 30 additions & 5 deletions lib/ios/AirMaps/AIRMapManager.m
Expand Up @@ -40,7 +40,7 @@ @interface AIRMapManager() <MKMapViewDelegate>
@implementation AIRMapManager{
BOOL _hasObserver;
}

RCT_EXPORT_MODULE()

- (UIView *)view
Expand Down Expand Up @@ -131,6 +131,31 @@ - (UIView *)view

#pragma mark exported MapView methods

RCT_EXPORT_METHOD(getMapBoundaries:(nonnull NSNumber *)reactTag
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
} else {
NSArray *boundingBox = [view getMapBoundaries];

resolve(@{
@"northEast" : @{
@"longitude" : boundingBox[0][0],
@"latitude" : boundingBox[0][1]
},
@"southWest" : @{
@"longitude" : boundingBox[1][0],
@"latitude" : boundingBox[1][1]
}
});
}
}];
}

RCT_EXPORT_METHOD(animateToNavigation:(nonnull NSNumber *)reactTag
withRegion:(MKCoordinateRegion)region
withBearing:(CGFloat)bearing
Expand Down Expand Up @@ -272,7 +297,7 @@ - (UIView *)view
NSArray *filteredMarkers = [mapView.annotations filteredArrayUsingPredicate:filterMarkers];

[mapView showAnnotations:filteredMarkers animated:animated];

}
}];
}
Expand Down Expand Up @@ -362,7 +387,7 @@ - (UIView *)view
[coordinate[@"longitude"] doubleValue]
)
toPointToView:mapView];

resolve(@{
@"x": @(touchPoint.x),
@"y": @(touchPoint.y),
Expand All @@ -388,7 +413,7 @@ - (UIView *)view
[point[@"y"] doubleValue]
)
toCoordinateFromView:mapView];

resolve(@{
@"latitude": @(coordinate.latitude),
@"longitude": @(coordinate.longitude),
Expand Down Expand Up @@ -538,7 +563,7 @@ - (void)handleMapTap:(UITapGestureRecognizer *)recognizer {
}
}
}

if ([overlay isKindOfClass:[AIRMapOverlay class]]) {
AIRMapOverlay *imageOverlay = (AIRMapOverlay*) overlay;
if (MKMapRectContainsPoint(imageOverlay.boundingMapRect, mapPoint)) {
Expand Down