Klusterpunktsdata i iOS SDK (förhandsversion)
Kommentar
Azure Kartor iOS SDK-tillbakadragning
Azure Kartor Native SDK för iOS är nu inaktuell och kommer att dras tillbaka den 3/31/25. För att undvika tjänststörningar migrerar du till Azure Kartor Web SDK senast 3/31/25. Mer information finns i migreringsguiden för Azure Kartor iOS SDK.
När du visar många datapunkter på kartan kan datapunkter överlappa varandra. Överlappningen kan orsaka att kartan blir oläslig och svår att använda. Klustringspunktsdata är en process för att kombinera punktdata som är nära varandra och som representerar dem på kartan som en enda klustrad datapunkt. När användaren zoomar in på kartan delas klustren upp i sina enskilda datapunkter. När du arbetar med ett stort antal datapunkter använder du klustringsprocesserna för att förbättra användarupplevelsen.
Internet of Things Show – Klustringsplatsdata i Azure Kartor
Förutsättningar
Slutför stegen i snabbstarten : Skapa ett iOS-appdokument . Kodblock i den här artikeln kan infogas i viewDidLoad
funktionen ViewController
.
Aktivera klustring på en datakälla
Aktivera klustring i DataSource
klassen genom att ange cluster
alternativet till true
. Ange clusterRadius
för att välja närliggande datapunkter och kombinera dem till ett kluster. Värdet för clusterRadius
är i punkter. Använd clusterMaxZoom
för att ange en zoomnivå där klustringslogik ska inaktiveras. Här är ett exempel på hur du aktiverar klustring i en datakälla.
// Create a data source and enable clustering.
let source = DataSource(options: [
//Tell the data source to cluster point data.
.cluster(true),
//The radius in points to cluster data points together.
.clusterRadius(45),
//The maximum zoom level in which clustering occurs.
//If you zoom in more than this, all points are rendered as symbols.
.clusterMaxZoom(15)
])
Varning
Klustring fungerar bara med Point
funktioner. Om datakällan innehåller funktioner för andra geometrityper, till exempel Polyline
eller Polygon
, uppstår ett fel.
Dricks
Om två datapunkter ligger nära varandra på marken är det möjligt att klustret aldrig bryts isär, oavsett hur nära användaren zoomar in. För att åtgärda detta kan du ange clusterMaxZoom
alternativet för att inaktivera klustringslogik och helt enkelt visa allt.
Klassen DataSource
innehåller även följande metoder som rör klustring.
Metod | Returtyp | beskrivning |
---|---|---|
children(of cluster: Feature) |
[Feature] |
Hämtar underordnade i det angivna klustret på nästa zoomnivå. Dessa underordnade kan vara en kombination av funktioner och undermappar. Underklusterarna blir funktioner med egenskaper som matchar ClusteredProperties. |
zoomLevel(forExpanding cluster: Feature) |
Double |
Beräknar en zoomnivå där klustret börjar expandera eller brytas isär. |
leaves(of cluster: Feature, offset: UInt, limit: UInt) |
[Feature] |
Hämtar alla punkter i ett kluster. limit Ange för att returnera en delmängd av punkterna och använd till-sidan offset genom punkterna. |
Visa kluster med ett bubbellager
Ett bubbellager är ett bra sätt att återge klustrade punkter. Använd uttryck för att skala radien och ändra färgen baserat på antalet punkter i klustret. Om du visar kluster med ett bubbellager bör du använda ett separat lager för att återge oupptäckta datapunkter.
Om du vill visa klustrets storlek ovanpå bubblan använder du ett symbolskikt med text och använder ingen ikon.
Följande kod visar klustrade punkter med ett bubbellager och antalet punkter i varje kluster med hjälp av ett symbollager. Ett andra symbolskikt används för att visa enskilda punkter som inte finns i ett kluster.
// Create a data source and enable clustering.
let source = DataSource(options: [
// Tell the data source to cluster point data.
.cluster(true),
// The radius in points to cluster data points together.
.clusterRadius(45),
// The maximum zoom level in which clustering occurs.
// If you zoom in more than this, all points are rendered as symbols.
.clusterMaxZoom(15)
])
// Import the geojson data and add it to the data source.
let url = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson")!
source.importData(fromURL: url)
// Add data source to the map.
map.sources.add(source)
// Create a bubble layer for rendering clustered data points.
map.layers.addLayer(
BubbleLayer(
source: source,
options: [
// Scale the size of the clustered bubble based on the number of points in the cluster.
.bubbleRadius(
from: NSExpression(
forAZMStepping: NSExpression(forKeyPath: "point_count"),
// Default of 20 point radius.
from: NSExpression(forConstantValue: 20),
stops: NSExpression(forConstantValue: [
// If point_count >= 100, radius is 30 points.
100: 30,
// If point_count >= 750, radius is 40 points.
750: 40
])
)
),
// Change the color of the cluster based on the value on the point_count property of the cluster.
.bubbleColor(
from: NSExpression(
forAZMStepping: NSExpression(forKeyPath: "point_count"),
// Default to green.
from: NSExpression(forConstantValue: UIColor.green),
stops: NSExpression(forConstantValue: [
// If the point_count >= 100, color is yellow.
100: UIColor.yellow,
// If the point_count >= 100, color is red.
750: UIColor.red
])
)
),
.bubbleStrokeWidth(0),
// Only rendered data points which have a point_count property, which clusters do.
.filter(from: NSPredicate(format: "point_count != NIL"))
]
)
)
// Create a symbol layer to render the count of locations in a cluster.
map.layers.addLayer(
SymbolLayer(
source: source,
options: [
// Hide the icon image.
.iconImage(nil),
// Display the point count as text.
.textField(from: NSExpression(forKeyPath: "point_count")),
.textOffset(CGVector(dx: 0, dy: 0.4)),
.textAllowOverlap(true),
// Allow clustered points in this layer.
.filter(from: NSPredicate(format: "point_count != NIL"))
]
)
)
// Create a layer to render the individual locations.
map.layers.addLayer(
SymbolLayer(
source: source,
options: [
// Filter out clustered points from this layer.
.filter(from: NSPredicate(format: "point_count = NIL"))
]
)
)
Följande bild visar ovanstående kod som visar klustrade punktfunktioner i ett bubbellager, skalade och färgade baserat på antalet punkter i klustret. Olustererade punkter återges med hjälp av ett symbolskikt.
Visa kluster med ett symbollager
När du visar datapunkter döljer symbolskiktet automatiskt symboler som överlappar varandra för att säkerställa ett renare användargränssnitt. Det här standardbeteendet kan vara oönskat om du vill visa datapunkternas densitet på kartan. De här inställningarna kan dock ändras. Om du vill visa alla symboler anger du iconAllowOverlap
alternativet för symbolskiktet till true
.
Använd klustring för att visa datapunkternas densitet samtidigt som du håller ett rent användargränssnitt. Följande exempel visar hur du lägger till anpassade symboler och representerar kluster och enskilda datapunkter med hjälp av symbolskiktet.
// Load all the custom image icons into the map resources.
map.images.add(UIImage(named: "earthquake_icon")!, withID: "earthquake_icon")
map.images.add(UIImage(named: "warning-triangle-icon")!, withID: "warning-triangle-icon")
// Create a data source and add it to the map.
let source = DataSource(options: [
// Tell the data source to cluster point data.
.cluster(true)
])
// Import the geojson data and add it to the data source.
let url = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson")!
source.importData(fromURL: url)
// Add data source to the map.
map.sources.add(source)
// Create a layer to render the individual locations.
map.layers.addLayer(
SymbolLayer(
source: source,
options: [
.iconImage("earthquake_icon"),
// Filter out clustered points from this layer.
.filter(from: NSPredicate(format: "point_count = NIL"))
]
)
)
// Create a symbol layer to render the clusters.
map.layers.addLayer(
SymbolLayer(
source: source,
options: [
.iconImage("warning-triangle-icon"),
.textField(from: NSExpression(forKeyPath: "point_count")),
.textOffset(CGVector(dx: 0, dy: -0.4)),
// Allow clustered points in this layer.
.filter(from: NSPredicate(format: "point_count != NIL"))
]
)
)
I det här exemplet läses följande bild in i appens resursmapp.
earthquake-icon.png | warning-triangle-icon.png |
Följande bild visar ovanstående kodåtergivning av klustrade och olustererade punktfunktioner med hjälp av anpassade ikoner.
Klustring och termisk kartskikt
Värmekartor är ett bra sätt att visa datadensiteten på kartan. Den här visualiseringsmetoden kan hantera ett stort antal datapunkter på egen hand. Om datapunkterna är klustrade och klusterstorleken används som vikt för värmekartan kan värmekartan hantera ännu mer data. För att uppnå det här alternativet anger du heatmapWeight
alternativet för värmekartlagret till NSExpression(forKeyPath: "point_count")
. När klusterradien är liten ser värmekartan nästan identisk ut med en värmekarta med hjälp av de olusterade datapunkterna, men den presterar bättre. Men ju mindre klusterradie, desto mer exakt är värmekartan, men med färre prestandafördelar.
// Create a data source and enable clustering.
let source = DataSource(options: [
// Tell the data source to cluster point data.
.cluster(true),
// The radius in points to cluster points together.
.clusterRadius(10)
])
// Import the geojson data and add it to the data source.
let url = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson")!
source.importData(fromURL: url)
// Add data source to the map.
map.sources.add(source)
// Create a heat map and add it to the map.
map.layers.insertLayer(
HeatMapLayer(
source: source,
options: [
// Set the weight to the point_count property of the data points.
.heatmapWeight(from: NSExpression(forKeyPath: "point_count")),
// Optionally adjust the radius of each heat point.
.heatmapRadius(20)
]
),
below: "labels"
)
Följande bild visar koden ovan som visar en värmekarta som optimerats med hjälp av klustrade punktfunktioner och antalet kluster som vikt i värmekartan.
Tryck på händelser på klustrade datapunkter
När tryckhändelser inträffar på ett lager som innehåller klustrade datapunkter återgår den klustrade datapunkten till händelsen som ett GeoJSON-punktfunktionsobjekt. Den här punktfunktionen har följande egenskaper:
Egenskapsnamn | Type | Beskrivning |
---|---|---|
cluster |
boolean | Anger om funktionen representerar ett kluster. |
point_count |
Nummer | Antalet punkter som klustret innehåller. |
point_count_abbreviated |
sträng | En sträng som förkortar point_count värdet om det är långt. (till exempel blir 4 000 4 000) |
Det här exemplet tar ett bubbellager som renderar klusterpunkter och lägger till en tryckhändelse. När tryckhändelsen utlöses beräknar och zoomar koden kartan till nästa zoomnivå, där klustret bryts isär. Den här funktionen implementeras med zoomLevel(forExpanding:)
hjälp av -metoden för DataSource
klassen.
// Create a data source and enable clustering.
let source = DataSource(options: [
// Tell the data source to cluster point data.
.cluster(true),
// The radius in points to cluster data points together.
.clusterRadius(45),
// The maximum zoom level in which clustering occurs.
// If you zoom in more than this, all points are rendered as symbols.
.clusterMaxZoom(15)
])
// Set data source to the class property to use in events handling later.
self.source = source
// Import the geojson data and add it to the data source.
let url = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson")!
source.importData(fromURL: url)
// Add data source to the map.
map.sources.add(source)
// Create a bubble layer for rendering clustered data points.
let clusterBubbleLayer = BubbleLayer(
source: source,
options: [
// Scale the size of the clustered bubble based on the number of points in the cluster.
.bubbleRadius(
from: NSExpression(
forAZMStepping: NSExpression(forKeyPath: "point_count"),
// Default of 20 point radius.
from: NSExpression(forConstantValue: 20),
stops: NSExpression(forConstantValue: [
// If point_count >= 100, radius is 30 points.
100: 30,
// If point_count >= 750, radius is 40 points.
750: 40
])
)
),
// Change the color of the cluster based on the value on the point_count property of the cluster.
.bubbleColor(
from: NSExpression(
forAZMStepping: NSExpression(forKeyPath: "point_count"),
// Default to green.
from: NSExpression(forConstantValue: UIColor.green),
stops: NSExpression(forConstantValue: [
// If the point_count >= 100, color is yellow.
100: UIColor.yellow,
// If the point_count >= 100, color is red.
750: UIColor.red
])
)
),
.bubbleStrokeWidth(0),
// Only rendered data points which have a point_count property, which clusters do.
.filter(from: NSPredicate(format: "point_count != NIL"))
]
)
// Add the clusterBubbleLayer to the map.
map.layers.addLayer(clusterBubbleLayer)
// Create a symbol layer to render the count of locations in a cluster.
map.layers.addLayer(
SymbolLayer(
source: source,
options: [
// Hide the icon image.
.iconImage(nil),
// Display the point count as text.
.textField(from: NSExpression(forKeyPath: "point_count_abbreviated")),
// Offset the text position so that it's centered nicely.
.textOffset(CGVector(dx: 0, dy: 0.4)),
// Allow text overlapping so text is visible anyway
.textAllowOverlap(true),
// Allow clustered points in this layer.
.filter(from: NSPredicate(format: "point_count != NIL"))
]
)
)
// Create a layer to render the individual locations.
map.layers.addLayer(
SymbolLayer(
source: source,
options: [
// Filter out clustered points from this layer.
.filter(from: NSPredicate(format: "point_count = NIL"))
]
)
)
// Add the delegate to handle taps on the clusterBubbleLayer only.
map.events.addDelegate(self, for: [clusterBubbleLayer.id])
func azureMap(_ map: AzureMap, didTapOn features: [Feature]) {
guard let source = source, let cluster = features.first else {
// Data source have been released or no features provided
return
}
// Get the cluster expansion zoom level. This is the zoom level at which the cluster starts to break apart.
let expansionZoom = source.zoomLevel(forExpanding: cluster)
// Update the map camera to be centered over the cluster.
map.setCameraOptions([
// Center the map over the cluster points location.
.center(cluster.coordinate),
// Zoom to the clusters expansion zoom level.
.zoom(expansionZoom),
// Animate the movement of the camera to the new position.
.animationType(.ease),
.animationDuration(200)
])
}
Följande bild visar ovanstående kod som visar klustrade punkter på en karta som när den trycks, zoomar in på nästa zoomnivå som ett kluster börjar bryta isär och expandera.
Visa klusterområde
Punktdata som ett kluster representerar är utspridda över ett område. I det här exemplet när ett kluster trycks på sker två huvudsakliga beteenden. För det första de enskilda datapunkter som finns i klustret som används för att beräkna ett konvext skrov. Sedan visas det konvexa skrovet på kartan för att visa ett område. Ett konvext skrov är en polygon som omsluter en uppsättning punkter som ett elastiskt band och kan beräknas med hjälp av convexHull(from:)
metoden. Alla punkter i ett kluster kan hämtas från datakällan med hjälp av leaves(of:offset:limit:)
metoden .
// Create a data source and enable clustering.
let source = DataSource(options: [
// Tell the data source to cluster point data.
.cluster(true)
])
// Set data source to the class property to use in events handling later.
self.source = source
// Import the geojson data and add it to the data source.
let url = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson")!
source.importData(fromURL: url)
// Add data source to the map.
map.sources.add(source)
// Create a data source for the convex hull polygon.
// Since this will be updated frequently it is more efficient to separate this into its own data source.
let polygonDataSource = DataSource()
// Set polygon data source to the class property to use in events handling later.
self.polygonDataSource = polygonDataSource
// Add data source to the map.
map.sources.add(polygonDataSource)
// Add a polygon layer and a line layer to display the convex hull.
map.layers.addLayer(PolygonLayer(source: polygonDataSource))
map.layers.addLayer(LineLayer(source: polygonDataSource))
// Load an icon into the image sprite of the map.
map.images.add(.azm_markerRed, withID: "marker-red")
// Create a symbol layer to render the clusters.
let clusterLayer = SymbolLayer(
source: source,
options: [
.iconImage("marker-red"),
.textField(from: NSExpression(forKeyPath: "point_count_abbreviated")),
.textOffset(CGVector(dx: 0, dy: -1.2)),
.textColor(.white),
.textSize(14),
// Only rendered data points which have a point_count property, which clusters do.
.filter(from: NSPredicate(format: "point_count != NIL"))
]
)
// Add the clusterLayer to the map.
map.layers.addLayer(clusterLayer)
// Create a layer to render the individual locations.
map.layers.addLayer(
SymbolLayer(
source: source,
options: [
// Filter out clustered points from this layer.
.filter(from: NSPredicate(format: "point_count = NIL"))
]
)
)
// Add the delegate to handle taps on the clusterLayer only
// and then calculate the convex hull of all the points within a cluster.
map.events.addDelegate(self, for: [clusterLayer.id])
func azureMap(_ map: AzureMap, didTapOn features: [Feature]) {
guard let source = source, let polygonDataSource = polygonDataSource, let cluster = features.first else {
// Data source have been released or no features provided
return
}
// Get all points in the cluster. Set the offset to 0 and the max int value to return all points.
let featureLeaves = source.leaves(of: cluster, offset: 0, limit: .max)
// When only two points in a cluster. Render a line.
if featureLeaves.count == 2 {
// Extract the locations from the feature leaves.
let locations = featureLeaves.map(\.coordinate)
// Create a line from the points.
polygonDataSource.set(geometry: Polyline(locations))
return
}
// When more than two points in a cluster. Render a polygon.
if let hullPolygon = Math.convexHull(from: featureLeaves) {
// Overwrite all data in the polygon data source with the newly calculated convex hull polygon.
polygonDataSource.set(geometry: hullPolygon)
}
}
Följande bild visar ovanstående kod som visar området för alla punkter i ett knackat kluster.
Aggregera data i kluster
Kluster representeras ofta med en symbol med antalet punkter som finns i klustret. Men ibland är det önskvärt att anpassa klusterformatet med ytterligare mått. Med klusteregenskaper kan anpassade egenskaper skapas och vara lika med en beräkning baserat på egenskaperna inom varje punkt med ett kluster. Klusteregenskaper kan definieras i clusterProperties
alternativet DataSource
.
Följande kod beräknar ett antal baserat på egenskapen entitetstyp för varje datapunkt i ett kluster. När en användare trycker på ett kluster visas ett popup-fönster med ytterligare information om klustret.
// Create a popup and add it to the map.
let popup = Popup()
map.popups.add(popup)
// Set popup to the class property to use in events handling later.
self.popup = popup
// Close the popup initially.
popup.close()
// Create a data source and enable clustering.
let source = DataSource(options: [
// Tell the data source to cluster point data.
.cluster(true),
// The radius in points to cluster data points together.
.clusterRadius(50),
// Calculate counts for each entity type in a cluster as custom aggregate properties.
.clusterProperties(self.entityTypes.map { entityType in
ClusterProperty(
name: entityType,
operator: NSExpression(
forFunction: "sum:",
arguments: [
NSExpression.featureAccumulatedAZMVariable,
NSExpression(forKeyPath: entityType)
]
),
map: NSExpression(
forConditional: NSPredicate(format: "EntityType = '\(entityType)'"),
trueExpression: NSExpression(forConstantValue: 1),
falseExpression: NSExpression(forConstantValue: 0)
)
)
})
])
// Import the geojson data and add it to the data source.
let url = URL(string: "https://samples.azuremaps.com/data/geojson/SamplePoiDataSet.json")!
source.importData(fromURL: url)
// Add data source to the map.
map.sources.add(source)
// Create a bubble layer for rendering clustered data points.
let clusterBubbleLayer = BubbleLayer(
source: source,
options: [
.bubbleRadius(20),
.bubbleColor(.purple),
.bubbleStrokeWidth(0),
// Only rendered data points which have a point_count property, which clusters do.
.filter(from: NSPredicate(format: "point_count != NIL"))
]
)
// Add the clusterBubbleLayer to the map.
map.layers.addLayer(clusterBubbleLayer)
// Create a symbol layer to render the count of locations in a cluster.
map.layers.addLayer(
SymbolLayer(
source: source,
options: [
// Hide the icon image.
.iconImage(nil),
// Display the 'point_count_abbreviated' property value.
.textField(from: NSExpression(forKeyPath: "point_count_abbreviated")),
.textColor(.white),
.textOffset(CGVector(dx: 0, dy: 0.4)),
// Allow text overlapping so text is visible anyway
.textAllowOverlap(true),
// Only rendered data points which have a point_count property, which clusters do.
.filter(from: NSPredicate(format: "point_count != NIL"))
]
)
)
// Create a layer to render the individual locations.
map.layers.addLayer(
SymbolLayer(
source: source,
options: [
// Filter out clustered points from this layer.
SymbolLayerOptions.filter(from: NSPredicate(format: "point_count = NIL"))
]
)
)
// Add the delegate to handle taps on the clusterBubbleLayer only
// and display the aggregate details of the cluster.
map.events.addDelegate(self, for: [clusterBubbleLayer.id])
func azureMap(_ map: AzureMap, didTapOn features: [Feature]) {
guard let popup = popup, let cluster = features.first else {
// Popup has been released or no features provided
return
}
// Create a number formatter that removes decimal places.
let nf = NumberFormatter()
nf.maximumFractionDigits = 0
// Create the popup's content.
var text = ""
let pointCount = cluster.properties["point_count"] as! Int
let pointCountString = nf.string(from: pointCount as NSNumber)!
text.append("Cluster size: \(pointCountString) entities\n")
entityTypes.forEach { entityType in
text.append("\n")
text.append("\(entityType): ")
// Get the aggregated entity type count from the properties of the cluster by name.
let aggregatedCount = cluster.properties[entityType] as! Int
let aggregatedCountString = nf.string(from: aggregatedCount as NSNumber)!
text.append(aggregatedCountString)
}
// Create the custom view for the popup.
let customView = PopupTextView()
// Set the text to the custom view.
customView.setText(text)
// Get the position of the cluster.
let position = Math.positions(from: cluster).first!
// Set the options on the popup.
popup.setOptions([
// Set the popups position.
.position(position),
// Set the anchor point of the popup content.
.anchor(.bottom),
// Set the content of the popup.
.content(customView)
])
// Open the popup.
popup.open()
}
Popup-fönstret följer de steg som beskrivs i visa ett popup-dokument .
Följande bild visar koden ovan som visar ett popup-fönster med aggregerade antal av varje entitetsvärdetyp för alla punkter i den knackade klustrade punkten.
Ytterligare information
Så här lägger du till mer data på kartan: