文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

OpenLayer基于vue的封装使用教程

2022-11-13 18:17

关注

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">${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的字符串值,决定是放在哪个图层数组里面。名称不能重复,如果检测到重复名称说明图层已经添加过了,就不会重新添加了。

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

  
  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内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯