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的封装使用_第1张图片

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

_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 : `© ${item.attributions}`      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对它有解释,它有三种值可以设置。

是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)    }  }

你可能感兴趣的:(OpenLayer基于vue的封装使用)