你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

在 iOS SDK(预览版)中创建数据源

注意

Azure Maps iOS SDK 停用

适用于 iOS 的 Azure Maps 本机 SDK 现已弃用,将于 2025 年 3 月 31 日停用。 为了避免服务中断,请在 2025 年 3 月 31 日之前迁移到 Azure Maps Web SDK。 有关详细信息,请参阅 Azure Maps iOS SDK 迁移指南

Azure Maps iOS SDK 将数据存储在数据源中。 使用数据源可优化用于查询和渲染的数据操作。 目前数据源有两种类型:

  • GeoJSON 源:在本地管理 GeoJSON 格式的原始位置数据。 适合中小型数据集(数十万个形状以上)。
  • 矢量图块源:基于地图图块系统,为当前的地图视图加载矢量图块格式的数据。 适合大型乃至超大型数据集(数百万或数十亿个形状)。

GeoJSON 数据源

Azure Maps 使用 GeoJSON 作为其主数据模型之一。 GeoJSON 是一种开放式地理空间标准方法,用于以 JSON 格式表示地理空间数据。 通过 Azure Maps iOS SDK 中可用的 GeoJSON 类,可轻松创建和序列化 GeoJSON 数据。 在 DataSource 类中加载并存储 GeoJSON 数据,并使用层呈现它。 以下代码演示了如何在 Azure Maps 中创建 GeoJSON 对象。

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

另外,还可以先将属性加载到字典 (JSON) 中,然后在创建特征时将其传入特征,如以下代码所演示:

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

创建 GeoJSON 特征后,可以通过地图的 sources 属性将数据源添加到地图中。 下面的代码演示如何创建 DataSource,将其添加到地图,以及如何将特征添加到数据源。

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

下面的代码演示了几种创建 GeoJSON FeatureFeatureCollection 和几何图形的方式。

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

对 GeoJSON 进行序列化和反序列化

特征集合、特征和几何图形类都具有 fromJson(_:)toJson() 静态方法,有助于进行序列化。 通过 fromJson() 方法传递的格式化有效 JSON 字符串将创建几何对象。 此 fromJson() 方法还意味着可以使用 JSONSerialization 或其他序列化/反序列化策略。 下面的代码演示如何获取字符串化 GeoJSON 特征并将其反序列化为 Feature 类,然后将其序列化回 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()

从 Web 或资产文件夹导入 GeoJSON 数据

大多数 GeoJSON 文件都包含 FeatureCollection。 将 GeoJSON 文件作为字符串读取,并使用 FeatureCollection.fromJson(_:) 方法对其进行反序列化。

DataSource 类具有一个名为 importData(fromURL:) 的内置方法,该方法可使用指向 Web 或设备上的文件的 URL 加载到 GeoJSON 文件中。

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

importData(fromURL:) 方法提供了一种方法来将 GeoJSON 源加载到数据源,但对数据加载方式以及加载后所发生情况的控制有限。 下面的代码是一个可重用的类,用于将 Web 或资产文件夹中的数据导入,并通过回调函数将其返回到 UI 线程。 在回调中,你可添加更多加载后逻辑来处理数据,将其添加到地图,计算其边界框和更新地图照相机。

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

下面的代码演示如何使用此实用工具将 GeoJSON 数据导入为字符串,并通过回调将其返回到主线程。 在回调中,可以将字符串数据序列化为 GeoJSON 特征集合,并将其添加到数据源。 还可选择性更新地图照相机,将焦点对准数据。

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

更新特征

DataSource 类可以轻松添加和移除功能。 更新特征的几何图形或属性需要在数据源中替换该特征。 可使用两种方法来更新特征:

  1. 使用所需的更新创建新特征,并使用 set 方法替换数据源中的所有特征。 当你希望更新数据源中的所有特征时,此方法非常有效。
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. 跟踪变量中的功能实例,并将其传递到数据源 remove 方法中以删除它。 使用所需更新创建新特征,更新变量引用并使用 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)
}

提示

如果有一些数据需要定期更新,而其他数据很少更改,最好将这些数据拆分到单独的数据源实例中。 当数据源中发生更新时,该更新会强制地图重画数据源中的所有特征。 拆分此数据后,当一个数据源中发生更新时,只有定期更新的特征才会重画,另一个数据源中的特征不需要重画。 这有助于提高性能。

矢量图块源

矢量图块源介绍如何访问矢量图块层。 使用 VectorTileSource 类实例化矢量图块源。 矢量图块层与图块层类似,但不同。 图块层是光栅图像。 矢量图块层是压缩文件,为 PBF 格式。 此压缩文件包含矢量地图数据以及一个或多个层。 根据每层的样式,可以在客户端渲染文件并设计文件样式。 矢量图块中的数据包含点、线和多边形形式的地理功能。 相比光栅图块层,使用矢量图块层有多种优势:

  • 矢量图块的文件大小通常比等效的光栅图块小得多。 因此,使用的带宽更少。 这意味着延迟较低、地图较快和用户体验更好。
  • 由于矢量图块在客户端渲染,所以它们会适应要显示它们的设备的分辨率。 因此,渲染的地图看起来更清晰,可清楚地看到标签。
  • 更改矢量地图中的数据样式不需要重新下载数据,因为新样式可应用于客户端。 相反,更改光栅图块层的样式通常需要从服务器加载图块,然后再应用新样式。
  • 由于数据以矢量形式传递,所以准备数据所需的服务器端处理更少。 因此,可以更快地提供较新的数据。

Azure Maps 遵循开放式标准 - Mapbox 矢量图块规范。 Azure Maps 在平台中提供以下矢量图块服务:

提示

通过 iOS SDK 使用 Azure Maps 渲染服务中的矢量或光栅图块时,可以使用 AzureMap 的 property' domainPlaceholder 替代 atlas.microsoft.com。 此占位符将替换为地图使用的相同域,还会自动附加相同的身份验证详细信息。 这样可大大简化使用 Microsoft Entra 身份验证时通过呈现服务进行的身份验证。

要在地图上显示矢量图块源中的数据,请将该源连接到数据渲染层之一。 使用矢量源的所有层都必须在选项中指定一个 sourceLayer 值。 以下代码可将 Azure Maps 交通流矢量图块服务加载为矢量图块源,然后使用线条层在地图上进行显示。 该矢量图块源在源层中有一个数据集,称为“交通流”。 此数据集中的线条数据包含一个 traffic_level 属性,在此代码中用于选择颜色和缩放线条大小。

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

带有色彩编码道路线以显示交通流情况的地图的屏幕截图。

将数据源连接到层

使用渲染层在地图上渲染数据。 一个或多个呈现层可以引用单个数据源。 以下渲染层需要数据源:

  • 气泡层 - 将点数据渲染为地图上的缩放圆圈。
  • 符号层 - 将点数据呈现为图标或文本。
  • 热度地图层 - 将点数据渲染为密度热度地图。
  • 线条层 - 渲染线条和/或渲染多边形边框。
  • 多边形层 - 使用纯色或图像图案填充多边形区域。

以下代码演示如何创建数据源、将其添加到映射、将 GeoJSON 点数据从远程位置导入到数据源,然后将其连接到气泡层。

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

还有其他渲染层不会连接到这些数据源,而是直接加载数据以进行渲染。

  • 图块层 - 将光栅图块层叠加到地图顶部。

包含多个层的单个数据源

可将多个层连接到单个数据源。 在许多不同方案中,这种选择都很有用。 例如,假设用户绘制多边形的场景。 当用户向地图中添加点时,我们应渲染并填充多边形区域。 如果对多边形边框添加样式化的线条,用户在绘制时则可以更清晰地看到多边形的边缘。 若要方便地编辑多边形中的单个位置,可以在每个位置上添加图柄,如单边锁定或标记。

显示单个数据源中的多层渲染数据的地图屏幕截图。

大多数地图平台中都需要一个多边形对象、一个线条对象以及用于多边形中各个位置的单边锁定。 由于修改了多边形,因此你需要手动更新线条和单边锁定,这可能很快就变得复杂起来。

使用 Azure Maps,只需在数据源中创建一个多边形,如以下代码所示。

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

提示

还可以使用 map.layers.insertLayer(_:below:) 方法,可以将现有层的 ID 或实例作为第二个参数传递。 这会告知地图将要添加的新层插入到现有层下方。 除传递层 ID 之外,这种方法还支持以下值。

  • "labels" - 将新层插入到地图标签层之下。
  • "transit" - 将新层插入到地图道路和中转层之下。

其他信息

有关可向地图添加的更多代码示例,请参阅以下文章: