Hand-Drawn Route Planning and Road Matching with AMap: Implementation, GPX Export, and Animation Optimization
This article presents a complete front‑end solution for hand‑drawing cycling routes on AMap, applying road‑matching correction, exporting to GPX, adding waypoint‑based route planning, and enhancing map animations with custom setCenter and setFitView functions, all illustrated with full source code.
The article introduces a feature inspired by Gaode (AMap) that allows users to draw a rough cycling route directly on the map, then automatically matches it to the road network using AMap's road‑correction API.
Technical analysis explains that while Gaode provides a route‑matching API, it lacks a direct road‑matching endpoint, so the author leverages the 线路纠偏 (road correction) service to align hand‑drawn points with actual roads.
Hand‑drawn line implementation sets up three touch events (touchstart, touchmove, touchend) to capture user strokes, disables map dragging, and builds a polyline as the user moves:
this.map.on("touchstart", (e) => {}); // prepare drawing
this.map.on("touchend", (e) => {}); // finish drawing
this.map.on("touchmove"); // drawing in progressThe core drawing logic records positions, creates markers for the start point, and updates a Polyline as more points are added:
// path array
this.path = [];
this.map.on("touchmove", _.throttle((e) => {
const position = [e.lnglat.lng, e.lnglat.lat];
if (!this.path.length) {
this.path.push(position);
new this.AMap.Marker({ map: this.map, position });
return;
}
if (this.path.length == 1) {
this.path.push(position);
this.line = new this.AMap.Polyline({
map: this.map,
path: this.path,
strokeColor: "#FF33FF",
strokeWeight: 6,
strokeOpacity: 0.5,
});
return;
}
if (this.path.length > 1) {
this.path.push(position);
this.line.setPath(this.path);
}
}, 30));To correct the hand‑drawn line, the author converts each point to a format required by the road‑correction API, calculating bearing angles with turf.bearing and assigning a constant speed:
let arr = this.path.map((item, index) => {
let angle = 0;
let tm = 1478031031;
if (this.path[index + 1]) {
const north = turf.bearing(turf.point([item[0], item[1]]), turf.point([item[0], item[1] + 1]));
angle = north < 0 ? 360 + north : north;
}
return {
x: item[0],
y: item[1],
sp: 10,
ag: Number(angle).toFixed(0),
tm: !index ? tm : 1 + index,
};
});The corrected path is obtained by calling the AMap graspRoad.driving service and rendering the result as a new polyline (blue), while the original hand‑drawn line remains green.
For exporting the route to devices or bike computers, the author uses the geojson-to-gpx library. The workflow converts the polyline to a GeoJSON LineString , then to a GPX document and triggers a download:
import GeoJsonToGpx from "@dwayneparton/geojson-to-gpx";
const geoJSON = turf.lineString(this.path);
const options = { metadata: { name: "导出为GPX", author: { name: "XiaoZuoOvO" } } };
const gpxLine = GeoJsonToGpx(geoJSON, options);
const gpxString = new XMLSerializer().serializeToString(gpxLine);
const link = document.createElement("a");
link.download = "高德地图路线绘制.gpx";
link.href = window.URL.createObjectURL(new Blob([gpxString], { type: "text/xml" }));
link.click();
ElMessage.success("导出PGX成功");The article also covers full route planning with start, end, and multiple waypoints using AMap's 线路规划2.0 API together with Element‑Plus el‑autocomplete for POI search:
// JavaScript query function
const querySearch = async (queryString, cb) => {
if (!queryString) return;
const res = await inputtips(queryString);
if (res.status == "1") {
const arr = res.tips.map(item => ({
value: item.name,
name: item.name,
district: item.district,
address: item.address,
location: item.location,
}));
cb(arr);
}
};
// Planning function
const plan = async () => {
path = [];
const res = await driving({
origin: startPositoin.value,
destination: endPosition.value,
cartype: 1,
waypoints: means.value.map(i => i.location).join(";"),
});
if (res.status == "1") {
res.route.paths[0].steps.forEach(item => {
const linestring = item.polyline;
path = path.concat(linestring.split(";").map(p => {
const [lng, lat] = p.split(",");
return [Number(lng), Number(lat)];
}));
});
}
};To improve the visual experience, custom animation utilities panTo (setCenter) and setFitView are provided. They interpolate zoom, pitch, rotation, and center using Bézier curves and stop automatically if the user interacts with the map.
export function panTo(center, map, loca) { /* ...animation route definition... */ }
export function setFitView(center, zoom, map, loca) { /* ...animation route definition... */ }All source code and assets are hosted at https://github.com/zuowenwu/LineDrawPlanning.git .
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.