リアルタイム渋滞情報をあなたの位置情報アプリに

# はじめに
HEREでエンジニアをしておりますkekishidaと申します。
HEREには**Traffic Vector Tile API**サービスというものが存在します。こちらを使用すればHEREが管理、提供をするリアルタイム渋滞情報を自身の位置情報アプリに容易に組み込むことが可能になります。

https://developer.here.com/documentation/traffic-vector-tiles/dev_guide/topics/quickstart.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104985/f9112d40-5eed-66d7-90e0-c3a64c5b2714.png)
本記事では２つのアプローチで実装します。

- HERE Maps API for Javascript
- HERE Traffic Vector Tile API over Maplibre GL JS

# HERE Maps API for Javascript
とても簡単です。以下の記事で紹介しましたReactコンポーネントであるMap.jsにワンラインを追加します。
（本アプリを実行する場合は、以下リンクの記事をご参考ください。）

https://qiita.com/kekishida/items/e3cfb97f2e7458fb0610

```diff_javascript:Map.js
import * as React from 'react';

const Map = (props) => {
  const apikey = props.apikey;
  const gps = props.gps;
  // Create a reference to the HTML element we want to put the map on
  const mapRef = React.useRef(null);
  /**
   * Create the map instance
   * While `useEffect` could also be used here, `useLayoutEffect` will render
   * the map sooner
   */
    // React.useEffect(() => {
   React.useLayoutEffect(() => {
    // `mapRef.current` will be `undefined` when this hook first runs; edge case that
    if (!mapRef.current) return;
    const H = window.H;
    const platform = new H.service.Platform({
        apikey: apikey
    });
    const defaultLayers = platform.createDefaultLayers();
    // configure an OMV service to use the `core` endpoint
    var omvService = platform.getOMVService({ path: "v2/vectortiles/core/mc" });
    var baseUrl = "https://js.api.here.com/v3/3.1/styles/omv/oslo/japan/";

    // create a Japan specific style
    var style = new H.map.Style(`${baseUrl}normal.day.yaml`, baseUrl);

    // instantiate provider and layer for the base map
    var omvProvider = new H.service.omv.Provider(omvService, style);
    var omvlayer = new H.map.layer.TileLayer(omvProvider, { max: 22 ,dark:true});

    // instantiate (and display) a map:
    var map = new H.Map(mapRef.current, omvlayer, {
        zoom: 16,
        center: { lat: gps.lat, lng: gps.lng },
    });

    // add a resize listener to make sure that the map occupies the whole container
    window.addEventListener("resize", () => map.getViewPort().resize());

    // MapEvents enables the event system
    // Behavior implements default interactions for pan/zoom (also on mobile touch environments)
    var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
    // Create the default UI components
    var ui = H.ui.UI.createDefault(map, defaultLayers); 
    // Marker code goes here
    var LocationOfMarker = { lat: gps.lat, lng: gps.lng };
    var pngIcon = new H.map.Icon('<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 2c2.131 0 4 1.73 4 3.702 0 2.05-1.714 4.941-4 8.561-2.286-3.62-4-6.511-4-8.561 0-1.972 1.869-3.702 4-3.702zm0-2c-3.148 0-6 2.553-6 5.702 0 3.148 2.602 6.907 6 12.298 3.398-5.391 6-9.15 6-12.298 0-3.149-2.851-5.702-6-5.702zm0 8c-1.105 0-2-.895-2-2s.895-2 2-2 2 .895 2 2-.895 2-2 2zm10.881-2.501c0-1.492-.739-2.83-1.902-3.748l.741-.752c1.395 1.101 2.28 2.706 2.28 4.5s-.885 3.4-2.28 4.501l-.741-.753c1.163-.917 1.902-2.256 1.902-3.748zm-3.381 2.249l.74.751c.931-.733 1.521-1.804 1.521-3 0-1.195-.59-2.267-1.521-3l-.74.751c.697.551 1.141 1.354 1.141 2.249s-.444 1.699-1.141 2.249zm-16.479 1.499l-.741.753c-1.395-1.101-2.28-2.707-2.28-4.501s.885-3.399 2.28-4.5l.741.752c-1.163.918-1.902 2.256-1.902 3.748s.739 2.831 1.902 3.748zm.338-3.748c0-.896.443-1.698 1.141-2.249l-.74-.751c-.931.733-1.521 1.805-1.521 3 0 1.196.59 2.267 1.521 3l.74-.751c-.697-.55-1.141-1.353-1.141-2.249zm16.641 14.501c0 2.209-3.581 4-8 4s-8-1.791-8-4c0-1.602 1.888-2.98 4.608-3.619l1.154 1.824c-.401.068-.806.135-1.178.242-3.312.949-3.453 2.109-.021 3.102 2.088.603 4.777.605 6.874-.001 3.619-1.047 3.164-2.275-.268-3.167-.296-.077-.621-.118-.936-.171l1.156-1.828c2.723.638 4.611 2.016 4.611 3.618z"/></svg>', { size: { w: 56, h: 56 } });

    // Create a marker using the previously instantiated icon:
    var marker = new H.map.Marker(LocationOfMarker, { icon: pngIcon });

    // Add the marker to the map:
    map.addObject(marker);

+   map.addLayer(defaultLayers.vector.traffic.map); 

    // Optionally, 
    //Show the marker in the center of the map
    map.setCenter(LocationOfMarker)

    // This will act as a cleanup to run once this hook runs again.
    // This includes when the component un-mounts
    return () => {
      map.dispose();
    };
  }, [props.gps]); // This will run this hook every time this ref is updated
  return <div style={ { width: "100%", height: "500px" } }  ref={mapRef}  />;
};
export default Map;
```
以下のラインを追加するだけで、リアルタイム渋滞情報を手にすることができます。
```javascript
map.addLayer(defaultLayers.vector.traffic.map);
```
それでは実行しましょう。（以下は、東京=>大阪=>福岡=>札幌と確認しています。）
![完成イメージ2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104985/9d521312-33ca-b227-473a-6f22bc9cc632.gif)
各地のリアルタイム渋滞状況を閲覧することが確認できました。
# HERE Traffic Vector Tile API over Maplibre GL JS
HEREが提供するリアルタイム渋滞情報は、HERE純正のMaps API for Javascriptのみではなく、Maplibre GL JSでも使用することができます。こちらについても以下の記事をベースに変更してみましょう。

https://qiita.com/kekishida/private/e3cfb97f2e7458fb0610

まずはMaplibre GL JSを使用するためにインストールします。
```bash:shell command
npm install maplibre-gl@2.4.0
```
先ほどのReactコンポーネントであるMap.jsをごっそりMaplibre GL JS用に変更します。ここではOpenStreetMap（OSM）のベクトルタイルを使って、OSM標準のスタイルを適用しています。
```diff_javascript:Map.js
import * as React from 'react';
import maplibregl from 'maplibre-gl';
import { useRef } from 'react';


const Map = (props) => {
  const apikey = props.apikey;
  const gps = props.gps;
  console.log(gps.lat);
  console.log(gps.lng);
  // Create a reference to the HTML element we want to put the map on
  const map = useRef(null);
  const mapContainer = useRef(null);
  /**
   * Create the map instance
   * While `useEffect` could also be used here, `useLayoutEffect` will render
   * the map sooner
   */
    React.useEffect(() => {
    if (map.current) return;
    map.current = new maplibregl.Map({
      container: mapContainer.current,
      style: 'https://tile.openstreetmap.jp/styles/osm-bright-ja/style.json', 
      center: [gps.lng, gps.lat ], 
      zoom: 16, 
  　});
  
    map.current.on('load', () => {
        map.current.addSource('traffic-flow-source', {
            type: 'vector',
            tiles: [
            'https://traffic.vector.hereapi.com/v2/traffictiles/flow/mc/{z}/{x}/{y}/omv?apiKey='+apikey,
            ],
        });
        map.current.addLayer(
            {
                "id": "traffic-flow-red",
                "type": "line",
                "source": "traffic-flow-source",
                "source-layer": "traffic_flow",
                "paint": {
                  "line-color": "#FF0000",
                  "line-width": 5
                },
                "filter": [
                  "all",
                  ["<", "congestion", 1.0],
                  [">=", "congestion", 0.8]
                ]
              }
        );
        map.current.addLayer(
            {
                "id": "traffic-flow-orange",
                "type": "line",
                "source": "traffic-flow-source",
                "source-layer": "traffic_flow",
                "paint": {
                  "line-color": "#FFD700",
                  "line-width": 5
                },
                "filter": [
                  "all",
                  ["<", "congestion", 0.8],
                  [">=", "congestion", 0.65]
                ]
              }
        );
        map.current.addLayer(
            {
                "id": "traffic-flow-yellow",
                "type": "line",
                "source": "traffic-flow-source",
                "source-layer": "traffic_flow",
                "paint": {
                  "line-color": "#FFFF00",
                  "line-width": 5
                },
                "filter": [
                  "all",
                  ["<", "congestion", 0.65],
                  [">=", "congestion", 0.4]
    
                ]
              }
        );
        map.current.addLayer(
            {
                "id": "traffic-flow-green",
                "type": "line",
                "source": "traffic-flow-source",
                "source-layer": "traffic_flow",
                "paint": {
                  "line-color": "#00FF00",
                  "line-width": 5
                },
                "filter": [
                  "all",
                  ["<", "congestion", 0.4],
                  [">=", "congestion", 0]
    
                ]
              }
        );
    });
    return () => {
      map.current.remove();
      map.current=null;
    };
  }, [props.gps]); // This will run this hook every time this ref is updated
  return <div style={ { width: "100%", height: "500px" } }  ref={mapContainer}  />;
};
export default Map;
```
ポイントとなるのは上記コードの中のちょうど以下のソースおよびスタイルを追加している部分です。
```javascript
    map.current.on('load', () => {
        map.current.addSource('traffic-flow-source', {
            type: 'vector',
            tiles: [
            'https://traffic.vector.hereapi.com/v2/traffictiles/flow/mc/{z}/{x}/{y}/omv?apiKey='+apikey,
            ],
        });
        map.current.addLayer(
            {
                "id": "traffic-flow-red",
                "type": "line",
                "source": "traffic-flow-source",
                "source-layer": "traffic_flow",
                "paint": {
                  "line-color": "#FF0000",
                  "line-width": 5
                },
                "filter": [
                  "all",
                  ["<", "congestion", 1.0],
                  [">=", "congestion", 0.8]
                ]
              }
        );
        map.current.addLayer(
            {
                "id": "traffic-flow-orange",
                "type": "line",
                "source": "traffic-flow-source",
                "source-layer": "traffic_flow",
                "paint": {
                  "line-color": "#FFD700",
                  "line-width": 5
                },
                "filter": [
                  "all",
                  ["<", "congestion", 0.8],
                  [">=", "congestion", 0.65]
                ]
              }
        );
        map.current.addLayer(
            {
                "id": "traffic-flow-yellow",
                "type": "line",
                "source": "traffic-flow-source",
                "source-layer": "traffic_flow",
                "paint": {
                  "line-color": "#FFFF00",
                  "line-width": 5
                },
                "filter": [
                  "all",
                  ["<", "congestion", 0.65],
                  [">=", "congestion", 0.4]
    
                ]
              }
        );
        map.current.addLayer(
            {
                "id": "traffic-flow-green",
                "type": "line",
                "source": "traffic-flow-source",
                "source-layer": "traffic_flow",
                "paint": {
                  "line-color": "#00FF00",
                  "line-width": 5
                },
                "filter": [
                  "all",
                  ["<", "congestion", 0.4],
                  [">=", "congestion", 0]
    
                ]
              }
        );
    });

```
それでは実行しましょう。
![完成イメージ3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104985/4a36ec06-9e89-4e91-cec8-e25e30855954.gif)
同じく各地のリアルタイム渋滞状況を確認することができました。
# おわりに
いかがでしたでしょうか？以上のようにHEREが提供するリアルタイム渋滞情報(HERE Traffic Vector Tile API)をHERE純正のMaps API for Javascriptだけでなく、Maplibre GL JSにおいても利用できることが確認できました。自身の位置情報アプリに容易にリアルタイム渋滞情報を組み込むことができるのはなんだか嬉しくないですか？どちらのアプローチを選択するかはお好み次第かと思いますが、（手前味噌ではありますが）私個人的にはHEREの地図デザインの方が好きです。是非、本記事を参考にHERE Traffic Vector Tile APIをご活用ください。ここまで読んで頂いてありがとうございました。