Skip to content
This repository has been archived by the owner on Jun 14, 2024. It is now read-only.

Updates cluster example for new cluster APIs #264

Merged
merged 6 commits into from
Jan 22, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
Updated for pending cluster changes in the SDK
  • Loading branch information
Julian Rex committed Jan 18, 2019
commit 3da05b7c8261cd329d06c54e4dad079f1fc8d5ef
105 changes: 98 additions & 7 deletions Examples/ObjectiveC/ClusteringExample.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

NSString *const MBXExampleClustering = @"ClusteringExample";

@interface ClusteringExample () <MGLMapViewDelegate>
@interface ClusteringExample () <MGLMapViewDelegate, UIGestureRecognizerDelegate>

@property (nonatomic) MGLMapView *mapView;
@property (nonatomic) UIImage *icon;
Expand All @@ -22,7 +22,28 @@ - (void)viewDidLoad {
self.mapView.delegate = self;
[self.view addSubview:self.mapView];

// Add our own gesture recognizer to handle taps on our custom map features. This gesture requires the built-in MGLMapView tap gestures (such as those for zoom and annotation selection) to fail.

// Add a double tap gesture recognizer. This gesture is used for double
// tapping on clusters, and then zooming in so the cluster expands to its
// children
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTapCluster:)];
doubleTap.numberOfTapsRequired = 2;
doubleTap.delegate = self;

// We require this new double tap fails before the map view's built-in
// gesture is recognized. (Note this is different from the order below for
// the single tap.)
for (UIGestureRecognizer *recognizer in self.mapView.gestureRecognizers) {
if ([recognizer isKindOfClass:[UITapGestureRecognizer class]] &&
((UITapGestureRecognizer*)recognizer).numberOfTapsRequired == 2) {
[recognizer requireGestureRecognizerToFail:doubleTap];
}
}
[self.mapView addGestureRecognizer:doubleTap];

// Add a single tap gesture recognizer. This gesture requires the built-in
// MGLMapView tap gestures (such as those for zoom and annotation selection)
// to fail.
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleMapTap:)];
for (UIGestureRecognizer *recognizer in self.mapView.gestureRecognizers) {
if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) {
Expand Down Expand Up @@ -58,6 +79,7 @@ - (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style {
@50: [UIColor orangeColor],
@100: [UIColor redColor],
@200: [UIColor purpleColor] };

// Show clustered features as circles. The `point_count` attribute is built into clustering-enabled source features.
MGLCircleStyleLayer *circlesLayer = [[MGLCircleStyleLayer alloc] initWithIdentifier:@"clusteredPorts" source:source];
circlesLayer.circleRadius = [NSExpression expressionForConstantValue:@(self.icon.size.width / 2)];
Expand All @@ -83,6 +105,64 @@ - (void)mapViewRegionIsChanging:(MGLMapView *)mapView {
[self showPopup:NO animated:NO];
}

- (MGLPointFeatureCluster *)firstClusterWithGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
CGPoint point = [gestureRecognizer locationInView:gestureRecognizer.view];
CGFloat width = self.icon.size.width;
CGRect rect = CGRectMake(point.x - width / 2, point.y - width / 2, width, width);

// If you want to identify the ports or clusters separately you can do
// the following:
//
// NSArray *clusters = [self.mapView visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:[NSSet setWithObject:@"clusteredPorts"]];
// NSArray *ports = [self.mapView visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:[NSSet setWithObject:@"ports"]];
//
// However, this example shows how to check if a feature is a cluster by
// checking for that the feature is a `MGLPointFeatureCluster` (you could
// also check for conformance with `MGLCluster`

NSArray<id<MGLFeature>> *features = [self.mapView visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:[NSSet setWithObjects:@"clusteredPorts", @"ports", nil]];

NSPredicate *clusterPredicate = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
return [evaluatedObject isKindOfClass:[MGLPointFeatureCluster class]];
}];

NSArray *clusters = [features filteredArrayUsingPredicate:clusterPredicate];

// Here we pick the first cluster, but ideally we'd pick the nearest one to
// the touch point
return (MGLPointFeatureCluster *)clusters.firstObject;
}

- (IBAction)handleDoubleTapCluster:(UITapGestureRecognizer *)sender {

MGLSource *source = [self.mapView.style sourceWithIdentifier:@"clusteredPorts"];

if (![source isKindOfClass:[MGLShapeSource class]]) {
return;
}

if (sender.state != UIGestureRecognizerStateEnded) {
return;
}

[self showPopup:NO animated:NO];

MGLPointFeatureCluster *cluster = [self firstClusterWithGestureRecognizer:sender];

if (!cluster) {
return;
}

double zoom = [(MGLShapeSource *)source zoomLevelForExpandingCluster:cluster];

if (zoom > 0.0) {
[self.mapView setCenterCoordinate:cluster.coordinate
zoomLevel:zoom
animated:YES];
}
}


- (IBAction)handleMapTap:(UITapGestureRecognizer *)tap {

MGLSource *source = [self.mapView.style sourceWithIdentifier:@"clusteredPorts"];
Expand Down Expand Up @@ -112,6 +192,8 @@ - (IBAction)handleMapTap:(UITapGestureRecognizer *)tap {

NSArray<id<MGLFeature>> *features = [self.mapView visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:[NSSet setWithObjects:@"clusteredPorts", @"ports", nil]];

// Here we pick the first feature, but ideally we'd pick the nearest feature
// to the touch point
id<MGLFeature> feature = features.firstObject;

if (!feature) {
Expand All @@ -121,15 +203,14 @@ - (IBAction)handleMapTap:(UITapGestureRecognizer *)tap {
NSString *description = @"No port name";
UIColor *color = UIColor.redColor;

if ([feature conformsToProtocol:@protocol(MGLCluster)]) {
if ([feature isKindOfClass:[MGLPointFeatureCluster class]]) {
// Tapped on a cluster
id<MGLCluster> cluster = (id<MGLCluster>)feature;
MGLPointFeatureCluster *cluster = (MGLPointFeatureCluster *)feature;

NSArray *children = [(MGLShapeSource*)source childrenOfCluster:cluster];
description = [NSString stringWithFormat:@"Cluster #%ld\n%ld children\n%@ points",
description = [NSString stringWithFormat:@"Cluster #%ld\n%ld children",
cluster.clusterIdentifier,
children.count,
cluster.clusterPointCountAbbreviation];
children.count];
color = UIColor.blueColor;
} else {
// Tapped on a port
Expand Down Expand Up @@ -204,4 +285,14 @@ - (void)showPopup:(BOOL)shouldShow animated:(BOOL)animated {
}
}

#pragma mark - UIGestureRecognizerDelegate

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return [self firstClusterWithGestureRecognizer:gestureRecognizer] != nil;
}

@end
97 changes: 84 additions & 13 deletions Examples/Swift/ClusteringExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,25 @@ class ClusteringExample_Swift: UIViewController, MGLMapViewDelegate {
mapView.delegate = self
view.addSubview(mapView)

// Add a single tap gesture recognizer. This gesture requires the built-in MGLMapView tap gestures (such as those for zoom and annotation selection) to fail.
// Add a double tap gesture recognizer. This gesture is used for double
// tapping on clusters, and then zooming in so the cluster expands to its
// children
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTapCluster(sender:)))
doubleTap.numberOfTapsRequired = 2
doubleTap.delegate = self

// We require this new double tap fails before the map view's built-in
julianrex marked this conversation as resolved.
Show resolved Hide resolved
// gesture is recognized. (Note this is different from the order below for
julianrex marked this conversation as resolved.
Show resolved Hide resolved
// the single tap.)
for recognizer in mapView.gestureRecognizers!
where (recognizer as? UITapGestureRecognizer)?.numberOfTapsRequired == 2 {
recognizer.require(toFail: doubleTap)
}
mapView.addGestureRecognizer(doubleTap)

// Add a single tap gesture recognizer. This gesture requires the built-in
// MGLMapView tap gestures (such as those for zoom and annotation selection)
// to fail.
let singleTap = UITapGestureRecognizer(target: self, action: #selector(handleMapTap(sender:)))
for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
singleTap.require(toFail: recognizer)
Expand Down Expand Up @@ -82,7 +100,53 @@ class ClusteringExample_Swift: UIViewController, MGLMapViewDelegate {
showPopup(false, animated: false)
}

@objc @IBAction func handleMapTap(sender: UITapGestureRecognizer) throws {
private func firstCluster(with gestureRecognizer: UIGestureRecognizer) -> MGLPointFeatureCluster? {
let point = gestureRecognizer.location(in: gestureRecognizer.view)
let width = icon.size.width
let rect = CGRect(x: point.x - width / 2, y: point.y - width / 2, width: width, height: width)

// If you want to identify the ports or clusters separately you can do
// the following:
//
// let ports = mapView.visibleFeatures(in: rect, styleLayerIdentifiers: ["ports"])
// let clusters = mapView.visibleFeatures(in: rect, styleLayerIdentifiers: ["clusteredPorts"])
//
// However, this example shows how to check if a feature is a cluster by
// checking for that the feature is a `MGLPointFeatureCluster` (you could
// also check for conformance with `MGLCluster`
captainbarbosa marked this conversation as resolved.
Show resolved Hide resolved

let features = mapView.visibleFeatures(in: rect, styleLayerIdentifiers: ["clusteredPorts", "ports"])
let clusters = features.compactMap { $0 as? MGLPointFeatureCluster }

// Here we pick the first cluster, but ideally we'd pick the nearest one to
// the touch point
captainbarbosa marked this conversation as resolved.
Show resolved Hide resolved
return clusters.first
}

@objc func handleDoubleTapCluster(sender: UITapGestureRecognizer) {

guard let source = mapView.style?.source(withIdentifier: "clusteredPorts") as? MGLShapeSource else {
return
}

guard sender.state == .ended else {
return
}

showPopup(false, animated: false)

guard let cluster = firstCluster(with: sender) else {
return
}

let zoom = source.zoomLevel(forExpanding: cluster)

if zoom > 0 {
mapView.setCenter(cluster.coordinate, zoomLevel: zoom, animated: true)
}
}

@objc func handleMapTap(sender: UITapGestureRecognizer) {

guard let source = mapView.style?.source(withIdentifier: "clusteredPorts") as? MGLShapeSource else {
return
Expand All @@ -98,28 +162,21 @@ class ClusteringExample_Swift: UIViewController, MGLMapViewDelegate {
let width = icon.size.width
let rect = CGRect(x: point.x - width / 2, y: point.y - width / 2, width: width, height: width)

// If you want to identify the ports or clusters separately you can do
// the following:
//
// let ports = mapView.visibleFeatures(in: rect, styleLayerIdentifiers: ["ports"])
// let clusters = mapView.visibleFeatures(in: rect, styleLayerIdentifiers: ["clusteredPorts"])
//
// However, this example shows how to check if a feature is a cluster by
// checking for conformance with the `MGLCluster` protocol.

let features = mapView.visibleFeatures(in: rect, styleLayerIdentifiers: ["clusteredPorts", "ports"])

// Here we pick the first feature, but ideally we'd pick the nearest feature
captainbarbosa marked this conversation as resolved.
Show resolved Hide resolved
// to the touch point
guard let feature = features.first else {
return
}

let description: String
let color: UIColor

if let cluster = feature as? MGLCluster {
if let cluster = feature as? MGLPointFeatureCluster {
// Tapped on a cluster
let children = source.children(of: cluster)
description = "Cluster #\(cluster.clusterIdentifier)\n\(children.count) children\n\(cluster.clusterPointCountAbbreviation) points"
description = "Cluster #\(cluster.clusterIdentifier)\n\(children.count) children"
color = .blue
} else if let featureName = feature.attribute(forKey: "name") as? String?,
// Tapped on a port
Expand Down Expand Up @@ -191,3 +248,17 @@ class ClusteringExample_Swift: UIViewController, MGLMapViewDelegate {
}
}
}

extension ClusteringExample_Swift: UIGestureRecognizerDelegate {

public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// Note, this will only get called for our custom double tap gesture,
// that we always want to recognize simultaneously.
return true
}

public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
// Note, this will only get called for our custom double tap gesture.
return firstCluster(with: gestureRecognizer) != nil
}
}