openlayers 绘图功能,编辑多边形,select,snap组件的使用(六)

本篇介绍一下vue3-openlayers的select,snap的使用

1 需求

  • 点击开始绘制按钮开始绘制多边形,可以连续绘制多个多边形
  • 点击撤销上步按钮,撤销上一步绘制点
  • 绘制多个多边形(或编辑多边形时),鼠标靠近之前的已绘制完的多边形顶点时,自动吸附
  • 点击结束绘制按钮,绘制完成,点击高亮选中多边形,开启编辑模式,显示顶点(调节点)进行编辑

2 分析

主要是vue3-openlayers 中 draw,select,snap,modify 功能的使用

3 实现

3.1 简单实现

javascript 复制代码
<template>
  <ol-map
    :loadTilesWhileAnimating="true"
    :loadTilesWhileInteracting="true"
    style="width: 100%; height: 100%"
    ref="mapRef"
  >
    <ol-view
      ref="view"
      :center="center"
      :rotation="rotation"
      :zoom="zoom"
      :projection="projection"
    />
    <ol-vector-layer>
      <ol-source-vector :projection="projection" :wrapX="false">
        <ol-interaction-draw
          ref="drawRef"
          :type="'Polygon'"
          :source="source"
          @drawend="drawend"
          @drawstart="drawstart"
        >
          <ol-style :overrideStyleFunction="handleStyleFunction"> </ol-style>
        </ol-interaction-draw>
        <ol-interaction-modify
          ref="modifyRef"
					v-if="modifyFlag"
          :features="selectedFeatures"
          :pixelTolerance="10"
          :insertVertexCondition="handleInsertVertexCondition"
          @modifyend="handleModifyEnd"
        >
          <ol-style :overrideStyleFunction="handleModifyStyleFunction"> </ol-style>
        </ol-interaction-modify>
        <ol-interaction-snap :edge="false" />
      </ol-source-vector>
      <ol-style :overrideStyleFunction="styleFunction"> </ol-style>
    </ol-vector-layer>
    <ol-interaction-select ref="selectRef"  :features="selectedFeatures" @select="handleSelect" :condition="selectCondition">
      <ol-style :overrideStyleFunction="selectStyleFunc"> </ol-style>
    </ol-interaction-select>
  </ol-map>
  <div class="toolbar">
    <el-button type="primary" @click="handleClick">{{ drawFlag ? '结束' : '开始' }}绘制</el-button>
    <el-button type="warning" :disabled="!drawFlag" @click="handleCancelClick">撤销上步</el-button>
  </div>
</template>

<script setup lang="ts">
import { Collection } from 'ol';
import { FeatureLike } from 'ol/Feature';
import { LineString, Point } from 'ol/geom';
import { DrawEvent } from 'ol/interaction/Draw';
import { Circle, Fill, Stroke, Style } from 'ol/style';
const center = ref([121, 31]);
const projection = ref('EPSG:4326');
const zoom = ref(5);
const rotation = ref(0);
const features = ref(new Collection()); //保存绘制的features
const selectedFeatures = ref(new Collection()); //保存绘制的features
const source = ref([]);
const mapRef = ref();
const drawRef = ref();
const selectRef = ref();
const modifyRef = ref();
const drawFlag = ref(false);
const modifyFlag = ref(true);
const selectConditions = inject('ol-selectconditions');

const selectCondition = selectConditions.click;

onMounted(() => {
  drawRef.value.draw.setActive(false);
  modifyRef.value.modify.setActive(false);
});

const drawstart = (event: Event) => {
  console.log(event);
};

const drawend = (event: DrawEvent) => {
  console.log(event.feature.getGeometry());
};

const handleClick = () => {
  drawFlag.value = !drawFlag.value;
  drawRef.value.draw.setActive(drawFlag.value);
	// modifyFlag.value=!drawFlag.value;
  modifyRef.value.modify.setActive(!drawFlag.value);
  selectRef.value.select.setActive(!drawFlag.value);
  selectRef.value.select.getFeatures().clear();
};
const handleCancelClick = () => {
  drawRef.value.draw.removeLastPoint();
};

const handleSelect = e => {
	modifyRef.value.modify.setActive(false);
	// modifyFlag.value=false;
  if (!e.selected.length) {
    selectedFeatures.value.clear();
    selectRef.value.select.getFeatures().clear();
  } else {
		nextTick(()=>{
			// modifyFlag.value=true;
			modifyRef.value?.modify.setActive(true);
			selectedFeatures.value=e.target.getFeatures();
		})
  }
};


const handleStyleFunction = (feature: FeatureLike, currentStyle: Style) => {
  const geometry = feature.getGeometry();
  const coord = geometry.getCoordinates();
  const type = geometry.getType();
  const styles: Array<Style> = [];
  if (type === 'LineString') {
    for (let i = 0; i < coord.length - 1; i++) {
      styles.push(
        new Style({
          geometry: new LineString([coord[i], coord[i + 1]]),
          stroke: new Stroke({
            color: 'orange',
            lineDash: coord.length > 2 && i < coord.length - 2 ? [] : [10],
            width: 2
          })
        })
      );
    }
  }
  return styles;
};

const handleInsertVertexCondition = e => {
  return false;
};

const handleModifyStyleFunction = (feature: FeatureLike, currentStyle: Style) => {
  const coord = feature.getGeometry().getCoordinates();
  const layer = mapRef.value.map.getLayers().item(0);
  const features = layer.getSource().getFeatures();
  const coords = features.map(feature => feature.getGeometry().getCoordinates()).flat(2);
  let style = undefined;
  // 只有鼠标在顶点时才能触发编辑功能
  if (coords.find(c => c.toString() === coord.toString())) {
    style = new Style({
      geometry: new Point(coord),
      image: new Circle({
        radius: 6,
        fill: new Fill({
          color: '#ffff'
        }),
        stroke: new Stroke({
          color: 'red',
          width: 2
        })
      })
    });
  }

  return style;
};

const handleModifyEnd = e => {
  features.value.push(e.feature); //这里可以把编辑后的feature添加到layer绑定的features中
  console.log('modifyend', e.features);
};

const styleFunction = (feature: FeatureLike, currentStyle: Style) => {
  const styles = [];
  styles.push(
    new Style({
      fill: new Fill({
        color: [128, 128, 255, 0.5]
      }),
      stroke: new Stroke({
        color: 'blue',
        width: 2
      })
    })
  );
  return styles;
};

const selectStyleFunc = feature => {
  const styles = [];
  const coord = feature.getGeometry().getCoordinates().flat(1);
  for (let i = 0; i < coord.length - 1; i++) {
    styles.push(
      new Style({
        geometry: new Point(coord[i]),
        image: new Circle({
          radius: 4,
          fill: new Fill({
            color: '#ffff'
          }),
          stroke: new Stroke({
            color: 'orange',
            width: 2
          })
        })
      })
    );
  }
  styles.push(
    new Style({
      stroke: new Stroke({
        color: 'orange',
        width: 2
      }),
      fill: new Fill({
        color: '#ffff'
      })
    })
  );
  return styles;
};
</script>
<style scoped lang="scss">
.toolbar {
  position: absolute;
  top: 20px;
  left: 100px;
}
</style>

存在问题(vue3-openlayers官网modify示例也存在这两个个问题)

  • 当绘制多个多边形时,只要一个多边形被编辑过,当编辑其他多边形时,尽管高亮选中的是当前多边形,但是之前编辑过的多边形也可以被编辑
  • 当两个多边形顶点重叠时,无法再分开,会导致同时编辑两个多边形

分析: modify组件没有仅仅使用当前选中的多边形,而是把select过的都进入编辑状态(其实原生写法也有这个问题,试着把modify传入的features参数绑定值从select.value.getFeatures()改为一个ref(new Collection()),在select组件的select事件中把选中的feature压入这个ref,既可以复现上面两个问题)

解决: modify添加v-if,强制重新渲染(会导致snap不会吸附,个人觉得可以接受)

3.2 重新实现

javascript 复制代码
<template>
  <ol-map
    :loadTilesWhileAnimating="true"
    :loadTilesWhileInteracting="true"
    style="width: 100%; height: 100%"
    ref="mapRef"
  >
    <ol-view
      ref="view"
      :center="center"
      :rotation="rotation"
      :zoom="zoom"
      :projection="projection"
    />
    <ol-vector-layer>
      <ol-source-vector :projection="projection" :wrapX="false">
        <ol-interaction-draw
          ref="drawRef"
          :type="'Polygon'"
          :source="source"
          @drawend="drawend"
          @drawstart="drawstart"
        >
          <ol-style :overrideStyleFunction="handleStyleFunction"> </ol-style>
        </ol-interaction-draw>
        <ol-interaction-modify
          ref="modifyRef"
					v-if="modifyFlag"
          :features="selectedFeatures"
          :pixelTolerance="10"
          :insertVertexCondition="handleInsertVertexCondition"
          @modifyend="handleModifyEnd"
        >
          <ol-style :overrideStyleFunction="handleModifyStyleFunction"> </ol-style>
        </ol-interaction-modify>
        <ol-interaction-snap :edge="false" />
      </ol-source-vector>
      <ol-style :overrideStyleFunction="styleFunction"> </ol-style>
    </ol-vector-layer>
    <ol-interaction-select ref="selectRef"  :features="selectedFeatures" @select="handleSelect" :condition="selectCondition">
      <ol-style :overrideStyleFunction="selectStyleFunc"> </ol-style>
    </ol-interaction-select>
  </ol-map>
  <div class="toolbar">
    <el-button type="primary" @click="handleClick">{{ drawFlag ? '结束' : '开始' }}绘制</el-button>
    <el-button type="warning" :disabled="!drawFlag" @click="handleCancelClick">撤销上步</el-button>
  </div>
</template>

<script setup lang="ts">
import { Collection } from 'ol';
import { FeatureLike } from 'ol/Feature';
import { LineString, Point } from 'ol/geom';
import { DrawEvent } from 'ol/interaction/Draw';
import { Circle, Fill, Stroke, Style } from 'ol/style';
const center = ref([121, 31]);
const projection = ref('EPSG:4326');
const zoom = ref(5);
const rotation = ref(0);
const features = ref(new Collection()); //保存绘制的features
const selectedFeatures = ref(new Collection()); //保存绘制的features
const source = ref([]);
const mapRef = ref();
const drawRef = ref();
const selectRef = ref();
const modifyRef = ref();
const drawFlag = ref(false);
const modifyFlag = ref(false);
const selectConditions = inject('ol-selectconditions');

const selectCondition = selectConditions.click;

onMounted(() => {
  drawRef.value.draw.setActive(false);
  // modifyRef.value.modify.setActive(false);
});

const drawstart = (event: Event) => {
  console.log(event);
};

const drawend = (event: DrawEvent) => {
  console.log(event.feature.getGeometry());
};

const handleClick = () => {
  drawFlag.value = !drawFlag.value;
  drawRef.value.draw.setActive(drawFlag.value);
	modifyFlag.value=!drawFlag.value;
  // modifyRef.value.modify.setActive(!drawFlag.value);
  selectRef.value.select.setActive(!drawFlag.value);
  selectRef.value.select.getFeatures().clear();
};
const handleCancelClick = () => {
  drawRef.value.draw.removeLastPoint();
};

const handleSelect = e => {
	// modifyRef.value.modify.setActive(false);
	modifyFlag.value=false;
  if (!e.selected.length) {
    selectedFeatures.value.clear();
    selectRef.value.select.getFeatures().clear();
  } else {
		nextTick(()=>{
			modifyFlag.value=true;
			// modifyRef.value?.modify.setActive(true);
			selectedFeatures.value=e.target.getFeatures();
		})
  }
};


const handleStyleFunction = (feature: FeatureLike, currentStyle: Style) => {
  const geometry = feature.getGeometry();
  const coord = geometry.getCoordinates();
  const type = geometry.getType();
  const styles: Array<Style> = [];
  if (type === 'LineString') {
    for (let i = 0; i < coord.length - 1; i++) {
      styles.push(
        new Style({
          geometry: new LineString([coord[i], coord[i + 1]]),
          stroke: new Stroke({
            color: 'orange',
            lineDash: coord.length > 2 && i < coord.length - 2 ? [] : [10],
            width: 2
          })
        })
      );
    }
  }
  return styles;
};

const handleInsertVertexCondition = e => {
  return false;
};

const handleModifyStyleFunction = (feature: FeatureLike, currentStyle: Style) => {
  const coord = feature.getGeometry().getCoordinates();
  const layer = mapRef.value.map.getLayers().item(0);
  const features = layer.getSource().getFeatures();
  const coords = features.map(feature => feature.getGeometry().getCoordinates()).flat(2);
  let style = undefined;
  // 只有鼠标在顶点时才能触发编辑功能
  if (coords.find(c => c.toString() === coord.toString())) {
    style = new Style({
      geometry: new Point(coord),
      image: new Circle({
        radius: 6,
        fill: new Fill({
          color: '#ffff'
        }),
        stroke: new Stroke({
          color: 'red',
          width: 2
        })
      })
    });
  }

  return style;
};

const handleModifyEnd = e => {
  features.value.push(e.feature); //这里可以把编辑后的feature添加到layer绑定的features中
  console.log('modifyend', e.features);
};

const styleFunction = (feature: FeatureLike, currentStyle: Style) => {
  const styles = [];
  styles.push(
    new Style({
      fill: new Fill({
        color: [128, 128, 255, 0.5]
      }),
      stroke: new Stroke({
        color: 'blue',
        width: 2
      })
    })
  );
  return styles;
};

const selectStyleFunc = feature => {
  const styles = [];
  const coord = feature.getGeometry().getCoordinates().flat(1);
  for (let i = 0; i < coord.length - 1; i++) {
    styles.push(
      new Style({
        geometry: new Point(coord[i]),
        image: new Circle({
          radius: 4,
          fill: new Fill({
            color: '#ffff'
          }),
          stroke: new Stroke({
            color: 'orange',
            width: 2
          })
        })
      })
    );
  }
  styles.push(
    new Style({
      stroke: new Stroke({
        color: 'orange',
        width: 2
      }),
      fill: new Fill({
        color: '#ffff'
      })
    })
  );
  return styles;
};
</script>
<style scoped lang="scss">
.toolbar {
  position: absolute;
  top: 20px;
  left: 100px;
}
</style>
相关推荐
To_OC18 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic19 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy19 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志19 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble21 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯21 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜1 天前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司1 天前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
kaiking_g1 天前
vue项目中,静态导入 vs 动态导入 详解
前端·javascript·vue.js
江华森1 天前
Web 开发基础:HTTP 与 REST
前端·网络协议·http