物流DXにも応用できる高速道路の料金計算API

# はじめに
HEREでエンジニアをしておりますkekishidaと申します。
以前、拙記事においてHERE Maps API for Javascripを使用してReactベースで簡単なルート検索アプリを作成する方法の紹介をしました。

https://qiita.com/kekishida/items/7587a2dcab916459e8dd

さて、先日HERE API群においても<font color="Red">**日本の高速道路料金を計算**</font>することが可能になりました。[^1]
本記事では、前回紹介したルート検索アプリのコードをベースに、**日本の高速道路料金を計算するアプリ**に改造してみたいと思います。

[^1]:一部の高速道路（海ほたる、本州四国連絡高速道路、日光宇都宮道路など）は未対応。

# アプリイメージ
前回紹介したルート検索アプリに、
- 車種(Choose Transport Type)
- 支払い方法種別(Choose Payment Type)
- 出発日時(Deperture)

という入力項目を追加しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104985/fee8c725-18cb-fef1-843f-509d56f7b17d.png)
入力パラメータを指定し、Calculate ROUTE!ボタンをクリックするとルートが地図上に描画され、そのルートで必要となる高速道路の料金が地図画面の下にリスト形式で表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104985/cf7753e7-27a2-c285-6f76-6104509eea3c.png)
さらに、地図上のマーカーは料金所の入口、出口に打たれますので、マーカーをクリックすると該当区間で徴収される高速道路の料金情報などもあわせてバルーン表示されます。[^2]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104985/26bb8600-4c2d-b5ba-a61d-8227f2668b8f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104985/f83274ce-ea75-3ecd-bf88-26d8b0c036b2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104985/d582fad7-62e3-3e5f-9390-8caea958228d.png)
最終的に、このサンプルでは森ノ宮ランプから一般道に出ますが、有料道が赤色になっているのに対して、一般道が青色になっていることを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104985/11e7476c-adc9-33df-0187-5b2f49fe28c6.png)

[^2]:徴収される高速道路名は執筆時点(2024/9/2)では目安としての表示。

# 日本における高速道路の料金計算機能について
HEREにおける高速道路の計算機能は以下のAPIを経由して利用することが可能です。

- HERE Route Matching API

https://www.here.com/docs/bundle/route-matching-api-developer-guide/page/topics/toll-cost.html

- HERE Routing API

https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/topics/use-cases/tolls-tunnel.html

- HERE Maps API for JavaScript

https://www.here.com/docs/bundle/maps-api-for-javascript-developer-guide/page/topics/routing.html

- HERE SDK for Android 

https://www.here.com/docs/bundle/sdk-for-android-explore-developer-guide/page/topics/routing-options.html#get-toll-costs

どのAPIを使用するかは、ユースケース次第になってきます。今回のサンプルで使用するHERE Maps API for JavaScriptでは、HERE Routing APIの入力パラメータの仕組みを理解する必要があります。前回の記事にて紹介しましたシンプルなルート検索を行う場合は、以下のようなパラメータを指定していました。

```javascript:Routing APIへの入力パラメータ
      let routingParameters = {
        'transportMode': 'car',
        'return': 'polyline'
      };
      let calculateRoute = () => {
        if (!origin || !destination) return;
        routingParameters.origin = origin.lat+","+origin.lng;
        routingParameters.destination = destination.lat+","+destination.lng;
        routingService.calculateRoute(routingParameters, onResult, onError);
      }
      calculateRoute();
```
すなわち、

- origin
- destination
- transportMode
- return

のみです。

**では、高速道路の料金計算をするには何が必要なのでしょうか？**

現在HEREの公式ドキュメントを確認すると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104985/1b2b30f2-dbc6-99bc-c4c1-5fa0e62cd8c1.png)
https://www.here.com/docs/bundle/routing-api-developer-guide-v8/page/topics/use-cases/tolls-tunnel.html

となっておりますが、こちらの説明だけで日本の高速道路料金を計算するアプリを実装するのはなかなか難しいかもしれません。

本記事では、HERE公式ドキュメントとのギャップを埋めるべく、日本の高速道路の料金を計算するための実装方法について解説します。

それでは始めましょう!

# 必要なもの
ご紹介するサンプルコードは、HEREアカウントの取得が必要になります。
## HEREアカウントの取得
https://qiita.com/kekomat/items/d2e29bac84a27c1ec521
# 作成手順 (step by step)
基本的には、前回紹介したルート検索アプリの拡張版になります。しかしながら、差分表示をするとかえって、みづらくなるためstep bey stepでコードを紹介いたします。
## React projectの作成
```shell:shell command
npx create-react-app <プロジェクトフォルダ名>
```
## index.htmlの編集
index.htmlを以下のように書き換えてください。
```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>HERE Maps API for Javascript Toll Cost Demo</title>
    <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    <link rel="stylesheet" type="text/css" href="https://js.api.here.com/v3/3.1/mapsjs-ui.css" />
    <script
      type="text/javascript"
      src="https://js.api.here.com/v3/3.1/mapsjs-core.js"
    ></script>
    <script
      type="text/javascript"
      src="https://js.api.here.com/v3/3.1/mapsjs-service.js"
    ></script>
    <script
      type="text/javascript"
      src="https://js.api.here.com/v3/3.1/mapsjs-ui.js"
    ></script>
    <script
      type="text/javascript"
      src="https://js.api.here.com/v3/3.1/mapsjs-mapevents.js"
    ></script> 
    <script 
      type="text/javascript" 
      src="https://js.api.here.com/v3/3.1/mapsjs-clustering.js"
    ></script> 
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

```
## index.jsの編集
index.jsを以下のように書き換えてください。
```react:index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <App/>
);
```
ここまでは前回のルート検索アプリと違いはありません。
## App.jsの編集
新たにTollCostというコンポーネントを作成しました。またRoutingコンポーネント及びMapコンポーネントは高速道路の料金計算に対応するための改造をおこなっています。
（以下のAPIKEY部分は、取得しましたAPIKEYに置き換えてください。）

```react:App.js
import Map from './Map';
import { useCallback, useState } from 'react';
import Routing from './Routing';
import TollCost from './TollCost';
let apikey = APIKEY;

function App() {
  const [ origin, setOrigin ] = useState({lat: "35.6814568602531", lng: "139.76799772026422"});
  const [ destination, setDestination ] = useState({lat: "35.6814568602531", lng: "139.76799772026422"});
  const [ routingResponse, setRoutingResponse ] = useState(null);
  const onOriginResult = useCallback((query)=> setOrigin(query),[]);
  const onDestinationResult = useCallback((query)=> setDestination(query),[]);
  const onRoutingResult = useCallback((query)=> setRoutingResponse(query),[]);
    return (
    <div>
      <Routing apikey={apikey} onOriginResult={onOriginResult} onDestinationResult={onDestinationResult} onRoutingResult={onRoutingResult}/>
      <Map apikey={apikey} origin={origin} destination={destination} routingResponse={routingResponse}/>
      <TollCost routingResponse={routingResponse} />
    </div>
  );
};

export default App;
```
## TollCost.jsの作成
以下のファイルを追加してください。
後術しますが、こちらはRouting APIのResponseの内、Toll(高速料金）に関わるJSONフォーマットを整形して表示するためだけのコンポーネントとなっています。
```react:TollCost.js
import * as React from 'react';

const TollCost = React.memo((props) => {
  return (
    <div>
      {props.routingResponse != null?
        props.routingResponse.tolls.map(toll=>
          (
            <div>
            <div>
              <hr></hr>
                {toll.fares !=null?
                    toll.fares.map(fare=>(
                    <div>
                        <div><b>Toll System</b>: {toll.tollSystem}</div>
                        <div>
                            <div><b>Price</b>: {fare.convertedPrice.value}</div>
                            {fare.paymentMethods !=null?
                                fare.paymentMethods.map(paymentMethod=>(
                                <div><b>paymentMethod</b>: {paymentMethod}</div>
                                ))
                                :
                                <div></div>
                            }
                            {fare.transponders !=null?
                                fare.transponders.map(system=>(
                                  <div><b>System</b>: {system.system}</div>
                                ))
                                :
                                <div></div>
                            }
                        </div>
                      </div>
                      ))
                    :
                    <div></div>
                }
            </div>
            <div>
                {toll.tollCollectionLocations !=null?
                    toll.tollCollectionLocations.map(loc=>(
                        <div>
                            <div><b>Toll Collections Location </b>: {loc.location.lat},{loc.location.lng}</div>
                        </div>
                    ))
                    :
                    <div></div>
                }
              <hr></hr>
              </div>
          </div>
          ) 
          )
      :
        <div></div>
      }
      </div>
  );
});
export default TollCost;
```
## Routing.jsの作成
前回のルート検索アプリで作成したRouting.jsに対していくつか改造をしています。
```react:Routing.js
import * as React from 'react';
import { useState } from 'react';


const Routing =  React.memo((props) =>{
  const [text1,setText1] = useState("");
  const [text2,setText2] = useState("");
  const [transport,setTransport] = useState("car");
  const [payment,setPayment] = useState("cash");
  const [selected, setSelected] = useState("");
  const apikey = props.apikey;

  const H = window.H;
  const platform = new H.service.Platform({
      apikey: apikey
  });
  const searchService = platform.getSearchService();
  const routingService = platform.getRoutingService(null, 8);

  const routingFunction = (origin, destination,transport,payment) => {
      let onError = (error) => {
          alert(error.message);
      }
      let onResult = function(result) {
        if (result.routes.length) {
          result.routes[0].sections.forEach((section) => {
              props.onRoutingResult(section);
          });
        }
      }
      let routingParameters = {}
      if(payment == "cash"){
        routingParameters = {
          'transportMode': transport,
          'departureTime': selected+":00",
          'currency': 'JPY',
          'avoid[tollTransponders]': 'all',
          'spans': 'carAttributes',
          'return': 'summary,travelSummary,tolls,polyline,elevation'
        };
      } else {
        routingParameters = {
          'transportMode': transport,
          'departureTime': selected+":00",
          'currency': 'JPY',
          'tolls[transponders]': 'all',
          'spans': 'carAttributes',
          'return': 'summary,travelSummary,tolls,polyline,elevation'
        };
      }
      let calculateRoute = () => {
        if (!origin || !destination) return;
        routingParameters.origin = origin.lat+","+origin.lng;
        routingParameters.destination = destination.lat+","+destination.lng;
        routingService.calculateRoute(routingParameters, onResult, onError);
      }
      calculateRoute();
  };
  const onClickRouting = () => {
      searchService.geocode({
        q: text1,
        limit: 1
      }, (result) => {
        result.items.forEach((item) => {
          let origin = item.position;
          props.onOriginResult(origin);
          searchService.geocode({
            q: text2,
            limit: 1
          }, (result) => {
            result.items.forEach((item) => {
              let destination = item.position;
              props.onDestinationResult(destination);
              routingFunction(origin,destination,transport,payment);
            });
          }, alert);
            });
      }, alert);
  }

  return (
    <div >
        <div >
          <label>
            Origin : 
          </label>
          &nbsp;
          <input type="text" name="origin" id="query1" value={text1}
              onChange={(event)=>setText1(event.target.value)}
              placeholder="Origin location" />
          &emsp;
          <label>
            Destination :
          </label>
          &nbsp;
          <input type="text" name="destination" id="query2" value={text2}
              onChange={(event)=>setText2(event.target.value)}
              placeholder="Destination location" />
        </div>
        <div>
        <label for="Transport type">Choose Transport Type:</label>
          <select name="transort" id="transport-select" onChange={(event)=>setTransport(event.target.value)}>
            <option value="scooter">Scooter</option>
            <option value="car" selected>Car</option>
            <option value="truck">Truck</option>
            <option value="bus">Bus</option>
          </select>          
        </div>
        <div>
        <label for="Payment type">Choose Payment Type:</label>
          <select name="payment" id="payment-select" onChange={(event)=>setPayment(event.target.value)}>
            <option value="cash" selected>Cash+Credit Card</option>
            <option value="transponder" >Transponder</option>
          </select>          
        </div>
        <div>
         <label>
            Departure :
          </label>
          &nbsp;
          <div className="form-check">
            <input type="datetime-local" value={selected}
            onChange={event => setSelected(event.target.value)}>
            </input>
　        </div>
        </div>
        <div>
          <button onClick={onClickRouting}>
              Calculate ROUTE!
          </button>
        </div>
    </div>
  );
});
export default Routing;

```
改造ポイントを以下に記載します。

### Routing APIのインプットパラメータ
日本の高速道路料金計算では大きく分けて、支払い方法を２つに分類することができます。すなわち、


| HERE支払い種別名 | 支払い種別 |
|:-----------|------------:|
| cash + credit card       | 現金またはクレジットカード        |
| transponder    | ETCまたはETC2.0      |


HERE Routing APIでは、**ETCまたはETC2.0**を指定する場合は、``tolls[transponders]=all``を指定します。逆に**現金またはクレジットカード**の場合は何も指定しません。すなわち、``tolls[transponders]``パラメータを引数として与えません。

一方で**現金またはクレジットカード**を指定する場合は、スマートICやETC専用ゲートのルートを使用しないという意味になりますので、明示的に``avoid[tollTransponders]=all``を指定して、ルート検索候補として、スマートICやETC専用ゲートが選択されないように指定します。

また、深夜割引、休日割引などを制御するために、``departureTime``も引数として指定します。

さらに、Routing API Response内に高速道路の料金計算結果を含めるために、``return``パラメータ内に``tolls``を追加します。

最後に高速道路料金とは直接関係ないのですが、**有料道路**か**一般道路**かの区別を判断する情報を得るために、``spans``パラメータ内に``carAttributes``を追加します。

```react:Routing APIのインプットパラメータ（以下上記コードからの抜粋）
      let routingParameters = {}
      if(payment == "cash"){
        routingParameters = {
          'apiKey': apikey,
          'transportMode': transport,
          'departureTime': selected+":00",
          'currency': 'JPY',
          'avoid[tollTransponders]': 'all',
          'spans': 'carAttributes',
          'return': 'summary,travelSummary,tolls,polyline,elevation'
        };
      } else {
        routingParameters = {
          'apiKey': apikey,
          'transportMode': transport,
          'departureTime': selected+":00",
          'currency': 'JPY',
          'tolls[transponders]': 'all',
          'spans': 'carAttributes',
          'return': 'summary,travelSummary,tolls,polyline,elevation'
        };
      }
      let calculateRoute = () => {
        if (!origin || !destination) return;
        routingParameters.origin = origin.lat+","+origin.lng;
        routingParameters.destination = destination.lat+","+destination.lng;
        routingService.calculateRoute(routingParameters, onResult, onError);
      }
      calculateRoute();
```
以下がRouting APIのResponseサンプルとなります。前出のTollCostコンポーネントはこちらの情報を整形し、ReactのUIコンポーネントとして形にしただけのものとなります。
```json:Routing API response サンプル
 {
        "countryCode": "JPN",
        "tollSystemRef": 1,
        "tollSystem": "NEXCO",
        "tollSystems": [
            1,
            3
        ],
        "fares": [
            {
                "id": "8477b926-8d13-4d6d-b8d2-c3666860ffed",
                "name": "NEXCO",
                "price": {
                    "type": "value",
                    "currency": "JPY",
                    "value": 11450.0
                },
                "convertedPrice": {
                    "type": "value",
                    "currency": "JPY",
                    "value": 11450.0
                },
                "reason": "toll",
                "paymentMethods": [
                    "cash",
                    "creditCard"
                ]
            }
        ],
        "tollCollectionLocations": [
            {
                "location": {
                    "lat": 35.59424,
                    "lng": 139.57352
                }
            },
            {
                "location": {
                    "lat": 34.84104,
                    "lng": 135.72367
                }
            }
        ]
    },
```


### HTMLのインプットコンポーネント 
前項のRouting APIのインプットパラメータで言及された高速道路の料金計算に必要なパラメータ群をHTMLのコンポーネントとして実装していきます。
```react:HTMLのインプットコンポーネント（上記コードからの抜粋）
    <div >
        <div >
          <label>
            Origin : 
          </label>
          &nbsp;
          <input type="text" name="origin" id="query1" value={text1}
              onChange={(event)=>setText1(event.target.value)}
              placeholder="Origin location" />
          &emsp;
          <label>
            Destination :
          </label>
          &nbsp;
          <input type="text" name="destination" id="query2" value={text2}
              onChange={(event)=>setText2(event.target.value)}
              placeholder="Destination location" />
        </div>
        <div>
        <label for="Transport type">Choose Transport Type:</label>
          <select name="transort" id="transport-select" onChange={(event)=>setTransport(event.target.value)}>
            <option value="scooter">Scooter</option>
            <option value="car" selected>Car</option>
            <option value="truck">Truck</option>
            <option value="bus">Bus</option>
          </select>          
        </div>
        <div>
        <label for="Payment type">Choose Payment Type:</label>
          <select name="payment" id="payment-select" onChange={(event)=>setPayment(event.target.value)}>
            <option value="cash" selected>Cash+Credit Card</option>
            <option value="transponder" >Transponder</option>
          </select>          
        </div>
        <div>
         <label>
            Departure :
          </label>
          &nbsp;
          <div className="form-check">
            <input type="datetime-local" value={selected}
            onChange={event => setSelected(event.target.value)}>
            </input>
　        </div>
        </div>
        <div>
          <button onClick={onClickRouting}>
              Calculate ROUTE!
          </button>
        </div>
    </div>
```
本実装では簡易的に、Transport Typeをそれぞれ、


| HERE Transport type | 車両区分 | 
|:-----------|------------:|
| scooter       | 軽自動車に該当        |
| car     | 普通車に該当      |
| bus       | 中型車に該当        |
| truck         | 大型車に該当          |

と実装していますが、

https://www.driveplaza.com/search/division/

車両区分表
https://ap47.salesforce.com/sfc/p/#100000001ffs/a/10000000QHDr/4DJg4LYHLKR8vDGwjnG920cYI2PdDA6YdaPkvCh49SI

高速道路の車両区分は、厳密には、車高、車長、車軸数など細かく設定することで、軽自動車、普通車、中型車、大型車、特大車を区分することができます。HERE Routing APIでは、これらのパラメータについても対応しており、適宜実装が可能です。詳細はHERE公式ドキュメントをご確認下さい。(vehicleパラメータ）
https://www.here.com/docs/bundle/routing-api-v8-api-reference/page/index.html#tag/Routing/operation/calculateRoutes
特筆すべき点としては、車高、車長、車軸数などのパラメータは料金計算のみに使用されるものではなく、シームレスに道路属性（大型車侵入禁止など）などを加味した上で、ルート検索が行われるため、さまざまなユースケースに応用することが可能になります。
## Map.jsの作成
前回のルート検索アプリで作成したMap.jsに対していくつか改造をしています。こちらの改造内容は高速道路の料金計算とは直接関係ないため、詳細については割愛しますが、TollCostコンポーネントがRouting APIのResponseをリスト形式に整形したのと同様に、地図上にマーカー、吹き出し表示およびポリラインオブジェクトに変換する処理を追加しています。

```react:Map.js
import * as React from 'react';
import { encode,decode } from '@here/flexpolyline';

const Map = (props) => {
  const apikey = props.apikey;
  const gps = props.origin;
  const destination = props.destination;
  const routingResponse = props.routingResponse;
  // 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.useLayoutEffect(() => {
    // `mapRef.current` will be `undefined` when this hook first runs; edge case that
    if (!mapRef.current) return;
    const H = window.H;
    function addMarkerToGroup(group, coordinate,html) {
      let marker = new H.map.Marker(coordinate);
      // add custom data to the marker
      marker.setData(html);
      group.addObject(marker);
    }
    function addInfoBubble(map,location,fare,toll) {
      let group = new H.map.Group();
    
      map.addObject(group,location,fare);    
      // add 'tap' event listener, that opens info bubble, to the group
      group.addEventListener('tap', (evt)=> {
        // event target is the marker itself, group is a parent event target
        // for all objects that it contains
        var bubble = new H.ui.InfoBubble(evt.target.getGeometry(), {
          // read custom data
          content: evt.target.getData()
        });
        // show info bubble
        ui.addBubble(bubble);
      }, false);
      if(fare.transponders){
        const systemName = [];
        fare.transponders.map(system=>(
          systemName.push("<div><b>System</b>: "+ system.system +"</div>")
        ));
        let transponders = "";
        systemName.map(system=>{
          transponders = transponders+system;
        });

        let html = '<div><a><b>Toll System Name</a></b></div>' +
          '<div>'+toll.tollSystem+'<div><b><a>Location</a></b></div>' +
          '<div>'+location.lat+','+location.lng+'<div><b><a>Price</a></b></div>' +
          '<div>'+fare.convertedPrice.value+'</div>' +'<div><b><a>paymentMethods</a></b></div>' +
          '<div>'+fare.paymentMethods+'</div>' + transponders;
        addMarkerToGroup(group, location,html);    
      }else{
        addMarkerToGroup(group, location,'<div><b><a>Toll System Name</a></b></div>' +
          '<div>'+toll.tollSystem+'<div><b><a>Location</a></b></div>' +
          '<div>'+location.lat+','+location.lng+'<div><b><a>Price</a></b></div>' +
          '<div>'+fare.convertedPrice.value+'</div>' +'<div><b><a>paymentMethods</a></b></div>' +
          '<div>'+fare.paymentMethods+'</div>')
      }
    }
  
    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 },
    });

    // 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);
    //Zooming so that the marker can be clearly visible
    map.setZoom(16)
    const startOffSetList =[];
    const endOffSetList = [];   
    if(routingResponse) {
      let new_polylines =[]
      routingResponse.spans.map(carAttr=>{
        let startOffset = carAttr.carAttributes.map(attr=>{
          if(attr == "tollRoad"){
            return carAttr.offset;
          } else {
            return -1
          }
        })
        let endOffset = carAttr.carAttributes.map(attr=>{
          if(attr == "open"){
            return carAttr.offset;
          } else {
            return -2
          }
        })
        startOffSetList.push(startOffset[1]);
        endOffSetList.push(endOffset[0]);
      })
      startOffSetList.map(sOffset=>{
        if(sOffset != undefined){
          for (var i = 0; i < endOffSetList.length; i++) {
            if(sOffset < endOffSetList[i]) {
              let new_cordinates = decode(routingResponse.polyline).polyline.slice(sOffset,endOffSetList[i]);
              let new_polyline = encode({polyline: new_cordinates});
              new_polylines.push(new_polyline);
              break;
            }
          }
        }
      })
      let linestring = H.geo.LineString.fromFlexiblePolyline(routingResponse.polyline);
      let routeLine = new H.map.Polyline(linestring, {
        style: { strokeColor: 'blue', lineWidth: 8 }
      });
      let startMarker = new H.map.Marker(gps);
      let endMarker = new H.map.Marker(destination);
      map.addObjects([routeLine, startMarker,endMarker]);
      new_polylines.map(new_polyline=>{
        linestring = H.geo.LineString.fromFlexiblePolyline(new_polyline);
        routeLine = new H.map.Polyline(linestring, {
          style: { strokeColor: 'red', lineWidth: 4 }
        });
        map.addObject(routeLine);
      }
    )
      map.getViewModel().setLookAtData({bounds: routeLine.getBoundingBox()});
    }
    if(routingResponse != null){
      routingResponse.tolls.map(toll=>
        {
          if(toll.tollCollectionLocations!=null){
            toll.tollCollectionLocations.map(loc=>{
              if(toll.fares !=null){
                toll.fares.map(fare=>{
                    addInfoBubble(map,loc.location,fare,toll)
                })
              }
            }
            )
          }
        } 
        )
    }
    // This will act as a cleanup to run once this hook runs again.
    // This includes when the component un-mounts
    return () => {
      map.dispose();
    };
  }, [routingResponse]); // This will run this hook every time this ref is updated
return <div style={ { width: "1500px", height: "500px" } }  ref={mapRef}  />;
};
export default Map;
```
# プロジェクトの実行

```shell:shell command
npm start
```
以下の動画では、東京から御殿場のルートということで、トラック指定（大型車）でETCまたは
ETC2.0使用時に<font color="Blue">**22:00出発**</font>と<font color="Red">**0:00出発**</font>のルート検索をそれぞれ実行しています。<font color="Blue">**22:00出発では通常料金(690円)**</font>、<font color="Red">**0:00出発では深夜料金(550円)**</font>がそれぞれ適用されることを確認できます。
![Tollcost.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104985/8eff3a0e-a59e-3356-339c-bbf38fe3a448.gif)

# おわりに
いかがでしたでしょうか？高速道路の料金は長距離輸送を担う運送会社様にとって必要経費であり、物流2024年問題で様々な課題がある中、そのコスト削減もひとつの課題となっているかもしれません。今回紹介したHEREの高速道路料金計算APIにより、その課題解決として、物流DXの一端を担うことができれば幸いかと思います。

また、今回紹介したアプリは、高速道路の料金計算を示す最も単純なサンプルとなっていますが、ユースケース次第ではよりきめ細かな設定が必要になることが想定されます。たとえば、特大車のトラックが必要な場合は、トラック（大型車）の設定に対して、以下の２つのパラメータを追加する必要があります。

```diff_javascript:特大車の例
        routingParameters = {
          'apiKey': apikey,
          'transportMode': 'truck',
+         'vehcile[axleCount]': '4',
+         'vehcile[height]': '410',
          'departureTime': selected+":00",
          'currency': 'JPY',
          'tolls[transponders]': 'all',
          'spans': 'carAttributes',
          'return': 'summary,travelSummary,tolls,polyline,elevation'
        };
```

ぜひ、今回の記事を参考に、HEREの高速道路の料金計算機能をご活用頂ければ幸いです。ここまで読んでいただきありがとうございました。