ガイドv3.2 API Referencev3.1 API Reference
ガイド

効率的なマップレンダリングのためのベストプラクティスを適用する

 HERE Maps API for JavaScript version 3.2

この記事では、マップの応答性の最適化とシームレスなユーザーエクスペリエンスに重点を置き、HERE Maps API for Javascriptを効果的に活用する方法について説明します。

マーカーアイコンを再利用する

HERE Maps API for Javascriptでは、H.map.Iconクラスが再利用可能なビジュアルマップマーカーを表しています。マップのパフォーマンスを最適化するには、マーカーごとに新しいH.map.Iconインスタンスを作成するのではなく、1つのアイコンインスタンスをできるだけ多くのマーカーオブジェクトで再利用します。

次の例は、新しいマーカーごとに新たなアイコンオブジェクトを作成する、非推奨の方法を示しています。

// Array of anchor points for markers
var points = [...],
    markers = [];

for (var i = 0; i < points.length; i++) {
    markers.push(new H.map.Marker(points[i], {
        // A new identical Icon instance is created at each iteration 
        // of the loop - do not do this:
        icon: new H.map.Icon('graphics/markerHouse.png')
    }));
};

マーカーごとに新たなiconオブジェクトを作成するのではなく、以下の例のように、1つのH.map.IconインスタンスをすべてのH.map.Markerインスタンスで再利用します。

// Array of anchor points for markers
var points = [...],
    markers = [],
    // Create single Icon instance
    icon = new H.map.Icon('graphics/markerHouse.png');

for (var i = 0; i < points.length; i++) {
    markers.push(new H.map.Marker(points[i], {
        // Reuse the Icon instance:
        icon: icon
      }));
}

前の例で示した方法により、パフォーマンスが向上し、メモリ消費量が削減されます。

📘

多数のマップマーカーを表示する場合は、マーカークラスタリングの使用を検討します。

onAttachonDetachのコールバックをシンプルに保つ

DomMarkerを使用する場合、onAttachonDetachオプションをDomIconコンストラクターに渡し、その特定のDomIconインスタンスに関連付けられたすべてのDomMarkersに対してイベントリスナーを追加できるようにすることで、機能を拡張できます。

これらのコールバックは、DomMarkerがマップに現れたり、ビューポートから見えなくなったりするたびにトリガーされます。パフォーマンスを最適化するために、これらのコールバックで計算コストの高い処理を実行しないでください。パンやズーム機能に悪影響を及ぼす可能性があります。

以下の例は、このようなパフォーマンス低下を引き起こす、非推奨の方法を示しています。

// Assume that domElement is an already defined DOM element
// and contains a large number of children:
var domIcon = new H.map.DomIcon(domElement, {
    onAttach: function(clonedElement, domIcon, domMarker) {
        // Avoid expensive computation inside onAttach 
        // Such as quering the DOM:
        var defaultButton = clonedElement.querySelector('.btn-default');
        var infoButton = clonedElement.querySelector('.btn-info');

        defaultButton.addEventListener('click', onDefaultClick);
        infoButton.addEventListener('click', onInfoClick);
    },
    onDetach: function(clonedElement, domIcon, domMarker) {
        // Avoid expensive computation inside onDetach
        // such as quering the DOM
        var defaultButton = clonedElement.querySelector('.btn-default');
        var infoButton = clonedElement.querySelector('.btn-info');

        defaultButton.removeEventListener('click', onDefaultClick);
        infoButton.removeEventListener('click', onInfoClick);
    }
});

効率的に処理するには、必要なすべてのアセットを事前に計算しておくか、onAttach()およびonDetach()メソッドからイベントコールバック関数に計算処理をオフロードします。効果的な方法の1つとして、次のコードスニペットに示すようなイベントデリゲーションがあります。

// Use event bubbling to handle all events on the parent node itself,
// and perform computations only when needed.
function onMarkerClick(e) {
    var targetClassName = e.target.className;
    if (targetClassName  === 'btn-default') {
        onDefaultClick(e);
    } else if (targetClassName  === 'btn-info') {
        onInfoClick(e);
    }
}

var domIcon = new H.map.DomIcon(domElement, {
    onAttach: function(clonedElement, domIcon, domMarker) {
        // Simply subscribe to the event:
        domElement.addEventListener('click', onMarkerClick);
    },
    onDetach: function(clonedElement, domIcon, domMarker) {
        // Simply unsubscribe from the event:
        domElement.removeEventListener('click', onMarkerClick);
    }
});

mapviewchangeではなくmapviewchangeendイベントを使用する

mapviewchangemapviewchangeendイベントには同様の機能があり、どちらもマップビューポートの変更を処理するために使用されます。ただし、次のような異なる動作を示します。mapviewchangeはビューポートの状態変更中にアニメーションステップごとにトリガーされるのに対し、mapviewchangeendはこのプロセスの完了をマークします。

どちらのイベントもビューポートの更新を処理するために使用できますが、多くの場合、mapviewchangeは過度な処理を引き起こす可能性があります。たとえば、ユーザーがマップをダブルクリックしたときに現在のズームレベルでUI要素を何度も更新してしまうなど、非効率な処理につながる場合があります。

次のコードスニペットは、非効率な処理の原因となる非推奨の構成例を示しています。

// Displays current zoom level to the end user:
function displayZoomLevel() {...}

// A listener updates the map zoom level on each map view change
// -- this occurs more than 20 times on a double-click on the map, 
// inefficient and to be avoided:
map.addEventListener('mapviewchange', function () {
    var zoom = map.getZoom();

    // This function is called more than 20 times on a double-click on the map!
    displayZoomLevel(zoom);
});

mapviewchangeendイベントは、ビューポートの変更が完了したときに一度のみ呼び出されます。このイベントを使用して、その時点でマップビューポートを更新します。次のスニペットは、この推奨事項に従う方法の例を示しています。

/* 
    Displays current zoom level to the end user 
 */
function displayZoomLevel() {...}

// A listener updates the map zoom level -- it is called once when the map 
// view change is complete.
map.addEventListener('mapviewchangeend', function () {
    var zoom = map.getZoom();

    // The function that displays the zoom level is called only once, 
    // after zoom level changed
    displayZoomLevel(zoom);
});

mapviewchangeendイベントはmapviewchangeの一度だけ呼び出されるバリアントです。一般的なガイドラインとして、mapviewchangeendを使用する方がより効率的です。

組み込みアニメーション機能を使用する

異なるマップ状態間の遷移をアニメーション化する場合 (マップの中心またはズームレベルの変更の結果など) は、マップエンジンの組み込みアニメーション機能を使用します。

H.Map#setZoomH.Map#setCenterH.map.ViewModel#setLookAtDataなどのメソッドは、アニメーション化された遷移を適用するかどうかを指定する第2の引数を受け取ります。

詳細については、「APIリファレンス」の「H.map」ページを参照してください。

次の例に示すような独自のズームアニメーションを作成しないでください。

// Assumption: 'map' is initialized and available.
var currentZoom = map.getZoom();
var endZoom = currentZoom + 3;

// Update zoom level on each animation frame,
// till we reach endZoom:
function step() {
    currentZoom += 0.05;
    map.setZoom(currentZoom);

    (currentZoom < endZoom) && requestAnimationFrame(step);
}

// Start zoom animation
step();

代わりに、次の例に示すような任意の2番目のパラメーターを持つsetZoom()メソッドを呼び出してアニメーションを実行します。

/**
 * Assumption: 'map' is initialized and available
 */
// Call getZoom() with an optional second parameter,
// indicating that an animation is to be performed:
map.setZoom(map.getZoom() + 3, true);

読み取り専用オブジェクトとしてH.map.SpatialStyleインスタンスを使用する

H.map.SpatialStyleのインスタンスは、読み取り専用オブジェクトとしての使用が想定されています。プロパティを変更すると視覚的な不一致を引き起こす可能性があるため、推奨されません。

次のスニペットは、既存のSpatialStyleオブジェクトを複数のSpatialオブジェクト間で再利用する有効なユースケースを示しています。この操作では元のSpatialStyleオブジェクトは変更されません。

// Assumption: 'spatial1' and 'spatial2' are initialized and available.
// Get current style of the first spatial:
var style = spatial1.setStyle();

// Reusing an existing SpatialStyle object is fine,
// because we are not modifying it:
spatial2.setStyle(style);

あるSpatialオブジェクトのスタイルを別のオブジェクトで再利用し、一度fillColorによる変更を行う場合、getStyleメソッドによって返されたオブジェクトを変更すると、元の空間の視覚的表現で非決定的な結果が生じる可能性があります。

// Assumption: 'spatial1' and 'spatial2' are initialized and available.
// Get current style of the first spatial:
var style = spatial1.getStyle();

// Modification of a SpatialStyle object -- never do this
style.fillColor = 'red';

// If we now apply the modified style to the second spatial, the results may be
// inconsistent -- sometimes the fillColor change takes effect, but not always!
spatial2.setStyle(style);

このスタイルを正しく変更するには、新しいSpatialStyleオブジェクトを作成するか、以下のコード例に示されているように、getCopy()メソッドを既存のオブジェクトに対して使用します。

/**
 * Assumption: 'spatial1' and 'spatial2' are initialized and available.
 */
// Get the current style of the first spatial and get an extended version
// by calling getCopy() method on it
var style = spatial1.setStyle().getCopy({
    fillColor: 'red'
});

// Now changing the style of the second class is completely safe and 
// produces the expected results consistently:
spatial2.setStyle(style);

SpatialStyleオブジェクトを再利用する場合は、同じオブジェクトを複数回使用するベストプラクティスに従います。ただし、変更が必要な場合は、getCopy()メソッドを使用して元のオブジェクトのコピーを取得するか、新しいインスタンスを作成します。