vue3+mapboxgl 鹰眼展示

1、参考文章:vue+mapboxgl 鹰眼展示_mapbox鹰眼图-CSDN博客

2、讲解:主要是基于mapbox来实现的功能

3、我这个是基于离线操作来实现的,地图不需要token

4、具体代码

<div ref="mapContainer" class="map-container" id="map">
        <!-- 鹰眼视图 -->
        <div style="position: absolute;bottom:0px;right: 0px;z-index: 200;width: 308px;height:208px;"
            v-show="isOverviewVisible">
            <div id="overview" class="overview" style="width:300px;height:200px;margin: 3px 3px 3px 3px;">
            </div>
        </div>
        <!-- 视图收起与展开 -->
        <div class="overview-toggle" @click="toggleOverview">
            <el-icon :size="24">
                <template v-if="isOverviewVisible">
                    <BottomRight />
                </template>
                <template v-else>
                    <TopLeft />
                </template>
            </el-icon>
        </div>
    </div>
const isOverviewVisible = ref(true);
let data = {}; // 初始为空对象
const toggleOverview = () => {
    isOverviewVisible.value = !isOverviewVisible.value;
    if (isOverviewVisible.value) {
        // 展开鹰眼视图
        document.getElementById('overview').style.display = 'block';
        overviewMap.resize(); // 调整鹰眼视图的大小
        // 使用 once 确保在地图渲染完成后再更新
        overviewMap.once('render', () => {
            syncMapPositions(false); // 同步视图
        });
    } else {
        // 收起鹰眼视图
        document.getElementById('overview').style.display = 'none';
    }
};
const eagleEyeView = () => {
    if (data) {
        // 初始化鹰眼图
        overviewMap = new mapboxgl.Map({
            container: 'overview',
            style: data,
            center: map.getCenter(),
            zoom: Math.max(5, map.getZoom() - 3), // 确保最小缩放级别
            pitch: 0, // 鹰眼图保持俯瞰视角
            bearing: 0, // 鹰眼图不旋转
            interactive: true, // 启用交互
            attributionControl: false
        });

        // 主地图 -> 鹰眼视图同步
        map.on('move', () => syncMapPositions(false));
        map.on('zoom', () => syncMapPositions(false));
        map.on('rotate', () => syncMapPositions(false));
        map.on('pitch', () => syncMapPositions(false));

        // 鹰眼视图 -> 主地图同步
        overviewMap.on('moveend', () => syncMapPositions(true));
        overviewMap.on('zoomend', () => syncMapPositions(true));

        // 点击鹰眼视图时更新主地图
        overviewMap.on('click', (e) => {
            if (isOverviewVisible.value) {
                updateMainMap(e.lngLat, overviewMap.getZoom() + 3);
            }
        });
        overviewMap.on('load', () => {
            // 添加矩形框
            const extentRectangleId ='main-map-extent';
            // 添加数据源
            overviewMap.addSource(extentRectangleId, {
                type: 'geojson',
                data: {
                    type: 'Feature',
                    geometry: {
                        type: 'Polygon',
                        coordinates: []
                    }
                }
            });
            // 添加图层
            overviewMap.addLayer({
                id: extentRectangleId,
                type: 'line',
                source: extentRectangleId,
                paint: {
                    'line-color': '#ff0000',
                    'line-width': 2,
                    'line-dasharray': [4, 2]
                }
            });
            overviewMap.once('render', () => {
                overviewMap.setStyle(map.getStyle());
                syncMapPositions(false);
            });

        });
    }
}
// 避免循环更新的标志
let isSyncing = false;
// 同步地图位置的通用方法
const syncMapPositions = (fromOverview) => {
    if (isSyncing || !map || !overviewMap) return;

    isSyncing = true;

    try {
        if (fromOverview) {
            // 鹰眼视图 -> 主地图
            updateMainMap(overviewMap.getCenter(), overviewMap.getZoom() + 3);
        } else {
            // 主地图 -> 鹰眼视图
            const mainBounds = map.getBounds();

            // 更新鹰眼视图中的可视区域多边形
            const extentRectangleId ='main-map-extent';
            if (overviewMap.getSource('main-map-extent')) {
                overviewMap.getSource('main-map-extent').setData({
                    type: 'Feature',
                    geometry: {
                        type: 'Polygon',
                        coordinates: [
                            [
                                [mainBounds.getSouthWest().lng, mainBounds.getNorthEast().lat],
                                [mainBounds.getNorthEast().lng, mainBounds.getNorthEast().lat],
                                [mainBounds.getNorthEast().lng, mainBounds.getSouthWest().lat],
                                [mainBounds.getSouthWest().lng, mainBounds.getSouthWest().lat],
                                [mainBounds.getSouthWest().lng, mainBounds.getNorthEast().lat]
                            ]
                        ]
                    }
                });
            }

            // 更新鹰眼视图的位置和缩放
            overviewMap.jumpTo({
                center: map.getCenter(),
                zoom: Math.max(5, map.getZoom() - 3),
                bearing: map.getBearing(),
                pitch: 0,
                duration: 0
            });
        }
    } catch (error) {
        console.error('同步地图位置时出错:', error);
    } finally {
        // 使用setTimeout确保异步执行,避免UI阻塞
        setTimeout(() => {
            isSyncing = false;
        }, 100);
    }
};

// 更新主地图位置
const updateMainMap = (center, zoom) => {
    // 限制最大和最小缩放级别
    const clampedZoom = Math.min(Math.max(zoom, 5), 22);

    map.flyTo({
        center: center,
        zoom: clampedZoom,
        bearing: 0,
        pitch: 0,
        duration: 500
    });
};
const initialization = () => {
    let bounds = [
        [103.500206757969, 24.6213633798346], // 左下角经纬度
        [109.750964369846, 29.2243422652764] // 右上角经纬度
    ];

    const glyphsUrl = '../../../static/Open Sans Semibold,Arial Unicode MS Bold/{fontstack}/{range}.pbf';
    baseService.get("/api/qjl/tile/json").then(response => {
        if (response) {
            // 将后端返回的数据赋值给 data
            data = response.data;
            // 动态添加 glyphs 字段
            data.glyphs = glyphsUrl;
            // 在数据准备好之后初始化地图
            map = new mapboxgl.Map({
                container: mapContainer.value,
                style: data,
                pitch: 0, // 初始倾斜角度
                bearing: 0, // 初始旋转角度
                dragRotate: true, // 启用拖动旋转
                touchRotate: true, // 启用触摸旋转
                dragPan: true, // 启用拖动平移
                scrollZoom: true, // 启用滚轮缩放
                touchZoomRotate: true, // 启用触摸缩放和旋转
                zoom: 5, // 初始层级
                minZoom: 5, // 最小缩放级别
                maxZoom: 22, // 最大缩放级别
                center: [106.7132, 26.5728], // 贵州省中心点
                maxBounds: bounds, // 设置地图的拖动范围
            });
            map.on('load', () => {
                //绘制图斑
                // 初始化Mapbox GL Draw插件
                draw = new MapboxDraw({
                    displayControlsDefault: false,
                    controls: {
                        polygon: false,
                        trash: false,
                        combine_features: true,
                        uncombine_features: true,
                    },
                });
                map.addControl(draw)
                //初始化贵州边界
                loadGuizhouBoundaries()
                // initializeDrawPlugin()
                //点击展示图斑信息
                // clickPolygon()
                bindClickEventsToLayers()
                //初始化鹰眼图
                eagleEyeView()

            });

        } else {
            console.error("No data received from the backend.");
        }
    }).catch(error => {
        console.error("Failed to fetch data from the backend:", error);
    });
}
/*鹰眼视图控制按钮样式 */
.overview-toggle {
    position: absolute;
    bottom: -8px;
    right: -8px;
    z-index: 201;
    width: 36px;
    height: 36px;
    background-color: rgba(0, 0, 0, 0.7);
    color: white;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
    transition: background-color 0.3s;
}

.overview-toggle:hover {
    background-color: rgba(0, 0, 0, 0.9);
}

主要是实现鹰眼功能,所有其他代码有些可以不需要

5、具体效果

### Mapbox 封装并全局引入后的路由判断逻辑 在 Vue 项目中,如果希望封装 Mapbox 并将其作为全局组件引入,同时能够根据当前路由决定是否显示地图,则可以通过以下方式进行实现。 #### 1. 封装 Mapbox 组件 可以将 Mapbox 的初始化逻辑封装成一个独立的 Vue 组件 `MapComponent.vue`,以便于复用和维护。以下是该组件的一个简单实现: ```javascript <template> <div ref="mapContainer" class="map-container"></div> </template> <script setup> import { onMounted, ref } from &#39;vue&#39;; const mapboxgl = require(&#39;mapbox-gl&#39;); // 导入自定义的小地图功能 import Minimap from &#39;./mapboxgl-control-minimap&#39;; onMounted(() => { const mapContainer = document.querySelector(&#39;.map-container&#39;); // 初始化主地图 mapboxgl.accessToken = &#39;your_mapbox_access_token&#39;; // 替换为实际 token const map = new mapboxgl.Map({ container: mapContainer, style: &#39;mapbox://styles/mapbox/streets-v11&#39;, center: [104.115, 37.55], // 默认中心点坐标 zoom: 4, // 默认缩放级别 }); // 添加小地图功能 mapboxgl.Minimap = Minimap; const minimapControl = new mapboxgl.Minimap(); map.addControl(minimapControl); }); </script> <style scoped> .map-container { width: 100%; height: 500px; /* 可调整 */ } </style> ``` 此部分实现了基本的地图加载以及鹰眼地图的功能[^1]。 --- #### 2. 全局注册 Mapbox 组件 为了让 `MapComponent` 成为全局可用的组件,在项目的入口文件(通常是 `main.js` 或 `main.ts` 中),可以通过以下方式注册它: ```javascript import { createApp } from &#39;vue&#39;; import App from &#39;./App.vue&#39;; import router from &#39;./router&#39;; // 假设已配置好路由 import MapComponent from &#39;@/components/MapComponent.vue&#39;; const app = createApp(App); // 注册为全局组件 app.component(&#39;GlobalMap&#39;, MapComponent); // 挂载路由和其他插件 app.use(router).mount(&#39;#app&#39;); ``` 此时,可以在任何地方通过 `<global-map></global-map>` 来调用这个封装好的地图组件。 --- #### 3. 路由守卫控制地图显示 为了仅在特定路由下展示地图,可以利用 Vue Router 提供的导航守卫机制来动态控制地图组件的渲染行为。例如: ```javascript import { createRouter, createWebHistory } from &#39;vue-router&#39;; const routes = [ { path: &#39;/&#39;, component: () => import(&#39;@/views/HomeView.vue&#39;) }, { path: &#39;/about&#39;, component: () => import(&#39;@/views/AboutView.vue&#39;), meta: { showMap: true } }, // 设置元数据 ]; const router = createRouter({ history: createWebHistory(), routes, }); export default router; // 在 main.js 中挂载路由守卫 router.beforeEach((to, from, next) => { const isShowMap = to.meta?.showMap || false; // 获取路由元信息 window.showMap = isShowMap; // 存储到全局变量中 next(); }); ``` 在此基础上,修改 `MapComponent.vue` 的模板结构以支持动态隐藏或显示: ```html <template> <div v-if="shouldDisplayMap" ref="mapContainer" class="map-container"></div> </template> <script setup> import { computed, onMounted, ref } from &#39;vue&#39;; const shouldDisplayMap = computed(() => window.showMap); // 动态读取全局变量 onMounted(() => { if (!shouldDisplayMap.value) return; const mapContainer = document.querySelector(&#39;.map-container&#39;); const mapboxgl = require(&#39;mapbox-gl&#39;); mapboxgl.accessToken = &#39;your_mapbox_access_token&#39;; const map = new mapboxgl.Map({ container: mapContainer, style: &#39;mapbox://styles/mapbox/streets-v11&#39;, center: [104.115, 37.55], zoom: 4, }); }); </script> ``` 以上代码片段展示了如何基于路由元信息 (`meta`) 和全局变量 (`window.showMap`) 控制地图的显示与隐藏。 --- #### 4. 结合 TypeScript 改进类型安全 对于更复杂的场景,建议结合 TypeScript 类型声明进一步增强代码的安全性和可维护性。例如,扩展路由元信息接口如下: ```typescript interface RouteMeta extends Record<string, unknown> { showMap?: boolean; } declare module &#39;vue-router&#39; { interface RouteRecordRaw { meta?: RouteMeta; } } ``` 随后即可按照上述方法操作 `route.meta.showMap` 属性而无需担心潜在的类型错误。 --- ### 总结 通过封装 Mapbox 组件、全局注册以及借助 Vue Router 的导航守卫机制,可以灵活地实现在不同路由条件下按需加载地图的效果。这种方法不仅提高了代码的重用率,也增强了项目的可扩展性[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值