diff options
Diffstat (limited to 'platform/darwin')
-rw-r--r-- | platform/darwin/src/MGLFeature.h | 137 | ||||
-rw-r--r-- | platform/darwin/src/MGLFeature.mm | 269 | ||||
-rw-r--r-- | platform/darwin/src/MGLFeature_Private.h | 11 | ||||
-rw-r--r-- | platform/darwin/src/MGLGeometry_Private.h | 5 | ||||
-rw-r--r-- | platform/darwin/src/MGLMultiPoint.h | 5 | ||||
-rw-r--r-- | platform/darwin/src/MGLMultiPoint.mm | 36 | ||||
-rw-r--r-- | platform/darwin/src/MGLMultiPoint_Private.h | 9 | ||||
-rw-r--r-- | platform/darwin/src/MGLPolygon.h | 55 | ||||
-rw-r--r-- | platform/darwin/src/MGLPolygon.mm | 96 | ||||
-rw-r--r-- | platform/darwin/src/MGLPolyline.h | 27 | ||||
-rw-r--r-- | platform/darwin/src/MGLPolyline.mm | 64 | ||||
-rw-r--r-- | platform/darwin/src/MGLShapeCollection.h | 35 | ||||
-rw-r--r-- | platform/darwin/src/MGLShapeCollection.m | 21 | ||||
-rw-r--r-- | platform/darwin/test/MGLFeatureTests.mm | 160 |
14 files changed, 865 insertions, 65 deletions
diff --git a/platform/darwin/src/MGLFeature.h b/platform/darwin/src/MGLFeature.h new file mode 100644 index 0000000000..69cff6b054 --- /dev/null +++ b/platform/darwin/src/MGLFeature.h @@ -0,0 +1,137 @@ +#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 ordinary `MGLAnnotation` objects, some kinds of `MGLFeature` + objects can also 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> +@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> +@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> +@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> +@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> +@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> +@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> +@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..777b296303 --- /dev/null +++ b/platform/darwin/src/MGLFeature.mm @@ -0,0 +1,269 @@ +#import "MGLFeature_Private.h" + +#import "MGLPointAnnotation.h" +#import "MGLPolyline.h" +#import "MGLPolygon.h" + +#import "MGLMultiPoint_Private.h" + +#import <mbgl/util/geometry.hpp> + +@protocol MGLFeaturePrivate <MGLFeature> + +@property (nonatomic, copy, nullable, readwrite) id identifier; +@property (nonatomic, copy, readwrite) NS_DICTIONARY_OF(NSString *, id) *attributes; + +@end + +@interface MGLPointFeature () <MGLFeaturePrivate> +@end + +@implementation MGLPointFeature + +@synthesize identifier; +@synthesize attributes; + +- (id)attributeForKey:(NSString *)key { + return self.attributes[key]; +} + +@end + +@interface MGLPolylineFeature () <MGLFeaturePrivate> +@end + +@implementation MGLPolylineFeature + +@synthesize identifier; +@synthesize attributes; + +- (id)attributeForKey:(NSString *)key { + return self.attributes[key]; +} + +@end + +@interface MGLPolygonFeature () <MGLFeaturePrivate> +@end + +@implementation MGLPolygonFeature + +@synthesize identifier; +@synthesize attributes; + +- (id)attributeForKey:(NSString *)key { + return self.attributes[key]; +} + +@end + +@interface MGLMultiPointFeature () <MGLFeaturePrivate> +@end + +@implementation MGLMultiPointFeature + +@synthesize identifier; +@synthesize attributes; + +- (id)attributeForKey:(NSString *)key { + return self.attributes[key]; +} + +@end + +@interface MGLMultiPolylineFeature () <MGLFeaturePrivate> +@end + +@implementation MGLMultiPolylineFeature + +@synthesize identifier; +@synthesize attributes; + +- (id)attributeForKey:(NSString *)key { + return self.attributes[key]; +} + +@end + +@interface MGLMultiPolygonFeature () <MGLFeaturePrivate> +@end + +@implementation MGLMultiPolygonFeature + +@synthesize identifier; +@synthesize attributes; + +- (id)attributeForKey:(NSString *)key { + return self.attributes[key]; +} + +@end + +@interface MGLShapeCollectionFeature () <MGLFeaturePrivate> +@end + +@implementation MGLShapeCollectionFeature + +@synthesize identifier; +@synthesize attributes; + +- (id)attributeForKey:(NSString *)key { + return self.attributes[key]; +} + +@end + +/** + Recursively transforms a C++ type into the corresponding Foundation type. + */ +class PropertyValueEvaluator { +public: + 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 { + NSMutableArray *objects = [NSMutableArray arrayWithCapacity:values.size()]; + for (const auto &v : values) { + [objects addObject:mbgl::Value::visit(v, *this)]; + } + return objects; + } + + id operator()(const std::unordered_map<std::string, mbgl::Value> &items) const { + NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:items.size()]; + for (auto &item : items) { + attributes[@(item.first.c_str())] = mbgl::Value::visit(item.second, *this); + } + return attributes; + } +}; + +/** + Transforms an `mbgl::geometry::geometry` type into an instance of the + corresponding Objective-C geometry class. + */ +template <typename T> +class GeometryEvaluator { +public: + MGLShape <MGLFeaturePrivate> * operator()(const mbgl::Point<T> &geometry) const { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + feature.coordinate = toLocationCoordinate2D(geometry); + return feature; + } + + MGLShape <MGLFeaturePrivate> * operator()(const mbgl::LineString<T> &geometry) const { + std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry); + return [MGLPolylineFeature polylineWithCoordinates:&coordinates[0] count:coordinates.size()]; + } + + MGLShape <MGLFeaturePrivate> * operator()(const mbgl::Polygon<T> &geometry) const { + return toShape<MGLPolygonFeature>(geometry); + } + + MGLShape <MGLFeaturePrivate> * operator()(const mbgl::MultiPoint<T> &geometry) const { + std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry); + return [[MGLMultiPointFeature alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()]; + } + + MGLShape <MGLFeaturePrivate> * operator()(const mbgl::MultiLineString<T> &geometry) const { + NSMutableArray *polylines = [NSMutableArray arrayWithCapacity:geometry.size()]; + for (auto &lineString : geometry) { + std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(lineString); + MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:&coordinates[0] count:coordinates.size()]; + [polylines addObject:polyline]; + } + + return [MGLMultiPolylineFeature multiPolylineWithPolylines:polylines]; + } + + MGLShape <MGLFeaturePrivate> * operator()(const mbgl::MultiPolygon<T> &geometry) const { + NSMutableArray *polygons = [NSMutableArray arrayWithCapacity:geometry.size()]; + for (auto &polygon : geometry) { + [polygons addObject:toShape(polygon)]; + } + + return [MGLMultiPolygonFeature multiPolygonWithPolygons:polygons]; + } + + MGLShape <MGLFeaturePrivate> * operator()(const mapbox::geometry::geometry_collection<T> &collection) const { + NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()]; + for (auto &geometry : collection) { + // This is very much like the transformation that happens in MGLFeaturesFromMBGLFeatures(), but these are raw geometries with no associated feature IDs or attributes. + MGLShape <MGLFeaturePrivate> *shape = mapbox::geometry::geometry<T>::visit(geometry, *this); + [shapes addObject:shape]; + } + return [MGLShapeCollectionFeature shapeCollectionWithShapes:shapes]; + } + +private: + static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point<T> &point) { + return CLLocationCoordinate2DMake(point.y, point.x); + } + + static std::vector<CLLocationCoordinate2D> toLocationCoordinates2D(const std::vector<mbgl::Point<T>> &points) { + std::vector<CLLocationCoordinate2D> coordinates; + coordinates.reserve(points.size()); + std::transform(points.begin(), points.end(), std::back_inserter(coordinates), toLocationCoordinate2D); + return coordinates; + } + + template<typename U = MGLPolygon> + static U *toShape(const mbgl::Polygon<T> &geometry) { + auto &linearRing = geometry.front(); + std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(linearRing); + NSMutableArray *innerPolygons; + if (geometry.size() > 1) { + innerPolygons = [NSMutableArray arrayWithCapacity:geometry.size() - 1]; + for (auto iter = geometry.begin() + 1; iter != geometry.end(); iter++) { + auto &innerRing = *iter; + std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(innerRing); + MGLPolygon *innerPolygon = [MGLPolygon polygonWithCoordinates:&coordinates[0] count:coordinates.size()]; + [innerPolygons addObject:innerPolygon]; + } + } + + return [U polygonWithCoordinates:&coordinates[0] count:coordinates.size() interiorPolygons:innerPolygons]; + } +}; + +NS_ARRAY_OF(MGLShape <MGLFeature> *) *MGLFeaturesFromMBGLFeatures(const std::vector<mbgl::Feature> &features) { + NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()]; + for (const auto &feature : features) { + NSMutableDictionary *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); + } + + GeometryEvaluator<double> evaluator; + MGLShape <MGLFeaturePrivate> *shape = mapbox::geometry::geometry<double>::visit(feature.geometry, evaluator); + if (feature.id) { + shape.identifier = @(*feature.id); + } + shape.attributes = attributes; + [shapes addObject:shape]; + } + return shapes; +} diff --git a/platform/darwin/src/MGLFeature_Private.h b/platform/darwin/src/MGLFeature_Private.h new file mode 100644 index 0000000000..fbc7f88559 --- /dev/null +++ b/platform/darwin/src/MGLFeature_Private.h @@ -0,0 +1,11 @@ +#import "MGLFeature.h" +#import "MGLShape.h" + +#import <mbgl/util/geo.hpp> +#import <mbgl/util/feature.hpp> + +/** + Returns an array of `MGLFeature` objects converted from the given vector of + vector tile features. + */ +NS_ARRAY_OF(MGLShape <MGLFeature> *) *MGLFeaturesFromMBGLFeatures(const std::vector<mbgl::Feature> &features); diff --git a/platform/darwin/src/MGLGeometry_Private.h b/platform/darwin/src/MGLGeometry_Private.h index 0538cd94ea..fc57460128 100644 --- a/platform/darwin/src/MGLGeometry_Private.h +++ b/platform/darwin/src/MGLGeometry_Private.h @@ -6,6 +6,7 @@ #endif #import <mbgl/util/geo.hpp> +#import <mbgl/util/geometry.hpp> /// Returns the smallest rectangle that contains both the given rectangle and /// the given point. @@ -15,6 +16,10 @@ NS_INLINE mbgl::LatLng MGLLatLngFromLocationCoordinate2D(CLLocationCoordinate2D return mbgl::LatLng(coordinate.latitude, coordinate.longitude); } +NS_INLINE mbgl::Point<double> MGLPointFromLocationCoordinate2D(CLLocationCoordinate2D coordinate) { + return mbgl::Point<double>(coordinate.longitude, coordinate.latitude); +} + NS_INLINE CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) { return CLLocationCoordinate2DMake(latLng.latitude, latLng.longitude); } diff --git a/platform/darwin/src/MGLMultiPoint.h b/platform/darwin/src/MGLMultiPoint.h index 041c52e8f2..2d6b327086 100644 --- a/platform/darwin/src/MGLMultiPoint.h +++ b/platform/darwin/src/MGLMultiPoint.h @@ -17,7 +17,10 @@ NS_ASSUME_NONNULL_BEGIN */ @interface MGLMultiPoint : MGLShape -/** The number of points associated with the shape. (read-only) */ +/** The array of coordinates associated with the shape. */ +@property (nonatomic, readonly) CLLocationCoordinate2D *coordinates NS_RETURNS_INNER_POINTER; + +/** The number of coordinates associated with the shape. (read-only) */ @property (nonatomic, readonly) NSUInteger pointCount; /** diff --git a/platform/darwin/src/MGLMultiPoint.mm b/platform/darwin/src/MGLMultiPoint.mm index a864b7bce7..6084535d05 100644 --- a/platform/darwin/src/MGLMultiPoint.mm +++ b/platform/darwin/src/MGLMultiPoint.mm @@ -14,7 +14,6 @@ mbgl::Color MGLColorObjectFromCGColorRef(CGColorRef cgColor) { @implementation MGLMultiPoint { - CLLocationCoordinate2D *_coords; size_t _count; MGLCoordinateBounds _bounds; } @@ -27,13 +26,13 @@ mbgl::Color MGLColorObjectFromCGColorRef(CGColorRef cgColor) { if (self) { _count = count; - _coords = (CLLocationCoordinate2D *)malloc(_count * sizeof(CLLocationCoordinate2D)); + _coordinates = (CLLocationCoordinate2D *)malloc(_count * sizeof(CLLocationCoordinate2D)); mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); for (NSUInteger i = 0; i < _count; i++) { - _coords[i] = coords[i]; + _coordinates[i] = coords[i]; bounds.extend(mbgl::LatLng(coords[i].latitude, coords[i].longitude)); } @@ -45,7 +44,7 @@ mbgl::Color MGLColorObjectFromCGColorRef(CGColorRef cgColor) { - (void)dealloc { - free(_coords); + free(_coordinates); } - (CLLocationCoordinate2D)coordinate @@ -59,7 +58,7 @@ mbgl::Color MGLColorObjectFromCGColorRef(CGColorRef cgColor) { assert(_count > 0); - return CLLocationCoordinate2DMake(_coords[0].latitude, _coords[0].longitude); + return CLLocationCoordinate2DMake(_coordinates[0].latitude, _coordinates[0].longitude); } - (NSUInteger)pointCount @@ -89,7 +88,7 @@ mbgl::Color MGLColorObjectFromCGColorRef(CGColorRef cgColor) { for (NSUInteger i = range.location; i < range.location + range.length; i++) { - coords[index] = _coords[i]; + coords[index] = _coordinates[i]; index++; } } @@ -104,28 +103,9 @@ mbgl::Color MGLColorObjectFromCGColorRef(CGColorRef cgColor) { return MGLLatLngBoundsFromCoordinateBounds(_bounds).intersects(MGLLatLngBoundsFromCoordinateBounds(overlayBounds)); } -- (void)addShapeAnnotationObjectToCollection:(std::vector<mbgl::ShapeAnnotation> &)shapes withDelegate:(id <MGLMultiPointDelegate>)delegate { - NSUInteger count = self.pointCount; - if (count == 0) { - return; - } - - CLLocationCoordinate2D *coordinates = (CLLocationCoordinate2D *)malloc(count * sizeof(CLLocationCoordinate2D)); - NSAssert(coordinates, @"Unable to allocate annotation with %lu points", (unsigned long)count); - [self getCoordinates:coordinates range:NSMakeRange(0, count)]; - - mbgl::AnnotationSegment segment; - segment.reserve(count); - for (NSUInteger i = 0; i < count; i++) { - segment.push_back(MGLLatLngFromLocationCoordinate2D(coordinates[i])); - } - free(coordinates); - shapes.emplace_back(mbgl::AnnotationSegments {{ segment }}, - [self shapeAnnotationPropertiesObjectWithDelegate:delegate]); -} - -- (mbgl::ShapeAnnotation::Properties)shapeAnnotationPropertiesObjectWithDelegate:(__unused id <MGLMultiPointDelegate>)delegate { - return mbgl::ShapeAnnotation::Properties(); +- (mbgl::Annotation)annotationObjectWithDelegate:(__unused id <MGLMultiPointDelegate>)delegate { + NSAssert(NO, @"Cannot add an annotation from an instance of %@", NSStringFromClass([self class])); + return mbgl::SymbolAnnotation({mbgl::Point<double>()}); } - (NSString *)description diff --git a/platform/darwin/src/MGLMultiPoint_Private.h b/platform/darwin/src/MGLMultiPoint_Private.h index c1f1fa1584..aa52a02fcb 100644 --- a/platform/darwin/src/MGLMultiPoint_Private.h +++ b/platform/darwin/src/MGLMultiPoint_Private.h @@ -3,7 +3,7 @@ #import "MGLGeometry.h" #import "MGLTypes.h" -#import <mbgl/annotation/shape_annotation.hpp> +#import <mbgl/annotation/annotation.hpp> #import <vector> #import <CoreGraphics/CoreGraphics.h> @@ -21,11 +21,8 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count; - (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds; -/** Adds a shape annotation to the given vector by asking the delegate for style values. */ -- (void)addShapeAnnotationObjectToCollection:(std::vector<mbgl::ShapeAnnotation> &)shapes withDelegate:(id <MGLMultiPointDelegate>)delegate; - -/** Constructs a shape annotation properties object by asking the delegate for style values. */ -- (mbgl::ShapeAnnotation::Properties)shapeAnnotationPropertiesObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate; +/** Constructs a shape annotation object, asking the delegate for style values. */ +- (mbgl::Annotation)annotationObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate; @end diff --git a/platform/darwin/src/MGLPolygon.h b/platform/darwin/src/MGLPolygon.h index 1a158874bb..3d5b36abb6 100644 --- a/platform/darwin/src/MGLPolygon.h +++ b/platform/darwin/src/MGLPolygon.h @@ -17,6 +17,17 @@ NS_ASSUME_NONNULL_BEGIN @interface MGLPolygon : MGLMultiPoint <MGLOverlay> /** + The array of polygons nested inside the receiver. + + The area occupied by any interior polygons is excluded from the overall shape. + Interior polygons should not overlap. An interior polygon should not have + interior polygons of its own. + + If there are no interior polygons, the value of this property is `nil`. + */ +@property (nonatomic, nullable, readonly) NS_ARRAY_OF(MGLPolygon *) *interiorPolygons; + +/** Creates and returns an `MGLPolygon` object from the specified set of coordinates. @@ -25,8 +36,48 @@ NS_ASSUME_NONNULL_BEGIN @param count The number of items in the `coords` array. @return A new polygon object. */ -+ (instancetype)polygonWithCoordinates:(CLLocationCoordinate2D *)coords - count:(NSUInteger)count; ++ (instancetype)polygonWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count; + +/** + Creates and returns an `MGLPolygon` object from the specified set of + coordinates and interior polygons. + + @param coords The array of coordinates defining the shape. The data in this + array is copied to the new object. + @param count The number of items in the `coords` array. + @param interiorPolygons An array of `MGLPolygon` objects that define regions + excluded from the overall shape. If this array is `nil` or empty, the shape + is considered to have no interior polygons. + @return A new polygon object. + */ ++ (instancetype)polygonWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count interiorPolygons:(nullable NS_ARRAY_OF(MGLPolygon *) *)interiorPolygons; + +@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. + + @note `MGLMultiPolygon` objects cannot be added to a map view using + `-[MGLMapView addAnnotations:]` and related methods. + */ +@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 diff --git a/platform/darwin/src/MGLPolygon.mm b/platform/darwin/src/MGLPolygon.mm index 5019385cb2..c009d9e3d6 100644 --- a/platform/darwin/src/MGLPolygon.mm +++ b/platform/darwin/src/MGLPolygon.mm @@ -1,28 +1,90 @@ #import "MGLPolygon.h" #import "MGLMultiPoint_Private.h" +#import "MGLGeometry_Private.h" @implementation MGLPolygon @dynamic overlayBounds; -+ (instancetype)polygonWithCoordinates:(CLLocationCoordinate2D *)coords - count:(NSUInteger)count -{ - return [[self alloc] initWithCoordinates:coords count:count]; -} - -- (mbgl::ShapeAnnotation::Properties)shapeAnnotationPropertiesObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate { - mbgl::ShapeAnnotation::Properties shapeProperties = [super shapeAnnotationPropertiesObjectWithDelegate:delegate]; - - mbgl::FillAnnotationProperties fillProperties; - fillProperties.opacity = [delegate alphaForShapeAnnotation:self]; - fillProperties.outlineColor = [delegate strokeColorForShapeAnnotation:self]; - fillProperties.color = [delegate fillColorForPolygonAnnotation:self]; - - shapeProperties.set<mbgl::FillAnnotationProperties>(fillProperties); - - return shapeProperties; ++ (instancetype)polygonWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count { + return [self polygonWithCoordinates:coords count:count interiorPolygons:nil]; +} + ++ (instancetype)polygonWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count interiorPolygons:(NSArray<MGLPolygon *> *)interiorPolygons { + return [[self alloc] initWithCoordinates:coords count:count interiorPolygons:interiorPolygons]; +} + +- (instancetype)initWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count interiorPolygons:(NSArray<MGLPolygon *> *)interiorPolygons { + if (self = [super initWithCoordinates:coords count:count]) { + if (interiorPolygons.count) { + _interiorPolygons = interiorPolygons; + } + } + return self; +} + +- (mbgl::LinearRing<double>)ring { + NSUInteger count = self.pointCount; + CLLocationCoordinate2D *coordinates = self.coordinates; + + mbgl::LinearRing<double> result; + result.reserve(self.pointCount); + for (NSUInteger i = 0; i < count; i++) { + result.push_back(mbgl::Point<double>(coordinates[i].longitude, coordinates[i].latitude)); + } + return result; +} + +- (mbgl::Annotation)annotationObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate { + mbgl::Polygon<double> geometry; + geometry.push_back(self.ring); + for (MGLPolygon *polygon in self.interiorPolygons) { + geometry.push_back(polygon.ring); + } + + mbgl::FillAnnotation annotation { geometry }; + annotation.opacity = [delegate alphaForShapeAnnotation:self]; + annotation.outlineColor = [delegate strokeColorForShapeAnnotation:self]; + annotation.color = [delegate fillColorForPolygonAnnotation:self]; + + return annotation; +} + +@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..78d9649751 100644 --- a/platform/darwin/src/MGLPolyline.h +++ b/platform/darwin/src/MGLPolyline.h @@ -30,4 +30,31 @@ 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. + + @note `MGLMultiPolyline` objects cannot be added to a map view using + `-[MGLMapView addAnnotations:]` and related methods. + */ +@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..15ea5a0952 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 @@ -12,17 +13,58 @@ return [[self alloc] initWithCoordinates:coords count:count]; } -- (mbgl::ShapeAnnotation::Properties)shapeAnnotationPropertiesObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate { - mbgl::ShapeAnnotation::Properties shapeProperties = [super shapeAnnotationPropertiesObjectWithDelegate:delegate]; - - mbgl::LineAnnotationProperties lineProperties; - lineProperties.opacity = [delegate alphaForShapeAnnotation:self]; - lineProperties.color = [delegate strokeColorForShapeAnnotation:self]; - lineProperties.width = [delegate lineWidthForPolylineAnnotation:self]; - - shapeProperties.set<mbgl::LineAnnotationProperties>(lineProperties); - - return shapeProperties; +- (mbgl::Annotation)annotationObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate { + NSUInteger count = self.pointCount; + CLLocationCoordinate2D *coordinates = self.coordinates; + + mbgl::LineString<double> geometry; + geometry.reserve(self.pointCount); + for (NSUInteger i = 0; i < count; i++) { + geometry.push_back(mbgl::Point<double>(coordinates[i].longitude, coordinates[i].latitude)); + } + + mbgl::LineAnnotation annotation { geometry }; + annotation.opacity = [delegate alphaForShapeAnnotation:self]; + annotation.color = [delegate strokeColorForShapeAnnotation:self]; + annotation.width = [delegate lineWidthForPolylineAnnotation:self]; + + return annotation; +} + +@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..a617223ea7 --- /dev/null +++ b/platform/darwin/src/MGLShapeCollection.h @@ -0,0 +1,35 @@ +#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. + + @note `MGLShapeCollection` objects cannot be added to a map view using + `-[MGLMapView addAnnotations:]` and related methods. + */ +@interface MGLShapeCollection : MGLShape + +/** + An array of shapes forming the shape collection. + */ +@property (nonatomic, copy, readonly) NS_ARRAY_OF(MGLShape *) *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 *) *)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..5d42b5a51c --- /dev/null +++ b/platform/darwin/src/MGLShapeCollection.m @@ -0,0 +1,21 @@ +#import "MGLShapeCollection.h" + +@implementation MGLShapeCollection + ++ (instancetype)shapeCollectionWithShapes:(NS_ARRAY_OF(MGLShape *) *)shapes { + return [[self alloc] initWithShapes:shapes]; +} + +- (instancetype)initWithShapes:(NS_ARRAY_OF(MGLShape *) *)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 diff --git a/platform/darwin/test/MGLFeatureTests.mm b/platform/darwin/test/MGLFeatureTests.mm new file mode 100644 index 0000000000..6cf038d4fb --- /dev/null +++ b/platform/darwin/test/MGLFeatureTests.mm @@ -0,0 +1,160 @@ +#import <Mapbox/Mapbox.h> +#import <XCTest/XCTest.h> + +#import "../../darwin/src/MGLFeature_Private.h" + +@interface MGLFeatureTests : XCTestCase + +@end + +@implementation MGLFeatureTests + +- (void)testGeometryConversion { + std::vector<mbgl::Feature> features; + + mapbox::geometry::point<double> point = { -90.066667, 29.95 }; + features.emplace_back(point); + + mapbox::geometry::line_string<double> lineString = { + { -84.516667, 39.1 }, + { -90.066667, 29.95 }, + }; + features.emplace_back(lineString); + + mapbox::geometry::polygon<double> polygon = { + { + { 1, 1 }, + { 4, 1 }, + { 4, 4 }, + { 1, 4 }, + }, + { + { 2, 2 }, + { 3, 2 }, + { 3, 3 }, + { 2, 3 }, + }, + }; + features.emplace_back(polygon); + + NS_ARRAY_OF(MGLShape <MGLFeature> *) *shapes = MGLFeaturesFromMBGLFeatures(features); + XCTAssertEqual(shapes.count, 3, @"All features should be converted into shapes"); + + MGLPointFeature *pointShape = (MGLPointFeature *)shapes[0]; + XCTAssertTrue([pointShape isKindOfClass:[MGLPointFeature class]]); + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:pointShape.coordinate], + [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]); + + MGLPolylineFeature *polylineShape = (MGLPolylineFeature *)shapes[1]; + XCTAssertTrue([polylineShape isKindOfClass:[MGLPolylineFeature class]]); + XCTAssertEqual(polylineShape.pointCount, 2); + CLLocationCoordinate2D polylineCoordinates[2]; + [polylineShape getCoordinates:polylineCoordinates range:NSMakeRange(0, polylineShape.pointCount)]; + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:polylineCoordinates[0]], + [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(39.1, -84.516667)]); + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:polylineCoordinates[1]], + [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]); + + MGLPolygonFeature *polygonShape = (MGLPolygonFeature *)shapes[2]; + XCTAssertTrue([polygonShape isKindOfClass:[MGLPolygonFeature class]]); + XCTAssertEqual(polygonShape.pointCount, 4); + CLLocationCoordinate2D *polygonCoordinates = polygonShape.coordinates; + XCTAssertNotEqual(polygonCoordinates, nil); + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:polygonCoordinates[0]], + [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(1, 1)]); + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:polygonCoordinates[1]], + [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(1, 4)]); + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:polygonCoordinates[2]], + [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(4, 4)]); + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:polygonCoordinates[3]], + [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(4, 1)]); + NS_ARRAY_OF(MGLPolygon *) *interiorPolygons = polygonShape.interiorPolygons; + XCTAssertEqual(interiorPolygons.count, 1); + MGLPolygon *interiorPolygon = interiorPolygons.firstObject; + XCTAssertEqual(interiorPolygon.pointCount, 4); + CLLocationCoordinate2D interiorPolygonCoordinates[4]; + [interiorPolygon getCoordinates:interiorPolygonCoordinates range:NSMakeRange(0, interiorPolygon.pointCount)]; + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:interiorPolygonCoordinates[0]], + [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(2, 2)]); + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:interiorPolygonCoordinates[1]], + [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(2, 3)]); + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:interiorPolygonCoordinates[2]], + [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(3, 3)]); + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:interiorPolygonCoordinates[3]], + [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(3, 2)]); +} + +- (void)testPropertyConversion { + std::vector<mbgl::Feature> features; + + mapbox::geometry::point<double> point = { -90.066667, 29.95 }; + mbgl::Feature pointFeature(point); + pointFeature.id = UINT64_MAX; + pointFeature.properties["null"] = nullptr; + pointFeature.properties["bool"] = true; + pointFeature.properties["unsigned int"] = UINT64_MAX; + pointFeature.properties["int"] = INT64_MIN; + pointFeature.properties["double"] = DBL_MAX; + pointFeature.properties["string"] = std::string("🚏"); + std::vector<bool> vector; + vector.push_back(true); + vector.push_back(false); + vector.push_back(true); + features.push_back(pointFeature); + + NS_ARRAY_OF(MGLShape <MGLFeature> *) *shapes = MGLFeaturesFromMBGLFeatures(features); + XCTAssertEqual(shapes.count, 1, @"All features should be converted into shapes"); + + MGLShape <MGLFeature> *shape = shapes.firstObject; + XCTAssertTrue([shape conformsToProtocol:@protocol(MGLFeature)]); + XCTAssertTrue([shape isKindOfClass:[MGLShape class]]); + + NSNumber *identifier = shape.identifier; + XCTAssertTrue([identifier isKindOfClass:[NSNumber class]], @"Feature identifier should be NSNumber"); + XCTAssertEqual(strcmp(identifier.objCType, @encode(uint64_t)), 0, @"Feature identifier should be 64-bit unsigned integer"); + + NSNull *null = [shape attributeForKey:@"null"]; + XCTAssertNotNil(null); + XCTAssertTrue([null isKindOfClass:[NSNull class]]); + XCTAssertEqual(null, shape.attributes[@"null"]); + + NSNumber *boolean = [shape attributeForKey:@"bool"]; + XCTAssertNotNil(boolean); + XCTAssertTrue([boolean isKindOfClass:[NSNumber class]]); +#if (TARGET_OS_IPHONE && __LP64__) || TARGET_OS_WATCH + XCTAssertEqual(strcmp(boolean.objCType, @encode(char)), 0, @"Boolean property should be converted to bool NSNumber"); +#else + XCTAssertEqual(strcmp(boolean.objCType, @encode(BOOL)), 0, @"Boolean property should be converted to bool NSNumber"); +#endif + XCTAssertTrue(boolean.boolValue); + XCTAssertEqual(boolean, shape.attributes[@"bool"]); + + NSNumber *unsignedInteger = [shape attributeForKey:@"unsigned int"]; + XCTAssertNotNil(unsignedInteger); + XCTAssertTrue([unsignedInteger isKindOfClass:[NSNumber class]]); + XCTAssertEqual(strcmp(unsignedInteger.objCType, @encode(uint64_t)), 0, @"Unsigned integer property should be converted to unsigned long long NSNumber"); + XCTAssertEqual(unsignedInteger.unsignedLongLongValue, UINT64_MAX); + XCTAssertEqual(unsignedInteger, shape.attributes[@"unsigned int"]); + + NSNumber *integer = [shape attributeForKey:@"int"]; + XCTAssertNotNil(integer); + XCTAssertTrue([integer isKindOfClass:[NSNumber class]]); + XCTAssertEqual(strcmp(integer.objCType, @encode(int64_t)), 0, @"Integer property should be converted to long long NSNumber"); + XCTAssertEqual(integer.longLongValue, INT64_MIN); + XCTAssertEqual(integer, shape.attributes[@"int"]); + + NSNumber *floatingPointNumber = [shape attributeForKey:@"double"]; + XCTAssertNotNil(floatingPointNumber); + XCTAssertTrue([floatingPointNumber isKindOfClass:[NSNumber class]]); + XCTAssertEqual(strcmp(floatingPointNumber.objCType, @encode(double)), 0, @"Floating-point number property should be converted to double NSNumber"); + XCTAssertEqual(floatingPointNumber.doubleValue, DBL_MAX); + XCTAssertEqual(floatingPointNumber, shape.attributes[@"double"]); + + NSString *string = [shape attributeForKey:@"string"]; + XCTAssertNotNil(string); + XCTAssertTrue([string isKindOfClass:[NSString class]]); + XCTAssertEqualObjects(string, @"🚏"); + XCTAssertEqual(string, shape.attributes[@"string"]); +} + +@end |