Compartir a través de


Creación de un origen de datos en el SDK de iOS (versión preliminar)

Nota:

Retirada del SDK de Azure Maps para iOS

El SDK nativo de Azure Maps para iOS ya está en desuso y se retirará el 31 de marzo de 2025. Para evitar interrupciones del servicio, migre al SDK de Azure Maps para web antes del 31 de marzo de 2025. Para obtener más información, consulte la Guía de migración del SDK de Maps para iOS.

El SDK de Azure Maps para iOS almacena los datos en orígenes de datos. El uso de orígenes de datos optimiza las operaciones de datos en las consultas y representaciones. Actualmente hay dos tipos de orígenes de datos:

  • Origen GeoJSON: administra localmente los datos de ubicación sin procesar en formato GeoJSON. Adecuado para conjuntos de datos de tamaño pequeño a medio (con cientos de miles de formas).
  • Origen de mosaico vectorial: carga los datos con formato de mosaico vectorial para la vista del mapa actual, en función del sistema de mosaico de mapas. Ideal para conjuntos de datos grandes o masivos (millones o miles de millones de formas).

Origen de datos de GeoJSON

Azure Maps usa GeoJSON como uno de sus modelos de datos principales. GeoJSON es un estándar geoespacial abierto para representar datos geoespaciales en formato JSON. Las clases de GeoJSON disponibles en el SDK de Azure Maps para iOS permiten crear y serializar datos GeoJSON de manera sencilla. Cargue y almacene datos de GeoJSON en la clase DataSource y represéntelos mediante capas. En el siguiente código se muestra cómo crear objetos GeoJSON en Azure Maps.

/*
    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)

Como alternativa, las propiedades se pueden cargar en un diccionario (JSON) en primer lugar y luego pasarlos a la característica al crearla, tal y como demuestra el siguiente código:

//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)

Una vez que se ha creado una característica de GeoJSON, se puede agregar un origen de datos al mapa mediante la propiedad sources del mapa. En el código siguiente se muestra cómo crear una clase DataSource, agregarla al mapa y agregar una característica al origen de datos.

//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)

En el código siguiente se muestran varias maneras de crear GeoJSON Feature, FeatureCollection y geometrías.

// 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)

Serialización y deserialización de GeoJSON

Todas las clases de colección de características, de característica y de geometría tienen métodos estáticos fromJson(_:) y toJson(), lo que ayuda con la serialización. La cadena JSON válida con formato que se pasa mediante el método fromJson() crea el objeto de geometría. Este método fromJson() también implica que puede usar JSONSerialization u otras estrategias de serialización y deserialización. En el código siguiente se muestra cómo tomar una característica de GeoJSON con formato de cadena y deserializarla en la clase Feature y luego serializarla de nuevo en una cadena de GeoJSON.

// 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()

Importación de datos de GeoJSON de la web o de la carpeta de recursos

La mayoría de los archivos GeoJSON contienen un FeatureCollection. Lea archivos GeoJSON como cadenas y use el método FeatureCollection.fromJson(_:) para deserializarlos.

La clase DataSource tiene un método integrado denominado importData(fromURL:) que se puede cargar en archivos GeoJSON mediante una dirección URL a un archivo en la web o en el dispositivo.

// 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)

El método importData(fromURL:) proporciona una manera de cargar una fuente GeoJSON en un origen de datos, pero proporciona un control limitado sobre cómo se cargan los datos y qué ocurre después de cargarlos. El código siguiente es una clase reutilizable para importar datos de la carpeta de recursos o la web, y devolverlos al subproceso de interfaz de usuario mediante una función de devolución de llamada. En la devolución de llamada, puede agregar lógica adicional posterior a la carga para procesar los datos, agregarlos al mapa, calcular su rectángulo delimitador y actualizar la cámara de mapas.

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()
    }
}

En el código siguiente se muestra cómo usar esta utilidad para importar datos de GeoJSON como cadena y devolverlos al subproceso principal mediante una devolución de llamada. En la devolución de llamada, los datos de cadena se pueden serializar en una colección de características GeoJSON y agregarse al origen de datos. Opcionalmente, actualice la cámara de Maps para centrarse en los datos.

// 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)
    ])
}

Actualización de una característica

La clase DataSource facilita la adición y eliminación de características. Para actualizar la geometría o las propiedades de una característica es necesario reemplazar la característica en el origen de datos. Se pueden usar dos métodos para actualizar una característica:

  1. Cree las características con las actualizaciones deseadas y reemplace todas las características del origen de datos mediante el método set. Este método funciona bien cuando se quieren actualizar todas las características de un origen de datos.
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. Realice el seguimiento de la instancia de característica en una variable y pásela al método remove del origen de datos para quitarla. Cree las características con las actualizaciones deseadas, actualice la referencia de variable y agréguela al origen de datos mediante el método add.
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)
}

Sugerencia

Si tiene algunos datos que se van a actualizar periódicamente y otros que rara vez se cambiarán, es mejor dividirlos en instancias de origen de datos independientes. Cuando se produce una actualización en un origen de datos, obliga al mapa a volver a dibujar todas las características del origen de datos. Al dividir estos datos, solo las características que se actualizan periódicamente se volverían a dibujar cuando se produzca una actualización en ese origen de datos, mientras que las características del otro origen de datos no tendrían que volver a dibujarse. Esto facilita el rendimiento.

Origen de mosaico vectorial

Un origen de mosaico vectorial describe cómo acceder a una capa de mosaico vectorial. Use la clase VectorTileSource para crear una instancia de un origen de mosaico vectorial. Las capas de mosaico vectorial son similares a las de mosaico, pero no son iguales. Una capa de mosaico es una imagen de trama. Las capas de mosaico vectorial son un archivo comprimido, en formato PBF. Este archivo comprimido contiene datos de mapas vectoriales y una o varias capas. El archivo se puede representar en el cliente, y aplicarle un estilo, en función del estilo de cada capa. Los datos de un mosaico vectorial contienen características geográficas en forma de puntos, líneas y polígonos. El uso de capas de mosaico vectorial en lugar de capas de mosaico de trama presenta varias ventajas:

  • Un tamaño de archivo de un mosaico vectorial suele ser mucho menor que el de un mosaico de trama equivalente. Como tal, se usa menos ancho de banda. Esto significa una menor latencia, un mapa más rápido y una mejor experiencia de usuario.
  • Como los mosaicos vectoriales se representan en el cliente, pueden adaptarse a la resolución del dispositivo en el que se muestran. Como resultado, los mapas representados aparecen mejor definidos, con etiquetas nítidas.
  • Para cambiar el estilo de los datos en los mapas vectoriales no es necesario descargar los datos de nuevo, ya que el nuevo estilo se puede aplicar en el cliente. Al contrario, para cambiar el estilo de una capa de mosaico de trama es necesario normalmente cargar los mosaicos desde el servidor y luego aplicar el nuevo estilo.
  • Dado que los datos se entregan en forma de vector, se requiere menos procesamiento del lado servidor para preparar los datos. Como resultado, los datos más recientes pueden estar disponibles más rápido.

Azure Maps se adhiere a la especificación de mosaicos vectoriales de Mapbox, un estándar abierto. Azure Maps proporciona los siguientes servicios de mosaicos vectoriales como parte de la plataforma:

Sugerencia

Al usar mosaicos de imagen vectoriales o de trama del servicio de representación de Azure Maps con el SDK para iOS, puede reemplazar atlas.microsoft.com por la propiedad domainPlaceholder de AzureMap. Este marcador de posición se reemplazará por el mismo dominio usado por el mapa y también anexará automáticamente los mismos detalles de autenticación. Esto simplifica considerablemente la autenticación con el servicio de representación cuando se usa la autenticación de Microsoft Entra.

Para mostrar los datos de un origen de mosaico vectorial en el mapa, conecte el origen a una de las capas de representación de datos. Todas las capas que usan un origen vectorial deben especificar un valor sourceLayer en las opciones. El código siguiente carga el servicio de mosaico vectorial de flujo de tráfico de Azure Maps como un origen de mosaico vectorial y, a continuación, lo muestra en un mapa mediante una capa de línea. Este origen de mosaico vectorial tiene un único conjunto de datos en la capa de origen denominado "Traffic flow". Los datos de línea de este conjunto de datos tienen una propiedad denominada traffic_level que se usa en este código para seleccionar el color y escalar el tamaño de las líneas.

// 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")

Captura de pantalla de un mapa con líneas de carreteras codificadas por colores que muestran los niveles de flujo de tráfico.

Conexión de un origen de datos a una capa

Los datos se representan en el mapa mediante las capas de representación. Una o varias capas de representación pueden hacer referencia a un único origen de datos. Las siguientes capas de representación requieren un origen de datos:

El código siguiente muestra cómo crear un origen de datos, agregarlo al mapa, importar datos de punto GeoJSON desde una ubicación remota en el origen de datos y, a continuación, conectarlo a una capa de burbujas.

// 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)

Hay otras capas de representación que no se conectan a estos orígenes de datos, sino que cargan los datos directamente para representarlos.

  • Capa de mosaico: superpone una capa de mosaico de trama en la parte superior del mapa.

Un origen de datos con varias capas

Varias capas pueden estar conectadas a un solo origen de datos. Hay muchos escenarios diferentes en los que esta opción es útil. Por ejemplo, considere aquel en el que un usuario dibuja un polígono. Debemos representar y rellenar el área del polígono a medida que el usuario agrega puntos al mapa. Agregar una línea con estilo para delinear el polígono permite ver más fácilmente sus bordes, a medida que el usuario dibuja. Para editar cómodamente una posición individual del polígono, podemos agregar un identificador, como una chincheta o una marca, encima de cada posición.

Captura de pantalla de un mapa que muestra varias capas que representan datos de un único origen de datos.

En la mayoría de las plataformas de mapas, necesitaría un objeto de polígono, un objeto de línea y una chincheta para cada posición del polígono. A medida que le polígono se modifica, tendría que actualizar manualmente las líneas y las chinchetas, que pueden volverse complejas rápidamente.

Con Azure Maps, lo único que necesita es un solo polígono en un origen de datos, como se muestra en el siguiente código.

// 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])

Sugerencia

También puede usar el método map.layers.insertLayer(_:below:), donde el identificador o la instancia de una capa existente se puede pasar como un segundo parámetro. Esto indicaría a ese mapa que debe insertar la nueva capa que se va a agregar debajo de la capa existente. Además de pasar un identificador de capa, este método también admite los valores siguientes.

  • "labels": inserta la nueva capa debajo de las capas de etiqueta del mapa.
  • "transit": inserta la nueva capa debajo de las capas de carretera y tránsito del mapa.

Información adicional

Para obtener más ejemplos de código para agregar a los mapas: