#import "MGLFoundation_Private.h" #import "MGLFeature_Private.h" #import "MGLCluster.h" #import "MGLPointAnnotation.h" #import "MGLPolyline.h" #import "MGLPolygon.h" #import "MGLValueEvaluator.h" #import "MGLShape_Private.h" #import "MGLPointCollection_Private.h" #import "MGLPolyline_Private.h" #import "MGLPolygon_Private.h" #import "NSDictionary+MGLAdditions.h" #import "NSArray+MGLAdditions.h" #import "NSExpression+MGLPrivateAdditions.h" #import "MGLLoggingConfiguration_Private.h" #import #import #import // Cluster constants static NSString * const MGLClusterIdentifierKey = @"cluster_id"; static NSString * const MGLClusterCountKey = @"point_count"; const NSUInteger MGLClusterIdentifierInvalid = NSUIntegerMax; @interface MGLEmptyFeature () @end @implementation MGLEmptyFeature @synthesize identifier; @synthesize attributes = _attributes; MGL_DEFINE_FEATURE_INIT_WITH_CODER(); MGL_DEFINE_FEATURE_ENCODE(); MGL_DEFINE_FEATURE_IS_EQUAL(); MGL_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { MGLLogDebug(@"Retrieving attributeForKey: %@", key); return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); } - (mbgl::GeoJSON)geoJSONObject { return mbglFeature({[self geometryObject]}, identifier, self.attributes); } - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p; identifier = %@, attributes = %@>", NSStringFromClass([self class]), (void *)self, self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, self.attributes.count ? self.attributes : @"none"]; } @end @interface MGLPointFeature () @end @implementation MGLPointFeature @synthesize identifier; @synthesize attributes = _attributes; MGL_DEFINE_FEATURE_INIT_WITH_CODER(); MGL_DEFINE_FEATURE_ENCODE(); MGL_DEFINE_FEATURE_IS_EQUAL(); MGL_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { MGLLogDebug(@"Retrieving attributeForKey: %@", key); return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); } - (mbgl::GeoJSON)geoJSONObject { return mbglFeature({[self geometryObject]}, identifier, self.attributes); } - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p; identifier = %@, coordinate = %f, %f, attributes = %@>", NSStringFromClass([self class]), (void *)self, self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, self.coordinate.latitude, self.coordinate.longitude, self.attributes.count ? self.attributes : @"none"]; } @end @implementation MGLPointFeatureCluster - (NSUInteger)clusterIdentifier { NSNumber *clusterNumber = MGL_OBJC_DYNAMIC_CAST([self attributeForKey:MGLClusterIdentifierKey], NSNumber); MGLAssert(clusterNumber, @"Clusters should have a cluster_id"); if (!clusterNumber) { return MGLClusterIdentifierInvalid; } NSUInteger clusterIdentifier = [clusterNumber unsignedIntegerValue]; MGLAssert(clusterIdentifier <= UINT32_MAX, @"Cluster identifiers are 32bit"); return clusterIdentifier; } - (NSUInteger)clusterPointCount { NSNumber *count = MGL_OBJC_DYNAMIC_CAST([self attributeForKey:MGLClusterCountKey], NSNumber); MGLAssert(count, @"Clusters should have a point_count"); return [count unsignedIntegerValue]; } @end @interface MGLPolylineFeature () @end @implementation MGLPolylineFeature @synthesize identifier; @synthesize attributes = _attributes; MGL_DEFINE_FEATURE_INIT_WITH_CODER(); MGL_DEFINE_FEATURE_ENCODE(); MGL_DEFINE_FEATURE_IS_EQUAL(); MGL_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { MGLLogDebug(@"Retrieving attributeForKey: %@", key); return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); } - (mbgl::GeoJSON)geoJSONObject { return mbglFeature({[self geometryObject]}, identifier, self.attributes); } - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", NSStringFromClass([self class]), (void *)self, self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, (unsigned long)[self pointCount], MGLStringFromCoordinateBounds(self.overlayBounds), self.attributes.count ? self.attributes : @"none"]; } @end @interface MGLPolygonFeature () @end @implementation MGLPolygonFeature @synthesize identifier; @synthesize attributes = _attributes; MGL_DEFINE_FEATURE_INIT_WITH_CODER(); MGL_DEFINE_FEATURE_ENCODE(); MGL_DEFINE_FEATURE_IS_EQUAL(); MGL_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { MGLLogDebug(@"Retrieving attributeForKey: %@", key); return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); } - (mbgl::GeoJSON)geoJSONObject { return mbglFeature({[self geometryObject]}, identifier, self.attributes); } - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", NSStringFromClass([self class]), (void *)self, self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, (unsigned long)[self pointCount], MGLStringFromCoordinateBounds(self.overlayBounds), self.attributes.count ? self.attributes : @"none"]; } @end @interface MGLPointCollectionFeature () @end @implementation MGLPointCollectionFeature @synthesize identifier; @synthesize attributes = _attributes; MGL_DEFINE_FEATURE_INIT_WITH_CODER(); MGL_DEFINE_FEATURE_ENCODE(); MGL_DEFINE_FEATURE_IS_EQUAL(); MGL_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { MGLLogDebug(@"Retrieving attributeForKey: %@", key); return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); } - (mbgl::GeoJSON)geoJSONObject { return mbglFeature({[self geometryObject]}, identifier, self.attributes); } @end @interface MGLMultiPolylineFeature () @end @implementation MGLMultiPolylineFeature @synthesize identifier; @synthesize attributes = _attributes; MGL_DEFINE_FEATURE_INIT_WITH_CODER(); MGL_DEFINE_FEATURE_ENCODE(); MGL_DEFINE_FEATURE_IS_EQUAL(); MGL_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { MGLLogDebug(@"Retrieving attributeForKey: %@", key); return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); } - (mbgl::GeoJSON)geoJSONObject { return mbglFeature({[self geometryObject]}, identifier, self.attributes); } - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", NSStringFromClass([self class]), (void *)self, self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, (unsigned long)self.polylines.count, MGLStringFromCoordinateBounds(self.overlayBounds), self.attributes.count ? self.attributes : @"none"]; } @end @interface MGLMultiPolygonFeature () @end @implementation MGLMultiPolygonFeature @synthesize identifier; @synthesize attributes = _attributes; MGL_DEFINE_FEATURE_INIT_WITH_CODER(); MGL_DEFINE_FEATURE_ENCODE(); MGL_DEFINE_FEATURE_IS_EQUAL(); MGL_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { MGLLogDebug(@"Retrieving attributeForKey: %@", key); return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); } - (mbgl::GeoJSON)geoJSONObject { return mbglFeature({[self geometryObject]}, identifier, self.attributes); } - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", NSStringFromClass([self class]), (void *)self, self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, (unsigned long)self.polygons.count, MGLStringFromCoordinateBounds(self.overlayBounds), self.attributes.count ? self.attributes : @"none"]; } @end @interface MGLShapeCollectionFeature () @end @implementation MGLShapeCollectionFeature @synthesize identifier; @synthesize attributes = _attributes; @dynamic shapes; + (instancetype)shapeCollectionWithShapes:(NSArray *> *)shapes { return [super shapeCollectionWithShapes:shapes]; } MGL_DEFINE_FEATURE_INIT_WITH_CODER(); MGL_DEFINE_FEATURE_ENCODE(); MGL_DEFINE_FEATURE_IS_EQUAL(); MGL_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); } - (mbgl::GeoJSON)geoJSONObject { mbgl::FeatureCollection featureCollection; featureCollection.reserve(self.shapes.count); for (MGLShape *feature in self.shapes) { auto geoJSONObject = feature.geoJSONObject; MGLAssert(geoJSONObject.is(), @"Feature collection must only contain features."); featureCollection.push_back(geoJSONObject.get()); } return featureCollection; } @end /** Transforms an `mbgl::geometry::geometry` type into an instance of the corresponding Objective-C geometry class. */ template class GeometryEvaluator { private: const mbgl::PropertyMap *shared_properties; public: GeometryEvaluator(const mbgl::PropertyMap *properties = nullptr): shared_properties(properties) {} MGLShape * operator()(const mbgl::EmptyGeometry &) const { MGLEmptyFeature *feature = [[MGLEmptyFeature alloc] init]; return feature; } MGLShape * operator()(const mbgl::Point &geometry) const { Class pointFeatureClass = [MGLPointFeature class]; // If we're dealing with a cluster, we should change the class type. // This could be generic and build the subclass at runtime if it turns // out we need to support more than point clusters. if (shared_properties) { auto clusterIt = shared_properties->find("cluster"); if (clusterIt != shared_properties->end()) { auto clusterValue = clusterIt->second; if (clusterValue.template is()) { if (clusterValue.template get()) { pointFeatureClass = [MGLPointFeatureCluster class]; } } } } MGLPointFeature *feature = [[pointFeatureClass alloc] init]; feature.coordinate = toLocationCoordinate2D(geometry); return feature; } MGLShape * operator()(const mbgl::LineString &geometry) const { std::vector coordinates = toLocationCoordinates2D(geometry); return [MGLPolylineFeature polylineWithCoordinates:&coordinates[0] count:coordinates.size()]; } MGLShape * operator()(const mbgl::Polygon &geometry) const { return toShape(geometry); } MGLShape * operator()(const mbgl::MultiPoint &geometry) const { std::vector coordinates = toLocationCoordinates2D(geometry); return [[MGLPointCollectionFeature alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()]; } MGLShape * operator()(const mbgl::MultiLineString &geometry) const { NSMutableArray *polylines = [NSMutableArray arrayWithCapacity:geometry.size()]; for (auto &lineString : geometry) { std::vector coordinates = toLocationCoordinates2D(lineString); MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:&coordinates[0] count:coordinates.size()]; [polylines addObject:polyline]; } return [MGLMultiPolylineFeature multiPolylineWithPolylines:polylines]; } MGLShape * operator()(const mbgl::MultiPolygon &geometry) const { NSMutableArray *polygons = [NSMutableArray arrayWithCapacity:geometry.size()]; for (auto &polygon : geometry) { [polygons addObject:toShape(polygon)]; } return [MGLMultiPolygonFeature multiPolygonWithPolygons:polygons]; } MGLShape * operator()(const mapbox::geometry::geometry_collection &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 *shape = mapbox::geometry::geometry::visit(geometry, *this); [shapes addObject:shape]; } return [MGLShapeCollectionFeature shapeCollectionWithShapes:shapes]; } private: static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point &point) { return CLLocationCoordinate2DMake(point.y, point.x); } static std::vector toLocationCoordinates2D(const std::vector> &points) { std::vector coordinates; coordinates.reserve(points.size()); std::transform(points.begin(), points.end(), std::back_inserter(coordinates), toLocationCoordinate2D); return coordinates; } template static U *toShape(const mbgl::Polygon &geometry) { auto &linearRing = geometry.front(); std::vector 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 innerCoordinates = toLocationCoordinates2D(innerRing); MGLPolygon *innerPolygon = [MGLPolygon polygonWithCoordinates:&innerCoordinates[0] count:innerCoordinates.size()]; [innerPolygons addObject:innerPolygon]; } } return [U polygonWithCoordinates:&coordinates[0] count:coordinates.size() interiorPolygons:innerPolygons]; } }; template class GeoJSONEvaluator { public: MGLShape * operator()(const mbgl::Geometry &geometry) const { GeometryEvaluator evaluator; MGLShape *shape = mapbox::geometry::geometry::visit(geometry, evaluator); return shape; } MGLShape * operator()(const mbgl::Feature &feature) const { MGLShape *shape = (MGLShape *)MGLFeatureFromMBGLFeature(feature); return shape; } MGLShape * operator()(const mbgl::FeatureCollection &collection) const { NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()]; for (const auto &feature : collection) { [shapes addObject:MGLFeatureFromMBGLFeature(feature)]; } return [MGLShapeCollectionFeature shapeCollectionWithShapes:shapes]; } }; NSArray *> *MGLFeaturesFromMBGLFeatures(const std::vector &features) { NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()]; for (const auto &feature : features) { [shapes addObject:MGLFeatureFromMBGLFeature(feature)]; } return shapes; } id MGLFeatureFromMBGLFeature(const mbgl::Feature &feature) { NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:feature.properties.size()]; for (auto &pair : feature.properties) { auto &value = pair.second; ValueEvaluator evaluator; attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator); } GeometryEvaluator evaluator(&feature.properties); MGLShape *shape = mapbox::geometry::geometry::visit(feature.geometry, evaluator); if (!feature.id.is()) { shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator()); } shape.attributes = attributes; return shape; } MGLShape* MGLShapeFromGeoJSON(const mapbox::geojson::geojson &geojson) { GeoJSONEvaluator evaluator; MGLShape *shape = mapbox::geojson::geojson::visit(geojson, evaluator); return shape; } mbgl::Feature mbglFeature(mbgl::Feature feature, id identifier, NSDictionary *attributes) { if (identifier) { NSExpression *identifierExpression = [NSExpression expressionForConstantValue:identifier]; feature.id = [identifierExpression mgl_featureIdentifier]; } feature.properties = [attributes mgl_propertyMap]; return feature; } NSDictionary *NSDictionaryFeatureForGeometry(NSDictionary *geometry, NSDictionary *attributes, id identifier) { NSMutableDictionary *feature = [@{@"type": @"Feature", @"properties": attributes, @"geometry": geometry} mutableCopy]; feature[@"id"] = identifier; return [feature copy]; }