在现代的地理信息系统(GIS)应用中,地图功能是不可或缺的一部分。无论是展示商业网点、旅游景点还是公共服务设施,地图都能以直观的方式呈现数据。然而,当数据量较大时,地图上可能会出现大量的标记点,这不仅会影响用户体验,还会导致性能问题。因此,我们需要一种方法来智能地聚合这些标记点,同时允许用户在需要时查看详细信息。
Leaflet 是一个轻量级的开源 JavaScript 地图库,它提供了丰富的 API 和插件支持,非常适合实现这种功能。结合 Leaflet.markercluster 插件,我们可以轻松实现标记点的聚合和动态显示。
项目背景
在最近开发中,我遇到了一个有趣的需求:实现基于 Leaflet 的地图功能,支持多类型点位标记和聚合。这个功能需要展示不同类型的数据点,并在地图缩放时智能聚合,以避免地图过于拥挤。经过一番努力,我成功实现了这一功能,并在此分享我的经验和代码实现。
实现思路
1. 数据结构设计
在实现功能之前,我们需要定义清晰的数据结构,以便更好地管理和展示不同类型的数据点。以下是点位数据的格式示例:
markerData: [
{
latitude: "23.131828",
longitude: "113.326821",
name: "麦当劳(正佳广场五楼店)",
address: "广州市天河区天河南街道天河路228号正佳广场五楼561铺",
type: "fast_food"
},
{
latitude: "23.116523",
longitude: "113.390699",
name: "肯德基(车陂南店)",
address: "广州市天河区车陂路6号一层",
type: "fast_food"
}
...
]
此外,我们还需要定义支持的标记点类型和对应的样式配置:
markerType: [
"fast_food",
"coffee",
"landmark",
"tourist",
"shopping",
"entertainment",
"transport"
],
typeList: [
{
name: "全部",
type: "all",
color: "#000000",
iconUrl: require("../../assets/img/all.png")
},
{
name: "快餐店",
type: "fast_food",
color: "blue",
iconUrl: require("../../assets/img/fast_food.png")
},
{
name: "咖啡店",
type: "coffee",
color: "green",
iconUrl: require("../../assets/img/coffee.png")
},
{
name: "城市地标",
type: "landmark",
color: "orange",
iconUrl: require("../../assets/img/landmark.png")
},
{
name: "旅游景点",
type: "tourist",
color: "red",
iconUrl: require("../../assets/img/tourist.png")
},
{
name: "购物中心",
type: "shopping",
color: "purple",
iconUrl: require("../../assets/img/shopping.png")
},
{
name: "娱乐场所",
type: "entertainment",
color: "yellow",
iconUrl: require("../../assets/img/entertainment.png")
},
{
name: "交通设施",
type: "transport",
color: "teal",
iconUrl: require("../../assets/img/transport.png")
}
]
2. 地图初始化
首先,我们需要初始化地图。Leaflet 提供了简单易用的 API 来创建地图实例,并设置中心点、缩放级别等基本参数。在我们的项目中,我们使用了天地图(Tianditu)作为地图源,这需要通过 WMTS 服务加载矢量地图和影像地图图层。
this.map = L.map("mapRef", {
center: [23.111532, 113.324357], // 地图中心
zoom: 13, // 缩放比例
zoomControl: false, // 是否显示缩放按钮
doubleClickZoom: false, // 是否双击放大
attributionControl: false, // 是否显示右下角 Leaflet 标识
minZoom: 3, // 最小缩放级别
});
3. 添加地图图层
我们使用了天地图的矢量地图和影像地图图层。需要注意的是,天地图的访问需要一个有效的 API 密钥(tk
),并且图层的 URL 需要按照 WMTS 服务的规范进行拼接。
const tiandiKey = "YOUR_TIANDITU_API_KEY"; // 替换为你的天地图 API 密钥
const mapUrl = `http://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tiandiKey}`;
const cvaLayer = L.tileLayer(
`http://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tiandiKey}`
);
this.map.addLayer(L.tileLayer(mapUrl)); // 添加矢量地图图层
this.map.addLayer(cvaLayer); // 添加影像地图图层
注意:如果你在加载天地图时遇到问题,请检查 API 密钥是否有效,以及 URL 拼接是否正确。如果问题仍然存在,可能是网络问题或链接本身的问题,建议适当重试或联系天地图官方支持。
4. 创建聚合图层
为了实现标记点的聚合,我们使用了 Leaflet.markercluster 插件。该插件可以将大量的标记点智能地聚合在一起,形成可点击的群组。当用户放大地图时,这些群组会自动拆分成更小的子集或单个标记点。
我们为每种类型的标记点创建了一个独立的聚合图层,并通过 iconCreateFunction
动态设置聚合图标的颜色和样式。
this.markerType.forEach((type) => {
const iconCreateFunction = (cluster) => {
const dynamicClassName = `custom-marker cluster-icon-${type}`;
return L.divIcon({
html: `<div class="${dynamicClassName}"><span>${cluster.getChildCount()}</span></div>`,
className: `custom-marker cluster-icon-${type}`,
iconSize: L.point(40, 40),
});
};
this.markerClusters[type] = L.markerClusterGroup({
iconCreateFunction: iconCreateFunction,
});
});
5. 添加标记点
标记点的数据通常以数组的形式存储,每个数据项包含类型、名称、地址和坐标等信息,可以点击展示点位信息。我们根据数据项的类型,将其添加到对应的聚合图层中。
this.markerData.forEach((item) => {
const [longitude, latitude] = gcoord.transform(
[item.longitude, item.latitude],
gcoord.GCJ02,
gcoord.WGS84
);
const marker = L.marker([latitude, longitude], {
icon: icons[item.type],
});
marker.bindPopup(`<b>${item.name}</b><br>${item.address}`);
this.markerClusters[item.type].addLayer(marker);
});
Object.values(this.markerClusters).forEach((cluster) => {
this.map.addLayer(cluster);
});
6. 动态过滤标记点
为了提升用户体验,我们允许用户通过点击按钮来筛选特定类型的标记点。当用户点击按钮时,我们移除所有聚合图层,并根据用户的选择重新添加对应的图层。
filterType(item) {
Object.values(this.markerClusters).forEach((cluster) => {
this.map.removeLayer(cluster);
});
if (item.type === "all") {
Object.values(this.markerClusters).forEach((cluster) => {
this.map.addLayer(cluster);
});
} else {
if (this.markerClusters[item.type]) {
this.map.addLayer(this.markerClusters[item.type]);
}
}
}
性能优化
在处理大量标记点时,性能优化是至关重要的。Leaflet.markercluster 插件本身已经对性能进行了优化,但我们仍然可以通过以下方式进一步提升性能:
-
限制标记点数量:在地图缩放级别较低时,可以隐藏一些不重要的标记点。
-
使用缓存:对于重复加载的数据,可以使用缓存机制减少重复请求。
-
异步加载数据:如果标记点数据量较大,可以采用分页或懒加载的方式动态加载数据。
总结
通过 Leaflet 和 Leaflet.markercluster 插件,我们成功实现了一个支持多类型点位标记和聚合的地图功能。这个功能不仅提升了用户体验,还为地图应用提供了更强大的数据展示能力。在开发过程中,我们需要注意地图图层的加载、聚合图标的动态设置以及性能优化等方面,以确保应用的稳定性和流畅性。