Compartilhar via


Criar uma fonte de dados no SDK do iOS (versão prévia)

Observação

Desativação do SDK do iOS no Azure Mapas

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

O SDK do iOS do Azure Mapas armazena dados em fontes de dados. O uso de fontes de dados otimiza as operações de dados para consulta e renderização. No momento, há dois tipos de fontes de dados:

  • Fonte GeoJSON: gerencia localmente dados de local brutos no formato GeoJSON. Boa para conjuntos de dados pequenos a médios (com mais de centenas de milhares de formas).
  • Fonte de peça de vetor: carrega dados formatados como peças de vetor para a exibição do mapa atual, com base no sistema de peças de mapas. Ideal para conjuntos de dados grandes a enormes (milhões ou bilhões de formas).

Fonte de dados GeoJSON

O Azure Mapas usa GeoJSON como um de seus modelos de dados primários. GeoJSON é uma forma padrão geoespacial aberta para representar dados geoespaciais no formato JSON. As classes GeoJSON disponíveis no SDK do iOS do Azure Mapas para facilitar a criação e serializar dados GeoJSON. Carregar e armazenar dados GeoJSON na classe DataSource e renderizá-los usando camadas. O código a seguir mostra como os dados GeoJSON podem ser criados no Azure Mapas.

/*
    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, depois passadas para o recurso ao criá-lo, conforme o seguinte código demonstra:

//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 que você tiver um recurso GeoJSON criado, uma fonte de dados poderá ser adicionada ao mapa por meio da propriedade sources 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 Feature, um FeatureCollection e geometrias GeoJSON.

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

Todas as classes de coleção de recursos, recurso e geometria têm os métodos estáticos fromJson(_:) e toJson(), que ajudam com a serialização. A Cadeia de caracteres JSON válida formatada, passada pelo método fromJson(), criará o objeto geometria. Esse método fromJson() 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 em cadeias e desserializá-lo na classe Feature 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 pasta de ativos ou da Web

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

A classe DataSource tem um método interno chamado importData(fromURL:) que pode ser carregado em arquivos GeoJSON usando uma URL para um arquivo na Web ou o 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 método importData(fromURL:) oferece uma maneira de carregar um feed GeoJSON em uma fonte de dados, mas oferece controle limitado sobre como os dados são carregados e o que acontece após seu carregamento. O código a seguir é uma classe reutilizável para importar dados da pasta de ativos da Web ou locais 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 lógica de pós-carregamento adicional 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 do usuário por meio de um retorno de chamada. No retorno de chamada, os dados de cadeia de caracteres podem ser serializados em uma Coleção de Recursos GeoJSON 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 classe DataSource facilita a adição e a remoção de recursos. Atualizar a geometria ou as 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 ou mais recursos:

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

Dica

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 redesenhar todos os recursos na fonte de dados. Dividindo esses dados, somente os recursos que são atualizados regularmente seriam redesenhados quando ocorre uma atualização nessa fonte de dados, enquanto os recursos da outra fonte de dados não precisariam ser redesenhados. Isso ajuda a melhorar o desempenho.

Fonte da peça de vetor

Uma fonte de peça de vetor descreve como acessar uma camada de peça de vetor. Use a classe VectorTileSource para criar uma instância de uma fonte de bloco de vetor. As camadas de peça de vetor são semelhantes às camadas de peça, mas não são as mesmas. Uma camada de peça é uma imagem de varredura. As camadas de peça de vetor são um arquivo compactado, no formato PBF. Esse arquivo compactado contém dados de mapa de vetor e uma ou mais camadas. O arquivo pode ser renderizado e estilizado no cliente, com base no estilo de cada camada. Os dados em uma peça de vetor contêm recursos geográficos na forma de pontos, linhas e polígonos. Há várias vantagens de usar camadas de peça de vetor em vez de camadas de peça de varredura:

  • Um tamanho do arquivo de uma peça de vetor normalmente é muito menor do que uma peça de varredura equivalente. Dessa forma, menos largura de banda é usada. Isso significa menor latência, um mapa mais rápido e melhor experiência do usuário.
  • Como as peças de vetor são renderizadas no cliente, elas se adaptam à resolução do dispositivo em que estão sendo exibidos. Como resultado, os mapas renderizados aparecem mais bem definidos, com rótulos nítidos.
  • A alteração no estilo dos dados nos mapas de vetor não exige o download dos dados novamente, já que o novo estilo pode ser aplicado no cliente. Por outro lado, alterar o estilo de uma camada de peça de varredura normalmente requer o carregamento de peças do servidor, aplicando o novo estilo.
  • Como os dados são entregues na forma de vetor, há menos processamento no lado do servidor necessário para preparar os dados. Como resultado, os dados mais recentes podem ser disponibilizados mais rapidamente.

O Azure Mapas segue a especificação de peça de vetor Mapbox, um padrão aberto. O Azure Mapas fornece os seguintes serviços de peças de vetor como parte da plataforma:

Dica

Ao usar peças de imagem vetorial ou de varredura do serviço de renderização do Azure Mapas com o SDK do iOS, você pode substituir atlas.microsoft.com pela propriedade domainPlaceholder de AzureMap. 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 peça de vetor no mapa, conecte a fonte a uma das camadas de renderização de dados. Todas as camadas que usam uma fonte de vetor precisam especificar um valor sourceLayer nas opções. O código a seguir carrega o serviço de peça de vetor do fluxo de tráfego do Azure Mapas como uma fonte de peça de vetor e o exibe em um mapa usando uma camada de linhas. Essa fonte de peça de vetor tem um conjunto de dados na camada de origem chamada "fluxo de tráfego". Os dados de linha nesse conjunto de dados têm 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 tela de um mapa com linhas de estrada codificadas por cores mostrando 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 referenciar uma única fonte de dados. As seguintes camadas de renderização exigem uma fonte de dados:

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á-la a uma camada de bolha.

// 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 peça – sobrepõe uma camada de peça de varredura na parte superior do mapa.

Uma fonte de dados com várias camadas

Várias camadas podem ser conectadas a uma fonte de dados. Há vários cenários diferentes nos quais essa 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, pois o usuário adiciona pontos ao mapa. A adição de uma linha com estilo para descrever o polígono facilita a visualização das bordas do polígono, conforme o usuário desenha. Para editar convenientemente uma posição individual no polígono, podemos adicionar um identificador, como uma marcação ou um marcador, acima de cada posição.

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

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

Com o Azure Mapas, tudo de que você precisa é de um 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])

Dica

Você também pode usar um método map.layers.insertLayer(_:below:), em que a ID ou a instância de uma camada existente pode ser passada como um segundo parâmetro. Isso dirá ao mapa para inserir a nova camada que está sendo adicionada abaixo da camada existente. Além de passar uma ID de camada, esse método também dá suporte aos valores a seguir.

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

Informações adicionais

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