From 910639168aaad8f06749c5be184ae50dbe9d566b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Sun, 28 Apr 2019 17:23:23 -0700 Subject: [ios, macos] Added circle geometry Added an MGLCircle class that generates a many-sided polygon under the hood. --- .../darwin/scripts/style-spec-overrides-v8.json | 2 +- platform/darwin/src/MGLCircle.h | 95 +++++++++++ platform/darwin/src/MGLCircle.mm | 177 +++++++++++++++++++++ platform/darwin/src/MGLCircleStyleLayer.h | 4 +- platform/darwin/src/MGLCircle_Private.h | 26 +++ platform/darwin/src/MGLGeometry.mm | 17 ++ platform/darwin/src/MGLGeometry_Private.h | 14 ++ platform/darwin/src/MGLMultiPoint_Private.h | 3 +- platform/darwin/src/MGLPolygon.h | 5 +- platform/darwin/src/MGLPolygon.mm | 2 +- platform/darwin/test/MGLGeometryTests.mm | 68 ++++++++ platform/ios/CHANGELOG.md | 2 + platform/ios/app/MBXViewController.m | 8 +- platform/ios/ios.xcodeproj/project.pbxproj | 18 +++ platform/ios/jazzy.yml | 1 + platform/ios/sdk-files.json | 3 + platform/ios/src/MGLMapView.h | 2 - platform/ios/src/MGLMapView.mm | 56 ++++++- platform/ios/src/MGLMapViewDelegate.h | 30 ++-- platform/ios/src/Mapbox.h | 1 + .../test/MGLMapViewDelegateIntegrationTests.swift | 2 + platform/macos/CHANGELOG.md | 2 + platform/macos/app/Base.lproj/MainMenu.xib | 4 +- platform/macos/app/MapDocument.m | 78 ++++++--- platform/macos/jazzy.yml | 1 + platform/macos/macos.xcodeproj/project.pbxproj | 12 ++ platform/macos/sdk-files.json | 3 + platform/macos/src/MGLMapView.mm | 63 ++++++-- platform/macos/src/MGLMapViewDelegate.h | 16 +- platform/macos/src/Mapbox.h | 1 + .../test/MGLMapViewDelegateIntegrationTests.swift | 2 + 31 files changed, 642 insertions(+), 76 deletions(-) create mode 100644 platform/darwin/src/MGLCircle.h create mode 100644 platform/darwin/src/MGLCircle.mm create mode 100644 platform/darwin/src/MGLCircle_Private.h 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 Dynamically style interactive points and Use images to cluster point data 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 Data-driven circles, Add multiple shapes from a single shape source, and Cluster point data 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 +#import + +#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 + spherical cap. 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 + +#import + +@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)linearRingWithNumberOfVertices:(NSUInteger)numberOfVertices { + CLLocationCoordinate2D center = self.coordinate; + CLLocationDistance radius = fabs(self.radius); + + mbgl::LinearRing 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)polygon { + mbgl::Polygon polygon; + polygon.push_back([self linearRingWithNumberOfVertices:self.numberOfVertices]); + return polygon; +} + +- (mbgl::Geometry)geometryObject { + return [self polygon]; +} + +- (NSDictionary *)geoJSONDictionary { + return @{ + @"type": @"Polygon", + @"coordinates": self.geoJSONGeometry, + }; +} + +- (NSArray *)geoJSONGeometry { + NSMutableArray *coordinates = [NSMutableArray array]; + + mbgl::LinearRing 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 )delegate { + + mbgl::FillAnnotation annotation { [self polygon] }; + annotation.opacity = { static_cast([delegate alphaForShapeAnnotation:self]) }; + annotation.outlineColor = { [delegate strokeColorForShapeAnnotation:self] }; + annotation.color = { [delegate fillColorForShape:self] }; + + return annotation; +} + +- (MGLCoordinateBounds)coordinateBounds { + mbgl::LinearRing 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 + +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)linearRingWithNumberOfVertices:(NSUInteger)numberOfVertices; + +/** Constructs a circle annotation object, asking the delegate for style values. */ +- (mbgl::Annotation)annotationObjectWithDelegate:(id )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 +#import #if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR #import @@ -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,11 +112,21 @@ 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. */ @@ -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 Add a polygon annotation 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([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 #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 diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 1940cd306f..633d27890b 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -4,6 +4,8 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ## 4.12.0 +* Added the `MGLCircle` class for adding physical circles to the map view as overlays or to a shape source as polygons. ([#14534](https://github.com/mapbox/mapbox-gl-native/pull/14534)) +* Deprecated the `-[MGLMapViewDelegate mapView:fillColorForPolygonAnnotation:]` method in favor of `-[MGLMapViewDelegate mapView:fillColorForShape:]`, which is also called for `MGLCircle` annotations. ([#14534](https://github.com/mapbox/mapbox-gl-native/pull/14534)) * Fixed an issue where `-[MGLMapView setVisibleCoordinates:count:edgePadding:direction:duration:animationTimingFunction:completionHandler:]` interpreted a negative `direction` as due north instead of maintaining the current direction. ([#14575](https://github.com/mapbox/mapbox-gl-native/pull/14575)) ## 4.11.0 diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 3335606f98..f98ee1244d 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -2128,9 +2128,13 @@ CLLocationCoordinate2D randomWorldCoordinate() { return [color colorWithAlphaComponent:0.9]; } -- (UIColor *)mapView:(__unused MGLMapView *)mapView fillColorForPolygonAnnotation:(__unused MGLPolygon *)annotation +- (UIColor *)mapView:(__unused MGLMapView *)mapView fillColorForShape:(MGLShape *)shape { - UIColor *color = annotation.pointCount > 3 ? [UIColor greenColor] : [UIColor redColor]; + UIColor *color = [UIColor redColor]; + if ([shape isKindOfClass:[MGLPolygon class]] && [(MGLPolygon *)shape pointCount] > 3) + { + color = [UIColor greenColor]; + } return [color colorWithAlphaComponent:0.5]; } diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index c1b0b875a2..d6e760ef06 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -516,6 +516,12 @@ DA35A2CB1CCAAAD200E826B2 /* NSValue+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DA35A2C81CCAAAD200E826B2 /* NSValue+MGLAdditions.m */; }; DA35A2CC1CCAAAD200E826B2 /* NSValue+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DA35A2C81CCAAAD200E826B2 /* NSValue+MGLAdditions.m */; }; DA35D0881E1A6309007DED41 /* one-liner.json in Resources */ = {isa = PBXBuildFile; fileRef = DA35D0871E1A6309007DED41 /* one-liner.json */; }; + DA44897E2270FFC5005B8357 /* MGLCircle.h in Headers */ = {isa = PBXBuildFile; fileRef = DA44897C2270FFC5005B8357 /* MGLCircle.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DA44897F2270FFC5005B8357 /* MGLCircle.h in Headers */ = {isa = PBXBuildFile; fileRef = DA44897C2270FFC5005B8357 /* MGLCircle.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DA4489802270FFC5005B8357 /* MGLCircle.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA44897D2270FFC5005B8357 /* MGLCircle.mm */; }; + DA4489812270FFC5005B8357 /* MGLCircle.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA44897D2270FFC5005B8357 /* MGLCircle.mm */; }; + DA448989227311C5005B8357 /* MGLCircle_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA448988227311C5005B8357 /* MGLCircle_Private.h */; }; + DA44898A227311C5005B8357 /* MGLCircle_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA448988227311C5005B8357 /* MGLCircle_Private.h */; }; DA5DB12A1FABF1EE001C2326 /* MGLMapAccessibilityElementTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5DB1291FABF1EE001C2326 /* MGLMapAccessibilityElementTests.m */; }; DA6408DB1DA4E7D300908C90 /* MGLVectorStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = DA6408D91DA4E7D300908C90 /* MGLVectorStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA6408DC1DA4E7D300908C90 /* MGLVectorStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = DA6408D91DA4E7D300908C90 /* MGLVectorStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1173,6 +1179,9 @@ DA35A2D11CCAB25200E826B2 /* jazzy.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = jazzy.yml; sourceTree = ""; }; DA35D0871E1A6309007DED41 /* one-liner.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "one-liner.json"; path = "../../darwin/test/one-liner.json"; sourceTree = ""; }; DA3C6FF21E2859E700F962BE /* test-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "test-Bridging-Header.h"; path = "../../darwin/test/test-Bridging-Header.h"; sourceTree = ""; }; + DA44897C2270FFC5005B8357 /* MGLCircle.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLCircle.h; sourceTree = ""; }; + DA44897D2270FFC5005B8357 /* MGLCircle.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLCircle.mm; sourceTree = ""; }; + DA448988227311C5005B8357 /* MGLCircle_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCircle_Private.h; sourceTree = ""; }; DA4A26961CB6E795000B7809 /* Mapbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Mapbox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DA57D4AA1EBA8ED300793288 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.lproj/Localizable.stringsdict; sourceTree = ""; }; DA57D4AB1EBA909900793288 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = lt; path = lt.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -2151,6 +2160,9 @@ children = ( CA65C4F721E9BB080068B0D4 /* MGLCluster.h */, DA8847E01CBAFA5100AB86E3 /* MGLAnnotation.h */, + DA448988227311C5005B8357 /* MGLCircle_Private.h */, + DA44897C2270FFC5005B8357 /* MGLCircle.h */, + DA44897D2270FFC5005B8357 /* MGLCircle.mm */, DAD1656A1CF41981001FF4B9 /* MGLFeature_Private.h */, DAD165691CF41981001FF4B9 /* MGLFeature.h */, DAD1656B1CF41981001FF4B9 /* MGLFeature.mm */, @@ -2314,6 +2326,7 @@ DA8848231CBAFA6200AB86E3 /* MGLOfflineStorage_Private.h in Headers */, 404326891D5B9B27007111BD /* MGLAnnotationContainerView_Private.h in Headers */, CA55CD41202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */, + DA44897E2270FFC5005B8357 /* MGLCircle.h in Headers */, DA88483B1CBAFB8500AB86E3 /* MGLCalloutView.h in Headers */, 35E0CFE61D3E501500188327 /* MGLStyle_Private.h in Headers */, 3510FFF01D6D9D8C00F413B2 /* NSExpression+MGLAdditions.h in Headers */, @@ -2373,6 +2386,7 @@ 74CB5ED1219B286400102936 /* MGLSymbolStyleLayer_Private.h in Headers */, 9221BAAD2069843A0054BDF4 /* MGLTilePyramidOfflineRegion_Private.h in Headers */, 96F3F73C1F57124B003E2D2C /* MGLUserLocationHeadingIndicator.h in Headers */, + DA448989227311C5005B8357 /* MGLCircle_Private.h in Headers */, 408AA8571DAEDA1700022900 /* NSDictionary+MGLAdditions.h in Headers */, DA88483F1CBAFB8500AB86E3 /* MGLUserLocation.h in Headers */, 558DE7A01E5615E400C7916D /* MGLFoundation_Private.h in Headers */, @@ -2480,6 +2494,7 @@ DA35A2CA1CCAAAD200E826B2 /* NSValue+MGLAdditions.h in Headers */, 350098BC1D480108004B2AF0 /* MGLVectorTileSource.h in Headers */, FA68F14B1E9D656600F9F6C2 /* MGLFillExtrusionStyleLayer.h in Headers */, + DA44897F2270FFC5005B8357 /* MGLCircle.h in Headers */, 96E516DE200054F700A02306 /* MGLGeometry_Private.h in Headers */, 353933FC1D3FB7C0003F57D7 /* MGLRasterStyleLayer.h in Headers */, 3566C76D1D4A8DFA008152BC /* MGLRasterTileSource.h in Headers */, @@ -2573,6 +2588,7 @@ 353933F31D3FB753003F57D7 /* MGLCircleStyleLayer.h in Headers */, 558DE7A11E5615E400C7916D /* MGLFoundation_Private.h in Headers */, 96E516F820005A3000A02306 /* MGLCompactCalloutView.h in Headers */, + DA44898A227311C5005B8357 /* MGLCircle_Private.h in Headers */, 96E516E22000551900A02306 /* MGLPointCollection_Private.h in Headers */, 3538AA1E1D542239008EC33D /* MGLForegroundStyleLayer.h in Headers */, 30E578181DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */, @@ -3148,6 +3164,7 @@ 96036A03200565C700510F3D /* NSOrthography+MGLAdditions.m in Sources */, ACA65F592140697200537748 /* MMEDispatchManager.m in Sources */, 40834BF31FE05E1800C1BD0D /* MMETimerManager.m in Sources */, + DA4489802270FFC5005B8357 /* MGLCircle.mm in Sources */, 35136D421D42274500C20EFD /* MGLRasterStyleLayer.mm in Sources */, 3538AA1F1D542239008EC33D /* MGLForegroundStyleLayer.mm in Sources */, AC1B0918221CA14D00DB56C8 /* CLLocationManager+MMEMobileEvents.m in Sources */, @@ -3272,6 +3289,7 @@ ACA65F5A2140697200537748 /* MMEDispatchManager.m in Sources */, 3538AA201D542239008EC33D /* MGLForegroundStyleLayer.mm in Sources */, DA00FC911D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */, + DA4489812270FFC5005B8357 /* MGLCircle.mm in Sources */, 40834C051FE05E1800C1BD0D /* MMEDate.m in Sources */, AC1B0919221CA14D00DB56C8 /* CLLocationManager+MMEMobileEvents.m in Sources */, 40834BFA1FE05E1800C1BD0D /* CLLocation+MMEMobileEvents.m in Sources */, diff --git a/platform/ios/jazzy.yml b/platform/ios/jazzy.yml index d459e93613..29ecc6b66e 100644 --- a/platform/ios/jazzy.yml +++ b/platform/ios/jazzy.yml @@ -42,6 +42,7 @@ custom_categories: - MGLMultiPoint - MGLPointAnnotation - MGLPointCollection + - MGLCircle - MGLPolygon - MGLPolyline - MGLMultiPolygon diff --git a/platform/ios/sdk-files.json b/platform/ios/sdk-files.json index 3031a87fd6..1e9e46d1fe 100644 --- a/platform/ios/sdk-files.json +++ b/platform/ios/sdk-files.json @@ -33,6 +33,7 @@ "platform/ios/src/NSOrthography+MGLAdditions.m", "platform/ios/vendor/mapbox-events-ios/MapboxMobileEvents/MMEDispatchManager.m", "platform/ios/vendor/mapbox-events-ios/MapboxMobileEvents/MMETimerManager.m", + "platform/darwin/src/MGLCircle.mm", "platform/darwin/src/MGLRasterStyleLayer.mm", "platform/darwin/src/MGLForegroundStyleLayer.mm", "platform/ios/vendor/mapbox-events-ios/MapboxMobileEvents/CLLocationManager+MMEMobileEvents.m", @@ -132,6 +133,7 @@ "MGLPolyline.h": "platform/darwin/src/MGLPolyline.h", "MGLStyleLayer.h": "platform/darwin/src/MGLStyleLayer.h", "MGLCameraChangeReason.h": "platform/ios/src/MGLCameraChangeReason.h", + "MGLCircle.h": "platform/darwin/src/MGLCircle.h", "MGLCalloutView.h": "platform/ios/src/MGLCalloutView.h", "NSExpression+MGLAdditions.h": "platform/darwin/src/NSExpression+MGLAdditions.h", "MGLShape.h": "platform/darwin/src/MGLShape.h", @@ -243,6 +245,7 @@ "MGLSymbolStyleLayer_Private.h": "platform/darwin/src/MGLSymbolStyleLayer_Private.h", "MGLTilePyramidOfflineRegion_Private.h": "platform/darwin/src/MGLTilePyramidOfflineRegion_Private.h", "MGLUserLocationHeadingIndicator.h": "platform/ios/src/MGLUserLocationHeadingIndicator.h", + "MGLCircle_Private.h": "platform/darwin/src/MGLCircle_Private.h", "NSDictionary+MGLAdditions.h": "platform/darwin/src/NSDictionary+MGLAdditions.h", "MGLFoundation_Private.h": "platform/darwin/src/MGLFoundation_Private.h", "MGLUserLocationHeadingBeamLayer.h": "platform/ios/src/MGLUserLocationHeadingBeamLayer.h", diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h index 63bd28fc0c..ed5462a755 100644 --- a/platform/ios/src/MGLMapView.h +++ b/platform/ios/src/MGLMapView.h @@ -11,8 +11,6 @@ NS_ASSUME_NONNULL_BEGIN @class MGLAnnotationView; @class MGLAnnotationImage; @class MGLUserLocation; -@class MGLPolyline; -@class MGLPolygon; @class MGLShape; @class MGLStyle; diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index fdd91dacd7..f43bb93115 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -38,6 +38,7 @@ #import "MGLFeature_Private.h" #import "MGLGeometry_Private.h" #import "MGLMultiPoint_Private.h" +#import "MGLCircle_Private.h" #import "MGLOfflineStorage_Private.h" #import "MGLVectorTileSource_Private.h" #import "MGLFoundation_Private.h" @@ -317,6 +318,7 @@ public: BOOL _delegateHasAlphasForShapeAnnotations; BOOL _delegateHasStrokeColorsForShapeAnnotations; + BOOL _delegateHasFillColorsForPolygonAnnotations; BOOL _delegateHasFillColorsForShapeAnnotations; BOOL _delegateHasLineWidthsForShapeAnnotations; @@ -826,7 +828,8 @@ public: _delegateHasAlphasForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)]; _delegateHasStrokeColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)]; - _delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)]; + _delegateHasFillColorsForPolygonAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)]; + _delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForShape:)]; _delegateHasLineWidthsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)]; } @@ -2557,7 +2560,7 @@ public: [MGLMapboxEvents ensureMetricsOptoutExists]; } } - else if ([keyPath isEqualToString:@"coordinate"] && [object conformsToProtocol:@protocol(MGLAnnotation)] && ![object isKindOfClass:[MGLMultiPoint class]]) + else if ([keyPath isEqualToString:@"coordinate"] && [object conformsToProtocol:@protocol(MGLAnnotation)] && ![object isKindOfClass:[MGLMultiPoint class]] && ![object isKindOfClass:[MGLCircle class]]) { id annotation = object; MGLAnnotationTag annotationTag = (MGLAnnotationTag)(NSUInteger)context; @@ -2588,7 +2591,8 @@ public: } } } - else if ([keyPath isEqualToString:@"coordinates"] && [object isKindOfClass:[MGLMultiPoint class]]) + else if (([keyPath isEqualToString:@"coordinates"] && [object isKindOfClass:[MGLMultiPoint class]]) || + (([keyPath isEqualToString:@"coordinate"] || [keyPath isEqualToString:@"radius"]) && [object isKindOfClass:[MGLCircle class]])) { MGLMultiPoint *annotation = object; MGLAnnotationTag annotationTag = (MGLAnnotationTag)(NSUInteger)context; @@ -4222,6 +4226,21 @@ public: [(NSObject *)annotation addObserver:self forKeyPath:@"coordinates" options:0 context:(void *)(NSUInteger)annotationTag]; } + else if ([annotation isKindOfClass:[MGLCircle class]]) + { + // The circle knows how to style itself (with the map view’s help). + MGLCircle *circle = (MGLCircle *)annotation; + + _isChangingAnnotationLayers = YES; + MGLAnnotationTag annotationTag = _mbglMap->addAnnotation([circle annotationObjectWithDelegate:self]); + MGLAnnotationContext context; + context.annotation = annotation; + _annotationContextsByAnnotationTag[annotationTag] = context; + _annotationTagsByAnnotation[annotation] = annotationTag; + + [(NSObject *)annotation addObserver:self forKeyPath:@"coordinate" options:0 context:(void *)(NSUInteger)annotationTag]; + [(NSObject *)annotation addObserver:self forKeyPath:@"radius" options:0 context:(void *)(NSUInteger)annotationTag]; + } else if ( ! [annotation isKindOfClass:[MGLMultiPolyline class]] && ![annotation isKindOfClass:[MGLMultiPolygon class]] && ![annotation isKindOfClass:[MGLShapeCollection class]] @@ -4439,11 +4458,26 @@ public: return color.mgl_color; } -- (mbgl::Color)fillColorForPolygonAnnotation:(MGLPolygon *)annotation +- (mbgl::Color)fillColorForShape:(MGLShape *)shape { - UIColor *color = (_delegateHasFillColorsForShapeAnnotations - ? [self.delegate mapView:self fillColorForPolygonAnnotation:annotation] - : self.tintColor); + UIColor *color = self.tintColor; + if (_delegateHasFillColorsForShapeAnnotations) + { + color = [self.delegate mapView:self fillColorForShape:shape]; + } + else if (_delegateHasFillColorsForPolygonAnnotations && [shape isKindOfClass:[MGLPolygon class]]) + { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"-[MGLMapViewDelegate mapView:fillColorForPolygonAnnotation:] is deprecated; " + @"use -[MGLMapViewDelegate mapView:fillColorForShape:] instead." + @"This warning will only appear once."); + }); + color = [self.delegate mapView:self fillColorForPolygonAnnotation:(MGLPolygon *)shape]; +#pragma clang diagnostic pop + } return color.mgl_color; } @@ -4535,6 +4569,10 @@ public: { [(NSObject *)annotation removeObserver:self forKeyPath:@"coordinates" context:(void *)(NSUInteger)annotationTag]; } + if ([annotation isKindOfClass:[MGLCircle class]]) + { + [(NSObject *)annotation removeObserver:self forKeyPath:@"radius" context:(void *)(NSUInteger)annotationTag]; + } _isChangingAnnotationLayers = YES; self.mbglMap.removeAnnotation(annotationTag); @@ -5286,6 +5324,10 @@ public: { bounds.extend(MGLLatLngBoundsFromCoordinateBounds(((id )annotation).overlayBounds)); } + else if ([annotation isKindOfClass:[MGLCircle class]]) + { + bounds.extend(MGLLatLngBoundsFromCoordinateBounds(((MGLCircle *)annotation).coordinateBounds)); + } else { bounds.extend(MGLLatLngFromLocationCoordinate2D(annotation.coordinate)); diff --git a/platform/ios/src/MGLMapViewDelegate.h b/platform/ios/src/MGLMapViewDelegate.h index 055d4c9517..c6128ae953 100644 --- a/platform/ios/src/MGLMapViewDelegate.h +++ b/platform/ios/src/MGLMapViewDelegate.h @@ -1,11 +1,19 @@ #import -#import "Mapbox.h" #import "MGLCameraChangeReason.h" NS_ASSUME_NONNULL_BEGIN +@protocol MGLAnnotation; +@protocol MGLCalloutView; +@class MGLAnnotationImage; +@class MGLAnnotationView; +@class MGLCircle; +@class MGLMapCamera; @class MGLMapView; +@class MGLPolygon; +@class MGLPolyline; +@class MGLShape; /** The `MGLMapViewDelegate` protocol defines a set of optional methods that you @@ -422,25 +430,25 @@ NS_ASSUME_NONNULL_BEGIN */ - (UIColor *)mapView:(MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation; +- (UIColor *)mapView:(MGLMapView *)mapView fillColorForPolygonAnnotation:(MGLPolygon *)annotation __attribute__((deprecated("", "-mapView:fillColorForShape:"))); + /** - Returns the color to use when rendering the fill of a polygon annotation. + Returns the color to use when rendering the fill of a shape annotation. + + This method is only called for `MGLPolygon` and `MGLCircle` annotations. It is + not possible to fill a polyline or point annotation. - The default fill color is the map view’s tint color. If a pattern color is + The default fill color is the selected menu item color. If a pattern color is specified, the result is undefined. Opacity may be set by specifying an alpha component. The default alpha value is `1.0` and results in a completely opaque shape. @param mapView The map view rendering the polygon annotation. - @param annotation The annotation being rendered. - @return The polygon’s interior fill color. - - #### Related examples - See the Add - a polygon annotation example to learn how to modify the color of a an - `MGLPolygon` at runtime. + @param shape The annotation being rendered. + @return The shape’s fill color. */ -- (UIColor *)mapView:(MGLMapView *)mapView fillColorForPolygonAnnotation:(MGLPolygon *)annotation; +- (UIColor *)mapView:(MGLMapView *)mapView fillColorForShape:(MGLShape *)shape; /** Returns the line width in points to use when rendering the outline of a diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h index 635bda490f..b4f5a707e9 100644 --- a/platform/ios/src/Mapbox.h +++ b/platform/ios/src/Mapbox.h @@ -34,6 +34,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLPointCollection.h" #import "MGLPolygon.h" #import "MGLPolyline.h" +#import "MGLCircle.h" #import "MGLShape.h" #import "MGLShapeCollection.h" #import "MGLStyle.h" diff --git a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift index 1330281faa..e117298d5a 100644 --- a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift +++ b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift @@ -82,6 +82,8 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapView(_ mapView: MGLMapView, strokeColorForShapeAnnotation annotation: MGLShape) -> UIColor { return .black } func mapView(_ mapView: MGLMapView, fillColorForPolygonAnnotation annotation: MGLPolygon) -> UIColor { return .black } + + func mapView(_ mapView: MGLMapView, fillColorFor shape: MGLShape) -> UIColor { return .black } func mapView(_ mapView: MGLMapView, leftCalloutAccessoryViewFor annotation: MGLAnnotation) -> UIView? { return nil } diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 98bdda0abc..f0ba162cf6 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -20,6 +20,8 @@ ### Annotations +* Added the `MGLCircle` class for adding physical circles to the map view as overlays or to a shape source as polygons. ([#14534](https://github.com/mapbox/mapbox-gl-native/pull/14534)) +* Deprecated the `-[MGLMapViewDelegate mapView:fillColorForPolygonAnnotation:]` method in favor of `-[MGLMapViewDelegate mapView:fillColorForShape:]`, which is also called for `MGLCircle` annotations. ([#14534](https://github.com/mapbox/mapbox-gl-native/pull/14534)) * Fixed a bug with `MGLMapView.visibleAnnotations` that resulted in incorrect results and performance degradation. ([#13745](https://github.com/mapbox/mapbox-gl-native/pull/13745)) * Fixed a bug where selecting partially on-screen annotations (without a callout) would move the map. ([#13727](https://github.com/mapbox/mapbox-gl-native/pull/13727)) diff --git a/platform/macos/app/Base.lproj/MainMenu.xib b/platform/macos/app/Base.lproj/MainMenu.xib index 6f8f24ce99..a359570d8a 100644 --- a/platform/macos/app/Base.lproj/MainMenu.xib +++ b/platform/macos/app/Base.lproj/MainMenu.xib @@ -542,9 +542,9 @@ - + - + diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 213aa33107..7716dfeb05 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -99,7 +99,7 @@ NSArray> *MBXFlattenedShapes(NSArray> *sha BOOL _showsToolTipsOnDroppedPins; BOOL _randomizesCursorsOnDroppedPins; BOOL _isTouringWorld; - BOOL _isShowingPolygonAndPolylineAnnotations; + BOOL _isShowingNightAndLighthouse; BOOL _isShowingAnimatedAnnotation; MGLMapSnapshotter *_snapshotter; @@ -627,7 +627,7 @@ NSArray> *MBXFlattenedShapes(NSArray> *sha - (IBAction)removeAllAnnotations:(id)sender { [self.mapView removeAnnotations:self.mapView.annotations]; - _isShowingPolygonAndPolylineAnnotations = NO; + _isShowingNightAndLighthouse = NO; _isShowingAnimatedAnnotation = NO; } @@ -673,33 +673,55 @@ NSArray> *MBXFlattenedShapes(NSArray> *sha self.mapView.camera = self.mapView.camera; } -- (IBAction)drawPolygonAndPolyLineAnnotations:(id)sender { +- (IBAction)addNightAndLighthouse:(id)sender { - if (_isShowingPolygonAndPolylineAnnotations) { - [self removeAllAnnotations:sender]; + if (_isShowingNightAndLighthouse) { return; } - _isShowingPolygonAndPolylineAnnotations = YES; + _isShowingNightAndLighthouse = YES; - // Pacific Northwest triangle - CLLocationCoordinate2D triangleCoordinates[3] = { - CLLocationCoordinate2DMake(44, -122), - CLLocationCoordinate2DMake(46, -122), - CLLocationCoordinate2DMake(46, -121) + // Daylight map, produced by multiple spherical caps simulating night + // https://en.wikipedia.org/wiki/Twilight + CLLocationDistance nightRadius = M_PI_2 * 6378137.0; + CLLocationDistance nightCapRadii[] = { + nightRadius, // civil twilight + nightRadius * (1.0 - 18.0 / 180 * 2), // nautical twilight + nightRadius * (1.0 - 12.0 / 180 * 2), // astronomical twilight + nightRadius * (1.0 - 6.0 / 180 * 2), // night }; - MGLPolygon *triangle = [MGLPolygon polygonWithCoordinates:triangleCoordinates count:3]; - [self.mapView addAnnotation:triangle]; - - // West coast line - CLLocationCoordinate2D lineCoordinates[4] = { - CLLocationCoordinate2DMake(47.6025, -122.3327), - CLLocationCoordinate2DMake(45.5189, -122.6726), - CLLocationCoordinate2DMake(37.7790, -122.4177), - CLLocationCoordinate2DMake(34.0532, -118.2349) - }; - MGLPolyline *line = [MGLPolyline polylineWithCoordinates:lineCoordinates count:4]; - [self.mapView addAnnotation:line]; + NSMutableArray *nightCaps = [NSMutableArray array]; + for (size_t i = 0; i < sizeof(nightCapRadii) / sizeof(nightCapRadii[0]); i++) { + MGLCircle *cap = [MGLCircle circleWithCenterCoordinate:CLLocationCoordinate2DMake(23.5, 0) + radius:nightCapRadii[i]]; + cap.title = @"Night"; + [nightCaps addObject:cap]; + } + [self.mapView addAnnotations:nightCaps]; + + // Concentric circles around Boston Light + // https://en.wikipedia.org/wiki/Boston_Light + CLLocationCoordinate2D epicenter = CLLocationCoordinate2DMake(42.32792025, -70.8901050288306); + NSMutableArray *concentricCircles = [NSMutableArray array]; + for (NSUInteger magnitude = 0; magnitude < 8; magnitude++) { + CLLocationDistance radius = exp(magnitude); + MGLCircle *circle = [MGLCircle circleWithCenterCoordinate:epicenter radius:radius]; + [concentricCircles addObject:circle]; + } + [self.mapView addAnnotations:concentricCircles]; + [self.mapView showAnnotations:@[concentricCircles[5]] animated:NO]; + + __block NSUInteger stepCount = 0; + [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { + // Pulse each circle for a minute. + [concentricCircles enumerateObjectsUsingBlock:^(MGLCircle * _Nonnull circle, NSUInteger magnitude, BOOL * _Nonnull stop) { + circle.radius = exp(magnitude) + exp(magnitude - 1) * sin(M_PI / 10 * stepCount); + }]; + + if (++stepCount >= 60) { + [timer invalidate]; + } + }]; } - (IBAction)drawAnimatedAnnotation:(id)sender { @@ -1249,8 +1271,8 @@ NSArray> *MBXFlattenedShapes(NSArray> *sha if (menuItem.action == @selector(dropManyPins:)) { return YES; } - if (menuItem.action == @selector(drawPolygonAndPolyLineAnnotations:)) { - return !_isShowingPolygonAndPolylineAnnotations; + if (menuItem.action == @selector(addNightAndLighthouse:)) { + return !_isShowingNightAndLighthouse; } if (menuItem.action == @selector(drawAnimatedAnnotation:)) { return !_isShowingAnimatedAnnotation; @@ -1442,7 +1464,11 @@ NSArray> *MBXFlattenedShapes(NSArray> *sha } - (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation { - return 0.8; + return [annotation isKindOfClass:[MGLCircle class]] ? 0.1 : 0.8; +} + +- (NSColor *)mapView:(MGLMapView *)mapView fillColorForShape:(MGLShape *)shape { + return shape.title ? [NSColor blackColor] : [NSColor whiteColor]; } #pragma mark - MGLComputedShapeSourceDataSource diff --git a/platform/macos/jazzy.yml b/platform/macos/jazzy.yml index 381e6f8b33..3cea18e80d 100644 --- a/platform/macos/jazzy.yml +++ b/platform/macos/jazzy.yml @@ -41,6 +41,7 @@ custom_categories: - MGLMultiPoint - MGLPointAnnotation - MGLPointCollection + - MGLCircle - MGLPolygon - MGLPolyline - MGLMultiPolygon diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index 91ed2f4cfa..e0c584b0ad 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -145,6 +145,9 @@ DA35A2CF1CCAAED300E826B2 /* NSValue+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = DA35A2CD1CCAAED300E826B2 /* NSValue+MGLAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA35A2D01CCAAED300E826B2 /* NSValue+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DA35A2CE1CCAAED300E826B2 /* NSValue+MGLAdditions.m */; }; DA35D08A1E1A631B007DED41 /* one-liner.json in Resources */ = {isa = PBXBuildFile; fileRef = DA35D0891E1A631B007DED41 /* one-liner.json */; }; + DA44898422730B4A005B8357 /* MGLCircle.h in Headers */ = {isa = PBXBuildFile; fileRef = DA44898222730B49005B8357 /* MGLCircle.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DA44898522730B4A005B8357 /* MGLCircle.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA44898322730B4A005B8357 /* MGLCircle.mm */; }; + DA44898722731139005B8357 /* MGLCircle_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA44898622731139005B8357 /* MGLCircle_Private.h */; }; DA551B821DB496AC0009AFAF /* MGLTileSource.h in Headers */ = {isa = PBXBuildFile; fileRef = DA551B7F1DB496AC0009AFAF /* MGLTileSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA551B831DB496AC0009AFAF /* MGLTileSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA551B801DB496AC0009AFAF /* MGLTileSource_Private.h */; }; DA551B841DB496AC0009AFAF /* MGLTileSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA551B811DB496AC0009AFAF /* MGLTileSource.mm */; }; @@ -488,6 +491,9 @@ DA35A2CD1CCAAED300E826B2 /* NSValue+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSValue+MGLAdditions.h"; sourceTree = ""; }; DA35A2CE1CCAAED300E826B2 /* NSValue+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSValue+MGLAdditions.m"; sourceTree = ""; }; DA35D0891E1A631B007DED41 /* one-liner.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "one-liner.json"; path = "../../darwin/test/one-liner.json"; sourceTree = ""; }; + DA44898222730B49005B8357 /* MGLCircle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCircle.h; sourceTree = ""; }; + DA44898322730B4A005B8357 /* MGLCircle.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLCircle.mm; sourceTree = ""; }; + DA44898622731139005B8357 /* MGLCircle_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLCircle_Private.h; sourceTree = ""; }; DA551B7F1DB496AC0009AFAF /* MGLTileSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLTileSource.h; sourceTree = ""; }; DA551B801DB496AC0009AFAF /* MGLTileSource_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLTileSource_Private.h; sourceTree = ""; }; DA551B811DB496AC0009AFAF /* MGLTileSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLTileSource.mm; sourceTree = ""; }; @@ -1047,6 +1053,9 @@ isa = PBXGroup; children = ( DAE6C34B1CC31E0400DB3429 /* MGLAnnotation.h */, + DA44898622731139005B8357 /* MGLCircle_Private.h */, + DA44898222730B49005B8357 /* MGLCircle.h */, + DA44898322730B4A005B8357 /* MGLCircle.mm */, CA4045C4216720D700B356E1 /* MGLCluster.h */, DACC22171CF3D4F700D220D9 /* MGLFeature_Private.h */, DACC22121CF3D3E200D220D9 /* MGLFeature.h */, @@ -1361,6 +1370,7 @@ DAE6C3621CC31E0400DB3429 /* MGLOverlay.h in Headers */, DAE6C3651CC31E0400DB3429 /* MGLPolyline.h in Headers */, DAE6C39A1CC31E2A00DB3429 /* NSProcessInfo+MGLAdditions.h in Headers */, + DA44898722731139005B8357 /* MGLCircle_Private.h in Headers */, 92F2C3EB1F0E3A1900268EC0 /* MGLRendererFrontend.h in Headers */, DA8F258B1D51CA540010E6B5 /* MGLLineStyleLayer.h in Headers */, 35C6DF841E214C0400ACA483 /* MGLDistanceFormatter.h in Headers */, @@ -1370,6 +1380,7 @@ 359819591E02F611008FC139 /* NSCoder+MGLAdditions.h in Headers */, DAE6C38E1CC31E2A00DB3429 /* MGLOfflineStorage_Private.h in Headers */, 747ABE61219B2C0000523B67 /* MGLLineStyleLayer_Private.h in Headers */, + DA44898422730B4A005B8357 /* MGLCircle.h in Headers */, 747ABE5F219B2BED00523B67 /* MGLHillshadeStyleLayer_Private.h in Headers */, DA87A9A01DC9DC6200810D09 /* MGLValueEvaluator.h in Headers */, 8946239D200E744800DA8EF2 /* MGLHeatmapStyleLayer.h in Headers */, @@ -1652,6 +1663,7 @@ 35602BFB1D3EA99F0050646F /* MGLFillStyleLayer.mm in Sources */, DAE6C3931CC31E2A00DB3429 /* MGLShape.mm in Sources */, 352742861D4C244700A1ECE6 /* MGLRasterTileSource.mm in Sources */, + DA44898522730B4A005B8357 /* MGLCircle.mm in Sources */, 558DE7A71E56161C00C7916D /* MGLFoundation.mm in Sources */, DAE6C39D1CC31E2A00DB3429 /* NSString+MGLAdditions.m in Sources */, 3598195A1E02F611008FC139 /* NSCoder+MGLAdditions.mm in Sources */, diff --git a/platform/macos/sdk-files.json b/platform/macos/sdk-files.json index 4448de1f5b..bb68180f65 100644 --- a/platform/macos/sdk-files.json +++ b/platform/macos/sdk-files.json @@ -23,6 +23,7 @@ "platform/darwin/src/MGLFillStyleLayer.mm", "platform/darwin/src/MGLShape.mm", "platform/darwin/src/MGLRasterTileSource.mm", + "platform/darwin/src/MGLCircle.mm", "platform/darwin/src/MGLFoundation.mm", "platform/darwin/src/NSString+MGLAdditions.m", "platform/darwin/src/NSCoder+MGLAdditions.mm", @@ -118,6 +119,7 @@ "MGLPolyline.h": "platform/darwin/src/MGLPolyline.h", "MGLLineStyleLayer.h": "platform/darwin/src/MGLLineStyleLayer.h", "MGLDistanceFormatter.h": "platform/darwin/src/MGLDistanceFormatter.h", + "MGLCircle.h": "platform/darwin/src/MGLCircle.h", "MGLHeatmapStyleLayer.h": "platform/darwin/src/MGLHeatmapStyleLayer.h", "MGLOfflineRegion.h": "platform/darwin/src/MGLOfflineRegion.h", "MGLTilePyramidOfflineRegion.h": "platform/darwin/src/MGLTilePyramidOfflineRegion.h", @@ -178,6 +180,7 @@ "NSCompoundPredicate+MGLAdditions.h": "platform/darwin/src/NSCompoundPredicate+MGLAdditions.h", "MGLSymbolStyleLayer_Private.h": "platform/darwin/src/MGLSymbolStyleLayer_Private.h", "NSProcessInfo+MGLAdditions.h": "platform/macos/src/NSProcessInfo+MGLAdditions.h", + "MGLCircle_Private.h": "platform/darwin/src/MGLCircle_Private.h", "MGLRendererFrontend.h": "platform/darwin/src/MGLRendererFrontend.h", "NSValue+MGLStyleAttributeAdditions.h": "platform/darwin/src/NSValue+MGLStyleAttributeAdditions.h", "NSImage+MGLAdditions.h": "platform/macos/src/NSImage+MGLAdditions.h", diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm index e9259cf907..9c1cc890f6 100644 --- a/platform/macos/src/MGLMapView.mm +++ b/platform/macos/src/MGLMapView.mm @@ -21,6 +21,7 @@ #import "MGLMapCamera.h" #import "MGLPolygon.h" #import "MGLPolyline.h" +#import "MGLCircle_Private.h" #import "MGLAnnotationImage.h" #import "MGLMapViewDelegate.h" #import "MGLImageSource.h" @@ -194,6 +195,7 @@ public: BOOL _delegateHasAlphasForShapeAnnotations; BOOL _delegateHasStrokeColorsForShapeAnnotations; + BOOL _delegateHasFillColorsForPolygonAnnotations; BOOL _delegateHasFillColorsForShapeAnnotations; BOOL _delegateHasLineWidthsForShapeAnnotations; @@ -566,7 +568,8 @@ public: [self adjustContentInsets]; } else if ([keyPath isEqualToString:@"coordinate"] && [object conformsToProtocol:@protocol(MGLAnnotation)] && - ![object isKindOfClass:[MGLMultiPoint class]]) { + ![object isKindOfClass:[MGLMultiPoint class]] && + ![object isKindOfClass:[MGLCircle class]]) { id annotation = object; MGLAnnotationTag annotationTag = (MGLAnnotationTag)(NSUInteger)context; // We can get here because a subclass registered itself as an observer @@ -581,8 +584,11 @@ public: _mbglMap->updateAnnotation(annotationTag, mbgl::SymbolAnnotation { point, annotationImage.styleIconIdentifier.UTF8String ?: "" }); [self updateAnnotationCallouts]; } - } else if ([keyPath isEqualToString:@"coordinates"] && - [object isKindOfClass:[MGLMultiPoint class]]) { + } else if (([keyPath isEqualToString:@"coordinates"] && + [object isKindOfClass:[MGLMultiPoint class]]) || + (([keyPath isEqualToString:@"coordinate"] || + [keyPath isEqualToString:@"radius"]) && + [object isKindOfClass:[MGLCircle class]])) { MGLMultiPoint *annotation = object; MGLAnnotationTag annotationTag = (MGLAnnotationTag)(NSUInteger)context; // We can get here because a subclass registered itself as an observer @@ -609,7 +615,8 @@ public: // hot loop, namely the annotation style methods. _delegateHasAlphasForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)]; _delegateHasStrokeColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)]; - _delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)]; + _delegateHasFillColorsForPolygonAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)]; + _delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForShape:)]; _delegateHasLineWidthsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)]; #pragma clang diagnostic push @@ -1979,6 +1986,19 @@ public: _annotationTagsByAnnotation[annotation] = annotationTag; [(NSObject *)annotation addObserver:self forKeyPath:@"coordinates" options:0 context:(void *)(NSUInteger)annotationTag]; + } else if ([annotation isKindOfClass:[MGLCircle class]]) { + // The circle knows how to style itself (with the map view’s help). + MGLCircle *circle = (MGLCircle *)annotation; + + _isChangingAnnotationLayers = YES; + MGLAnnotationTag annotationTag = _mbglMap->addAnnotation([circle annotationObjectWithDelegate:self]); + MGLAnnotationContext context; + context.annotation = annotation; + _annotationContextsByAnnotationTag[annotationTag] = context; + _annotationTagsByAnnotation[annotation] = annotationTag; + + [(NSObject *)annotation addObserver:self forKeyPath:@"coordinate" options:0 context:(void *)(NSUInteger)annotationTag]; + [(NSObject *)annotation addObserver:self forKeyPath:@"radius" options:0 context:(void *)(NSUInteger)annotationTag]; } else if (![annotation isKindOfClass:[MGLMultiPolyline class]] && ![annotation isKindOfClass:[MGLMultiPolygon class]] && ![annotation isKindOfClass:[MGLShapeCollection class]] @@ -2111,6 +2131,9 @@ public: } else if ([annotation isKindOfClass:[MGLMultiPoint class]]) { [(NSObject *)annotation removeObserver:self forKeyPath:@"coordinates" context:(void *)(NSUInteger)annotationTag]; } + if ([annotation isKindOfClass:[MGLCircle class]]) { + [(NSObject *)annotation removeObserver:self forKeyPath:@"radius" context:(void *)(NSUInteger)annotationTag]; + } _isChangingAnnotationLayers = YES; _mbglMap->removeAnnotation(annotationTag); @@ -2477,14 +2500,12 @@ public: mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); - for (id annotation in annotations) - { - if ([annotation conformsToProtocol:@protocol(MGLOverlay)]) - { + for (id annotation in annotations) { + if ([annotation conformsToProtocol:@protocol(MGLOverlay)]) { bounds.extend(MGLLatLngBoundsFromCoordinateBounds(((id )annotation).overlayBounds)); - } - else - { + } else if ([annotation isKindOfClass:[MGLCircle class]]) { + bounds.extend(MGLLatLngBoundsFromCoordinateBounds(((MGLCircle *)annotation).coordinateBounds)); + } else { bounds.extend(MGLLatLngFromLocationCoordinate2D(annotation.coordinate)); } } @@ -2634,10 +2655,22 @@ public: return color.mgl_color; } -- (mbgl::Color)fillColorForPolygonAnnotation:(MGLPolygon *)annotation { - NSColor *color = (_delegateHasFillColorsForShapeAnnotations - ? [self.delegate mapView:self fillColorForPolygonAnnotation:annotation] - : [NSColor selectedMenuItemColor]); +- (mbgl::Color)fillColorForShape:(MGLShape *)shape { + NSColor *color = [NSColor selectedMenuItemColor]; + if (_delegateHasFillColorsForShapeAnnotations) { + color = [self.delegate mapView:self fillColorForShape:shape]; + } else if (_delegateHasFillColorsForPolygonAnnotations && [shape isKindOfClass:[MGLPolygon class]]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"-[MGLMapViewDelegate mapView:fillColorForPolygonAnnotation:] is deprecated; " + @"use -[MGLMapViewDelegate mapView:fillColorForShape:] instead." + @"This warning will only appear once."); + }); + color = [self.delegate mapView:self fillColorForPolygonAnnotation:(MGLPolygon *)shape]; +#pragma clang diagnostic pop + } return color.mgl_color; } diff --git a/platform/macos/src/MGLMapViewDelegate.h b/platform/macos/src/MGLMapViewDelegate.h index c7d6786666..4717df68ea 100644 --- a/platform/macos/src/MGLMapViewDelegate.h +++ b/platform/macos/src/MGLMapViewDelegate.h @@ -6,6 +6,7 @@ NS_ASSUME_NONNULL_BEGIN @class MGLAnnotationImage; @class MGLPolygon; @class MGLPolyline; +@class MGLCircle; @class MGLShape; /** @@ -227,20 +228,25 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSColor *)mapView:(MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation; +- (NSColor *)mapView:(MGLMapView *)mapView fillColorForPolygonAnnotation:(MGLPolygon *)annotation __attribute__((deprecated("", "-mapView:fillColorForShape:"))); + /** - Returns the color to use when rendering the fill of a polygon annotation. + Returns the color to use when rendering the fill of a shape annotation. + This method is only called for `MGLPolygon` and `MGLCircle` annotations. It is + not possible to fill a polyline or point annotation. + The default fill color is the selected menu item color. If a pattern color is specified, the result is undefined. Opacity may be set by specifying an alpha component. The default alpha value is `1.0` and results in a completely opaque shape. - @param mapView The map view rendering the polygon annotation. - @param annotation The annotation being rendered. - @return The polygon’s interior fill color. + @param mapView The map view rendering the shape annotation. + @param shape The annotation being rendered. + @return The shape’s fill color. */ -- (NSColor *)mapView:(MGLMapView *)mapView fillColorForPolygonAnnotation:(MGLPolygon *)annotation; +- (NSColor *)mapView:(MGLMapView *)mapView fillColorForShape:(MGLShape *)shape; /** Returns the line width in points to use when rendering the outline of a diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h index 6728992d6b..595ce08d92 100644 --- a/platform/macos/src/Mapbox.h +++ b/platform/macos/src/Mapbox.h @@ -32,6 +32,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLPointCollection.h" #import "MGLPolygon.h" #import "MGLPolyline.h" +#import "MGLCircle.h" #import "MGLShape.h" #import "MGLShapeCollection.h" #import "MGLStyle.h" diff --git a/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift b/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift index 83c7160fde..345b15df49 100644 --- a/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift +++ b/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift @@ -54,6 +54,8 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapView(_ mapView: MGLMapView, strokeColorForShapeAnnotation annotation: MGLShape) -> NSColor { return .black } func mapView(_ mapView: MGLMapView, fillColorForPolygonAnnotation annotation: MGLPolygon) -> NSColor { return .black } + + func mapView(_ mapView: MGLMapView, fillColorFor shape: MGLShape) -> NSColor { return .black } func mapView(_ mapView: MGLMapView, calloutViewControllerFor annotation: MGLAnnotation) -> NSViewController? { return nil } -- cgit v1.2.1