Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

[ios] Support getLeaves (and related) clustering methods #12952

Merged
merged 29 commits into from
Jan 14, 2019
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9e19405
[core] - expose getChildren, getLeaves, getClusterExpansionZoom on Su…
tobrun Aug 22, 2018
99265a7
[ios, macos] Expose getLeaves, getChildren, getClusterExpansionZoom. …
Sep 24, 2018
0e029a0
[ios] Updated core files list.
Sep 25, 2018
31b0ce6
[ios] Updated Mapbox.h
Sep 25, 2018
fb69c1f
[macos] Updated macos project to reference MGLPointCluster.h
Sep 26, 2018
32332ff
[macos] Updated core files.
Sep 26, 2018
423657f
[ios, macos] Updated MGLCluster protocol, create dynamic subclass at …
Oct 3, 2018
3430c3b
[ios, macos] Added additional tests
Oct 5, 2018
da5fdf6
[ios, macos] cleaned up recursive log
Oct 5, 2018
dba896c
Compile fixes following rebase
Nov 22, 2018
385cd96
Updated core-files.txt and macos project.
Nov 22, 2018
7b13def
Updated types of parameters to `leavesOfCluster:offset:limit`
Nov 24, 2018
9a1f429
Updated docs. Replaced `nil` return values with empty array due to no…
Nov 25, 2018
bd32ad4
Updated to using MGLLogging methods.
Nov 25, 2018
65f0360
Updated documentation and associated test.
Nov 26, 2018
1bb7dd3
Tweaked documentation
Nov 26, 2018
4596fd6
Regenerated example.
Nov 26, 2018
ddcd83f
Updated changelogs
Nov 26, 2018
08de82b
Documentation nits.
Nov 26, 2018
8ebce7f
Reverting old core changes.
Jan 11, 2019
8c4066d
WIP updates
Jan 11, 2019
360ad1c
Introduces MGLPointFeatureCluster subclass (since update feature exte…
Jan 12, 2019
7120a4b
Clean up
Jan 12, 2019
1726296
Updated change logs
Jan 12, 2019
72381ba
Removed deleted files from macos project
Jan 12, 2019
c7baac8
Updated macos file list
Jan 12, 2019
015ec9c
Update predicates/expressions guide with the other cluster attributes.
Jan 12, 2019
6de3ffa
Documentation and whitespace tweaks.
Jan 14, 2019
9434779
Another minor documentation tweak.
Jan 14, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion platform/darwin/docs/guides/Predicates and Expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,24 @@ dictionary contains the `floorCount` key, then the key path `floorCount` refers
to the value of the `floorCount` attribute when evaluating that particular
polygon.

The following special attribute is also available on features that are produced
The following special attributes are also available on features that are produced
as a result of clustering multiple point features together in a shape source:

<table>
<thead>
<tr><th>Attribute</th><th>Type</th><th>Meaning</th></tr>
</thead>
<tbody>
<tr>
<td><code>cluster</code></td>
<td>Bool</td>
<td>True if the feature is a point cluster. If the attribute is false (or not present) then the feature should not be considered a cluster.</td>
</tr>
<tr>
<td><code>cluster_id</code></td>
<td>Number</td>
<td>Identifier for the point cluster.</td>
</tr>
<tr>
<td><code>point_count</code></td>
<td>Number</td>
Expand Down
53 changes: 53 additions & 0 deletions platform/darwin/src/MGLCluster.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#import "MGLFoundation.h"

@protocol MGLFeature;

NS_ASSUME_NONNULL_BEGIN

/**
An `NSUInteger` constant used to indicate an invalid cluster identifier.
This indicates a missing cluster feature.
*/
FOUNDATION_EXTERN MGL_EXPORT const NSUInteger MGLClusterIdentifierInvalid;

/**
A protocol that feature subclasses (i.e. those already conforming to
the `MGLFeature` protocol) conform to if they represent clusters.

Currently the only class that conforms to `MGLCluster` is
`MGLPointFeatureCluster` (a subclass of `MGLPointFeature`).

To check if a feature is a cluster, check conformity to `MGLCluster`, for
example:

```swift
let shape = try! MGLShape(data: clusterShapeData, encoding: String.Encoding.utf8.rawValue)

guard let pointFeature = shape as? MGLPointFeature else {
throw ExampleError.unexpectedFeatureType
}

// Check for cluster conformance
guard let cluster = pointFeature as? MGLCluster else {
throw ExampleError.featureIsNotACluster
}

// Currently the only supported class that conforms to `MGLCluster` is
// `MGLPointFeatureCluster`
guard cluster is MGLPointFeatureCluster else {
throw ExampleError.unexpectedFeatureType
}
```
*/
MGL_EXPORT
@protocol MGLCluster <MGLFeature>

/** The identifier for the cluster. */
@property (nonatomic, readonly) NSUInteger clusterIdentifier;

/** The number of points within this cluster */
@property (nonatomic, readonly) NSUInteger clusterPointCount;

@end

NS_ASSUME_NONNULL_END
18 changes: 17 additions & 1 deletion platform/darwin/src/MGLFeature.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#import "MGLPointAnnotation.h"
#import "MGLPointCollection.h"
#import "MGLShapeCollection.h"
#import "MGLCluster.h"

NS_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -186,12 +187,27 @@ MGL_EXPORT
#### Related examples
See the <a href="https://www.mapbox.com/ios-sdk/maps/examples/runtime-multiple-annotations/">
Dynamically style interactive points</a> example to learn how to initialize
`MGLPointFeature` objects and add it them your map.
`MGLPointFeature` objects and add them to your map.
*/
MGL_EXPORT
@interface MGLPointFeature : MGLPointAnnotation <MGLFeature>
@end

/**
An `MGLPointFeatureCluster` object associates a point shape (with an optional
identifier and attributes) and represents a point cluster.

@see `MGLCluster`

#### Related examples
See the <a href="https://www.mapbox.com/ios-sdk/maps/examples/clustering/">
Clustering point data</a> example to learn how to initialize
clusters and add them to your map.
*/
julianrex marked this conversation as resolved.
Show resolved Hide resolved
MGL_EXPORT
@interface MGLPointFeatureCluster : MGLPointFeature <MGLCluster>
@end

/**
An `MGLPolylineFeature` object associates a polyline shape with an optional
identifier and attributes.
Expand Down
62 changes: 59 additions & 3 deletions platform/darwin/src/MGLFeature.mm
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#import "MGLFoundation_Private.h"
#import "MGLFeature_Private.h"
#import "MGLCluster.h"

#import "MGLPointAnnotation.h"
#import "MGLPolyline.h"
Expand All @@ -19,6 +21,11 @@
#import <mbgl/style/conversion/geojson.hpp>
#import <mapbox/feature.hpp>

// Cluster constants
static NSString * const MGLClusterIdentifierKey = @"cluster_id";
static NSString * const MGLClusterCountKey = @"point_count";
const NSUInteger MGLClusterIdentifierInvalid = NSUIntegerMax;

@interface MGLEmptyFeature ()
@end

Expand Down Expand Up @@ -92,6 +99,31 @@ - (NSString *)description

@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

Expand Down Expand Up @@ -318,14 +350,38 @@ - (NSDictionary *)geoJSONDictionary {
*/
template <typename T>
class GeometryEvaluator {
private:
const mbgl::PropertyMap *shared_properties;

public:
GeometryEvaluator(const mbgl::PropertyMap *properties = nullptr):
shared_properties(properties)
{}

MGLShape <MGLFeature> * operator()(const mbgl::EmptyGeometry &) const {
MGLEmptyFeature *feature = [[MGLEmptyFeature alloc] init];
return feature;
}

MGLShape <MGLFeature> * operator()(const mbgl::Point<T> &geometry) const {
MGLPointFeature *feature = [[MGLPointFeature alloc] init];
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<bool>()) {
if (clusterValue.template get<bool>()) {
pointFeatureClass = [MGLPointFeatureCluster class];
}
}
}
}

MGLPointFeature *feature = [[pointFeatureClass alloc] init];
feature.coordinate = toLocationCoordinate2D(geometry);
return feature;
}
Expand Down Expand Up @@ -374,7 +430,7 @@ - (NSDictionary *)geoJSONDictionary {
return [MGLShapeCollectionFeature shapeCollectionWithShapes:shapes];
}

private:
private:
julianrex marked this conversation as resolved.
Show resolved Hide resolved
static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point<T> &point) {
return CLLocationCoordinate2DMake(point.y, point.x);
}
Expand Down Expand Up @@ -443,7 +499,7 @@ static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point<T> &point
ValueEvaluator evaluator;
attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator);
}
GeometryEvaluator<double> evaluator;
GeometryEvaluator<double> evaluator(&feature.properties);
MGLShape <MGLFeature> *shape = mapbox::geometry::geometry<double>::visit(feature.geometry, evaluator);
if (!feature.id.is<mapbox::feature::null_value_t>()) {
shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator());
Expand Down
1 change: 1 addition & 0 deletions platform/darwin/src/MGLFeature_Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ NSArray<MGLShape <MGLFeature> *> *MGLFeaturesFromMBGLFeatures(const std::vector<
/**
Returns an `MGLFeature` object converted from the given mbgl::Feature
*/
MGL_EXPORT
id <MGLFeature> MGLFeatureFromMBGLFeature(const mbgl::Feature &feature);

/**
Expand Down
6 changes: 6 additions & 0 deletions platform/darwin/src/MGLFoundation_Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ void MGLInitializeRunLoop();
(type *)([temp##__LINE__ isKindOfClass:[type class]] ? temp##__LINE__ : nil); \
})

#define MGL_OBJC_DYNAMIC_CAST_AS_PROTOCOL(object, proto) \
({ \
__typeof__( object ) temp##__LINE__ = (object); \
(id< proto >)([temp##__LINE__ conformsToProtocol:@protocol( proto )] ? temp##__LINE__ : nil); \
})

42 changes: 42 additions & 0 deletions platform/darwin/src/MGLShapeSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
NS_ASSUME_NONNULL_BEGIN

@protocol MGLFeature;
@class MGLPointFeature;
@class MGLPointFeatureCluster;
@class MGLShape;

/**
Expand Down Expand Up @@ -321,6 +323,46 @@ MGL_EXPORT
*/
- (NSArray<id <MGLFeature>> *)featuresMatchingPredicate:(nullable NSPredicate *)predicate;

/**
Returns an array of map features that are the leaves of the specified cluster.
("Leaves" are the original points that belong to the cluster.)

This method supports pagination; you supply an offset (number of features to skip)
and a maximum number of features to return.

@param cluster An object of type `MGLPointFeatureCluster` (that conforms to the `MGLCluster` protocol).
@param offset Number of features to skip.
@param limit Maximum number of features to return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@param limit Maximum number of features to return
@param limit The maximum number of features to return.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☝️


@return An array of objects that conform to the `MGLFeature` protocol.
*/
- (NSArray<id <MGLFeature>> *)leavesOfCluster:(MGLPointFeatureCluster *)cluster offset:(NSUInteger)offset limit:(NSUInteger)limit;

/**
Returns an array of map features that are the immediate children of the specified
cluster *on the next zoom level*. The may include features that also conform to
the `MGLCluster` protocol (currently only objects of type `MGLPointFeatureCluster`).

@param cluster An object of type `MGLPointFeatureCluster` (that conforms to the `MGLCluster` protocol).

@return An array of objects that conform to the `MGLFeature` protocol.

@note The returned array may contain the `cluster` that was passed in, if the next
zoom level doesn't match the zoom level for expanding that cluster. See
`-[MGLShapeSource zoomLevelForExpandingCluster:]`.
*/
- (NSArray<id<MGLFeature>> *)childrenOfCluster:(MGLPointFeatureCluster *)cluster;

/**
Returns the zoom level at which the given cluster expands.

@param cluster An object of type `MGLPointFeatureCluster` (that conforms to the `MGLCluster` protocol).

@return Zoom level. This should be >= 0; any negative return value should be
considered an error.
*/
- (double)zoomLevelForExpandingCluster:(MGLPointFeatureCluster *)cluster;

@end

NS_ASSUME_NONNULL_END
Loading