OpenLayer基于vue的封裝使用教程
openlayer是目前我們gis常用的一款開源的,并且反饋都特別好的軟件了,像之前的ol3, 風靡一時,地圖實現(xiàn)也很簡單,很實用,目前vue中使用地圖也是非常多的,那么如果在vue中引入openlayer并且實現(xiàn)地圖撒點效果,甚至是更深層的地圖聚合效果呢,本文來分享下OpenLayer基于vue的封裝使用,感興趣的朋友一起看看吧!
前言
公司項目使用了openlayer作為2d平面地圖來使用,之前沒有接觸過,開一篇文章記錄一下。順便捋一下代碼里面封裝的結(jié)構(gòu)。
基本結(jié)構(gòu)
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類,避免沖突就需要更改一下這個方法名了。
主要結(jié)構(gòu)有這幾種:map地圖,view視圖,layer圖層,controls控制器,mapClickFunc地圖相關(guān)的點擊事件,mapEvtBus地圖事件總線。
_initMap()方法用來初始化地圖。方法代碼內(nèi)容如下:
_initMap () { this._view = this._createView() this._baseLayers = this._createBaseLayer() this._map = this._createMap() this._initMapEvt() }
_createView
_createView()方法用來初始化view視圖。方法代碼內(nèi)容如下:
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方法進行合并。
然后就是判斷坐標系編碼,這個判斷邏輯可以根據(jù)需要來更改,ol默認的坐標系就是3857,在官網(wǎng)中有說明。
注釋掉的代碼,是對分辨率進行的處理,根據(jù)需要可以自行添加進去。
_getDefaultViewOptions()方法存儲一些默認配置,比如中心點,坐標系,縮放這種。
_getDefaultViewOptions() { let options = { projectionCode: '3857', center: [120, 69], zoom: 5 } return options }
如果地圖的配置項是通過接口獲取數(shù)據(jù),那默認配置最好和接口返回的數(shù)據(jù)對應(yīng),這樣即使接口中有某個數(shù)據(jù)沒法通過校驗,就可以使用默認值了。校驗方法放在_createView中和默認配置分開,邏輯會清晰點,不會擠在同一個方法里面。
_createBaselayer
_createBaselayer()方法主要是創(chuàng)建底圖,底圖可能是天地圖,mapbox,高德,百度等,因此不同的底圖執(zhí)行的代碼邏輯是不一樣的,需要加判斷分別處理。
_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為例,通過接口請求到的底圖參數(shù)中有一個baseLayer屬性,存儲一個對象,除了攜帶type屬性外,還有對應(yīng)的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: '天地圖衛(wèi)星影像', layerName: 'img_c', attributions: '右下角署名', visible: false, type: 'img' }, { title: '天地圖衛(wèi)星影像注記', layerName: 'cia_c', attributions: '', visible: false, type: 'img' }, ] }
底圖可以是多個圖層疊加的,因此baseLayers是一個數(shù)組。layerOptions存儲了一些天地圖的信息,通過visible設(shè)置是否啟用,一般是矢量圖或者圖片加上對應(yīng)的標注。
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 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 })
最主要的內(nèi)容還是layer,使用ol/layer/Tile設(shè)置標題,數(shù)據(jù)源,最大最小縮放,tileGrid根據(jù)坐標系設(shè)置范圍。openlayer的圖層添加后,會在右下角有一個感嘆號,里面的內(nèi)容就是由source的attributions來定義的。crossOrigin是設(shè)置canvas的跨域?qū)傩浴dn對它有解釋,它有三種值可以設(shè)置。
是h5的特性支持,和openlayer無關(guān)就是了。
為layer設(shè)置了兩個值,這兩個值本身是沒有的,用setProperties添加進去。后續(xù)可以使用getProperties()來獲取這兩個值。根據(jù)設(shè)置好的visible設(shè)置layer的可見性。這樣關(guān)于天地圖的底圖設(shè)置邏輯就完成了。
_createMap
_createMap()方法創(chuàng)建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的時候觸發(fā)。
_initMapClickEvent()
_initMapClickEvent () { this._map.on('click', (evt) => { // 單擊事件優(yōu)先選擇控件中的單擊選中事件 const features = this._map.getFeaturesAtPixel(evt.pixel) if(features.length > 0) { features.forEach((ft) => { // map上的單擊事件和layer的單擊事件,取其一,優(yōu)先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()主要處理單擊和雙擊事件,后續(xù)加入進去的layer圖層可以自己定義單擊事件。初始化map對象的時候,也可以自己傳入mapClickFunc。代碼中優(yōu)先取map的單擊事件。
findLayer方法為自定義方法,主要是通過layername拿到對應(yīng)的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的字符串值,決定是放在哪個圖層數(shù)組里面。名稱不能重復(fù),如果檢測到重復(fù)名稱說明圖層已經(jīng)添加過了,就不會重新添加了。
當存在一些編輯功能的時候,防止沖突,就要停止和恢復(fù)一些交互功能。封裝兩個方法。
/** * 暫停作用域以外的交互控件(默認不暫停) * @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) } } }) }
單擊顯示的數(shù)據(jù)
showDetail (data, zoom, point, id, geomType) { if (this._mapEvtBus) { const options = { data, zoom, point, id, geomType } this._mapEvtBus.$emit('showDetail', options) } }
到此這篇關(guān)于OpenLayer基于vue的封裝使用的文章就介紹到這了,更多相關(guān)vue OpenLayer內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue項目動態(tài)渲染input,綁定的參數(shù)不實時更新問題
這篇文章主要介紹了vue項目動態(tài)渲染input,綁定的參數(shù)不實時更新問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03解決vue中修改export default中腳本報一大堆錯的問題
今天小編就為大家分享一篇解決vue中修改export default中腳本報一大堆錯的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08vue中設(shè)置echarts寬度自適應(yīng)的代碼步驟
這篇文章主要介紹了vue中設(shè)置echarts寬度自適應(yīng)的問題及解決方案,常常需要做到echarts圖表的自適應(yīng),一般是根據(jù)頁面的寬度做對應(yīng)的適應(yīng),本文記錄一下設(shè)置echarts圖表的自適應(yīng)的步驟,需要的朋友可以參考下2022-09-09