vue-openlayers-plugin
v1.2.5
Published
A Vue 3 plugin for OpenLayers with custom components and utilities
Maintainers
Readme
Vue OpenLayers Plugin
一个基于 Vue 3 和 OpenLayers 的地图插件,提供丰富的地图功能和组件。
特性
- 🗺️ 完整的地图功能 - 基于 OpenLayers 8.x 构建
- 🎨 Vue 3 组件 - 完全支持 Composition API
- 📏 测量工具 - 距离、面积、角度测量
- ✏️ 标绘功能 - 点、线、面、文字等标绘工具
- 🎛️ 图层管理 - 图层控制面板
- 🏷️ 图标图例 - 支持 PNG、JPG、SVG 等格式的图标图例
- 📱 响应式设计 - 适配各种屏幕尺寸
- 🔧 TypeScript 支持 - 完整的类型定义
- 📦 轻量级 - 按需引入,减小包体积
安装
npm install vue-openlayers-plugin
# 或
yarn add vue-openlayers-plugin
# 或
pnpm add vue-openlayers-plugin快速开始
全局注册
import { createApp } from 'vue'
import VueOpenLayersPlugin from 'vue-openlayers-plugin'
import 'vue-openlayers-plugin/style'
const app = createApp(App)
app.use(VueOpenLayersPlugin)
app.mount('#app')如果使用移动端组件(MobileMap、MobileLegend 等),请额外安装并注册 Vant:
npm install vant
# 或
yarn add vant
# 或
pnpm add vantimport { createApp } from 'vue'
import Vant from 'vant'
import 'vant/lib/index.css'
import VueOpenLayersPlugin from 'vue-openlayers-plugin'
import 'vue-openlayers-plugin/style'
const app = createApp(App)
app.use(Vant)
app.use(VueOpenLayersPlugin)
app.mount('#app')按需引入
import { CustomOpenlayer, CustomDialog, SvgIcon, MobileMap } from 'vue-openlayers-plugin'
import 'vue-openlayers-plugin/style'
// 在组件中使用
export default {
components: {
CustomOpenlayer,
CustomDialog,
SvgIcon,
MobileMap
}
}CDN 引入
<!-- 引入 Vue 3 -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<!-- 引入 OpenLayers -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ol.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/ol.css">
<!-- 引入插件 -->
<script src="https://unpkg.com/vue-openlayers-plugin/lib/index.js"></script>
<link rel="stylesheet" href="https://unpkg.com/vue-openlayers-plugin/lib/style.css">基础使用
<template>
<div class="map-container">
<CustomOpenlayer
:center="[116.397428, 39.90923]"
:zoom="10"
:show-layer-panel="true"
:show-measurement-tools="true"
:show-drawing-tools="true"
style="width: 100%; height: 500px;"
@map-ready="onMapReady"
@feature-selected="onFeatureSelected"
/>
</div>
</template>
<script setup lang="ts">
import { CustomOpenlayer } from 'vue-openlayers-plugin'
const onMapReady = (map: unknown) => {
console.log('地图已准备就绪', map)
}
const onFeatureSelected = (feature: any) => {
console.log('选中要素', feature)
}
</script>
<style >
.map-container {
width: 100%;
height: 500px;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>视角书签使用
视角书签的数据由父组件统一管理,通过 viewBookmarks 传给地图组件。地图组件内部只负责展示、应用视角和触发事件,不直接请求后端接口。
推荐接入方式:
- 父组件初始化时调用查询接口,拿到书签列表后传给
CustomOpenlayer - 用户在地图中新增、编辑、删除、清空、应用书签时,地图组件通过
bookmark-action向外抛出事件 - 父组件在
onBookmarkAction中根据action调用新增、更新、删除、清空等接口 - 接口成功后重新查询书签列表,或本地同步更新后再回传给
viewBookmarks
<template>
<CustomOpenlayer
:map-config="mapConfig"
:view-bookmarks="viewBookmarks"
@bookmark-action="onBookmarkAction"
/>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { CustomOpenlayer } from 'vue-openlayers-plugin'
import type { BookmarkAction, MapConfig, ViewBookmark } from 'vue-openlayers-plugin'
const viewBookmarks = ref<ViewBookmark[]>([])
const mapConfig: MapConfig = {
center: [116.397428, 39.90923],
zoom: 10,
projection: 'EPSG:4326',
controls: {
viewBookmarks: true
}
}
const fetchBookmarks = async () => {
const list = await bookmarkApi.list()
viewBookmarks.value = list
}
const onBookmarkAction = async (event: BookmarkAction) => {
const { action, bookmark } = event
if (action === 'add' && bookmark) {
await bookmarkApi.create(bookmark)
}
if (action === 'update' && bookmark) {
await bookmarkApi.update(bookmark.id, bookmark)
}
if (action === 'delete' && bookmark) {
await bookmarkApi.remove(bookmark.id)
}
if (action === 'clear') {
await bookmarkApi.clear()
}
if (action === 'apply' && bookmark) {
console.log('已应用书签视角', bookmark)
}
await fetchBookmarks()
}
onMounted(() => {
fetchBookmarks()
})
const bookmarkApi = {
async list(): Promise<ViewBookmark[]> {
return []
},
async create(bookmark: ViewBookmark) {
return bookmark
},
async update(id: string, bookmark: ViewBookmark) {
return { id, ...bookmark }
},
async remove(id: string) {
return id
},
async clear() {
return true
}
}
</script>数据传递说明
viewBookmarks:父组件传入的书签数组,作为地图组件的书签数据源controls.viewBookmarks: true:开启视角书签按钮,用户点击后会打开“视角书签”面板- 地图组件不会自动持久化书签;是否存本地、是否调后端接口,都由父组件决定
视角书签数据结构
viewBookmarks 的元素类型为 ViewBookmark,结构如下:
interface ViewBookmark {
id: string
name: string
category: string
description: string
center: [number, number]
zoom: number
rotation: number
createTime: string | Date
creator?: string
thumbnail?: string
layerStates?: {
id: string
visible: boolean
opacity: number
title?: string
}[]
}字段说明:
id:书签唯一标识,通常对应后端主键name:书签名称category:书签分类,便于筛选和分组description:书签描述信息center:视角中心点坐标,格式为[经度, 纬度]zoom:缩放级别rotation:旋转角度,单位为度createTime:创建时间,支持字符串或Datecreator:创建人,可选thumbnail:视角缩略图,可选,通常为图片 URL 或 Base64layerStates:保存书签时记录的图层状态,可选,用于恢复图层显隐和透明度
示例数据:
const viewBookmarks: ViewBookmark[] = [
{
id: 'bk-1',
name: '北京中心',
category: '城市',
description: '北京中心视角',
center: [116.407526, 39.90403],
zoom: 12,
rotation: 0,
createTime: '2026-05-05 10:00:00',
creator: 'admin',
thumbnail: 'https://example.com/bookmarks/bk-1.jpg',
layerStates: [
{
id: 'osm',
visible: true,
opacity: 1,
title: 'OpenStreetMap'
},
{
id: 'land_acquisition',
visible: false,
opacity: 0.8,
title: '征地图层'
}
]
}
]事件说明
地图组件会抛出 bookmark-action 事件,事件参数类型如下:
type BookmarkAction = {
action: 'add' | 'update' | 'delete' | 'apply' | 'clear'
bookmark?: ViewBookmark
}不同 action 的含义如下:
add:用户保存了一个新书签update:用户修改了已有书签delete:用户删除了某个书签clear:用户清空了全部书签apply:用户点击应用书签,地图已切换到对应视角
移动端使用(MobileMap)
MobileMap 内置了移动端常用 UI(搜索、图层面板、图例入口等),通过 Props 控制显示:
<template>
<div class="mobile-page">
<MobileMap
:config="mapConfig"
:show-search="true"
:show-layer-panel="true"
:show-legend="true"
@map-ready="onMapReady"
>
<MobileBasemapSwitcher
class="basemap-switcher"
:base-layers="mapConfig.baseLayers"
/>
</MobileMap>
</div>
</template>
<script setup lang="ts">
import { MobileBasemapSwitcher, MobileMap } from 'vue-openlayers-plugin'
import type { MapConfig } from 'vue-openlayers-plugin'
const mapConfig: MapConfig = {
center: [116.397428, 39.90923],
zoom: 10,
baseLayers: [
{
id: 'osm',
name: 'OpenStreetMap',
type: 'osm',
visible: true,
isBasemap: true,
legend: {
title: '开源地图',
items: [
{ label: '道路', color: '#ffffff' },
{ label: '建筑', color: '#f2f2f2' }
]
}
},
{
id: 'arcgis_imagery',
name: 'ArcGIS影像',
type: 'xyz',
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
visible: false,
isBasemap: true,
legend: {
title: '全球影像',
items: [{ label: '高清卫星图', color: '#8B4513' }]
}
}
],
overlayLayers: []
}
const onMapReady = (map: unknown) => {
console.log('移动端地图已准备就绪', map)
}
</script>
<style>
.mobile-page {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.basemap-switcher {
position: absolute;
right: 12px;
bottom: 160px;
z-index: 230;
}
</style>组件
CustomOpenlayer
主要的地图组件,提供完整的地图功能。
Props
| 属性 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| center | [number, number] | [0, 0] | 地图中心点坐标 |
| zoom | number | 2 | 地图缩放级别 |
| minZoom | number | 0 | 最小缩放级别 |
| maxZoom | number | 28 | 最大缩放级别 |
| showLayerPanel | boolean | false | 是否显示图层面板 |
| showMeasurementTools | boolean | false | 是否显示测量工具 |
| showDrawingTools | boolean | false | 是否显示标绘工具 |
| viewBookmarks | ViewBookmark[] | [] | 视角书签列表,由父组件传入 |
Events
| 事件名 | 参数 | 描述 |
|------|------|------|
| bookmark-action | BookmarkAction | 视角书签新增、编辑、删除、清空、应用时触发 |
CustomDialog
可拖拽、可缩放的对话框组件。
Props
| 属性 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| modelValue | boolean | false | 对话框显示状态 |
| title | string | '' | 对话框标题 |
| width | string | '500px' | 对话框宽度 |
| height | string | '300px' | 对话框高度 |
| draggable | boolean | true | 是否可拖拽 |
| resizable | boolean | true | 是否可缩放 |
LegendPanel
图例面板组件,支持多种图例类型包括图标图例。
Props
| 属性 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| position | 'left' \| 'right' | 'right' | 面板位置 |
| width | number | 280 | 面板宽度 |
| collapsible | boolean | true | 是否可折叠 |
| defaultCollapsed | boolean | false | 默认是否折叠 |
| showOnlyVisible | boolean | true | 只显示可见图层的图例 |
| leftOffset | string \| number | undefined | 左侧偏移 |
| rightOffset | string \| number | undefined | 右侧偏移 |
| topOffset | string \| number | undefined | 顶部偏移 |
| bottomOffset | string \| number | undefined | 底部偏移 |
支持的图例类型
- icon - 图标图例,支持 PNG、JPG、SVG 等格式
- color - 颜色图例
- symbol - 符号图例
- gradient - 渐变图例
- category - 分类图例
- custom - 自定义图例
图标图例配置示例
const layerConfig = {
id: 'poi-layer',
name: 'POI图层',
legend: {
type: 'icon',
title: 'POI类型',
items: [
{
label: '餐厅',
value: 'restaurant',
image: '/icons/restaurant.svg',
size: 18
},
{
label: '酒店',
value: 'hotel',
image: '/icons/hotel.png',
size: 16
}
]
}
}SvgIcon
SVG 图标组件。
Props
| 属性 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| name | string | '' | 图标名称 |
| size | string \| number | '1em' | 图标大小 |
| color | string | 'currentColor' | 图标颜色 |
工具函数
测量工具
import { MeasurementTool } from 'vue-openlayers-plugin'
const measurementTool = new MeasurementTool(map)
measurementTool.startMeasurement('length') // 'length' | 'area' | 'angle'图层管理
import { LayerManager } from 'vue-openlayers-plugin'
const layerManager = new LayerManager(map)
layerManager.addLayer({
id: 'osm',
name: 'OpenStreetMap',
type: 'tile',
url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png'
})GeoJSON 请求图层配置
GeoJSON 图层除了支持直接传入 data,也支持通过 geojsonConfig.request 发起远程请求,适合按行政区、业务类型、时间范围等条件动态加载矢量数据。
const mapConfig = {
center: [121.473, 31.23],
zoom: 12,
projection: 'EPSG:4326',
vectorLayers: [
{
id: 'land_acquisition',
name: '征地',
type: 'GeoJSON',
visible: true,
opacity: 80,
geojsonLandType: '征地未结案',
geojsonConfig: {
request: {
url: '/zzd/parcel/geojson/by-user-land-type',
method: 'GET',
headers: {
Authorization: 'Bearer <token>',
'X-Tenant-Id': 'demo'
},
params: {
landType: '征地未结案'
},
managementUnitParamName: 'managementUnit',
landTypeParamName: 'landType',
dataPath: 'data.features'
}
},
style: {
fill: {
color: 'rgba(255, 0, 0, 0.2)'
},
stroke: {
color: '#FF0000',
width: 2
}
}
}
]
}GeoJSON 请求配置项
| 字段 | 类型 | 说明 |
|------|------|------|
| geojsonConfig.data | object \| string | 直接传入 GeoJSON 数据,适合本地静态数据 |
| geojsonConfig.request.url | string | 请求地址 |
| geojsonConfig.request.method | 'GET' \| 'POST' \| 'PUT' \| 'DELETE' | 请求方法,默认 GET |
| geojsonConfig.request.headers | Record<string, string> | 自定义请求头,比如 Authorization |
| geojsonConfig.request.params | Record<string, any> | 查询参数,会自动拼接到 URL 上 |
| geojsonConfig.request.body | Record<string, any> \| string | 非 GET 请求体 |
| geojsonConfig.request.dataPath | string | 返回结果中的 GeoJSON 数据路径,例如 data.features |
| geojsonConfig.request.managementUnit | string | 管理单位值 |
| geojsonConfig.request.managementUnitParamName | string | 管理单位参数名,默认 managementUnit |
| geojsonConfig.request.landType | string | 地类值 |
| geojsonConfig.request.landTypeParamName | string | 地类参数名,默认 landType |
| geojsonConfig.refresh | object | 刷新配置,支持定时刷新、缩放后刷新、移动后刷新 |
请求头设置方式
最直接的方式是在 geojsonConfig.request.headers 中传入:
geojsonConfig: {
request: {
url: '/api/parcel/geojson',
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'X-Project-Id': 'project-a'
}
}
}如果不希望把 token 写死在配置中,可以使用请求拦截器动态注入:
geojsonConfig: {
request: {
url: '/api/parcel/geojson',
method: 'GET',
requestInterceptor: async (request) => {
const token = localStorage.getItem('token') || ''
return {
headers: {
...request.headers,
Authorization: `Bearer ${token}`
}
}
}
}
}响应转换与数据解析
如果后端返回结构不固定,除了 dataPath 之外,还可以使用 responseInterceptor 或 responseParser 做二次处理:
geojsonConfig: {
request: {
url: '/api/parcel/geojson',
method: 'GET',
responseParser: async (response) => {
return response.result.list
}
}
}运行时刷新 GeoJSON 图层
组件实例暴露了 refreshLayer 和 updateLayerRequestParams,可以在不重建地图的情况下更新请求参数并重新拉取数据:
<script setup lang="ts">
import { ref } from 'vue'
import { CustomOpenlayer } from 'vue-openlayers-plugin'
const mapRef = ref<InstanceType<typeof CustomOpenlayer> | null>(null)
const updateLandType = async () => {
mapRef.value?.updateLayerRequestParams('land_acquisition', {
landType: '征地已结案'
})
await mapRef.value?.refreshLayer('land_acquisition')
}
</script>使用建议
- 需要鉴权时,优先使用
headers或requestInterceptor - 参数频繁变化时,优先使用
updateLayerRequestParams - 后端返回结构复杂时,优先使用
dataPath,不够再使用responseParser - 如果鉴权依赖 Cookie,也可以不传
headers,由浏览器自动携带
标绘工具
import { DrawingManager } from 'vue-openlayers-plugin'
const drawingManager = new DrawingManager(map)
drawingManager.startDrawing('polygon')GeoJSON 数据导入
支持从标准 GeoJSON 格式导入地理数据:
// 导入单个 GeoJSON Feature
const feature = {
type: "Feature",
properties: {
name: "示例多边形",
style: {
fillColor: "#ffcc00",
fillOpacity: 0.4,
strokeColor: "#ff6600",
strokeWidth: 2
}
},
geometry: {
type: "Polygon",
coordinates: [[[93.2, 42.45], [93.6, 42.95], [94.2, 42.8], [93.2, 42.45]]]
}
}
const drawing = drawingManager.createDrawingFromJSON(feature)
drawingManager.addDrawing(drawing)
// 批量导入 GeoJSON FeatureCollection
const featureCollection = {
type: "FeatureCollection",
features: [feature1, feature2, feature3]
}
drawingManager.addGeoJSONData(featureCollection)支持的几何类型:
- Point(点)
- LineString(线)
- Polygon(多边形)
- MultiPoint(多点)
- MultiLineString(多线)
- MultiPolygon(多多边形)
支持的样式属性:
fillColor- 填充颜色fillOpacity- 填充透明度strokeColor- 边框颜色strokeWidth- 边框宽度markerColor- 点标记颜色markerSize- 点标记大小markerSymbol- 点标记符号lineDash- 虚线样式
开发
# 克隆项目
git clone https://github.com/your-username/vue-openlayers-plugin.git
cd vue-openlayers-plugin
# 安装依赖
npm install
# 启动开发服务器
npm run dev
# 构建库
npm run build:lib
# 类型检查
npm run type-check浏览器支持
- Chrome >= 87
- Firefox >= 78
- Safari >= 14
- Edge >= 88
许可证
MIT License
贡献
欢迎提交 Issue 和 Pull Request!
更新日志
1.2.0
- ✨ 新增图标图例功能,支持 PNG、JPG、SVG、GIF、WebP 等图像格式
- ✨ 新增
LegendPanel组件的icon图例类型 - ✨ 支持图标大小自定义和错误处理
- ✨ 新增图标图例配置指南和示例
- 🔧 更新
LegendItem和LegendConfig接口,增加图标相关属性 - 📚 完善文档,添加图标图例使用说明
1.1.0
- ✨ 新增 GeoJSON 数据导入支持
- ✨ 新增
createDrawingFromJSON方法,支持标准 GeoJSON Feature 转换 - ✨ 新增
addGeoJSONData方法,支持批量导入 GeoJSON FeatureCollection - ✨ 支持 GeoJSON 样式属性映射(fillColor、strokeColor、markerColor 等)
- ✨ 支持多种几何类型:Point、LineString、Polygon、MultiPoint、MultiLineString、MultiPolygon
- 🐛 修复私有方法命名冲突问题
1.0.0
- 🎉 初始版本发布
- ✨ 支持基础地图功能
- ✨ 测量工具
- ✨ 标绘功能
- ✨ 图层管理
- ✨ 自定义对话框
- ✨ SVG 图标组件
