动态

详情 返回 返回

vue+openlayers矢量瓦片示例:GeoJSON數據源模式渲染(附源碼下載) - 动态 详情

接到一位知識星友的邀請,他那邊的需求是需要矢量瓦片渲染點數據,所以針對他的業務需求定製開發一個vue+oepnlayers版本的矢量瓦片渲染示例

demo源碼運行環境以及配置

  • 運行環境:依賴Node安裝環境,demo本地Node版本:推薦v16+。
  • 運行工具:vscode或者其他工具。
  • 配置方式:下載demo源碼,vscode打開,然後順序執行以下命令:
    (1)下載demo環境依賴包命令:npm i
    (2)啓動demo命令:npm run dev
    (3)打包demo命令: npm run build

示例效果

image

技術棧

  • 前端框架 :Vue 3.5.13
  • 地圖庫 :OpenLayers 10.4.0
  • 矢量瓦片處理 :geojson-vt 4.0.2
  • 構建工具 :Vite 6.2.0

功能特點

  • 基於OpenLayers實現的交互式地圖
  • 使用geojson-vt將GeoJSON數據轉換為矢量瓦片
  • 支持點擊地圖上的點位顯示詳細信息彈窗
  • 鼠標懸停時顯示指針樣式變化
  • 自定義點位樣式和標籤顯示
  • 包含1000+中國城市和地點數據
  • 提供數據生成腳本,可自定義生成更多數據

矢量瓦片實現

項目使用geojson-vt庫將GeoJSON數據轉換為矢量瓦片,主要實現步驟:

  1. 加載GeoJSON數據
  2. 使用geojson-vt創建瓦片索引
  3. 自定義VectorTileSourcetileUrlFunctiontileLoadFunction
  4. tileLoadFunction中從瓦片索引獲取對應瓦片數據
  5. 將瓦片數據轉換為OpenLayers的Feature對象

這種方式可以高效處理大量點位數據,提升地圖渲染性能。

核心源碼

<template>
    <div id="map" class="map"></div>
    <div id="popup" class="ol-popup">
        <a href="#" id="popup-closer" class="ol-popup-closer"></a>
        <div id="popup-content"></div>
    </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import 'ol/ol.css';
import geojsonvt from 'geojson-vt';
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import GeoJSON from 'ol/format/GeoJSON.js';
import VectorTileLayer from 'ol/layer/VectorTile.js';
import Projection from 'ol/proj/Projection.js';
import VectorTileSource from 'ol/source/VectorTile.js';
import TileArcGISRest from 'ol/source/TileArcGISRest';
import TileLayer from 'ol/layer/Tile';
import Overlay from 'ol/Overlay';
import { fromLonLat, toLonLat } from 'ol/proj';
import { Style, Circle, Fill, Stroke, Text } from 'ol/style';

const popup = ref(null);
const popupContent = ref(null);
const popupCloser = ref(null);
// 添加當前縮放級別的引用
const currentZoom = ref(3); // 默認值與初始zoom相同

onMounted(async () => {
    // 初始化彈窗
    popupContent.value = document.getElementById('popup-content');
    popupCloser.value = document.getElementById('popup-closer');
    popup.value = new Overlay({
        element: document.getElementById('popup'),
        autoPan: true,
        autoPanAnimation: {
            duration: 250
        }
    });
    // 關閉彈窗的點擊事件
    popupCloser.value.onclick = function () {
        popup.value.setPosition(undefined);
        popupCloser.value.blur();
        return false;
    };
    // Converts geojson-vt data to GeoJSON
    const replacer = function (key, value) {
        if (!value || !value.geometry) {
            return value;
        }

        let type;
        const rawType = value.type;
        let geometry = value.geometry;
        if (rawType === 1) {
            type = 'MultiPoint';
            if (geometry.length == 1) {
                type = 'Point';
                geometry = geometry[0];
            }
        } else if (rawType === 2) {
            type = 'MultiLineString';
            if (geometry.length == 1) {
                type = 'LineString';
                geometry = geometry[0];
            }
        } else if (rawType === 3) {
            type = 'Polygon';
            if (geometry.length > 1) {
                type = 'MultiPolygon';
                geometry = [geometry];
            }
        }

        return {
            'type': 'Feature',
            'geometry': {
                'type': type,
                'coordinates': geometry,
            },
            'properties': value.tags,
        };
    };

    // 創建矢量瓦片圖層樣式
    const createPointStyle = (feature) => {
        // 根據當前縮放級別決定是否顯示文本
        const showLabel = currentZoom.value >= 8; // 當縮放級別大於等於5時顯示標籤
        
        return new Style({
            image: new Circle({
                radius: 6,
                fill: new Fill({ color: 'rgba(255, 0, 0, 0.6)' }),
                stroke: new Stroke({ color: 'white', width: 2 })
            }),
            text: showLabel ? new Text({
                text: feature.get('name'),
                font: '12px Microsoft YaHei',
                offsetY: -15,
                fill: new Fill({ color: '#333' }),
                stroke: new Stroke({ color: '#fff', width: 3 })
            }) : null // 如果不顯示標籤,則設置為null
        });
    };
    const layer = new VectorTileLayer({
        // background: '#1a2b39',
        style: createPointStyle
    });

    const view = new View({
        center: fromLonLat([116.3912, 39.9073]), // 默認中心點為北京
        zoom: 3,
    });
    
    const map = new Map({
        layers: [
            new TileLayer({
                source: new TileArcGISRest({
                    url: 'https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer'
                })
            }),
            layer],
        target: 'map',
        view: view,
        overlays: [popup.value]
    });
    
    // 監聽視圖變化事件,更新當前縮放級別
    view.on('change:resolution', function() {
        const newZoom = Math.round(view.getZoom());
        if (newZoom !== currentZoom.value) {
            currentZoom.value = newZoom;
            // 強制重新渲染圖層以應用新樣式
            layer.changed();
        }
    });
    
    // 添加點擊事件,顯示彈窗
    map.on('click', function (evt) {
        const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) {
            return feature;
        });

        if (feature) {
            const coordinates = feature.getGeometry().getCoordinates();
            const name = feature.get('name');
            const description = feature.get('description');
            
            const coordinates4326 = toLonLat(coordinates);
            popupContent.value.innerHTML = `
            <h3>${name}</h3>
            <p>${description}</p>
            <p>經度: ${coordinates4326[0].toFixed(4)}</p>
            <p>緯度: ${coordinates4326[1].toFixed(4)}</p>
          `;

            popup.value.setPosition(coordinates);
        } else {
            popup.value.setPosition(undefined);
        }
    });
    // 鼠標懸停樣式
    // map.on('pointermove', function (e) {
    //     if (e.dragging) return;

    //     const pixel = map.getEventPixel(e.originalEvent);
    //     const hit = map.hasFeatureAtPixel(pixel);

    //     map.getTargetElement().style.cursor = hit ? 'pointer' : '';
    // });

    const url = 'src/public/data/data.json';
    fetch(url)
        .then(function (response) {
            return response.json();
        })
        .then(function (json) {
            const tileIndex = geojsonvt(json, {
                extent: 4096,
                debug: 1,
            });
            const format = new GeoJSON({
                // Data returned from geojson-vt is in tile pixel units
                dataProjection: new Projection({
                    code: 'TILE_PIXELS',
                    units: 'tile-pixels',
                    extent: [0, 0, 4096, 4096],
                }),
            });
         ……
        });
});
</script>

<style scoped>
.map {
    width: 100%;
    height: 100%;
}
</style>

源碼下載

GIS之家的學習交流圈

user avatar vjmap 头像
点赞 1 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.