Zum Inhalt

Map Matching

Dieses Beispiel gleicht eine Reihe von rohen Punkten mit dem Straßennetz ab – nützlich, um verrauschte GPS-Tracks eines Trackinggeräts zu bereinigen. Klicken Sie entlang einer Straße, um Punkte zu setzen, und gleichen Sie sie anschließend mit dem Straßennetz ab.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <script src="https://cdn.smartmaps.cloud/packages/smartmaps/smartmaps-gl/v2/umd/smartmaps-gl.min.js"></script>
        <style>
            body {
                margin: 0;
                padding: 0;
            }

            #map {
                height: 100vh;
            }

            #controls {
                position: absolute;
                top: 10px;
                left: 10px;
                z-index: 1;
                background: rgba(255, 255, 255, 0.9);
                padding: 10px;
                border-radius: 4px;
                font: 13px sans-serif;
                max-width: 240px;
            }

            #match-button,
            #reset-button {
                margin-top: 8px;
                margin-right: 6px;
                background-color: #18345c;
                color: white;
                border: none;
                padding: 8px 14px;
                cursor: pointer;
                border-radius: 4px;
            }

            #match-button:disabled {
                background-color: #9aa5b1;
                cursor: default;
            }

            .raw-point {
                width: 10px;
                height: 10px;
                border-radius: 50%;
                background: #9aa5b1;
                border: 2px solid #fff;
            }
        </style>
    </head>

    <body>
        <div id="controls">
            <strong>Click along a street to add GPS points</strong>
            <div id="status">0 points added.</div>
            <button id="match-button" disabled>Match to road</button>
            <button id="reset-button">Reset</button>
        </div>

        <div id="map"></div>

        <script>
            const map = new smartmapsgl.Map({
                apiKey: '[INSERT API-KEY]',
                container: 'map',
                center: { lat: 49.02164948779226, lng: 8.439330018049352 },
                zoom: 16,
                style: smartmapsgl.MapStyle.LIGHT
            });

            const statusEl = document.getElementById('status');
            const matchButton = document.getElementById('match-button');

            let points = [];
            let rawMarkers = [];

            function removeMatchLayer() {
                if (map.getLayer('matched-route')) map.removeLayer('matched-route');
                if (map.getSource('matched-route')) map.removeSource('matched-route');
            }

            function reset() {
                points = [];
                rawMarkers.forEach((marker) => marker.remove());
                rawMarkers = [];
                removeMatchLayer();
                matchButton.disabled = true;
                statusEl.textContent = '0 points added.';
            }

            document.getElementById('reset-button').addEventListener('click', reset);

            map.on('click', (event) => {
                points.push({ lng: event.lngLat.lng, lat: event.lngLat.lat });

                const el = document.createElement('div');
                el.className = 'raw-point';
                rawMarkers.push(new smartmapsgl.Marker({ element: el }).setLngLat(event.lngLat).addTo(map));

                statusEl.textContent = `${points.length} point${points.length === 1 ? '' : 's'} added.`;
                matchButton.disabled = points.length < 2;
            });

            matchButton.addEventListener('click', async () => {
                statusEl.textContent = 'Matching to road network…';

                try {
                    const response = await fetch(
                        'https://www.yellowmap.de/api_rst/v2/geojson/route?apiKey=[INSERT API-KEY]',
                        {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify({
                                type: 'FeatureCollection',
                                routingparams: {
                                    type: 'MATCH',
                                    isoLocale: 'en-GB',
                                    coordFormatOut: 'GEODECIMAL_POINT',
                                    speedProfile: 'FAST',
                                    routingTimeMode: 'ARRIVAL',
                                    channel: ''
                                },
                                authentication: { channel: '' },
                                crs: { type: 'name', properties: { name: 'urn:ogc:def:crs:OGC::CRS84' } },
                                features: [
                                    {
                                        type: 'Feature',
                                        geometry: {
                                            type: 'MultiPoint',
                                            coordinates: points.map((p) => [p.lng, p.lat])
                                        }
                                    }
                                ]
                            })
                        }
                    );

                    if (!response.ok) throw new Error(`Server error: ${response.status}`);

                    const result = await response.json();
                    const matched = result.features[0];
                    const destinations = matched.properties.matchDestinations;
                    const matchedCount = destinations.filter((d) => d.isMatched).length;

                    removeMatchLayer();
                    map.addSource('matched-route', { type: 'geojson', data: matched });
                    map.addLayer({
                        id: 'matched-route',
                        type: 'line',
                        source: 'matched-route',
                        layout: { 'line-join': 'round', 'line-cap': 'round' },
                        paint: { 'line-color': '#0A5FA0', 'line-width': 5 }
                    });

                    // distance/duration live on the FeatureCollection's top-level
                    // properties, NOT on the matched feature's properties
                    // (which only holds matchDestinations).
                    const km = (result.properties.distance / 1000).toFixed(2);
                    statusEl.textContent = `Matched ${matchedCount}/${destinations.length} points. Route: ${km} km.`;
                } catch (error) {
                    console.error('Error matching points:', error);
                    statusEl.textContent = 'Failed to match points. Please try again.';
                }
            });
        </script>
    </body>
</html>

Hinweise

  • Die Eingabe ist ein einzelnes MultiPoint-Feature, das alle rohen Punkte zusammenfasst – nicht ein Point-Feature pro Punkt.
  • Die Antwort enthält ein Feature: eine LineString-Geometrie für die abgeglichene Route, sowie properties.matchDestinations mit einem Eintrag pro Eingabepunkt in derselben Reihenfolge, in der sie gesendet wurden – jeweils mit isMatched, distance (Entfernung vom ursprünglichen Punkt zum abgeglichenen Punkt in Metern), inPoint und matchedPoint.
  • Ein Punkt kann nicht zugeordnet werden (isMatched: false), wenn er zu weit von jeder Straße entfernt liegt – prüfen Sie das, statt davon auszugehen, dass jeder Punkt zugeordnet wurde.

Die vollständige Parameterliste finden Sie in der Map-Matching-Referenz. Anders als Route, Trip, Isochrone und Matrix gibt es für Map Matching keine Wrapper-Klasse im @smartmaps/routing-npm-Paket – rufen Sie den REST-Endpunkt direkt auf, wie oben gezeigt.