// ParentComponent
<script setup lang="ts">
import {onMounted} from 'vue'
import L from "leaflet";
import 'leaflet/dist/leaflet.css'
import { MarkerClusterGroup } from "leaflet.markercluster" // 标记聚类
import 'leaflet.markercluster/dist/MarkerCluster.css';
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
import 'leaflet.heat';
onMounted(()=> {
// 在 Leaflet 的瓦片图层 URL 模板中,{s} 是一个占位符,用于表示瓦片服务的子域名(subdomain)。这是一种常见的负载均衡技术,地图服务提供商通过多个子域名来分散请求,提高并发性能和稳定性
const tdtKey = ''; // 天地图key
// 初始化地图
const map = L.map('map').setView([39.9, 116.4], 17); // 设置中心点为北京,缩放级别17
const subdomains = ['t0','t1','t2','t3','t4','t5','t6','t7']; // 共8个子域
const vecLayer = L.tileLayer(
`http://{s}.tianditu.gov.cn/vec_w/wmts?tk=${tdtKey}&service=wmts&request=GetTile&version=1.0.0&layer=vec&tileMatrixSet=w&format=tiles&tileMatrix={z}&tileRow={y}&tileCol={x}`,
{
subdomains,
maxZoom: 18,
minZoom: 1,
attribution: '© <a href="https://www.openstreetmap.org/copyright">Hello World</a>'
}
).addTo(map);
// coordinates跟leaflet的坐标是反的 不过会自动转换
// Feature 点
const pointGeoJSON = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [116.4, 39.9] // [经度, 纬度]
},
"properties": {
"name": "Beijing"
}
};
L.geoJSON(pointGeoJSON, {
pointToLayer: (feature, latlng) => {
return L.circleMarker(latlng, {
radius: 66,
fillColor: "red",
color: "#000",
weight: 1
});
}
}).addTo(map);
// LineString 线
const lineGeoJSON = {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[116.3, 39.8], // 起点
[116.4, 39.9], // 途经点
[116.5, 40.0] // 终点
]
},
"properties": {
"name": "Road ABC"
}
};
L.geoJSON(lineGeoJSON, {
style: {
color: "blue",
weight: 5,
opacity: 0.7
}
}).addTo(map);
// Polygon 多边形
const polygonGeoJSON = {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[ // 外轮廓(必须闭合)
[116.3, 39.8],
[116.5, 39.8],
[116.5, 40.0],
[116.3, 40.0],
[116.3, 39.8] // 首尾相同,闭合多边形
],
[ // 可选:内洞(如岛屿)
[116.35, 39.85],
[116.45, 39.85],
[116.45, 39.95],
[116.35, 39.95],
[116.35, 39.85]
]
]
},
"properties": {
"name": "A Lake"
}
};
L.geoJSON(polygonGeoJSON, {
style: {
fillColor: "green",
fillOpacity: 0.5,
color: "darkgreen"
}
}).addTo(map);
// MultiPoint 多点
const multiPointGeoJSON = {
"type": "Feature",
"geometry": {
"type": "MultiPoint",
"coordinates": [
[116.4, 39.9],
[116.41, 39.91]
]
},
"properties": {
"name": "Cities"
}
};
L.geoJSON(multiPointGeoJSON, {
pointToLayer: (feature, latlng) => {
return L.circleMarker(latlng, { radius: 5 });
}
}).addTo(map);
// MultiLineString 多线
const multiLineGeoJSON = {
"type": "Feature",
"geometry": {
"type": "MultiLineString",
"coordinates": [
[[116.3, 39.8], [116.4, 39.9]], // 第一条线
[[116.4, 39.9], [116.5, 40.0]] // 第二条线
]
}
};
// MultiPolygon 多面
const multiPolygonGeoJSON = {
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[ // 第一个多边形
[[116.3, 39.8], [116.5, 39.8], [116.5, 40.0], [116.3, 39.8]]
],
[ // 第二个多边形
[[116.6, 39.6], [116.7, 39.6], [116.7, 39.7], [116.6, 39.6]]
]
]
}
};
L.geoJSON(multiPolygonGeoJSON, {
style: { fillColor: "purple" }
}).addTo(map);
L.geoJSON(multiLineGeoJSON, {
style: { color: "orange" }
}).addTo(map);
// GeometryCollection 几何集合
const collectionGeoJSON = {
"type": "Feature",
"geometry": {
"type": "GeometryCollection",
"geometries": [
{ "type": "Point", "coordinates": [116.4, 39.9] },
{ "type": "LineString", "coordinates": [[116.3, 39.8], [116.5, 40.0]] }
]
},
properties: {
name: '12121212'
}
};
L.geoJSON(collectionGeoJSON).addTo(map);
// 添加自定义图标标记
const govIcon = L.icon({
iconUrl: '/icons/government.png',
iconSize: [36, 40],
popupAnchor: [0, -15]
});
const marker = L.marker([31.2304, 121.4737], {
icon: govIcon,
title: '上海市人民政府'
}).addTo(map);
marker.bindPopup(`
<div class="custom-popup">
<h3>上海市人民政府</h3>
<p>地址:人民大道200号</p>
</div>
`).openPopup();
// 标记聚类
const poiCluster = new MarkerClusterGroup();
// 模拟加载POI数据
const poiData = [
{name: "东方明珠", latlng: [31.2398, 121.4997], type: "landmark"},
{name: "豫园", latlng: [31.2269, 121.4904], type: "park"}
];
poiData.forEach(poi => {
const marker = L.marker(poi.latlng)
.bindPopup(poi.name);
poiCluster.addLayer(marker);
});
map.addLayer(poiCluster);
// 绘制业务区域
const businessArea = L.polygon([
[31.235, 121.460],
[31.235, 121.490],
[31.215, 121.490],
[31.215, 121.460]
], {
color: '#3388ff',
fillColor: '#3388ff',
fillOpacity: 0.3
}).addTo(map);
businessArea.bindPopup("核心商务区");
// 热力图
// 生成模拟热力点
const heatPoints = [];
for(let i=0; i<500; i++) {
const lat = 31.22 + Math.random() * 0.1;
const lng = 121.46 + Math.random() * 0.1;
heatPoints.push([lat, lng, Math.random()]);
}
// 添加热力图层
const heatLayer = L.heatLayer(heatPoints, {
radius: 25,
blur: 15,
gradient: {0.4: 'blue', 0.65: 'lime', 1: 'red'}
}).addTo(map);
// 车辆图标
const busIcon = L.icon({
iconUrl: 'bus.png',
iconSize: [32, 32],
iconAnchor: [16, 16]
});
// 轨迹线路
const route = L.polyline([], {
color: 'red',
weight: 3
}).addTo(map);
// 车辆标记
const busMarker = L.marker([31.2304, 121.4737], {
icon: busIcon,
rotationAngle: 0,
rotationOrigin: 'center'
}).addTo(map);
// 模拟车辆移动
setInterval(() => {
const newLat = busMarker.getLatLng().lat + (Math.random() - 0.5)*0.001;
const newLng = busMarker.getLatLng().lng + (Math.random() - 0.5)*0.001;
// 更新位置
busMarker.setLatLng([newLat, newLng]);
// 更新方向
const newAngle = Math.atan2(
newLng - busMarker.getLatLng().lng,
newLat - busMarker.getLatLng().lat
) * 180 / Math.PI;
busMarker.setRotationAngle(newAngle);
// 更新轨迹
route.addLatLng([newLat, newLng]);
}, 1000);
});
</script>
<template>
<div id="map" ref="mapDiv"></div>
</template>
<style scoped>
#map { height: 100vh; width: 100vw; }
</style>