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,在官网中有说明。

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

_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对它有解释,它有三种值可以设置。

是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的封装使用的文章就介绍到这了,更多相关vue OpenLayer内容请搜索阿兔在线工具以前的文章或继续浏览下面的相关文章希望大家以后多多支持阿兔在线工具!

点赞(0)

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部