文章目录
简介
vue3中mapbox初始化地图,及添加绘制、全屏、切换底图、缩放等按钮
一、HTML元素
<template>
<div class="map-container">
<div id="map">
<!-- 放在右上角,使用mapbox本身右上角按钮的样式 -->
<div class="mapboxgl-ctrl-top-right">
<div class="mapboxgl-ctrl mapboxgl-custom-ctrl-group">
<button class="custom-layer-switch-control" title="图层" @click="layerSwitch()">
<i class="iconfont icon-tuceng-" />
</button>
<button class="custom-draw-line-control" title="测距" @click="changeDrawMode('draw_line_string')">
<i class="iconfont icon-ceju" />
</button>
<button class="custom-draw-polygon-control" title="测面" @click="changeDrawMode('draw_polygon')">
<i class="iconfont icon-cemian" />
</button>
<button class="custom-draw-trash-control" title="删除" @click="changeDrawMode('')">
<i class="iconfont icon-shanchu" />
</button>
<button class="custom-location-control" title="定位" @click="location()">
<i class="iconfont icon-Location" />
</button>
</div>
</div>
</div>
</div>
</template>
二、Css修改
// 样式这些就看效果自己改一改,我反正都是直接复制的原有的,然后再调调
.mapboxgl-ctrl-top-right {
// position: relative;
top: 7%;
right: 1.5%;
.mapboxgl-custom-ctrl-group {
background: #fff;
border-radius: 4px;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
}
.mapboxgl-style-switcher {
position: absolute;
background: #fff;
border-radius: 4px;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
right: 45px;//为了不让这个按钮妨碍到其他按钮,把位置调了,如果不调的话应该可以不用设置样式
top: -145px;
background-image: url();//随便改一个,原本的有点丑
background-position: center;
background-repeat: no-repeat;
background-size: 70%;
}
// 打开图层切换后,弹出的图层菜单
.mapboxgl-style-list {
display: none;
width: 130px;
top: -145px;
position: absolute;
right: 45px;
background-color: #fff;
border: 0;
border-radius: 4px;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
}
.mapboxgl-custom-ctrl-group:not(:empty) {
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
}
.mapboxgl-custom-ctrl-group {
top: 140px;
position: relative;
background: #fff;
border-radius: 4px;
button {
background-color: transparent;
border: 0;
box-sizing: border-box;
cursor: pointer;
display: block;
height: 29px;
outline: none;
overflow: hidden;
padding: 0;
width: 29px;
}
button:first-child {
border-radius: 4px 4px 0 0;
}
button + button {
border-top: 1px solid #ddd;
}
button:last-child {
border-radius: 0 0 4px 4px;
}
}
}
.mapboxgl-ctrl-bottom-left {
left: 2%;
.mapboxgl-ctrl-logo {
display: none;
}
.mapboxgl-ctrl-scale {
height: 10px;
// background-color: transparent;
line-height: 10%;
text-align: center;
}
}
.mapboxgl-ctrl-bottom-right {
display: none;
}
三、JS方法
1、初始化地图
function initMap() {
mapboxgl.accessToken = "pk.eyJ1IjoibGt4MTIzIiwiYSI6ImNsZ2J4NGM5ejF1emczbnFyNzF1dWt1NXUifQ.-sJsEMb8N8m19dac72C6JA";
let map = new mapboxgl.Map({
container: "map",
style: "./static/map/tiandituVec.json",
center: [120, 36],
zoom: 6,
language: "zh",
projection: "equirectangular"
});
// 全屏控件
map.addControl(new mapboxgl.FullscreenControl());
// 缩放控件
map.addControl(new mapboxgl.NavigationControl());
// 比例尺控件
map.addControl(
new mapboxgl.ScaleControl(
{
maxWidth: 100,
unit: "metric"
},
"bottom-left"
)
);
// 语言控件
map.addControl(
new LanguageControl({
language: "zh"
})
);
// 测距控件,不好设置样式
// map.addControl(
// new RulerControl({
// units: "miles",
// labelFormat: (n) => `${n.toFixed(2)} ml`
// }),
// "top-right"
// );
// 底图切换控件
let mapStyleSwitchControl = mapStyleSwitch();
map.addControl(mapStyleSwitchControl, "top-right");
// 绘制控件
map.addControl(draw, "top-right");
return map;
}
2、底图切换样式对象
function mapStyleSwitch() {
const mapStyleSwitchControl = new MapboxStyleSwitcherControl([
{
title: "天地图矢量",
uri: "./static/map/tiandituVec.json"
},
{
title: "天地图影像",
uri: "./static/map/tiandituImg.json"
},
{
title: "街道底图",
uri: "mapbox://styles/mapbox/streets-v11"
},
{
title: "户外底图",
uri: "mapbox://styles/mapbox/outdoors-v11"
},
{
title: "亮色底图",
uri: "mapbox://styles/mapbox/light-v11"
},
{
title: "暗色底图",
uri: "mapbox://styles/mapbox/dark-v11"
},
{
title: "卫星底图",
uri: "mapbox://styles/mapbox/satellite-v9"
},
{
title: "卫星街道混合底图",
uri: "mapbox://styles/mapbox/satellite-streets-v11"
},
{
title: "导航底图(白天)",
uri: "mapbox://styles/mapbox/navigation-day-v1"
},
{
title: "导航底图(晚上)",
uri: "mapbox://styles/mapbox/navigation-night-v1"
}
]);
return mapStyleSwitchControl;
}
3、绘制控件
3.1绘制控件对象
const draw = new MapboxDraw({
displayControlsDefault: false, //是否显示默认的地图控件
controls: {
//显示的地图控件,自定义控件时不设置
// 自行设置按钮可以方便很多,绑定事件修改图标什么的,还可以根据不同需求设置不同mode
polygon: false,
line_string: false,
point: false,
trash: false
},
// defaultMode: "draw_polygon"
styles: drawStyles
});
3.2绘制要素样式数组
const drawStyles = [
{
id: "gl-draw-polygon-fill-inactive",
type: "fill",
filter: ["all", ["==", "active", "false"], ["==", "$type", "Polygon"], ["!=", "mode", "static"]],
paint: {
"fill-color": "#3bb2d0",
"fill-outline-color": "#3bb2d0",
"fill-opacity": 0.1
}
},
{
id: "gl-draw-polygon-fill-active",
type: "fill",
filter: ["all", ["==", "active", "true"], ["==", "$type", "Polygon"]],
paint: {
"fill-color": "#fbb03b",
"fill-outline-color": "#fbb03b",
"fill-opacity": 0.1
}
},
{
id: "gl-draw-polygon-midpoint",
type: "circle",
filter: ["all", ["==", "$type", "Point"], ["==", "meta", "midpoint"]],
paint: {
"circle-radius": 3,
"circle-color": "#fbb03b"
}
},
// ...
];
还可设定其余样式,在node_modules@mapbox\mapbox-gl-draw\dist\mapbox-gl-draw-unminified.js里找就行,不同版本可能路径不同,但都能找到,不做过多赘述
3.3 绑定绘制按钮的事件,及监听绘制过程
测量方法
function calculate(feature, map) {
let pop;
let lastCoord = feature.geometry.coordinates[feature.geometry.coordinates.length - 1];
let id = feature.id;
switch (feature.geometry.type) {
case "LineString":
const lineDistance = turf.length(feature, { units: "kilometers" });
pop = lineDistance.toFixed(4) + "km";
// console.log(lastCoord, pop);
break;
case "Polygon":
lastCoord = feature.geometry.coordinates[0][feature.geometry.coordinates[0].length - 2];
const polygonArea = turf.area(feature);
pop = polygonArea.toFixed(4) + "㎡";
// console.log(lastCoord, pop);
break;
default:
break;
}
pop = `<p>${pop}</p>`;
const popup = new mapboxgl.Popup({
closeOnClick: false,
closeOnMove: false,
closeButton: false,
className: "draw-popup " + id //每个绘制的要素根据id设置className,便于删除,draw-popup便于统一设置样式
})
.setLngLat(lastCoord)
.setHTML(pop)
.addTo(map);
}
// 我把上述代码都放在了独立的mapbox.js文件中,所以在vue文件中
import * as mapbox from "./mapbox";
let draw = mapbox.draw;
// 绘制按钮事件
const changeDrawMode = (type) => {
// console.log(type, mapbox.draw);
if (type) {
draw.changeMode(type);
} else {
// console.log(selectedDraw.value);
if (selectedDraw.value?.features?.length > 0 || selectedDraw.value?.points?.length > 0) {
//如果选中了要素,只删除选中的那个要素
draw.trash();
} else {
// 没有选中要素,则全部删掉
draw.deleteAll();
deletePopup("draw-popup");
}
selectedDraw.value = {};
}
};
监听绘制过程,需要在onMounted时调用drawEvent()方法
// 创建绘制要素
const createDraw = (e) => {
// console.log(e);
const currentFeature = e.features[0];
mapbox.calculate(currentFeature, map);
};
// 更新绘制要素
const updateDraw = (e) => {
// console.log(e);
e.features.forEach((feature) => {
const id = feature.id;
deletePopup(id);
});
const currentFeature = e.features[0];
mapbox.calculate(currentFeature, map);
};
// 删除绘制要素
const deleteDraw = (e) => {
// console.log(e);
e.features.forEach((feature) => {
const id = feature.id;
deletePopup(id);
});
};
const selectedDraw = ref({});
// 选择绘制要素
const selectDraw = (e) => {
selectedDraw.value = e;
// console.log(e);
};
// 绘制事件
const drawEvent = () => {
map.on("draw.create", createDraw);
map.on("draw.update", updateDraw);
map.on("draw.delete", deleteDraw);
map.on("draw.selectionchange", selectDraw);
// 其余三种暂时用不到
map.on("draw.actionable", (e) => {
// console.log(e);
});
map.on("draw.render", (e) => {
// console.log(e);
});
map.on("draw.modechange", (e) => {
// console.log(e);
});
};
删除时,也要删除弹窗
// 删除对应弹窗
const deletePopup = (className) => {
// console.log(className);
const selectPopup = document.getElementsByClassName(className);
// console.log(selectPopup);
if (selectPopup.length > 0) {
// selectPopup[0].remove();
Array.from(selectPopup).forEach((popup) => {
popup.remove();
});
}
};
4、定位
const locationOption = ref(null);
const location = () => {
map.flyTo({
...locationOption.value,
speed: 1,
// curve: 1,
easing(t) {
return t;
}
});
};
在onMounted时
locationOption.value = {
center: [map.getCenter().lng, map.getCenter().lat],
zoom: map.getZoom(),
bearing: map.getBearing(),
pitch: map.getPitch()
};
注:getFreeCameraOptions方法,不支持除 mercator 以外的其他投影,我这里需要使用WGS84的投影
四、完整vue文件及mapbox.js代码块
vue:
<template>
<div class="map-container">
<div id="map">
<!-- 放在右上角,使用mapbox本身右上角按钮的样式 -->
<div class="mapboxgl-ctrl-top-right">
<div class="mapboxgl-ctrl mapboxgl-custom-ctrl-group">
<button class="custom-layer-switch-control" title="图层" @click="layerSwitch()">
<i class="iconfont icon-tuceng-" />
</button>
<button class="custom-draw-line-control" title="测距" @click="changeDrawMode('draw_line_string')">
<i class="iconfont icon-ceju" />
</button>
<button class="custom-draw-polygon-control" title="测面" @click="changeDrawMode('draw_polygon')">
<i class="iconfont icon-cemian" />
</button>
<button class="custom-draw-trash-control" title="删除" @click="changeDrawMode('')">
<i class="iconfont icon-shanchu" />
</button>
<button class="custom-location-control" title="定位" @click="location()">
<i class="iconfont icon-Location" />
</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import * as mapbox from "./mapbox";
// import mapboxgl from "mapbox-gl";
// import * as turf from "@turf/turf";
import { ref, inject, onMounted, reactive } from "vue";
let map = null;
let draw = mapbox.draw;
let drawStyles = mapbox.drawStyles;
const selectedDraw = ref({});
const changeDrawMode = (type) => {
// console.log(type, mapbox.draw);
if (type) {
draw.changeMode(type);
} else {
// console.log(selectedDraw.value);
if (selectedDraw.value?.features?.length > 0 || selectedDraw.value?.points?.length > 0) {
draw.trash();
} else {
draw.deleteAll();
deletePopup("draw-popup");
}
selectedDraw.value = {};
const data = draw.getAll();
// console.log(data);
// data.features.forEach((feature) => {
// mapbox.calculate(feature, map);
// });
}
};
const layerSwitch = () => {
console.log("切换图层");
};
const locationOption = ref(null);
const location = () => {
map.flyTo({
...locationOption.value,
speed: 1,
// curve: 1,
easing(t) {
return t;
}
});
};
// 删除对应弹窗
const deletePopup = (className) => {
// console.log(className);
const selectPopup = document.getElementsByClassName(className);
// console.log(selectPopup);
if (selectPopup.length > 0) {
// selectPopup[0].remove();
Array.from(selectPopup).forEach((popup) => {
popup.remove();
});
}
};
// 创建绘制要素
const createDraw = (e) => {
// console.log(e);
const currentFeature = e.features[0];
mapbox.calculate(currentFeature, map);
};
// 更新绘制要素
const updateDraw = (e) => {
// console.log(e);
e.features.forEach((feature) => {
const id = feature.id;
deletePopup(id);
});
const currentFeature = e.features[0];
mapbox.calculate(currentFeature, map);
};
// 删除绘制要素
const deleteDraw = (e) => {
// console.log(e);
e.features.forEach((feature) => {
const id = feature.id;
deletePopup(id);
});
};
// 选择绘制要素
const selectDraw = (e) => {
selectedDraw.value = e;
// console.log(e);
};
// 绘制事件
const drawEvent = () => {
map.on("draw.create", createDraw);
map.on("draw.update", updateDraw);
map.on("draw.delete", deleteDraw);
map.on("draw.selectionchange", selectDraw);
map.on("draw.actionable", (e) => {
// console.log(e);
});
map.on("draw.render", (e) => {
// console.log(e);
});
map.on("draw.modechange", (e) => {
// console.log(e);
});
};
onMounted(() => {
// mapbox.initMap();
map = mapbox.initMap();
locationOption.value = {
center: [map.getCenter().lng, map.getCenter().lat],
zoom: map.getZoom(),
bearing: map.getBearing(),
pitch: map.getPitch()
};
console.log(locationOption.value);
drawEvent();
});
</script>
<style lang="scss" scoped></style>
mapbox.js :
import mapboxgl from "mapbox-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import "mapbox-gl-controls/lib/controls.css";
import MapboxDraw from "@mapbox/mapbox-gl-draw";
import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css";
import "mapbox-gl-style-switcher/styles.css";
import * as turf from "@turf/turf";
import { LanguageControl, RulerControl } from "mapbox-gl-controls";
import { MapboxStyleSwitcherControl } from "mapbox-gl-style-switcher";
export function initMap() {
mapboxgl.accessToken = "pk.eyJ1IjoibGt4MTIzIiwiYSI6ImNsZ2J4NGM5ejF1emczbnFyNzF1dWt1NXUifQ.-sJsEMb8N8m19dac72C6JA";
let map = new mapboxgl.Map({
container: "map",
style: "./static/map/tiandituVec.json",
center: [120, 36],
zoom: 6,
language: "zh",
projection: "equirectangular"
});
map.addControl(new mapboxgl.FullscreenControl());
map.addControl(new mapboxgl.NavigationControl());
map.addControl(
new mapboxgl.ScaleControl(
{
maxWidth: 100,
unit: "metric"
},
"bottom-left"
)
);
map.addControl(
new LanguageControl({
language: "zh"
})
);
// map.addControl(
// new RulerControl({
// units: "miles",
// labelFormat: (n) => `${n.toFixed(2)} ml`
// }),
// "top-right"
// );
let mapStyleSwitchControl = mapStyleSwitch();
map.addControl(mapStyleSwitchControl, "top-right");
map.addControl(draw, "top-right");
return map;
}
export function mapStyleSwitch() {
const mapStyleSwitchControl = new MapboxStyleSwitcherControl([
{
title: "天地图矢量",
uri: "./static/map/tiandituVec.json"
},
{
title: "天地图影像",
uri: "./static/map/tiandituImg.json"
},
{
title: "街道底图",
uri: "mapbox://styles/mapbox/streets-v11"
},
{
title: "户外底图",
uri: "mapbox://styles/mapbox/outdoors-v11"
},
{
title: "亮色底图",
uri: "mapbox://styles/mapbox/light-v11"
},
{
title: "暗色底图",
uri: "mapbox://styles/mapbox/dark-v11"
},
{
title: "卫星底图",
uri: "mapbox://styles/mapbox/satellite-v9"
},
{
title: "卫星街道混合底图",
uri: "mapbox://styles/mapbox/satellite-streets-v11"
},
{
title: "导航底图(白天)",
uri: "mapbox://styles/mapbox/navigation-day-v1"
},
{
title: "导航底图(晚上)",
uri: "mapbox://styles/mapbox/navigation-night-v1"
}
]);
return mapStyleSwitchControl;
}
// 默认绘制样式
export const drawStyles = [
{
id: "gl-draw-polygon-fill-inactive",
type: "fill",
filter: ["all", ["==", "active", "false"], ["==", "$type", "Polygon"], ["!=", "mode", "static"]],
paint: {
"fill-color": "#3bb2d0",
"fill-outline-color": "#3bb2d0",
"fill-opacity": 0.1
}
},
{
id: "gl-draw-polygon-fill-active",
type: "fill",
filter: ["all", ["==", "active", "true"], ["==", "$type", "Polygon"]],
paint: {
"fill-color": "#fbb03b",
"fill-outline-color": "#fbb03b",
"fill-opacity": 0.1
}
},
{
id: "gl-draw-polygon-midpoint",
type: "circle",
filter: ["all", ["==", "$type", "Point"], ["==", "meta", "midpoint"]],
paint: {
"circle-radius": 3,
"circle-color": "#fbb03b"
}
},
];
export const draw = new MapboxDraw({
displayControlsDefault: false, //是否显示默认的地图控件
controls: {
//显示的地图控件,自定义控件时不设置
polygon: false,
line_string: false,
point: false,
trash: false
},
// defaultMode: "draw_polygon"
styles: drawStyles
});
// 弹窗
export const popup = new mapboxgl.Popup({
closeOnClick: false,
closeOnMove: false,
closeButton: false
});
export function calculate(feature, map) {
let pop;
let lastCoord = feature.geometry.coordinates[feature.geometry.coordinates.length - 1];
let id = feature.id;
switch (feature.geometry.type) {
case "LineString":
const lineDistance = turf.length(feature, { units: "kilometers" });
pop = lineDistance.toFixed(4) + "km";
// console.log(lastCoord, pop);
break;
case "Polygon":
lastCoord = feature.geometry.coordinates[0][feature.geometry.coordinates[0].length - 2];
const polygonArea = turf.area(feature);
pop = polygonArea.toFixed(4) + "㎡";
// console.log(lastCoord, pop);
break;
default:
break;
}
pop = `<p>${pop}</p>`;
const popup = new mapboxgl.Popup({
closeOnClick: false,
closeOnMove: false,
closeButton: false,
className: "draw-popup " + id
})
.setLngLat(lastCoord)
.setHTML(pop)
.addTo(map);
}
总结
最终成果如图: