Share via


Criar uma fonte de dados no SDK do iOS (Visualização)

Nota

Aposentadoria do SDK do iOS do Azure Maps

O SDK nativo do Azure Maps para iOS foi preterido e será desativado em 31/03/25. Para evitar interrupções de serviço, migre para o SDK da Web do Azure Maps até 31/03/25. Para obter mais informações, consulte O guia de migração do SDK do iOS do Azure Maps.

O SDK do iOS do Azure Maps armazena dados em fontes de dados. O uso de fontes de dados otimiza as operações de dados para consulta e renderização. Atualmente, existem dois tipos de fontes de dados:

  • Origem GeoJSON: gerencia dados brutos de localização no formato GeoJSON localmente. Bom para conjuntos de dados pequenos a médios (mais de centenas de milhares de formas).
  • Origem do mosaico vetorial: carrega dados formatados como mosaicos vetoriais para a vista de mapa atual, com base no sistema de mosaico de mapas. Ideal para conjuntos de dados grandes a massivos (milhões ou bilhões de formas).

Fonte de dados GeoJSON

O Azure Maps usa GeoJSON como um de seus principais modelos de dados. GeoJSON é uma maneira padrão geoespacial aberta para representar dados geoespaciais no formato JSON. Classes GeoJSON disponíveis no SDK do iOS do Azure Maps para criar e serializar dados GeoJSON facilmente. Carregue e armazene dados GeoJSON na DataSource classe e renderize-os usando camadas. O código a seguir mostra como os objetos GeoJSON podem ser criados no 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, as propriedades podem ser carregadas em um dicionário (JSON) primeiro e, em seguida, passadas para o recurso ao criá-lo, como demonstra o código a seguir:

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

Depois de criar um recurso GeoJSON, uma fonte de dados pode ser adicionada ao mapa por meio sources da propriedade do mapa. O código a seguir mostra como criar um DataSource, adicioná-lo ao mapa e adicionar um recurso à fonte de dados.

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

O código a seguir mostra várias maneiras de criar um GeoJSON Feature, FeatureCollectione geometrias.

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

Serializar e desserializar GeoJSON

A coleção de recursos, o recurso e as classes de geometria têm fromJson(_:)toJson() métodos estáticos, que ajudam na serialização. A String JSON válida formatada fromJson() passada pelo método cria o objeto de geometria. Esse fromJson() método também significa que você pode usar JSONSerialization ou outras estratégias de serialização/desserialização. O código a seguir mostra como pegar um recurso GeoJSON stringified e desserializá-lo na Feature classe e, em seguida, serializá-lo novamente em uma cadeia de caracteres 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()

Importar dados GeoJSON da Web ou da pasta de ativos

A maioria dos arquivos GeoJSON contém um FeatureCollectionarquivo . Leia arquivos GeoJSON como strings e use o FeatureCollection.fromJson(_:) método para desserializá-lo.

A DataSource classe tem um método interno chamado importData(fromURL:) que pode carregar em arquivos GeoJSON usando uma URL para um arquivo na Web ou no 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)

O importData(fromURL:) método fornece uma maneira de carregar um feed GeoJSON em uma fonte de dados, mas fornece controle limitado sobre como os dados são carregados e o que acontece depois que eles são carregados. O código a seguir é uma classe reutilizável para importar dados da pasta Web ou ativos e retorná-los ao thread da interface do usuário por meio de uma função de retorno de chamada. No retorno de chamada, você pode adicionar mais lógica de pós-carregamento para processar os dados, adicioná-los ao mapa, calcular sua caixa delimitadora e atualizar a câmera 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()
    }
}

O código a seguir mostra como usar esse utilitário para importar dados GeoJSON como uma cadeia de caracteres e retorná-los ao thread principal por meio de um retorno de chamada. No retorno de chamada, os dados da cadeia de caracteres podem ser serializados em uma coleção GeoJSON Feature e adicionados à fonte de dados. Opcionalmente, atualize a câmera de mapas para se concentrar nos dados.

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

Atualizar um recurso

A DataSource classe facilita a adição e remoção de recursos. A atualização da geometria ou das propriedades de um recurso requer a substituição do recurso na fonte de dados. Há dois métodos que podem ser usados para atualizar um recurso:

  1. Crie o(s) novo(s) recurso(s) com as atualizações desejadas e substitua todos os recursos na fonte de dados usando o set método. Esse método funciona bem quando você deseja atualizar todos os recursos em uma fonte de dados.
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. Acompanhe a instância do recurso em uma variável e passe-a para o método de fontes remove de dados para removê-la. Crie o(s) novo(s) recurso(s) com as atualizações desejadas, atualize a referência da variável e adicione-a à fonte de dados usando o add método.
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)
}

Gorjeta

Se você tiver alguns dados que serão atualizados regularmente e outros dados que raramente serão alterados, é melhor dividi-los em instâncias de fonte de dados separadas. Quando uma atualização ocorre em uma fonte de dados, ela força o mapa a repintar todos os recursos na fonte de dados. Ao dividir esses dados, apenas os recursos que são atualizados regularmente seriam repintados quando uma atualização ocorre nessa fonte de dados, enquanto os recursos na outra fonte de dados não precisariam ser repintados. Isso ajuda no desempenho.

Origem do mosaico vetorial

Uma fonte de mosaico vetorial descreve como aceder a uma camada de mosaico vetorial. Use a VectorTileSource classe para instanciar uma fonte de bloco vetorial. As camadas de mosaico vetorial são semelhantes às camadas de mosaico, mas não são iguais. Uma camada de mosaico é uma imagem rasterizada. As camadas de mosaico vetorial são um ficheiro comprimido, em formato PBF . Esse arquivo compactado contém dados de mapa vetorial e uma ou mais camadas. O arquivo pode ser renderizado e estilizado no cliente, com base no estilo de cada camada. Os dados em um bloco vetorial contêm características geográficas na forma de pontos, linhas e polígonos. Há várias vantagens de usar camadas de mosaico vetorial em vez de camadas de mosaico raster:

  • Um tamanho de arquivo de um bloco de vetor é normalmente muito menor do que um bloco raster equivalente. Como tal, menos largura de banda é usada. Isso significa menor latência, um mapa mais rápido e uma melhor experiência do usuário.
  • Como os blocos vetoriais são renderizados no cliente, eles se adaptam à resolução do dispositivo em que estão sendo exibidos. Como resultado, os mapas renderizados aparecem mais bem definidos, com rótulos cristalinos.
  • Alterar o estilo dos dados nos mapas vetoriais não requer baixar os dados novamente, uma vez que o novo estilo pode ser aplicado no cliente. Por outro lado, alterar o estilo de uma camada de mosaico raster normalmente requer carregar blocos do servidor e, em seguida, aplicar o novo estilo.
  • Como os dados são entregues em forma vetorial, há menos processamento do lado do servidor necessário para preparar os dados. Como resultado, os dados mais recentes podem ser disponibilizados mais rapidamente.

O Azure Maps adere à Mapbox Vetor Tile Specification, um padrão aberto. O Azure Maps fornece os seguintes serviços de blocos vetoriais como parte da plataforma:

Gorjeta

Ao usar blocos de imagem vetorial ou raster do serviço de renderização do Azure Maps com o SDK do iOS, você pode substituir atlas.microsoft.com pela AzureMappropriedade 's' domainPlaceholder. Esse espaço reservado será substituído pelo mesmo domínio usado pelo mapa e também acrescentará automaticamente os mesmos detalhes de autenticação. Isso simplifica muito a autenticação com o serviço de renderização ao usar a autenticação do Microsoft Entra.

Para exibir dados de uma fonte de bloco vetorial no mapa, conecte a fonte a uma das camadas de renderização de dados. Todas as camadas que usam uma fonte de vetor devem especificar um sourceLayer valor nas opções. O código a seguir carrega o serviço de bloco de vetor de fluxo de tráfego do Azure Maps como uma fonte de bloco vetorial e, em seguida, o exibe em um mapa usando uma camada de linha. Esta fonte de mosaico vetorial tem um único conjunto de dados na camada de origem chamado "Fluxo de tráfego". Os dados de linha neste conjunto de dados tem uma propriedade chamada traffic_level que é usada neste código para selecionar a cor e dimensionar o tamanho das linhas.

// 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 ecrã de um mapa com linhas rodoviárias codificadas por cores que mostram os níveis de fluxo de tráfego.

Conectar uma fonte de dados a uma camada

Os dados são renderizados no mapa usando camadas de renderização. Uma ou mais camadas de renderização podem fazer referência a uma única fonte de dados. As seguintes camadas de renderização exigem uma fonte de dados:

  • Camada de bolhas - renderiza dados de pontos como círculos dimensionados no mapa.
  • Camada de símbolos- renderiza dados de pontos como ícones ou texto.
  • Camada de mapa de calor - renderiza dados de pontos como um mapa de calor de densidade.
  • Camada de linha - renderiza uma linha e/ou renderiza o contorno de polígonos.
  • Camada de polígono - preenche a área de um polígono com uma cor sólida ou padrão de imagem.

O código a seguir mostra como criar uma fonte de dados, adicioná-la ao mapa, importar dados de ponto GeoJSON de um local remoto para a fonte de dados e, em seguida, conectá-los a uma camada de bolhas.

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

Há outras camadas de renderização que não se conectam a essas fontes de dados, mas carregam diretamente os dados para renderização.

  • Camada de mosaico - sobrepõe uma camada de mosaico raster no topo do mapa.

Uma fonte de dados com várias camadas

Várias camadas podem ser conectadas a uma única fonte de dados. Existem muitos cenários diferentes em que esta opção é útil. Por exemplo, considere o cenário no qual um usuário desenha um polígono. Devemos renderizar e preencher a área do polígono à medida que o usuário adiciona pontos ao mapa. Adicionar uma linha estilizada para delinear o polígono torna mais fácil ver as bordas do polígono, à medida que o usuário desenha. Para editar convenientemente uma posição individual no polígono, podemos adicionar uma alça, como um pino ou um marcador, acima de cada posição.

Captura de tela de um mapa mostrando várias camadas renderizando dados de uma única fonte de dados.

Na maioria das plataformas de mapeamento, você precisaria de um objeto de polígono, um objeto de linha e um pino para cada posição no polígono. À medida que o polígono é modificado, você precisa atualizar manualmente a linha e os pinos, que podem se tornar complexos rapidamente.

Com o Azure Maps, tudo o que você precisa é de um único polígono em uma fonte de dados, conforme mostrado no código a seguir.

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

Gorjeta

Você também pode usar map.layers.insertLayer(_:below:) o método, onde a ID ou instância de uma camada existente pode ser passada como um segundo parâmetro. Isso daria a esse mapa para inserir a nova camada que está sendo adicionada abaixo da camada existente. Além de passar um ID de camada, esse método também suporta os seguintes valores.

  • "labels" - Insere a nova camada abaixo das camadas do rótulo do mapa.
  • "transit" - Insere a nova camada abaixo das camadas de estrada e trânsito do mapa.

Informações adicionais

Consulte os seguintes artigos para obter mais exemplos de código para adicionar aos seus mapas: