接到一位知識星友的邀請,他那邊的需求是需要矢量瓦片渲染點數據,所以針對他的業務需求定製開發一個vue+oepnlayers版本的矢量瓦片渲染示例
demo源碼運行環境以及配置
- 運行環境:依賴Node安裝環境,demo本地Node版本:推薦v16+。
- 運行工具:vscode或者其他工具。
- 配置方式:下載demo源碼,vscode打開,然後順序執行以下命令:
(1)下載demo環境依賴包命令:npm i
(2)啓動demo命令:npm run dev
(3)打包demo命令: npm run build
示例效果
技術棧
- 前端框架 :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數據轉換為矢量瓦片,主要實現步驟:
- 加載GeoJSON數據
- 使用
geojson-vt創建瓦片索引 - 自定義
VectorTileSource的tileUrlFunction和tileLoadFunction - 在
tileLoadFunction中從瓦片索引獲取對應瓦片數據 - 將瓦片數據轉換為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之家的學習交流圈