mapbox-gl-draw` 自定义按钮画线,画面,计算面积,计算长度

1,480 阅读5分钟

自定义工具箱替换@mapbox/mapbox-gl-draw自带工具箱

自定义
画线,计算长度
画面,计算面积
标注
删除

动画.gif

改造后改造前
微信截图_20240328172103.png微信截图_20240328172210.png

目的就是为了还原UI 前端与UI早晚得打一架


  • ✅ 自定义按钮
  • ✅ 使用按钮关联事件
  • ✅ 测量距离
  • ✅ 测量面积

甲、引入相关插件

  1. mapbox-gl
  2. @mapbox/mapbox-gl-draw
    import mapboxgl from 'mapbox-gl'
    import MapboxDraw from "@mapbox/mapbox-gl-draw";
    import 'mapbox-gl/dist/mapbox-gl.css';
    import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'

乙、初始化mapboxgl初始化MapboxDraw

自主申请mapboxaccess_token

1)地图展示
     const mapContainer = useRef<mapboxgl.Map | null>(null);
     const map = useRef<MapboxDraw | null>(null);


    return (
        <div className="w-full h-full relative">
            <div ref={mapContainer} className="w-full h-full"/>
        </div>
    )
2) 初始化map,drwa:displayControlsDefault: false
import { useMapToolBarHook } from "@/hooks/map-toolbar-hook";


const {initMap, drawPolygon, deleteDraw, drawLine, drawPoint} = useMapToolBarHook()


// 初始化Draw, 设置displayControlsDefault: false
// 注意: MapboxDraw初始化放到hooks中不生效,缘由可能可能是在hooks初始化时 无法与地图关联生成相关API
    const DrawControl: MapboxDraw = new MapboxDraw({
      displayControlsDefault: false ,
      controls: {
        line_string: false,
        polygon: false,
        trash: false
      }
    });
    
   useEffect(( => {
      map.current = new mapboxgl.Map({
          container: mapContainer.current,
          style: mapbox://styles/xxxxxx,
          zoom: 10,
          projection: 'equirectangular'
     });
     
      map.current.on('load', function () {
            initMap(map.current, DrawControl);
      });
   }, []))
   
  

丙、重要指令

监听创建draw.create

监听更新draw.update

触发画面draw_polygon

触发划线draw_line_string

触发画标注draw_point

触发删除图层


丁、准备就绪,封装绘制工具箱

1) iniMap: 初始化地图监听与赋值

监听创建draw.create
监听更新draw.update

2) 创建操作函数

drawPolygon,drawLine,drawPoint,deleteDraw

3) 其他操作函数

clearMeasure: 清除Map上的标注,当清除Draw中图层时,需要手动清除自定义标注
getCenterOfLine: 计算中心坐标给标注插眼
createLabel: 绘制标注文本的
addLengthLabel: 添加长度标注
addAreaLabel: 添加面积标注
updateMeasure: 图层更新


戍、源码[AI生成的注释,将就看]

import { useRef } from "react";
import MapboxDraw from "@mapbox/mapbox-gl-draw";
import { FeaturesMeta, MapLabelMeta } from "@/types/map-features";
import * as turf from '@turf/turf';
import { AllGeoJSON, Feature, FeatureCollection, Geometry, GeometryCollection, LineString } from "@turf/turf";

export const useMapToolBarHook = () => {
    const useMap = useRef<mapboxgl.Map | null>(null);
    const DrawControl = useRef<MapboxDraw | null>(null);

/**
 * 函数“clearMeasure”使用提供的ID从地图中删除图层和源。
 * @param {string} [id] - “clearMeasure”函数中的“id”参数是一个字符串,表示要从地图中删除的图层和源的标识符。
 */
    const clearMeasure = (id?: string) => {
        try {
            useMap.current.removeLayer(id);
             useMap.current.removeSource(id);
        }catch (error) {
            console.log(error);
            
        }
      }

 /**
  * `initMap` 函数使用绘图控件和用于测量要素的事件侦听器来初始化地图。
  * @param map - `map` 参数是 Mapbox GL JS 地图对象的实例。它表示您要在其上初始化 Mapbox Draw 控件以绘制和编辑要素的地图。
  * @param {MapboxDraw} drawControl - `drawControl` 参数是 MapboxDraw
  * 控件的一个实例,该控件允许用户在地图上绘制点、线和多边形等形状。它提供了在地图上绘制和编辑要素的工具。
  */
    const initMap = (map: mapboxgl.Map, drawControl: MapboxDraw) => {
        useMap.current = map;
        DrawControl.current = drawControl
        useMap.current.addControl(DrawControl.current);
        useMap.current.on('draw.create', createMeasure);
        useMap.current.on('draw.update', updateMeasure);
        useMap.current.on('draw.changeMode', clearMeasure);
    }

/**
 * 函数“getCenterOfLine”计算给定线几何形状的中点坐标。
 * @param {Feature<any> | FeatureCollection<any> | GeometryCollection} coords - getCenterOfLine 函数中的
 * coords 参数表示 GeoJSON 对象,可以是以下类型之一:Feature<any>、FeatureCollection<any> 或
 * GeometryCollection。该参数用于计算Geo表示的线的中心点
 * @returns `getCenterOfLine` 函数返回由 `coords` 参数表示的给定线的中点坐标。
 */
    const getCenterOfLine = (coords:  Feature<any> | FeatureCollection<any> | GeometryCollection) => {
        const totalLength: number = turf.length(coords, { units: 'kilometers' });
        const midPoint = turf.along(coords as Feature<LineString> | LineString, totalLength / 2);
        return midPoint.geometry.coordinates;
      }

/**
 * 函数“createLabel”使用 Mapbox GL JS 在地图上生成具有指定文本和坐标的标签特征。
 * @param {string} text - createLabel 函数中的 text 参数是一个字符串,表示将在地图上显示的标签的文本内容。
 * @param {Feature<any> | FeatureCollection<any> | GeometryCollection} coords -
 * “createLabel”函数中的“coords”参数应为 GeoJSON 要素、要素集合或几何集合,表示应放置标签的坐标。它的类型应该是 `Feature<any> |特征集合<任意>
 * |几何集合`.
 * @param {FeaturesMeta} feature -
 * “createLabel”函数中的“feature”参数的类型为“FeaturesMeta”。它用于提供有关地图应用程序中特定功能的元数据信息。此元数据可以包括要素
 * ID、其属性以及与其关联的任何其他相关信息等详细信息。
 * @returns `createLabel` 函数返回 `label` 对象,它是一个 GeoJSON
 * FeatureCollection,包含单个要素,该要素具有表示指定坐标处标签文本的点几何图形。标签对象还可以使用 Mapbox GL JS 库作为源和图层添加到地图中。
 */
    const createLabel = (text: string, coords: Feature<any> | FeatureCollection<any> | GeometryCollection, feature: FeaturesMeta) => {
        const _id = `label-${feature.id}`;
            const label = {
                type: 'FeatureCollection',
                features: [
                {
                    type: 'Feature',
                    geometry: {
                        type: 'Point',
                        coordinates: coords
                    },
                    properties: {
                    text
                    }
                }
                ]
            };
            useMap.current.addSource(_id, { data: label as any, type: 'geojson' });
            useMap.current.addLayer({
                id: _id,
                source: _id,
                type: 'symbol',
                layout: {
                    'text-field': ['get', 'text'],
                    'text-size': 16,
                    'text-offset': [0, -1]
                },
                paint: {
                    'text-color': '#ffffff'
                }
            });
       return label;
    }

  /**
   * 函数“addLengthLabel”计算以公里为单位的长度,创建带有长度的标签,并将其与地图上的特定要素关联起来。
   * @param {number} length - “length”参数是一个数字,表示需要格式化并以公里为单位显示的长度值。
   * @param {Feature<any> | FeatureCollection<any> | GeometryCollection} coords - `addLengthLabel` 函数中的
   * `coords` 参数应该是一个 GeoJSON 对象,表示单个要素 (`Feature`) 或要素集合 (`FeatureCollection`) 或几何图形集合
   * (`GeometryCollection`)。该参数用于指定标签所在的地理坐标
   * @param {FeaturesMeta} feature - `addLengthLabel` 函数中的 `feature` 参数属于 `FeaturesMeta`
   * 类型。它用于提供有关要添加长度标签的特征的其他元数据或信息。此元数据可能包括特征类型、其属性或任何其他相关
   */
      const addLengthLabel = (length: number, coords: Feature<any> | FeatureCollection<any> | GeometryCollection, feature: FeaturesMeta) => {
        const label = `${length.toFixed(2)} km`;
        createLabel(label, coords, feature)
      }
      
    /**
     * `addAreaLabel` 函数计算要素的面积并创建面积以平方公里为单位的标签。
     * @param {number} area - “area”参数是一个代表面积(以平方公里为单位)的数字。
     * @param {Feature<any> | FeatureCollection<any> | GeometryCollection} coords - `addAreaLabel` 函数中的
     * `coords` 参数应该是一个 GeoJSON 对象,表示单个要素 (`Feature`)、要素集合 (`FeatureCollection`) 或几何图形集合
     * (`GeometryCollection`)。它用于指定地理坐标或几何图形
     * @param {FeaturesMeta} feature - `addAreaLabel` 函数中的 `feature` 参数属于 `FeaturesMeta`
     * 类型。它用于提供有关地图或地理环境中特定特征的附加元数据或信息。此信息可用于增强该特征的可视化或交互。
     */
      const addAreaLabel = (area: number, coords: Feature<any> | FeatureCollection<any> | GeometryCollection, feature: FeaturesMeta)=> {
        const label = `${area.toFixed(2)} km²`;
        createLabel(label, coords, feature)
      }

      const createMeasure = (e: any,) => {
        updateMeasure(e, true)
      }

/**
 * 函数“updateMeasure”接收一个要素对象,根据几何类型计算长度或面积,并将带有测量值的标签添加到地图上。
 * @param {any} e - updateMeasure 函数中的 e
 * 参数通常是一个事件对象,其中包含有关触发该函数的事件的信息。在这种情况下,“e”似乎应该有一个“features”属性,它是一个特征数组。然后该函数提取第一个
 * @param [iscreate=false] - updateMeasure 函数中的 iscreate
 * 参数是一个布尔参数,用于确定是否正在创建新的测量。当“iscreate”设置为“true”时,表示正在创建新的测量。如果设置了“iscreate”
 * @returns `updateMeasure` 函数不显式返回任何值。它是一个空函数,根据输入参数和提供的功能执行某些操作。
 */
    const updateMeasure = (e: any, iscreate = false) => {
        const features: FeaturesMeta = e.features[0];
        if(!features)return;
         // 清除旧的标注
        !iscreate && clearMeasure(`label-${features.id}`);
        const getGeometryHandler = {
            LineString: (feature: FeaturesMeta) => {
              const length = turf.length(feature as Feature<any> | FeatureCollection<any> | GeometryCollection, { units: 'kilometers' });
              const coords: any = getCenterOfLine(feature as Feature<any> | FeatureCollection<any> | GeometryCollection);
              addLengthLabel(length, coords, feature);
            },
            Polygon: (feature: FeaturesMeta) => {
              const area = turf.area(feature as Feature<any> | FeatureCollection<any> | Geometry) / 1000000; // 平方千米
              const coords: any = turf.centroid(feature as unknown as AllGeoJSON).geometry.coordinates;
              addAreaLabel(area, coords, feature);
            },
          };
          const { type } = features.geometry;
          const handler = getGeometryHandler[type];
          if (handler) {
            handler(features);
          }
    }
    
      /**
       * “drawPolygon”函数更改使用 DrawControl 绘制多边形的模式。
       */
        const drawPolygon = () => {
            DrawControl.current.changeMode('draw_polygon');
        }
     /**
      * “drawLine”函数将 DrawControl 的模式更改为“draw_line_string”。
      */
        const drawLine = () => {
            DrawControl.current.changeMode('draw_line_string');
        }
    /**
     * `drawPoint` 函数将 DrawControl 的模式更改为“draw_point”。
     */
        const drawPoint = () => {
            DrawControl.current.changeMode('draw_point');
        }
/**
 * “deleteDraw”函数删除地图上选定的绘制要素,并清除任何关联的测量(如果适用)。
 * @returns 如果 `_id` 为真且其长度为 0,则不会返回任何内容,因为函数将提前退出。否则,函数将使用 `DrawControl.current.delete(_id)` 方法删除所选特征。
 */
        const deleteDraw = () => {
            let _id = DrawControl.current.getSelectedIds();
            if(_id && _id.length === 0)return;
            const _point: MapLabelMeta = DrawControl.current.getSelected();
            _point.features.length > 0 && _point.features[0].geometry.type !== "Point" && clearMeasure(`label-${_id[0]}`);
            DrawControl.current.delete(_id)
        }


    return {
        initMap,
        drawPolygon,
        deleteDraw,
        drawLine,
        drawPoint
    }
}