Leaflet 是一个开源并且对移动端友好的交互式地图 JavaScript 库。 它大小仅仅只有 42 KB of JS, 并且拥有绝大部分开发者所需要的所有地图特性 。
Leaflet 简单、高效并且易用。 它可以高效的运行在桌面和移动平台, 拥有着大量的 扩展插件、 优秀的文档、简单易用的 API 和完善的案例, 以及可读性较好的 源码 。
2023 年 5 月 18 日 — Leaflet 1.9.4 正式发布!
添加高德离线地图:
const map = L.map("map").setView([28.2008247, 112.9812698], 13);
L.tileLayer("http://127.0.0.1:8000/{z}/{x}/{y}/tile.png", {
minZoom: 7,
maxZoom: 16,
attribution: "© contributors",
}).addTo(map);
以下为部署的高德地图,“ {z}/{x}/{y}/tile.png”为固定写法,其它地图类似:
http://127.0.0.1:8000/{z}/{x}/{y}/tile.png
给地图标记添加自定义事件:
const map = L.map("map").setView([28.2008247, 112.9812698], 13);
L.tileLayer("http://127.0.0.1:8000/{z}/{x}/{y}/tile.png", {
minZoom: 7,
maxZoom: 16,
attribution: "© contributors",
}).addTo(map);
const marker = L.marker([28.2008247, 112.9812698]).addTo(map);
marker.data = {
lat: "",
lon: "",
};
marker.on("click", function (e) {
console.log(e.target.data);
window.open("http://www.xxx.com", "_blank");
});
Leaflet弹框Popup中添加图片及链接:
const map = L.map("map").setView([28.2008247, 112.9812698], 13);
L.tileLayer("http://127.0.0.1:8000/{z}/{x}/{y}/tile.png", {
minZoom: 7,
maxZoom: 16,
attribution: "© contributors",
}).addTo(map);
const marker = L.marker([28.2008247, 112.9812698]).addTo(map);
marker
.bindPopup(
"<b>Hello world!</b><div><a href='http://www.baidu.com' target='_blank'><img width='110' src='https://img0.baidu.com/it/u=3620159376,1528769435&fm=253&fmt=auto&app=138&f=JPEG?w=640&h=480'/></a></div>", { permanent: true, direction: "top", offset: [0, -20] }
)
.openPopup();
Popup特点:1、如果有多个标记都添加了Popup,则一次只会有一个生效;2、可以添加链接;
Leaflet中添加标注Tooltip:
const map = L.map("map").setView([28.2008247, 112.9812698], 13);
L.tileLayer("http://127.0.0.1:8000/{z}/{x}/{y}/tile.png", {
minZoom: 7,
maxZoom: 16,
attribution: "© contributors",
}).addTo(map);
marker
.addTo(map)
.bindTooltip(
"<b>Hello world!</b><div><a href='http://www.xxx.com' target='_blank'><img width='50' src='https://img0.baidu.com/it/u=3620159376,1528769435&fm=253&fmt=auto&app=138&f=JPEG?w=640&h=480'/></a></div>",
{ permanent: true, direction: "top", offset: [0, -20] }
);
Tooltip特点: 1、多个标注可以同时显示;2、如果添加了链接,链接会不生效;
React+TypeScript实例:
import React, { useEffect, useRef } from "react";
import * as L from "leaflet";
import "leaflet/dist/leaflet.css";
const App: React.FC = () => {
const mapRef = useRef<HTMLDivElement>(null);
const points: [number, number][]= [
[28.222, 112.92],
[28.224, 112.922],
[28.226, 112.924],
[28.228, 112.926],
[28.23, 112.928],
[28.23, 112.93],
[28.2321, 112.932],
[28.234, 112.9342],
[28.2361, 112.9361],
[28.238, 112.938],
[28.24, 112.94],
[28.242, 112.942],
[28.244, 112.944],
[28.246, 112.946],
[28.248, 112.948],
[28.25, 112.95],
[28.252, 112.952],
[28.255, 112.955]
];
const draw = (points: [number, number][], width: number) => {
const map: L.Map = L.map(mapRef.current as HTMLInputElement).setView([28.23, 112.93], 13);
L.tileLayer(
"http://wprd01.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
{
minZoom: 11,
maxZoom: 16,
attribution:
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}
).addTo(map);
L.marker([28.23, 112.93]).addTo(map);
points.forEach(point => {
L.circle(point, {
color: "red",
fillColor: "red",
fillOpacity: 1,
radius: 30
}).addTo(map);
});
const circle = L.circle(points[points.length - 1], {
color: "red",
fillColor: "#f03",
fillOpacity: 0.5,
weight: 2,
radius: 1200 * width
}).addTo(map);
circle.bindPopup(`<b>半径为${width}公里</b>`);
};
useEffect(() => {
draw(points, 0.25);
}, []);
return <div ref={mapRef} style={{ height: "100vh" }}></div>;
};
export default App;
HTML实例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Leaflet</title>
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
/>
<script
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""
></script>
<style>
#map {
height: 100vh;
}
.text-icon {
color: red;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
const map = L.map("map").setView([28.23, 112.93], 13);
L.tileLayer(
"http://wprd01.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
{
minZoom: 11,
maxZoom: 16,
attribution:
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}
).addTo(map);
const marker = L.marker([28.23, 112.93]).addTo(map);
const points = [
[28.222, 112.92],
[28.224, 112.922],
[28.226, 112.924],
[28.228, 112.926],
[28.23, 112.928],
[28.23, 112.93],
[28.2321, 112.932],
[28.234, 112.9342],
[28.2361, 112.9361],
[28.238, 112.938],
[28.24, 112.94],
[28.242, 112.942],
[28.244, 112.944],
[28.246, 112.946],
[28.248, 112.948],
[28.25, 112.95],
[28.252, 112.952],
[28.255, 112.955]
];
// const myIcon = L.divIcon({
// html: "0.5km",
// className: "text-icon",
// fillColor: "red",
// iconSize: 30
// });
// 然后,使用这个icon创建一个标记并添加到地图上
// L.marker([51.5, -0.09], {
// icon: myIcon
// }).addTo(map);
let moveLocationLayerGroup = L.layerGroup().addTo(map); // 定义一个图层,用于加载移动路径
// width:半径,单位为公里
function draw(points, width) {
if (points.length === 0) {
return;
}
map.removeLayer(moveLocationLayerGroup);
moveLocationLayerGroup = L.layerGroup().addTo(map);
const marker = L.marker(points[0]).addTo(moveLocationLayerGroup);
let antPolyline = L.polyline.antPath(points, {
// 点的集合
color: "#006600",
opacity: 6,
fillColor: "#006600",
pulseColor: "#e5ffe5",
delay: 10000,
dashArray: [10, 30]
}).addTo(moveLocationLayerGroup);
// 首先,创建一个icon的实例
// const myIcon = L.icon({
// iconUrl: "images/navigate.svg", // 图标图片的URL
// iconSize: [24, 24], // 图标的大小
// iconAnchor: [15, 15], // 图标的锚点,即图标的位置应该放置在地图上的位置
// popupAnchor: [-3, -76] // 弹出框的锚点,即当你点击图标时,弹出框应该出现在哪个位置
// });
// L.marker(points[points.length - 1], { icon: myIcon }).addTo(
// moveLocationLayerGroup
// );
// L.popup()
// .setLatLng(points[points.length - 1])
// .setContent("I am a standalone popup.")
// .openOn(map);
const circle = L.circle(points[points.length - 1], {
stroke: true,
dashArray: "4",
color: "red",
fillColor: "#f03",
fillOpacity: 0.2,
weight: 1,
radius: 1200 * width
}).addTo(moveLocationLayerGroup);
L.circle(points[points.length - 1], {
color: "red",
fillColor: "red",
fillOpacity: 1,
weight: 1,
radius: 400 * width
}).addTo(moveLocationLayerGroup);
function onCircleOver(e) {
// L.popup()
// .setLatLng(points[points.length - 1])
// .setContent("半径为0.5km.")
// .openOn(map);
alert("半径为0.5km.");
}
// circle.bindLabel(`<b>半径为${width}公里</b>`);
circle.on("mouseover", onCircleOver);
}
let width = 0.25;
setInterval(function () {
points.push([28.23 + Math.random() / 20, 112.93 + Math.random() / 20]);
// width += 0.01;
draw(points, width);
}, 2000);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Leaflet</title>
<link rel="stylesheet" href="leaflet.css" />
<script src="leaflet.js"></script>
<script src="leaflet-ant-path/leaflet-ant-path.js"></script>
<script src="leaflet-trackplayer/leaflet-trackplayer.js"></script>
<style>
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
#map {
height: 100vh;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
const map = L.map("map").setView([39.898457, 116.391844], 13);
L.tileLayer(
"http://{s}.tile.osm.org/{z}/{x}/{y}.png",
{
minZoom: 7,
maxZoom: 16,
attribution:
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}
).addTo(map);
const points = [
];
const newPoints = [
[39.898457, 116.391844],
[39.898595, 116.377947],
[39.898341, 116.368001],
[39.898063, 116.357144],
[39.899095, 116.351934],
[39.905871, 116.35067],
[39.922329, 116.3498],
[39.931017, 116.349671],
[39.939104, 116.349225],
[39.942233, 116.34991],
[39.947263, 116.366892],
[39.947568, 116.387537],
[39.947764, 116.401988],
[39.947929, 116.410824],
[39.947558, 116.42674],
[39.9397, 116.427338],
[39.932404, 116.427919],
[39.923109, 116.428377],
[39.907094, 116.429583],
[39.906858, 116.41404],
[39.906622, 116.405321],
[39.906324, 116.394954],
[39.906308, 116.391264],
[39.916611, 116.390748],
];
// const myIcon = L.divIcon({
// html: "0.5km",
// className: "text-icon",
// fillColor: "red",
// iconSize: 30
// });
// 然后,使用这个icon创建一个标记并添加到地图上
// L.marker([51.5, -0.09], {
// icon: myIcon
// }).addTo(map);
let moveLocationLayerGroup = L.layerGroup().addTo(map); // 定义一个图层,用于加载移动路径
// width:半径,单位为公里
function draw(points, width) {
if (points.length === 0) {
return;
}
map.removeLayer(moveLocationLayerGroup);
moveLocationLayerGroup = L.layerGroup().addTo(map);
const marker = L.marker(points[0]).addTo(moveLocationLayerGroup);
let antPolyline = L.polyline
.antPath(points, {
// 点的集合
color: "#006600",
opacity: 6,
fillColor: "#006600",
pulseColor: "#e5ffe5",
delay: 10000,
dashArray: [10, 30],
})
.addTo(moveLocationLayerGroup);
// 首先,创建一个icon的实例
// const myIcon = L.icon({
// iconUrl: "images/navigate.svg", // 图标图片的URL
// iconSize: [24, 24], // 图标的大小
// iconAnchor: [15, 15], // 图标的锚点,即图标的位置应该放置在地图上的位置
// popupAnchor: [-3, -76] // 弹出框的锚点,即当你点击图标时,弹出框应该出现在哪个位置
// });
// L.marker(points[points.length - 1], { icon: myIcon }).addTo(
// moveLocationLayerGroup
// );
// L.popup()
// .setLatLng(points[points.length - 1])
// .setContent("I am a standalone popup.")
// .openOn(map);
const circle = L.circle(points[points.length - 1], {
stroke: true,
dashArray: "4",
color: "red",
fillColor: "#f03",
fillOpacity: 0.2,
weight: 1,
radius: 1200 * width,
}).addTo(moveLocationLayerGroup);
L.circle(points[points.length - 1], {
color: "red",
fillColor: "red",
fillOpacity: 1,
weight: 1,
radius: 400 * width,
}).addTo(moveLocationLayerGroup);
function onCircleOver(e) {
// L.popup()
// .setLatLng(points[points.length - 1])
// .setContent("半径为0.5km.")
// .openOn(map);
alert("半径为0.5km.");
}
// circle.bindLabel(`<b>半径为${width}公里</b>`);
circle.on("mouseover", onCircleOver);
}
let width = 0.25;
let index = 0;
const timer = setInterval(function () {
if (index < newPoints.length) {
points.push(newPoints[index]);
draw(points, width);
index++;
} else {
clearTimeout(timer);
}
// width += 0.01;
}, 1000);
</script>
</body>
</html>
实时展示多条动态轨迹图:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Leaflet</title>
<link rel="stylesheet" href="leaflet.css" />
<script src="leaflet.js"></script>
<script src="plugins/leaflet-ant-path.js"></script>
<script src="plugins/leaflet.rotatedMarker.js"></script>
<style>
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
#map {
height: 100vh;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
// 计算点位方向
function calculateBearing(lat1, lon1, lat2, lon2) {
const dLon = (lon2 - lon1) * (Math.PI / 180);
const y = Math.sin(dLon) * Math.cos(lat2 * (Math.PI / 180));
const x =
Math.cos(lat1 * (Math.PI / 180)) * Math.sin(lat2 * (Math.PI / 180)) -
Math.sin(lat1 * (Math.PI / 180)) *
Math.cos(lat2 * (Math.PI / 180)) *
Math.cos(dLon);
let bearing = Math.atan2(y, x) * (180 / Math.PI);
// 将方位角规范化为0到360度之间
if (bearing < 0) {
bearing = (bearing + 360) % 360;
}
return bearing;
}
const map = L.map("map").setView([28.23, 112.93], 13);
L.tileLayer(
"http://wprd01.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
{
minZoom: 11,
maxZoom: 16
// attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}
).addTo(map);
const points = [
[28.222, 112.92],
[28.224, 112.922],
[28.226, 112.924],
[28.228, 112.926],
[28.23, 112.928],
[28.23, 112.93],
[28.2321, 112.932],
[28.234, 112.9342],
[28.2361, 112.9361],
[28.238, 112.938],
[28.24, 112.94],
[28.242, 112.942],
[28.244, 112.944],
[28.246, 112.946],
[28.248, 112.948],
[28.25, 112.95],
[28.252, 112.952],
[28.255, 112.955]
];
const polylineStyleConfig = [
{
color: "#006600",
fillColor: "#006600",
pulseColor: "#e5ffe5"
},
{
color: "#0000cc",
fillColor: "#0000cc",
pulseColor: "#ccccff"
},
{
color: "#e62f00",
fillColor: "#e62f00",
pulseColor: "#ffc2b3"
},
{
color: "#8600b3",
fillColor: "#8600b3",
pulseColor: "#ecb3ff"
},
{
color: "#b35900",
fillColor: "#b35900",
pulseColor: "#ffd9b3"
},
{
color: "#ff9900",
fillColor: "#ff9900",
pulseColor: "#ffe0b3"
},
{
color: "#00b2b3",
fillColor: "#00b2b3",
pulseColor: "#b3ffff"
},
{
color: "#000000",
fillColor: "#000000",
pulseColor: "#FFFFFF"
},
{
color: "#006699",
fillColor: "#006699",
pulseColor: "#b3e6ff"
},
{
color: "#cc0099",
fillColor: "#cc0099",
pulseColor: "#ffb3ec"
}
];
const iconConfig = [
"./icon/car0.svg",
"./icon/car1.svg",
"./icon/car2.svg",
"./icon/car3.svg",
"./icon/car4.svg",
"./icon/car5.svg",
"./icon/car6.svg",
"./icon/car7.svg",
"./icon/car8.svg",
"./icon/car9.svg"
];
const layerGroups = {};
// width:半径,单位为公里
function drawPolyline(
points,
width,
polylineStyleConfig,
iconConfig,
layerGroupName
) {
if (points.length === 0) {
return;
}
if (layerGroups[layerGroupName]) {
map.removeLayer(layerGroups[layerGroupName]);
layerGroups[layerGroupName] = L.layerGroup().addTo(map);
} else {
layerGroups[layerGroupName] = L.layerGroup().addTo(map);
}
// 起始点
const marker = L.marker(points[0], {
icon: L.icon({
iconUrl: "./icon/nav.svg", // 图标图片的URL
iconSize: [32, 32], // 图标的大小
iconAnchor: [15, 15], // 图标的锚点,即图标的位置应该放置在地图上的位置
popupAnchor: [-3, -76] // 弹出框的锚点,即当你点击图标时,弹出框应该出现在哪个位置
})
}).addTo(layerGroups[layerGroupName]);
// 线
L.polyline
.antPath(points, {
// 点的集合
...polylineStyleConfig,
opacity: 6,
delay: 10000,
dashArray: [5, 30]
})
.addTo(layerGroups[layerGroupName]);
// 最新的点
L.marker(points[points.length - 1], {
icon: L.icon({
iconUrl: iconConfig,
iconSize: [30, 30], // icon的大小
iconAnchor: [16, 15], // icon的渲染的位置(相对与marker)
shadowAnchor: [0, 0], // shadow的渲染的位置(相对于marker)
popupAnchor: [0, 0] //若是绑定了popup的popup的打开位置(相对于icon)
}),
title: "test",
draggable: true,
rotationAngle:
points.length > 1
? calculateBearing(
points[points.length - 2][0],
points[points.length - 2][1],
points[points.length - 1][0],
points[points.length - 1][1]
)
: 0
}).addTo(layerGroups[layerGroupName]);
const circle = L.circle(points[points.length - 1], {
stroke: true,
dashArray: "4",
color: "red",
fillColor: "#f03",
fillOpacity: 0.2,
weight: 2,
radius: 1200 * width
}).addTo(layerGroups[layerGroupName]);
circle.on("mouseover", e => {
alert("半径为0.5km.");
});
}
let width = 0.25;
setInterval(function () {
points.push([28.23 + Math.random() / 20, 112.93 + Math.random() / 20]);
drawPolyline(points, width, polylineStyleConfig[0], iconConfig[0], "line1");
}, 2000);
</script>
</body>
</html>
转化成class:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Leaflet</title>
<link rel="stylesheet" href="leaflet.css" />
<script src="leaflet.js"></script>
<!--叶片状蚂蚁路径-->
<script src="plugins/leaflet-ant-path.js"></script>
<!--方向插件-->
<script src="plugins/leaflet.rotatedMarker.js"></script>
<style>
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
#map {
height: 100vh;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
class AntPathPolyline {
static polylineStyleConfig = [
{
color: "#006600",
fillColor: "#006600",
pulseColor: "#e5ffe5"
},
{
color: "#0000cc",
fillColor: "#0000cc",
pulseColor: "#ccccff"
},
{
color: "#e62f00",
fillColor: "#e62f00",
pulseColor: "#ffc2b3"
},
{
color: "#8600b3",
fillColor: "#8600b3",
pulseColor: "#ecb3ff"
},
{
color: "#b35900",
fillColor: "#b35900",
pulseColor: "#ffd9b3"
},
{
color: "#ff9900",
fillColor: "#ff9900",
pulseColor: "#ffe0b3"
},
{
color: "#00b2b3",
fillColor: "#00b2b3",
pulseColor: "#b3ffff"
},
{
color: "#000000",
fillColor: "#000000",
pulseColor: "#FFFFFF"
},
{
color: "#006699",
fillColor: "#006699",
pulseColor: "#b3e6ff"
},
{
color: "#cc0099",
fillColor: "#cc0099",
pulseColor: "#ffb3ec"
}
];
static carIconConfig = [
"./icons/car0.png",
"./icons/car1.png",
"./icons/car2.png",
"./icons/car3.png",
"./icons/car4.png",
"./icons/car5.png",
"./icons/car6.png",
"./icons/car7.png",
"./icons/car8.png",
"./icons/car9.png"
];
static positionIconConfig = "./icons/nav.svg";
static layerGroups = {};
// 获取当前位置方向
static calculateBearing(lat1, lon1, lat2, lon2) {
const dLon = (lon2 - lon1) * (Math.PI / 180);
const y = Math.sin(dLon) * Math.cos(lat2 * (Math.PI / 180));
const x =
Math.cos(lat1 * (Math.PI / 180)) *
Math.sin(lat2 * (Math.PI / 180)) -
Math.sin(lat1 * (Math.PI / 180)) *
Math.cos(lat2 * (Math.PI / 180)) *
Math.cos(dLon);
let bearing = Math.atan2(y, x) * (180 / Math.PI);
// 将方位角规范化为0到360度之间
if (bearing < 0) {
bearing = (bearing + 360) % 360;
}
return bearing;
}
// 生成随机字符串
static generateUniqueChar() {
// 获取当前时间戳,确保每次调用都是唯一的
const timestamp = new Date().getTime().toString(36);
// 生成一个随机数,并转换为36进制字符串
const randomValue = Math.random()
.toString(36)
.substr(2, 9);
// 返回时间戳和随机数的组合,确保唯一性
return timestamp + randomValue;
}
drawPolyline(
map,
points,
width,
polylineStyle,
carIcon,
layerGroupName
) {
if (points.length === 0) {
return;
}
if (AntPathPolyline.layerGroups[layerGroupName]) {
map.removeLayer(AntPathPolyline.layerGroups[layerGroupName]);
AntPathPolyline.layerGroups[layerGroupName] = L.layerGroup().addTo(
map
);
} else {
AntPathPolyline.layerGroups[layerGroupName] = L.layerGroup().addTo(
map
);
}
// 起始点
const marker = L.marker(points[0], {
icon: L.icon({
iconUrl: AntPathPolyline.positionIconConfig, // 图标图片的URL
iconSize: [32, 32], // 图标的大小
iconAnchor: [15, 15], // 图标的锚点,即图标的位置应该放置在地图上的位置
popupAnchor: [-3, -76] // 弹出框的锚点,即当你点击图标时,弹出框应该出现在哪个位置
})
}).addTo(AntPathPolyline.layerGroups[layerGroupName]);
// 线
L.polyline
.antPath(points, {
...polylineStyle,
opacity: 6,
delay: 10000,
dashArray: [5, 30]
})
.addTo(AntPathPolyline.layerGroups[layerGroupName]);
// 最新的点
L.marker(points[points.length - 1], {
icon: L.icon({
iconUrl: carIcon,
iconSize: [30, 30], // icon的大小
iconAnchor: [16, 15], // icon的渲染的位置(相对与marker)
shadowAnchor: [0, 0], // shadow的渲染的位置(相对于marker)
popupAnchor: [0, 0] //若是绑定了popup的popup的打开位置(相对于icon)
}),
title: "test",
draggable: true,
rotationAngle:
points.length > 1
? AntPathPolyline.calculateBearing(
points[points.length - 2][0],
points[points.length - 2][1],
points[points.length - 1][0],
points[points.length - 1][1]
)
: 0
}).addTo(AntPathPolyline.layerGroups[layerGroupName]);
const circle = L.circle(points[points.length - 1], {
stroke: true,
dashArray: "4",
color: "red",
fillColor: "#f03",
fillOpacity: 0.2,
weight: 2,
radius: 1200 * width
}).addTo(AntPathPolyline.layerGroups[layerGroupName]);
circle.on("mouseover", e => {
alert("半径为0.5km.");
});
}
}
const map = L.map("map").setView([39.898457, 116.391844], 13);
L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
minZoom: 7,
maxZoom: 16,
attribution:
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
const points1 = [];
const points2 = [];
const newPoints1 = [
[39.898457, 116.391844],
[39.898595, 116.377947],
[39.898341, 116.368001],
[39.898063, 116.357144],
[39.899095, 116.351934],
[39.905871, 116.35067],
[39.922329, 116.3498],
[39.931017, 116.349671],
[39.939104, 116.349225],
[39.942233, 116.34991],
[39.947263, 116.366892],
[39.947568, 116.387537],
[39.947764, 116.401988],
[39.947929, 116.410824],
[39.947558, 116.42674],
[39.9397, 116.427338],
[39.932404, 116.427919],
[39.923109, 116.428377],
[39.907094, 116.429583],
[39.906858, 116.41404],
[39.906622, 116.405321],
[39.906324, 116.394954],
[39.906308, 116.391264],
[39.916611, 116.390748]
];
const newPoints2 = JSON.parse(JSON.stringify(newPoints1)).reverse();
const test1 = new AntPathPolyline();
const testName1 = AntPathPolyline.generateUniqueChar();
const test2 = new AntPathPolyline();
const testName2 = AntPathPolyline.generateUniqueChar();
let width = 0.25;
let index = 0;
const timer = setInterval(function () {
if (index < newPoints1.length) {
points1.push(newPoints1[index]);
points2.push(newPoints2[index]);
test1.drawPolyline(
map,
points1,
width,
AntPathPolyline.polylineStyleConfig[0],
AntPathPolyline.carIconConfig[0],
testName1
);
test2.drawPolyline(
map,
points2,
width,
AntPathPolyline.polylineStyleConfig[1],
AntPathPolyline.carIconConfig[1],
testName2
);
index++;
} else {
clearTimeout(timer);
}
}, 1000);
console.log(AntPathPolyline.generateUniqueChar());
// 叶片状蚂蚁路径效果:https://blog.csdn.net/gitblog_00009/article/details/137951724
// 叶片状蚂蚁路径插件:https://gitcode.com/rubenspgcavalcante/leaflet-ant-path/overview?utm_source=artical_gitcode&isLogin=1
// 使用Leaflet.rotatedMaker进行航班飞行航向模拟的实践:https://blog.csdn.net/yelangkingwuzuhu/article/details/137154712
// 方向插件:https://gitee.com/mirrors_bbecquet/Leaflet.RotatedMarker
// Leaflet 带箭头轨迹以及沿轨迹带方向的动态marker:https://segmentfault.com/a/1190000039319512?sort=votes
</script>
</body>
</html>
在vue3中实现:
package.json:
{
"name": "leaflet-vue3",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"leaflet": "^1.9.4",
"leaflet-ant-path": "^1.3.0",
"leaflet-rotatedmarker": "^0.2.0",
"leaflet-trackplayer": "^2.0.2",
"vue": "^3.4.29"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.5",
"vite": "^5.3.1"
}
}
/src/tools/AntPathPolyline.js
import { antPath } from 'leaflet-ant-path';
import 'leaflet-rotatedmarker'
class AntPathPolyline {
static polylineStyleConfig = [
{
color: "#006600",
fillColor: "#006600",
pulseColor: "#e5ffe5"
},
{
color: "#0000cc",
fillColor: "#0000cc",
pulseColor: "#ccccff"
},
{
color: "#e62f00",
fillColor: "#e62f00",
pulseColor: "#ffc2b3"
},
{
color: "#8600b3",
fillColor: "#8600b3",
pulseColor: "#ecb3ff"
},
{
color: "#b35900",
fillColor: "#b35900",
pulseColor: "#ffd9b3"
},
{
color: "#ff9900",
fillColor: "#ff9900",
pulseColor: "#ffe0b3"
},
{
color: "#00b2b3",
fillColor: "#00b2b3",
pulseColor: "#b3ffff"
},
{
color: "#000000",
fillColor: "#000000",
pulseColor: "#FFFFFF"
},
{
color: "#006699",
fillColor: "#006699",
pulseColor: "#b3e6ff"
},
{
color: "#cc0099",
fillColor: "#cc0099",
pulseColor: "#ffb3ec"
}
];
static carIconConfig = [
"src/assets/icons/car0.png",
"src/assets/icons/car1.png",
"src/assets/icons/car2.png",
"src/assets/icons/car3.png",
"src/assets/icons/car4.png",
"src/assets/icons/car5.png",
"src/assets/icons/car6.png",
"src/assets/icons/car7.png",
"src/assets/icons/car8.png",
"src/assets/icons/car9.png"
];
static positionIconConfig = "src/assets/icons/nav.svg";
static layerGroups = {};
// 获取当前位置方向
static calculateBearing(lat1, lon1, lat2, lon2) {
const dLon = (lon2 - lon1) * (Math.PI / 180);
const y = Math.sin(dLon) * Math.cos(lat2 * (Math.PI / 180));
const x =
Math.cos(lat1 * (Math.PI / 180)) *
Math.sin(lat2 * (Math.PI / 180)) -
Math.sin(lat1 * (Math.PI / 180)) *
Math.cos(lat2 * (Math.PI / 180)) *
Math.cos(dLon);
let bearing = Math.atan2(y, x) * (180 / Math.PI);
// 将方位角规范化为0到360度之间
if (bearing < 0) {
bearing = (bearing + 360) % 360;
}
return bearing;
}
// 生成随机字符串
static generateUniqueChar() {
// 获取当前时间戳,确保每次调用都是唯一的
const timestamp = new Date().getTime().toString(36);
// 生成一个随机数,并转换为36进制字符串
const randomValue = Math.random()
.toString(36)
.substr(2, 9);
// 返回时间戳和随机数的组合,确保唯一性
return timestamp + randomValue;
}
drawPolyline(
map,
points,
width,
polylineStyle,
carIcon,
layerGroupName
) {
if (points.length === 0) {
return;
}
if (AntPathPolyline.layerGroups[layerGroupName]) {
map.removeLayer(AntPathPolyline.layerGroups[layerGroupName]);
AntPathPolyline.layerGroups[layerGroupName] = L.layerGroup().addTo(
map
);
} else {
AntPathPolyline.layerGroups[layerGroupName] = L.layerGroup().addTo(
map
);
}
// 起始点
const marker = L.marker(points[0], {
icon: L.icon({
iconUrl: AntPathPolyline.positionIconConfig, // 图标图片的URL
iconSize: [32, 32], // 图标的大小
iconAnchor: [15, 15], // 图标的锚点,即图标的位置应该放置在地图上的位置
popupAnchor: [-3, -76] // 弹出框的锚点,即当你点击图标时,弹出框应该出现在哪个位置
})
}).addTo(AntPathPolyline.layerGroups[layerGroupName]);
// 线
L.polyline
.antPath(points, {
...polylineStyle,
opacity: 6,
delay: 10000,
dashArray: [5, 30]
})
.addTo(AntPathPolyline.layerGroups[layerGroupName]);
// 最新的点
L.marker(points[points.length - 1], {
icon: L.icon({
iconUrl: carIcon,
iconSize: [30, 30], // icon的大小
iconAnchor: [16, 15], // icon的渲染的位置(相对与marker)
shadowAnchor: [0, 0], // shadow的渲染的位置(相对于marker)
popupAnchor: [0, 0] //若是绑定了popup的popup的打开位置(相对于icon)
}),
title: "test",
draggable: true,
rotationAngle:
points.length > 1
? AntPathPolyline.calculateBearing(
points[points.length - 2][0],
points[points.length - 2][1],
points[points.length - 1][0],
points[points.length - 1][1]
)
: 0
}).addTo(AntPathPolyline.layerGroups[layerGroupName]);
const circle = L.circle(points[points.length - 1], {
stroke: true,
dashArray: "4",
color: "red",
fillColor: "#f03",
fillOpacity: 0.2,
weight: 2,
radius: 1200 * width
}).addTo(AntPathPolyline.layerGroups[layerGroupName]);
circle.on("mouseover", e => {
alert("半径为0.5km.");
});
}
}
export default AntPathPolyline;
src/App.vue
<template>
<div id="map"></div>
</template>
<script setup>
import L from "leaflet";
import "leaflet/dist/leaflet.css";
import { onMounted } from "vue";
import AntPathPolyline from "./tools/AntPathPolyline";
onMounted(() => {
const map = L.map("map").setView([39.898457, 116.391844], 13);
L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
minZoom: 7,
maxZoom: 16,
attribution:
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
const points1 = [];
const points2 = [];
const newPoints1 = [
[39.898457, 116.391844],
[39.898595, 116.377947],
[39.898341, 116.368001],
[39.898063, 116.357144],
[39.899095, 116.351934],
[39.905871, 116.35067],
[39.922329, 116.3498],
[39.931017, 116.349671],
[39.939104, 116.349225],
[39.942233, 116.34991],
[39.947263, 116.366892],
[39.947568, 116.387537],
[39.947764, 116.401988],
[39.947929, 116.410824],
[39.947558, 116.42674],
[39.9397, 116.427338],
[39.932404, 116.427919],
[39.923109, 116.428377],
[39.907094, 116.429583],
[39.906858, 116.41404],
[39.906622, 116.405321],
[39.906324, 116.394954],
[39.906308, 116.391264],
[39.916611, 116.390748],
];
const newPoints2 = JSON.parse(JSON.stringify(newPoints1)).reverse();
const test1 = new AntPathPolyline();
const testName1 = AntPathPolyline.generateUniqueChar();
const test2 = new AntPathPolyline();
const testName2 = AntPathPolyline.generateUniqueChar();
let width = 0.25;
let index = 0;
const timer = setInterval(function () {
if (index < newPoints1.length) {
points1.push(newPoints1[index]);
points2.push(newPoints2[index]);
test1.drawPolyline(
map,
points1,
width,
AntPathPolyline.polylineStyleConfig[0],
AntPathPolyline.carIconConfig[0],
testName1
);
test2.drawPolyline(
map,
points2,
width,
AntPathPolyline.polylineStyleConfig[1],
AntPathPolyline.carIconConfig[1],
testName2
);
index++;
} else {
clearTimeout(timer);
}
}, 1000);
});
</script>
<style scoped>
#map {
height: 100% !important;
width: 100% !important;
}
</style>
添加操作功能:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Leaflet</title>
<link rel="stylesheet" href="leaflet.css" />
<script src="leaflet.js"></script>
<!--叶片状蚂蚁路径-->
<script src="plugins/leaflet-ant-path.js"></script>
<!--方向插件-->
<script src="plugins/leaflet.rotatedMarker.js"></script>
<style>
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
#search {
height: 60px;
width: 100%;
position: fixed;
top: 0;
left: 0;
background: #000000;
z-index: 10000000;
}
#search-btn,
#stop-btn {
cursor: pointer;
}
#map {
height: 100vh;
}
</style>
</head>
<body>
<div id="search">
<input id="search-input" />
<button id="search-btn">搜索</button>
<button id="stop-btn">停止</button>
</div>
<div id="map"></div>
<script>
class AntPathPolyline {
static polylineStyleConfig = [
{
color: "#006600",
fillColor: "#006600",
pulseColor: "#e5ffe5"
},
{
color: "#0000cc",
fillColor: "#0000cc",
pulseColor: "#ccccff"
},
{
color: "#e62f00",
fillColor: "#e62f00",
pulseColor: "#ffc2b3"
},
{
color: "#8600b3",
fillColor: "#8600b3",
pulseColor: "#ecb3ff"
},
{
color: "#b35900",
fillColor: "#b35900",
pulseColor: "#ffd9b3"
},
{
color: "#ff9900",
fillColor: "#ff9900",
pulseColor: "#ffe0b3"
},
{
color: "#00b2b3",
fillColor: "#00b2b3",
pulseColor: "#b3ffff"
},
{
color: "#000000",
fillColor: "#000000",
pulseColor: "#FFFFFF"
},
{
color: "#006699",
fillColor: "#006699",
pulseColor: "#b3e6ff"
},
{
color: "#cc0099",
fillColor: "#cc0099",
pulseColor: "#ffb3ec"
}
];
static carIconConfig = [
"./icons/car0.png",
"./icons/car1.png",
"./icons/car2.png",
"./icons/car3.png",
"./icons/car4.png",
"./icons/car5.png",
"./icons/car6.png",
"./icons/car7.png",
"./icons/car8.png",
"./icons/car9.png"
];
static positionIconConfig = "./icons/nav.svg";
static layerGroups = {};
// 获取当前位置方向
static calculateBearing(lat1, lon1, lat2, lon2) {
const dLon = (lon2 - lon1) * (Math.PI / 180);
const y = Math.sin(dLon) * Math.cos(lat2 * (Math.PI / 180));
const x =
Math.cos(lat1 * (Math.PI / 180)) *
Math.sin(lat2 * (Math.PI / 180)) -
Math.sin(lat1 * (Math.PI / 180)) *
Math.cos(lat2 * (Math.PI / 180)) *
Math.cos(dLon);
let bearing = Math.atan2(y, x) * (180 / Math.PI);
// 将方位角规范化为0到360度之间
if (bearing < 0) {
bearing = (bearing + 360) % 360;
}
return bearing;
}
// 生成随机字符串
static generateUniqueChar() {
// 获取当前时间戳,确保每次调用都是唯一的
const timestamp = new Date().getTime().toString(36);
// 生成一个随机数,并转换为36进制字符串
const randomValue = Math.random()
.toString(36)
.substr(2, 9);
// 返回时间戳和随机数的组合,确保唯一性
return timestamp + randomValue;
}
drawPolyline(
map,
points,
width,
polylineStyle,
carIcon,
layerGroupName
) {
if (points.length === 0) {
return;
}
if (AntPathPolyline.layerGroups[layerGroupName]) {
map.removeLayer(AntPathPolyline.layerGroups[layerGroupName]);
AntPathPolyline.layerGroups[layerGroupName] = L.layerGroup().addTo(
map
);
} else {
AntPathPolyline.layerGroups[layerGroupName] = L.layerGroup().addTo(
map
);
}
// 起始点
const marker = L.marker(points[0], {
icon: L.icon({
iconUrl: AntPathPolyline.positionIconConfig, // 图标图片的URL
iconSize: [32, 32], // 图标的大小
iconAnchor: [15, 15], // 图标的锚点,即图标的位置应该放置在地图上的位置
popupAnchor: [-3, -76] // 弹出框的锚点,即当你点击图标时,弹出框应该出现在哪个位置
})
}).addTo(AntPathPolyline.layerGroups[layerGroupName]);
// 线
L.polyline
.antPath(points, {
...polylineStyle,
opacity: 6,
delay: 10000,
dashArray: [5, 30]
})
.addTo(AntPathPolyline.layerGroups[layerGroupName]);
// 最新的点
L.marker(points[points.length - 1], {
icon: L.icon({
iconUrl: carIcon,
iconSize: [30, 30], // icon的大小
iconAnchor: [16, 15], // icon的渲染的位置(相对与marker)
shadowAnchor: [0, 0], // shadow的渲染的位置(相对于marker)
popupAnchor: [0, 0] //若是绑定了popup的popup的打开位置(相对于icon)
}),
title: "test",
draggable: true,
rotationAngle:
points.length > 1
? AntPathPolyline.calculateBearing(
points[points.length - 2][0],
points[points.length - 2][1],
points[points.length - 1][0],
points[points.length - 1][1]
)
: 0
}).addTo(AntPathPolyline.layerGroups[layerGroupName]);
const circle = L.circle(points[points.length - 1], {
stroke: true,
dashArray: "4",
color: "red",
fillColor: "#f03",
fillOpacity: 0.2,
weight: 2,
radius: 1200 * width
}).addTo(AntPathPolyline.layerGroups[layerGroupName]);
circle.on("mouseover", e => {
alert("半径为0.5km.");
});
}
}
const map = L.map("map").setView([28.23, 112.93], 13);
L.tileLayer(
"http://wprd01.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
{
minZoom: 11,
maxZoom: 16
// attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}
).addTo(map);
let width = 0.25;
let timers = [];
let points = {};
document.getElementById("search-btn").onclick = function() {
const allLayers = map._layers; // 注意:_layers是一个内部属性,可能在未来版本中改变
// 遍历所有图层,并检查它们是否是L.layerGroup的实例
for (const layerId in allLayers) {
const layer = allLayers[layerId];
// 检查图层是否是L.layerGroup的实例
if (layer instanceof L.LayerGroup) {
// 如果是,则从地图上移除它
map.removeLayer(layer);
}
// 注意:对于嵌套的layerGroup(即layerGroup中包含其他layerGroup),
// 你可能需要递归地处理它们,但上面的代码只处理了一级layerGroup。
}
if (timers.length > 0) {
for (let i = 0; i < timers.length; i++) {
clearInterval(timers[i]);
}
}
timers = [];
points = {};
const polylineArr = ["a", "b", "c", "d", "e"];
for (let i = 0; i < polylineArr.length; i++) {
const antPathPolylineObj = new AntPathPolyline();
const antPathPolylineName = AntPathPolyline.generateUniqueChar();
points["data" + i] = [];
const timer = setInterval(function() {
points["data" + i].push([
28.23 + ((1 + i) * Math.random()) / 20,
112.93 + ((1 + i) * Math.random()) / 20
]);
antPathPolylineObj.drawPolyline(
map,
points["data" + i],
width,
AntPathPolyline.polylineStyleConfig[i],
AntPathPolyline.carIconConfig[i],
antPathPolylineName
);
}, 1000);
timers.push(timer);
}
};
document.getElementById("stop-btn").onclick = function() {
if (timers.length > 0) {
for (let i = 0; i < timers.length; i++) {
clearInterval(timers[i]);
}
}
};
</script>
</body>
</html>
Leaflet 带箭头轨迹以及沿轨迹带方向的动态marker:
<!DOCTYPE html>
<html>
<head>
<title>Leaflet.RouteAnimate</title>
<meta charset="utf-8" />
<!-- 引入leafletapi -->
<link rel="stylesheet" href="./leaflet.css" />
<script src="./leaflet.js"></script>
<!-- 引入插件 -->
<script src="./plugins/leaflet.polylineDecorator.js"></script>
<script src="./plugins/Leaflet.AnimatedMarker.js"></script>
<style>
body {
margin: 0;
}
.map {
position: absolute;
height: 100%;
right: 0;
left: 0;
}
.menuBar {
position: relative;
text-align: center;
top: 10px;
margin: 0 50px;
padding: 5px;
border-radius: 3px;
z-index: 999;
color: #ffffff;
background-color: rgba(0, 168, 0, 1);
}
</style>
</head>
<body>
<div class="map" id="map"></div>
<div class="menuBar">
<input type="button" value="开始" onclick="startClick()" />
<input type="button" value="暂停" onclick="pauseClick()" />
<input type="button" value="加速" onclick="speetUp()" />
<input type="button" value="减速" onclick="speetDown()" />
<input type="button" value="停止" onclick="stopClick()" />
</div>
</body>
<script>
// 初始化地图
var map = L.map("map", {
center: [39.924317, 116.390619],
zoom: 14,
preferCanvas: true // 使用canvas模式渲染矢量图形
});
// 添加底图
var tiles = L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png").addTo(
map
);
var latlngs = [
[39.898457, 116.391844],
[39.898595, 116.377947],
[39.898341, 116.368001],
[39.898063, 116.357144],
[39.899095, 116.351934],
[39.905871, 116.35067],
[39.922329, 116.3498],
[39.931017, 116.349671],
[39.939104, 116.349225],
[39.942233, 116.34991],
[39.947263, 116.366892],
[39.947568, 116.387537],
[39.947764, 116.401988],
[39.947929, 116.410824],
[39.947558, 116.42674],
[39.9397, 116.427338],
[39.932404, 116.427919],
[39.923109, 116.428377],
[39.907094, 116.429583],
[39.906858, 116.41404],
[39.906622, 116.405321],
[39.906324, 116.394954],
[39.906308, 116.391264],
[39.916611, 116.390748]
];
var speedList = [
1,
1,
2,
2,
3,
3,
3,
4,
4,
4,
4,
4,
4,
5,
5,
4,
4,
4,
3,
2,
2,
1,
1,
1
];
// 轨迹线
var routeLine = L.polyline(latlngs, {
weight: 8
}).addTo(map);
// 实时轨迹线
var realRouteLine = L.polyline([], {
weight: 8,
color: "#FF9900"
}).addTo(map);
// 轨迹方向箭头
var decorator = L.polylineDecorator(routeLine, {
patterns: [
{
repeat: 50,
symbol: L.Symbol.arrowHead({
pixelSize: 5,
headAngle: 75,
polygon: false,
pathOptions: {
stroke: true,
weight: 2,
color: "#FFFFFF"
}
})
}
]
}).addTo(map);
var carIcon = L.icon({
iconSize: [37, 26],
iconAnchor: [19, 13],
iconUrl: "./icons/car.png"
});
// 动态marker
var animatedMarker = L.animatedMarker(routeLine.getLatLngs(), {
speedList: speedList,
interval: 200, // 默认为100mm
icon: carIcon,
playCall: updateRealLine
}).addTo(map);
var newLatlngs = [routeLine.getLatLngs()[0]];
// 绘制已行走轨迹线(橙色那条)
function updateRealLine(latlng) {
newLatlngs.push(latlng);
realRouteLine.setLatLngs(newLatlngs);
}
let speetX = 1; // 默认速度倍数
// 加速
function speetUp() {
speetX = speetX * 2;
animatedMarker.setSpeetX(speetX);
}
// 减速
function speetDown() {
speetX = speetX / 2;
animatedMarker.setSpeetX(speetX);
}
// 开始
function startClick() {
animatedMarker.start();
}
// 暂停
function pauseClick() {
animatedMarker.pause();
}
// 停止
function stopClick() {
newLatlngs = [];
animatedMarker.stop();
}
</script>
</html>
轨迹回放效果&控制台控制轨迹运动效果
App.vue
<template>
<div id="map"></div>
</template>
<script setup lang="ts">
import { onMounted } from "vue";
import L from "leaflet";
import "leaflet-trackplayer";
import "leaflet/dist/leaflet.css";
import CAR from "@/assets/icons/car0.png";
import path from "@/tools/points";
onMounted(() => {
let map = null;
const sourceUrl =
"https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png";
map = L.map("map").setView([23, 120], 15);
const tileLayer = L.tileLayer(sourceUrl, {
maxZoom: 18,
minZoom: 2,
attribution: "© modify",
});
tileLayer.addTo(map);
let track = null;
const initPath = () => {
// 定义沿着轨迹移动的Icon
let markerIcon = L.icon({
iconSize: [28, 28],
iconUrl: CAR, // 前面导入的img资源
iconAnchor: [12, 12],
});
// 创建播放器对象并添加至地图
track = new L.TrackPlayer(
path,
// 轨迹配置,都可以不要,保留markerIcon一个就可以了
{
markerIcon,
speed: 500, // 播放速度
weight: 6, // 轨迹线宽度
passedLineColor: "#006600", // 已行驶轨迹部分的颜色
notPassedLineColor: "red", // 未行驶轨迹部分的颜色
panTo: true, // 地图跟随移动
// 轨迹箭头样式
polylineDecoratorOptions: {
patterns: [
/**
* offset 第一个图案符号的偏移量,从线的起点开始。默认值:0
* endOffset 最后一个图案符号的最小偏移量,从线的端点开始。默认值:0
* repeat 重复间隔。定义每个连续符号的锚点之间的距离
* symbol 图标样式
* */
{
offset: 0,
repeat: 40,
symbol: L.Symbol.arrowHead({
pixelSize: 5,
pathOptions: { color: "#fbeee2", weight: 2, stroke: true },
}),
},
],
},
markerRotation: true, // 是否开启marker的旋转
}
).addTo(map);
track.start();
// 停止播放
// track.pause();
// 清除轨迹
// track.remove();
};
initPath();
});
// https://github.com/weijun-lab/Leaflet.TrackPlayer/blob/master/examples/index.html
// https://blog.csdn.net/qq_44973159/article/details/139859569
</script>
<style scoped>
#map {
height: 100%;
}
</style>
package.json
{
"name": "latest-vue3-ts",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build --mode production",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build --force"
},
"dependencies": {
"leaflet": "^1.9.4",
"leaflet-trackplayer": "^2.0.2",
"vue": "^3.4.29",
},
"devDependencies": {
"@tsconfig/node20": "^20.1.4",
"@types/leaflet": "^1.9.12",
"@types/node": "^20.14.5",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/tsconfig": "^0.5.1",
"npm-run-all2": "^6.2.0",
"typescript": "~5.4.0",
"unplugin-auto-import": "^0.17.6",
"unplugin-vue-components": "^0.27.0",
"vite": "^5.3.1",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vue-tsc": "^2.0.21"
}
}
/src/tools/points.ts
let path = [
{
lat: 34.25615548523744,
lng: 108.91164044842363,
},
{
lat: 34.256155386830855,
lng: 108.91179023713374,
},
{
lat: 34.256155386830855,
lng: 108.91179023713374,
},
{
lat: 34.25607942383744,
lng: 108.91177925878043,
},
{
lat: 34.255720609670156,
lng: 108.91171038494707,
},
{
lat: 34.255607664345405,
lng: 108.91169441655762,
},
{
lat: 34.25553269366626,
lng: 108.91169442258713,
},
{
lat: 34.25544769867856,
lng: 108.91173736885014,
},
{
lat: 34.25544769867856,
lng: 108.91173736885014,
},
{
lat: 34.25541482067108,
lng: 108.91157060617357,
},
{
lat: 34.255437230925885,
lng: 108.91091151687152,
},
{
lat: 34.2554647726071,
lng: 108.90999074936826,
},
{
lat: 34.255474922592086,
lng: 108.90972209999609,
},
{
lat: 34.255470035735925,
lng: 108.90952435653506,
},
{
lat: 34.25546585239153,
lng: 108.90796530095042,
},
{
lat: 34.255466079902156,
lng: 108.90748786950532,
},
{
lat: 34.255466139078585,
lng: 108.90736001962813,
},
{
lat: 34.25546047844199,
lng: 108.90659889522819,
},
{
lat: 34.25545553696015,
lng: 108.90646504623344,
},
{
lat: 34.255455684520776,
lng: 108.90610644487133,
},
{
lat: 34.25543990484673,
lng: 108.90555904106137,
},
{
lat: 34.255434929044085,
lng: 108.90550010453336,
},
{
lat: 34.25671044153241,
lng: 108.90546803620235,
},
{
lat: 34.256994331993134,
lng: 108.9054630187248,
},
{
lat: 34.2573861821876,
lng: 108.90545199896282,
},
{
lat: 34.2583997892619,
lng: 108.90543593456538,
},
{
lat: 34.25896857276571,
lng: 108.90541491120209,
},
{
lat: 34.2600241555513,
lng: 108.90541482639716,
},
{
lat: 34.26038901329847,
lng: 108.9054088034598,
},
{
lat: 34.260957801498556,
lng: 108.9053717970368,
},
{
lat: 34.261048767618306,
lng: 108.90536579609017,
},
{
lat: 34.26174549083055,
lng: 108.90536574011179,
},
{
lat: 34.262888033588865,
lng: 108.9053716419483,
},
{
lat: 34.263321862668384,
lng: 108.90536561345179,
},
{
lat: 34.26381066919356,
lng: 108.90536057947523,
},
{
lat: 34.264314469827035,
lng: 108.90535454535133,
},
{
lat: 34.264416428997436,
lng: 108.90535453715839,
},
{
lat: 34.264545377344014,
lng: 108.90535452679667,
},
{
lat: 34.26485025108296,
lng: 108.90536549063917,
},
{
lat: 34.26494221420379,
lng: 108.90536548324928,
},
{
lat: 34.265745895588346,
lng: 108.9053544303257,
},
{
lat: 34.26596581086138,
lng: 108.90534442324677,
},
{
lat: 34.2664006399377,
lng: 108.90533339995,
},
{
lat: 34.26711335674291,
lng: 108.90532235431407,
},
{
lat: 34.267682127119045,
lng: 108.90532230860484,
},
{
lat: 34.267977007932025,
lng: 108.90532228490632,
},
{
lat: 34.26842182796332,
lng: 108.90532224915717,
},
{
lat: 34.26893662309246,
lng: 108.90531221835984,
},
{
lat: 34.26961734908727,
lng: 108.90530616999233,
},
{
lat: 34.27079687296456,
lng: 108.90529608575685,
},
{
lat: 34.27079687296456,
lng: 108.90529608575685,
},
{
lat: 34.270796835711245,
lng: 108.90539697877264,
},
{
lat: 34.27080243135706,
lng: 108.90641586657812,
},
{
lat: 34.270802076591195,
lng: 108.9072299373526,
},
{
lat: 34.270812817234265,
lng: 108.90777629238795,
},
{
lat: 34.270822675023936,
lng: 108.90806094950152,
},
{
lat: 34.27082259586891,
lng: 108.90822075550248,
},
{
lat: 34.27082849640933,
lng: 108.9084135191401,
},
{
lat: 34.27083332877497,
lng: 108.90873512064815,
},
{
lat: 34.27083823372032,
lng: 108.90891189899708,
},
{
lat: 34.270843970260856,
lng: 108.9093942860198,
},
{
lat: 34.270843671165785,
lng: 108.90992459787954,
},
{
lat: 34.27084322644142,
lng: 108.91067459821011,
},
{
lat: 34.270842940218785,
lng: 108.91113596785353,
},
{
lat: 34.270842859216124,
lng: 108.91126379113685,
},
{
lat: 34.270847625398574,
lng: 108.91162328889843,
},
{
lat: 34.27084755144006,
lng: 108.91173612991112,
},
{
lat: 34.27085335722669,
lng: 108.91202471962777,
},
{
lat: 34.270852784122816,
lng: 108.9128555142759,
},
{
lat: 34.27085267748,
lng: 108.91300529292631,
},
{
lat: 34.27085254672574,
lng: 108.91318702269936,
},
{
lat: 34.27085205628723,
lng: 108.91385101989933,
},
{
lat: 34.27087119213721,
lng: 108.91615435172467,
},
{
lat: 34.27087566746897,
lng: 108.91675434843464,
},
{
lat: 34.2708814553737,
lng: 108.91698994875553,
},
{
lat: 34.27085429757733,
lng: 108.9171776307563,
},
{
lat: 34.27080602434836,
lng: 108.91749908177066,
},
{
lat: 34.27080602434836,
lng: 108.91749908177066,
},
{
lat: 34.270751948023054,
lng: 108.91760590116054,
},
{
lat: 34.27073590010758,
lng: 108.9176648001958,
},
{
lat: 34.270708807347326,
lng: 108.91777660774166,
},
{
lat: 34.27070375357,
lng: 108.91783650359831,
},
{
lat: 34.270708656282736,
lng: 108.91793832501797,
},
{
lat: 34.27073056759363,
lng: 108.91802317433239,
},
{
lat: 34.270778426427114,
lng: 108.91815194371763,
},
{
lat: 34.27082633013241,
lng: 108.91823279701194,
},
{
lat: 34.27090620143976,
lng: 108.91833361195992,
},
{
lat: 34.27096613594049,
lng: 108.91837653091702,
},
{
lat: 34.27103006872475,
lng: 108.91841944945133,
},
{
lat: 34.271094016769126,
lng: 108.91844639632818,
},
{
lat: 34.27115297737252,
lng: 108.91846236313094,
},
{
lat: 34.27124992726748,
lng: 108.91847333575198,
},
{
lat: 34.271362907088765,
lng: 108.91844637465212,
},
{
lat: 34.27141590080522,
lng: 108.9184303988106,
},
{
lat: 34.27146990941202,
lng: 108.91839845127609,
},
{
lat: 34.271506924906745,
lng: 108.91836650505593,
},
{
lat: 34.27161499324317,
lng: 108.91824870518059,
},
{
lat: 34.27165204453143,
lng: 108.91817882572266,
},
{
lat: 34.27169010556983,
lng: 108.91809796526304,
},
{
lat: 34.27170014671252,
lng: 108.91805004879423,
},
{
lat: 34.27170014671252,
lng: 108.91805004879423,
},
{
lat: 34.271813130666544,
lng: 108.91801809584462,
},
{
lat: 34.271926114560934,
lng: 108.91798614283951,
},
{
lat: 34.27202208565869,
lng: 108.91797515437597,
},
{
lat: 34.27211304847173,
lng: 108.91797514704386,
},
{
lat: 34.27364144760009,
lng: 108.91794807112001,
},
{
lat: 34.27430117651852,
lng: 108.91794801793834,
},
{
lat: 34.27430117651852,
lng: 108.91794801793834,
},
{
lat: 34.27452116643137,
lng: 108.91786215053015,
},
{
lat: 34.27504196130342,
lng: 108.91785212600149,
},
{
lat: 34.275856640147865,
lng: 108.91783608824171,
},
{
lat: 34.27635543402423,
lng: 108.91783604803085,
},
{
lat: 34.27635543402423,
lng: 108.91783604803085,
},
{
lat: 34.277267066837524,
lng: 108.91782499372012,
},
{
lat: 34.27870948266951,
lng: 108.91780890532164,
},
{
lat: 34.27895038797587,
lng: 108.91780289635481,
},
{
lat: 34.2793632206358,
lng: 108.91779787178181,
},
{
lat: 34.28040878884263,
lng: 108.91779279619023,
},
{
lat: 34.28146034867298,
lng: 108.91779271140113,
},
{
lat: 34.28146034867298,
lng: 108.91779271140113,
},
{
lat: 34.28263387644584,
lng: 108.91777065508535,
},
{
lat: 34.28417326218677,
lng: 108.91773359532593,
},
{
lat: 34.28442516136318,
lng: 108.91772758544468,
},
{
lat: 34.28569565334534,
lng: 108.91769553859388,
},
{
lat: 34.28609849266868,
lng: 108.9176845252085,
},
{
lat: 34.28774477772293,
lng: 108.91770036464814,
},
{
lat: 34.289932847077175,
lng: 108.91769519685113,
},
{
lat: 34.29036166314886,
lng: 108.91769516226238,
},
{
lat: 34.29064054341951,
lng: 108.91769513976726,
},
{
lat: 34.291648114741015,
lng: 108.91769006717632,
},
{
lat: 34.29183603934873,
lng: 108.91768406243645,
},
{
lat: 34.29314945213906,
lng: 108.91770591827063,
},
{
lat: 34.293712204034165,
lng: 108.9177108641832,
},
{
lat: 34.294226985630914,
lng: 108.91770583134237,
},
{
lat: 34.29428596006031,
lng: 108.9177058265846,
},
{
lat: 34.29436110539907,
lng: 108.9175131548741,
},
{
lat: 34.29435643485554,
lng: 108.91715377349566,
},
{
lat: 34.29435732039652,
lng: 108.916147466944,
},
{
lat: 34.29435732039652,
lng: 108.916147466944,
},
{
lat: 34.294572232299814,
lng: 108.91614145952745,
},
{
lat: 34.29463620453703,
lng: 108.91614145436851,
},
{
lat: 34.29495306695566,
lng: 108.91614142881548,
},
{
lat: 34.29495306695566,
lng: 108.91614142881548,
},
{
lat: 34.29496736180883,
lng: 108.91578701078069,
},
];
export default path;