ガイド変更履歴HERE SDK API references
ガイド

検索機能とジオコーディング機能

HERE SDKは、地理データをシームレスに操作できる包括的な検索機能とジオコーディング機能を備えています。座標を人間が読み取り可能な住所に変換する場合でも、住所入力に基づいて位置を検索する場合でも、HERE SDK は対応できます。

地理座標から住所をリバース ジオコーディングする

SearchEngine を使用して、地図上の特定の位置やエリアの場所を簡単に検索できます。位置の座標しかない場合、一般的なユース ケースでは、ユーザーが地図を操作する必要があります。たとえば、長押しジェスチャーを実行すると、選択した位置の緯度と経度が表示されます。ユーザーは地図でこの位置を見ることができますが、対応する住所情報などの詳細はありません。

ここで、リバースジオコーディングが役に立ちます。

関心のある場所は、GeoCoordinates インスタンスによって表されます。これは、たとえば、ユーザーが地図をタップすることで取得できます。その位置を「ジオコーディング」する方法については、次のコードを参照してください。

private func getAddressForCoordinates(geoCoordinates: GeoCoordinates) {
    // By default results are localized in EN_US.
    let reverseGeocodingOptions = SearchOptions(languageCode: LanguageCode.enGb,
                                                maxItems: 1)
    _ = searchEngine.searchByCoordinates(geoCoordinates,
                            options: reverseGeocodingOptions,
                            completion: onReverseGeocodingCompleted)
}

// Completion handler to receive reverse geocoding results.
func onReverseGeocodingCompleted(error: SearchError?, items: [Place]?) {
    if let searchError = error {
        showDialog(title: "ReverseGeocodingError", message: "Error: \(searchError)")
        return
    }

    // If error is nil, it is guaranteed that the place list will not be empty.
    let addressText = items!.first!.address.addressText
    showDialog(title: "Reverse geocoded address:", message: addressText)
}

SearchEngine によって提供される他の検索機能と同様に、目的の LanguageCode を設定するには、SearchOptions インスタンスを提供する必要があります。これにより、結果として得られる住所の言語が決まります。次に、エンジンの searchByAddress() メソッドを呼び出して、渡された座標の住所をオンラインで検索します。デバイスがオフラインの場合など、エラーが発生した場合、SearchError にエラーの原因が格納されます。

リバースジオコーディングのレスポンスにエラーまたは結果が含まれています。SearchErrorとアイテムリストは一度にnilにすることや、一度にnil以外にすることはできません。

Place インスタンス内に含まれる Address オブジェクトは、国、都市、通りの名前など、未処理の場所の住所を記述する複数の String フィールドを含むデータ クラスです。詳細については、「API リファレンス」を参照してください。読み取り可能な住所表現の受信にのみ興味がある場合は、上記の例に示すように、addressText にアクセスできます。これは、場所のタイトルなど、最も関連性の高い住所の詳細を含む String です。

Screenshot: Showing a long press coordinate resolved to an address.

リバースジオコーディングには特定の検索エリアは必要なく、座標を世界中の住所に変換できます。

住所を位置情報にジオコーディングする

リバースジオコーディングでは未加工の座標から住所を取得できますが、フォワードジオコーディングではその逆が行われるため、通りの名前や都市などの住所の詳細を渡すだけで、未加工の座標やその他の位置情報の詳細を検索できます。

ほとんどの場合、リバースジオコーディングでは1つの結果しか得られませんが、ジオコーディングでは1つ以上の結果が得られる場合があります。

その方法は次のとおりです。まず、検索する場所の近くの座標を指定する必要があります。queryString として、正確な場所を見つける住所を設定します。

// The geoCoordinates act as a reference location to prioritize the search results.
// This helps the `SearchEngine` return addresses that are more relevant and closer to the user’s
// current location instead of global or less relevant matches.
let query = AddressQuery(queryString, near: geoCoordinates)
let geocodingOptions = SearchOptions(languageCode: LanguageCode.deDe,
                                     maxItems: 25)
_ = searchEngine.searchByAddress(query,
                        options: geocodingOptions,
                        completion: onGeocodingCompleted)

この例では、HEREのベルリン本社の通り名 "Invalidenstraße 116" をクエリ文字列として渡します (オプションで都市が続きます)。これはドイツ語の通りの名前であるため、ドイツ語の言語コード deDe を渡します。これにより、返される結果の言語も決まります。

結果は指定された位置から遠く離れた場所にある可能性がありますが、指定された座標に近い結果が上位にランク付けされ、優先的に返されます。

次のステップとして、完了ハンドラーを実装する必要があります。

// Completion handler to receive geocoding results.
func onGeocodingCompleted(error: SearchError?, items: [Place]?) {
    if let searchError = error {
        showDialog(title: "Geocoding", message: "Error: \(searchError)")
        return
    }

    // If error is nil, it is guaranteed that the items will not be nil.
    for geocodingResult in items! {
        //...
    }
}

完了ハンドラーがエラー受け取っていないことを確認した後、リストで Place 要素を確認します。

searchError が nil の場合、結果として得られる items は必ず nil にはなりません。また、その逆も同様です。したがって、オプションのリストを開いても安全です。

結果は、未処理の座標を含む Places オブジェクトにラップされます。Address オブジェクトや、HERE Places API で位置を識別する場所 ID など、その他の住所の詳細もこのオブジェクトには含まれます。以下では、リストを反復処理して、住所テキストと座標を取得します。

for geocodingResult in items! {
    // Note that geoCoordinates are always set, but can be nil for suggestions only.
    let geoCoordinates = geocodingResult.geoCoordinates!
    let address = geocodingResult.address
    let locationDetails = address.addressText
        + ". Coordinates: \(geoCoordinates.latitude)"
        + ", \(geoCoordinates.longitude)"
    //...
}

ユーザーが地図からそのような結果を選択した場合にどのように表示されるかを示す例については、以下のスクリーンショットを参照してください。ご興味がある場合は、付属の "Search" サンプル アプリを参照してください。このアプリでは、住所テキストを検索し、地図上の見つかった場所にマップ マーカーを配置する方法を紹介しています。

Screenshot: Showing a picked geocoding result.

自動候補提示を取得する

ほとんどの場合、場所検索に対応したアプリケーションでは、ユーザーが編集可能なテキスト フィールド コンポーネントに目的の検索語を入力できます。通常、入力中に、考えられる語句の予測を取得すると便利です。

エンジンによって提供される候補は、最も関連性の高い語句が結果リストの上位に表示されるようにランク付けされます。たとえば、最初のリスト アイテムは、ユーザーが現在入力している検索語の自動完了を提供するために使用できます。ユーザーの入力中に更新される、一致する候補のリストを表示することもできます。ユーザーは候補のリストから適切なキーワードを選択し、選択した語句に対して新しい検索を開始できます。タイトルや周辺地などの結果の詳細をあらかじめ取得しておいてそれをユーザーに提示することも可能です。

HERE SDK には、UI や完全に統合された自動完了ソリューションはありません。このようなソリューションは、必要に応じてアプリケーションによって実装できます。Suggestion 機能を使用すると、TextQuery に基づいて考えられる Place の結果が得られます。これらの場所からタイトル テキスト (「Pizza XL」) またはその他の関連する場所情報 (住所など) を使用して、クリック可能な完了結果を提案するなど、ユーザーにフィードバックを提供できます。ただし、このようなソリューションはアプリケーションの個々の要件に依存するため、プラットフォーム API を使用してアプリ側で実装する必要があります。

通常のテキスト クエリと比較して、候補の検索では、入力されたクエリ語句に対して、優先順位に従ってランク付けされた結果を迅速に提供することに特化しています。

エンジンを使用して候補を検索する方法を見てみましょう。

let centerGeoCoordinates = getMapViewCenter()
let autosuggestOptions = SearchOptions(languageCode: LanguageCode.enUs,
                                       maxItems: 5)

let queryArea = TextQuery.Area(areaCenter: centerGeoCoordinates)

// Simulate a user typing a search term.
_ = searchEngine.suggestByText(TextQuery("p", area: queryArea),
                         options: autosuggestOptions,
                         completion: onSearchCompleted)

_ = searchEngine.suggestByText(TextQuery("pi", area: queryArea),
                         options: autosuggestOptions,
                         completion: onSearchCompleted)

_ = searchEngine.suggestByText(TextQuery("piz", area: queryArea),
                         options: autosuggestOptions,
                         completion: onSearchCompleted)

ヘルパー メソッド getMapViewCenter() はここでは省略されていますが、付属のサンプル アプリで確認できます。現在マップ ビューの中央に表示されている GeoCoordinates を返すだけです。

新しいテキスト入力ごとに、次のリクエストを行います。ユーザーが「Pizza」と入力すると仮定すると、最初に「p」、次に「pi」、最後に「piz」の結果を探します。ユーザーが本当に「Pizza」を検索する場合は、3 回目の呼び出しで十分な興味深い候補が得られるはずです。

SearchEngine の他の search() メソッドと同様に、suggestByText() メソッドは TaskHandle を返し、進行中の呼び出しのステータスを確認したり、呼び出しをキャンセルしたりするために必要に応じて使用できます。

結果を取得する方法を見てみましょう。

// Completion handler to receive auto suggestion results.
func onSearchCompleted(error: SearchError?, items: [Suggestion]?) {
    if let searchError = error {
        print("Autosuggest Error: \(searchError)")
        return
    }

    // If error is nil, it is guaranteed that the items will not be nil.
    print("Autosuggest: Found \(items!.count) result(s).")

    for autosuggestResult in items! {
        var addressText = "Not a place."
        if let place = autosuggestResult.place {
            addressText = place.address.addressText
        }
        print("Autosuggest result: \(autosuggestResult.title), addressText: \(addressText)")
    }
}

ここでは、Suggestion で見つかったリスト アイテムを記録する完了ハンドラーを定義します。エラーがない場合、エンジンは結果のリストを返しますが、そうでない場合は nil になります。

すべての候補が場所になるわけではありません。たとえば、「disco」のような一般的な語を新しい検索に入力できます。一般的な用語を使用すると、Suggestion の結果には Place オブジェクトは含まれず、title のみが含まれます。これは特定の場所を参照せずにテキストを表すためです。Suggestion の結果で使用できるすべてのフィールドについては、「API リファレンス」を参照してください。

結果の順序はランク付けされますが、完了イベントが届く順序は保証されないことに注意してください。したがって、まれに、「pi」の結果よりも先に「piz」の結果を受け取ることがあります。

注 (Navigateにのみ適用)

OfflineSearchEngineでも提案を使用できます。この場合、SearchEngineOfflineSearchEngine のインスタンスと交換するだけです。これには、ダウンロードまたはキャッシュに保存されたマップデータが必要です。この例は、GitHubにある「SearchHybrid」サンプルアプリで見つけることができます。

場所カテゴリーを検索する

「ピザ」のように TextQuery を使用してキーワード検索を行う代わりに、カテゴリーを検索して、Place の結果を予想されるカテゴリーに限定することもできます。

カテゴリー ID は特定の形式に従い、HERE プラットフォームでは 700 を超えるさまざまなカテゴリーが利用可能です。幸いなことに、HERE SDK には、カテゴリー検索をより簡単にできるように事前定義された値のセットが用意されています。必要に応じて、xxx​​-xxxx-xxxx の形式に従ってカスタム カテゴリー文字列を渡すこともできます。各グループは、第 1、第 2、第 3 レベルのカテゴリーを表します。第 1 レベルはメイン カテゴリーを表し、第 3 レベルは第 2 レベルのサブカテゴリーに属するサブカテゴリーを表します。各カテゴリー レベルはプレース カテゴリー システムの数値として定義されます。

例として、以下では「Eat and Drink」カテゴリーか「Shopping Electronics」カテゴリーに属する​​すべての場所を検索しています。

private func searchForCategories() {
    let categoryList = [PlaceCategory(id: PlaceCategory.eatAndDrink),
                        PlaceCategory(id: PlaceCategory.shoppingElectronics)]
    let queryArea = CategoryQuery.Area(areaCenter: GeoCoordinates(latitude: 52.520798,
                                                                  longitude: 13.409408))
    let categoryQuery = CategoryQuery(categoryList, area: queryArea)
    let searchOptions = SearchOptions(languageCode: LanguageCode.enUs,
                                      maxItems: 30)

    _ = searchEngine.searchByCategory(categoryQuery,
                            options: searchOptions,
                            completion: onSearchCompleted)
}

public func onSearchCompleted(error: SearchError?, items: [Place]?) {
    if let searchError = error {
        print("Search Error: \(searchError)")
        return
    }

    // If error is nil, it is guaranteed that the items will not be nil.
    showDialog(title: "Search Result", message: "\(items!.count) result(s) found. See log for details.")

    for place in items! {
        let addressText = place.address.addressText
        print(addressText)
    }
}

PlaceCategory には String を使用できます。ここでは、事前定義されたカテゴリー eatAndDrinkshoppingElectronics を使用しています。String値には、プレースカテゴリーシステムで表されるIDが含まれます。ここでも、SearchEnginesearchByCategory()メソッドを使用し、カテゴリーリストと場所を検索する地理座標を含むCategoryQueryオブジェクトを渡します。

場所チェーンを検索する

場所チェーンを使用すると、特定のブランドに属するすべての場所を検索できます。たとえば、IDが「K-Supermarket」を表す場合は、「K-Supermarket」店舗のみに絞って検索することができます。PlaceCategoryが場所チェーンのカテゴリーと一致していることを確認します。

PlaceChain識別子は、場所チェーンを関連付けるために使用されます。サポートされている場所チェーンのリストはこのリンクにあります。

let placeChain = PlaceChain(id: "1566")
var includeChains: [PlaceChain] = []
includeChains.append(placeChain)
categoryQuery.includeChains = includeChains

上記のチェーンID「1566」は「マクドナルド」の場所チェーンを識別し、eatAndDrinkカテゴリーに属しています。複数の場所カテゴリーを指定した場合、少なくとも1つのカテゴリーが一致しないと結果は表示されません。

ルートに沿って検索する

SearchEngine では、長方形または円形のエリアではなく GeoPolyline やその他のパラメーターで定義できるより複雑な GeoCorridor に沿って検索するという特殊な検索ケースをサポートしています。

このようなケースの最も一般的なシナリオは、Route に沿ってレストランを検索することです。Route オブジェクトをすでに計算していると仮定します。ルートの計算方法については、「経路」セクションを参照してください。TextQuery を指定すると、ルート全体を囲む長方形のエリアを簡単に定義できます。

let textQuery = TextQuery("restaurants", in: route.boundingBox)

ただし、より長いルートの場合、ルートの形状によっては、route.boundingBox で長方形エリアのルート全体を囲む必要があるため、結果が実際のルート パスから非常に遠くなる場合があります。

HERE SDK にはルートの実際の形状から検索エリアを決定できる GeoCorridor クラスが用意されており、より正確なソリューションを提供しています。この方法では、パス上またはパスの下にある検索結果のみが含まれます。

以下に、ルート上の充電スタンドを検索する方法の例を示します。

private func searchAlongARoute(route: Route) {
    // We specify here that we only want to include results
    // within a max distance of xx meters from any point of the route.
    let routeCorridor = GeoCorridor(polyline: route.geometry.vertices,
                                    halfWidthInMeters: Int32(200))
    let queryArea = CategoryQuery.Area(inCorridor: routeCorridor, near: mapView.camera.state.targetCoordinates)
    let placeCategory = PlaceCategory(id: PlaceCategory.businessAndServicesEvChargingStation)
    let categoryQuery = CategoryQuery(placeCategory, area: queryArea)

    let searchOptions = SearchOptions(languageCode: LanguageCode.enUs,
                                      maxItems: 30)
    searchEngine.searchByCategory(categoryQuery,
                        options: searchOptions,
                        completion: onSearchCompleted)
}

// Completion handler to receive results for found charging stations along the route.
func onSearchCompleted(error: SearchError?, items: [Place]?) {
    if let searchError = error {
        print("No charging stations found along the route. Error: \(searchError)")
        return
    }

    // If error is nil, it is guaranteed that the items will not be nil.
    print("Search along route found \(items!.count) charging stations:")

    for place in items! {
        // ...
    }
}

電気自動車充電所の利用状況ステータスを取得する

コネクターの総数、使用中および使用可能なコネクター、追加の詳細を含む利用状況情報です。enableEVChargingStationDetails()オプションを選択すると、個々の充電スタンドのステータスを含む、電気自動車充電所の利用状況をオンラインで取得できます。

有効な資格情報を使用せずにこの機能を使用しようとすると、SearchErrorが発生します。詳細については、APIリファレンスを参照してください。

この機能を使用するには、次の示すようなカスタムオプション呼び出しが必要です。

// Enable fetching online availability details for EV charging stations.
// It allows retrieving additional details, such as whether a charging station is currently occupied.
// Check the API Reference for more details.
private func enableEVChargingStationDetails() {
    // Fetching additional charging stations details requires a custom option call.
    if let error = searchEngine.setCustomOption(name: "browse.show", value: "ev") {
    showDialog(
        title: "Charging Station",
        message: "Failed to enableEVChargingStationDetails.")
    } else {
        print("EV charging station availability enabled successfully.")
    }
}

// Perform a search for charging stations along the found route.
private func searchAlongARoute(route: Route) {
    // We specify here that we only want to include results
    // within a max distance of xx meters from any point of the route.
    let routeCorridor = GeoCorridor(polyline: route.geometry.vertices,
                                    halfWidthInMeters: Int32(200))
    let queryArea = CategoryQuery.Area(inCorridor: routeCorridor, near: mapView.camera.state.targetCoordinates)
    let placeCategory = PlaceCategory(id: PlaceCategory.businessAndServicesEvChargingStation)
    let categoryQuery = CategoryQuery(placeCategory, area: queryArea)

    let searchOptions = SearchOptions(languageCode: LanguageCode.enUs,
                                        maxItems: 30)
    enableEVChargingStationDetails()
    
    searchEngine.searchByCategory(categoryQuery,
                        options: searchOptions,
                        completion: onSearchCompleted)
}

// Completion handler to receive results for found charging stations along the route.
func onSearchCompleted(error: SearchError?, items: [Place]?) {
    if let searchError = error {
        print("No charging stations found along the route. Error: \(searchError)")
        return
    }

    // If error is nil, it is guaranteed that the items will not be nil.
    print("Search along route found \(items!.count) charging stations:")

    for place in items ?? [] {
        let details = place.details
        let metadata = getMetadataForEVChargingPools(details)
        var foundExistingChargingStation = false

        for mapMarker in mapMarkers {
            if let markerMetadata = mapMarker.metadata,
                let id = markerMetadata.getString(key: requiredChargingMetadataKey),
                id.lowercased() == place.id.lowercased() {
                print("Skipping: This charging station was already required to reach the destination (see red charging icon).")
                mapMarker.metadata = metadata
                foundExistingChargingStation = true
                break
            }
        }
        if !foundExistingChargingStation {
            self.addMapMarker(geoCoordinates: place.geoCoordinates!, imageName: "charging.png", metadata: metadata)
        }
    }
}

func getMetadataForEVChargingPools(_ placeDetails: Details) -> Metadata {
    let metadata = Metadata()
    
    if let chargingPool = placeDetails.evChargingPool {
        for station in chargingPool.chargingStations {

            if let supplierName = station.supplierName {
                metadata.setString(key: supplierNameMetadataKey, value: supplierName)
            }
            if let connectorCount = station.connectorCount {
                metadata.setString(key: connectorCountMetadataKey, value: "\(connectorCount)")
            }
            if let availableConnectorCount = station.availableConnectorCount {
                metadata.setString(key: availableConnectorsMetadataKey, value: "\(availableConnectorCount)")
            }
            if let occupiedConnectorCount = station.occupiedConnectorCount {
                metadata.setString(key: occupiedConnectorsMetadataKey, value: "\(occupiedConnectorCount)")
            }
            if let outOfServiceConnectorCount = station.outOfServiceConnectorCount {
                metadata.setString(key: outOfServiceConnectorsMetadataKey, value: "\(outOfServiceConnectorCount)")
            }
            if let reservedConnectorCount = station.reservedConnectorCount {
                metadata.setString(key: reservedConnectorsMetadataKey, value: "\(reservedConnectorCount)")
            }
            if let lastUpdated = station.lastUpdated {
                metadata.setString(key: lastUpdatedMetadataKey, value: "\(lastUpdated)")
            }
        }
    }
    return metadata
}

充電スタンドをクリックしたときに表示される例につい次のスクリーンショットを参照してください。ポップアップに充電スタンド名、コネクター数、使用可能なコネクター、最終更新時刻などが表示されます。

Screenshot: Showing charging station availability details.

ご覧のとおり、GeoCorridor にはルートの GeoPolylineおよび halfWidthInMeters が必要です。このパラメーターは、ポリライン上の任意の地点から経路の端までで、最も遠い端を定義します。値が小さい場合、最終的なコリドーでは実際のルートに沿った非常に近いエリアを定義します。

Screenshot: Showing found charging stations along a route.

ルートの出発地と目的地の座標ではコリドーが丸い形になります。ある程度の太さはあるが、頭と尾の端が丸いだけのヘビを想像してみてください。これを上記のスクリーンショットと混同しないでください。上記のスクリーンショットはルートの出発地と目的地を示すために緑色の円をレンダリングしただけです。

非常に長いルートの場合、検索アルゴリズムは内部で検索コリドーの最適化を試みます。これは、アプリ側で halfWidthInMeters パラメーターを使って制御することもできます。値を大きくするとコリドーの複雑さは軽減しますが、結果の精度は低くなります。

エラーが発生しなかった場合は、上記のセクションですでに示したように Place の結果を処理できます。

このセクションの完全なコードは、GitHubEVRouting サンプル アプリに含まれています。


EN 日本語

HERE documentation

Find answers to your product and technical questions

Documentation

What's new

Videos

EN 日本語

HERE ドキュメント

製品や技術に関する質問の答えを見つけましょう。より多くの内容と最新の情報については、英語版をご覧ください。

ドキュメント

ダイナミックマップ

動的コンテンツ関連のAPIをアプリやサービスに活用して、ドライバーが安全・快適かつ予定どおりに目的地へ到着できるよう支援します。

地図とデータ

世界中を走行する多数のマッピング車両から得られる最新の位置情報データを活用し、精度の高い地図やカスタムレイヤーを構築できます。

最新情報

動画

(function () { const input = document.querySelector('input[data-typeahead]'); if (!input) return; // Prevent the form from submitting/navigating input.closest('form')?.addEventListener('submit', e => e.preventDefault()); input.addEventListener('input', function () { const q = this.value.trim().toLowerCase(); document.querySelectorAll('.nav-group-name').forEach(group => { let anyVisible = false; group.querySelectorAll('.nav-group-task').forEach(task => { const text = task.textContent.trim().toLowerCase(); const show = !q || text.includes(q); task.style.display = show ? '' : 'none'; if (show) anyVisible = true; }); // Hide the whole group header if nothing matches group.style.display = anyVisible || !q ? '' : 'none'; }); }); })(); (function () { function onTokenClick(event) { var link = event.target.closest('.sdk-for-ios .item .token'); if (!link) return; event.preventDefault(); console.log('token clicked', link.textContent.trim()); var item = link.closest('.item'); if (!item) return; var content = item.querySelector('.height-container'); if (!content) { console.log('no .height-container found for item', item); return; } var isHidden = window.getComputedStyle(content).display === 'none'; content.style.display = isHidden ? 'block' : 'none'; link.classList.toggle('token-open', isHidden); var href = link.getAttribute('href'); if (href) { if (history.pushState) history.pushState({}, '', href); else location.hash = href; } } function openHashTarget() { var hash = window.location.hash.slice(1); if (!hash) return; var anchor = document.querySelector('.sdk-for-ios a[name="' + hash + '"]'); if (!anchor) return; var item = anchor.closest('.item'); if (!item) return; var link = item.querySelector('.token'); var content = item.querySelector('.height-container'); if (!link || !content) return; content.style.display = 'block'; link.classList.add('token-open'); } function init() { console.log('HERE SDK accordion init'); openHashTarget(); } document.removeEventListener('click', onTokenClick); document.addEventListener('click', onTokenClick); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } window.addEventListener('hashchange', openHashTarget); window.addEventListener('pageLoad', init); })();