Freigeben über


Erstellen einer Datenquelle im iOS SDK (Vorschau)

Hinweis

Einstellung des Azure Maps iOS SDK

Das Azure Maps Native SDK für iOS ist jetzt veraltet und wird am 31.3.25 eingestellt. Um Dienstunterbrechungen zu vermeiden, migrieren Sie bis zum 31.3.25 zum Azure Maps Web SDK. Weitere Informationen finden Sie unter Anleitung zur Migration des Azure Maps iOS SDK.

Das Azure Maps iOS SDK speichert Daten in Datenquellen. Die Verwendung von Datenquellen optimiert die Datenvorgänge zum Abfragen und Rendern. Derzeit gibt es zwei Arten von Datenquellen:

  • GeoJSON-Quelle: Dient für die lokale Verwaltung von Standortrohdaten im GeoJSON-Format. Sie ist geeignet für kleine bis mittlere Datasets (mit mehr als Hunderttausenden Formen).
  • Vektorkachelquelle: Lädt Daten, die als Vektorkacheln für die aktuelle Kartenansicht formatiert sind, basierend auf dem Kartenkachelsystem. Sie eignet sich ideal für große bis sehr große Datasets (Millionen oder Milliarden Formen).

GeoJSON-Datenquelle

Azure Maps verwendet GeoJSON als eines der primären Datenmodelle. GeoJSON ist eine offene standardmäßige Geomethode zum Darstellen räumlicher Daten im JSON-Format. Im Azure Maps iOS SDK verfügbare GeoJSON-Klassen zum einfachen Erstellen und Serialisieren von GeoJSON-Daten. Laden und speichern Sie GeoJSON-Daten in der DataSource-Klasse, und rendern Sie diese mithilfe von Ebenen. Der folgende Code veranschaulicht, wie GeoJSON-Objekte in Azure Maps erstellt werden können.

/*
    Raw GeoJSON feature

    {
        type": "Feature",
        "geometry": {
            "type": "Point",
            "coordinates": [-100, 45]
        },
        "properties": {
            "custom-property": "value"
        }
    }

*/

//Create a point feature.
let feature = Feature(Point(CLLocationCoordinate2D(latitude: 45, longitude: -100)))

//Add a property to the feature.
feature.addProperty("custom-property", value: "value")

//Add the feature to the data source.
source.add(feature: feature)

Alternativ können die Eigenschaften zuerst in ein Wörterbuch (JSON) geladen und dann beim Erstellen an das Feature übergeben werden, wie der folgende Code zeigt:

//Create a dictionary to store properties for the feature.
var properties: [String: Any] = [:]
properties["custom-property"] = "value"

let feature = Feature(Point(CLLocationCoordinate2D(latitude: 45, longitude: -100)), properties: properties)

Nachdem Sie ein GeoJSON-Feature erstellt haben, kann der Karte mithilfe der sources-Eigenschaft der Karte eine Datenquelle hinzugefügt werden. Der folgende Code zeigt, wie Sie eine Datenquelle (DataSource) erstellen, der Karte hinzufügen und dann der Datenquelle ein Feature hinzufügen.

//Create a data source and add it to the map.
let source = DataSource()
map.sources.add(source)

//Add GeoJSON feature to the data source.
source.add(feature: feature)

Der folgende Code zeigt mehrere Möglichkeiten zum Erstellen von GeoJSON Feature, FeatureCollection, and Geometrien.

// GeoJSON Point Geometry
let point = Point(location)

// GeoJSON LineString Geometry
let polyline = Polyline(locations)

// GeoJSON Polygon Geometry
let polygon = Polygon(locations)

let polygonWithInteriorPolygons = Polygon(locations, interiorPolygons: polygons)

// GeoJSON MultiPoint Geometry
let pointCollection = PointCollection(locations)

// GeoJSON MultiLineString Geometry
let multiPolyline = MultiPolyline(polylines)

let multiPolylineFromLocations = MultiPolyline(locations: arrayOfLocationArrays) // [[CLLocationCoordinate2D]]

// GeoJSON MultiPolygon Geometry
let multiPolygon = MultiPolygon(polygons)

let multiPolygonFromLocations = MultiPolygon(locations: arrayOfLocationArrays) // [[CLLocationCoordinate2D]]

// GeoJSON GeometryCollection Geometry

let geometryCollection = GeometryCollection(geometries)

// GeoJSON Feature
let pointFeature = Feature(Point(location))

// GeoJSON FeatureCollection
let featureCollection = FeatureCollection(features)

Serialisieren und Deserialisieren von GeoJSON

Die FeatureCollection-, Feature- und Geometry-Klassen verfügen alle über eine statische fromJson(_:)-Methode und toJson()-Methode, die die Serialisierung unterstützen. Die formatierte gültige JSON-Zeichenfolge, die über die fromJson()-Methode übergeben wird, erstellt das geometry-Objekt. Diese fromJson() Methode bedeutet auch, dass Sie oder andere JSONSerialization Serialisierungs-/Deserialisierungsstrategien verwenden können. Der folgende Code zeigt, wie Sie ein stringifiziertes GeoJSON-Feature nehmen und in die Feature Klasse deserialisieren und dann wieder in einen GeoJSON-String serialisieren.

// Take a stringified GeoJSON object.
let geoJSONString = """
    {
        "type": "Feature",
        "geometry": {
            "type": "Point",
            "coordinates": [-100, 45]
        },
        "properties": {
            "custom-property": "value"
        }
    }
"""

// Deserialize the JSON string into a feature.
guard let feature = Feature.fromJson(geoJSONString) else {
    throw GeoJSONSerializationError.couldNotSerialize
}

// Serialize a feature collection to a string.
let featureString = feature.toJson()

Importieren von GeoJSON-Daten aus dem Web oder einem Ressourcenordner

Die meisten GeoJSON-Dateien enthalten eine FeatureCollection. Lesen Sie GeoJSON-Dateien als Zeichenfolgen, und verwenden Sie die FeatureCollection.fromJson(_:) Methode, um sie zu deserialisieren.

Die DataSource-Klasse verfügt über eine integrierte Methode namens importData(fromURL:), die GeoJSON-Dateien mithilfe einer URL zu einer Datei im Web oder auf dem Gerät laden kann.

// Create a data source.
let source = DataSource()

// Import the geojson data and add it to the data source.
let url = URL(string: "URL_or_FilePath_to_GeoJSON_data")!
source.importData(fromURL: url)

// Examples:
// source.importData(fromURL: URL(string: "asset://sample_file.json")!)
// source.importData(fromURL: URL(string: "https://example.com/sample_file.json")!)

// Add data source to the map.
map.sources.add(source)

Die importData(fromURL:)-Methode bietet eine Möglichkeit, einen GeoJSON-Feed in eine Datenquelle zu laden, bietet dabei jedoch nur eingeschränkte Kontrolle darüber, wie die Daten geladen werden und was nach dem Laden der Daten geschieht. Der folgende Code stellt eine wiederverwendbare Klasse dar, mit der Daten aus dem Web oder aus dem Ressourcenordner importiert und über eine Rückruffunktion an den Benutzeroberflächenthread zurückgegeben werden können. Im Rückruf können Sie dann zusätzliche Logik für nach dem Laden hinzufügen, um die Daten zu verarbeiten, sie der Karte hinzuzufügen, den Begrenzungsrahmen zu berechnen und die Kartenkamera zu aktualisieren.

import Foundation

@objc
public class Utils: NSObject {
    /// Imports data from a web url or local file url and returns it as a string to a callback on the main thread.
    /// - Parameters:
    ///     - url: A web url or local file url that points to data to load.
    ///     - completion: The callback function to return the data to.
    @objc
    public static func importData(fromURL url: URL, completion: @escaping (String?) -> Void) {
        URLSession.shared.dataTask(with: url) { data, _, _ in
            DispatchQueue.main.async {
                if let data = data {
                    completion(String(decoding: data, as: UTF8.self))
                } else {
                    completion(nil)
                }
            }
        }.resume()
    }
}

Der folgende Code zeigt, wie Sie mit diesem Dienstprogramm GeoJSON-Daten als String importieren und über einen Callback an den Hauptthread zurückgeben. Im Rückruf können die Zeichenfolgendaten in eine GeoJSON-Featuresammlung serialisiert und der Datenquelle hinzugefügt werden. Aktualisieren Sie optional die Kartenkamera, um den Fokus auf die Daten zu richten.

// Create a data source and add it to the map.
let source = DataSource()
map.sources.add(source)

// Create a web url or a local file url
let url = URL(string: "URL_to_GeoJSON_data")!
// Examples:
// let url = Bundle.main.url(forResource: "FeatureCollectionSample", withExtension: "geojson")!
// let url = URL(string: "www.yourdomain.com/path_to_feature_collection_sample")!

// Import the geojson data and add it to the data source.
Utils.importData(fromURL: url) { result in
    guard let result = result else {
        // No data imported.
        return
    }

    // Parse the data as a GeoJSON Feature Collection.
    guard let fc = FeatureCollection.fromJson(result) else {
        // Invalid data for FeatureCollection type.
        return
    }

    // Add the feature collection to the data source.
    source.add(featureCollection: fc)

    // Optionally, update the maps camera to focus in on the data.

    // Calculate the bounding box of all the data in the Feature Collection.
    guard let bbox = BoundingBox.fromData(fc) else {
        // The feature collection is empty.
        return
    }

    // Update the maps camera so it is focused on the data.
    map.setCameraBoundsOptions([
        .bounds(bbox),
        .padding(20)
    ])
}

Aktualisieren eines Features

Die DataSource-Klasse vereinfacht das Hinzufügen und Entfernen von Features. Zum Aktualisieren der Geometrie oder Eigenschaften eines Features muss das Feature in der Datenquelle ersetzt werden. Es gibt zwei Methoden, die zum Aktualisieren von Features verwendet werden können:

  1. Erstellen Sie die neuen Features mit den gewünschten Aktualisierungen, und ersetzen Sie alle Features in der Datenquelle mithilfe der set-Methode. Diese Methode funktioniert gut, wenn Sie alle Features in einer Datenquelle aktualisieren möchten.
var source: DataSource!

private func onReady(map: AzureMap) {
    // Create a data source and add it to the map.
    source = DataSource()
    map.sources.add(source)

    // Create a feature and add it to the data source.
    let myFeature = Feature(Point(CLLocationCoordinate2D(latitude: 0, longitude: 0)))
    myFeature.addProperty("Name", value: "Original value")
    source.add(feature: myFeature)
}

private func updateFeature() {
    // Create a new replacement feature with an updated geometry and property value.
    let myNewFeature = Feature(Point(CLLocationCoordinate2D(latitude: -10, longitude: 10)))
    myNewFeature.addProperty("Name", value: "New value")

    // Replace all features to the data source with the new one.
    source.set(feature: myNewFeature)
}
  1. Verfolgen Sie die Featureinstanz in einer Variablen, und übergeben Sie sie an die Datenquellenmethode remove, um sie zu entfernen. Erstellen Sie die neuen Features mit den gewünschten Aktualisierungen, aktualisieren Sie den Variablenverweis, und fügen Sie ihn der Datenquelle mithilfe der add-Methode hinzu.
var source: DataSource!
var myFeature: Feature!

private func onReady(map: AzureMap) {
    // Create a data source and add it to the map.
    source = DataSource()
    map.sources.add(source)

    // Create a feature and add it to the data source.
    myFeature = Feature(Point(CLLocationCoordinate2D(latitude: 0, longitude: 0)))
    myFeature.addProperty("Name", value: "Original value")
    source.add(feature: myFeature)
}

private func updateFeature() {
    // Remove the feature instance from the data source.
    source.remove(feature: myFeature)

    // Get properties from original feature.
    var props = myFeature.properties

    // Update a property.
    props["Name"] = "New value"

    // Create a new replacement feature with an updated geometry.
    myFeature = Feature(
        Point(CLLocationCoordinate2D(latitude: -10, longitude: 10)),
        properties: props
    )

    // Re-add the feature to the data source.
    source.add(feature: myFeature)
}

Tipp

Wenn Sie einige Daten haben, die regelmäßig aktualisiert werden, und andere Daten, die selten geändert werden, sollten Sie diese in separate Datenquelleninstanzen aufteilen. Wenn eine Aktualisierung in einer Datenquelle auftritt, zwingt dies die Karte, alle Features in der Datenquelle neu zu zeichnen. Durch Aufteilen dieser Daten würden nur die Features, die regelmäßig aktualisiert werden, neu gezeichnet, wenn eine Aktualisierung in dieser einen Datenquelle auftritt, während die Features in der anderen Datenquelle nicht neu gezeichnet werden müssten. Dies verbessert die Leistung.

Vektorkachelquelle

Eine Vektorkachelquelle beschreibt, wie auf eine Vektorkachelebene zugegriffen wird. Verwenden Sie die VectorTileSource-Klasse, um eine Vektorkachelquelle zu instanziieren. Vektorkachelebenen ähneln Kachelebenen, sind aber nicht identisch. Eine Kachelebene ist ein Rasterbild. Vektorkachelebenen sind komprimierte Dateien und liegen im Format PBF vor. Diese komprimierte Datei enthält Vektorkartendaten und eine oder mehrere Ebenen. Die Datei kann auf dem Client gerendert und formatiert werden, basierend auf dem Stil jeder einzelnen Ebene. Die Daten in einer Vektorkachel enthalten geografische Merkmale in Form von Punkten, Linien und Polygonen. Vektorkachelebenen haben gegenüber Rasterkachelebenen mehrere Vorteile:

  • Eine Dateigröße einer Vektorkachel ist in der Regel wesentlich kleiner als eine vergleichbare Rasterkachel. Somit wird auch weniger Bandbreite verwendet. Dies bedeutet geringere Wartezeiten, eine schnellere Karte und eine bessere Benutzererfahrung.
  • Da Vektorkacheln auf dem Client gerendert werden, passen sie sich an die Auflösung des Geräts an, auf dem sie angezeigt werden. Hieraus resultiert, dass die gerenderten Karten definierter und mit sehr klaren Bezeichnungen dargestellt werden.
  • Um den Stil der Daten in den Vektorkarten zu ändern, müssen Sie die Daten nicht erneut herunterladen, da der neue Stil auf den Client angewendet werden kann. Im Gegensatz dazu erfordert das Ändern des Stils einer Rasterkachelebene in der Regel das Laden von Kacheln vom Server und das anschließende Anwenden des neuen Stils.
  • Da die Daten in Vektorform bereitgestellt werden, ist zur Aufbereitung der Daten weniger serverseitige Verarbeitung erforderlich. Als Folge hieraus können die neueren Daten schneller zur Verfügung gestellt werden.

Azure Maps hält den offenen Standard Mapbox Vector Tile Specification ein. Azure Maps bietet als Teil der Plattform die folgenden Dienste für Vektorkacheln an:

Tipp

Wenn Sie Vektor- oder Rasterbildkacheln aus dem Azure Maps-Renderingdienst mit dem iOS SDK verwenden, dann können Sie atlas.microsoft.com durch die Eigenschaft domainPlaceholder von AzureMap ersetzen. Dieser Platzhalter wird durch dieselbe Domäne ersetzt, die von der Karte verwendet wird, und automatisch an dieselben Authentifizierungsdetails angefügt. Dies vereinfacht die Authentifizierung beim Renderingdienst bei Verwendung der Microsoft Entra-Authentifizierung erheblich.

Um Daten aus einer Vektorkachelquelle auf der Karte anzuzeigen, verbinden Sie die Quelle mit einer der Datenrenderingebenen. Für alle Ebenen, die eine Vektorquelle verwenden, muss in den Optionen einen sourceLayer-Wert angegeben sein. Der folgende Code lädt den Azure Maps-Vektorkacheldienst für den Verkehrsfluss als Vektorkachelquelle und zeigt den Vektor dann auf einer Linienebene auf einer Karte an. Diese Vektorkachelquelle verfügt über einen einzigen Datensatz in der Quellebene namens „Verkehrsfluss“. Die Liniendaten in diesem Dataset besitzen eine Eigenschaft namens traffic_level, die in diesem Code zum Auswählen der Farbe und Skalieren der Linienstärke verwendet wird.

// Formatted URL to the traffic flow vector tiles.
let trafficFlowUrl = "\(map.domainPlaceholder)/traffic/flow/tile/pbf?api-version=1.0&style=relative&zoom={z}&x={x}&y={y}"

// Create a vector tile source and add it to the map.
let source = VectorTileSource(options: [
    .tiles([trafficFlowUrl]),
    .maxSourceZoom(22)
])
map.sources.add(source)

// Create a layer for traffic flow lines.
let layer = LineLayer(
    source: source,
    options: [

        // The name of the data layer within the data source to pass into this rendering layer.
        .sourceLayer("Traffic flow"),

        // Color the roads based on the traffic_level property.
        .strokeColor(
            from: NSExpression(
                forAZMInterpolating: NSExpression(forKeyPath: "traffic_level"),
                curveType: .linear,
                parameters: nil,
                stops: NSExpression(forConstantValue: [
                    0: UIColor.red,
                    0.33: UIColor.yellow,
                    0.66: UIColor.green
                ])
            )
        ),

        // Scale the width of roads based on the traffic_level property.
        .strokeWidth(
            from: NSExpression(
                forAZMInterpolating: NSExpression(forKeyPath: "traffic_level"),
                curveType: .linear,
                parameters: nil,
                stops: NSExpression(forConstantValue: [
                    0: 6,
                    1: 1
                ])
            )
        )
    ]
)

// Add the traffic flow layer below the labels to make the map clearer.
map.layers.insertLayer(layer, below: "labels")

Screenshot einer Karte mit farbcodierten Straßenlinien, die den Verkehrsfluss anzeigen.

Verbinden einer Datenquelle mit einer Ebene

Daten werden mithilfe von Renderebenen auf der Karte gerendert. Eine oder mehrere Renderingebenen können auf eine einzelne Datenquelle verweisen. Die folgenden Renderebenen erfordern eine Datenquelle:

  • Blasenebene – Punktdaten werden als skalierte Kreise mit Pixelradius gerendert.
  • Symbolebene – Punktdaten werden als Symbole oder Text gerendert.
  • Wärmebildebene – Punktdaten werden als Dichtewärmebild gerendert.
  • Linienebene – Rendert eine Linie und/oder die Kontur von Polygonen.
  • Polygonebene – füllt den Bereich eines Polygons mit einer Volltonfarbe oder einem Bildmuster aus.

Der folgende Code zeigt, wie Sie eine Datenquelle erstellen, sie der Karte hinzufügen, GeoJSON-Punktdaten von einem Remotestandort in die Datenquelle importieren und dann mit einer Blasenebene verbinden.

// Create a data source.
let source = DataSource()

// Create a web url or a local file url
let url = URL(string: "URL_or_FilePath_to_GeoJSON_data")!
// Examples:
// let url = Bundle.main.url(forResource: "FeatureCollectionSample", withExtension: "geojson")!
// let url = URL(string: "yourdomain.com/path_to_feature_collection_sample")!

// Import the geojson data and add it to the data source.
source.importData(fromURL: url)

// Add data source to the map.
map.sources.add(source)

// Create a layer that defines how to render points in the data source and add it to the map.
let layer = BubbleLayer(source: source)
map.layers.addLayer(layer)

Es gibt zusätzliche Renderingebenen, die keine Verbindung mit diesen Datenquellen herstellen, sondern die Daten zum Rendern direkt laden.

  • Kachelebene – überlagert eine Rasterkachelebene oberhalb der Karte.

Eine Datenquelle mit mehreren Ebenen

Es können mehrere Ebenen mit einer Datenquelle verbunden sein. Es gibt viele verschiedene Szenarien, in denen diese Option nützlich ist. Nehmen wir als Beispiel ein Szenario, in dem ein Benutzer ein Polygon zeichnet. Wir sollten den Polygonbereich rendern und ausfüllen, während der Benutzer der Karte Punkte hinzufügt. Durch Hinzufügen einer formatierten Linie als Umriss des Polygons lassen sich die Kanten des Polygons besser erkennen, während der Benutzer ihn zeichnet. Um eine einzelne Position im Polygon bequem zu bearbeiten, können wir oberhalb von jeder Position einen Handle, z. B. eine Stecknadel oder einen Marker, hinzufügen.

Screenshot einer Karte mit mehreren Layern, die Daten aus einer einzigen Datenquelle rendern.

Auf den meisten Kartenplattformen müssten Sie ein Polygonobjekt, ein Linienobjekt und für jede Position auf dem Polygon eine Stecknadel erstellen. Wenn das Polygon verändert wird, müssen Sie die Linien und Pins manuell aktualisieren. Dies kann sehr schnell zu einem komplexen Vorgang werden.

Bei Azure Maps benötigen Sie nur ein einzelnes Polygon in einer Datenquelle, wie im folgenden Code gezeigt.

// Create a data source and add it to the map.
let source = DataSource()
map.sources.add(source)

// Create a polygon and add it to the data source.
source.add(geometry: Polygon([
    CLLocationCoordinate2D(latitude: 33.15, longitude: -104.5),
    CLLocationCoordinate2D(latitude: 38.5, longitude: -113.5),
    CLLocationCoordinate2D(latitude: 43, longitude: -111.5),
    CLLocationCoordinate2D(latitude: 43.5, longitude: -107),
    CLLocationCoordinate2D(latitude: 43.6, longitude: -94)
]))

// Create a polygon layer to render the filled in area of the polygon.
let polygonLayer = PolygonLayer(
    source: source,
    options: [.fillColor(UIColor(red: 1, green: 165/255, blue: 0, alpha: 0.2))]
)

// Create a line layer for greater control of rendering the outline of the polygon.
let lineLayer = LineLayer(source: source, options: [
    .strokeColor(.orange),
    .strokeWidth(2)
])

// Create a bubble layer to render the vertices of the polygon as scaled circles.
let bubbleLayer = BubbleLayer(
    source: source,
    options: [
        .bubbleColor(.orange),
        .bubbleRadius(5),
        .bubbleStrokeColor(.white),
        .bubbleStrokeWidth(2)
    ]
)

// Add all layers to the map.
map.layers.addLayers([polygonLayer, lineLayer, bubbleLayer])

Tipp

Sie können auch die map.layers.insertLayer(_:below:) Methode verwenden, bei der die ID oder Instanz einer vorhandenen Ebene als zweiter Parameter übergeben werden kann. Dadurch wird die Karte angewiesen, die neue Ebene unterhalb der vorhandenen Ebene hinzuzufügen. Mit dieser Methode wird nicht nur eine Ebenen-ID übergeben. Sie unterstützt zusätzlich die folgenden Werte.

  • "labels": Fügt die neue Ebene unterhalb der Kartenbezeichnungsebenen ein.
  • "transit": Fügt die neue Ebene unterhalb der Straßen- und Transitebene der Karte ein.

Zusätzliche Informationen

In den folgenden Artikeln finden Sie weitere Codebeispiele, die Sie Ihren Karten hinzufügen können: