vue+openlayers 开发一个卷帘图层控件

4 篇文章 0 订阅
2 篇文章 0 订阅

vue+openlayers 开发一个卷帘图层控件

在这里插入图片描述

卷帘功能是gis项目中常见的一种基础功能,主要用于数据的叠加比对。在openlayers中实现卷帘图层并不复杂,下面开讲怎么做。

在vue中开发一个openlayers控件

开发openlayers控件主要使用ol/control/Control,openlayers中所有的control都是继承这个类。
这个类接收3个参数 element,target,render,我们关注element和target就可以,render暂时用不到。

new Control({
  element: 控件的dom元素,
  target: 控件创建位置的dom元素,当需要创建在地图元素之外时才需要设置这个,
})

在vue中,我们在template中创建面板,然后在mount中创建Control,通过refs将dom元素作为参数,就可以创建出一个自定义控件。

<template>
<div v-show="false">
	<div ref="myControl">...</div>
</div>
</template>
<script>
import Control from "ol/control/Control"
export default {
	...
	mounted(){
		//	创建openlayers的Map和view
		...
		let myControl = new Control({
		  element: this.$refs["myControl"],
		})
		map.addControl(myControl)
	}
}
</script>

卷帘功能原理

卷帘功能由两个部分组成,操作面板(分割线和按钮)和图层。
逻辑是按钮按下时拖动分割线,根据分割线当前位置控制图层显示部分。

先随便创建一个地图并添加一个图层

从官网示例复制的,数据服务都是公开的。

<template>
  <div>
    <div id="map"></div>
  </div>
</template>
<script>
import "ol/ol.css"
import Map from "ol/Map"
import XYZ from "ol/source/XYZ"
import TileLayer from "ol/layer/Tile"
import View from "ol/View"
import GeoJSON from 'ol/format/GeoJSON.js';
import VectorLayer from 'ol/layer/Vector.js';
import VectorSource from 'ol/source/Vector.js';
import {Fill, Style} from 'ol/style.js';
export default {
  data() {
    return {}
  },
  computed: {},
  methods: {},
  mounted() {
    const layer = new TileLayer({
      source: new XYZ({
        attributions: 'Tiles © <a href="https://services.arcgisonline.com/ArcGIS/' + 'rest/services/World_Topo_Map/MapServer">ArcGIS</a>',
        url: "https://server.arcgisonline.com/ArcGIS/rest/services/" + "World_Topo_Map/MapServer/tile/{z}/{y}/{x}",
      }),
    })
    const style = new Style({
      fill: new Fill({
        color: "#eeeeee",
      }),
    })

    const vectorLayer = new VectorLayer({
      source: new VectorSource({
        url: "https://openlayers.org/data/vector/ecoregions.json",
        format: new GeoJSON(),
      }),
      style: function (feature) {
        const color = feature.get("COLOR") || "#eeeeee"
        style.getFill().setColor(color)
        return style
      },
    })
    const map = new Map({
      layers: [layer, vectorLayer],
      target: "map",
      view: new View({
        center: [0, 0],
        zoom: 2,
      }),
    })
    // 为了后续使用方便将map放入this中
    this.map = map
  },
}
</script>

<style lang="scss">
#map {
  width: 1980px;
  height: 800px;
}
</style>

控制图层仅显示局部

实现方式与做地图裁切时相同,利用prerender和postrender时间来实现。
我们封装一下,放到method中方便后续调用。

export default {
  data() {
    return {
      swipeLayer: null
    }
  },
  computed: {},
  methods: {
	setSwipeLayer(layer) {
      this.unsetSwipeLayer()
      if (layer) {
        layer.on("prerender", this.prerender)
        layer.on("postrender", this.postrender)
        this.swipeLayer = layer
      }
    },
    unsetSwipeLayer() {
      if (this.swipeLayer) {
        this.swipeLayer.un("prerender", this.prerender)
        this.swipeLayer.un("postrender", this.postrender)
        this.swipeLayer = null
      }
    },
    prerender(e) {
      const ctx = e.context
      const hOrw = ctx.canvas.width
      const nVal = hOrw * 50 / 100 //仅显示一半
      ctx.save()
      ctx.beginPath()
      ctx.rect(0, 0, nVal, ctx.canvas.height)
      ctx.clip() // 裁剪

      this.map.render()
    },
    postrender(e) {
      const ctx = e.context
      ctx.restore()
    },
  },
  mounted() {
    ...
    this.setSwipeLayer(vectorLayer)
  },
}

此时再看地图应该只会显示一半,下面来创建分割线和按钮。

创建分割线和按钮,添加自定义控件

创建一个不可见的div来存放控件UI,这样在页面初始化时就不会出现面板闪一下的问题。

<template>
  <div>
    <div id="map"></div>
    <div v-show="false">
      <div
        class="ol-control ol-unselectable my-swipe"
        :style="{pointerEvents: isMoving?'auto':'none'}"
        ref="mySwipe"
        @mousemove="mouseMove"
        @mouseup="mouseUp"
      >
        <div
          class="my-swipe-line swipe-horizontal"
          :style="{left:swipeVal+'%',pointerEvents:isMoving?'none':'auto'}"
        >
          <span
            class="my-swipe-line-btn"
            :style="{pointerEvents:isMoving?'none':'auto'}"
            @mousedown="mouseDown"
          >
            <img :src="require('./change.png')" alt="拖动" />
          </span>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
...
import Control from "ol/control/Control"
export default {
  data() {
    return {
      swipeLayer: null,
      isMoving: false,
      swipeVal: 50,
    }
  },
  computed: {},
  methods: {
	...
	mouseMove(e) {
      if (!this.isMoving) return
      const clientWidth = e.currentTarget.clientWidth
      const clientHeight = e.currentTarget.clientHeight
      const x = e.offsetX
      const y = e.offsetY
      if (x < 0 || x > clientWidth || y < 0 || y > clientHeight) return
      this.swipeVal = (x / clientWidth) * 100
    },
    mouseDown(e) {
      this.isMoving = true
    },
    mouseUp(e) {
      this.isMoving = false
    },
  },
  mounted() {
    ...
    // 创建卷帘控件
    this.swipeControl = new Control({
      element: this.$refs["mySwipe"],
    })
    map.addControl(this.swipeControl)
  },
}
</script>
<style lang="scss">
...
.my-swipe {
  width: 100%;
  height: 100%;
  z-index: 110;
  background: none !important;
  padding: 0;
  pointer-events: none;
  .my-swipe-line {
    position: absolute;
    width: 1px;
    height: 100%;
    background: white;
    .my-swipe-line-btn {
      position: relative;
      display: block;
      top: 50%;
      width: 20px;
      height: 30px;
      margin-left: -10px;
      background: white;
      border: 1px #efefef solid;
      border-radius: 3px;
      cursor: col-resize;
      img {
        vertical-align: middle;
        border-style: none;
      }
    }
  }
}
</style>

这一步创建出了控制界面,可通过鼠标拖动分割线。

将控件和图层关联起来

得益于vue的数据绑定,只需要修改prerender方法中的nVal部分就可以了。

export default {
  data() {
    return {
      swipeLayer: null
    }
  },
  computed: {},
  methods: {
    prerender(e) {
      ...
      const nVal = hOrw * this.swipeVal / 100 //根据分割线显示
      ...
    },
  },
  mounted() {
    ...
  },
}

完整代码

<template>
  <div>
    <div id="map"></div>
    <div v-show="false">
      <div
        class="ol-control ol-unselectable my-swipe"
        :style="{pointerEvents: isMoving?'auto':'none'}"
        ref="mySwipe"
        @mousemove="mouseMove"
        @mouseup="mouseUp"
      >
        <div
          class="my-swipe-line swipe-horizontal"
          :style="{left:swipeVal+'%',pointerEvents:isMoving?'none':'auto'}"
        >
          <span
            class="my-swipe-line-btn"
            :style="{pointerEvents:isMoving?'none':'auto'}"
            @mousedown="mouseDown"
          >
            <img :src="require('./change.png')" alt="拖动" />
          </span>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import "ol/ol.css"
import Map from "ol/Map"
import XYZ from "ol/source/XYZ"
import TileLayer from "ol/layer/Tile"
import View from "ol/View"
import GeoJSON from "ol/format/GeoJSON.js"
import VectorLayer from "ol/layer/Vector.js"
import VectorSource from "ol/source/Vector.js"
import { Fill, Style } from "ol/style.js"
import Control from "ol/control/Control"
export default {
  data() {
    return {
      swipeLayer: null,
      isMoving: false,
      swipeVal: 50,
    }
  },
  computed: {},
  methods: {
    setSwipeLayer(layer) {
      this.unsetSwipeLayer()
      if (layer) {
        layer.on("prerender", this.prerender)
        layer.on("postrender", this.postrender)
        this.swipeLayer = layer
      }
    },
    unsetSwipeLayer() {
      if (this.swipeLayer) {
        this.swipeLayer.un("prerender", this.prerender)
        this.swipeLayer.un("postrender", this.postrender)
        this.swipeLayer = null
      }
    },
    prerender(e) {
      const ctx = e.context
      const hOrw = ctx.canvas.width
      const nVal = (hOrw * this.swipeVal) / 100 //根据分割线显示
      ctx.save()
      ctx.beginPath()
      ctx.rect(0, 0, nVal, ctx.canvas.height)
      ctx.clip() // 裁剪

      this.map.render()
    },
    postrender(e) {
      const ctx = e.context
      ctx.restore()
    },
    mouseMove(e) {
      if (!this.isMoving) return
      const clientWidth = e.currentTarget.clientWidth
      const clientHeight = e.currentTarget.clientHeight
      const x = e.offsetX
      const y = e.offsetY
      if (x < 0 || x > clientWidth || y < 0 || y > clientHeight) return
      this.swipeVal = (x / clientWidth) * 100
    },
    mouseDown(e) {
      this.isMoving = true
    },
    mouseUp(e) {
      this.isMoving = false
    },
  },
  mounted() {
    const layer = new TileLayer({
      source: new XYZ({
        attributions: 'Tiles © <a href="https://services.arcgisonline.com/ArcGIS/' + 'rest/services/World_Topo_Map/MapServer">ArcGIS</a>',
        url: "https://server.arcgisonline.com/ArcGIS/rest/services/" + "World_Topo_Map/MapServer/tile/{z}/{y}/{x}",
      }),
    })
    const style = new Style({
      fill: new Fill({
        color: "#eeeeee",
      }),
    })

    const vectorLayer = new VectorLayer({
      source: new VectorSource({
        url: "https://openlayers.org/data/vector/ecoregions.json",
        format: new GeoJSON(),
      }),
      style: function (feature) {
        const color = feature.get("COLOR") || "#eeeeee"
        style.getFill().setColor(color)
        return style
      },
    })
    const map = new Map({
      layers: [layer, vectorLayer],
      target: "map",
      view: new View({
        center: [0, 0],
        zoom: 2,
      }),
    })
    // 为了后续使用方便将map放入this中
    this.map = map

    this.setSwipeLayer(vectorLayer)
    // 创建卷帘控件
    this.swipeControl = new Control({
      element: this.$refs["mySwipe"],
    })
    map.addControl(this.swipeControl)
  },
}
</script>

<style lang="scss">
#map {
  width: 1980px;
  height: 800px;
}
.my-swipe {
  width: 100%;
  height: 100%;
  z-index: 110;
  background: none !important;
  padding: 0;
  pointer-events: none;
  .my-swipe-line {
    position: absolute;
    width: 1px;
    height: 100%;
    background: white;
    .my-swipe-line-btn {
      position: relative;
      display: block;
      top: 50%;
      width: 20px;
      height: 30px;
      margin-left: -10px;
      background: white;
      border: 1px #efefef solid;
      border-radius: 3px;
      cursor: col-resize;
      img {
        vertical-align: middle;
        border-style: none;
      }
    }
  }
}
</style>

延伸

代码逻辑比较简单,实际应用中通常会要求扩展一下功能,比如可以控制分割线哪一侧需要显示,卷动方向是水平还是垂直的,卷动图层要可修改等等。根据实际情况稍微改动一下就可以实现。
在这里插入图片描述

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
要在 Vue 中使用 OpenLayers 实现图层控制控件,可以按照以下步骤进行操作: 1. 安装 OpenLayersVue: ``` npm install ol vue ``` 2. 在 Vue 中引入 OpenLayers: ```javascript import ol from 'ol' import 'ol/ol.css' ``` 3. 创建地图容器: ```html <template> <div ref="map" class="map"></div> </template> ``` 4. 在 Vue 的 mounted 钩子函数中创建地图: ```javascript mounted() { // 创建地图容器 const map = new ol.Map({ target: this.$refs.map, layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ], view: new ol.View({ center: ol.proj.fromLonLat([116.3975, 39.9085]), zoom: 12 }) }); this.map = map; } ``` 5. 创建图层控制控件: ```html <template> <div ref="map" class="map"> <div class="ol-control ol-custom-control"> <div class="ol-custom-control-header">图层控制</div> <div class="ol-custom-control-body"> <div v-for="(layer, index) in layers" :key="index"> <input type="checkbox" :id="layer.name" v-model="layer.visible"> <label :for="layer.name">{{ layer.name }}</label> </div> </div> </div> </div> </template> ``` 6. 在 Vue 的 data 中定义图层数据和控件的状态: ```javascript data() { return { map: null, layers: [ { name: 'OSM', visible: true, layer: new ol.layer.Tile({ source: new ol.source.OSM() }) }, { name: 'Bing Maps', visible: false, layer: new ol.layer.Tile({ source: new ol.source.BingMaps({ key: 'your-bingmaps-api-key', imagerySet: 'Road' }) }) } ] } } ``` 7. 在 Vue 的 watch 中监听图层状态的变化并更新地图: ```javascript watch: { layers: { deep: true, handler(layers) { const map = this.map; map.getLayers().clear(); layers.forEach(layer => { if (layer.visible) { map.addLayer(layer.layer); } }); } } } ``` 通过以上步骤就可以在 Vue 中实现图层控制控件了。需要注意的是,在实际应用中,可以根据需要自定义控件的样式和布局。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值