Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

[ios,macos] multipolygon coordinate #8713

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions platform/darwin/src/MGLGeometry_Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@
#import <mbgl/util/geo.hpp>
#import <mbgl/util/geometry.hpp>

typedef double MGLLocationRadians;
typedef double MGLRadianDistance;
typedef double MGLRadianDirection;

/** Defines the coordinate by a `MGLRadianCoordinate2D`. */
typedef struct MGLRadianCoordinate2D {
MGLLocationRadians latitude;
MGLLocationRadians longitude;
} MGLRadianCoordinate2D;

/**
Creates a new `MGLRadianCoordinate2D` from the given latitudinal and longitudinal.
*/
NS_INLINE MGLRadianCoordinate2D MGLRadianCoordinate2DMake(MGLLocationRadians latitude, MGLLocationRadians longitude) {
MGLRadianCoordinate2D radianCoordinate;
radianCoordinate.latitude = latitude;
radianCoordinate.longitude = longitude;
return radianCoordinate;
}

/// Returns the smallest rectangle that contains both the given rectangle and
/// the given point.
CGRect MGLExtendRect(CGRect rect, CGPoint point);
Expand All @@ -18,6 +38,10 @@ NS_INLINE mbgl::Point<double> MGLPointFromLocationCoordinate2D(CLLocationCoordin
return mbgl::Point<double>(coordinate.longitude, coordinate.latitude);
}

NS_INLINE CLLocationCoordinate2D MGLLocationCoordinate2DFromPoint(mbgl::Point<double> point) {
return CLLocationCoordinate2DMake(point.y, point.x);
}

NS_INLINE CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) {
return CLLocationCoordinate2DMake(latLng.latitude(), latLng.longitude());
}
Expand Down Expand Up @@ -59,3 +83,43 @@ CLLocationDistance MGLAltitudeForZoomLevel(double zoomLevel, CGFloat pitch, CLLo
@param size The size of the viewport.
@return A zero-based zoom level. */
double MGLZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch, CLLocationDegrees latitude, CGSize size);

/** Returns MGLRadianCoordinate2D, converted from CLLocationCoordinate2D. */
NS_INLINE MGLRadianCoordinate2D MGLRadianCoordinateFromLocationCoordinate(CLLocationCoordinate2D locationCoordinate) {
return MGLRadianCoordinate2DMake(MGLRadiansFromDegrees(locationCoordinate.latitude),
MGLRadiansFromDegrees(locationCoordinate.longitude));
}

/*
Returns the distance in radians given two coordinates.
*/
NS_INLINE MGLRadianDistance MGLDistanceBetweenRadianCoordinates(MGLRadianCoordinate2D from, MGLRadianCoordinate2D to)
{
double a = pow(sin((to.latitude - from.latitude) / 2), 2)
+ pow(sin((to.longitude - from.longitude) / 2), 2) * cos(from.latitude) * cos(to.latitude);

return 2 * atan2(sqrt(a), sqrt(1 - a));
}

/*
Returns direction in radians given two coordinates.
*/
NS_INLINE MGLRadianDirection MGLRadianCoordinatesDirection(MGLRadianCoordinate2D from, MGLRadianCoordinate2D to) {
double a = sin(to.longitude - from.longitude) * cos(to.latitude);
double b = cos(from.latitude) * sin(to.latitude)
- sin(from.latitude) * cos(to.latitude) * cos(to.longitude - from.longitude);
return atan2(a, b);
}

/*
Returns coordinate at a given distance and direction away from coordinate.
*/
NS_INLINE MGLRadianCoordinate2D MGLRadianCoordinateAtDistanceFacingDirection(MGLRadianCoordinate2D coordinate,
MGLRadianDistance distance,
MGLRadianDirection direction) {
double otherLatitude = asin(sin(coordinate.latitude) * cos(distance)
+ cos(coordinate.latitude) * sin(distance) * cos(direction));
double otherLongitude = coordinate.longitude + atan2(sin(direction) * sin(distance) * cos(coordinate.latitude),
cos(distance) - sin(coordinate.latitude) * sin(otherLatitude));
return MGLRadianCoordinate2DMake(otherLatitude, otherLongitude);
}
20 changes: 19 additions & 1 deletion platform/darwin/src/MGLPolygon.mm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#import "MGLPolygon+MGLAdditions.h"

#import <mbgl/util/geojson.hpp>
#import <mapbox/polylabel.hpp>

@implementation MGLPolygon

Expand Down Expand Up @@ -54,6 +55,13 @@ - (NSUInteger)hash {
return [super hash] + [[self geoJSONDictionary] hash];
}

- (CLLocationCoordinate2D)coordinate {
// pole of inaccessibility
auto poi = mapbox::polylabel([self polygon]);

return MGLLocationCoordinate2DFromPoint(poi);
}

- (mbgl::LinearRing<double>)ring {
NSUInteger count = self.pointCount;
CLLocationCoordinate2D *coordinates = self.coordinates;
Expand Down Expand Up @@ -155,11 +163,17 @@ - (NSUInteger)hash {
return hash;
}

- (CLLocationCoordinate2D)coordinate {
MGLPolygon *firstPolygon = self.polygons.firstObject;

return firstPolygon.coordinate;
}

- (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds {
return MGLCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds);
}

- (mbgl::Geometry<double>)geometryObject {
- (mbgl::MultiPolygon<double>)multiPolygon {
mbgl::MultiPolygon<double> multiPolygon;
multiPolygon.reserve(self.polygons.count);
for (MGLPolygon *polygon in self.polygons) {
Expand All @@ -173,6 +187,10 @@ - (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds {
return multiPolygon;
}

- (mbgl::Geometry<double>)geometryObject {
return [self multiPolygon];
}

- (NSDictionary *)geoJSONDictionary {
NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.polygons.count];
for (MGLPolygonFeature *feature in self.polygons) {
Expand Down
65 changes: 65 additions & 0 deletions platform/darwin/src/MGLPolyline.mm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#import "MGLPolyline+MGLAdditions.h"

#import <mbgl/util/geojson.hpp>
#import <mapbox/polylabel.hpp>

@implementation MGLPolyline

Expand Down Expand Up @@ -52,6 +53,61 @@ - (BOOL)isEqual:(id)other {
return self == other || ([other isKindOfClass:[MGLPolyline class]] && [super isEqual:other]);
}

- (CLLocationCoordinate2D)coordinate {
NSUInteger count = self.pointCount;
NSAssert(count > 0, @"Polyline must have coordinates");

CLLocationCoordinate2D *coordinates = self.coordinates;
CLLocationDistance middle = [self length] / 2.0;
CLLocationDistance traveled = 0.0;

if (count > 1 || middle > traveled) {
for (NSUInteger i = 0; i < count; i++) {

MGLRadianCoordinate2D from = MGLRadianCoordinateFromLocationCoordinate(coordinates[i]);
MGLRadianCoordinate2D to = MGLRadianCoordinateFromLocationCoordinate(coordinates[i + 1]);

if (traveled >= middle) {
double overshoot = middle - traveled;
if (overshoot == 0) {
return coordinates[i];
}
to = MGLRadianCoordinateFromLocationCoordinate(coordinates[i - 1]);
CLLocationDirection direction = [self direction:from to:to] - 180;
MGLRadianCoordinate2D otherCoordinate = MGLRadianCoordinateAtDistanceFacingDirection(from,
overshoot/mbgl::util::EARTH_RADIUS_M,
MGLRadiansFromDegrees(direction));
return CLLocationCoordinate2DMake(MGLDegreesFromRadians(otherCoordinate.latitude),
MGLDegreesFromRadians(otherCoordinate.longitude));
}

traveled += (MGLDistanceBetweenRadianCoordinates(from, to) * mbgl::util::EARTH_RADIUS_M);

}
}

return coordinates[count - 1];
}

- (CLLocationDistance)length
{
CLLocationDistance length = 0.0;

NSUInteger count = self.pointCount;
CLLocationCoordinate2D *coordinates = self.coordinates;

for (NSUInteger i = 0; i < count - 1; i++) {
length += (MGLDistanceBetweenRadianCoordinates(MGLRadianCoordinateFromLocationCoordinate(coordinates[i]), MGLRadianCoordinateFromLocationCoordinate(coordinates[i + 1])) * mbgl::util::EARTH_RADIUS_M);
}

return length;
}

- (CLLocationDirection)direction:(MGLRadianCoordinate2D)from to:(MGLRadianCoordinate2D)to
{
return MGLDegreesFromRadians(MGLRadianCoordinatesDirection(from, to));
}

@end

@interface MGLMultiPolyline ()
Expand Down Expand Up @@ -114,6 +170,15 @@ - (NSUInteger)hash {
return hash;
}

- (CLLocationCoordinate2D)coordinate {
MGLPolyline *polyline = self.polylines.firstObject;
CLLocationCoordinate2D *coordinates = polyline.coordinates;
NSAssert([polyline pointCount] > 0, @"Polyline must have coordinates");
CLLocationCoordinate2D firstCoordinate = coordinates[0];

return firstCoordinate;
}

- (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds {
return MGLCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds);
}
Expand Down
119 changes: 116 additions & 3 deletions platform/darwin/test/MGLCodingTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,54 @@ - (void)testPolyline {
[unarchivedPolyline replaceCoordinatesInRange:NSMakeRange(0, 1) withCoordinates:otherCoordinates];

XCTAssertNotEqualObjects(polyline, unarchivedPolyline);

CLLocationCoordinate2D multiLineCoordinates[] = {
CLLocationCoordinate2DMake(51.000000, 0.000000),
CLLocationCoordinate2DMake(51.000000, 1.000000),
CLLocationCoordinate2DMake(51.000000, 2.000000),
};

NSUInteger multiLineCoordinatesCount = sizeof(multiLineCoordinates) / sizeof(CLLocationCoordinate2D);
MGLPolyline *multiLine = [MGLPolyline polylineWithCoordinates:multiLineCoordinates count:multiLineCoordinatesCount];
CLLocationCoordinate2D multiLineCenter = CLLocationCoordinate2DMake(51.000000, 1.000000);

XCTAssertEqual([multiLine coordinate].latitude, multiLineCenter.latitude);
XCTAssertEqual([multiLine coordinate].longitude, multiLineCenter.longitude);

CLLocationCoordinate2D segmentCoordinates[] = {
CLLocationCoordinate2DMake(35.040390, -85.311477),
CLLocationCoordinate2DMake(35.040390, -85.209510),
};

NSUInteger segmentCoordinatesCount = sizeof(segmentCoordinates) / sizeof(CLLocationCoordinate2D);
MGLPolyline *segmentLine = [MGLPolyline polylineWithCoordinates:segmentCoordinates count:segmentCoordinatesCount];
CLLocationCoordinate2D segmentCenter = CLLocationCoordinate2DMake(35.0404006631, -85.2604935);

XCTAssertEqualWithAccuracy([segmentLine coordinate].latitude, segmentCenter.latitude, 0.0001);
XCTAssertEqualWithAccuracy([segmentLine coordinate].longitude, segmentCenter.longitude, 0.0001);

CLLocationCoordinate2D sfToBerkeleyCoordinates[] = {
CLLocationCoordinate2DMake(37.782440, -122.397111),
CLLocationCoordinate2DMake(37.818384, -122.352994),
CLLocationCoordinate2DMake(37.831401, -122.274545),
CLLocationCoordinate2DMake(37.862172, -122.262700),
};

NSUInteger sfToBerkeleyCoordinatesCount = sizeof(sfToBerkeleyCoordinates) / sizeof(CLLocationCoordinate2D);
MGLPolyline *sfToBerkeleyLine = [MGLPolyline polylineWithCoordinates:sfToBerkeleyCoordinates count:sfToBerkeleyCoordinatesCount];
CLLocationCoordinate2D sfToBerkeleyCenter = CLLocationCoordinate2DMake(37.8230575118,-122.324867587);

XCTAssertEqualWithAccuracy([sfToBerkeleyLine coordinate].latitude, sfToBerkeleyCenter.latitude, 0.0001);
XCTAssertEqualWithAccuracy([sfToBerkeleyLine coordinate].longitude, sfToBerkeleyCenter.longitude, 0.0001);

}

- (void)testPolygon {
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.664482398, 1.8865675),
CLLocationCoordinate2DMake(2.13224687, 3.9984632)
CLLocationCoordinate2DMake(35.090745, -85.300259),
CLLocationCoordinate2DMake(35.092035, -85.298885),
CLLocationCoordinate2DMake(35.090639, -85.297416),
CLLocationCoordinate2DMake(35.089112, -85.298928)
};

NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);
Expand All @@ -84,8 +126,24 @@ - (void)testPolygon {
[NSKeyedArchiver archiveRootObject:polygon toFile:filePath];

MGLPolygon *unarchivedPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
[unarchivedPolygon coordinate];

XCTAssertEqualObjects(polygon, unarchivedPolygon);

CLLocationCoordinate2D squareCoordinates[] = {
CLLocationCoordinate2DMake(100.0, 0.0),
CLLocationCoordinate2DMake(101.0, 0.0),
CLLocationCoordinate2DMake(101.0, 1.0),
CLLocationCoordinate2DMake(100.0, 1.0),
};

NSUInteger squareCoordinatesCount = sizeof(squareCoordinates) / sizeof(CLLocationCoordinate2D);
MGLPolygon *squarePolygon = [MGLPolygon polygonWithCoordinates:squareCoordinates count:squareCoordinatesCount];
CLLocationCoordinate2D squareCenter = CLLocationCoordinate2DMake(100.5, 0.5);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this is an adequate test of the added functionality? Do we need more unit tests specifically about the coordinate properties that this PR adds? Perhaps we could port some tests from Turf or from GeometryTests.swift.

Copy link
Contributor Author

@fabian-guerra fabian-guerra May 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this is an adequate test of the added functionality?

Yes... if you think I'm missing test cases I will port the tests you mention above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the tests are less related to poi but I added two test scenarios to testPolyline that cover a single and multiple segments.


XCTAssertEqual([squarePolygon coordinate].latitude, squareCenter.latitude);
XCTAssertEqual([squarePolygon coordinate].longitude, squareCenter.longitude);

}

- (void)testPolygonWithInteriorPolygons {
Expand Down Expand Up @@ -169,6 +227,11 @@ - (void)testPointCollection {
NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);

MGLPointCollection *pointCollection = [MGLPointCollection pointCollectionWithCoordinates:coordinates count:numberOfCoordinates];
CLLocationCoordinate2D pointsCenter = CLLocationCoordinate2DMake(0, 1);

XCTAssertEqual([pointCollection coordinate].latitude, pointsCenter.latitude);
XCTAssertEqual([pointCollection coordinate].longitude, pointsCenter.longitude);

NSString *filePath = [self temporaryFilePathForClass:[MGLPointCollection class]];
[NSKeyedArchiver archiveRootObject:pointCollection toFile:filePath];

Expand Down Expand Up @@ -218,6 +281,30 @@ - (void)testMultiPolyline {
CLLocationCoordinate2DMake(20, 21),
CLLocationCoordinate2DMake(30, 31),
};

CLLocationCoordinate2D line1[] = {
CLLocationCoordinate2DMake(100, 40),
CLLocationCoordinate2DMake(105, 45),
CLLocationCoordinate2DMake(110, 55)
};

CLLocationCoordinate2D line2[] = {
CLLocationCoordinate2DMake(105, 40),
CLLocationCoordinate2DMake(110, 45),
CLLocationCoordinate2DMake(115, 55)
};

NSUInteger road1CoordinatesCount = sizeof(line1) / sizeof(CLLocationCoordinate2D);
NSUInteger road2CoordinatesCount = sizeof(line2) / sizeof(CLLocationCoordinate2D);

MGLPolyline *road1Polyline = [MGLPolyline polylineWithCoordinates:line1 count:road1CoordinatesCount];
MGLPolyline *road2Polyline = [MGLPolyline polylineWithCoordinates:line1 count:road2CoordinatesCount];

MGLMultiPolyline *roads = [MGLMultiPolyline multiPolylineWithPolylines:@[road1Polyline, road2Polyline]];
CLLocationCoordinate2D roadCenter = CLLocationCoordinate2DMake(100, 40);

XCTAssertEqual([roads coordinate].latitude, roadCenter.latitude);
XCTAssertEqual([roads coordinate].longitude, roadCenter.longitude);

NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);

Expand Down Expand Up @@ -248,6 +335,31 @@ - (void)testMultiPolygon {
CLLocationCoordinate2DMake(20, 21),
CLLocationCoordinate2DMake(30, 31),
};

CLLocationCoordinate2D outerSquare[] = {
CLLocationCoordinate2DMake(100.0, 0.0),
CLLocationCoordinate2DMake(101.0, 0.0),
CLLocationCoordinate2DMake(101.0, 1.0),
CLLocationCoordinate2DMake(100.0, 1.0),
};

CLLocationCoordinate2D innerSquare[] = {
CLLocationCoordinate2DMake(100.35, 0.35),
CLLocationCoordinate2DMake(100.65, 0.35),
CLLocationCoordinate2DMake(100.65, 0.65),
CLLocationCoordinate2DMake(100.35, 0.65),
};

NSUInteger outerCoordinatesCount = sizeof(outerSquare) / sizeof(CLLocationCoordinate2D);
NSUInteger innerCoordinatesCount = sizeof(innerSquare) / sizeof(CLLocationCoordinate2D);

MGLPolygon *innerPolygonSquare = [MGLPolygon polygonWithCoordinates:innerSquare count:innerCoordinatesCount];
MGLPolygon *outerPolygonSquare = [MGLPolygon polygonWithCoordinates:outerSquare count:outerCoordinatesCount interiorPolygons:@[innerPolygonSquare]];
MGLMultiPolygon *squares = [MGLMultiPolygon multiPolygonWithPolygons:@[outerPolygonSquare, innerPolygonSquare]];
CLLocationCoordinate2D squareCenter = CLLocationCoordinate2DMake(100.5, 0.5);

XCTAssertEqual([squares coordinate].latitude, squareCenter.latitude);
XCTAssertEqual([squares coordinate].longitude, squareCenter.longitude);

NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);

Expand All @@ -265,9 +377,10 @@ - (void)testMultiPolygon {

MGLMultiPolygon *unarchivedMultiPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
MGLMultiPolygon *anotherMultiPolygon = [MGLMultiPolygon multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count/2)]];

XCTAssertEqualObjects(multiPolygon, unarchivedMultiPolygon);
XCTAssertNotEqualObjects(anotherMultiPolygon, unarchivedMultiPolygon);

}

- (void)testShapeCollection {
Expand Down
1 change: 1 addition & 0 deletions platform/ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
### Other changes

* Xcode 8.0 or higher is now recommended for using this SDK. ([#8775](https://github.com/mapbox/mapbox-gl-native/pull/8775))
* Fixed a crash when calling `MGLMultiPolygon.coordinate` [#8713](https://github.com/mapbox/mapbox-gl-native/pull/8713)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note (perhaps in the “Annotations” section) that the coordinate property on MGLPolyline and MGLPolygon now returns a sensible value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We’ve already branched for iOS SDK v3.6.0: either retarget and rebase this PR on the release-ios-v3.6.0-android-v5.1.0 branch or add it to this project to be cherry-picked into that branch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note (perhaps in the “Annotations” section) that the coordinate property on MGLPolyline and MGLPolygon now returns a sensible value.

#9088

* Updated MGLMapView’s logo view to display [the new Mapbox logo](https://www.mapbox.com/blog/new-mapbox-logo/). ([#8771](https://github.com/mapbox/mapbox-gl-native/pull/8771), [#8773](https://github.com/mapbox/mapbox-gl-native/pull/8773))
* Fixed a crash or console spew when MGLMapView is initialized with a frame smaller than 64 points wide by 64 points tall. ([#8562](https://github.com/mapbox/mapbox-gl-native/pull/8562))
* The error passed into `-[MGLMapViewDelegate mapViewDidFailLoadingMap:withError:]` now includes a more specific description and failure reason. ([#8418](https://github.com/mapbox/mapbox-gl-native/pull/8418))
Expand Down
Loading