@@ -97,6 +97,9 @@ - (UIView *)view
9797RCT_EXPORT_VIEW_PROPERTY(onMarkerDragEnd, RCTDirectEventBlock)
9898RCT_EXPORT_VIEW_PROPERTY(onCalloutPress, RCTDirectEventBlock)
9999RCT_EXPORT_VIEW_PROPERTY(initialRegion, MKCoordinateRegion)
100+ RCT_EXPORT_VIEW_PROPERTY(minZoomLevel, CGFloat)
101+ RCT_EXPORT_VIEW_PROPERTY(maxZoomLevel, CGFloat)
102+
100103
101104RCT_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