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

ルート上の交通状況を視覚化する

HERE SDKは、ルートポリライン上に交通状況を直接視覚化できます。リアルタイムの交通流レイヤーに隣接して地図上にルートをレンダリングする方法と、route自体から取得した予測交通データを使用してカスタムの交通情報オーバーレイを描画する2つ方法から選択できます。

リアルタイム交通データと予測交通データのデータソースの違いと各方法の使い分けを理解するには、こちらを参照してください。

交通流に隣接するポリラインをレンダリングする

デフォルトで HERE SDK は交通流レイヤーをカラー エンコーディングして交通状況を示します。MapMeasureDependentRenderSizeを使用すると、MapMeasure.Kindに応じてレンダリングするポリラインの幅が決定され、交通ポリラインに隣接するようにレンダリングします。

これを実装する例は次のとおりです。

GeoPolyline routeGeoPolyline = route.getGeometry();
double outlineWidthInPixel;
Color polylineColor = Color.valueOf(0.051f, 0.380f, 0.871f, 1.0f);
Color outlineColor = Color.valueOf(0.043f, 0.325f, 0.749f, 1.0f);
MapPolyline routeMapPolyline = null;
try {

    // Below, we're creating an instance of MapMeasureDependentRenderSize. This instance will use the scaled width values to render the route polyline.
    // We can also apply the same values to MapArrow.setMeasureDependentTailWidth().
    // The parameters for the constructor are: the kind of MapMeasure (in this case, ZOOM_LEVEL), the unit of measurement for the render size (PIXELS), and the scaled width values.
    MapMeasureDependentRenderSize mapMeasureDependentLineWidth = new MapMeasureDependentRenderSize(MapMeasure.Kind.ZOOM_LEVEL, RenderSize.Unit.PIXELS, getDefaultLineWidthValues());

    // We can also use MapMeasureDependentRenderSize to specify the outline width of the polyline.
    outlineWidthInPixel = 1.23 * mapView.getPixelScale();
    MapMeasureDependentRenderSize mapMeasureDependentOutlineWidth = new MapMeasureDependentRenderSize(RenderSize.Unit.PIXELS, outlineWidthInPixel);
    routeMapPolyline = new MapPolyline(routeGeoPolyline, new MapPolyline.SolidRepresentation(
            mapMeasureDependentLineWidth,
            polylineColor,
            mapMeasureDependentOutlineWidth,
            outlineColor,
            LineCap.ROUND));

} catch (MapPolyline.Representation.InstantiationException e) {
    Log.e("MapPolyline Representation Exception:", e.error.name());
} catch (MapMeasureDependentRenderSize.InstantiationException e) {
    Log.e("MapMeasureDependentRenderSize Exception:", e.error.name());
}
mapView.getMapScene().addMapPolyline(routeMapPolyline);

MapMeasureDependentRenderSizeに指定された幅の値は、2つの連続する各データポイントの間に直線で補間されます。次のように生成できます (コードはExploreにのみ適用)。

// Returns a hashMap where the key is the zoom level and the value is the corresponding scaled width.
private HashMap<Double, Double> getDefaultLineWidthValues() {
    HashMap<Double, Double> widthsPerZoomLevel = new HashMap<>();
    // Here width value will remain 2.18 from 0.0 zoom level to 6.0.
    // From 6.0 the updated value 2.48 will be used.
    widthsPerZoomLevel.put(0.0, 2.18);
    widthsPerZoomLevel.put(6.0, 2.48);
    widthsPerZoomLevel.put(7.0, 2.78);
    widthsPerZoomLevel.put(11.0, 3.1);
    widthsPerZoomLevel.put(16.0, 3.15);
    widthsPerZoomLevel.put(17.0, 3.35);
    widthsPerZoomLevel.put(18.0, 3.78);
    widthsPerZoomLevel.put(19.0, 6.5);
    widthsPerZoomLevel.put(20.0, 10.2);
    widthsPerZoomLevel.put(24.0, 77.5);
    return scaleCurrentHashMap(widthsPerZoomLevel);
}

private HashMap<Double, Double> scaleCurrentHashMap(HashMap<Double, Double> currentValues) {
    for (Map.Entry<Double, Double> entry : currentValues.entrySet()) {
        entry.setValue(entry.getValue() * mapView.getPixelScale());
    }
    return currentValues;
}

VisualNavigatorを使用してポリラインをレンダリングする (Navigateでのみ使用可能)

経路誘導ガイダンス中は、VisualNavigatorのデフォルト値を使用します。これらの値はdefaultRouteManeuverArrowMeasureDependentWidthsを使用して取得します。幅の値をさらにスケーリングすると、さまざまなデバイス間ですっきりと一貫した表示が可能になります。

// Retrieves the default widths of a route polyline and maneuver arrows from VisualNavigator,
// scaling them based on the screen's pixel density.
private HashMap<Double, Double> getDefaultLineWidthValues() {
    HashMap<Double, Double> widthsPerZoomLevel = new HashMap<>();
    for (Map.Entry<MapMeasure, Double> defaultValues : VisualNavigator.defaultRouteManeuverArrowMeasureDependentWidths().entrySet()) {
        Double key = defaultValues.getKey().value;
        Double value = defaultValues.getValue() * mapView.getPixelScale();
        widthsPerZoomLevel.put(key, value);
    }
    return widthsPerZoomLevel;
}

ルートに沿ってカスタムトラフィックをレンダリングする

交通流に隣接してルートのポリラインをレンダリングする代わりに、Routeオブジェクトの一部として利用可能な交通データを使用して、自身で交通状況をレンダリングすることもできます。これは、実際のガイダンスを開始する前のルートプレビューのユースケースに適しています。

このアプローチの場合、交通情報には交通流スキームによって提供されるリアルタイム情報ではなく、ルートからの予測データが含まれるため、精度が低くなる可能性があります。ただし、交通流レイヤーに使用されるカラーエンコーディングと同様に、0 (交通量なし) から10 (道路が通行止め) までの範囲を持つdynamicSpeedInfo.calculateJamFactor()を使用して、ルート上の交通量を示すことができます。各RouteSectionには、さまざまなDynamicSpeedInfoインスタンスを含めることができます。これらは、Spanに沿って次のSpanまで有効です。各Spanジオメトリーは、ルート全体のポリライン形状の一部であるポリラインで表されます。

次のコード スニペットは、Section の最初の SpanDynamicSpeedInfo 要素を取得する方法を示しています。

Section firstSection = route.getSections().get(0);
DynamicSpeedInfo dynamicSpeed = firstSection.getSpans().get(0).getDynamicSpeedInfo();
val firstSection = route.sections[0]
val dynamicSpeed = firstSection.spans[0].dynamicSpeedInfo

DynamicSpeedInfo には、予想されるデフォルトの移動速度である baseSpeedInMetersPerSecond が含まれています。これは、道路の現在の制限速度と同じではない場合があることに注意してください。道路状況が悪い場合は、より遅い移動速度が妥当である場合があります。さらに、trafficSpeedInMetersPerSecondを使用すると、現在の交通状況に基づいて推定される実際の移動速度を取得できます。

この値を適切な色にマッピングする方法の例を以下に示します。

通常、渋滞要因は次のように解釈します。

  • 0 <=渋滞要因< 4:交通量がないか少ない。
  • 4 <=渋滞要因< 8:交通量が中程度、または交通の流れが遅い。
  • 8 <=渋滞要因< 10:激しい渋滞。
  • 渋滞要因= 10:道路が封鎖されており、通行不可。

DynamicSpeedInfoから取得できる渋滞要因は、trafficSpeedInMetersPerSecond / baseSpeedInMetersPerSecondの比率から区分線形的に算出されており、道路タイプやその他のパラメーターは考慮されていない点に注意してください。そのため、提供される渋滞要因は、マップビュー上の交通流の視覚要素 (有効な場合) と正確に一致するとは限りません。さらに、RoutingEngineは予測型位置情報サービスを使用して、ルート上を走行しながら今後の交通状況を予測します。一方、交通流の視覚要素は現時点 (リアルタイム) の情報のみを表示します。事前にレンダリングされた交通流では、現在のズームレベルに基づいて異なる色表現が使用される可能性があることにも注意してください。

指定された到着予測時刻に一致するルートに沿った予測交通量を優先するか、代わりにリアルタイムの交通状況を表示するかはアプリで決定されます。この場合、将来目的地に到着した時点で正確ではなくなる可能性があります。

後者の場合に推奨されるアプローチは、ルート自体に交通状況を追加でレンダリングするのではなく、ルートのポリラインに地図メジャー依存の幅を使用して、ルートと交通流を並べて表示することです。

ルートに沿って交通状況を視覚化する場合は、複数の色のMapPolylineオブジェクトをセクションの各スパンにレンダリングすることを検討してください。

// This renders the traffic jam factor on top of the route as multiple MapPolylines per span.
private void showTrafficOnRoute(Route route) {
    if (route.getLengthInMeters() / 1000 > 5000) {
        Log.d(TAG, "Skip showing traffic-on-route for longer routes.");
        return;
    }

    for (Section section : route.getSections()) {
        for (Span span : section.getSpans()) {
            DynamicSpeedInfo dynamicSpeed = span.getDynamicSpeedInfo();
            Color lineColor = getTrafficColor(dynamicSpeed.calculateJamFactor());
            if (lineColor == null) {
                // We skip rendering low traffic.
                continue;
            }
            float widthInPixels = 10;
            MapPolyline trafficSpanMapPolyline = null;
            try {
                trafficSpanMapPolyline = new MapPolyline(span.getGeometry(), new MapPolyline.SolidRepresentation(
                                new MapMeasureDependentRenderSize(RenderSize.Unit.PIXELS, widthInPixels),
                                lineColor,
                                LineCap.ROUND));
            } catch (MapPolyline.Representation.InstantiationException e) {
                Log.e("MapPolyline Representation Exception:", e.error.name());
            } catch (MapMeasureDependentRenderSize.InstantiationException e) {
                Log.e("MapMeasureDependentRenderSize Exception:", e.error.name());
            }

            mapView.getMapScene().addMapPolyline(trafficSpanMapPolyline);
            mapPolylines.add(trafficSpanMapPolyline);
        }
    }
}

// Define a traffic color scheme based on the route's jam factor.
// 0 <= jamFactor < 4: No or light traffic.
// 4 <= jamFactor < 8: Moderate or slow traffic.
// 8 <= jamFactor < 10: Severe traffic.
// jamFactor = 10: No traffic, that is the road is blocked.
// Returns null in case of no or light traffic.
@Nullable
private Color getTrafficColor(Double jamFactor) {
    if (jamFactor == null || jamFactor < 4) {
        return null;
    } else if (jamFactor >= 4 && jamFactor < 8) {
        return Color.valueOf(1, 1, 0, 0.63f); // Yellow
    } else if (jamFactor >= 8 && jamFactor < 10) {
        return Color.valueOf(1, 0, 0, 0.63f); // Red
    }
    return Color.valueOf(0, 0, 0, 0.63f); // Black
}
// This renders the traffic jam factor on top of the route as multiple MapPolylines per span.
private fun showTrafficOnRoute(route: Route) {
    if (route.lengthInMeters / 1000 > 5000) {
        Log.d(TAG, "Skip showing traffic-on-route for longer routes.")
        return
    }

    for (section in route.sections) {
        for (span in section.spans) {
            val dynamicSpeed: DynamicSpeedInfo? = span.dynamicSpeedInfo
            val lineColor = getTrafficColor(dynamicSpeed?.calculateJamFactor())
                ?: // We skip rendering low traffic.
                continue
            val widthInPixels = 10f
            var trafficSpanMapPolyline: MapPolyline? = null
            try {
                trafficSpanMapPolyline = MapPolyline(
                    span.geometry, MapPolyline.SolidRepresentation(
                        MapMeasureDependentRenderSize(
                            RenderSize.Unit.PIXELS,
                            widthInPixels.toDouble()
                        ),
                        lineColor,
                        LineCap.ROUND
                    )
                )
            } catch (e: MapPolyline.Representation.InstantiationException) {
                Log.e("MapPolyline Representation Exception:", e.error.name)
            } catch (e: MapMeasureDependentRenderSize.InstantiationException) {
                Log.e("MapMeasureDependentRenderSize Exception:", e.error.name)
            }

            if (trafficSpanMapPolyline != null) {
                mapView.mapScene.addMapPolyline(trafficSpanMapPolyline)
                mapPolylines.add(trafficSpanMapPolyline)
            }
        }
    }
}

// Define a traffic color scheme based on the route's jam factor.
// 0 <= jamFactor < 4: No or light traffic.
// 4 <= jamFactor < 8: Moderate or slow traffic.
// 8 <= jamFactor < 10: Severe traffic.
// jamFactor = 10: No traffic, ie. the road is blocked.
// Returns null in case of no or light traffic.
private fun getTrafficColor(jamFactor: Double?): Color? {
    if (jamFactor == null || jamFactor < 4) {
        return null
    } else if (jamFactor >= 4 && jamFactor < 8) {
        return Color.valueOf(1f, 1f, 0f, 0.63f) // Yellow
    } else if (jamFactor >= 8 && jamFactor < 10) {
        return Color.valueOf(1f, 0f, 0f, 0.63f) // Red
    }
    return Color.valueOf(0f, 0f, 0f, 0.63f) // Black
}

この例では、交通状況がある各スパンが個別のポリラインとしてレンダリングされているため、パフォーマンス上の理由から、長いルートの交通状況レンダリングがスキップされることに注意してください。


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