次の方法で共有


iOS SDK でデータ ソースを作成する (プレビュー)

Note

Azure Maps iOS SDK の廃止

iOS 用 Azure Maps Native SDK は非推奨となり、2025 年 3 月 31 日に廃止されます。 サービスの中断を回避するには、2025 年 3 月 31 日までに Azure Maps Web SDK に移行します。 詳細については、「Azure Maps iOS SDK 移行ガイド」を参照してください。

Azure Maps iOS SDK では、データがデータ ソースに格納されます。 データ ソースを使用すると、クエリとレンダリングのデータ操作が最適化されます。 現在、次の 2 種類のデータ ソースがあります。

  • GeoJSON ソース: 場所の生データを GeoJSON 形式でローカルに管理します。 小規模から中規模のデータ セット (数十万点を超える図形) に適しています。
  • ベクター タイル ソース: マップのタイル システムに基づいて、現在のマップ ビューのベクター タイルとして書式設定されたデータを読み込みます。 大規模から大規模なデータセット (数百万または数十億点の図形) に最適です。

GeoJSON データ ソース

Azure Maps では、プライマリ データ モデルの 1 つとして 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()

GeoJSON データを Web または assets フォルダーからインポートする

ほとんどの 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 または assets フォルダーからデータをインポートし、コールバック関数を介して 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 クラスを使用すると、フィーチャーを簡単に追加および削除できます。 フィーチャーのジオメトリまたはプロパティを更新するには、データ ソース内のフィーチャーを置き換える必要があります。 フィーチャーを更新するには、次の 2 つのメソッドを使用できます。

  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 形式の圧縮ファイルです。 この圧縮ファイルには、ベクター マップ データと 1 つ以上のレイヤーが含まれています。 このファイルは、各レイヤーのスタイルに基づいて、クライアントでレンダリングおよびスタイル設定できます。 ベクター タイルのデータには、ポイント、線、および多角形の形式で地理的特徴が含まれています。 ベクター タイル レイヤーの使用には、ラスター タイル レイヤーよりも優れている点がいくつかあります。

  • ベクター タイルのファイル サイズは、通常、同等のラスター タイルよりはるかに小さくなります。 そのため、使用される帯域幅が少なくなります。 つまり、待機時間の短縮、より高速なマップ、ユーザー エクスペリエンスの向上を意味します。
  • ベクター タイルはクライアント上でレンダリングされるため、表示されているデバイスの解像度に適応できます。 結果として、レンダリングされるマップはより明確に定義され、鮮明なラベルが表示されます。
  • ベクター マップ内のデータのスタイルを変更しても、クライアントに新しいスタイルを適用できるので、データを再度ダウンロードする必要はありません。 これに対し、ラスター タイル レイヤーのスタイルを変更するには、通常、タイルをサーバーから読み込み、新しいスタイルを適用する必要があります。
  • データはベクター形式で配信されるので、データを準備するために必要なサーバー側の処理が少なくなります。 つまり、新しいデータをより速く使用できるようになります。

Azure Maps は、オープン スタンダードである Mapbox Vector Tile 仕様に準拠しています。 Azure Maps では、プラットフォームの一部として次のベクター タイル サービスが提供されます。

ヒント

iOS SDK を使用して Azure Maps の Render Service からベクターまたはラスター イメージのタイルを使用する場合、atlas.microsoft.comAzureMap のプロパティ domainPlaceholder に置き換えることができます。 このプレースホルダーは、マップによって使用されるのと同じドメインに置き換えられ、同じ認証の詳細も自動的に追加されます。 これにより、Microsoft Entra 認証を使うときに、Render Service での認証が大幅に簡単になります。

マップにベクター タイル ソースからのデータを表示するには、ソースをいずれかのデータ レンダリング レイヤーに接続します。 ベクター ソースを使用するすべてのレイヤーでオプションの sourceLayer 値を指定する必要があります。 次のコードでは、Azure Maps のトラフィック フロー ベクター タイル サービスをベクター タイル ソースとして読み込み、その後、線レイヤーを使用してマップに表示します。 このベクター タイル ソースには、ソース レイヤーに "Traffic flow" という 1 つのデータ セットがあります。 このデータ セット内の行データには、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")

交通流のレベルを示す色分けされた道路線が含まれたマップのスクリーンショット。

データ ソースをレイヤーに接続する

データは、レンダリング レイヤーを使用してマップ上にレンダリングされます。 1 つ以上のレンダリング レイヤーで 1 つのデータ ソースを参照できます。 次のレンダリング レイヤーでは、データ ソースが必要です。

  • バブル レイヤー - ポイント データをマップ上で拡大縮小された円としてレンダリングします。
  • シンボル レイヤー - ポイント データをアイコンやテキストとしてレンダリングします。
  • ヒート マップ レイヤー - ポイント データを密度ヒート マップとしてレンダリングします。
  • 線レイヤー - 線をレンダリングしたり、多角形のアウトラインをレンダリングしたりします。
  • 多角形レイヤー - 純色または画像パターンで多角形の領域を塗りつぶします。

次のコードは、データ ソースを作成し、それをマップに追加し、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)

これらのデータ ソースに接続せず、レンダリング用データを直接読み込むレンダリング レイヤーが他にもあります。

  • タイル レイヤー - マップの上にラスター タイル レイヤーを重ねて表示します。

複数のレイヤーがある 1 つのデータ ソース

複数のレイヤーを 1 つのデータ ソースに接続できます。 このオプションが役に立つさまざまなシナリオがあります。 たとえば、ユーザーが多角形を描画するシナリオを考えてみます。 ユーザーがポイントをマップに追加するときに、多角形領域をレンダリングして塗りつぶす必要があります。 多角形の輪郭を描画するスタイル指定された線を追加すると、ユーザーが描画するときに多角形のエッジが見やすくなります。 多角形内の個々の位置を簡単に編集するために、各位置の上にピンやマーカーなどのハンドルを追加することもできます。

1 つのデータ ソースからのデータをレンダリングする複数のレイヤーを示すマップのスクリーンショット。

ほとんどのマッピング プラットフォームで、多角形オブジェクト、線オブジェクト、および多角形内の各位置に対するピンが必要になります。 多角形を変更するときは、線とピンを手動で更新する必要がありますが、これはすぐに複雑になる可能性があります。

Azure Maps で必要なものは、次のコードで示すように、データ ソース内の 1 つの多角形だけです。

// 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 またはインスタンスを 2 番目のパラメーターとして渡すことができます。 これにより、マップは、追加される新しいレイヤーを既存のレイヤーの下に挿入するように指示されます。 このメソッドは、レイヤー ID を渡すだけでなく、次の値もサポートします。

  • "labels" - マップ ラベル レイヤーの下に新しいレイヤーを挿入します。
  • "transit" - マップの道路および輸送のレイヤーの下に新しいレイヤーを挿入します。

関連情報

マップに追加できる他のコード サンプルについては、次の記事をご覧ください。