#ZENRIN Maps API と GoogleMapAPI をPHPで最適巡回ルート検索を実装してみた

## 要約
本記事では、**ZENRIN Maps API** と **GoogleMapAPI**をPHPで実装し、経路探索と最適巡回ルート検索の比較を行いました。
ZENRIN Maps APIを使用すると、複雑なアルゴリズムを実装せずに複数の経由地を最適な順序で巡るルートを簡単に取得できます。
一方、Google Maps APIも同様の機能を提供し、柔軟な地図表示が可能です。
実装例として、ZENRIN Maps APIを用いたルート検索のPHPコードと、Google Maps APIを用いたルート検索のPHPコードが紹介されています。
それぞれのAPIを使用する際の注意点や、PHPとJavaScriptでの実装の違いについても触れ、それぞれの特徴や利点を解説しています。
両APIの特性、実装の容易さ、カスタマイズ性、コストなどを比較し、開発者がプロジェクトに適したAPIを選択する際の参考となる情報を提供しています。


## はじめに
経路探索は地図アプリケーションにおける重要な機能の一つで物流や観光アプリなどで需要の高い機能です。
また、効率化が求められる業界では特に重要視されています。
この記事では、ZENRIN Maps API と GoogleMapAPI を用いて、PHPで最適巡回ルート検索を実装し、その過程と結果を比較します。

### 対象読者
- PHPで地図アプリケーションを開発したい方
- 日本国内向けアプリで地図APIの選定をしている方
- GoogleMapAPI とZENRIN Maps API の特徴・違いを知りたい方


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

### 実装概要

ZENRIN Maps API には drive_tsp エンドポイントが用意されています。
これにより、複雑なアルゴリズムを実装する必要がなく、複数の経由地を最適な順番で巡るルートを取得可能です。
- 地図の初期化
- 出発地・経由地・目的地の設定
- ZENRIN Maps API の 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=676392850dbf8e04ded60aa4&utm_campaign=6757baf3203e3a00bb118509&utm_medium=else&utm_source=Qiita13)

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

**主な機能：**
- 経由地の順序を自動で最適化
- 所要時間・総移動距離の取得
- JavaScript SDK による容易な地図表示の実装

#### 実装コード

以下は、ZENRIN Maps API を使って「新宿駅 → 経由地3つ → 東京タワー」のルートを描画し、距離・所要時間を表示するコードです。

```php
<?php
// ルート検索のための関数
function performRouteSearch($origin, $destination, $waypointString) {
    $startPoint = $origin['lng'] . ',' . $origin['lat'];
    $goalPoint = $destination['lng'] . ',' . $destination['lat'];
    $api = "https://{ドメイン}/route/route_mbn/drive_tsp";
    $apiKey = "{APIキー}";
    
    // APIリクエストパラメータの設定
    $params = [
        'key' => $apiKey,
        'auth' => 'ip',
        'search_type' => 1,
        'from' => $startPoint,
        'to' => $goalPoint,
        'waypoint' => $waypointString
    ];
    
    // URLクエリパラメータの構築
    $queryString = http_build_query($params);
    $url = $api . '?' . $queryString;
    
    // cURLを使用してAPIリクエストを送信
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    
    if (curl_errno($ch)) {
        return [
            'error' => true,
            'message' => 'cURLエラー: ' . curl_error($ch)
        ];
    }
    
    curl_close($ch);
    return json_decode($response, true);
}

// 所要時間を適切な形式で表示する関数
function convertTime($rawDuration) {
    $hours = floor($rawDuration / 60);
    $minutes = round($rawDuration % 60);
    
    if ($hours === 0 && $minutes > 0) {
        return "{$minutes}分";
    } elseif ($hours > 0 && $minutes === 0) {
        return "{$hours}時間";
    } elseif ($hours > 0 && $minutes > 0) {
        return "{$hours}時間{$minutes}分";
    } else {
        return "すぐに到着します";
    }
}

// 距離をキロメートル単位で表示する関数
function convertDistance($rawDistance) {
    return number_format($rawDistance / 1000, 1) . " km";
}

// 初期設定
$mapCenter = ['lat' => 35.68989861111111, 'lng' => 139.75450958333334];
$waypointString = '139.7966564581166,35.71480344840882,139.81071112543816,35.71017592344799,139.7729444161881,35.716797251823365';
$origin = ['lat' => 35.690881942542795, 'lng' => 139.6996382651929]; // 新宿駅
$destination = ['lat' => 35.658711231010265, 'lng' => 139.74543289660156]; // 東京タワー

// ルート検索を実行
$routeData = null;
$duration = "計算中...";
$distance = "計算中...";

// ルート情報をJavaScriptに渡すためのJSON
$routeInfo = [
    'mapCenter' => $mapCenter,
    'waypointString' => $waypointString,
    'origin' => $origin,
    'destination' => $destination
];
$routeInfoJson = json_encode($routeInfo);
?>

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Zenrin Maps Optimal Route</title>
    <script src="https://{ドメイン}/zma_loader.js?key={APIキー}&auth=ip"></script>
</head>
<body>
    <div class="map-container">
        <div id="ZMap"></div>
    </div>
    <div class="info-panel">
        <div class="info-card">
            <span class="info-label">所要時間 ⇒ <span id="time" class="info-value"><?= $duration ?></span></span>
        </div>
        <div class="info-card">
            <span class="info-label">総距離 ⇒ <span id="dist" class="info-value"><?= $distance ?></span></span>
        </div>
    </div>
    <script>
        // PHPから受け取ったルート情報
        const routeInfo = <?= $routeInfoJson ?>;
        
        // マップオブジェクトと中心座標の設定
        var map;
        let mapCenter = routeInfo.mapCenter;
        let waypointString = routeInfo.waypointString;

        // ルート情報（所要時間と距離）を表示する関数
        function showRouteInfo(rawDuration, rawDistance) {
            convertTime(rawDuration);
            convertDtn(rawDistance);
        }

        // 経由地にマーカーを表示する関数
        function showMarker(waypoints) {
            waypoints.forEach((location, index) => {
                const marker = new ZDC.Marker(
                    new ZDC.LatLng(location.lat, location.lng),
                    {
                        styleId: ZDC.MARKER_COLOR_ID_RED_L,
                        contentStyleId: ZDC[`MARKER_NUMBER_ID_${index + 1}_L`],
                    }
                );
                map.addWidget(marker);
            });
        }

        // 経由地の文字列を解析し、最適化された順序で配列を返す関数
        function parseWaypoints(waypointString, origin, destination, routeorder) {
            const coordinates = waypointString.split(",").map(Number);
            const waypoints = [origin];

            for (let i = 0; i < coordinates.length; i += 2) {
                const lng = coordinates[i];
                const lat = coordinates[i + 1];
                waypoints.push(new ZDC.LatLng(lat, lng));
            }
            optWaypts = waypointOpt(waypoints, routeorder);
            optWaypts.push(destination);
            return optWaypts;
        }

        // 経由地の順序を最適化する関数
        function waypointOpt(waypoints, routeorder) {
            let stringArray = routeorder.split(',');
            let integerArray = stringArray.map(num => parseInt(num, 10));
            integerArray = [0, ...integerArray];
            const optWaypts = [];
            for (let i = 0; i < integerArray.length; i++) {
                optWaypts.push(waypoints[integerArray[i]]);
            }
            return optWaypts
        }

        // 所要時間を適切な形式で表示する関数
        function convertTime(rawDuration) {
            const timeInfoArea = document.getElementById('time');
            if (timeInfoArea) {
                const hours = Math.floor(rawDuration / 60);
                const minutes = (rawDuration % 60).toFixed(0);

                if (hours === 0 && minutes > 0) {
                    timeInfoArea.textContent = `${minutes}分`;
                } else if (hours > 0 && minutes === 0) {
                    timeInfoArea.textContent = `${hours}時間`;
                } else if (hours > 0 && minutes > 0) {
                    timeInfoArea.textContent = `${hours}時間${minutes}分`;
                } else {
                    timeInfoArea.textContent = 'すぐに到着します';
                }
            } else {
                console.error("所要時間の表示エリアが見つかりません。");
            }
        }

        // 距離をキロメートル単位で表示する関数
        function convertDtn(rawDistance) {
            const distInfoArea = document.getElementById('dist');
            if (rawDistance && distInfoArea) {
                const distanceInKm = (rawDistance / 1000).toFixed(1);
                distInfoArea.textContent = `${distanceInKm} km`;
            } else {
                console.error("距離の表示エリアが見つかりません。");
            }
        }

        // ルート検索を実行する関数
        function performRouteSearch(origin, destination, waypointString) {
            const startPoint = `${origin.lng},${origin.lat}`;
            const goalPoint = `${destination.lng},${destination.lat}`;
            const api = "/route/route_mbn/drive_tsp";
            const params = {
                search_type: 1,
                from: startPoint,
                to: goalPoint,
                waypoint: waypointString,
            };

            try {
                map.requestAPI(api, params, function (response) {
                    if (response.ret && response.ret.status === 'OK') {
                        const route = response.ret.message.result.item[0].route;
                        const coordinates = route.link.flatMap(link =>
                            link.line.coordinates.map(coord => new ZDC.LatLng(coord[1], coord[0]))
                        );

                        const bounds = calculatePolylineBounds(coordinates);
                        if (bounds) {
                            const adjustZoom = map.getAdjustZoom(coordinates, { fix: false });
                            map.setCenter(adjustZoom.center);
                            map.setZoom(adjustZoom.zoom - 0.5);
                        }

                        const routeorder = route.waypoint_order;
                        const rawDuration = route.time;
                        const rawDistance = route.distance;
                        const waypoints = parseWaypoints(waypointString, origin, destination, routeorder);
                        showMarker(waypoints);
                        showRouteInfo(rawDuration, rawDistance);

                        const polyline = new ZDC.Polyline(coordinates, {
                            color: '#1a73e8',
                            width: 5,
                            pattern: 'solid',
                            opacity: 0.8
                        });
                        map.addWidget(polyline);
                    } else {
                        console.error("ルート検索に失敗しました。");
                    }
                });
            } catch (error) {
                console.error("ルート検索中にエラーが発生しました:", error);
            }
        }

        // ポリラインの境界を計算する関数
        function calculatePolylineBounds(polylineCoordinates) {
            if (!polylineCoordinates || polylineCoordinates.length === 0) {
                console.log('ポリラインの座標が無効です。');
            }

            let minLat = Number.POSITIVE_INFINITY;
            let maxLat = Number.NEGATIVE_INFINITY;
            let minLng = Number.POSITIVE_INFINITY;
            let maxLng = Number.NEGATIVE_INFINITY;

            polylineCoordinates.forEach(point => {
                if (point.lat < minLat) minLat = point.lat;
                if (point.lat > maxLat) maxLat = point.lat;
                if (point.lng < minLng) minLng = point.lng;
                if (point.lng > maxLng) maxLng = point.lng;
            });

            const southWest = new ZDC.LatLng(minLat, minLng);
            const northEast = new ZDC.LatLng(maxLat, maxLng);
            const bounds = new ZDC.LatLngBounds(southWest, northEast);

            return bounds;
        }

        // 地図の読み込み処理
        document.addEventListener('DOMContentLoaded', function() {
            // ZMALoaderの初期化
            ZMALoader.setOnLoad(function (mapOptions, error) {
                if (error) {
                    console.error(error);
                    return;
                }
                mapOptions.center = new ZDC.LatLng(mapCenter.lat, mapCenter.lng);
                mapOptions.zoom = 13;
                mapOptions.centerZoom = false; // 地図の中心点を中心に拡大縮小する指定
                mapOptions.mouseWheelReverseZoom = true;
                mapOptions.minZoom = 4.5;

                map = new ZDC.Map(
                    document.getElementById('ZMap'),
                    mapOptions,
                    function () {
                        const origin = new ZDC.LatLng(routeInfo.origin.lat, routeInfo.origin.lng); // 新宿駅
                        const destination = new ZDC.LatLng(routeInfo.destination.lat, routeInfo.destination.lng); // 東京タワー
                        performRouteSearch(origin, destination, waypointString);

                        map.addControl(new ZDC.ZoomButton('bottom-left'));
                        map.addControl(new ZDC.Compass('top-right'));
                        map.addControl(new ZDC.ScaleBar('bottom-left'));
                    },
                    function () {
                        console.log("APIエラー");
                    }
                );
            });
        });
    </script>
</body>
</html>
```
### 実装結果と課題

- **結果**：ZENRIN Maps API の drive_tsp を使用することで、複数の経由地を最適な順序で巡るルートを容易に取得できました。日本の道路事情に特化した精度の高いルート案内が可能でした。
- **課題**：ZENRIN Maps API の仕様上、リクエスト制限や認証の取り扱いに注意が必要で、実運用にあたってはAPIキーの保護やリクエスト回数制限の設計も考慮する必要があります。

##  GoogleMapAPI を使用したルート検索

### 実装概要

以下は主な機能です
- 経由地の順序を自動で最適化
- 所要時間・総移動距離の取得
- Google Maps JavaScript API による柔軟な地図表示の実装

#### 実装コード

以下は実装したコードです。

```php
<?php
$apiKey = "{APIキー}";
// 始点、終点、経由点の設定
$origin = [
    'lat' => 35.690881942542795,
    'lng' => 139.6996382651929  // 新宿駅
];

$destination = [
    'lat' => 35.658711231010265,
    'lng' => 139.74543289660156  // 東京タワー
];

// 経由点の座標
$waypoints = [
    ['lat' => 35.71480344840882, 'lng' => 139.7966564581166],
    ['lat' => 35.71017592344799, 'lng' => 139.81071112543816],
    ['lat' => 35.716797251823365, 'lng' => 139.7729444161881]
];

// Google Directions APIにリクエストを送信する関数
function getOptimalRoute($origin, $destination, $waypoints, $apiKey) {
    // APIエンドポイント
    $url = "https://maps.googleapis.com/maps/api/directions/json";

    // 始点と終点の準備
    $originParam = $origin['lat'] . ',' . $origin['lng'];
    $destinationParam = $destination['lat'] . ',' . $destination['lng'];

    // 経由点の準備
    $waypointsParam = "";
    foreach ($waypoints as $point) {
        if (!empty($waypointsParam)) {
            $waypointsParam .= "|";
        }
        $waypointsParam .= $point['lat'] . ',' . $point['lng'];
    }

    // APIパラメータの設定
    $params = [
        'origin' => $originParam,
        'destination' => $destinationParam,
        'waypoints' => 'optimize:true|' . $waypointsParam,
        'mode' => 'driving',
        'language' => 'ja',
        'key' => $apiKey
    ];

    // URLの構築
    $requestUrl = $url . '?' . http_build_query($params);

    // cURLオプションを追加
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $requestUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    // SSL証明書の検証を緩和する設定
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);

    // システムのデフォルトCA証明書を使用
    $caPathOrFile = ini_get('curl.cainfo');
    if (!empty($caPathOrFile)) {
        curl_setopt($ch, CURLOPT_CAINFO, $caPathOrFile);
    }

    // タイムアウト設定を追加
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);

    // ユーザーエージェントを設定
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');

    // リクエストを実行
    $response = curl_exec($ch);

    // エラーチェック
    if (curl_errno($ch)) {
        $error = curl_error($ch);
        curl_close($ch);
        return [
            'error' => true,
            'message' => 'cURLエラー: ' . $error
        ];
    }

    curl_close($ch);

    // レスポンスをデコード
    $result = json_decode($response, true);

    return $result;
}

// 所要時間をフォーマットする関数
function formatDuration($seconds) {
    $hours = floor($seconds / 3600);
    $minutes = floor(($seconds % 3600) / 60);

    if ($hours > 0 && $minutes > 0) {
        return "{$hours}時間{$minutes}分";
    } elseif ($hours > 0) {
        return "{$hours}時間";
    } elseif ($minutes > 0) {
        return "{$minutes}分";
    } else {
        return "すぐに到着します";
    }
}

// 距離をフォーマットする関数
function formatDistance($meters) {
    return number_format($meters / 1000, 1) . " km";
}

// APIからルート情報を取得
$routeData = getOptimalRoute($origin, $destination, $waypoints, $apiKey);

// エラーチェック
$error = null;
$duration = "計算中...";
$distance = "計算中...";
$waypoint_order = [];
$legs = [];

if (isset($routeData['error'])) {
    $error = $routeData['message'];
} elseif (isset($routeData['status']) && $routeData['status'] !== 'OK') {
    $error = "APIエラー: " . $routeData['status'];
} elseif (empty($routeData['routes'])) {
    $error = "ルートが見つかりませんでした。";
} else {
    // ルート情報を取得
    $route = $routeData['routes'][0];

    // 経由点の順序を取得
    if (isset($route['waypoint_order'])) {
        $waypoint_order = $route['waypoint_order'];
    }

    // 総距離と所要時間を計算
    $totalDuration = 0;
    $totalDistance = 0;
    $legs = $route['legs'];

    foreach ($route['legs'] as $leg) {
        $totalDuration += $leg['duration']['value'];
        $totalDistance += $leg['distance']['value'];
    }

    // フォーマットされた所要時間と距離
    $duration = formatDuration($totalDuration);
    $distance = formatDistance($totalDistance);
}

// JavaScriptに渡すデータの準備
$jsData = [
    'apiKey' => $apiKey,
    'origin' => $origin,
    'destination' => $destination,
    'waypoints' => $waypoints,
    'waypointOrder' => $waypoint_order,
    'duration' => $duration,
    'distance' => $distance,
    'error' => $error,
    'polyline' => isset($route['overview_polyline']['points']) ? $route['overview_polyline']['points'] : null
];
?>

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://maps.googleapis.com/maps/api/js?key=<?= $apiKey ?>&libraries=places,geometry"></script>
</head>
<body>
    <?php if ($error): ?>
    <div class="error-card">
        <strong>エラー:</strong> <?= htmlspecialchars($error) ?>
    </div>
    <?php else: ?>
    <div class="map-container">
        <div id="map"></div>
    </div>

    <div class="info-panel">
        <div class="info-card">
            <span class="info-label">所要時間 ⇒ <span id="time" class="info-value"><?= $duration ?></span></span>
        </div>
        <div class="info-card">
            <span class="info-label">総距離 ⇒ <span id="dist" class="info-value"><?= $distance ?></span></span>
        </div>
    </div>
    <?php endif; ?>

    <script>
        // PHPから受け取ったデータ
        const routeData = <?= json_encode($jsData) ?>;

        // 地図とマーカーの変数
        let map;
        let markers = [];
        let directionsService;
        let directionsRenderer;

        // 地図の初期化
        function initMap() {
            // エラーがある場合は地図を表示しない
            if (routeData.error) {
                console.error("ルート検索エラー:", routeData.error);
                return;
            }

            // 地図の作成
            map = new google.maps.Map(document.getElementById("map"), {
                center: { lat: 35.68989861111111, lng: 139.75450958333334 }, // 東京周辺
                zoom: 12,
                mapTypeControl: true,
                scrollwheel: true,
                streetViewControl: true
            });

            // Directions APIのサービスとレンダラーを初期化
            directionsService = new google.maps.DirectionsService();
            directionsRenderer = new google.maps.DirectionsRenderer({
                map: map,
                suppressMarkers: true, // デフォルトのマーカーを非表示
                polylineOptions: {
                    strokeColor: "#1a73e8",
                    strokeWeight: 5,
                    strokeOpacity: 0.8
                }
            });

            // ルートを表示
            displayRoute();
        }

        // カスタムマーカーを追加
        function addMarker(position, label, color = "red") {
            const marker = new google.maps.Marker({
                position: position,
                map: map,
                label: {
                    text: label,
                    color: "white",
                    fontWeight: "bold"
                },
                icon: {
                    path: google.maps.SymbolPath.CIRCLE,
                    fillColor: color,
                    fillOpacity: 1,
                    strokeWeight: 0,
                    scale: 12
                }
            });

            markers.push(marker);
            return marker;
        }

        // ルートを表示
        function displayRoute() {
            // 始点と終点のマーカーを追加
            addMarker(routeData.origin, "S", "green");
            addMarker(routeData.destination, "E", "blue");

            // 並べ替えられた経由点のマーカーを追加
            if (routeData.waypointOrder && routeData.waypointOrder.length > 0) {
                for (let i = 0; i < routeData.waypointOrder.length; i++) {
                    const wpIndex = routeData.waypointOrder[i];
                    addMarker(
                        routeData.waypoints[wpIndex], 
                        (i + 1).toString()
                    );
                }
            } else {
                // 経由点の順序が不明な場合は元の順序でマーカーを追加
                for (let i = 0; i < routeData.waypoints.length; i++) {
                    addMarker(
                        routeData.waypoints[i], 
                        (i + 1).toString()
                    );
                }
            }

            // 経由点のGoogleマップAPI形式に変換
            const waypointsForApi = routeData.waypoints.map(wp => {
                return {
                    location: new google.maps.LatLng(wp.lat, wp.lng),
                    stopover: true
                };
            });

            // もしPHPから返されたポリラインがある場合は直接描画
            if (routeData.polyline) {
                try {
                    const decodedPath = google.maps.geometry.encoding.decodePath(routeData.polyline);

                    const polyline = new google.maps.Polyline({
                        path: decodedPath,
                        strokeColor: "#1a73e8",
                        strokeWeight: 5,
                        strokeOpacity: 0.8,
                        map: map
                    });

                    // 地図の表示範囲を調整
                    const bounds = new google.maps.LatLngBounds();
                    decodedPath.forEach(point => bounds.extend(point));
                    map.fitBounds(bounds);
                } catch (error) {
                    console.error("ポリラインのデコードエラー:", error);
                }
            }
        }

        // 地図の読み込み
        window.onload = initMap;
    </script>
</body>
</html>
```
### 実装結果と課題

- **結果**：Google Maps Directions API を利用して、出発地、経由地、目的地の間のルートを地図上に表示できました。optimize:true オプションを使用することで、API は最適化された順序で経由地を巡るルートを計算しようとします。総距離と所要時間もAPIレスポンスから取得し、表示することができました。
- **課題**：大規模な利用の場合、ZENRIN Maps API と比較してコストが高くなる可能性があります。

## 地図表示
- **ZENRIN Maps API**
![zma_php.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3979666/9e351bb2-8635-4ae7-9052-a0d2c20341a5.png)

- **GoogleMapAPI**
![gma_php.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3979666/73eef651-4a67-42b7-b252-79760efc847f.png)


### ZENRIN Maps API と Google Maps API の最適順路ルート検索における経由ポイントの順序の違いについて
- **使用しているアルゴリズムの違い：**　ZENRIN Maps API と Google Maps API で最適順路ルート検索の結果が異なる原因として、以下の要因が考えられます。各APIプロバイダーは、独自のアルゴリズムを使用して最適ルートを計算しています。これらのアルゴリズムは、距離、時間、交通状況、道路の種類、制限速度など、さまざまな要素を考慮に入れます。アルゴリズムが異なれば、結果として得られるルートも異なる可能性があります。
- **交通状況の考慮：** リアルタイムの交通状況を考慮するかどうか、また、そのデータの精度や更新頻度が異なる場合があります。交通状況はルートの選択に大きな影響を与えるため、この違いが結果に影響を与える可能性があります。
- **データソースの違い：** 地図データ、交通情報、POI（ポイント・オブ・インタレスト）データなどのソースが異なる場合があります。ZENRIN Maps API は日本の詳細な地図データに強みがありますが、Google Maps API はグローバルなデータセットを持っています。データの違いがルートの計算に影響を与える可能性があります。



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

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

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

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

## API比較
### 実装の容易さ
ZENRIN Maps APIはdrive_tsp エンドポイントにより、最適巡回ルート検索が容易に実装出来ました。JavaScript SDK も提供しています。
Google Maps APIはDirections API のウェイポイント最適化オプション (optimize:true) を利用しました。実装にはある程度の知識が必要な印象です。
### カスタマイズ性
ZENRIN Maps APIは提供されている機能を組み合わせることで、ある程度のカスタマイズが可能です。
Google Maps APIは非常に高いです。地図のスタイル、マーカー、情報ウィンドウなど、様々な要素をカスタマイズ可能です。
### コスト
両APIとも無料利用枠がありますが、大規模な利用には課金が必要です。具体的な料金体系は各APIのウェブサイトで確認してください。


## まとめ
本記事では、ZENRIN Maps API と Google Maps API をPHPで実装し、経路探索と最適巡回ルート検索の比較を行いました。
ZENRIN Maps APIとGoogle Maps APIを使用して、最適巡回ルート検索を実装しました。ZENRIN Maps API は、最適巡回ルート検索に特化したエンドポイントを提供しており、日本国内での利用においては非常に強力です。一方、Google Maps API は、グローバルな地図データと豊富な機能を提供していますが、最適巡回ルートの精度には課題が残る場合があります。
プロジェクトの要件に応じて、これらのAPIの特性を理解し、適切なAPIを選択することが重要です。