React Native Mapbox GL如何使用turf.js计算绘制多边形面积
React Native Mapbox GL 基于Turf.js实现多边形面积计算
实现步骤
1. 安装依赖
在项目根目录执行命令安装Turf.js:npm install @turf/turf
如果你使用yarn作为包管理工具,执行:yarn add @turf/turf
2. 核心计算逻辑
Turf.js内置的area方法原生兼容Mapbox使用的WGS84经纬度坐标系,直接传入符合GeoJSON规范的Polygon/Feature对象即可返回面积值,返回结果默认单位为平方米,可根据业务需要自行转换为平方公里、公顷、亩等单位。
计算逻辑不需要额外做坐标偏移、投影转换,和Web端Mapbox绘制工具的面积计算结果完全一致。
3. 代码改造
首先在文件头部引入依赖和需要的React hooks:
import React, { useState, useEffect } from "react"; import * as turf from '@turf/turf';
新增状态存储计算得到的面积值:const [polygonArea, setPolygonArea] = useState(0);
添加监听逻辑,当多边形坐标更新时自动重新计算面积:
useEffect(() => { // 校验多边形数据合法性,至少3个点才能构成有效多边形 if (polygon?.geometry?.coordinates?.[0]?.length >= 3) { const calculatedArea = turf.area(polygon); setPolygonArea(calculatedArea); } }, [polygon]);
添加面积展示组件,放在地图上层即可:
<View style={styles.areaDisplay}> <Text style={styles.areaText}> 绘制面积: {polygonArea < 1000000 ? `${polygonArea.toFixed(2)} ㎡` : `${(polygonArea / 1000000).toFixed(2)} k㎡`} </Text> </View>
在样式表中补充对应样式:
areaDisplay: { position: "absolute", top: 24, left: 16, backgroundColor: "#fff", paddingHorizontal: 14, paddingVertical: 10, borderRadius: 8, zIndex: 99, elevation: 4 }, areaText: { fontSize: 15, color: "#222" }
注意事项
- 传入计算的多边形必须是闭合结构,即坐标数组的第一个点和最后一个点坐标完全一致,否则会出现计算误差。现有代码中的示例多边形已经满足闭合要求,后续开发手绘交互时,绘制结束时需要手动补全闭合点。
- 该方法同时支持MultiPolygon类型、带内环孔洞的多边形面积计算,不需要额外做兼容处理。
- 如果需要做面积实时计算,只需要在手绘过程中每次更新polygon状态即可,useEffect会自动触发重新计算。
完整改造后的代码
import React, { useState, useEffect } from "react"; import { View, StyleSheet, Text } from "react-native"; import MapboxGL from "@react-native-mapbox-gl/maps"; import * as turf from '@turf/turf'; MapboxGL.setAccessToken( "pk.eyJ1IjoiZGVla3NoYW1laHRhMTI1IiwiYSI6ImNrcWV6OWE0bDBjcmMydXF1enZqMjd5MDMifQ.hFB7SI_kojKYfNQ42c62BA" ); const App = () => { const [coordinates] = useState([78.9629, 20.5937]); const [polygonArea, setPolygonArea] = useState(0); const [route, setRoute] = useState({ type: "FeatureCollection", features: [ { type: "Feature", properties: {}, geometry: { type: "LineString", coordinates: [ [77.5946, 12.9716], [80.2707, 13.0827], ], }, }, ], }); const [polygon, setPolygon] = useState({ type: "Feature", geometry: { type: "Polygon", coordinates: [ [ [72.685547, 20.055931], [76.640625, 21.207458], [76.904297, 17.978733], [72.685547, 20.055931], ], ], }, }); useEffect(() => { if (polygon?.geometry?.coordinates?.[0]?.length >= 3) { const calculatedArea = turf.area(polygon); setPolygonArea(calculatedArea); } }, [polygon]); const renderAnnotations = () => { return ( <MapboxGL.PointAnnotation key="pointAnnotation" id="pointAnnotation" coordinate={[74, 27]} > <View style={{ height: 30, width: 30, backgroundColor: "red", borderRadius: 50, borderColor: "#fff", borderWidth: 3, }} /> </MapboxGL.PointAnnotation> ); }; return ( <View style={styles.page}> <View style={styles.container}> <MapboxGL.MapView style={styles.map} onPress={() => console.log("test")}> <MapboxGL.Camera zoomLevel={4} centerCoordinate={coordinates} /> <MapboxGL.PointAnnotation coordinate={coordinates} /> <View>{renderAnnotations()}</View> <MapboxGL.MarkerView id={"marker"} coordinate={[72, 23]}> <View> <View style={styles.markerContainer}> <View style={styles.textContainer}> <Text style={styles.text}>{"Gujarat"}</Text> </View> </View> </View> </MapboxGL.MarkerView> <MapboxGL.ShapeSource id="line1" shape={route}> <MapboxGL.LineLayer id="linelayer1" style={{ lineColor: "red", lineWidth: 5 }} /> </MapboxGL.ShapeSource> <MapboxGL.ShapeSource id="source" shape={polygon}> <MapboxGL.FillLayer id="fill" style={{ fillColor: "blue", fillOpacity: 0.7 }} /> <MapboxGL.LineLayer id="line" style={{ lineColor: "red", lineWidth: 2 }} /> </MapboxGL.ShapeSource> </MapboxGL.MapView> {/* 面积展示层 */} <View style={styles.areaDisplay}> <Text style={styles.areaText}> 绘制面积: {polygonArea < 1000000 ? `${polygonArea.toFixed(2)} ㎡` : `${(polygonArea / 1000000).toFixed(2)} k㎡`} </Text> </View> </View> </View> ); }; const styles = StyleSheet.create({ page: { flex: 1, }, container: { height: "100%", width: "100%", flex: 1, }, markerContainer: { alignItems: "center", width: 60, backgroundColor: "transparent", height: 70, }, map: { flex: 1, }, textContainer: { backgroundColor: "white", borderRadius: 10, flex: 1, flexDirection: "row", alignItems: "center", }, text: { textAlign: "center", paddingHorizontal: 5, flex: 1, }, icon: { paddingTop: 10, }, areaDisplay: { position: "absolute", top: 24, left: 16, backgroundColor: "#fff", paddingHorizontal: 14, paddingVertical: 10, borderRadius: 8, zIndex: 99, elevation: 4 }, areaText: { fontSize: 15, color: "#222" } }); export default App;
内容的提问来源于stack exchange,提问作者Swift
相关产品推荐
相关产品推荐

