summaryrefslogtreecommitdiff
path: root/platform/darwin
diff options
context:
space:
mode:
Diffstat (limited to 'platform/darwin')
-rw-r--r--platform/darwin/src/MGLFeature.h137
-rw-r--r--platform/darwin/src/MGLFeature.mm269
-rw-r--r--platform/darwin/src/MGLFeature_Private.h11
-rw-r--r--platform/darwin/src/MGLGeometry_Private.h5
-rw-r--r--platform/darwin/src/MGLMultiPoint.h5
-rw-r--r--platform/darwin/src/MGLMultiPoint.mm36
-rw-r--r--platform/darwin/src/MGLMultiPoint_Private.h9
-rw-r--r--platform/darwin/src/MGLPolygon.h55
-rw-r--r--platform/darwin/src/MGLPolygon.mm96
-rw-r--r--platform/darwin/src/MGLPolyline.h27
-rw-r--r--platform/darwin/src/MGLPolyline.mm64
-rw-r--r--platform/darwin/src/MGLShapeCollection.h35
-rw-r--r--platform/darwin/src/MGLShapeCollection.m21
-rw-r--r--platform/darwin/test/MGLFeatureTests.mm160
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