与地图库配合
Turf 只算数据,不画图。它的输出是标准 GeoJSON,可以直接交给 Leaflet 的
L.geoJSON 或 MapLibre 的 GeoJSON 源。真正需要转换的只有两处:
视野范围的数组顺序,以及更新数据的方式。
三种坐标表达要分清
| 来源 | 形式 | 顺序 |
|---|---|---|
| GeoJSON / Turf | [lng, lat] | 经度在前 |
Leaflet 的 L.latLng | [lat, lng] | 纬度在前 |
Leaflet 的 fitBounds | [[south, west], [north, east]] | 先纬度后经度 |
MapLibre 的 fitBounds | [[west, south], [east, north]] | 与 GeoJSON 一致 |
turf.bbox | [minX, minY, maxX, maxY] | 西、南、东、北 |
两个地图库的 fitBounds 顺序相反,这是跨库复制代码时最常见的一处错位。
传错时地图不会报错,只是飞到地球另一端或缩放到全球级别。
Leaflet:把分析结果叠上去
L.geoJSON 直接吃 Turf 的输出,onEachFeature
里统一绑弹窗。视野用 turf.bbox 转换后传给 fitBounds。
var site = turf.point([116.391263, 39.907359]);
var zone = turf.buffer(site, 1.2, { units: 'kilometers' });
var map = L.map('map').setView(
[site.geometry.coordinates[1], site.geometry.coordinates[0]], 14
);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19
}).addTo(map);
var layer = L.geoJSON(zone, {
style: {
color: '#17694a',
weight: 2,
fillOpacity: 0.2
},
onEachFeature: function (feature, lyr) {
var area = turf.area(feature) / 1e6; // 平方公里
lyr.bindPopup('影响范围 ' + area.toFixed(2) + ' 平方公里');
}
}).addTo(map);
var box = turf.bbox(zone); // [西, 南, 东, 北]
map.fitBounds([[box[1], box[0]], [box[3], box[2]]], { padding: [20, 20] });
缓冲区是 Turf 里最常与地图搭配的分析:半径为 0 时返回的点面几乎重合, 半径写错单位则圈的大小差几个数量级。单位统一用公里,展示时再换算。
MapLibre:更新一份源
MapLibre 里图层引用数据源,重算结果后只需要 setData,
不必删图层再重建。这样样式、层级、事件绑定都不会丢。
map.on('load', function () {
map.addSource('zone', { type: 'geojson', data: emptyFC() });
map.addSource('origin', {
type: 'geojson',
data: turf.featureCollection([site])
});
map.addLayer({
id: 'zone-fill',
type: 'fill',
source: 'zone',
paint: { 'fill-color': '#17694a', 'fill-opacity': 0.2 }
});
map.addLayer({
id: 'zone-line',
type: 'line',
source: 'zone',
paint: { 'line-color': '#17694a', 'line-width': 2 }
});
map.addLayer({
id: 'origin-dot',
type: 'circle',
source: 'origin',
paint: { 'circle-radius': 5, 'circle-color': '#c2410c' }
});
redraw(1.2);
});
function redraw(radiusKm) {
var zone = turf.buffer(site, radiusKm, { units: 'kilometers' });
map.getSource('zone').setData(zone);
var b = turf.bbox(zone);
map.fitBounds([[b[0], b[1]], [b[2], b[3]]], {
padding: 40,
duration: 600
});
}
function emptyFC() {
return { type: 'FeatureCollection', features: [] };
}
setData 接受要素或要素集合。传空集合可以把图层清空,
比 removeLayer 再 addLayer 更轻,也不会引起样式重排。
按结果迭代要素属性
需要按属性给每个要素单独配色时,Leaflet 在 style 回调里返回样式,
MapLibre 则用表达式按 properties 取色。Turf 负责把属性算好。
var enriched = turf.featureCollection(
zones.features.map(function (f) {
var out = turf.clone(f);
out.properties.areaKm2 = Number((turf.area(f) / 1e6).toFixed(2));
out.properties.density = Number(
(out.properties.population / out.properties.areaKm2).toFixed(0)
);
return out;
})
);
// Leaflet 侧
L.geoJSON(enriched, {
style: function (f) {
return { color: '#17694a', fillOpacity: f.properties.density / 5000 };
}
}).addTo(map);
注意事项
直接改 Turf 输出的 properties
多数 Turf 函数返回的是新对象,但传入 mutate 的调用会改原数据。
把原数据同时喂给两个库时,先 turf.clone,
否则一边改了属性,另一边看到的是被改后的结果。
每次重算都 addSource
map.addSource('zone', ...) 重复执行会抛
Source "zone" already exists。把源的创建放在
load 里做一次,之后只更新 setData。
缓冲区结果为 MultiPolygon
线要素或相邻多点做缓冲时,结果是 MultiPolygon,
个别渲染路径只处理 Polygon。需要单面时先做一次
turf.dissolve,或按 geometry.type 分开处理。
经纬志(2026)。《与地图库配合》。https://www.jingweizhi.com/turf/tutorial/map-integration.html(访问日期 2026-11-03)。