GoogleMapsAPIとZENRINMapsAPIの雨の日の屋根がある歩行者ルート検索をPHPで比較してみた


## 要約

本記事では、GoogleMapsAPIとZENRIN Maps APIを使用して雨の日に便利な屋根付きの歩行者ルートを検索・表示する機能をPHPで実装し、その違いを比較しました。ZENRIN Maps APIは屋根付きルートの検索が可能で、比較的簡単に実装できました。一方、Google Maps APIでは屋根付きルートの直接的な検索ができませんでした。また、PHPとJavaScriptでの実装の違いについても触れ、それぞれの特徴や利点を解説しています。両APIの特性、実装の容易さ、カスタマイズ性、コストなどを比較し、開発者がプロジェクトに適したAPIを選択する際の参考となる情報を提供しています。

## はじめに

雨の日の外出時、屋根のある経路を通ることができれば快適に目的地まで到達できます。本記事では、この「屋根付きルート」を検索・表示する機能をGoogleMapsAPIとZENRIN Maps APIを使って実装し、その過程と結果を比較します。

### 対象読者

- PHPで地図アプリケーションを開発したい方
- 経路探索に興味がある方
- GoogleMapsAPIとZENRIN Maps APIの違いを知りたい方

## ZENRIN Maps APIを使用したルート検索

### 実装概要

ZENRIN Maps APIを利用して、特定の出発地点から目的地点までの歩行者ルートを検索し、屋根付きの区間を優先的に表示する機能を実装しました。
ZENRIN Maps APIを使用するためには検証用IDとPW取得が必要です。
お試しIDは下記から簡単に発行できました。
[ZENRIN Maps API　無料お試しID　お申込みフォーム（2か月無料でお試しできます）](https://www.zenrin-datacom.net/solution/zenrin-maps-api/trial?fm_cp=6757baf3203e3a00bb118509&fm_mu=676395ae0dbf8e0512d617d9&utm_campaign=6757baf3203e3a00bb118509&utm_medium=else&utm_source=Qiita19)

詳細手順は[こちら](https://qiita.com/goldie9/items/1983bf8c4ad796290cbf)を参照しました。


### 実装コード
```php
<?php
// APIのエンドポイントURL
$routeUrl = "https://{ドメイン}/route/route_mbn/walk?search_type=4&from=139.7970,35.7106&to=139.7940,35.7136&llunit=dec&datum=JGD";

// APIキーと認証情報
$headers = [
    'x-api-key: {APIキー}',
    'Authorization: ip'
];

// cURLを使用してAPIにリクエストを送る
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $routeUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

$response = curl_exec($ch);
if ($response === false) {
    echo 'Curl error: ' . curl_error($ch);
} else {
    echo "Raw API Response:<br>";
    echo "<pre>" . htmlspecialchars($response) . "</pre>";
}
curl_close($ch);

// レスポンスをJSONとしてデコード
$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
    echo "JSON decode error: " . json_last_error_msg();
} else {
    echo "Decoded data:<br>";
    echo "<pre>" . print_r($data, true) . "</pre>";
}

// エラーチェック
if (isset($data['error'])) {
    echo "APIエラー: " . $data['error']['message'];
}

// データ構造の確認
if (isset($data['result']['item'][0]['route']['section'])) {
    $sections = $data['result']['item'][0]['route']['section'];
    echo "Sections found: " . count($sections);
} else {
    echo "Expected data structure not found";
}

// 結果がOKの場合、ルート情報を取得
$decodedPath = [];
if (isset($data['result']['item'][0]['route']['section'])) {
    $sections = $data['result']['item'][0]['route']['section'];
    foreach ($sections as $section) {
        if (isset($section['link'])) {
            foreach ($section['link'] as $link) {
                if (isset($link['line']['coordinates'])) {
                    foreach ($link['line']['coordinates'] as $coord) {
                        $decodedPath[] = [$coord[1], $coord[0]];
                    }
                }
            }
        }
    }
}
echo "Decoded path count: " . count($decodedPath);

?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ZENRIN Maps API Walk Route Display</title>
    <style>
        body { margin: 0; padding: 0; }
        #map { position: absolute; top: 0; bottom: 0; width: 100%; }
    </style>
    <script src="https://{ドメイン}/zma_loader.js?key={APIキー}&auth=ip"></script>
</head>
<body>
<div id="map"></div>
<script>
    ZMALoader.setOnLoad(function(mapOptions, error) {
        if (error) {
            console.error('ZMALoader error:', error);
            return;
        }

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

        mapOptions.center = new ZDC.LatLng(35.7106, 139.7970);
        mapOptions.zoom = 15;

        const map = new ZDC.Map(mapElement, mapOptions, function() {
            console.log('Map initialized successfully');

            // コントロールを追加
            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.7106, 139.7970);
            const end = new ZDC.LatLng(35.7136, 139.7940);

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

            // PHPからデコードされたパスを受け取る
            const decodedPath = <?php echo json_encode($decodedPath); ?>;
            console.log('Decoded path:', decodedPath);

            if (decodedPath && decodedPath.length > 0) {
                const routeCoords = decodedPath.map(coord => new ZDC.LatLng(coord[0], coord[1]));
                const routeLine = new ZDC.Polyline(routeCoords, {
                    color: '#008dcb',
                    width: 5,
                    opacity: 0.7
                });
                map.addWidget(routeLine);
                console.log('Route line added to map');

                const bounds = new ZDC.LatLngBounds();
                routeCoords.forEach(point => bounds.extend(point));
                map.fitBounds(bounds);
            } else {
                console.error('デコードされたパスが空です。APIレスポンスを確認してください。');
            }

        }, function() {
            console.error('地図の生成に失敗しました');
        });
    });
</script>
</body>
</html>

```

### 実装コード
- **結果**：ZENRIN Maps APIを使用することで、屋根付き区間を含むルートを地図上に表示することができました。

- **課題**：APIの仕様上、屋根付き区間の詳細な情報を取得することが難しく、視覚的に屋根付き区間を区別して表示することができませんでした。


## GoogleMapAPIを使用したルート検索
### 実装概要
Google Maps APIを使用して、出発地点から目的地点までの歩行者ルートを検索し、地図上に表示する機能を実装しました。

### 実装コード
```php
<?php
// APIキーを設定
$api_key = '{APIキー}';

// 出発地と目的地の座標を設定
$start = ['lat' => 35.7106, 'lng' => 139.7970];
$end = ['lat' => 35.7136, 'lng' => 139.7940];

// Google Maps Directions APIのURLを構築
$url = "https://maps.googleapis.com/maps/api/directions/json?origin={$start['lat']},{$start['lng']}&destination={$end['lat']},{$end['lng']}&mode=walking&key={$api_key}";

// APIリクエストを送信し、結果を取得
$response = file_get_contents($url);
if ($response === false) {
    die("APIからのレスポンスが取得できませんでした。");
}

$data = json_decode($response, true);
if ($data === null || !isset($data['routes'][0])) {
    die("ルート情報が見つかりませんでした。");
}

// ルート情報を抽出
$route = $data['routes'][0]['legs'][0];
$distance = $route['distance']['text'];
$duration = $route['duration']['text'];
$steps = $route['steps'];
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Google Maps APIで歩行ルート検索</title>
    <script src="https://maps.googleapis.com/maps/api/js?key=<?php echo $api_key; ?>&callback=initMap" async defer></script>
    <style>
        #map { height: 500px; width: 100%; }
    </style>
</head>
<body>
    <h1>歩行者ルート検索結果</h1>
    <p>総距離: <?php echo $distance; ?></p>
    <p>所要時間: <?php echo $duration; ?></p>
    <div id="map"></div>
    <h2>ルート詳細</h2>
    <ol>
        <?php foreach ($steps as $step): ?>
            <li><?php echo $step['html_instructions']; ?> (<?php echo $step['distance']['text']; ?>)</li>
        <?php endforeach; ?>
    </ol>
    <script>
        function initMap() {
            // マップの初期化
            const map = new google.maps.Map(document.getElementById("map"), {
                center: { lat: <?php echo $start['lat']; ?>, lng: <?php echo $start['lng']; ?> },
                zoom: 15,
            });

            const directionsService = new google.maps.DirectionsService();
            const directionsRenderer = new google.maps.DirectionsRenderer();
            directionsRenderer.setMap(map);

            const request = {
                origin: new google.maps.LatLng(<?php echo $start['lat']; ?>, <?php echo $start['lng']; ?>),
                destination: new google.maps.LatLng(<?php echo $end['lat']; ?>, <?php echo $end['lng']; ?>),
                travelMode: google.maps.TravelMode.WALKING,
            };

            directionsService.route(request, (result, status) => {
                if (status === 'OK') {
                    directionsRenderer.setDirections(result);
                } else {
                    console.error("ルート検索に失敗しました: " + status);
                }
            });
        }
    </script>
</body>
</html>
```
### 実装結果と課題
- **結果**：Google Maps APIを使用して、歩行者ルートを地図上に表示し、詳細な経路情報を提供することができました。

- **課題**：Google Maps APIでは屋根付きルートの直接的な検索ができないため、雨天時に特化したルート提案ができませんでした。

## 地図表示
- **ZENRIN Maps API**
![ZenrinMapAPI_rain.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3979666/811bc51d-a819-4363-80c6-f6c948bb02cd.png)


- **Google Maps API**
![GoogleMapAPI_rain.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3979666/d6aea2d7-7e44-4f64-8573-9e18cc73dd66.png)


### phpとJavaScriptでコーディングする違い
- **実行環境**： PHPはサーバーサイドで実行され、JavaScriptはクライアントサイド（ブラウザ）で実行されます。

- **APIリクエスト**： PHPではcURLやfile_get_contents()を使用してサーバーサイドでAPIリクエストを行いますが、JavaScriptではFetch APIやAjaxを使用してクライアントサイドでリクエストを行います。

- **データ処理**：PHPではサーバーサイドでデータを処理し、HTMLに埋め込んで送信しますが、JavaScriptではクライアントサイドでDOMを操作してデータを表示します。

- **セキュリティ**：PHPではAPIキーをサーバーサイドで管理できるため、よりセキュアですが、JavaScriptではクライアントサイドでAPIキーを使用するため、適切な対策が必要です。

### API比較
- **屋根付きルート検索**：ZENRIN Maps APIでは屋根付きルートの検索が可能ですが、Google Maps APIでは直接的な検索ができません。

- **カスタマイズ性**：Google Maps APIは豊富なドキュメントと広範なコミュニティサポートがあり、カスタマイズの幅が広いです。ZENRIN Maps APIは日本国内に特化した詳細な地図情報を提供しています。

- **使いやすさ**：Google Maps APIは世界中で広く使われており、導入しやすいです。ZENRIN Maps APIは日本国内での利用に特化しており、日本語のサポートが充実しています。

- **コスト**：両APIとも無料利用枠がありますが、大規模な利用には課金が必要です。具体的な料金体系は各APIのウェブサイトで確認してください。

### まとめ
ZENRIN Maps APIとGoogle Maps APIを使用して、雨の日の屋根付き歩行者ルート検索を実装しました。ZENRIN Maps APIは屋根付きルートの検索が可能で日本国内の詳細な地図情報を提供する一方、Google Maps APIは世界中で広く使われており、豊富なドキュメントとコミュニティサポートがあります。開発者は、プロジェクトの要件や対象地域に応じて適切なAPIを選択することが重要です。