日本国内の大型車ルーティング：MapboxとZENRIN Maps APIの実装ガイド

## はじめに

<strong>本記事の目的</strong>
この記事の目的は、MapboxとZENRIN Maps APIという2つの地図サービスを使用した大型車向けルート検索機能の実装方法を比較することです。
両サービスの特徴、使いやすさ、機能の違いを明らかにし、開発者が自身のプロジェクトに最適な選択ができるよう支援します。
<strong>対象読者</strong>
この記事は以下のような読者を対象としています。
・Webアプリケーション開発者
・地図サービスを利用したアプリケーションの開発に興味がある人
・MapboxやZENRIN Maps APIを比較検討している開発者
・日本国内での大型車向けルート検索機能を実装したい開発者

## 両サービスの概要
<strong>Mapbox</strong>
Mapboxは、カスタマイズ可能な地図とロケーションサービスを提供する世界的に有名なプラットフォームです。
<strong>特徴：</strong>
・グローバルな地図データを提供
・豊富なカスタマイズオプション
・大型車両のパラメータ（最大高さ、最大幅）を考慮したルート検索が可能
・ルート情報（距離、所要時間）の取得が容易
<strong>ZENRIN Maps API</strong>
ZENRIN Maps APIは、日本の地図会社であるゼンリンが提供する地図サービスです。
<strong>特徴：</strong>
・日本国内の詳細な地図データを提供
・日本の道路事情に特化したルート検索機能
・日本語対応が充実
・大型車両や特殊な交通規制に対応したルート検索が可能（regulation_typeパラメータを使用）
・詳細なルート情報（座標列）を取得可能

## APIキーの取得
<strong>Mapbox</strong>
<strong>アクセストークンの取得方法</strong>
1.まずは下記のページに接続してください。
[mapboxコンソール](https://console.mapbox.com/)

2.「View all tokens」をクリックしてください。
![Mapbox_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965389/c718d27e-ab74-716f-66dc-bc1c1bfc63a9.png)

3.「Create a token」をクリックして必要な情報を入力し、トークンを作成してください
![Mapbox_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965389/27e81891-0897-ada0-98f0-56b25c5d3f3b.png)
![Mapbox_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965389/6f729dfb-1d41-f6be-d630-fb2bf0a138de.png)

4.トークンの作成が完了すると「Access tokens」の画面にて、トークンが追加されるため、
　画面赤枠内のトークンを使用してください。

これで準備は完了です。
![Mapbox_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965389/f0a6369e-ef90-48da-dfd8-0e8317e951af.png)


<strong>ZENRIN Maps API</strong>
１．検証用IDとパスワード（PW）取得
ZENRIN Maps APIを使用するためには、検証用IDとPWを取得します。

１－１．検証用IDとPWの発行を依頼
[ZENRIN Maps API　無料お試しID　お申込みフォーム](https://www.zenrin-datacom.net/solution/zenrin-maps-api/trial?fm_cp=6757baf3203e3a00bb118509&fm_mu=676391081fd14c0407cd2528&utm_campaign=6757baf3203e3a00bb118509&utm_medium=else&utm_source=Qiita3)から必要事項を入力して送信します。
![ZMA_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965389/bd6879b2-4ccd-e78b-7cf5-e48d47b7ac73.png)

１－２．検証用IDとPWの確認
フォーム送信から3営業日以内にメールにて検証用IDとPWが発行されます。

２．コンソールにログイン
以下のURLでメールにて送られてきたIDとPWを入力し、[ログイン](https://test-console.zmaps-api.com/)をします。
![ZMA_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965389/3e184372-64c3-1a2d-774c-b049f73f3a4c.png)

３．ユーザーの設定
ユーザー設定にて、「コンソール管理者メールアドレス」と「情報配信用メールアドレス」を設定します。
特にコンソール管理者メールアドレスはZENRIN Maps API コンソールのパスワードを忘れて再設定が必要となった場合に必要になります。
![ZMA_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965389/a9948b72-b0c8-e751-2531-5d1153c1446b.png)

４．チャネルの認証方式の設定、チャネル名変更
チャネルの設定変更は左メニューの「チャネル設定」から行うことができます。
「チャネル設定」押下後表示される画面にはチャネルの一覧が表示されています。
設定を行うチャネルの「編集」ボタンを押下してください。
![ZMA_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965389/6e53e2f0-61f3-e67b-48f4-4454411c2fd4.png)

認証方式は3種類あり、設定したいタブを押下するとそれぞれの認証方式の設定項目が表示されます。
認証方式を有効にするには、「無効」を「有効」に変更してください。
有効にしたい認証方式が複数ある場合は、それぞれの認証方式のタブで「有効」状態に変更してください。
それぞれの設定項目を入力後「変更」ボタンを押下すると有効状態と設定項目が反映されます。
![ZMA_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965389/19bfbee6-b973-3e4b-adde-7e6319ea2b05.png)

５．APIキーの取得・API利用開始
「チャネル一覧」のグレーアウトされている箇所にマウスオーバーすると表示される「APIキー」を使い、
[APIリファレンス](https://developers.zmaps-api.com/v20/reference/)に則りAPIの利用を開始してください。

これで準備は完了です。
![ZMA_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965389/0ff5f1b3-46dd-efe6-55ce-6bdab4a2005f.png)

 ## サンプルコード
<strong>Mapbox</strong>
```Mapbox_large_route.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Mapboxで大型車ルート検索</title>
    <script src='https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.js'></script>
    <link href='https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.css' rel='stylesheet' />
    <style>
        #map { height: 500px; width: 100%; }
    </style>
</head>
<body>
    <h1>大型車ルート検索デモ</h1>
    <div id="map"></div>
    <div id="route-info"></div>
    <script>
        mapboxgl.accessToken = <accessToken>;
        const map = new mapboxgl.Map({
            container: 'map',
            style: 'mapbox://styles/mapbox/streets-v11',
            center: [139.6707547792902, 35.637085839175484],
            zoom: 15
        });

        const origin = [139.6707547792902, 35.637085839175484];
        const destination = [139.66985552394794, 35.63448502797939];

        map.on('load', function() {
            // 大型車用パラメータ
            const maxHeight = 4.5; // 単位: メートル
            const maxWidth = 2.5;  // 単位: メートル

            const url = `https://api.mapbox.com/directions/v5/mapbox/driving-traffic/${origin[0]},${origin[1]};${
                `overview=full&geometries=geojson&steps=true&exclude=toll,motorway&access_token=${mapboxgl.accessToken}` +
                `&max_height=${maxHeight}&max_width=${maxWidth}`;                

            fetch(url)
                .then(response => {
                    if (!response.ok) {
                        console.error(`Error: ${response.status} ${response.statusText}`);
                        console.error(`Request URL: ${url}`);
                        throw new Error(`HTTP error! status: ${response.status}`);
                    }
                    return response.json();
                })
                .then(data => {
                    if (!data.routes || data.routes.length === 0) {
                        throw new Error('No routes found');
                    }

                    const route = data.routes[0];
                    map.addLayer({
                        id: 'route',
                        type: 'line',
                        source: {
                            type: 'geojson',
                            data: {
                                type: 'Feature',
                                properties: {},
                                geometry: route.geometry
                            }
                        },
                        layout: {
                            'line-join': 'round',
                            'line-cap': 'round'
                        },
                        paint: {
                            'line-color': '#0000FF',
                            'line-width': 8
                        }
                    });

                    document.getElementById('route-info').innerHTML = `
                        <p>距離: ${(route.distance / 1000).toFixed(2)} km</p>
                        <p>所要時間: ${(route.duration / 60).toFixed(2)} 分</p>
                        <p>最大高さ制限: ${maxHeight} m</p>
                        <p>最大幅制限: ${maxWidth} m</p>
                    `;
                })
                .catch(error => {
                    console.error('Error:', error);
                    document.getElementById('route-info').innerHTML = '<p>ルート検索中にエラーが発生しました。</p>';
                });
        });
    </script>
</body>
</html>

```

<strong>ZENRIN Maps API</strong>
```zma_route.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ZENRIN Maps API Route Display</title>
<style>
    body { margin: 0; padding: 0; }
    #ZMap { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
<script src="https://test-js.zmaps-api.com/zma_loader.js?key=[APIキー]&auth=referer"></script>
<script src="js/zma_route.js"></script>
</head>
<body>
<div id="ZMap"></div>
</body>
</html>
```
```zma_route.js
ZMALoader.setOnLoad(function (mapOptions, error) {
    if (error) {
        console.error(error);
        return;
    }

    const mapElement = document.getElementById('ZMap');

    // 地図の初期設定
    mapOptions.center = new ZDC.LatLng(35.637085839175484, 139.6707547792902); //日本大学認定こども園
    mapOptions.zoom = 17;
    mapOptions.mouseWheelReverseZoom = true; // ★マウスホイールのズーム方向の反転を指定


    const map = new ZDC.Map(mapElement, mapOptions, function() {
        // 地図生成成功時の処理

        // コントロールを追加
        map.addControl(new ZDC.ZoomButton('top-left'));
        map.addControl(new ZDC.Compass('top-right'));
        map.addControl(new ZDC.ScaleBar('bottom-left'));
        
        const start = new ZDC.LatLng(35.637085839175484, 139.6707547792902); //日本大学認定こども園
        const end = new ZDC.LatLng(35.63448502797939, 139.66985552394794); //青葉学園野沢こども園

        const startMarker = new ZDC.Marker(start);
        const endMarker = new ZDC.Marker(end);
        map.addWidget(startMarker);
        map.addWidget(endMarker);

        const routeUrl = `https://test-web.zmaps-api.com/route/route_mbn/drive_ptp?search_type=1&from=${start.lng},${start.lat}&to=${end.lng},${end.lat}&regulation_type=121200&toll_type=large`;

        fetch(routeUrl, {
            method: 'GET',
            headers: {
                'x-api-key': '[APIキー]',
                'Authorization': 'referer'
            },
        })
        .then(response => response.json())
        .then(data => {
            if (data.status === 'OK' && data.result?.item?.length > 0) {
                const routeItem = data.result.item[0];
                const links = routeItem.route?.link;

                if (Array.isArray(links) && links.length > 0) {
                    const decodedPath = [];
                    links.forEach(link => {
                        if (Array.isArray(link.line.coordinates)) {
                            link.line.coordinates.forEach(coord => {
                                if (Array.isArray(coord) && coord.length === 2) {
                                    decodedPath.push(new ZDC.LatLng(coord[1], coord[0]));
                                }
                            });
                        }
                    });

                    console.log("デコードされた座標:", decodedPath);

                    if (decodedPath.length > 0) {
                        const routeLine = new ZDC.Polyline(
                            decodedPath,
                            {
                                color: '#008dcb',
                                width: 5,
                                opacity: 0.7
                            }
                        );
                        map.addWidget(routeLine);
                    } else {
                        console.error("デコードされたルートデータが空です。");
                    }
                } else {
                    console.error("リンクデータが存在しません。");
                }
            } else {
                console.error("ルート検索に失敗しました。レスポンス:", data);
            }
        })
        .catch(error => console.error('ルート検索エラー:', error));
    }, function() {
        console.error('地図の生成に失敗しました');
    });
});

```


 ## 地図表示
<strong>Mapbox</strong>
![Mapboxルート_野沢.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965389/fda192e1-a635-47c0-7e73-17b27d7ac018.png)
<strong>ZENRIN Maps API</strong>
[ZENRINMapsAPIルート表示](https://goldiek24.github.io/qiita-zma_largeroute_noge/zma_route.html)
![ZMA大型車_野沢.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965389/5ba1cad0-b7de-e9da-ce4f-dd13c1600777.png)

## 解説
### Mapbox
#### １．HTML基本構造
このセクションでは、HTMLの基本構造を定義しています。
Mapbox GLのJavaScriptとCSSファイルを読み込み、マップを表示するためのdiv要素と、ルート情報を表示するためのdiv要素を作成しています。
```Mapbox_large_route.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Mapboxで大型車ルート検索</title>
    <script src='https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.js'></script>
    <link href='https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.css' rel='stylesheet' />
    <style>
        #map { height: 500px; width: 100%; }
    </style>
</head>
<body>
    <h1>大型車ルート検索デモ</h1>
    <div id="map"></div>
    <div id="route-info"></div>
</body>
</html>
```

#### ２．Mapboxの初期化
このセクションでは、Mapboxのアクセストークンを設定し、新しいマップオブジェクトを作成しています。
マップの中心座標と初期ズームレベルを設定しています。
```javascript
mapboxgl.accessToken = <accessToken>;
const map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [139.6707547792902, 35.637085839175484],
    zoom: 15
});
```

#### ３．ルート検索パラメータの設定
このセクションでは、ルート検索の始点と終点を設定し、大型車用のパラメータ（最大高さと最大幅）を定義しています。
また、Mapbox Directions APIのURLを構築しています。
```javascript
const origin = [139.6707547792902, 35.637085839175484];
const destination = [139.66985552394794, 35.63448502797939];

map.on('load', function() {
    const maxHeight = 4.5; // 単位: メートル
    const maxWidth = 2.5;  // 単位: メートル

    const url = `https://api.mapbox.com/directions/v5/mapbox/driving-traffic/${origin[0]},${origin[1]};${
        destination[0]},${destination[1]}?` +
        `overview=full&geometries=geojson&steps=true&exclude=toll,motorway&access_token=${mapboxgl.accessToken}` +
        `&max_height=${maxHeight}&max_width=${maxWidth}`;

```

#### ４．ルート検索とマップへの描画
このセクションでは、Mapbox Directions APIにリクエストを送信し、レスポンスを処理しています。
取得したルート情報をマップ上に青い線で描画し、ルートの詳細情報（距離、所要時間、制限）をHTML要素に表示しています。
エラーが発生した場合は、コンソールにエラーを出力し、ユーザーにエラーメッセージを表示します。
```javascript
fetch(url)
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.json();
    })
    .then(data => {
        if (!data.routes || data.routes.length === 0) {
            throw new Error('No routes found');
        }

        const route = data.routes[0];
        map.addLayer({
            id: 'route',
            type: 'line',
            source: {
                type: 'geojson',
                data: {
                    type: 'Feature',
                    properties: {},
                    geometry: route.geometry
                }
            },
            layout: {
                'line-join': 'round',
                'line-cap': 'round'
            },
            paint: {
                'line-color': '#0000FF',
                'line-width': 8
            }
        });

        document.getElementById('route-info').innerHTML = `
            <p>距離: ${(route.distance / 1000).toFixed(2)} km</p>
            <p>所要時間: ${(route.duration / 60).toFixed(2)} 分</p>
            <p>最大高さ制限: ${maxHeight} m</p>
            <p>最大幅制限: ${maxWidth} m</p>
        `;
    })
    .catch(error => {
        console.error('Error:', error);
        document.getElementById('route-info').innerHTML = '<p>ルート検索中にエラーが発生しました。</p>';
    });
```


### ZENRIN Maps API
#### １．地図の初期化
ZENRINMapsAPIの初期化と地図の生成を行っています。ZMALoader.setOnLoad関数を使用してAPIのロードを待ち、地図のオプションを設定し、ZDC.Mapオブジェクトを作成しています。
 ```zma_route.js
 ZMALoader.setOnLoad(function (mapOptions, error) {
    if (error) {
        console.error(error);
        return;
    }

    const mapElement = document.getElementById('ZMap');

    // 地図の初期設定
    mapOptions.center = new ZDC.LatLng(35.681406, 139.767132); // 東京駅
    mapOptions.zoom = 13;

    const map = new ZDC.Map(mapElement, mapOptions, function() {
        // 地図生成成功時の処理
    }, function() {
        console.error('地図の生成に失敗しました');
    });
});
```

#### ２．地図コントロールの追加
ここでは、地図上にズームボタン、コンパス、スケールバーなどのコントロールを追加しています。これらのコントロールにより、ユーザーは地図の操作や情報の確認が容易になります。
 ```zma_route.js
 // コントロールを追加
map.addControl(new ZDC.ZoomButton('top-left'));
map.addControl(new ZDC.Compass('top-right'));
map.addControl(new ZDC.ScaleBar('bottom-left'));
 ```

#### ３．マーカーの設定
ルートの始点（日本大学認定こども園）と終点（青葉学園野沢こども園）にマーカーを設置しています。ZDC.Markerオブジェクトを作成し、map.addWidgetメソッドで地図上に追加しています。
 ```zma_route.js
        const start = new ZDC.LatLng(35.637085839175484, 139.6707547792902); //日本大学認定こども園
        const end = new ZDC.LatLng(35.63448502797939, 139.66985552394794); //青葉学園野沢こども園
const startMarker = new ZDC.Marker(start);
const endMarker = new ZDC.Marker(end);
map.addWidget(startMarker);
map.addWidget(endMarker);
 ```

#### ４．ルート検索APIリクエスト
ZENRINMapsAPIのルート検索エンドポイントにリクエストを送信しています。fetch関数を使用してHTTPリクエストを行い、APIキーと認証情報をヘッダーに含めています。
リクエストパラメータに「ルート探索用車種(regulation_type=121200)」を設定しています。
 ```zma_route.js
const routeUrl = `https://test-web.zmaps-api.com/route/route_mbn/drive_ptp?search_type=1&from=${start.lng},${start.lat}&to=${end.lng},${end.lat}&regulation_type=121200`;

fetch(routeUrl, {
    method: 'GET',
    headers: {
        'x-api-key': '[APIキー]',
        'Authorization': 'referer'
    },
})
.then(response => response.json())
.then(data => {
    // ルートデータの処理
})
.catch(error => console.error('ルート検索エラー:', error));
```

#### ５．ルートデータの処理とポリライン描画
APIから返されたルートデータを処理し、ポリラインとして地図上に描画しています。ルートの座標データをZDC.LatLngオブジェクトに変換し、ZDC.Polylineを使用してルートを描画しています。
```zma_route.js
if (data.status === 'OK' && data.result?.item?.length > 0) {
    const routeItem = data.result.item[0];
    const links = routeItem.route?.link;

    if (Array.isArray(links) && links.length > 0) {
        const decodedPath = [];
        links.forEach(link => {
            if (Array.isArray(link.line.coordinates)) {
                link.line.coordinates.forEach(coord => {
                    if (Array.isArray(coord) && coord.length === 2) {
                        decodedPath.push(new ZDC.LatLng(coord[1], coord[0]));
                    }
                });
            }
        });

        if (decodedPath.length > 0) {
            const routeLine = new ZDC.Polyline(
                decodedPath,
                {
                    color: '#008dcb',
                    width: 5,
                    opacity: 0.7
                }
            );
            map.addWidget(routeLine);
        }
    }
}
```

## API比較
### 1.初期化と地図表示
**Mapbox**
・mapboxgl.Mapオブジェクトを使用して地図を初期化
・スタイルURLを指定して地図のデザインをカスタマイズ可能
・中心座標とズームレベルを直接指定
**ZENRIN Maps API**
・ZDC.Mapオブジェクトを作成して地図を表示
・コントロール（ズームボタン、コンパス、スケールバー）を個別に追加可能

### 2. ルート検索
**Mapbox**
・Directions APIを使用
・クエリパラメータで大型車の制限（最大高さ、最大幅）を指定可能
・driving-trafficプロファイルを使用して交通状況を考慮したルート検索が可能
**ZENRIN Maps API**
・regulation_typeパラメータで大型車向けの規制を指定（例：121200）
・細かい車両指定が設定可能
・日本の道路事情（規制考慮）に特化したルート検索が可能

### 3. レスポンス処理
**Mapbox**
・GeoJSON形式のルートジオメトリを直接取得
・距離と所要時間の情報が簡単に取得可能
**ZENRIN Maps API**
・ルートの座標を個別に処理する必要がある
・より詳細なルート情報（リンクごとの座標）が取得可能

### ４．ルート表示
**Mapbox**
・map.addLayerメソッドを使用してルートを地図上に描画
・GeoJSONデータを直接使用可能
**ZENRIN Maps API**
・ZDC.Polylineオブジェクトを作成してルートを描画
・座標データを手動でZDC.LatLngオブジェクトに変換する必要がある

## まとめ
両APIは大型車向けのルート検索機能を提供していますが、使用方法と特徴に違いがあります。
・Mapboxは、グローバルな地図データと使いやすいAPIを提供し、大型車の制限を簡単に指定できます。GeoJSON形式のデータを直接扱えるため、ルートの描画が容易です。
・ZENRIN Maps APIは、日本の道路事情（規制考慮など）に特化したより詳細なルート情報を提供します。大型車向けの規制タイプを指定でき、日本国内での精度の高いルート検索が可能です。
開発者は、プロジェクトの要件（グローバル対応か日本特化か）、必要なルート情報の詳細度、APIの使いやすさなどを考慮して、適切なAPIを選択することが重要です。
Mapboxは使いやすさとグローバル対応に優れ、ZENRIN Maps APIは日本国内での精度と詳細な情報提供に強みがあります。