Mapboxgl draw 自定义标绘:圆、矩形、自由多边形、上传读取geojson

发布时间 2023-03-30 18:45:33作者: 宇宙野牛

还没做文字标绘,累了,以后有需要有机会再说

自定义标绘方法

Mapboxgl标绘相关库

我当前使用的版本是:

    "@mapbox/mapbox-gl-draw": "^1.4.1",
    "@mapbox/mapbox-gl-draw-static-mode": "^1.0.1",
    "mapbox-gl-draw-circle": "^1.1.2",
    "mapbox-gl-draw-rectangle-mode": "^1.0.4",

api地址:
@mapbox/mapbox-gl-draw:https://github.com/mapbox/mapbox-gl-draw/blob/main/docs/API.md
@mapbox/mapbox-gl-draw是主要的库,本身包含的模式有普通选择、点、线、多边形等。另外三个分别是静态模式、圆模式、矩形模式三种扩展,对应的介绍和api在Modes章节点链接查看。
ps. 如果你使用的是vue3+vite,mapbox-gl-draw-circle使用commonjs语法在打包时可能会坑你一把,而我至今还没找到解决办法。但相关的方法已经写了,所以也会放在下面,开发环境使用没有问题。

注册绘制工具

需要注意的是,MapboxDraw可以在map定义后就添加到map,但必须要等到地图load()以后才能使用。

import MapboxDraw from "@mapbox/mapbox-gl-draw";
import { CircleMode, DragCircleMode, DirectMode, SimpleSelectMode } from "mapbox-gl-draw-circle"; // 打包可能会遇到问题
import DrawRectangle from "mapbox-gl-draw-rectangle-mode";
import StaticMode from "@mapbox/mapbox-gl-draw-static-mode";
import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css";

let draw = new MapboxDraw({
	userProperties: true,
	displayControlsDefault: false, // 不显示默认绘制工具条
	modes: {
		...MapboxDraw.modes,
		draw_circle: CircleMode,  // 打包可能会遇到问题
		drag_circle: DragCircleMode,  // 打包可能会遇到问题
		direct_select: DirectMode,  // 打包可能会遇到问题
		simple_select: SimpleSelectMode,  // 打包可能会遇到问题
		draw_rectangle: DrawRectangle,
		static: StaticMode,
	},
});
window.draw = draw; // 挂载到全局
map.addControl(draw); // 添加到map对象

自定义绘制类CustomDraw.js

export default class CustomDraw {
	constructor(drawType) {
		this._map = window.map;
		this._draw = window.draw;
		this.drawType = drawType || "single"; // 默认单图形模式,即新画出新图形时清除之前画的图形
		if (this.drawType === "view") { // view 仅查看模式,禁止编辑
			this.enableEdit(false);
		} else {
			this.enableEdit(true);
		}
	}

	initListener(cb) {
		let _this = this;
		// 清除之前的监听
		if (this._map._listeners["draw.create"]) {
			this._map._listeners["draw.create"] = undefined;
		}
		if (this._map._listeners["draw.update"]) {
			this._map._listeners["draw.update"] = undefined;
		}
		if (this._map._listeners["draw.delete"]) {
			this._map._listeners["draw.delete"] = undefined;
		}
		// 重新初始化监听
		this._map.on("draw.create", function (e) {
			console.log("draw.create");
			if (typeof cb === "function") cb(e.features);
			// 清除之前画出的图形
			let thisId = e.features[0].id;
			_this.singleClear(thisId);
		});
		this._map.on("draw.update", (e) => {
			console.log("draw.update");
			if (typeof cb === "function") cb(e.features);
		});
		this._map.on("draw.delete", (e) => {
			console.log("draw.delete");
			if (typeof cb === "function") cb(e.features);
		});
	}

        // 进入矩形模式
	addRectangle(cb) {
		this._draw.changeMode("draw_rectangle", {
			// The id of the LineString to continue drawing
			featureId: "",
			// The point to continue drawing from
			from: [],
		});
		this.initListener(cb); // 创建监听,画完后获取图形的geojson数据
	}

        // 进入圆形模式。使用时是点击一下在地图上画一个默认半径的圆,选中后可以点边缘调整半径和中心点,如果发现点了一下什么都没发生的样子,可能只是你的地图zoom比较小,圆的半径太小了所以看不到
	addCircle(cb) {
		this._draw.changeMode("draw_circle");
		this.initListener(cb);
	}

        // 进入自由多边形模式
	addPolygon(cb) {
		this._draw.changeMode("draw_polygon", {
			// The id of the LineString to continue drawing
			featureId: "",
			// The point to continue drawing from
			from: [],
		});
		this.initListener(cb);
	}

        // 外部传入geojson并添加到地图上
	addGeoJson(json, cb) {
		let featureIds = [];
		if (Array.isArray(json)) {
			json.forEach((item) => {
				featureIds = featureIds.concat(this._draw.add(item));
			});
		} else {
			featureIds = this._draw.add(json);
		}
		this.singleClear(featureIds);
		this.initListener(cb);
	}

	stopDraw() {
		this._draw.changeMode("simple_select", {
			featureIds: [],
		});
	}

        // 控制是否可编辑
	enableEdit(enable) {
		if (enable) {
			this._draw.changeMode("simple_select", {
				featureIds: [],
			});
			this.drawType = "single";
		} else {
			this._draw.changeMode("static");
			this.drawType = "view";
		}
	}

        // 单图形模式画完一个要清除之前画出来的图形
	singleClear(thisIds) {
		if (this.drawType === "single") {
			let all = this._draw.getAll();
			all.features.forEach((feature) => {
				if (thisIds.indexOf(feature.id) === -1) {
					this._draw.delete([feature.id]);
				}
			});
		}
	}

	clearAll() {
		this._draw.deleteAll();
	}
}

画圆的默认半径优化,根据当前zoom改变

addCircle(cb) {
	let scale = getScale();
	this._draw.changeMode("draw_circle", { initialRadiusInKm: scale / 1000 });
	this.initListener(cb);
}

/**
 * @description: 获取比例尺(整数缩放级别),即在纬度上每像素代表的实际距离
 * @return {*}
 */
// https://www.freesion.com/article/9003119133/
function getScale() {
	let scale;
	// let metersPerPixel = window.map.getProjection().getMetersPerPixelAtLatitude(window.map1.getCenter()); // 这个是参考例子给的方法但我使用时报错且解决不了
	let metersPerPixel = 1;
	let zoom = window.map.getZoom();
	switch (Math.round(zoom)) {
		case 2:
			scale = 1000000 / metersPerPixel;
			break;
		case 3:
			scale = 500000 / metersPerPixel;
			break;
		case 4:
			scale = 200000 / metersPerPixel;
			break;
		case 5:
			scale = 100000 / metersPerPixel;
			break;
		case 6:
			scale = 50000 / metersPerPixel;
			break;
		case 7:
			scale = 20000 / metersPerPixel;
			break;
		case 8:
			scale = 10000 / metersPerPixel;
			break;
		case 9:
			scale = 5000 / metersPerPixel;
			break;
		case 10:
			scale = 2000 / metersPerPixel;
			break;
		case 11:
			scale = 1000 / metersPerPixel;
			break;
		case 12:
			scale = 500 / metersPerPixel;
			break;
		case 13:
			scale = 200 / metersPerPixel;
			break;
		case 14:
			scale = 100 / metersPerPixel;
			break;
		case 15:
			scale = 50 / metersPerPixel;
			break;
		case 16:
			scale = 20 / metersPerPixel;
			break;
		case 17:
			scale = 10 / metersPerPixel;
			break;
		case 18:
			scale = 5 / metersPerPixel;
			break;
		default:
			break;
	}
	return scale;
}

使用方法

const draw = new CustomDraw(); // 默认单图形可编辑模式
// const draw = new CustomDraw("view"); // 多图形仅查看模式

draw.clearAll(); // 清除
draw.enableEdit(false); // 停止编辑

// 进入对应模式,并获取画完后返回的数据
const addRectangle = () => {
	draw.addRectangle(finishDraw);
};
const addCircle = () => {
	draw.addCircle(finishDraw);
};
const addPolygon = () => {
	draw.addPolygon(finishDraw);
};
// 画完以后返回feature的json数据
const finishDraw = (feature) => {
	// 处理feature,比如显示在页面上
};

// 上传.json或.geojson格式的文件,读取并画在地图上。这里上传使用了element-plus的上传组件
const fileList = ref([]);
const handleUpload = (uploadFile) => {
	loadJson(uploadFile.raw);
	return false;
};
const loadJson = async (file) => {
	const reader = new FileReader(); // 新建一个FileReader
	reader.readAsText(file, "UTF-8"); // 读取文件
	reader.onload = function (evt) {
		// 读取完文件之后会回来这里
		try {
			let fileString = evt.target.result; // 读取文件内容
			const jsonObj = JSON.parse(fileString);
			// jsonObj 格式和作用类似上面 finishDraw 返回的 feature
			draw.addGeoJson(jsonObj, finishDraw); // 这里添加监听是为了能够修改上传的形状
		} catch (e) {
			console.error("json读取或添加失败", e);
		}
	};
};

修改绘制的默认样式

官方主页下方有说明。在new MapboxDraw时的选项里添加styles:

		styles: [
			// ACTIVE (being drawn)
			// line stroke
			{
				id: "gl-draw-line",
				type: "line",
				filter: ["all", ["==", "$type", "LineString"], ["!=", "mode", "static"]],
				layout: {
					"line-cap": "round",
					"line-join": "round",
				},
				paint: {
					"line-color": "#D20C0C",
					"line-dasharray": [0.2, 2],
					"line-width": 2,
				},
			},
			// polygon fill
			{
				id: "gl-draw-polygon-fill",
				type: "fill",
				filter: ["all", ["==", "$type", "Polygon"], ["!=", "mode", "static"]],
				paint: {
					"fill-color": "#D20C0C",
					"fill-outline-color": "#D20C0C",
					"fill-opacity": 0.1,
				},
			},
			// polygon mid points
			{
				id: "gl-draw-polygon-midpoint",
				type: "circle",
				filter: ["all", ["==", "$type", "Point"], ["==", "meta", "midpoint"]],
				paint: {
					"circle-radius": 3,
					"circle-color": "#fbb03b",
				},
			},
			// polygon outline stroke
			// This doesn't style the first edge of the polygon, which uses the line stroke styling instead
			{
				id: "gl-draw-polygon-stroke-active",
				type: "line",
				filter: ["all", ["==", "$type", "Polygon"], ["!=", "mode", "static"]],
				layout: {
					"line-cap": "round",
					"line-join": "round",
				},
				paint: {
					"line-color": "#D20C0C",
					"line-dasharray": [0.2, 2],
					"line-width": 2,
				},
			},
			// vertex point halos
			{
				id: "gl-draw-polygon-and-line-vertex-halo-active",
				type: "circle",
				filter: ["all", ["==", "meta", "vertex"], ["==", "$type", "Point"], ["!=", "mode", "static"]],
				paint: {
					"circle-radius": 5,
					"circle-color": "#FFF",
				},
			},
			// vertex points
			{
				id: "gl-draw-polygon-and-line-vertex-active",
				type: "circle",
				filter: ["all", ["==", "meta", "vertex"], ["==", "$type", "Point"], ["!=", "mode", "static"]],
				paint: {
					"circle-radius": 3,
					"circle-color": "#D20C0C",
				},
			},

			// INACTIVE (static, already drawn)
			// line stroke
			{
				id: "gl-draw-line-static",
				type: "line",
				filter: ["all", ["==", "$type", "LineString"], ["==", "mode", "static"]],
				layout: {
					"line-cap": "round",
					"line-join": "round",
				},
				paint: {
					"line-color": "#ff0000",
					"line-width": 3,
				},
			},
			// polygon fill
			{
				id: "gl-draw-polygon-fill-static",
				type: "fill",
				filter: ["all", ["==", "$type", "Polygon"], ["==", "mode", "static"]],
				paint: {
					"fill-color": "#FF0000",
					"fill-outline-color": "#000",
					"fill-opacity": 0.23,
				},
			},
			// polygon outline
			{
				id: "gl-draw-polygon-stroke-static",
				type: "line",
				filter: ["all", ["==", "$type", "Polygon"], ["==", "mode", "static"]],
				layout: {
					"line-cap": "round",
					"line-join": "round",
				},
				paint: {
					"line-color": "#E00000",
					"line-width": 3,
				},
			},
		],