summaryrefslogtreecommitdiff
path: root/platform/darwin/src
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2016-05-23 08:47:10 -0700
committerMinh Nguyễn <mxn@1ec5.org>2016-05-27 21:42:31 -0700
commitc6683ee1d3baebf46f3397e7ed2e44e8a23dc8ed (patch)
tree3962b9433647f91436b0cb70344e272132935f80 /platform/darwin/src
parentc9520713ca5cc5352dada77de87aede97e7947f2 (diff)
downloadqtlocation-mapboxgl-c6683ee1d3baebf46f3397e7ed2e44e8a23dc8ed.tar.gz
[ios, osx] Feature querying; complex geometry classes
Added methods to MGLMapView that return the rendered features at a visible point or within a visible rectangle on the map, optionally restricted to a set of layers, plus voluminous documentation. Added several new geometry classes corresponding to distinct geometry types supported by geometry.hpp. Added parallel “feature” classes to represent these geometries along with tags (IDs) and attributes (properties) from the source. Grouped classes in the Foundation and SDK groups by theme. In iosapp, dropped pins’ callout views now display the name of the topmost named feature at that point. In osxapp, a long press on the map highlights the features under the cursor. Dropping a pin via the menu or context menu item shows the usual dropped pin, but the pin’s title is now the name of a feature under the cursor, if available, rather than “Dropped Pin”. Fixes the iOS/OS X side of #352.
Diffstat (limited to 'platform/darwin/src')
-rw-r--r--platform/darwin/src/MGLFeature.h164
-rw-r--r--platform/darwin/src/MGLFeature.mm244
-rw-r--r--platform/darwin/src/MGLFeature_Private.h7
-rw-r--r--platform/darwin/src/MGLPolygon.h24
-rw-r--r--platform/darwin/src/MGLPolygon.mm37
-rw-r--r--platform/darwin/src/MGLPolyline.h24
-rw-r--r--platform/darwin/src/MGLPolyline.mm37
-rw-r--r--platform/darwin/src/MGLShapeCollection.h32
-rw-r--r--platform/darwin/src/MGLShapeCollection.m21
9 files changed, 590 insertions, 0 deletions
diff --git a/platform/darwin/src/MGLFeature.h b/platform/darwin/src/MGLFeature.h
new file mode 100644
index 0000000000..389b8ab67f
--- /dev/null
+++ b/platform/darwin/src/MGLFeature.h
@@ -0,0 +1,164 @@
+#import <Foundation/Foundation.h>
+
+#import "MGLPolyline.h"
+#import "MGLPolygon.h"
+#import "MGLPointAnnotation.h"
+#import "MGLShapeCollection.h"
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ The `MGLFeature` protocol is used to provide details about geographic features
+ contained in a map view’s
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile sources</a>.
+ Each concrete subclass of `MGLShape` in turn has a subclass that conforms to
+ this protocol.
+
+ Typically, you do not create feature objects yourself but rather obtain them
+ using `-[MGLMapView visibleFeaturesAtPoint:]` and related methods. Each feature
+ object associates a shape with an identifier and attributes as specified by the
+ source. Like any `MGLAnnotation` object, an `MGLFeature` object can be added to
+ a map view using `-[MGLMapView addAnnotations:]` and related methods.
+ */
+@protocol MGLFeature <MGLAnnotation>
+
+/**
+ An object that uniquely identifies the feature in its containing
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile source</a>.
+
+ The value of this property is currently always an `NSNumber` object but may in
+ the future be an instance of another class, such as `NSString`.
+
+ The identifier corresponds to the
+ <a href="https://github.com/mapbox/vector-tile-spec/tree/master/2.1#42-features">feature identifier</a>
+ (`id`) in the tile source. If the source does not specify the feature’s
+ identifier, the value of this property is `nil`.
+
+ For details about the identifiers used in most Mapbox-provided styles, consult
+ the
+ <a href="https://www.mapbox.com/vector-tiles/mapbox-streets/">Mapbox Streets</a>
+ layer reference.
+ */
+@property (nonatomic, copy, nullable, readonly) id identifier;
+
+/**
+ A dictionary of attributes for this feature specified by the
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile source</a>.
+
+ The keys and values of this dictionary are determined by the tile source. In
+ the tile source, each attribute name is a string, while each attribute value
+ may be a null value, Boolean value, integer, floating-point number, or string.
+ These data types are mapped to instances of the following Foundation classes:
+
+ <table>
+ <thead>
+ <tr><th>In the tile source</th><th>In this dictionary</th></tr>
+ </thead>
+ <tbody>
+ <tr><td>Null</td> <td><code>NSNull</code></td></tr>
+ <tr><td>Boolean</td> <td><code>NSNumber</code> (use the <code>boolValue</code> property)</td></tr>
+ <tr><td>Integer</td> <td><code>NSNumber</code> (use the <code>unsignedLongLongValue</code> or <code>longLongValue</code> property)</td></tr>
+ <tr><td>Floating-point number</td> <td><code>NSNumber</code> (use the <code>doubleValue</code> property)</td></tr>
+ <tr><td>String</td> <td><code>NSString</code></td></tr>
+ </tbody>
+ </table>
+
+ For details about the attribute names and values found in Mapbox-provided
+ vector tile sources, consult the
+ <a href="https://www.mapbox.com/vector-tiles/mapbox-streets/">Mapbox Streets</a>
+ and
+ <a href="https://www.mapbox.com/vector-tiles/mapbox-terrain/">Mapbox Terrain</a>
+ layer references.
+ */
+@property (nonatomic, copy, readonly) NS_DICTIONARY_OF(NSString *, id) *attributes;
+
+/**
+ Returns the feature attribute for the given attribute name.
+
+ See the `attributes` property’s documentation for details on keys and values
+ associated with this method.
+ */
+- (nullable id)attributeForKey:(NSString *)key;
+
+@end
+
+/**
+ The `MGLPointFeature` class represents a point in a
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile source</a>.
+ */
+@interface MGLPointFeature : MGLPointAnnotation <MGLFeature>
+
+@property (nonatomic, copy, nullable, readwrite) id identifier;
+@property (nonatomic, copy, readwrite) NS_DICTIONARY_OF(NSString *, id) *attributes;
+
+@end
+
+/**
+ The `MGLPolylineFeature` class represents a polyline in a
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile source</a>.
+ */
+@interface MGLPolylineFeature : MGLPolyline <MGLFeature>
+
+@property (nonatomic, copy, nullable, readwrite) id identifier;
+@property (nonatomic, copy, readwrite) NS_DICTIONARY_OF(NSString *, id) *attributes;
+
+@end
+
+/**
+ The `MGLPolygonFeature` class represents a polygon in a
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile source</a>.
+ */
+@interface MGLPolygonFeature : MGLPolygon <MGLFeature>
+
+@property (nonatomic, copy, nullable, readwrite) id identifier;
+@property (nonatomic, copy, readwrite) NS_DICTIONARY_OF(NSString *, id) *attributes;
+
+@end
+
+/**
+ The `MGLMultiPointFeature` class represents a multipoint in a
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile source</a>.
+ */
+@interface MGLMultiPointFeature : MGLMultiPoint <MGLFeature>
+
+@property (nonatomic, copy, nullable, readwrite) id identifier;
+@property (nonatomic, copy, readwrite) NS_DICTIONARY_OF(NSString *, id) *attributes;
+
+@end
+
+/**
+ The `MGLMultiPolylineFeature` class represents a multipolyline in a
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile source</a>.
+ */
+@interface MGLMultiPolylineFeature : MGLMultiPolyline <MGLFeature>
+
+@property (nonatomic, copy, nullable, readwrite) id identifier;
+@property (nonatomic, copy, readwrite) NS_DICTIONARY_OF(NSString *, id) *attributes;
+
+@end
+
+/**
+ The `MGLMultiPolygonFeature` class represents a multipolygon in a
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile source</a>.
+ */
+@interface MGLMultiPolygonFeature : MGLMultiPolygon <MGLFeature>
+
+@property (nonatomic, copy, nullable, readwrite) id identifier;
+@property (nonatomic, copy, readwrite) NS_DICTIONARY_OF(NSString *, id) *attributes;
+
+@end
+
+/**
+ The `MGLShapeCollectionFeature` class represents a shape collection in a
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile source</a>.
+ */
+@interface MGLShapeCollectionFeature : MGLShapeCollection <MGLFeature>
+
+@property (nonatomic, copy, nullable, readwrite) id identifier;
+@property (nonatomic, copy, readwrite) NS_DICTIONARY_OF(NSString *, id) *attributes;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLFeature.mm b/platform/darwin/src/MGLFeature.mm
new file mode 100644
index 0000000000..68c57a193a
--- /dev/null
+++ b/platform/darwin/src/MGLFeature.mm
@@ -0,0 +1,244 @@
+#import "MGLFeature_Private.h"
+
+#import "MGLPointAnnotation.h"
+#import "MGLPolyline.h"
+#import "MGLPolygon.h"
+
+#import "MGLMultiPoint_Private.h"
+
+class PropertyValueEvaluator {
+public:
+ PropertyValueEvaluator() {}
+
+ id operator()(const std::nullptr_t &) const {
+ return [NSNull null];
+ }
+
+ id operator()(const bool &value) const {
+ return value ? @YES : @NO;
+ }
+
+ id operator()(const uint64_t &value) const {
+ return @(value);
+ }
+
+ id operator()(const int64_t &value) const {
+ return @(value);
+ }
+
+ id operator()(const double &value) const {
+ return @(value);
+ }
+
+ id operator()(const std::string &value) const {
+ return @(value.c_str());
+ }
+
+ id operator()(const std::vector<mbgl::Value> &values) const {
+ std::vector<id> objects;
+ objects.reserve(values.size());
+ std::transform(values.begin(), values.end(), std::back_inserter(objects), ^id (const mbgl::Value &value) {
+ PropertyValueEvaluator evaluator;
+ return mbgl::Value::visit(value, evaluator);
+ });
+ return [NSArray arrayWithObjects:&objects[0] count:objects.size()];
+ }
+
+ id operator()(const std::unordered_map<std::string, mbgl::Value> &items) const {
+ std::vector<NSString *> keys;
+ keys.reserve(items.size());
+ std::vector<id> objects;
+ objects.reserve(items.size());
+ for (auto &item : items) {
+ keys.push_back(@(item.first.c_str()));
+ PropertyValueEvaluator evaluator;
+ objects.push_back(mbgl::Value::visit(item.second, evaluator));
+ }
+ return [NSDictionary dictionaryWithObjects:&objects[0] forKeys:&keys[0] count:keys.size()];
+ }
+};
+
+template <typename T>
+class GeometryEvaluator {
+public:
+ GeometryEvaluator()
+ : attributes([NSMutableDictionary dictionary]) {}
+
+ GeometryEvaluator(const mbgl::Feature &feature)
+ : tag(feature.id ? @(*feature.id) : nullptr),
+ attributes([NSMutableDictionary dictionaryWithCapacity:feature.properties.size()]) {
+ for (auto &pair : feature.properties) {
+ auto &value = pair.second;
+ PropertyValueEvaluator evaluator;
+ attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator);
+ }
+ }
+
+ MGLShape <MGLFeature> * operator()(const mapbox::geometry::point<T> &geometry) const {
+ MGLPointFeature *feature = [[MGLPointFeature alloc] init];
+ feature.coordinate = coordinateFromPoint(geometry);
+ feature.identifier = tag;
+ feature.attributes = attributes;
+ return feature;
+ }
+
+ MGLShape <MGLFeature> * operator()(const mapbox::geometry::line_string<T> &geometry) const {
+ std::vector<CLLocationCoordinate2D> coordinates;
+ coordinates.reserve(geometry.size());
+ std::transform(geometry.begin(), geometry.end(), std::back_inserter(coordinates), coordinateFromPoint);
+
+ MGLPolylineFeature *feature = [[MGLPolylineFeature alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()];
+ feature.identifier = tag;
+ feature.attributes = attributes;
+ return feature;
+ }
+
+ MGLShape <MGLFeature> * operator()(const mapbox::geometry::polygon<T> &geometry) const {
+ // TODO: MGLPolygon doesn’t support holes, so what to do?
+ auto &linearRing = geometry.front();
+
+ std::vector<CLLocationCoordinate2D> coordinates;
+ coordinates.reserve(linearRing.size());
+ std::transform(linearRing.begin(), linearRing.end(), std::back_inserter(coordinates), coordinateFromPoint);
+
+ MGLPolygonFeature *feature = [[MGLPolygonFeature alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()];
+ feature.identifier = tag;
+ feature.attributes = attributes;
+ return feature;
+ }
+
+ MGLShape <MGLFeature> * operator()(const mapbox::geometry::multi_point<T> &geometry) const {
+ std::vector<CLLocationCoordinate2D> coordinates;
+ coordinates.reserve(geometry.size());
+ std::transform(geometry.begin(), geometry.end(), std::back_inserter(coordinates), coordinateFromPoint);
+
+ MGLMultiPointFeature *feature = [[MGLMultiPointFeature alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()];
+ feature.identifier = tag;
+ feature.attributes = attributes;
+ return feature;
+ }
+
+ MGLShape <MGLFeature> * operator()(const mapbox::geometry::multi_line_string<T> &geometry) const {
+ NSMutableArray *polylines = [NSMutableArray arrayWithCapacity:geometry.size()];
+ for (auto &lineString : geometry) {
+ std::vector<CLLocationCoordinate2D> coordinates;
+ coordinates.reserve(lineString.size());
+ std::transform(lineString.begin(), lineString.end(), std::back_inserter(coordinates), coordinateFromPoint);
+
+ MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:&coordinates[0] count:coordinates.size()];
+ [polylines addObject:polyline];
+ }
+
+ MGLMultiPolylineFeature *feature = [MGLMultiPolylineFeature multiPolylineWithPolylines:polylines];
+ feature.identifier = tag;
+ feature.attributes = attributes;
+ return feature;
+ }
+
+ MGLShape <MGLFeature> * operator()(const mapbox::geometry::multi_polygon<T> &geometry) const {
+ NSMutableArray *polygons = [NSMutableArray arrayWithCapacity:geometry.size()];
+ for (auto &polygon : geometry) {
+ // TODO: MGLPolygon doesn’t support holes, so what to do?
+ auto &linearRing = polygon.front();
+
+ std::vector<CLLocationCoordinate2D> coordinates;
+ coordinates.reserve(linearRing.size());
+ std::transform(linearRing.begin(), linearRing.end(), std::back_inserter(coordinates), coordinateFromPoint);
+
+ MGLPolygon *polygonObject = [MGLPolygon polygonWithCoordinates:&coordinates[0] count:coordinates.size()];
+ [polygons addObject:polygonObject];
+ }
+
+ MGLMultiPolygonFeature *feature = [MGLMultiPolygonFeature multiPolygonWithPolygons:polygons];
+ feature.identifier = tag;
+ feature.attributes = attributes;
+ return feature;
+ }
+
+ MGLShape <MGLFeature> * operator()(const mapbox::geometry::geometry_collection<T> &collection) const {
+ NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()];
+ for (auto &geometry : collection) {
+ GeometryEvaluator<T> evaluator;
+ id <MGLFeature> feature = mapbox::geometry::geometry<T>::visit(geometry, evaluator);
+ [shapes addObject:feature];
+ }
+ MGLShapeCollectionFeature *feature = [MGLShapeCollectionFeature shapeCollectionWithShapes:shapes];
+ feature.identifier = tag;
+ feature.attributes = attributes;
+ return feature;
+ }
+
+private:
+ NSNumber *tag = nullptr;
+ NS_MUTABLE_DICTIONARY_OF(NSString *, id) *attributes;
+
+ static CLLocationCoordinate2D coordinateFromPoint(const mapbox::geometry::point<T> &point) {
+ return CLLocationCoordinate2DMake(point.y, point.x);
+ }
+};
+
+NS_ARRAY_OF(MGLShape <MGLFeature> *) *MGLFeaturesFromMBGLFeatures(const std::vector<mbgl::Feature> &features) {
+ std::vector<MGLShape <MGLFeature> *> shapes;
+ shapes.reserve(features.size());
+ std::transform(features.begin(), features.end(), std::back_inserter(shapes), ^MGLShape <MGLFeature> * (const mbgl::Feature &feature) {
+ GeometryEvaluator<double> evaluator(feature);
+ return mapbox::geometry::geometry<double>::visit(feature.geometry, evaluator);
+ });
+ return [NSArray arrayWithObjects:&shapes[0] count:shapes.size()];
+}
+
+@implementation MGLPointFeature
+
+- (id)attributeForKey:(NSString *)key {
+ return self.attributes[key];
+}
+
+@end
+
+@implementation MGLPolylineFeature
+
+- (id)attributeForKey:(NSString *)key {
+ return self.attributes[key];
+}
+
+@end
+
+@implementation MGLPolygonFeature
+
+- (id)attributeForKey:(NSString *)key {
+ return self.attributes[key];
+}
+
+@end
+
+@implementation MGLMultiPointFeature
+
+- (id)attributeForKey:(NSString *)key {
+ return self.attributes[key];
+}
+
+@end
+
+@implementation MGLMultiPolylineFeature
+
+- (id)attributeForKey:(NSString *)key {
+ return self.attributes[key];
+}
+
+@end
+
+@implementation MGLMultiPolygonFeature
+
+- (id)attributeForKey:(NSString *)key {
+ return self.attributes[key];
+}
+
+@end
+
+@implementation MGLShapeCollectionFeature
+
+- (id)attributeForKey:(NSString *)key {
+ return self.attributes[key];
+}
+
+@end
diff --git a/platform/darwin/src/MGLFeature_Private.h b/platform/darwin/src/MGLFeature_Private.h
new file mode 100644
index 0000000000..837461871e
--- /dev/null
+++ b/platform/darwin/src/MGLFeature_Private.h
@@ -0,0 +1,7 @@
+#import "MGLFeature.h"
+#import "MGLShape.h"
+
+#import <mbgl/util/geo.hpp>
+#import <mbgl/util/feature.hpp>
+
+NS_ARRAY_OF(MGLShape <MGLFeature> *) *MGLFeaturesFromMBGLFeatures(const std::vector<mbgl::Feature> &features);
diff --git a/platform/darwin/src/MGLPolygon.h b/platform/darwin/src/MGLPolygon.h
index 1a158874bb..a12e62b106 100644
--- a/platform/darwin/src/MGLPolygon.h
+++ b/platform/darwin/src/MGLPolygon.h
@@ -30,4 +30,28 @@ NS_ASSUME_NONNULL_BEGIN
@end
+/**
+ The `MGLMultiPolygon` class represents a shape consisting of one or more
+ polygons that do not overlap. For example, you would use an `MGLMultiPolygon`
+ object to represent an atoll together with an island in the atoll’s lagoon:
+ the atoll itself would be one `MGLPolygon` object, while the inner island would
+ be another.
+ */
+@interface MGLMultiPolygon : MGLShape <MGLOverlay>
+
+/**
+ An array of polygons forming the multipolygon.
+ */
+@property (nonatomic, copy, readonly) NS_ARRAY_OF(MGLPolygon *) *polygons;
+
+/**
+ Creates and returns a multipolygon object consisting of the given polygons.
+
+ @param polygons The array of polygons defining the shape.
+ @return A new multipolygon object.
+ */
++ (instancetype)multiPolygonWithPolygons:(NS_ARRAY_OF(MGLPolygon *) *)polygons;
+
+@end
+
NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLPolygon.mm b/platform/darwin/src/MGLPolygon.mm
index 5019385cb2..6febf6d81b 100644
--- a/platform/darwin/src/MGLPolygon.mm
+++ b/platform/darwin/src/MGLPolygon.mm
@@ -1,6 +1,7 @@
#import "MGLPolygon.h"
#import "MGLMultiPoint_Private.h"
+#import "MGLGeometry_Private.h"
@implementation MGLPolygon
@@ -26,3 +27,39 @@
}
@end
+
+@interface MGLMultiPolygon ()
+
+@property (nonatomic, copy, readwrite) NS_ARRAY_OF(MGLPolygon *) *polygons;
+
+@end
+
+@implementation MGLMultiPolygon {
+ MGLCoordinateBounds _overlayBounds;
+}
+
+@synthesize overlayBounds = _overlayBounds;
+
++ (instancetype)multiPolygonWithPolygons:(NS_ARRAY_OF(MGLPolygon *) *)polygons {
+ return [[self alloc] initWithPolygons:polygons];
+}
+
+- (instancetype)initWithPolygons:(NS_ARRAY_OF(MGLPolygon *) *)polygons {
+ if (self = [super init]) {
+ _polygons = polygons;
+
+ mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
+
+ for (MGLPolygon *polygon in _polygons) {
+ bounds.extend(MGLLatLngBoundsFromCoordinateBounds(polygon.overlayBounds));
+ }
+ _overlayBounds = MGLCoordinateBoundsFromLatLngBounds(bounds);
+ }
+ return self;
+}
+
+- (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds {
+ return MGLLatLngBoundsFromCoordinateBounds(_overlayBounds).intersects(MGLLatLngBoundsFromCoordinateBounds(overlayBounds));
+}
+
+@end
diff --git a/platform/darwin/src/MGLPolyline.h b/platform/darwin/src/MGLPolyline.h
index 5e45513735..a453a43a41 100644
--- a/platform/darwin/src/MGLPolyline.h
+++ b/platform/darwin/src/MGLPolyline.h
@@ -30,4 +30,28 @@ NS_ASSUME_NONNULL_BEGIN
@end
+/**
+ The `MGLMultiPolyline` class represents a shape consisting of one or more
+ polylines. For example, you could use an `MGLMultiPolyline` object to represent
+ both sides of a divided highway (dual carriageway), excluding the median
+ (central reservation): each carriageway would be a distinct `MGLPolyline`
+ object.
+ */
+@interface MGLMultiPolyline : MGLShape <MGLOverlay>
+
+/**
+ An array of polygons forming the multipolyline.
+ */
+@property (nonatomic, copy, readonly) NS_ARRAY_OF(MGLPolyline *) *polylines;
+
+/**
+ Creates and returns a multipolyline object consisting of the given polylines.
+
+ @param polylines The array of polylines defining the shape.
+ @return A new multipolyline object.
+ */
++ (instancetype)multiPolylineWithPolylines:(NS_ARRAY_OF(MGLPolyline *) *)polylines;
+
+@end
+
NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLPolyline.mm b/platform/darwin/src/MGLPolyline.mm
index f560a571bc..810b359bb0 100644
--- a/platform/darwin/src/MGLPolyline.mm
+++ b/platform/darwin/src/MGLPolyline.mm
@@ -1,6 +1,7 @@
#import "MGLPolyline.h"
#import "MGLMultiPoint_Private.h"
+#import "MGLGeometry_Private.h"
@implementation MGLPolyline
@@ -26,3 +27,39 @@
}
@end
+
+@interface MGLMultiPolyline ()
+
+@property (nonatomic, copy, readwrite) NS_ARRAY_OF(MGLPolyline *) *polylines;
+
+@end
+
+@implementation MGLMultiPolyline {
+ MGLCoordinateBounds _overlayBounds;
+}
+
+@synthesize overlayBounds = _overlayBounds;
+
++ (instancetype)multiPolylineWithPolylines:(NS_ARRAY_OF(MGLPolyline *) *)polylines {
+ return [[self alloc] initWithPolylines:polylines];
+}
+
+- (instancetype)initWithPolylines:(NS_ARRAY_OF(MGLPolyline *) *)polylines {
+ if (self = [super init]) {
+ _polylines = polylines;
+
+ mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
+
+ for (MGLPolyline *polyline in _polylines) {
+ bounds.extend(MGLLatLngBoundsFromCoordinateBounds(polyline.overlayBounds));
+ }
+ _overlayBounds = MGLCoordinateBoundsFromLatLngBounds(bounds);
+ }
+ return self;
+}
+
+- (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds {
+ return MGLLatLngBoundsFromCoordinateBounds(_overlayBounds).intersects(MGLLatLngBoundsFromCoordinateBounds(overlayBounds));
+}
+
+@end
diff --git a/platform/darwin/src/MGLShapeCollection.h b/platform/darwin/src/MGLShapeCollection.h
new file mode 100644
index 0000000000..f4dc226228
--- /dev/null
+++ b/platform/darwin/src/MGLShapeCollection.h
@@ -0,0 +1,32 @@
+#import <Foundation/Foundation.h>
+
+#import "MGLShape.h"
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ The `MGLShapeCollection` class represents a shape consisting of one or more
+ distinct but related shapes that are instances of `MGLShape`. The constituent
+ shapes can be a mixture of different kinds of shapes.
+ */
+@interface MGLShapeCollection : MGLShape
+
+/**
+ An array of shapes forming the shape collection.
+ */
+@property (nonatomic, copy, readonly) NS_ARRAY_OF(MGLShape <MGLAnnotation> *) *shapes;
+
+/**
+ Creates and returns a shape collection consisting of the given shapes.
+
+ @param shapes The array of shapes defining the shape collection. The data in
+ this array is copied to the new object.
+ @return A new shape collection object.
+ */
++ (instancetype)shapeCollectionWithShapes:(NS_ARRAY_OF(MGLShape <MGLAnnotation> *) *)shapes;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLShapeCollection.m b/platform/darwin/src/MGLShapeCollection.m
new file mode 100644
index 0000000000..3fc4cdda75
--- /dev/null
+++ b/platform/darwin/src/MGLShapeCollection.m
@@ -0,0 +1,21 @@
+#import "MGLShapeCollection.h"
+
+@implementation MGLShapeCollection
+
++ (instancetype)shapeCollectionWithShapes:(NS_ARRAY_OF(MGLShape <MGLAnnotation> *) *)shapes {
+ return [[self alloc] initWithShapes:shapes];
+}
+
+- (instancetype)initWithShapes:(NS_ARRAY_OF(MGLShape <MGLAnnotation> *) *)shapes {
+ if (self = [super init]) {
+ NSAssert(shapes.count, @"Cannot create an empty shape collection");
+ _shapes = shapes.copy;
+ }
+ return self;
+}
+
+- (CLLocationCoordinate2D)coordinate {
+ return _shapes.firstObject.coordinate;
+}
+
+@end