OpenLayer基于vue的封装使用教程

作者:GhostPaints 时间:2024-05-11 09:16:22 

openlayer是目前我们gis常用的一款开源的,并且反馈都特别好的软件了,像之前的ol3, 风靡一时,地图实现也很简单,很实用,目前vue中使用地图也是非常多的,那么如果在vue中引入openlayer并且实现地图撒点效果,甚至是更深层的地图聚合效果呢,本文来分享下OpenLayer基于vue的封装使用,感兴趣的朋友一起看看吧!

前言

公司项目使用了openlayer作为2d平面地图来使用,之前没有接触过,开一篇文章记录一下。顺便捋一下代码里面封装的结构。

基本结构 

openlayer使用的版本是"^6.4.3",引入了mapbox的样式,"ol-mapbox-style": "^8.2.0"。地图的初始化专门封装了一个class类,用于初始化地图使用。

import Object from 'ol/Object'
import View from 'ol/View'
import Map from 'ol/Map'

class EMap extends Object {
 constructor (options) {
   super(options)
   this.options = assignObj({}, options)
   this._view = undefined
   this._baseLayers = []
   this._map = undefined
   this.vectorLayers = []
   this.rasterLayers = []
   this.controls = []
   this._mapClickFunc = options.mapclickFunction
   this._mapEvtBus = options.mapEvtBus

this._interactionsState = {}
   this._initMap()
 }
}

assignObj方法是Object.assign方法,但是刚好ol自己有一个Object类,避免冲突就需要更改一下这个方法名了。

主要结构有这几种:map地图,view视图,layer图层,controls控制器,mapClickFunc地图相关的点击事件,mapEvtBus地图事件总线。

_initMap()方法用来初始化地图。方法代码内容如下:

_initMap () {
   this._view = this._createView()
   this._baseLayers = this._createBaseLayer()
   this._map = this._createMap()
   this._initMapEvt()
 }

_createView 

_createView()方法用来初始化view视图。方法代码内容如下:

import {get as getProject} from 'ol/proj'
_createView () {
   let viewOptions = assignObj( this._getDefaultViewOptions(), this.options.view)
   if (!viewOptions.projectionCode) {
     viewOptions.projection = 'EPSG:3857'
   } else {
     viewOptions.projection = `EPSG:${viewOptions.projectionCode}`
   }
   delete viewOptions.projectionCode

// let projection = getProject(viewOptions.projection)
   // if (!projection) {
   //  projection = getProject('EPSG:4326')
   // }
   // let projectionExtent = projection.getExtent()
   // let width = getWidth(projectionExtent)
   // let resolutions = []
   // for (let z = 0; z < 25; z++) {
   //   resolutions[z] = width / (256 * Math.pow(2, z))
   // }

// console.log('分辨率1', resolutions)

// viewOptions.resolutions = resolutions
   let view = new View(viewOptions)
   return view
 }

首先通过_getDefaultViewOptions方法,获取view的一些默认配置,然后将传入的options的配置使用assign方法进行合并。

然后就是判断坐标系编码,这个判断逻辑可以根据需要来更改,ol默认的坐标系就是3857,在官网中有说明。

OpenLayer基于vue的封装使用教程

 注释掉的代码,是对分辨率进行的处理,根据需要可以自行添加进去。

_getDefaultViewOptions()方法存储一些默认配置,比如中心点,坐标系,缩放这种。

_getDefaultViewOptions() {
 let options = {
   projectionCode: '3857',
   center: [120, 69],
   zoom: 5
 }
 return options
}

如果地图的配置项是通过接口获取数据,那默认配置最好和接口返回的数据对应,这样即使接口中有某个数据没法通过校验,就可以使用默认值了。校验方法放在_createView中和默认配置分开,逻辑会清晰点,不会挤在同一个方法里面。

_createBaselayer

_createBaselayer()方法主要是创建底图,底图可能是天地图,mapbox,高德,百度等,因此不同的底图执行的代码逻辑是不一样的,需要加判断分别处理。

_createBaseLayer () {
   const baseLayerOptions = this.options.baseLayer
   if (!baseLayerOptions.type ) {
     baseLayerOptions.type = 'mapbox'
   }

if (baseLayerOptions.type === 'tianditu') {
     return this._createTianDiTuLayers(baseLayerOptions)
   } else if (baseLayerOptions.type === 'mapbox') {
     return this._createMapBoxLayers(baseLayerOptions)
   } else {
     return this._createXYZLayer(baseLayerOptions)
   }
 }

以处理天地图_createTianDiTuLayers为例,通过接口请求到的底图参数中有一个baseLayer属性,存储一个对象,除了携带type属性外,还有对应的token信息。

import {createXYZ} from 'ol/tilegrid'
import Tile from 'ol/layer/Tile'
import XYZ from 'ol/source/XYZ'

_createTianDiTuLayers() {
   const tdtToken = baseLayerOptions.tdtToken
   const baseURL = 'http://t{0-7}.tianditu.gov.cn/'
   const layerOptions = [
     {
       title: '天地图矢量',
       layerName: 'vec_c',
       attributions: '右下角署名',
       visible: true,
       type: 'vec'
     },
     {
       title: '天地图矢量注记',
       layerName: 'cva_c',
       attributions: '',
       visible: true,
       type: 'vec'
     },
     {
       title: '天地图卫星影像',
       layerName: 'img_c',
       attributions: '右下角署名',
       visible: false,
       type: 'img'
     },
     {
       title: '天地图卫星影像注记',
       layerName: 'cia_c',
       attributions: '',
       visible: false,
       type: 'img'
     },
   ]
}

底图可以是多个图层叠加的,因此baseLayers是一个数组。layerOptions存储了一些天地图的信息,通过visible设置是否启用,一般是矢量图或者图片加上对应的标注。

var projection = new getProject('EPSG:3857')

let tilegrid = createXYZ({
     extent: projection.getExtent()
   })

const layers = layerOptions.map((item) => {
     let layerType = item.layerName.split('_')
     const url = `${baseURL}${item.layerName}/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=${layerType[0]}&STYLE=default&TILEMATRIXSET=${layerType[1]}&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tdtToken}`
     const attributions = item.attributions === '' ? undefined : `? <a href="http://www.baidu.com" target="_blank">${item.attributions}</a>`
     const layer = new Tile({
       title: item.title,
       source: new XYZ({
         attributions: attributions,
         url: url,
         wrapx: false,
         crossOrigin: 'anonymous',
         projection: projection,
         tileGrid: tilegrid
       }),
       minZoom: 0,
       maxZoom: 20
     })
     layer.setProperties({
       layerType: item.type,
       isBaseLayer: true
     })
     layer.setVisible(item.visible)
     return layer
   })

最主要的内容还是layer,使用ol/layer/Tile设置标题,数据源,最大最小缩放,tileGrid根据坐标系设置范围。openlayer的图层添加后,会在右下角有一个感叹号,里面的内容就是由source的attributions来定义的。crossOrigin是设置canvas的跨域属性。mdn对它有解释,它有三种值可以设置。

OpenLayer基于vue的封装使用教程

是h5的特性支持,和openlayer无关就是了。

为layer设置了两个值,这两个值本身是没有的,用setProperties添加进去。后续可以使用getProperties()来获取这两个值。根据设置好的visible设置layer的可见性。这样关于天地图的底图设置逻辑就完成了。 

_createMap

_createMap()方法创建map地图,添加一些控件,代码中添加了一个比例尺

import ScaleLine from 'ol/control/ScaleLine'
import { defaults } from 'ol/control'

_createMap () {
   const map = new Map({
     target: this.options.target,
     view: this._view,
     layers: this._baseLayers,
     controls: new defaults({
       attribution: true,
       attributionOptions: {
         tipLabel: '信息'
       },
       zoomOptions: {
         zoomInTipLabel: '放大',
         zoomOutTipLabel: '缩小',
       }
     })
   })
   const scale = new ScaleLine({
     bar: true,
     text: true,
     minWidth: 125
   })
   map.addControl(scale)
   return map
 }

 _initMapEvt

_initMapEvt()处理地图的一些控制和交互功能。

_initMapEvt () {
   this._initMapControl()
   this._initMapClickEvent()
   this._initPointMoveEvent()
 }

 _initMapControl

_initMapControl()方法主要是去除一些容易和后面的操作冲突的事件。

import DoubleClickZoom from 'ol/interaction/DoubleClickZoom'

_initMapControl () {
   // 移除双击缩放控件(与双击弹属性窗冲突)
   let controls = this._map.getInteractions()
   let dbClickZoomControl = controls.getArray().find((control) => control instanceof DoubleClickZoom)
   if(dbClickZoomControl) {
     this._map.removeInteraction(dbClickZoomControl)
   }

this._singleClickControl = new Select({
     condition: function (evt) {
       return evt.type === 'singleclick' || evt.type === 'dblclick'
     },
     // style: this._singleClickStyle.bind(this), // 如果需要自定义每个图层的选中样式,请开启这个属性
     layers: function (layer) {
       const layerName = layer.rootLayerName
       return this.findLayer(this.vectorLayers, layerName)
     }.bind(this)
   })

var selectedFeatures = this._singleClickControl.getFeatures()
   selectedFeatures.on(['add','remove'], (evt) => {
     this.dispatchEvent({
       type: 'selectDataChanged',
       target: selectedFeatures,
       element: evt.element,
       option: evt.type
     })
   })

if(this._map) {
     this._map.addInteraction(this._singleClickControl)
   }
 }

 使用getInteractions()获取到所有交互,用类型检测出双击事件,然后移除。再加入自定义的singleClickControl,在add和remove的时候触发。

 _initMapClickEvent()

_initMapClickEvent () {
   this._map.on('click', (evt) => {
     // 单击事件优先选择控件中的单击选中事件
     const features = this._map.getFeaturesAtPixel(evt.pixel)
     if(features.length > 0) {
       features.forEach((ft) => {
         // map上的单击事件和layer的单击事件,取其一,优先map
         if(this._mapClickFunc) {
           this._mapClickFunc({
             data: ft,
             evt: evt
           })
         } else {
           const layerName = ft.get('layerName')
           const eLayer = this.findLayer(this.vectorLayers, layerName)
           if(eLayer) {
             eLayer._singleClick(ft, evt)
           }
         }
       })
     } else {
       if(this._mapClickFunc) {
         this._mapClickFunc({
           data: undefined,
           evt: evt
         })
       }
     }
   })

this._map.on('dblclick', (evt) => {
     const features = this._map.getFeaturesAtPixel(evt.pixel)
     if (features.length > 0) {
       features.forEach((ft) => {
         const layerName = ft.get('layerName')
         const eLayer = this.findLayer(this.vectorLayers, layerName)
         if(eLayer) {
           eLayer._dbClick(ft, evt)
         }
       })
     }
   })
 }

_initMapClickEvent()主要处理单击和双击事件,后续加入进去的layer图层可以自己定义单击事件。初始化map对象的时候,也可以自己传入mapClickFunc。代码中优先取map的单击事件。

findLayer方法为自定义方法,主要是通过layername拿到对应的layer。

_initPointMoveEvent

_initPointMoveEvent () {
   this._map.on('pointermove', (evt) => {
     const features = this._map.getFeaturesAtPixel(evt.pixel)
     if(features.length > 0) {
       this._map.getTargetElement().style.cursor = 'pointer'
     } else {
       this._map.getTargetElement().style.cursor = 'auto'
     }
   })
 }

 _initPointMoveEvent()方法,当鼠标移动到某个features上时候,鼠标形状改变。用来告诉用户,鼠标位置存在可以交互的东西。

然后就是一些普通的getter和setter方法。可以按自己喜好多封装一些常用的。

getOlMap () {
   return this._map
 }

getView () {
   return this._view
 }

getMapProjection () {
   return this.getView().getProjection()
 }

getZoom () {
   if(this._view) {
     return this._view.getZoom()
   }
 }

getBaseLayers () {
   return this._baseLayers
 }

setZoom (zoom) {
   if (this._view) {
     this._view.setZoom(zoom)
   }
 }

setCenter (point) {
   this._view.setCenter(point)
 }

setView (view) {
   this._map.setView(view)
   this._view = view
 }

zoomToNext () {
   let zoom = this.getZoom()
   zoom = parseInt(zoom)
   this.setZoom(zoom + 1)
 }

fit (geom) {
   this._view.fit(geom)
 }

fitToLayer (eLayer) {
   if(eLayer.getDataExtent) {
     const extent = eLayer.getDataExtent()
     const resolution = this._view.getResolution()
     // 范围缩小一点,要不然碰到地图边界
     extent[0] = extent[0] - 1 * resolution
     extent[1] = extent[1] - 1 * resolution
     extent[2] = extent[2] + 1 * resolution
     extent[3] = extent[3] + 1 * resolution
     if (extent) {
       this.fit(extent)
     }
   }
 }

zoomToPrevious () {
   let zoom = this.getZoom()
   zoom = parseInt(zoom)
   this.setZoom(zoom - 1)
 }

getExtent () {
   return this._view.calculateExtent(this._map.getSize())
 }

地图的初始化操作就这么多,接下来就是一些layer图层上面的添加,查找,移除的操作。

import _ from 'lodash'

addLayer (eLayer) {
   const layer = eLayer.getLayer()
   if (layer) {
     if (eLayer.get('eLayerType') === layerDataType.vector) {
       if (!this.findLayer(this.vectorLayers, eLayer.get('layerName'))) {
         this.vectorLayers.push(eLayer)
         this._map.addLayer(layer)
       } else {
         console.log('layer is exist')
       }
     } else if (eLayer.get('eLayerType') === layerDataType.raster ) {
       if (!this.findLayer(this.rasterLayers, eLayer.get('layerName'))) {
         this.rasterLayers.push(eLayer)
         this._map.addLayer(layer)
       } else {  
         console.log('layer is exist')
       }
     } else {
       console.log('layer is not eMapLayer...')
     }
   }
 }

findLayer (layerList, layerName) {
   if (layerList) {
     const layer = _.find(layerList, (layer) => {
       return layer.get('layerName') === layerName
     })
     return layer
   }
   return null
 }

removeLayer (eLayer) {
   const layer = eLayer.getLayer()
   if (layer) {
     if (eLayer.get('eLayerType') === layerDataType.vector) {
       _.remove(this.vectorLayers, (layer) => {
         return layer === eLayer
       })
       this._map.removeLayer(layer)
     } else if(eLayer.get('eLayerType') === layerDataType.raster) {
       _.remove(this.rasterLayers, (layer) => {
         return layer === eLayer
       })
       this._map.removeLayer(layer)
     } else {
       console.log('layer is not eMapLayer...')
     }
   }
 }

removeLayerByName (layerName) {
   let eLayer = this.findLayer(this.vectorLayers, layerName)
   if (eLayer) {
     this.removeLayer(eLayer)
   } else {
     eLayer = this.findLayer(this.rasterLayers, layerName)
     if (eLayer) {
       this.removeLayer(eLayer)
     }
   }
 }

后面layer图层也会进行一次封装,有一个eLayerType的字符串值,决定是放在哪个图层数组里面。名称不能重复,如果检测到重复名称说明图层已经添加过了,就不会重新添加了。

当存在一些编辑功能的时候,防止冲突,就要停止和恢复一些交互功能。封装两个方法。

/**
  * 暂停作用域以外的交互控件(默认不暂停)
  * @param {string}} scope
  */
 pauseInteraction (scope) {
   let interactions = this._map.getInteractions()
   interactions.forEach((itc) => {
     if(!itc.rootName) {
       return
     }
     if(itc.rootName !== scope) {
       let id = itc.ol_uid
       this._interactionsState[id] = itc.getActive()
       itc.setActive(false)
     }
   })
 }

resumeInteraction () {
   let interactions = this._map.getInteractions()
   interactions.forEach((itc) => {
     if(itc.rootName) {
       let id = itc.ol_uid
       let active = this._interactionsState[id]
       if(active) {
         itc.setActive(active)
       }
     }
   })
 }

单击显示的数据

showDetail (data, zoom, point, id, geomType) {
   if (this._mapEvtBus) {
     const options = {
       data,
       zoom,
       point,
       id,
       geomType
     }
     this._mapEvtBus.$emit('showDetail', options)
   }
 }

来源:https://blog.csdn.net/GhostPaints/article/details/127257586

标签:vue,OpenLayer,封装
0
投稿

猜你喜欢

  • pytorch中的广播语义

    2023-04-22 15:16:36
  • pytorch 禁止/允许计算局部梯度的操作

    2021-01-17 01:55:35
  • 考虑SQL Server安全时所应注意的几个方面

    2009-01-04 13:57:00
  • 基于telepath库实现Python和JavaScript之间交换数据

    2023-05-24 04:20:39
  • Python星号*与**用法分析

    2021-11-30 10:32:50
  • 详解微信小程序中的页面代码中的模板的封装

    2024-04-29 13:40:35
  • Python 通过调用接口获取公交信息的实例

    2023-06-17 21:16:46
  • 如何在Django中使用聚合的实现示例

    2021-08-02 10:32:30
  • 9行Python3代码实现批量提取PDF文件的指定内容

    2023-02-12 03:22:41
  • python对一个数向上取整的实例方法

    2023-04-12 09:07:50
  • 简单了解SQL常用删除语句原理区别

    2024-01-14 22:38:57
  • Matplotlib实现各种条形图绘制

    2023-11-20 15:52:51
  • 10个顶级Python实用库推荐

    2023-08-27 17:41:46
  • js实现动态增加文件域表单功能

    2024-04-19 09:50:33
  • 利用 PyCharm 实现本地代码和远端的实时同步功能

    2022-03-05 08:54:10
  • 这十大Python库你真应该知道

    2022-09-20 00:09:03
  • opencv3/python 鼠标响应操作详解

    2022-10-11 09:29:52
  • Python+tkinter实现制作文章搜索软件

    2021-02-01 15:11:05
  • 基于Django框架的rest_framework的身份验证和权限解析

    2021-02-21 23:42:58
  • python turtle库画一个方格和圆实例

    2021-09-25 14:06:10
  • asp之家 网络编程 m.aspxhome.com