Сравнение маршрутов для GPX и KML

Open in CodeSandbox

В примере показано, как сравнить между собой два маршрута: найти их общие участки и отклонения.

Первый маршрут будем считать эталонным, второй - пройденным по факту. Маршруты загружаются в формате KML или GPX.

Для каждой точки пройденного маршрута вычисляется расстояние до ближайшей точки эталонного маршрута(берутся точки, с помощью которых описан маршрут). Расстояние вычисляется с помощью getClosest().

Если полученное расстояние больше заданной точности (точность задается в переменной diff), то считаем, что маршруты в этой точке не совпадают. В балуне будем выводить протяженность отклонения от эталонного маршрута.

Отклонения от эталонного маршрута отображаются на карте красным цветом.

Для загрузки данных используется функция geoXml.load.

  <!DOCTYPE html>

<head>
    <title>Сравнение маршрутов для GPX и KML</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <!--
        Укажите свой API-ключ. Тестовый ключ НЕ БУДЕТ работать на других сайтах.
        Получить ключ можно в Кабинете разработчика: https://developer.tech.yandex.ru/keys/
    -->
    <script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU&amp;load=package.full,vow&amp;apikey=<ваш API-ключ>" type="text/javascript"></script>
    <script src="tracks_compare.js" type="text/javascript"></script>
    <style type="text/css">
        html, body, #map {
            width: 100%;
            height: 100%;
            padding: 0;
            margin: 0;
        }
    </style>
</head>
<body>
<div id="map"></div>
</body>
</html>
ymaps.ready(init);

function init() {
    var myMap = new ymaps.Map("map", {
        center: [59.9567962610097, 30.264981955459618],
        zoom: 9,
    });
    // Ссылка на запланированный путь в формате kml/gpx.
    var originalTrackUrl = "original.gpx",
        // Ссылка на путь в формате kml/gpx, который будем сравнивать с запланированным путем.
        comparableTrackUrl = "comparable.gpx",
        // Допустимая разница в метрах.
        diff = 1;
    // Сравним два пути.
    compare(originalTrackUrl, comparableTrackUrl);

    // Метод, который добавляет два пути на карту и сравнивает их.
    function compare(originalTrackUrl, comparableTrackUrl) {
        // Создадим коллекцию участков пути, которые не совпадают с запланированным путем.
        var collection = new ymaps.GeoObjectCollection(
            {},
            {
                strokeColor: "#FF0000",
                strokeWidth: 3,
            }
        );
        // Дожидаемся загрузки файлов.
        ymaps.vow
            .all([
                ymaps.geoXml.load(originalTrackUrl),
                ymaps.geoXml.load(comparableTrackUrl),
            ])
            .then(
                function (res) {
                    // Получаем запланированный путь.
                    var originalTrack = res[0].geoObjects.get(0),
                        // Получаем сравниваемый путь.
                        comparableTrack = res[1].geoObjects.get(0);
                    // Для kml есть ещё один уровень вложенности.
                    if (!originalTrack.geometry) {
                        originalTrack = originalTrack.get(0);
                        comparableTrack = comparableTrack.get(0);
                    }
                    // Получаем геометрию запланированного пути.
                    var originalGeometry = originalTrack.geometry,
                        // Получаем геометрию для сравниваемого пути.
                        comparableGeometry = comparableTrack.geometry,
                        // Счётчик количества точек, которые не совпали с запланированным маршрутом.
                        diffPoints = 0;
                    // Изменим толщину и цвет путей, после чего добавим их на карту.
                    originalTrack.options.set({
                        strokeWidth: 3,
                        strokeColor: "#4585E6",
                    });
                    comparableTrack.options.set({
                        strokeWidth: 3,
                        strokeColor: "#4585E6",
                    });
                    myMap.geoObjects
                        .add(originalTrack)
                        .add(comparableTrack);
                    // Выставим границы карты так, чтобы отобразились сравниваемые пути.
                    myMap.setBounds(myMap.geoObjects.getBounds());

                    for (
                        var i = 0, isNotEqual, isPreviousNotEqual = false;
                        i < comparableGeometry.getLength();
                        i++
                    ) {
                        // Проверим, что от каждой точки сравниваемого пути расстояние до запланированного пути меньше допустимого.
                        isNotEqual =
                            originalGeometry.getClosest(
                                comparableGeometry.get(i)
                            ).distance > diff;

                        if (isNotEqual) {
                            // Инкрементируем счетчик количества точек, которые не совпали с запланированным маршрутом.
                            diffPoints++;
                            // Сохраняем состояние для следующей итерации.
                            isPreviousNotEqual = true;
                            // Пропускаем одну итерацию в случае несовпадения начальных точек.
                            if (i === 0) continue;
                            // Добавим в коллекцию участок пути от предыдущей точки до текущей точки, которая не лежит
                            // на запланированном маршруте.
                            collection.add(
                                new ymaps.Polyline([
                                    comparableGeometry.get(i - 1),
                                    comparableGeometry.get(i),
                                ])
                            );
                        } else if (isPreviousNotEqual) {
                            // Добавим в коллекцию участок пути от предыдущей точки, которая не лежит на запланированном маршруте,
                            // до текущей точки.
                            collection.add(
                                new ymaps.Polyline([
                                    comparableGeometry.get(i - 1),
                                    comparableGeometry.get(i),
                                ])
                            );
                            // Сохраняем состояние для следующей итерации.
                            isPreviousNotEqual = false;
                        }
                    }
                    // Добавим коллекцию на карту.
                    myMap.geoObjects.add(collection);
                    // Получим протяженность отличающихся участков.
                    var diffDistance = 0;
                    collection.each(function (obj) {
                        diffDistance += obj.geometry.getDistance();
                    });
                    diffDistance = Math.round(diffDistance);
                    // Получим протяженность сравниваемого пути.
                    var comparableDistance =
                            comparableGeometry.getDistance(),
                        // Получим сколько процентов пройдено вне запланированного пути.
                        diffDistanceRatio = Math.abs(
                            (100 * diffDistance) / comparableDistance
                        ).toFixed(1),
                        // Сформируем текст для балуна.
                        content =
                            "Красные участки - отклонения от запланированного пути. <br> Вне запланированного пути пройдено %k м (%m %).";
                    content = content
                        .replace("%k", diffDistance)
                        .replace("%m", diffDistanceRatio);
                    // Добавим новый текст для балунов всех путей.
                    originalTrack.properties.set("balloonContent", content);
                    comparableTrack.properties.set(
                        "balloonContent",
                        content
                    );
                    collection.each(function (obj) {
                        obj.properties.set("balloonContent", content);
                    });
                    // Откроем балун на запланированном пути.
                    originalTrack.balloon.open();
                },
                function (error) {
                    console.log("Ошибка: " + error);
                }
            );
    }
}