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

オフライン検索機能

オフラインマップはNavigateライセンスでのみ使用できます。

インターネットに接続されているか、オフライン環境で作業しているかにかかわらず、効率的に場所を検索する機能は重要です。両方のユース ケースに対応するために、HERE SDK では 2 つの汎用性の高い検索ソリューションを提供しており、オンラインのみの SearchEngine とオフラインでそれに相当する OfflineSearchEngine を利用できます。OfflineSearchEngine は、オンライン版と同様の強固な検索機能を提供するように作られており、インターネットに接続していなくてもシームレスに情報を取得できます。SearchEngine と同様に、OfflineSearchEngine は次のように簡単に構築してアプリケーションに統合できます。

do {
    // Allows to search on already downloaded or cached map data.
    try offlineSearchEngine = OfflineSearchEngine()
} catch let engineInstantiationError {
    fatalError("Failed to initialize OfflineSearchEngine. Cause: \(engineInstantiationError)")
}

OfflineSearchEngineSearchEngine とほぼ同じインターフェースを備えていますが、HERE バックエンド サービスへの新しいリクエストを開始するのではなく、すでにダウンロードまたはキャッシュに保存されたマップ データから結果が取得されるため、結果は若干異なる場合があります。

この方法では、たとえば、SearchEngine の使用時に受信するデータと比較してデータが古い可能性があります。一方、このクラスではオンライン接続が必要ないため、より速く結果が得られる場合があります。

検索できるのは、すでにキャッシュに保存されているかプリロードされているオフラインマップデータのみです。キャッシュに保存されたマップ データのみを使用する場合、一部のタイルが読み込まれない場合があります。その場合、これらのタイルも読み込まれるまで結果は見つかりません。オフライン マップではこのようなことは起こり得ず、ダウンロードした地域で必要なマップ データが利用できることが保証されます。したがって、キャッシュに保存されたマップ データに依存しないことをお勧めします。

使用可能な OfflineSearchEngine インターフェースのほとんどは SearchEngine でも使用できますが、その逆は当てはまりません。すべてのオンライン機能がオフライン データからアクセスできるわけではないためです。

さらに、HERE Places API で場所を識別する場所 ID は、オフラインの検索結果では異なります。

通常、自由形式の TextQuery を使用すると、少なくとも半径 62.5 km 以内で場所をオフラインで見つけることができます。首都は世界中のどこからでも見つけることができます。都市名などの専用の場所をクエリ文字列に追加すると、その場所の近くが検索されます。

以下に、OfflineSearchEngineの考えられるユースケースを1つ示します。たとえば、外出中に接続が一時的に失われることがあります。このような場合はダウンロード済みのマップ データを検索するのが合理的です。

これを行うには、まずデバイスの接続が失われたかどうかを確認する必要があります。2番目のステップとして、自分の好きなエンジンを使用できます。

if isDeviceConnected {
    _ = searchEngine.searchByText(textQuery,
                            options: searchOptions,
                            completion: onSearchCompleted)
} else {
    _ = offlineSearchEngine.searchByText(textQuery,
                                   options: searchOptions,
                                   completion: onSearchCompleted)
}

検索結果を処理するには、(上記のように) 同じ onSearchCompleted ハンドラーを設定します。

同様の方法で、住所をリバース ジオコーディングすることもできます。

if isDeviceConnected {
    _ = searchEngine.searchByCoordinates(geoCoordinates,
                            options: reverseGeocodingOptions,
                            completion: onReverseGeocodingCompleted)
} else {
    _ = offlineSearchEngine.searchByCoordinates(geoCoordinates,
                                   options: reverseGeocodingOptions,
                                   completion: onReverseGeocodingCompleted)
}

次のようにして住所を地理座標にジオコーディングすることもできます。

if isDeviceConnected {
    _ = searchEngine.searchByAddress(query,
                            options: geocodingOptions,
                            completion: onGeocodingCompleted)
} else {
    _ = offlineSearchEngine.searchByAddress(query,
                                   options: geocodingOptions,
                                   completion: onGeocodingCompleted)
}

デバイスの接続を確認できる解決策は、最初に実際の接続を試みて実装することができます。これに失敗した場合は、OfflineSearchEngine に切り替えるか、その逆を行うことができます。最初にオフラインでの検索を試すこともできますが、マップ データが利用できない場合は、オンラインで試すことができます。

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

インデックス作成でオフライン検索を使用する

OfflineSearchEngine の検索結果を改善するには、OfflineSearchIndex.Options を設定します。デフォルトでは、インデックス作成は無効になっています。

インデックス作成は、提供された検索センターから遠く離れている場合でも、インストールされた Region データ内の場所を検索するメカニズムを提供します。これは、オンラインでのみ動作するため、検索インデックスを作成する必要がなく、デフォルトでこれを行う SearchEngine の動作と一致させるのに役立ちます。

OfflineSearchIndexListener を設定して、インデックス作成プロセスを追跡できます。

これはこの機能のベータリリースであるため、いくつかのバグや予期しない動作が発生する可能性があります。API は、非推奨のプロセスを経ずに、新しいリリースに変更される可能性があります。

内部的には、インデックス作成によってデバイス上に追加データが作成され、インストールされているすべての地図情報が検索可能になります。

インデックス作成が有効になっている場合は永続ストレージのコンテンツを変更するすべての操作に影響します。

  • mapDownloader.downloadRegions(..)
  • mapDownloader.deleteRegions(..)
  • mapDownloader.clearPersistentMapStorage(..)
  • mapDownloader.repairPersistentMap(..)
  • mapUpdater.performMapUpdate(..)
  • mapUpdater.updateCatalog(..)

これらのメソッドにより、永続ストレージにインストールされている Region データのエントリーを含むようにインデックスが作成、削除、更新されることが保証されます。

インデックスの作成には時間がかかるため、オフライン マップのダウンロードや更新を行うすべての操作により長い時間がかかりますが、通常は数秒から数分程度です (インストールされているオフライン マップ データの量によって異なります)。また、保存されたインデックスにより、オフライン マップが占有する容量が約 5% 増加します。

enableOfflineSearchIndexing(...)メソッドは、OfflineSearchEngineのインデックス作成を有効にする方法と、IndexListenerを実装してインデックス作成プロセスをモニタリングする方法を示します。

func enableOfflineSearchIndexing(sdkNativeEngine: SDKNativeEngine) {
    var offlineSearchIndexOptions = OfflineSearchIndex.Options()
    offlineSearchIndexOptions.enabled = true

    offlineSearchIndexListener = OfflineSearchIndexListenerImpl()

    let error = OfflineSearchEngine.setIndexOptions(sdkEngine: sdkNativeEngine,
                                                    options: offlineSearchIndexOptions,
                                                    listener: offlineSearchIndexListener)
    if let error = error {
        print("Failed to set index options: \(error)")
        showMessage("Failed to set index options: \(error)")
    }
}

class OfflineSearchIndexListenerImpl: OfflineSearchIndexListener {

    func onStarted(operation: OfflineSearchIndex.Operation) {
        print("Indexing started. Operation: \(operation)")
    }

    func onProgress(percentage: Int32) {
        print("Indexing progress: \(percentage)%")
    }

    func onComplete(error: OfflineSearchIndex.Error?) {
        if let error = error {
            print("Indexing failed: \(error)")
        } else {
            print("Indexing completed successfully.")
        }
    }
}

上記では、インデックス作成プロセスを追跡するためにOfflineSearchIndexListenerが実装されています。このリスナーにより、インデックス作成のステータスをモニタリングするための3つのコールバックが提供されます。onStarted()コールバックはインデックス作成が開始されると呼び出されます。これは、マップデータが変更された場合や、OfflineSearchEngine.setIndexOptions()が呼び出された場合に行われます。既存のインデックスがインストール済みのマップリージョンと一致している場合は、何も実行されません。インデックス作成が進行すると、onProgress()コールバックが現在の進行状況をパーセンテージで報告し、インデックスの作成または削除の進行状況を示します。インデックス作成が完了すると、onComplete()コールバックが呼び出されます。成功した場合、エラーはnilになります。失敗した場合は、エラーによって問題の詳細が表示されます。通知は、インデックスの再構築が必要な場合にのみ送信されます。

オフラインで独自の場所を使用する

HERE SDK では、オフラインで場所を検索する場合、実行時に独自の場所を使用できます。

OfflineSearchEngine を使用すると、オフラインで検索できる場所のカスタム データを挿入できます。このような個人的な場所は通常のクエリによって見つけることができます。結果は、HERE から直接取得された他の場所と同様にランク付けされます。

カスタム場所データを含めることができる複数の GeoPlace インスタンスを作成できます。これらのインスタンスはその後 MyPlaces データ ソースに追加できます。

let geoPlaces = [
    GeoPlace.makeMyPlace(title: "Pizza Pino", coordinates: GeoCoordinates(latitude: 52.518032, longitude: 13.420632)),
    GeoPlace.makeMyPlace(title: "PVR mdh", coordinates: GeoCoordinates(latitude: 52.51772, longitude: 13.42038)),
    GeoPlace.makeMyPlace(title: "Harley's bar", coordinates: GeoCoordinates(latitude: 52.51764, longitude: 13.42062))
]

myPlaces.addPlaces(places: geoPlaces) { taskOutcome in
    if (taskOutcome == TaskOutcome.completed) {
      // Task completed.
    } else {
      // Task cancelled.
    }
}

このデータ ソースは OfflineSearchEngine に添付できます。

offlineSearchEngine.attach(dataSource: myPlaces) { taskOutcome in
    if (taskOutcome == TaskOutcome.completed) {
      // Task completed.
    } else {
      // Task cancelled.
    }
}

アプリケーションによって一度に数千の場所が追加される可能性があるため、addPlaces()attach() は非同期で動作します。どちらのメソッドも TaskHandle を返すため、操作をキャンセルできます。TaskOutcome イベントは操作がいつ終了したかを通知します。

以下は、そのような場所を見つける方法の一例です。

let queryArea = TextQuery.Area(inCircle: GeoCircle(center: GeoCoordinates(latitude: 52.518032, longitude: 13.420632),
                                                   radiusInMeters: 100.0))
let textQuery = TextQuery("Pizza Pino", area: queryArea)
offlineSearchEngine.searchByText(textQuery: textQuery, options: SearchOptions()) { (searchError, placeList) in
    // ...
}

追加された場所は、offlineSearchEngineインスタンスが有効な限りメモリ内に残ることに注意してください。

軌道に沿って分散された結果

OfflineSearchEngineでは、検索結果がルートの軌道に沿って適切に分散されるようにする、distributedResultsという検索機能をサポートしています。すべての結果を1つの領域にまとめるのではなく、移動ルートに沿って等間隔に配置された施設情報を検索する場合にこの機能が特に便利です。distributedResults機能を使用するには、SearchOptionsdistributedResults = trueを設定し、複数の座標ポイントがあるGeoCorridorを使用して軌道エリアを作成する必要があります。この機能では並列化を使用しており、次の場合にのみサポートされます。

  • searchByCategory (CategoryQuery.Area.corridorAreaが指定されている場合)
  • searchByText (TextQuery.Area.corridorAreaが指定されている場合)

distributedResults機能はOfflineSearchEngineでのみ使用可能で、軌道エリアでオフラインマップデータを使用できるようにする必要があります。

public func testDistributedResultsWithCorridor() {
    let categoryList = [PlaceCategory(id: PlaceCategory.eatAndDrink),
                        PlaceCategory(id: PlaceCategory.shoppingElectronics)]

    // Create a corridor area for the search - this is required for distributedResults to work
    let corridorPoints = [GeoCoordinates(latitude: 52.520798, longitude: 13.409408), // Start point (Berlin)
                          GeoCoordinates(latitude: 52.518032, longitude: 13.420632), // Middle point
                          GeoCoordinates(latitude: 52.515597, longitude: 13.377704), // End point
                          GeoCoordinates(latitude: 52.512000, longitude: 13.390000)] // Additional point for more complex corridor

    let queryArea = CategoryQuery.Area(
        inCorridor: GeoCorridor(polyline: corridorPoints),
        near: GeoCoordinates(latitude: 52.518032, longitude: 13.420632)
    )

    let categoryQuery = CategoryQuery(categoryList, area: queryArea)

    var searchOptions = SearchOptions(languageCode: LanguageCode.enUs, maxItems: 50, distributedResults: true)

    _ = offlineSearchEngine.searchByCategory(categoryQuery, options: searchOptions, completion: onDistributedSearchCompleted)
}

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

    for (index, searchResult) in items!.enumerated() {
        let name = searchResult.title
        let address = searchResult.address.addressText
        let coords = searchResult.geoCoordinates

        print(String(
            format: "Result %d/%d: %@ at %@ (%.6f, %.6f)",
            index + 1, items!.count, name, address,
            coords!.latitude, coords!.longitude
        ))
    }
}

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