From 1d11624efdf2d73a8adb43310c481969ead18d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Thu, 25 Jun 2015 11:58:23 -0700 Subject: [PATCH] Test fit to bounds; added MGLCoordinateBounds functions Added a bunch of functions to work with MGLCoordinateBounds in a separate header analogous to MKGeometry.h. Added resolution-independent tests for common fit to bounds scenarios. --- gyp/platform-ios.gypi | 2 + include/mbgl/ios/MGLGeometry.h | 85 ++++++++++++++++++++++++++++++++++ include/mbgl/ios/MGLMapView.h | 2 +- include/mbgl/ios/MGLOverlay.h | 2 +- include/mbgl/ios/MGLTypes.h | 15 +----- include/mbgl/ios/MapboxGL.h | 1 + platform/ios/MGLGeometry.m | 3 ++ platform/ios/MGLMultiPoint.mm | 2 +- test/ios/MapViewTests.m | 35 ++++++++++++++ 9 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 include/mbgl/ios/MGLGeometry.h create mode 100644 platform/ios/MGLGeometry.m diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi index 484135a2e..52a66ea45 100644 --- a/gyp/platform-ios.gypi +++ b/gyp/platform-ios.gypi @@ -36,6 +36,8 @@ '../platform/ios/MGLUserLocationAnnotationView.m', '../include/mbgl/ios/MGLTypes.h', '../platform/ios/MGLTypes.m', + '../include/mbgl/ios/MGLGeometry.h', + '../platform/ios/MGLGeometry.m', '../include/mbgl/ios/MGLMultiPoint.h', '../platform/ios/MGLMultiPoint_Private.h', '../platform/ios/MGLMultiPoint.mm', diff --git a/include/mbgl/ios/MGLGeometry.h b/include/mbgl/ios/MGLGeometry.h new file mode 100644 index 000000000..6226af1d0 --- /dev/null +++ b/include/mbgl/ios/MGLGeometry.h @@ -0,0 +1,85 @@ +#pragma once + +#import "MGLTypes.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Defines the area spanned by an MGLCoordinateBounds. */ +typedef struct { + CLLocationDegrees latitudeDelta; + CLLocationDegrees longitudeDelta; +} MGLCoordinateSpan; + +/** Creates a new MGLCoordinateSpan from the given latitudinal and longitudinal deltas. */ +NS_INLINE MGLCoordinateSpan MGLCoordinateSpanMake(CLLocationDegrees latitudeDelta, CLLocationDegrees longitudeDelta) { + MGLCoordinateSpan span; + span.latitudeDelta = latitudeDelta; + span.longitudeDelta = longitudeDelta; + return span; +} + +/** Returns `YES` if the two coordinate spans represent the same latitudinal change and the same longitudinal change. */ +NS_INLINE BOOL MGLCoordinateSpanEqualToCoordinateSpan(MGLCoordinateSpan span1, MGLCoordinateSpan span2) { + return (span1.latitudeDelta == span2.latitudeDelta && + span1.longitudeDelta == span2.longitudeDelta); +} + +/** An area of zero width and zero height. */ +extern const MGLCoordinateSpan MGLCoordinateSpanZero; + +/** A rectangular area as measured on a two-dimensional map projection. */ +typedef struct { + CLLocationCoordinate2D sw; + CLLocationCoordinate2D ne; +} MGLCoordinateBounds; + +/** Creates a new MGLCoordinateBounds structure from the given southwest and northeast coordinates. */ +NS_INLINE MGLCoordinateBounds MGLCoordinateBoundsMake(CLLocationCoordinate2D sw, CLLocationCoordinate2D ne) { + MGLCoordinateBounds bounds; + bounds.sw = sw; + bounds.ne = ne; + return bounds; +} + +/** Returns `YES` if the two coordinate bounds are equal to each other. */ +NS_INLINE BOOL MGLCoordinateBoundsEqualToCoordinateBounds(MGLCoordinateBounds bounds1, MGLCoordinateBounds bounds2) { + return (bounds1.sw.latitude == bounds2.sw.latitude && + bounds1.sw.longitude == bounds2.sw.longitude && + bounds1.ne.latitude == bounds2.ne.latitude && + bounds1.ne.longitude == bounds2.ne.longitude); +} + +/** Returns the area spanned by the coordinate bounds. */ +NS_INLINE MGLCoordinateSpan MGLCoordinateBoundsGetCoordinateSpan(MGLCoordinateBounds bounds) { + return MGLCoordinateSpanMake(bounds.ne.latitude - bounds.sw.latitude, + bounds.ne.longitude - bounds.sw.longitude); +} + +/** Returns a coordinate bounds with southwest and northeast coordinates that are offset from those of the source bounds. */ +NS_INLINE MGLCoordinateBounds MGLCoordinateBoundsOffset(MGLCoordinateBounds bounds, MGLCoordinateSpan offset) { + MGLCoordinateBounds offsetBounds = bounds; + offsetBounds.sw.latitude += offset.latitudeDelta; + offsetBounds.sw.longitude += offset.longitudeDelta; + offsetBounds.ne.latitude += offset.latitudeDelta; + offsetBounds.ne.longitude += offset.longitudeDelta; + return offsetBounds; +} + +/** Returns `YES` if the coordinate bounds covers no area. +* +* Note that a bounds may be empty but have a non-zero coordinate span (e.g., when its northeast point lies due north of its southwest point). */ +NS_INLINE BOOL MGLCoordinateBoundsIsEmpty(MGLCoordinateBounds bounds) { + MGLCoordinateSpan span = MGLCoordinateBoundsGetCoordinateSpan(bounds); + return span.latitudeDelta == 0 || span.longitudeDelta == 0; +} + +/** Returns a formatted string for the given coordinate bounds. */ +NS_INLINE NSString *MGLStringFromCoordinateBounds(MGLCoordinateBounds bounds) { + return [NSString stringWithFormat:@"{{%.1f, %.1f}, {%.1f, %.1f}}", + bounds.sw.latitude, bounds.sw.longitude, + bounds.ne.latitude, bounds.ne.longitude]; +} + +NS_ASSUME_NONNULL_END diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index bf42f47e7..1d4f5b4ac 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -1,4 +1,4 @@ -#import "MGLTypes.h" +#import "MGLGeometry.h" #import #import diff --git a/include/mbgl/ios/MGLOverlay.h b/include/mbgl/ios/MGLOverlay.h index 4a07339b2..104a5e608 100644 --- a/include/mbgl/ios/MGLOverlay.h +++ b/include/mbgl/ios/MGLOverlay.h @@ -2,7 +2,7 @@ #import #import "MGLAnnotation.h" -#import "MGLTypes.h" +#import "MGLGeometry.h" NS_ASSUME_NONNULL_BEGIN diff --git a/include/mbgl/ios/MGLTypes.h b/include/mbgl/ios/MGLTypes.h index 9213dafb3..75a0d4619 100644 --- a/include/mbgl/ios/MGLTypes.h +++ b/include/mbgl/ios/MGLTypes.h @@ -1,5 +1,6 @@ #import -#import + +#pragma once #if !__has_feature(nullability) #define NS_ASSUME_NONNULL_BEGIN @@ -23,18 +24,6 @@ typedef NS_ENUM(NSUInteger, MGLUserTrackingMode) { MGLUserTrackingModeFollowWithHeading }; -typedef struct { - CLLocationCoordinate2D sw; - CLLocationCoordinate2D ne; -} MGLCoordinateBounds; - -NS_INLINE MGLCoordinateBounds MGLCoordinateBoundsMake(CLLocationCoordinate2D sw, CLLocationCoordinate2D ne) { - MGLCoordinateBounds bounds; - bounds.sw = sw; - bounds.ne = ne; - return bounds; -} - NS_ASSUME_NONNULL_END #pragma clang diagnostic push diff --git a/include/mbgl/ios/MapboxGL.h b/include/mbgl/ios/MapboxGL.h index d3b032b9c..bcdc5359b 100644 --- a/include/mbgl/ios/MapboxGL.h +++ b/include/mbgl/ios/MapboxGL.h @@ -1,5 +1,6 @@ #import "MGLAccountManager.h" #import "MGLAnnotation.h" +#import "MGLGeometry.h" #import "MGLMapView.h" #import "MGLMultiPoint.h" #import "MGLOverlay.h" diff --git a/platform/ios/MGLGeometry.m b/platform/ios/MGLGeometry.m new file mode 100644 index 000000000..9eab5565f --- /dev/null +++ b/platform/ios/MGLGeometry.m @@ -0,0 +1,3 @@ +#import "MGLGeometry.h" + +const MGLCoordinateSpan MGLCoordinateSpanZero = {0, 0}; diff --git a/platform/ios/MGLMultiPoint.mm b/platform/ios/MGLMultiPoint.mm index 5721086e8..624c1658a 100644 --- a/platform/ios/MGLMultiPoint.mm +++ b/platform/ios/MGLMultiPoint.mm @@ -1,5 +1,5 @@ #import "MGLMultiPoint.h" -#import "MGLTypes.h" +#import "MGLGeometry.h" #import diff --git a/test/ios/MapViewTests.m b/test/ios/MapViewTests.m index 90447680a..de4095497 100644 --- a/test/ios/MapViewTests.m +++ b/test/ios/MapViewTests.m @@ -150,6 +150,41 @@ @"disabling zoom gesture should disallow pinching"); } +- (void)testFitToBounds { + // No-op + MGLCoordinateBounds initialBounds = tester.mapView.visibleCoordinateBounds; + [tester.mapView setVisibleCoordinateBounds:initialBounds animated:NO]; + XCTAssertEqualObjects(MGLStringFromCoordinateBounds(initialBounds), + MGLStringFromCoordinateBounds(tester.mapView.visibleCoordinateBounds), + @"setting visible coordinate bounds to currently visible coordinate bounds should be a no-op"); + + // Roundtrip after zooming + tester.mapView.zoomLevel -= 3; + [tester.mapView setVisibleCoordinateBounds:initialBounds animated:NO]; + XCTAssertEqualObjects(MGLStringFromCoordinateBounds(initialBounds), + MGLStringFromCoordinateBounds(tester.mapView.visibleCoordinateBounds), + @"after zooming out, setting visible coordinate bounds back to %@ should not leave them at %@", + MGLStringFromCoordinateBounds(initialBounds), + MGLStringFromCoordinateBounds(tester.mapView.visibleCoordinateBounds)); + tester.mapView.zoomLevel += 3; + [tester.mapView setVisibleCoordinateBounds:initialBounds animated:NO]; + XCTAssertEqualObjects(MGLStringFromCoordinateBounds(initialBounds), + MGLStringFromCoordinateBounds(tester.mapView.visibleCoordinateBounds), + @"after zooming in, setting visible coordinate bounds back to %@ should not leave them at %@", + MGLStringFromCoordinateBounds(initialBounds), + MGLStringFromCoordinateBounds(tester.mapView.visibleCoordinateBounds)); + + // Roundtrip after panning + MGLCoordinateBounds offsetBounds = MGLCoordinateBoundsOffset(initialBounds, MGLCoordinateSpanMake(0, 30)); + [tester.mapView setVisibleCoordinateBounds:offsetBounds animated:NO]; + [tester.mapView setVisibleCoordinateBounds:initialBounds animated:NO]; + XCTAssertEqualObjects(MGLStringFromCoordinateBounds(initialBounds), + MGLStringFromCoordinateBounds(tester.mapView.visibleCoordinateBounds), + @"after panning 30° to the east, setting visible coordinate bounds back to %@ should not leave them at %@", + MGLStringFromCoordinateBounds(initialBounds), + MGLStringFromCoordinateBounds(tester.mapView.visibleCoordinateBounds)); +} + - (void)testPan { CLLocationCoordinate2D centerCoordinate = tester.mapView.centerCoordinate;