summaryrefslogtreecommitdiff
path: root/platform/darwin
diff options
context:
space:
mode:
Diffstat (limited to 'platform/darwin')
-rw-r--r--platform/darwin/scripts/style-spec-overrides-v8.json2
-rw-r--r--platform/darwin/src/MGLCircle.h95
-rw-r--r--platform/darwin/src/MGLCircle.mm177
-rw-r--r--platform/darwin/src/MGLCircleStyleLayer.h4
-rw-r--r--platform/darwin/src/MGLCircle_Private.h26
-rw-r--r--platform/darwin/src/MGLGeometry.mm17
-rw-r--r--platform/darwin/src/MGLGeometry_Private.h14
-rw-r--r--platform/darwin/src/MGLMultiPoint_Private.h3
-rw-r--r--platform/darwin/src/MGLPolygon.h5
-rw-r--r--platform/darwin/src/MGLPolygon.mm2
-rw-r--r--platform/darwin/test/MGLGeometryTests.mm68
11 files changed, 407 insertions, 6 deletions
diff --git a/platform/darwin/scripts/style-spec-overrides-v8.json b/platform/darwin/scripts/style-spec-overrides-v8.json
index 0ba2b77dc5..ae8fbac785 100644
--- a/platform/darwin/scripts/style-spec-overrides-v8.json
+++ b/platform/darwin/scripts/style-spec-overrides-v8.json
@@ -26,7 +26,7 @@
"examples": "See the <a href=\"https://docs.mapbox.com/ios/maps/examples/runtime-multiple-annotations/\">Dynamically style interactive points</a> and <a href=\"https://docs.mapbox.com/ios/maps/examples/clustering-with-images/\">Use images to cluster point data</a> examples learn how to style data on your map using this layer."
},
"circle": {
- "doc": "An `MGLCircleStyleLayer` is a style layer that renders one or more filled circles on the map.\n\nUse a circle style layer to configure the visual appearance of point or point collection features. These features can come from vector tiles loaded by an `MGLVectorTileSource` object, or they can be `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` or `MGLComputedShapeSource` object.\n\nA circle style layer renders circles whose radii are measured in screen units. To display circles on the map whose radii correspond to real-world distances, use many-sided regular polygons and configure their appearance using an `MGLFillStyleLayer` object.",
+ "doc": "An `MGLCircleStyleLayer` is a style layer that renders one or more filled circles on the map.\n\nUse a circle style layer to configure the visual appearance of point or point collection features. These features can come from vector tiles loaded by an `MGLVectorTileSource` object, or they can be `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` or `MGLComputedShapeSource` object.\n\nA circle style layer renders circles whose radii are measured in screen units. To display circles on the map whose radii correspond to real-world distances, use `MGLCircle` or many-sided regular `MGLPolygon` objects and configure their appearance using an `MGLFillStyleLayer` object.",
"examples": "See the <a href=\"https://docs.mapbox.com/ios/maps/examples/dds-circle-layer/\">Data-driven circles</a>, <a href=\"https://docs.mapbox.com/ios/maps/examples/shape-collection/\">Add multiple shapes from a single shape source</a>, and <a href=\"https://docs.mapbox.com/ios/maps/examples/clustering/\">Cluster point data</a> examples to learn how to add circles to your map using this style layer."
},
"heatmap": {
diff --git a/platform/darwin/src/MGLCircle.h b/platform/darwin/src/MGLCircle.h
new file mode 100644
index 0000000000..7d0b3b11d6
--- /dev/null
+++ b/platform/darwin/src/MGLCircle.h
@@ -0,0 +1,95 @@
+#import <Foundation/Foundation.h>
+#import <CoreLocation/CoreLocation.h>
+
+#import "MGLShape.h"
+#import "MGLGeometry.h"
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ An `MGLCircle` object represents a closed, circular shape also known as a
+ <a href="https://en.wikipedia.org/wiki/Spherical_cap">spherical cap</a>. A
+ circle is defined by a center coordinate, specified as a
+ `CLLocationCoordinate2D` instance, and a physical radius measured in meters.
+ The circle is approximated as a polygon with a large number of vertices and
+ edges. Due to the map’s Spherical Mercator projection, a large enough circle
+ appears as an elliptical or even sinusoidal shape. You could use a circle to
+ visualize for instance an impact zone, the `CLLocation.horizontalAccuracy` of a
+ GPS location update, the regulated airspace around an airport, or the
+ addressable consumer market around a city.
+
+ You can add a circle overlay directly to a map view using the
+ `-[MGLMapView addAnnotation:]` or `-[MGLMapView addOverlay:]` method. Configure
+ a circle overlay’s appearance using
+ `-[MGLMapViewDelegate mapView:strokeColorForShapeAnnotation:]` and
+ `-[MGLMapViewDelegate mapView:fillColorForShape:]`.
+
+ Alternatively, you can add a circle to the map by adding it to an
+ `MGLShapeSource` object. Because GeoJSON cannot represent a curve per se, the
+ circle is automatically converted to a polygon. See the `MGLPolygon` class for
+ more information about polygons.
+
+ Do not confuse this class with `MGLCircleStyleLayer`, which renders a circle
+ defined by a center coordinate and a fixed _screen_ radius measured in points
+ regardless of the map’s zoom level.
+
+ The polygon that approximates an `MGLCircle` has a large number of vertices.
+ If you do not need the circle to appear smooth at every possible zoom level,
+ use a many-sided regular `MGLPolygon` instead for better performance.
+ */
+MGL_EXPORT
+@interface MGLCircle : MGLShape
+
+/**
+ The coordinate around which the circle is centered.
+
+ Each coordinate along the circle’s edge is equidistant from this coordinate.
+ The center coordinate’s latitude helps determine the minimum spacing between
+ each vertex along the edge of the polygon that approximates the circle.
+ */
+@property (nonatomic) CLLocationCoordinate2D coordinate;
+
+/**
+ The radius of the circular area, measured in meters across the Earth’s surface.
+ */
+@property (nonatomic) CLLocationDistance radius;
+
+/**
+ Creates and returns an `MGLCircle` object centered around the given coordinate
+ and extending in all directions by the given physical distance.
+
+ @param centerCoordinate The coordinate around which the circle is centered.
+ @param radius The radius of the circular area, measured in meters across the
+ Earth’s surface.
+ @return A new circle object.
+ */
++ (instancetype)circleWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate radius:(CLLocationDistance)radius;
+
+/**
+ Creates and returns an `MGLCircle` object that fills the given coordinate
+ bounds.
+
+ @param coordinateBounds The coordinate bounds to fill. The circle is centered
+ around the center of the coordinate bounds. The circle’s edge touches at
+ least two of the sides of the coordinate bounds. If the coordinate bounds
+ does not represent a square area, the circle extends beyond two of its
+ sides.
+ @return A new circle object.
+ */
++ (instancetype)circleWithCoordinateBounds:(MGLCoordinateBounds)coordinateBounds;
+
+/**
+ The smallest coordinate rectangle that completely encompasses the circle.
+
+ If the circle spans the antimeridian, its bounds may extend west of −180
+ degrees longitude or east of 180 degrees longitude. For example, a circle
+ covering the Pacific Ocean from Tokyo to San Francisco might have a bounds
+ extending from (35.68476, −220.24257) to (37.78428, −122.41310).
+ */
+@property (nonatomic, readonly) MGLCoordinateBounds coordinateBounds;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLCircle.mm b/platform/darwin/src/MGLCircle.mm
new file mode 100644
index 0000000000..f4f507ac44
--- /dev/null
+++ b/platform/darwin/src/MGLCircle.mm
@@ -0,0 +1,177 @@
+#import "MGLCircle.h"
+
+#import "MGLGeometry_Private.h"
+#import "MGLMultiPoint_Private.h"
+#import "NSCoder+MGLAdditions.h"
+
+#import <mbgl/util/projection.hpp>
+
+#import <vector>
+
+@implementation MGLCircle
+
+@synthesize coordinate = _coordinate;
+
++ (instancetype)circleWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate radius:(CLLocationDistance)radius {
+ return [[self alloc] initWithCenterCoordinate:centerCoordinate radius:radius];
+}
+
++ (instancetype)circleWithCoordinateBounds:(MGLCoordinateBounds)coordinateBounds {
+ MGLCoordinateSpan span = MGLCoordinateBoundsGetCoordinateSpan(coordinateBounds);
+ BOOL latitudinal = span.latitudeDelta > span.longitudeDelta;
+ // TODO: Latitudinal distances aren’t uniform, so get the mean northing.
+ CLLocationCoordinate2D center = CLLocationCoordinate2DMake(coordinateBounds.ne.latitude - span.latitudeDelta / 2.0,
+ coordinateBounds.ne.longitude - span.longitudeDelta / 2.0);
+ CLLocationCoordinate2D southOrWest = CLLocationCoordinate2DMake(latitudinal ? coordinateBounds.sw.latitude : 0,
+ latitudinal ? 0 : coordinateBounds.sw.longitude);
+ CLLocationCoordinate2D northOrEast = CLLocationCoordinate2DMake(latitudinal ? coordinateBounds.ne.latitude : 0,
+ latitudinal ? 0 : coordinateBounds.ne.longitude);
+ CLLocationDistance majorAxis = MGLDistanceBetweenLocationCoordinates(southOrWest, northOrEast);
+ return [[self alloc] initWithCenterCoordinate:center radius:majorAxis / 2.0];
+}
+
+- (instancetype)initWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate radius:(CLLocationDistance)radius {
+ if (self = [super init]) {
+ _coordinate = centerCoordinate;
+ _radius = radius;
+ }
+ return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)decoder {
+ if (self = [super initWithCoder:decoder]) {
+ _coordinate = [decoder decodeMGLCoordinateForKey:@"coordinate"];
+ _radius = [decoder decodeDoubleForKey:@"radius"];
+ }
+ return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)coder {
+ [super encodeWithCoder:coder];
+ [coder encodeMGLCoordinate:_coordinate forKey:@"coordinate"];
+ [coder encodeDouble:_radius forKey:@"radius"];
+}
+
+- (BOOL)isEqual:(id)other {
+ if (other == self) {
+ return YES;
+ }
+ if (![other isKindOfClass:[MGLCircle class]]) {
+ return NO;
+ }
+
+ MGLCircle *otherCircle = other;
+ return ([super isEqual:other]
+ && self.coordinate.latitude == otherCircle.coordinate.latitude
+ && self.coordinate.longitude == otherCircle.coordinate.longitude
+ && self.radius == otherCircle.radius);
+}
+
+- (NSUInteger)hash {
+ return super.hash + @(self.coordinate.latitude).hash + @(self.coordinate.longitude).hash;
+}
+
+- (NSUInteger)numberOfVertices {
+ // Due to the z16 zoom level and Douglas–Peucker tolerance specified by
+ // mbgl::ShapeAnnotationImpl::updateTileData() and GeoJSONVT, the smallest
+ // circle that can be displayed at z22 at the poles has a radius of about
+ // 5 centimeters and is simplified to four sides each about 0.31 meters
+ // (50 points) long. The smallest displayable circle at the Equator has a
+ // radius of about 5 decimeters and is simplified to four sides each about
+ // 3.1 meters (75 points) long.
+ constexpr NSUInteger maximumZoomLevel = 16;
+ CLLocationDistance maximumEdgeLength = mbgl::Projection::getMetersPerPixelAtLatitude(self.coordinate.latitude, maximumZoomLevel);
+ CLLocationDistance circumference = 2 * M_PI * self.radius;
+ NSUInteger maximumSides = ceil(fabs(circumference) / maximumEdgeLength);
+
+ // The smallest perceptible angle is about 1 arcminute.
+ // https://en.wikipedia.org/wiki/Naked_eye#Small_objects_and_maps
+ constexpr CLLocationDirection maximumInternalAngle = 180.0 - 1.0 / 60;
+ constexpr CLLocationDirection maximumCentralAngle = 180.0 - maximumInternalAngle;
+ constexpr CGFloat maximumVertices = 360.0 / maximumCentralAngle;
+
+ // Make the circle’s resolution high enough that the user can’t perceive any
+ // angles, but not so high that detail would be lost through simplification.
+ return ceil(MIN(maximumSides, maximumVertices));
+}
+
+- (mbgl::LinearRing<double>)linearRingWithNumberOfVertices:(NSUInteger)numberOfVertices {
+ CLLocationCoordinate2D center = self.coordinate;
+ CLLocationDistance radius = fabs(self.radius);
+
+ mbgl::LinearRing<double> ring;
+ ring.reserve(numberOfVertices);
+ for (NSUInteger i = 0; i < numberOfVertices; i++) {
+ // Start at due north and go counterclockwise, or phase shift by 90° if
+ // centered in the southern hemisphere, so it’s easy to fix up for ±90°
+ // latitude in the conditional below.
+ CLLocationDirection direction = 360.0 / numberOfVertices * i + (center.latitude >= 0 ? 0 : 180);
+ CLLocationCoordinate2D vertex = MGLCoordinateAtDistanceFacingDirection(center, radius, direction);
+ // If the circle extends to ±90° latitude and has wrapped around, extend
+ // the polygon to include all of ±90° latitude and beyond.
+ if (i == 0 && radius > 1
+ && fabs(vertex.latitude) < fabs(MGLCoordinateAtDistanceFacingDirection(center, radius - 1, direction).latitude)) {
+ short hemisphere = center.latitude >= 0 ? 1 : -1;
+ ring.push_back({ center.longitude - 180.0, vertex.latitude });
+ ring.push_back({ center.longitude - 180.0, 90.0 * hemisphere });
+ ring.push_back({ center.longitude + 180.0, 90.0 * hemisphere });
+ }
+ ring.push_back(MGLPointFromLocationCoordinate2D(vertex));
+ }
+ return ring;
+}
+
+- (mbgl::Polygon<double>)polygon {
+ mbgl::Polygon<double> polygon;
+ polygon.push_back([self linearRingWithNumberOfVertices:self.numberOfVertices]);
+ return polygon;
+}
+
+- (mbgl::Geometry<double>)geometryObject {
+ return [self polygon];
+}
+
+- (NSDictionary *)geoJSONDictionary {
+ return @{
+ @"type": @"Polygon",
+ @"coordinates": self.geoJSONGeometry,
+ };
+}
+
+- (NSArray<id> *)geoJSONGeometry {
+ NSMutableArray *coordinates = [NSMutableArray array];
+
+ mbgl::LinearRing<double> ring = [self polygon][0];
+ NSMutableArray *geoJSONRing = [NSMutableArray array];
+ for (auto &point : ring) {
+ [geoJSONRing addObject:@[@(point.x), @(point.y)]];
+ }
+ [coordinates addObject:geoJSONRing];
+
+ return [coordinates copy];
+}
+
+- (mbgl::Annotation)annotationObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate {
+
+ mbgl::FillAnnotation annotation { [self polygon] };
+ annotation.opacity = { static_cast<float>([delegate alphaForShapeAnnotation:self]) };
+ annotation.outlineColor = { [delegate strokeColorForShapeAnnotation:self] };
+ annotation.color = { [delegate fillColorForShape:self] };
+
+ return annotation;
+}
+
+- (MGLCoordinateBounds)coordinateBounds {
+ mbgl::LinearRing<double> ring = [self linearRingWithNumberOfVertices:4];
+ CLLocationCoordinate2D southWest = CLLocationCoordinate2DMake(ring[2].y, ring[3].x);
+ CLLocationCoordinate2D northEast = CLLocationCoordinate2DMake(ring[0].y, ring[1].x);
+ return MGLCoordinateBoundsMake(southWest, northEast);
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"<%@: %p; coordinate = %@; radius = %f m>",
+ NSStringFromClass([self class]), (void *)self,
+ MGLStringFromCLLocationCoordinate2D(self.coordinate), self.radius];
+}
+
+@end
diff --git a/platform/darwin/src/MGLCircleStyleLayer.h b/platform/darwin/src/MGLCircleStyleLayer.h
index e2b043a729..4ffe0d5289 100644
--- a/platform/darwin/src/MGLCircleStyleLayer.h
+++ b/platform/darwin/src/MGLCircleStyleLayer.h
@@ -69,8 +69,8 @@ typedef NS_ENUM(NSUInteger, MGLCircleTranslationAnchor) {
A circle style layer renders circles whose radii are measured in screen units.
To display circles on the map whose radii correspond to real-world distances,
- use many-sided regular polygons and configure their appearance using an
- `MGLFillStyleLayer` object.
+ use `MGLCircle` or many-sided regular `MGLPolygon` objects and configure their
+ appearance using an `MGLFillStyleLayer` object.
You can access an existing circle style layer using the
`-[MGLStyle layerWithIdentifier:]` method if you know its identifier;
diff --git a/platform/darwin/src/MGLCircle_Private.h b/platform/darwin/src/MGLCircle_Private.h
new file mode 100644
index 0000000000..ef8fb372bf
--- /dev/null
+++ b/platform/darwin/src/MGLCircle_Private.h
@@ -0,0 +1,26 @@
+#import "MGLCircle.h"
+
+#import <mbgl/annotation/annotation.hpp>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol MGLMultiPointDelegate;
+
+@interface MGLCircle (Private)
+
+/**
+ The optimal number of vertices in the circle’s polygonal approximation.
+ */
+@property (nonatomic, readonly) NSUInteger numberOfVertices;
+
+/**
+ Returns a linear ring with the given number of vertices.
+ */
+- (mbgl::LinearRing<double>)linearRingWithNumberOfVertices:(NSUInteger)numberOfVertices;
+
+/** Constructs a circle annotation object, asking the delegate for style values. */
+- (mbgl::Annotation)annotationObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLGeometry.mm b/platform/darwin/src/MGLGeometry.mm
index c6fb5a5fc2..bd25b00475 100644
--- a/platform/darwin/src/MGLGeometry.mm
+++ b/platform/darwin/src/MGLGeometry.mm
@@ -3,6 +3,7 @@
#import "MGLFoundation.h"
#import <mbgl/util/projection.hpp>
+#import <mbgl/util/constants.hpp>
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
#import <Cocoa/Cocoa.h>
@@ -67,6 +68,12 @@ MGLRadianDistance MGLDistanceBetweenRadianCoordinates(MGLRadianCoordinate2D from
return 2 * atan2(sqrt(a), sqrt(1 - a));
}
+CLLocationDistance MGLDistanceBetweenLocationCoordinates(CLLocationCoordinate2D from, CLLocationCoordinate2D to) {
+ MGLRadianDistance radianDistance = MGLDistanceBetweenRadianCoordinates(MGLRadianCoordinateFromLocationCoordinate(from),
+ MGLRadianCoordinateFromLocationCoordinate(to));
+ return radianDistance * mbgl::util::EARTH_RADIUS_M;
+}
+
MGLRadianDirection MGLRadianCoordinatesDirection(MGLRadianCoordinate2D from, MGLRadianCoordinate2D to) {
double a = sin(to.longitude - from.longitude) * cos(to.latitude);
double b = cos(from.latitude) * sin(to.latitude)
@@ -84,6 +91,16 @@ MGLRadianCoordinate2D MGLRadianCoordinateAtDistanceFacingDirection(MGLRadianCoor
return MGLRadianCoordinate2DMake(otherLatitude, otherLongitude);
}
+CLLocationCoordinate2D MGLCoordinateAtDistanceFacingDirection(CLLocationCoordinate2D coordinate,
+ CLLocationDistance distance,
+ CLLocationDirection direction) {
+ MGLRadianCoordinate2D radianCenter = MGLRadianCoordinateFromLocationCoordinate(coordinate);
+ MGLRadianCoordinate2D radianVertex = MGLRadianCoordinateAtDistanceFacingDirection(radianCenter,
+ distance / mbgl::util::EARTH_RADIUS_M,
+ MGLRadiansFromDegrees(direction));
+ return MGLLocationCoordinateFromRadianCoordinate(radianVertex);
+}
+
CLLocationDirection MGLDirectionBetweenCoordinates(CLLocationCoordinate2D firstCoordinate, CLLocationCoordinate2D secondCoordinate) {
// Ported from https://github.com/mapbox/turf-swift/blob/857e2e8060678ef4a7a9169d4971b0788fdffc37/Turf/Turf.swift#L23-L31
MGLRadianCoordinate2D firstRadianCoordinate = MGLRadianCoordinateFromLocationCoordinate(firstCoordinate);
diff --git a/platform/darwin/src/MGLGeometry_Private.h b/platform/darwin/src/MGLGeometry_Private.h
index b91d4e0f81..1b0bcc3861 100644
--- a/platform/darwin/src/MGLGeometry_Private.h
+++ b/platform/darwin/src/MGLGeometry_Private.h
@@ -112,12 +112,22 @@ NS_INLINE MGLRadianCoordinate2D MGLRadianCoordinateFromLocationCoordinate(CLLoca
MGLRadiansFromDegrees(locationCoordinate.longitude));
}
+NS_INLINE CLLocationCoordinate2D MGLLocationCoordinateFromRadianCoordinate(MGLRadianCoordinate2D radianCoordinate) {
+ return CLLocationCoordinate2DMake(MGLDegreesFromRadians(radianCoordinate.latitude),
+ MGLDegreesFromRadians(radianCoordinate.longitude));
+}
+
/**
Returns the distance in radians given two coordinates.
*/
MGLRadianDistance MGLDistanceBetweenRadianCoordinates(MGLRadianCoordinate2D from, MGLRadianCoordinate2D to);
/**
+ Returns the distance given two coordinates.
+ */
+CLLocationDistance MGLDistanceBetweenLocationCoordinates(CLLocationCoordinate2D from, CLLocationCoordinate2D to);
+
+/**
Returns direction in radians given two coordinates.
*/
MGLRadianDirection MGLRadianCoordinatesDirection(MGLRadianCoordinate2D from, MGLRadianCoordinate2D to);
@@ -129,6 +139,10 @@ MGLRadianCoordinate2D MGLRadianCoordinateAtDistanceFacingDirection(MGLRadianCoor
MGLRadianDistance distance,
MGLRadianDirection direction);
+CLLocationCoordinate2D MGLCoordinateAtDistanceFacingDirection(CLLocationCoordinate2D coordinate,
+ CLLocationDistance distance,
+ CLLocationDirection direction);
+
/**
Returns the direction from one coordinate to another.
*/
diff --git a/platform/darwin/src/MGLMultiPoint_Private.h b/platform/darwin/src/MGLMultiPoint_Private.h
index a9b4b72ca5..37ab6dc4af 100644
--- a/platform/darwin/src/MGLMultiPoint_Private.h
+++ b/platform/darwin/src/MGLMultiPoint_Private.h
@@ -13,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
@class MGLPolygon;
@class MGLPolyline;
+@class MGLCircle;
@protocol MGLMultiPointDelegate;
@@ -36,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
- (mbgl::Color)strokeColorForShapeAnnotation:(MGLShape *)annotation;
/** Returns the fill color object for the given annotation. */
-- (mbgl::Color)fillColorForPolygonAnnotation:(MGLPolygon *)annotation;
+- (mbgl::Color)fillColorForShape:(MGLShape *)annotation;
/** Returns the stroke width object for the given annotation. */
- (CGFloat)lineWidthForPolylineAnnotation:(MGLPolyline *)annotation;
diff --git a/platform/darwin/src/MGLPolygon.h b/platform/darwin/src/MGLPolygon.h
index 900e43334e..f75c4481be 100644
--- a/platform/darwin/src/MGLPolygon.h
+++ b/platform/darwin/src/MGLPolygon.h
@@ -25,7 +25,7 @@ NS_ASSUME_NONNULL_BEGIN
`-[MGLMapView addAnnotation:]` or `-[MGLMapView addOverlay:]` method. Configure
a polygon overlay’s appearance using
`-[MGLMapViewDelegate mapView:strokeColorForShapeAnnotation:]` and
- `-[MGLMapViewDelegate mapView:fillColorForPolygonAnnotation:]`.
+ `-[MGLMapViewDelegate mapView:fillColorForShape:]`.
The vertices are automatically connected in the order in which you provide
them. You should close the polygon by specifying the same
@@ -45,6 +45,9 @@ NS_ASSUME_NONNULL_BEGIN
To make the polygon straddle the antimeridian, specify some longitudes less
than −180 degrees or greater than 180 degrees.
+ To approximate a circle that corresponds to a physical radius, use an
+ `MGLCircle` object.
+
#### Related examples
See the <a href="https://docs.mapbox.com/ios/maps/examples/polygon/">
Add a polygon annotation</a> example to learn how to initialize an
diff --git a/platform/darwin/src/MGLPolygon.mm b/platform/darwin/src/MGLPolygon.mm
index 52bff01b20..164ba759b3 100644
--- a/platform/darwin/src/MGLPolygon.mm
+++ b/platform/darwin/src/MGLPolygon.mm
@@ -95,7 +95,7 @@
mbgl::FillAnnotation annotation { [self polygon] };
annotation.opacity = { static_cast<float>([delegate alphaForShapeAnnotation:self]) };
annotation.outlineColor = { [delegate strokeColorForShapeAnnotation:self] };
- annotation.color = { [delegate fillColorForPolygonAnnotation:self] };
+ annotation.color = { [delegate fillColorForShape:self] };
return annotation;
}
diff --git a/platform/darwin/test/MGLGeometryTests.mm b/platform/darwin/test/MGLGeometryTests.mm
index e3b1836e8d..b7e0398400 100644
--- a/platform/darwin/test/MGLGeometryTests.mm
+++ b/platform/darwin/test/MGLGeometryTests.mm
@@ -2,6 +2,7 @@
#import <XCTest/XCTest.h>
#import "../../darwin/src/MGLGeometry_Private.h"
+#import "../../darwin/src/MGLCircle_Private.h"
@interface MGLGeometryTests : XCTestCase
@end
@@ -208,4 +209,71 @@
}
}
+- (void)testCircles {
+ {
+ MGLCircle *circle = [MGLCircle circleWithCenterCoordinate:CLLocationCoordinate2DMake(0, 0) radius:0];
+ XCTAssertEqual(circle.coordinate.latitude, 0);
+ XCTAssertEqual(circle.coordinate.longitude, 0);
+ XCTAssertEqual(circle.radius, 0);
+ XCTAssertEqual(circle.numberOfVertices, 0ul);
+ XCTAssertEqual([circle linearRingWithNumberOfVertices:0].size(), 0ul);
+ }
+ {
+ MGLCircle *positiveCircle = [MGLCircle circleWithCenterCoordinate:CLLocationCoordinate2DMake(0, 0) radius:10];
+ XCTAssertEqual(positiveCircle.radius, 10);
+ MGLCircle *negativeCircle = [MGLCircle circleWithCenterCoordinate:CLLocationCoordinate2DMake(0, 0) radius:-10];
+ XCTAssertEqual(negativeCircle.radius, -10);
+ XCTAssertEqual(positiveCircle.numberOfVertices, negativeCircle.numberOfVertices);
+ }
+ {
+ MGLCircle *bigCircle = [MGLCircle circleWithCenterCoordinate:CLLocationCoordinate2DMake(0, 0) radius:1000];
+ XCTAssertEqual(bigCircle.radius, 1000);
+ XCTAssertEqual(bigCircle.numberOfVertices, 5261ul);
+ MGLCircle *biggerCircle = [MGLCircle circleWithCenterCoordinate:CLLocationCoordinate2DMake(0, 0) radius:10000];
+ XCTAssertEqual(biggerCircle.radius, 10000);
+ XCTAssertEqual(biggerCircle.numberOfVertices, 21600ul);
+ }
+ {
+ MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(0, 0));
+ MGLCircle *circle = [MGLCircle circleWithCoordinateBounds:bounds];
+ XCTAssertEqual(circle.coordinate.latitude, 0);
+ XCTAssertEqual(circle.coordinate.longitude, 0);
+ XCTAssertEqual(circle.radius, 0);
+ }
+ {
+ MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 0));
+ MGLCircle *circle = [MGLCircle circleWithCoordinateBounds:bounds];
+ XCTAssertEqual(circle.coordinate.latitude, 0.5);
+ XCTAssertEqual(circle.coordinate.longitude, 0);
+ XCTAssertGreaterThan(circle.radius, 0);
+ MGLCoordinateSpan span = MGLCoordinateBoundsGetCoordinateSpan(circle.coordinateBounds);
+ XCTAssertGreaterThan(span.latitudeDelta, 0);
+ XCTAssertGreaterThan(span.longitudeDelta, 0);
+ }
+ const CLLocationDistance earthRadius = 6378137.0;
+ {
+ MGLCircle *circle = [MGLCircle circleWithCenterCoordinate:CLLocationCoordinate2DMake(0, 0) radius:M_PI_2 * earthRadius];
+ XCTAssertEqual(circle.coordinate.latitude, 0);
+ XCTAssertEqual(circle.coordinate.longitude, 0);
+ XCTAssertEqual(circle.radius, M_PI_2 * earthRadius);
+ XCTAssertEqual([circle linearRingWithNumberOfVertices:128].size(), 128ul);
+ }
+ {
+ MGLCircle *circle = [MGLCircle circleWithCenterCoordinate:CLLocationCoordinate2DMake(23.5, 0) radius:M_PI_2 * earthRadius];
+ XCTAssertEqual(circle.coordinate.latitude, 23.5);
+ XCTAssertEqual(circle.coordinate.longitude, 0);
+ XCTAssertEqual(circle.radius, M_PI_2 * earthRadius);
+ XCTAssertEqual([circle linearRingWithNumberOfVertices:128].size(), 128ul + 3,
+ @"Polar cap should have extra vertices to cover North Pole.");
+ }
+ {
+ MGLCircle *circle = [MGLCircle circleWithCenterCoordinate:CLLocationCoordinate2DMake(-23.5, 20) radius:M_PI_2 * earthRadius];
+ XCTAssertEqual(circle.coordinate.latitude, -23.5);
+ XCTAssertEqual(circle.coordinate.longitude, 20);
+ XCTAssertEqual(circle.radius, M_PI_2 * earthRadius);
+ XCTAssertEqual([circle linearRingWithNumberOfVertices:128].size(), 128ul + 3,
+ @"Polar cap should have extra vertices to cover South Pole.");
+ }
+}
+
@end