• openlayers 绘图功能,编辑多边形,长度面积测量,tip提示(八)


    本篇介绍一下使用vue3-openlayers添加tips,比如测量时,实时显示长度、面积等,两种方法:

    1. 使用Overlay,会添加指定的HTML元素到dom中,会跟随地图的平移】缩放等动作
    2. 使用vector样式,会渲染到地图的canvas中,会跟随地图的平移】缩放等动作
      推荐第2种,与地图无缝集成,实现简单

    1 需求

    • 基础多边形绘制,编辑功能
    • 绘制编辑时跟随鼠标给出提示
    • 多边形的边显示长度,中心显示面积

    2 分析

    主要是 vue3-openlayers 中 styleFunc 功能的使用

    3 实现

    <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 ref="vectorRef">
          <ol-source-vector :projection="projection" :wrapX="false">
            <ol-interaction-draw
              ref="drawRef"
              :type="'Polygon'"
              :source="source"
              @drawend="drawend"
              @drawstart="drawstart"
            >
              <ol-style
                :overrideStyleFunction="
                  feature => handleStyleFunction(feature, showLength, showArea, tips)
                "
              >
              </ol-style>
            </ol-interaction-draw>
            <ol-interaction-modify
              ref="modifyRef"
              v-if="modifyFlag"
              :features="selectedFeatures"
              :pixelTolerance="10"
              :insertVertexCondition="handleInsertVertexCondition"
              @modifyend="handleModifyEnd"
            >
              <ol-style
                :overrideStyleFunction="
                  feature => handleModifyStyleFunction(feature, showLength, showArea)
                "
              >
              </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>
        <el-button type="success" @click="handleShowDataClick(true)"
          >{{ showLength ? '隐藏' : '显示' }}边长</el-button
        >
        <el-button type="success" @click="handleShowDataClick(false)"
          >{{ showArea ? '隐藏' : '显示' }}面积</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, RegularShape, Text } from 'ol/style';
    import { getArea, getLength } from 'ol/sphere.js';
    import { start } from 'nprogress';
    
    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 vectorRef = ref();
    const drawFlag = ref(false);
    const modifyFlag = ref(false);
    const selectConditions = inject('ol-selectconditions');
    const showLength = ref(false);
    const showArea = ref(false);
    const tips = ref('');
    
    const selectCondition = selectConditions.click;
    
    const startTip = '点击鼠标定位第一个顶点';
    
    const labelStyle = new Style({
      text: new Text({
        font: '14px Microsoft YaHei',
        fill: new Fill({
          color: 'rgba(255, 255, 255, 1)'
        }),
        backgroundFill: new Fill({
          color: 'rgba(0, 0, 0, 0.7)'
        }),
        padding: [3, 3, 3, 3],
        textBaseline: 'bottom',
        offsetY: -15
      }),
      image: new RegularShape({
        radius: 8,
        points: 3,
        angle: Math.PI,
        displacement: [0, 10],
        fill: new Fill({
          color: 'rgba(0, 0, 0, 0.7)'
        })
      })
    });
    
    const tipStyle = new Style({
      text: new Text({
        font: '12px Microsoft YaHei',
        fill: new Fill({
          color: 'rgba(255, 255, 255, 1)'
        }),
        backgroundFill: new Fill({
          color: 'rgba(0, 0, 0, 0.5)'
        }),
        padding: [2, 2, 2, 2],
        textAlign: 'left',
        offsetX: 15
      })
    });
    
    const modifyStyle = new Style({
      image: new Circle({
        radius: 5,
        stroke: new Stroke({
          color: 'rgba(255, 255, 255, 0.4)'
        }),
        fill: new Fill({
          color: 'rgba(255, 255, 255, 0.4)'
        })
      }),
      text: new Text({
        text: '拖动顶点进行修改',
        font: '12px Microsoft YaHei',
        fill: new Fill({
          color: 'rgba(255, 255, 255, 1)'
        }),
        backgroundFill: new Fill({
          color: 'rgba(0, 0, 0, 0.7)'
        }),
        padding: [5, 5, 5, 5],
        textAlign: 'left',
        offsetX: 15
      })
    });
    
    const edgeStyle = new Style({
      text: new Text({
        font: '12px Microsoft YaHei',
        fill: new Fill({
          color: 'rgba(255, 255, 255, 1)'
        }),
        backgroundFill: new Fill({
          color: 'rgba(0, 0, 0, 0.4)'
        }),
        padding: [2, 2, 2, 2],
        textBaseline: 'bottom',
        offsetY: -12
      }),
      image: new RegularShape({
        radius: 6,
        points: 3,
        angle: Math.PI,
        displacement: [0, 8],
        fill: new Fill({
          color: 'rgba(0, 0, 0, 0.4)'
        })
      })
    });
    
    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());
      tips.value = startTip;
    
    };
    
    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();
    	tips.value = startTip;
    };
    const handleCancelClick = () => {
      drawRef.value.draw.removeLastPoint();
    };
    
    const handleShowDataClick = isLength => {
      if (isLength) {
        showLength.value = !showLength.value;
      } else {
        showArea.value = !showArea.value;
      }
      drawRef.value.draw.getOverlay().changed();
      vectorRef.value.vectorLayer.changed();
    };
    
    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, showLength, showArea, tip) => {
      const styles: Array<Style> = [];
      const geometry = feature.getGeometry();
      const type = geometry.getType();
      const coord = geometry.getCoordinates();
      const tStyle = tipStyle.clone();
      if (type === 'LineString') {
        for (let i = 0; i < coord.length - 1; i++) {
          const line = new LineString([coord[i], coord[i + 1]]);
          const label = formatLength(line);
          const point = new Point(line.getCoordinateAt(0.5));
          const style = edgeStyle.clone();
          style.setGeometry(point);
          style.getText().setText(label);
          if (showLength) {
            styles.push(style);
          }
          styles.push(
            new Style({
              geometry: line,
              stroke: new Stroke({
                color: 'orange',
                lineDash: coord.length > 2 && i < coord.length - 2 ? [] : [10],
                width: 2
              })
            })
          );
        }
        const point = new Point(geometry.getLastCoordinate());
        tStyle.setGeometry(point);
        if (coord.length <= 3) {
          tips.value = '继续定位顶点';
        } else if (coord.length > 3) {
          tips.value = '点击第一个点可结束绘制';
        }
      }
      if (type === 'Polygon') {
        if (showArea) {
          const label = formatArea(geometry);
          const point = geometry.getInteriorPoint();
          const style = labelStyle.clone();
          style.setGeometry(point);
          style.getText().setText(label);
          styles.push(style);
        }
      }
      tStyle.getText().setText(tip);
      styles.push(tStyle);
      return styles;
    };
    
    const handleInsertVertexCondition = e => {
      return false;
    };
    
    const handleModifyStyleFunction = (feature: FeatureLike, showLength, showArea) => {
      const layer = mapRef.value.map.getLayers().item(0);
      const features = layer.getSource().getFeatures();
      const geometry = feature.getGeometry();
      const coord = geometry.getCoordinates();
      const type = geometry.getType();
      const coords = features.map(feature => feature.getGeometry().getCoordinates()).flat(2);
      let styles = [];
      // 只有鼠标在顶点时才能触发编辑功能
      if (coords.find(c => c.toString() === coord.toString())) {
        styles.push(
          new Style({
            geometry: new Point(coord),
            image: new Circle({
              radius: 6,
              fill: new Fill({
                color: '#ffff'
              }),
              stroke: new Stroke({
                color: 'red',
                width: 2
              })
            })
          }),
          modifyStyle
        );
      }
    
      if (type === 'Point') {
        const feature = selectedFeatures.value.getArray()[0];
        const geometry = feature.getGeometry();
        const coord = feature.getGeometry().getCoordinates()[0];
        if (showLength) {
          for (let i = 0; i < coord.length - 1; i++) {
            const line = new LineString([coord[i], coord[i + 1]]);
            const label = formatLength(line);
            const point = new Point(line.getCoordinateAt(0.5));
            const style = edgeStyle.clone();
            style.setGeometry(point);
            style.getText().setText(label);
            styles.push(style);
          }
        }
        if (showArea) {
          const label = formatArea(geometry);
          const point = geometry.getInteriorPoint();
          const style = labelStyle.clone();
          style.setGeometry(point);
          style.getText().setText(label);
          styles.push(style);
        }
      }
    
      return styles;
    };
    
    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;
    };
    
    const formatArea = polygon => {
      const area = getArea(polygon, { projection: 'EPSG:4326' });
      let output; //根据面积大小实时换算成平方千米
      if (area > 10000) {
        //平方千米
        output = Math.round((area / 1000000) * 100) / 100 + ' km\xB2';
      } else {
        //平方米
        output = Math.round(area * 100) / 100 + ' m\xB2';
      }
      return output;
    };
    
    const formatLength = line => {
      const length = getLength(line, { projection: 'EPSG:4326' });
      let output; //根据面积大小实时换算成平方千米
      if (length > 100) {
        output = Math.round((length / 1000) * 100) / 100 + ' km';
      } else {
        output = Math.round(length * 100) / 100 + ' m';
      }
      return output;
    };
    </script>
    <style scoped lang="scss">
    .toolbar {
      position: absolute;
      top: 20px;
      left: 100px;
    }
    </style>
    
    
    

    4 问题

    • 跟随鼠标的tip背景会加深
    • 鼠标tip会有重复
  • 相关阅读:
    重置Jetson设备的Ubuntu密码:通过挂载根目录到另一个Linux系统
    java毕业设计LIS检验系统2021mybatis+源码+调试部署+系统+数据库+lw
    Netty简介
    TB/T 3237 动车组用内装材料阻燃要求与测试
    【附源码】计算机毕业设计java自助旅游平台设计与实现
    【摘要】Cpp核心指南
    推荐10个堪称神器的 Java 学习网站
    python sklearn 多输出回归
    [从零开始学习FPGA编程-44]:视野篇 - 集成电路助力数字化时代高质量发展-1-集成电路芯片主要形态
    基于Unity引擎利用OpenCV和MediaPipe的面部表情和人体运动捕捉系统
  • 原文地址:https://blog.csdn.net/weixin_56624286/article/details/139748552