vue3+mapbox初始化地图及添加按钮


简介

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-gl-draw的git地址
API

// 我把上述代码都放在了独立的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);
}


总结

最终成果如图:
效果图

Vue3是一种流行的JavaScript框架,而Mapbox是一个提供地图和地理位置服务的平台。在Vue3中使用Mapbox进行测距可以通过以下步骤实现: 1. 首先,你需要在Vue项目中安装MapboxJavaScript库。可以使用npm或者yarn命令来安装,例如: ``` npm install mapbox-gl ``` 2. 在Vue组件中引入MapboxJavaScript库,并创建一个地图容器。可以在`mounted`钩子函数中进行初始化,例如: ```javascript import mapboxgl from 'mapbox-gl'; export default { mounted() { mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN'; const map = new mapboxgl.Map({ container: 'map-container', style: 'mapbox://styles/mapbox/streets-v11', center: [longitude, latitude], // 设置地图中心点的经纬度 zoom: 12 // 设置地图缩放级别 }); } } ``` 请注意替换`YOUR_MAPBOX_ACCESS_TOKEN`为你自己的Mapbox访问令牌,并根据需要设置地图的样式、中心点和缩放级别。 3. 接下来,你可以使用Mapbox提供的测距功能来计算两个点之间的距离。可以在Vue组件中定义一个方法来处理测距逻辑,例如: ```javascript export default { methods: { calculateDistance() { const point1 = [longitude1, latitude1]; // 第一个点的经纬度 const point2 = [longitude2, latitude2]; // 第二个点的经纬度 const distance = turf.distance(point1, point2); // 使用turf.js库计算距离 console.log('Distance:', distance); } } } ``` 请注意替换`longitude1`、`latitude1`、`longitude2`和`latitude2`为实际的经纬度值。 4. 最后,在Vue模板中添加一个按钮或者其他触发事件的元素,调用测距方法,例如: ```html <template> <div> <button @click="calculateDistance">计算距离</button> <div id="map-container"></div> </div> </template> ``` 这样,当点击"计算距离"按钮时,测距方法将被调用,并在控制台输出两个点之间的距离。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值