Clustering MGLAnnotations Images on iOS mapbox using MGLPointCollectionFeature - ios

Using latest Mapbox iOS SDK. Custom MGLAnnotations with different images that all appear correctly. I am setting annotations like so:
self.mapView.addAnnotations(annotations)
self.setupClustering(for:annotations)
Trying to implement example provided by mapbox using MGLPointCollectionFeature instead of GeojSON file.
Hardcoding with to a large number to make sure that clustering occurs. Actual width of the icon is 29.
EDIT: no clustering, circler with number does not appear
let clusterSource = MGLShapeSource(identifier: "clusterSource", shape: nil, options: [.clustered: true, .clusterRadius: 100])
func setupClustering(for annotations:[MGLAnnotation]) {
let coordinates = annotations.map { $0.coordinate }
let allAnnotationsFeature = MGLPointCollectionFeature(coordinates: coordinates, count: UInt(coordinates.count))
self.clusterSource.shape = allAnnotationsFeature
}
func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
self.mapView.style?.addSource(self.clusterSource)
// Color clustered features based on clustered point counts.
let stops = [
2: UIColor.lightGray,
5: UIColor.orange,
10: UIColor.red
]
// Show clustered features as circles. The `point_count` attribute is built into
// clustering-enabled source features.
let circlesLayer = MGLCircleStyleLayer(identifier: "clusteredItems", source: self.clusterSource)
circlesLayer.circleRadius = NSExpression(forConstantValue: NSNumber(value: Double(100) / 2))
circlesLayer.circleOpacity = NSExpression(forConstantValue: 0.75)
circlesLayer.circleStrokeColor = NSExpression(forConstantValue: UIColor.white.withAlphaComponent(0.75))
circlesLayer.circleStrokeWidth = NSExpression(forConstantValue: 2)
circlesLayer.circleColor = NSExpression(format: "mgl_step:from:stops:(point_count, %#, %#)", UIColor.lightGray, stops)
circlesLayer.predicate = NSPredicate(format: "cluster == YES")
self.mapView.style?.addLayer(circlesLayer)
// Label cluster circles with a layer of text indicating feature count. The value for
// `point_count` is an integer. In order to use that value for the
// `MGLSymbolStyleLayer.text` property, cast it as a string.
let numbersLayer = MGLSymbolStyleLayer(identifier: "clusterNumbers", source: self.clusterSource)
numbersLayer.textColor = NSExpression(forConstantValue: UIColor.white)
numbersLayer.textFontSize = NSExpression(forConstantValue: NSNumber(value: Double(100) / 2))
numbersLayer.iconAllowsOverlap = NSExpression(forConstantValue: true)
numbersLayer.text = NSExpression(format: "CAST(point_count, 'NSString')")
numbersLayer.predicate = NSPredicate(format: "cluster == YES")
self.mapView.style?.addLayer(numbersLayer)
}

It looks like you're initializing your MGLShapeSource with shapes instead of features (https://docs.mapbox.com/ios/api/maps/6.0.0/Classes/MGLShapeSource.html#/c:objc(cs)MGLShapeSource(im)initWithIdentifier:features:options:). This is preventing your points from clustering because any MGLFeature instance passed into this initializer is treated as an ordinary shape, causing any attributes to be inaccessible to an MGLVectorStyleLayer when evaluating a predicate or configuring certain layout or paint attributes.
Additionally, for clustering, you would want to use point shapes as mentioned here. At the moment, you're using MGLPointCollectionFeatures instead of MGLPointFeatures, which means that instead of having 5 point features with 5 features, each with their own attributes, you have a point collection feature containing five points with one feature that has the same attribute.
If you create arrays of MGLPointFeatures instead, you can use these in the features parameter for your shape source initializers.
I’ve included an example below:
var unclusteredFeatures = [MGLPointFeature]()
for coordinate in coordinates {
let point = MGLPointFeature()
point.coordinate = coordinate
unclusteredFeatures.append(point)
}
let source = MGLShapeSource(identifier: "Features",
features: unclusteredFeatures,
options: [.clustered: true, .clusterRadius: 20])
style.addSource(source)

Related

Building a conditional expression using the Mapbox iOS SDK

I'm updating an existing iOS app that uses the Mapbox v6 SDK. The app defines a shape source using an array of point features and specifies that the points should be clustered based on a particular radius. Here's the code to do this:
let source = MGLShapeSource(identifier: sourceIdentifier, features: features, options: [.clustered: true, .clusterRadius: 50.0, .maximumZoomLevelForClustering: 14.0])
I also have a circle style layer defined to show the clusters on the map:
let circularClusterLayer = MGLCircleStyleLayer(identifier: "clusteredMarkers", source: source)
circularClusterLayer.predicate = NSPredicate(format: "cluster == YES")
This is working as expected but now I need to define the circle color based on an attribute of all the features located in the cluster. Specifically, each of my features has a boolean property in its attributes dictionary called .needsHelp. I'm trying to define the circle color for the cluster marker based on this logic:
if any of the features in the cluster have .needsHelp == true {
// set the circle color = UIColor.red
circularClusterLayer.circleColor = NSExpression(forConstantValue: UIColor.red)
} else {
// set the circle color = UIColor.gray
circularClusterLayer.circleColor = NSExpression(forConstantValue: UIColor.gray)
}
I'm pretty sure that I should be able to build an expression that returns true if any of the features in the cluster have .needsHelp == true or otherwise, returns false. And I believe this expression would be assigned to the .clusterProperties option for the source like this:
let expression1 = NSExpression(mglJSONObject: ["any", ["==", ["boolean", ["get", "isInEmergency"]], true]])
let expression2 = NSExpression(forConstantValue: UIColor.red)
let clusterPropertyDictionary: NSDictionary = ["clusterContainsFeatureThatNeedsHelp" : [expression1, expression2]]
let selectedContactsSource = MGLShapeSource(identifier: sourceIdentifier, features: features, options: [.clustered: true, .clusterRadius: 50.0, .maximumZoomLevelForClustering: 14.0, .clusterProperties: clusterPropertyDictionary])
I've been looking at the Mapbox expressions guide and I think I should be using the "any" decision expression to get a boolean value back indicating whether any of the features have .needsHelp == true or not. But so far I've been unable to translate the LISP-like expression syntax into an NSExpression that works.
I'm assuming that in addition to the expression above, I will also need another expression to check the value of "clusterContainsFeatureThatNeedsHelp" and then set the circle color accordingly. My best guess is that that would look something like this:
// TODO: check value of clusterContainsFeatureThatNeedsHelp to determine circle color
circularClusterLayer.circleColor = NSExpression(forConditional: NSPredicate(format: "%K == %#", "clusterContainsFeatureThatNeedsHelp", NSNumber(value: true)), trueExpression: NSExpression(forConstantValue: UIColor.red), falseExpression: NSExpression(forConstantValue: UIColor.gray))
Unfortunately, I'm not having any luck building either of these NSExpressions successfully. Has anyone done something like this before? If so, I'd appreciate any tips or suggestions!
I finally got this working. I'm now creating the clusterPropertyDictionary like this:
let firstExpression = NSExpression(format: "max:({$featureAccumulated, clusterContainsFeatureThatNeedsHelp})")
let secondExpression = NSExpression(format: "CAST(needsHelp, 'NSNumber')")
let clusterPropertiesDictionary = ["clusterContainsFeatureThatNeedsHelp" : [firstExpression, secondExpression]]
And the expression that actually sets the circle color looks like this:
let expressionForClusterCircleColor = NSExpression(
forConditional: NSPredicate(format: "clusterContainsFeatureThatNeedsHelp > 0"),
trueExpression: NSExpression(forConstantValue: UIColor.red),
falseExpression: NSExpression(forConstantValue: UIColor.gray)
)
circularClusterLayer.circleColor = expressionForClusterCircleColor

Mapbox iOS clustering works, but circle style layer and numbers layer aren't appearing/reflecting the marker density of the cluster

I am using Mapbox to create an iOS application. The application gets makes a request to my API that returns a number of events going on within the map's bounding box in JSON format.
I was previously not using clustering, so some map annotations were simply covering others. I am using this Mapbox tutorial which creates an MGLShapeCollectionFeature from a GeoJSON file, creates an MGLShapeSource from the shape collection feature, and then creates a marker layer as an MGLSymbolStyleLayer, a circle layer as an MGLCircleStyleLayer, and a numbers layer as an MGLSymbolStyleLayer. The marker layer shows each individual event geographically, the circle layer and number layer come together to represent the marker count of each cluster.
The final product should look similar to the Mapbox example:
This is the GeoJSON file that the example uses to show clustering seaports on a world map.
The following is the relevant code that the example uses to convert said GeoJSON into the relevant source and layers to populate the map:
let url = URL(fileURLWithPath: Bundle.main.path(forResource: "ports", ofType: "geojson")!)
let source = MGLShapeSource(identifier: "clusteredPorts",
url: url,
options: [.clustered: true, .clusterRadius: icon.size.width])
style.addSource(source)
// Use a template image so that we can tint it with the `iconColor` runtime styling property.
style.setImage(icon.withRenderingMode(.alwaysTemplate), forName: "icon")
// Show unclustered features as icons. The `cluster` attribute is built into clustering-enabled
// source features.
let ports = MGLSymbolStyleLayer(identifier: "ports", source: source)
ports.iconImageName = NSExpression(forConstantValue: "icon")
ports.iconColor = NSExpression(forConstantValue: UIColor.darkGray.withAlphaComponent(0.9))
ports.predicate = NSPredicate(format: "cluster != YES")
style.addLayer(ports)
// Color clustered features based on clustered point counts.
let stops = [
20: UIColor.lightGray,
50: UIColor.orange,
100: UIColor.red,
200: UIColor.purple
]
// Show clustered features as circles. The `point_count` attribute is built into
// clustering-enabled source features.
let circlesLayer = MGLCircleStyleLayer(identifier: "clusteredPorts", source: source)
circlesLayer.circleRadius = NSExpression(forConstantValue: NSNumber(value: Double(icon.size.width) / 2))
circlesLayer.circleOpacity = NSExpression(forConstantValue: 0.75)
circlesLayer.circleStrokeColor = NSExpression(forConstantValue: UIColor.white.withAlphaComponent(0.75))
circlesLayer.circleStrokeWidth = NSExpression(forConstantValue: 2)
circlesLayer.circleColor = NSExpression(format: "mgl_step:from:stops:(point_count, %#, %#)", UIColor.lightGray, stops)
circlesLayer.predicate = NSPredicate(format: "cluster == YES")
style.addLayer(circlesLayer)
// Label cluster circles with a layer of text indicating feature count. The value for
// `point_count` is an integer. In order to use that value for the
// `MGLSymbolStyleLayer.text` property, cast it as a string.
let numbersLayer = MGLSymbolStyleLayer(identifier: "clusteredPortsNumbers", source: source)
numbersLayer.textColor = NSExpression(forConstantValue: UIColor.white)
numbersLayer.textFontSize = NSExpression(forConstantValue: NSNumber(value: Double(icon.size.width) / 2))
numbersLayer.iconAllowsOverlap = NSExpression(forConstantValue: true)
numbersLayer.text = NSExpression(format: "CAST(point_count, 'NSString')")
numbersLayer.predicate = NSPredicate(format: "cluster == YES")
style.addLayer(numbersLayer)
This is the GeoJSON format that my events are being returned from my API as. This formatting should be correct, as Mapbox is accepting it and creating an MGLShapeCollectionFeature from its data.
My code is very similar to that seen in the Mapbox example. I first create the GeoJSON file
//geoJson is my GeoJSON file as [String: Any]
var shapes: MGLShapeCollectionFeature!
if let data = try? JSONSerialization.data(withJSONObject: geoJson, options: .prettyPrinted) {
do {
shapes = try MGLShape(data: data, encoding: String.Encoding.utf8.rawValue) as! MGLShapeCollectionFeature
} catch {
print(error.localizedDescription)
}
}
I know this GeoJSON is being converted into MGLShapeCollectionFeature as the app would crash if it didn't, and the MGLShapeCollectionFeature created successfully creates a source that layers are being created from/populating the map. So I create an MGLShapeSource from this MGLShapeCollectionFeature:
let marker = UIImage(named: "redPin")?.resize(targetSize: CGSize(width: 25, height: 25))
let source = MGLShapeSource(identifier: "clusteredPoints", shape: shapes, options: [.clustered: true, .clusterRadius: 0.5])
self.mapStyle!.addSource(source)
// Use a template image so that we can tint it with the `iconColor` runtime styling property.
self.mapStyle!.setImage(marker!, forName: "marker")
I then create layers from 'source' and add them to my map's style.
// Show unclustered features as icons. The `cluster` attribute is built into clustering-enabled
// source features.
let events = MGLSymbolStyleLayer(identifier: "events", source: source)
events.iconImageName = NSExpression(forConstantValue: "marker")
events.iconColor = NSExpression(forConstantValue: UIColor.darkGray.withAlphaComponent(0.9))
events.predicate = NSPredicate(format: "cluster != YES")
self.mapStyle!.addLayer(events)
// Color clustered features based on clustered point counts.
let stops = [
5: UIColor.lightGray,
10: UIColor.orange,
20: UIColor.red,
30: UIColor.purple
]
// Show clustered features as circles. The `point_count` attribute is built into
// clustering-enabled source features.
let circlesLayer = MGLCircleStyleLayer(identifier: "clusteredEvents", source: source)
circlesLayer.circleRadius = NSExpression(forConstantValue: NSNumber(value: Double(self.mapStyle!.image(forName: "marker")!.size.width) / 2))
circlesLayer.circleOpacity = NSExpression(forConstantValue: 0.75)
circlesLayer.circleStrokeColor = NSExpression(forConstantValue: UIColor.white.withAlphaComponent(0.75))
circlesLayer.circleStrokeWidth = NSExpression(forConstantValue: 2)
circlesLayer.circleColor = NSExpression(format: "mgl_step:from:stops:(point_count, %#, %#)", UIColor.lightGray, stops)
circlesLayer.predicate = NSPredicate(format: "cluster == YES")
self.mapStyle!.addLayer(circlesLayer)
// Label cluster circles with a layer of text indicating feature count. The value for
// `point_count` is an integer. In order to use that value for the
// `MGLSymbolStyleLayer.text` property, cast it as a string.
let numbersLayer = MGLSymbolStyleLayer(identifier: "clusteredEventsNumbers", source: source)
numbersLayer.textColor = NSExpression(forConstantValue: UIColor.white)
numbersLayer.textFontSize = NSExpression(forConstantValue: NSNumber(value: Double(self.mapStyle!.image(forName: "marker")!.size.width) / 2))
numbersLayer.iconAllowsOverlap = NSExpression(forConstantValue: true)
numbersLayer.text = NSExpression(format: "CAST(point_count, 'NSString')")
numbersLayer.predicate = NSPredicate(format: "cluster == YES")
self.mapStyle!.addLayer(numbersLayer)
So the code is essentially the exact same, just the GeoJSON being input is different. And yet, the circle layer and numbers layer is not appearing when the events markers cluster. See below:
I know that the issue isn't that the Mapbox example's source is being loaded from a URL while my implementation's source is being loaded from an MGLShapeCollectionFeature, because I've tried loading the Mapbox example's seaports GeoJSON as an MGLShapeCollectionFeature and the seaports still show the circle/numbers layers when clustered.
So, I feel like an idiot.
The issue was in the MGLShapeSource:
MGLShapeSource(identifier: "clusteredPoints", shape: shapes, options: [.clustered: true, .clusterRadius: 0.5])
For whatever reason, I had been messing around with the clusterRadius, and had it set to 0.5, which I assume is in points. Note that the example was using the marker's width to determine the cluster radius.
let source = MGLShapeSource(identifier: "clusteredPorts",
url: url,
options: [.clustered: true, .clusterRadius: icon.size.width])
I though that, because some of the markers were disappearing whenever they overlapped with another marker, that they were clustering but the cluster layer was not being shown. They weren't clustering, I guess shape sources are just able to know when they are overlapping with another, and will disappear accordingly. Just because they disappear doesn't meant that they are clustered.

How can I set fill color depends on attribute value of polygon feature?

This is how I define polygon feature:
let polygon = MGLPolygonFeature(coordinates: &coordinates, count: UInt(coordinates.count))
polygon.attributes = ["name": card.name, "identifier": card.identifier, "color": card.set.colorMode] //UIColor
let cardSource = MGLShapeSource(identifier: "cards", features: [polygon], options: [:])
let polygonLayer = MGLFillStyleLayer(identifier: "polygon-level", source: cardSource)
polygonLayer.fillColor = MGLStyleValue(rawValue: "{color}")
polygonLayer.fillOpacity = MGLStyleValue(rawValue: 0.4)
but this doesnt work. How can I set fill color depends on attribute?
If you would like to set the color of a polygon based on a color value in its attributes dictionary, you can use an identity function.
polygonLayer.fillColor = MGLStyleValue(interpolationMode: .identity,
sourceStops: nil,
attributeName: "color",
options: nil)
The feature selection example shows other ways to style a fill style layer based on a value.

MapBox - detect zoomLevel changes

How can I simply detect zoom level changes? Is it possible?
I simply need to hide my annotation views when zoom level is not enough.
regionDidChange:animated: is not intended to use for me. Any other way?
I need to hide my labels here:
and show them here:
This is what I currently do with my labels:
class CardAnnotation: MGLPointAnnotation {
var card: Card
init(card: Card) {
self.card = card
super.init()
let coordinates = card.border.map { $0.coordinate }
let sumLatitudes = coordinates.map { $0.latitude }.reduce(0, +)
let sumLongitudes = coordinates.map { $0.longitude }.reduce(0, +)
let averageLatitude = sumLatitudes / Double(coordinates.count)
let averageLongitude = sumLongitudes / Double(coordinates.count)
coordinate = CLLocationCoordinate2D(latitude: averageLatitude, longitude: averageLongitude)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
var annotations = [CardAnnotation]()
mapView.addAnnotations(annotations)
Of the two main ways to add overlays to an MGLMapView, the runtime styling API is better suited for text labels and also for varying the appearance based on the zoom level. While you’re at it, you might as well create the polygons using the same API too.
Start by creating polygon features for the areas you want shaded in:
var cards: [MGLPolygonFeature] = []
var coordinates: [CLLocationCoordinate2D] = […]
let card = MGLPolygonFeature(coordinates: &coordinates, count: UInt(coordinates.count))
card.attributes = ["address": 123]
// …
cards.append(card)
Within any method that runs after the map finishes loading, such as MGLMapViewDelegate.mapView(_:didFinishLoading:), add a shape source containing these features to the current style:
let cardSource = MGLShapeSource(identifier: "cards", features: cards, options: [:])
mapView.style?.addSource(cardSource)
With the shape source in place, create a style layer that renders the polygon features as mauve fills:
let fillLayer = MGLFillStyleLayer(identifier: "card-fills", source: cardSource)
fillLayer.fillColor = NSExpression(forConstantValue: #colorLiteral(red: 0.9098039216, green: 0.8235294118, blue: 0.9647058824, alpha: 1))
mapView.style?.addLayer(fillLayer)
Then create another style layer that renders labels at each polygon feature’s centroid. (MGLSymbolStyleLayer automatically calculates the centroids, accounting for irregularly shaped polygons.)
// Same source as the fillLayer.
let labelLayer = MGLSymbolStyleLayer(identifier: "card-labels", source: cardSource)
// Each feature’s address is an integer, but text has to be a string.
labelLayer.text = NSExpression(format: "CAST(address, 'NSString')")
// Smoothly interpolate from transparent at z16 to opaque at z17.
labelLayer.textOpacity = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %#)",
[16: 0, 17: 1])
mapView.style?.addLayer(labelLayer)
As you customize these style layers, pay particular attention to the options on MGLSymbolStyleLayer that control whether nearby symbols are automatically hidden due to collision. You may find that the automatic collision detection makes it unnecessary to specify the textOpacity property.
When you create the source, one of the options you can pass into the MGLShapeSource initializer is MGLShapeSourceOption.clustered. However, in order to use that option, you’d have to create MGLPointFeatures, not MGLPolygonFeatures. Fortunately, MGLPolygonFeature has a coordinate property that lets you find the centroid without manual calculations:
var cardCentroids: [MGLPointFeature] = []
var coordinates: [CLLocationCoordinate2D] = […]
let card = MGLPolygonFeature(coordinates: &coordinates, count: UInt(coordinates.count))
let cardCentroid = MGLPointFeature()
cardCentroid.coordinate = card.coordinate
cardCentroid.attributes = ["address": 123]
cardCentroids.append(cardCentroid)
// …
let cardCentroidSource = MGLShapeSource(identifier: "card-centroids", features: cardCentroids, options: [.clustered: true])
mapView.style?.addSource(cardCentroidSource)
This clustered source can only be used with MGLSymbolStyleLayer or MGLCircleStyleLayer, not MGLFillStyleLayer. This example shows how to work with clustered points in more detail.
One option is to add the labels as a MGLSymbolStyleLayer, then determine the textOpacity based on zoom level.
If you are using the current version of the Maps SDK for iOS, you could try something like:
symbols.textOpacity = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %#)", [16.9: 0, 17: 1])
The dynamically styled interactive points example shows one approach to this.
Is the problem that when you zoom out, your annotations are too close together? If so, it is better to group them together than to hide them entirely. See Decluttering a Map with MapKit Annotation Clustering.

Clustering annotations with Mapbox

I'm trying to implement clustering in Mapbox for iOS and literally using this example from Mapbox website
It works fine, but I'd like to be able to use simple MGLAnnotations to place on the map and cluster them together if they are too close.
I read here that MGLShapeSource not only accepts external geoJSON, but other sources, such as polylines and annotations. But when I feed it with annotations array no clustering occur, I just see bunch of my markers from my annotations array:
let source = MGLShapeSource(identifier: "clusteredParkings", shapes: annotationsArray, options: [.clustered: true, .clusterRadius: 20])
When I swap the source back to geoJSON everything works again for clusters.
Btw there are no errors or warnings.
What am I doing wrong? Does anyone have a working example of Mapbox clustering with MGLAnnotations rather than geoJSON source file?
https://www.mapbox.com/ios-sdk/api/3.6.0/Classes/MGLShapeSource.html
I did some research on this not too long ago and it didn't seem to be possible on iOS. Here is the suggestion on github that is still open. Here is another issue about how it wasn't mentioned in the documentation but has since been added.
Does anyone have a working example of Mapbox clustering with MGLAnnotations rather than geoJSON source file?
Given a Style
let style: MGLStyle
1) Build/Get your [MGLPointFeature]
2) Build a MGLShapeSource
let source = MGLShapeSource(identifier: "YOUR_IDENTIFIER_A", features: YOUR_ MGLPointFeature_ARRAY, options: [.clustered: true, .clusterRadius: YOUR_WIDTH])
style.addSource(source)
3) Build a style for the marker when it's not clustered
let markerLayer = MGLSymbolStyleLayer(identifier: "YOUR_IDENTIFIER_B", source: source)
markerLayer.iconImageName = NSExpression(forConstantValue: "MARKER_IDENTIFIER")
markerLayer.predicate = NSPredicate(format: "cluster != YES")
style.setImage(UIImage(named: "MARKER_IMAGE")!, forName: "MARKER_IDENTIFIER")
4) Build a style for the cluster
let clusterLayer = MGLSymbolStyleLayer(identifier: "YOUR_IDENTIFIER_C", source: source)
clusterLayer.textColor = NSExpression(forConstantValue: UIColor.white)
clusterLayer.textFontSize = NSExpression(forConstantValue: NSNumber(value: Double(YOUR_WIDTH) / 2.5))
clusterLayer.iconAllowsOverlap = NSExpression(forConstantValue: true)
clusterLayer.textOffset = NSExpression(forConstantValue: CGVector(dx: 0, dy: -0.2))
clusterLayer.predicate = NSPredicate(format: "cluster == YES")
style.setImage(UIImage(named: "CLUSTER_IMAGE")!, forName: "CLUSTER_IDENTIFIER")
5) You can use Stops features. This will let you change cluster image based on clustering count change.
let stops = [
10: NSExpression(forConstantValue: "CLUSTER_IMAGE"),
50: NSExpression(forConstantValue: "ANOTHER_CLUSTER_IMAGE")
]
6) Use expressions to set each cluster's image based on defined stops and display the point count over the corresponding image
let defaultShape = NSExpression(forConstantValue: "CLUSTER_IDENTIFIER")
clusterLayer.iconImageName = NSExpression(format: "mgl_step:from:stops:(point_count, %#, %#)", defaultShape, stops)
clusterLayer.text = NSExpression(format: "CAST(point_count, 'NSString')")
7) Add layers to the Style
style.addLayer(markerLayer)
style.addLayer(clusterLayer)

Resources