Skip to content

Commit 681e7d2

Browse files
foyarashpatricio
authored andcommitted
Add minZoom and maxZoom properties for android and ios (react-native-maps#1360)
* Add minZoom and maxZoom properties for android and ios * Add min/max zoom level properties to AIRMap.h * Fix assignation for zoom levels for gmaps ios * Resolve conflicts, fix potential nil value on ios * Resolve conflicts
1 parent f9daee2 commit 681e7d2

File tree

7 files changed

+216
-0
lines changed

7 files changed

+216
-0
lines changed

lib/android/src/main/java/com/airbnb/android/react/maps/AirMapManager.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,16 @@ public void setPitchEnabled(AirMapView view, boolean pitchEnabled) {
178178
view.map.getUiSettings().setTiltGesturesEnabled(pitchEnabled);
179179
}
180180

181+
@ReactProp(name = "minZoomLevel")
182+
public void setMinZoomLevel(AirMapView view, float minZoomLevel) {
183+
view.map.setMinZoomPreference(minZoomLevel);
184+
}
185+
186+
@ReactProp(name = "maxZoomLevel")
187+
public void setMaxZoomLevel(AirMapView view, float maxZoomLevel) {
188+
view.map.setMaxZoomPreference(maxZoomLevel);
189+
}
190+
181191
@Override
182192
public void receiveCommand(AirMapView view, int commandId, @Nullable ReadableArray args) {
183193
Integer duration;

lib/components/MapView.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,16 @@ const propTypes = {
385385
*/
386386
onMarkerDragEnd: PropTypes.func,
387387

388+
/**
389+
* Minimum zoom value for the map, must be between 0 and 20
390+
*/
391+
minZoomLevel: PropTypes.number,
392+
393+
/**
394+
* Maximum zoom value for the map, must be between 0 and 20
395+
*/
396+
maxZoomLevel: PropTypes.number,
397+
388398
};
389399

390400
class MapView extends React.Component {

lib/ios/AirGoogleMaps/AIRGoogleMap.m

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,13 @@ - (BOOL)showsMyLocationButton {
300300
return self.settings.myLocationButton;
301301
}
302302

303+
- (void)setMinZoomLevel:(CGFloat)minZoomLevel {
304+
[self setMinZoom:minZoomLevel maxZoom:self.maxZoom ];
305+
}
306+
307+
- (void)setMaxZoomLevel:(CGFloat)maxZoomLevel {
308+
[self setMinZoom:self.minZoom maxZoom:maxZoomLevel ];
309+
}
303310

304311
+ (MKCoordinateRegion) makeGMSCameraPositionFromMap:(GMSMapView *)map andGMSCameraPosition:(GMSCameraPosition *)position {
305312
// solution from here: http://stackoverflow.com/a/16587735/1102215

lib/ios/AirGoogleMaps/AIRGoogleMapManager.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ - (UIView *)view
6666
RCT_EXPORT_VIEW_PROPERTY(onRegionChange, RCTDirectEventBlock)
6767
RCT_EXPORT_VIEW_PROPERTY(onRegionChangeComplete, RCTDirectEventBlock)
6868
RCT_EXPORT_VIEW_PROPERTY(mapType, GMSMapViewType)
69+
RCT_EXPORT_VIEW_PROPERTY(minZoomLevel, CGFloat)
70+
RCT_EXPORT_VIEW_PROPERTY(maxZoomLevel, CGFloat)
6971

7072
RCT_EXPORT_METHOD(animateToRegion:(nonnull NSNumber *)reactTag
7173
withRegion:(MKCoordinateRegion)region

lib/ios/AirMaps/AIRMap.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ extern const CGFloat AIRMapZoomBoundBuffer;
3737
@property (nonatomic, assign) UIEdgeInsets legalLabelInsets;
3838
@property (nonatomic, strong) NSTimer *regionChangeObserveTimer;
3939
@property (nonatomic, assign) MKCoordinateRegion initialRegion;
40+
@property (nonatomic, assign) CGFloat minZoomLevel;
41+
@property (nonatomic, assign) CGFloat maxZoomLevel;
4042

4143
@property (nonatomic, assign) CLLocationCoordinate2D pendingCenter;
4244
@property (nonatomic, assign) MKCoordinateSpan pendingSpan;

lib/ios/AirMaps/AIRMapManager.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,23 @@
88
*/
99

1010
#import <React/RCTViewManager.h>
11+
#import "AIRMap.h"
12+
13+
#define MERCATOR_RADIUS 85445659.44705395
14+
#define MERCATOR_OFFSET 268435456
15+
#define MAX_GOOGLE_LEVELS 20
1116

1217
@interface AIRMapManager : RCTViewManager
1318

19+
20+
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
21+
zoomLevel:(double)zoomLevel
22+
animated:(BOOL)animated
23+
mapView:(AIRMap *)mapView;
24+
25+
- (MKCoordinateRegion)coordinateRegionWithMapView:(AIRMap *)mapView
26+
centerCoordinate:(CLLocationCoordinate2D)centerCoordinate
27+
andZoomLevel:(double)zoomLevel;
28+
- (double) zoomLevel:(AIRMap *)mapView;
29+
1430
@end

lib/ios/AirMaps/AIRMapManager.m

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ - (UIView *)view
9797
RCT_EXPORT_VIEW_PROPERTY(onMarkerDragEnd, RCTDirectEventBlock)
9898
RCT_EXPORT_VIEW_PROPERTY(onCalloutPress, RCTDirectEventBlock)
9999
RCT_EXPORT_VIEW_PROPERTY(initialRegion, MKCoordinateRegion)
100+
RCT_EXPORT_VIEW_PROPERTY(minZoomLevel, CGFloat)
101+
RCT_EXPORT_VIEW_PROPERTY(maxZoomLevel, CGFloat)
102+
100103

101104
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, AIRMap)
102105
{
@@ -625,11 +628,19 @@ - (void)mapView:(AIRMap *)mapView regionWillChangeAnimated:(__unused BOOL)animat
625628

626629
- (void)mapView:(AIRMap *)mapView regionDidChangeAnimated:(__unused BOOL)animated
627630
{
631+
CGFloat zoomLevel = [self zoomLevel:mapView];
628632
[mapView.regionChangeObserveTimer invalidate];
629633
mapView.regionChangeObserveTimer = nil;
630634

631635
[self _regionChanged:mapView];
632636

637+
if (mapView.minZoomLevel != nil && zoomLevel < mapView.minZoomLevel) {
638+
[self setCenterCoordinate:[mapView centerCoordinate] zoomLevel:mapView.minZoomLevel animated:TRUE mapView:mapView];
639+
}
640+
else if (mapView.maxZoomLevel != nil && zoomLevel > mapView.maxZoomLevel) {
641+
[self setCenterCoordinate:[mapView centerCoordinate] zoomLevel:mapView.maxZoomLevel animated:TRUE mapView:mapView];
642+
}
643+
633644
// Don't send region did change events until map has
634645
// started rendering, as these won't represent the final location
635646
if (mapView.hasStartedRendering) {
@@ -665,6 +676,7 @@ - (void)_regionChanged:(AIRMap *)mapView
665676
BOOL needZoom = NO;
666677
CGFloat newLongitudeDelta = 0.0f;
667678
MKCoordinateRegion region = mapView.region;
679+
CGFloat zoomLevel = [self zoomLevel:mapView];
668680
// On iOS 7, it's possible that we observe invalid locations during initialization of the map.
669681
// Filter those out.
670682
if (!CLLocationCoordinate2DIsValid(region.center)) {
@@ -759,4 +771,161 @@ - (double)metersFromPixel:(NSUInteger)px atPoint:(CGPoint)pt forMap:(AIRMap *)ma
759771
return MKMetersBetweenMapPoints(MKMapPointForCoordinate(coordA), MKMapPointForCoordinate(coordB));
760772
}
761773

774+
+ (double)longitudeToPixelSpaceX:(double)longitude
775+
{
776+
return round(MERCATOR_OFFSET + MERCATOR_RADIUS * longitude * M_PI / 180.0);
777+
}
778+
779+
+ (double)latitudeToPixelSpaceY:(double)latitude
780+
{
781+
if (latitude == 90.0) {
782+
return 0;
783+
} else if (latitude == -90.0) {
784+
return MERCATOR_OFFSET * 2;
785+
} else {
786+
return round(MERCATOR_OFFSET - MERCATOR_RADIUS * logf((1 + sinf(latitude * M_PI / 180.0)) / (1 - sinf(latitude * M_PI / 180.0))) / 2.0);
787+
}
788+
}
789+
790+
+ (double)pixelSpaceXToLongitude:(double)pixelX
791+
{
792+
return ((round(pixelX) - MERCATOR_OFFSET) / MERCATOR_RADIUS) * 180.0 / M_PI;
793+
}
794+
795+
+ (double)pixelSpaceYToLatitude:(double)pixelY
796+
{
797+
return (M_PI / 2.0 - 2.0 * atan(exp((round(pixelY) - MERCATOR_OFFSET) / MERCATOR_RADIUS))) * 180.0 / M_PI;
798+
}
799+
800+
#pragma mark -
801+
#pragma mark Helper methods
802+
803+
- (MKCoordinateSpan)coordinateSpanWithMapView:(AIRMap *)mapView
804+
centerCoordinate:(CLLocationCoordinate2D)centerCoordinate
805+
andZoomLevel:(double)zoomLevel
806+
{
807+
// convert center coordiate to pixel space
808+
double centerPixelX = [AIRMapManager longitudeToPixelSpaceX:centerCoordinate.longitude];
809+
double centerPixelY = [AIRMapManager latitudeToPixelSpaceY:centerCoordinate.latitude];
810+
811+
// determine the scale value from the zoom level
812+
double zoomExponent = 20 - zoomLevel;
813+
double zoomScale = pow(2, zoomExponent);
814+
815+
// scale the map’s size in pixel space
816+
CGSize mapSizeInPixels = mapView.bounds.size;
817+
double scaledMapWidth = mapSizeInPixels.width * zoomScale;
818+
double scaledMapHeight = mapSizeInPixels.height * zoomScale;
819+
820+
// figure out the position of the top-left pixel
821+
double topLeftPixelX = centerPixelX - (scaledMapWidth / 2);
822+
double topLeftPixelY = centerPixelY - (scaledMapHeight / 2);
823+
824+
// find delta between left and right longitudes
825+
CLLocationDegrees minLng = [AIRMapManager pixelSpaceXToLongitude:topLeftPixelX];
826+
CLLocationDegrees maxLng = [AIRMapManager pixelSpaceXToLongitude:topLeftPixelX + scaledMapWidth];
827+
CLLocationDegrees longitudeDelta = maxLng - minLng;
828+
829+
// find delta between top and bottom latitudes
830+
CLLocationDegrees minLat = [AIRMapManager pixelSpaceYToLatitude:topLeftPixelY];
831+
CLLocationDegrees maxLat = [AIRMapManager pixelSpaceYToLatitude:topLeftPixelY + scaledMapHeight];
832+
CLLocationDegrees latitudeDelta = -1 * (maxLat - minLat);
833+
834+
// create and return the lat/lng span
835+
MKCoordinateSpan span = MKCoordinateSpanMake(latitudeDelta, longitudeDelta);
836+
return span;
837+
}
838+
839+
#pragma mark -
840+
#pragma mark Public methods
841+
842+
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
843+
zoomLevel:(double)zoomLevel
844+
animated:(BOOL)animated
845+
mapView:(AIRMap *)mapView
846+
{
847+
// clamp large numbers to 28
848+
zoomLevel = MIN(zoomLevel, 28);
849+
850+
// use the zoom level to compute the region
851+
MKCoordinateSpan span = [self coordinateSpanWithMapView:mapView centerCoordinate:centerCoordinate andZoomLevel:zoomLevel];
852+
MKCoordinateRegion region = MKCoordinateRegionMake(centerCoordinate, span);
853+
854+
// set the region like normal
855+
[mapView setRegion:region animated:animated];
856+
}
857+
858+
//KMapView cannot display tiles that cross the pole (as these would involve wrapping the map from top to bottom, something that a Mercator projection just cannot do).
859+
-(MKCoordinateRegion)coordinateRegionWithMapView:(AIRMap *)mapView
860+
centerCoordinate:(CLLocationCoordinate2D)centerCoordinate
861+
andZoomLevel:(double)zoomLevel
862+
{
863+
// clamp lat/long values to appropriate ranges
864+
centerCoordinate.latitude = MIN(MAX(-90.0, centerCoordinate.latitude), 90.0);
865+
centerCoordinate.longitude = fmod(centerCoordinate.longitude, 180.0);
866+
867+
// convert center coordiate to pixel space
868+
double centerPixelX = [AIRMapManager longitudeToPixelSpaceX:centerCoordinate.longitude];
869+
double centerPixelY = [AIRMapManager latitudeToPixelSpaceY:centerCoordinate.latitude];
870+
871+
// determine the scale value from the zoom level
872+
double zoomExponent = 20 - zoomLevel;
873+
double zoomScale = pow(2, zoomExponent);
874+
875+
// scale the map’s size in pixel space
876+
CGSize mapSizeInPixels = mapView.bounds.size;
877+
double scaledMapWidth = mapSizeInPixels.width * zoomScale;
878+
double scaledMapHeight = mapSizeInPixels.height * zoomScale;
879+
880+
// figure out the position of the left pixel
881+
double topLeftPixelX = centerPixelX - (scaledMapWidth / 2);
882+
883+
// find delta between left and right longitudes
884+
CLLocationDegrees minLng = [AIRMapManager pixelSpaceXToLongitude:topLeftPixelX];
885+
CLLocationDegrees maxLng = [AIRMapManager pixelSpaceXToLongitude:topLeftPixelX + scaledMapWidth];
886+
CLLocationDegrees longitudeDelta = maxLng - minLng;
887+
888+
// if we’re at a pole then calculate the distance from the pole towards the equator
889+
// as MKMapView doesn’t like drawing boxes over the poles
890+
double topPixelY = centerPixelY - (scaledMapHeight / 2);
891+
double bottomPixelY = centerPixelY + (scaledMapHeight / 2);
892+
BOOL adjustedCenterPoint = NO;
893+
if (topPixelY > MERCATOR_OFFSET * 2) {
894+
topPixelY = centerPixelY - scaledMapHeight;
895+
bottomPixelY = MERCATOR_OFFSET * 2;
896+
adjustedCenterPoint = YES;
897+
}
898+
899+
// find delta between top and bottom latitudes
900+
CLLocationDegrees minLat = [AIRMapManager pixelSpaceYToLatitude:topPixelY];
901+
CLLocationDegrees maxLat = [AIRMapManager pixelSpaceYToLatitude:bottomPixelY];
902+
CLLocationDegrees latitudeDelta = -1 * (maxLat - minLat);
903+
904+
// create and return the lat/lng span
905+
MKCoordinateSpan span = MKCoordinateSpanMake(latitudeDelta, longitudeDelta);
906+
MKCoordinateRegion region = MKCoordinateRegionMake(centerCoordinate, span);
907+
// once again, MKMapView doesn’t like drawing boxes over the poles
908+
// so adjust the center coordinate to the center of the resulting region
909+
if (adjustedCenterPoint) {
910+
region.center.latitude = [AIRMapManager pixelSpaceYToLatitude:((bottomPixelY + topPixelY) / 2.0)];
911+
}
912+
913+
return region;
914+
}
915+
916+
- (double) zoomLevel:(AIRMap *)mapView {
917+
MKCoordinateRegion region = mapView.region;
918+
919+
double centerPixelX = [AIRMapManager longitudeToPixelSpaceX: region.center.longitude];
920+
double topLeftPixelX = [AIRMapManager longitudeToPixelSpaceX: region.center.longitude - region.span.longitudeDelta / 2];
921+
922+
double scaledMapWidth = (centerPixelX - topLeftPixelX) * 2;
923+
CGSize mapSizeInPixels = mapView.bounds.size;
924+
double zoomScale = scaledMapWidth / mapSizeInPixels.width;
925+
double zoomExponent = log(zoomScale) / log(2);
926+
double zoomLevel = 20 - zoomExponent;
927+
928+
return zoomLevel;
929+
}
930+
762931
@end

0 commit comments

Comments
 (0)