Vue + AILabel.js 实现图片标注(简单粗暴代码待优化版本)

6,495 阅读4分钟

直接初始化一个vue项目

升级版

上代码(大部分注释都在代码当中


<template>
  <div>

    <div id="map">
      <div class="button-wrap">
        <!--      <button class="btn btn-default" @click="setMode('PAN');">平移</button>-->
        <!--      <button class="btn btn-default" @click="setMode('MARKER');">注记</button>-->
        &nbsp;&nbsp;
        <!--      <button class="btn btn-default" @click="setMode('POINT');">点</button>-->
        <button class="btn btn-default" @click="setMode('LINE');">线段</button>
        <button class="btn btn-default" @click="setMode('POLYLINE');">多段线</button>
        <!--      <button class="btn btn-default" @click="setMode('CIRCLE');">圆</button>-->
        <button class="btn btn-default" @click="setMode('RECT');">矩形</button>
        <button class="btn btn-default" @click="setMode('POLYGON');">多边形</button>
        <button class="btn btn-default" @click="getFeatures();">获取标注数据</button>
        &nbsp;&nbsp;
        <!--      <button class="btn btn-default" onclick="setMode('DRAWMASK');">涂抹</button>-->
        <!--      <button class="btn btn-default" onclick="setMode('CLEARMASK');">擦除</button>-->
        <!--      <button class="btn btn-default" @click="getRle();">获取rle数据</button>-->
        &nbsp;&nbsp;
      </div>
      <!--      <div class="zoom-icon-wrapper">-->
      <!--        <div class="zoom-icon-plus" @click="zoomIn">+</div>-->
      <!--        <div class="zoom-icon-minus" @click="zoomOut">-</div>-->
      <!--      </div>-->
    </div>
  </div>
</template>

<script>
import AILabel from "ailabel";

export default {
  name: 'HelloWorld',
  data() {
    return {
      imgUrl: '../../static/img/bg.jpg',
      drawingStyle: {},
      mode: '',
      itemName: '',
      editId: '',
      deleteIconId: 'delete01'
    }
  },
  watch: {
    mode(mode) {
      this.gMap.setMode(mode);
      this.setDrawingStyle(mode);
    }
  },
  methods: {
    zoomIn() {
      this.gMap.zoomIn();
    },
    zoomOut() {
      this.gMap.zoomOut();
    },
    setMode(mode) {
      this.mode = mode;
    },
    // 获取所有features
    getFeatures() {
      const allFeatures = this.gFirstFeatureLayer.getAllFeatures();
      console.log('--allFeatures--', allFeatures);
    },
    // 初始样式
    setDrawingStyle(mode) {
      let drawingStyle = {};
      switch (mode) {
        case 'PAN': {
          break;
        }
        case 'MARKER': {
          // 忽略
          break;
        }
        case 'POINT': {
          this.drawingStyle = {fillStyle: '#9370DB'};
          this.gMap.setDrawingStyle(drawingStyle);
          break;
        }
        case 'CIRCLE': {
          this.drawingStyle = {fillStyle: '#9370DB', strokeStyle: '#0000FF', lineWidth: 2};
          this.gMap.setDrawingStyle(drawingStyle);
          break;
        }
        case 'LINE': {
          this.drawingStyle = {
            strokeStyle: '#FF00FF',
            lineJoin: 'round',
            lineCap: 'round',
            lineWidth: 10,
            arrow: false
          };
          this.gMap.setDrawingStyle(drawingStyle);
          break;
        }
        case 'POLYLINE': {
          this.drawingStyle = {strokeStyle: '#FF1493', lineJoin: 'round', lineCap: 'round', lineWidth: 10}
          this.gMap.setDrawingStyle(drawingStyle);
          break;
        }
        case 'RECT': {
          this.drawingStyle = {strokeStyle: '#0f0', lineWidth: 1}
          this.gMap.setDrawingStyle(drawingStyle);
          break;
        }
        case 'POLYGON': {
          this.drawingStyle = {
            strokeStyle: '#00f',
            fillStyle: '#0f0',
            globalAlpha: .3,
            lineWidth: 1,
            fill: true,
            stroke: true
          }
          this.gMap.setDrawingStyle(drawingStyle);
          break;
        }
        case 'DRAWMASK': {
          this.drawingStyle = {strokeStyle: 'rgba(255, 0, 0, .5)', fillStyle: '#00f', lineWidth: 50}
          this.gMap.setDrawingStyle(drawingStyle);
          break;
        }
        case 'CLEARMASK': {
          this.drawingStyle = {fillStyle: '#00f', lineWidth: 30}
          this.gMap.setDrawingStyle(drawingStyle);
          break;
        }
        default:
          break;
      }
    },
    // 添加图形
    addFeature(data, type, name) {
      let that = this;
      let drawingStyle = this.drawingStyle;
      if (type === 'LINE') {
        const scale = that.gMap.getScale();
        const width = drawingStyle.lineWidth / scale;
        const lineFeature = new AILabel.Feature.Line(
          `${+new Date()}`, // id
          {...data, width}, // shape
          {name}, // props
          drawingStyle // style
        );
        that.gFirstFeatureLayer.addFeature(lineFeature);
      } else if (type === 'POLYLINE') {
        const scale = that.gMap.getScale();
        const width = drawingStyle.lineWidth / scale;
        const polylineFeature = new AILabel.Feature.Polyline(
          `${+new Date()}`, // id
          {points: data, width}, // shape
          {name}, // props
          drawingStyle // style
        );
        that.gFirstFeatureLayer.addFeature(polylineFeature);
      } else if (type === 'RECT') {
        const rectFeature = new AILabel.Feature.Rect(
          `${+new Date()}`, // id
          data, // shape
          {name}, // props
          drawingStyle // style
        );
        that.gFirstFeatureLayer.addFeature(rectFeature);
      } else if (type === 'POLYGON') {
        const polygonFeature = new AILabel.Feature.Polygon(
          `${+new Date()}`, // id
          {points: data}, // shape
          {name}, // props
          drawingStyle // style
        );
        that.gFirstFeatureLayer.addFeature(polygonFeature);
      }
    },
    // 获取历史数据
    getHistoryData() {
      const gFirstMaskLayer = new AILabel.Layer.Mask(
        'first-layer-mask', // id
        {name: '第一个涂抹图层'}, // props
        {zIndex: 11, opacity: .5} // style
      );
      this.gMap.addLayer(gFirstMaskLayer);

      const gFirstTextLayer = new AILabel.Layer.Text(
        'first-layer-text', // id
        {name: '第一个文本图层'}, // props
        {zIndex: 12, opacity: 1} // style
      );
      this.gMap.addLayer(gFirstTextLayer);

      const gFirstText = new AILabel.Text(
        'first-text', // id
        {text: '中华人民共和国', position: {x: 300, y: 300}, offset: {x: 0, y: 0}}, // shape
        {name: '第一个文本对象'}, // props
        {fillStyle: '#F4A460', strokeStyle: '#D2691E', background: true, globalAlpha: 1, fontColor: '#0f0'} // style
      );
      gFirstTextLayer.addText(gFirstText);

      const gFirstFeaturePoint = new AILabel.Feature.Point(
        'first-feature-point', // id
        {x: 250, y: 177, sr: 3}, // shape
        {name: '第一个矢量图层'}, // props
        {fillStyle: '#f00', lineCap: 'round'} // style
      );
      this.gFirstFeatureLayer.addFeature(gFirstFeaturePoint);

      const gFirstFeatureCircle = new AILabel.Feature.Circle(
        'first-feature-circle', // id
        {cx: 100, cy: 100, sr: 20}, // shape
        {name: '第一个矢量图层'}, // props
        {fillStyle: '#F4A460', strokeStyle: '#00f', lineWidth: 1, stroke: true, fill: false} // style
      );
      this.gFirstFeatureLayer.addFeature(gFirstFeatureCircle);

      const gFirstFeatureLine = new AILabel.Feature.Line(
        'first-feature-line', // id
        {start: {x: 100, y: 150}, end: {x: 200, y: 250}, width: 10}, // shape
        {name: '第一个矢量图层'}, // props
        {strokeStyle: '#FF4500', lineCap: 'round'} // style
      );
      this.gFirstFeatureLayer.addFeature(gFirstFeatureLine);
      const gFirstFeatureRect = new AILabel.Feature.Rect(
        'first-feature-rect', // id
        {x: 200, y: 50, width: 100, height: 100}, // shape
        {name: '第一个矢量图层'}, // props
        {strokeStyle: '#00f', lineWidth: 1, fillStyle: '#00f', globalAlpha: .3, fill: true} // style
      );
      this.gFirstFeatureLayer.addFeature(gFirstFeatureRect);
      const gFirstFeaturePolygon = new AILabel.Feature.Polygon(
        'first-feature-polygon', // id
        {
          points: [
            {x: 367, y: 161}, {x: 371, y: 220}, {x: 412, y: 241},
            {x: 474, y: 210}, {x: 467, y: 151}, {x: 426, y: 124}
          ]
        }, // shape
        {name: '第一个多边形'}, // props
        {strokeStyle: '#0000FF', lineWidth: 1} // style
      );
      this.gFirstFeatureLayer.addFeature(gFirstFeaturePolygon);
      console.log('--AILabel--', AILabel);
    },
    // 画完取名
    getName(mode) {
      return this.$prompt('请输入填写名字', {
        confirmButtonText: '确定',
        showCancelButton: false
      }).then(({value}) => {
        this.itemName = value;
        return value;
      }).catch(() => {
        return null;
      });
    },
    // 增加删除图标
    addDeleteIcon(feature) {
      let gMap = this.gMap;
      let that = this;
      // 添加delete-icon
      let points = that.getPoints(feature);
      console.log(points);
      const gFirstMarker = new AILabel.Marker(
        that.deleteIconId, // id
        {
          src: '../../static/img/delete.png',
          position: points[1], // 矩形右上角
          offset: {
            x: -20,
            y: -4
          }
        }, // markerInfo
        {name: 'delete'} // props
      );
      gFirstMarker.events.on('click', marker => {
        // 首先删除当前marker
        gMap.markerLayer.removeMarkerById(marker.id);
        // 删除对应text
        // gFirstTextLayer.removeTextById(textId);
        // 删除对应feature
        that.gFirstFeatureLayer.removeFeatureById(feature.id);
      });
      gMap.markerLayer.addMarker(gFirstMarker);
    },
    // 删除 删除按钮
    deIcon() {
      this.gMap.markerLayer.removeAllMarkers();
    },
    // 增加事件
    addEvent() {
      let that = this;
      let gMap = this.gMap;
      gMap.events.on('drawDone', (type, data) => {
        console.log('--type, data--', type, data);
        that.addFeature(data, type);
        /*that.getName(type).then(val => {
          if (val) {
            that.addFeature(data, type, val);
          } else {
            this.$message({
              type: 'info',
              message: '请填写名字'
            });
          }
        })*/
      });
      gMap.events.on('boundsChanged', data => {
        console.log('--map boundsChanged--', data);
        return '';
      });
      // 双击编辑
      gMap.events.on('featureSelected', feature => {
        that.editId = feature.id;
        // console.log('--map featureSelected--', feature);
        gMap.setActiveFeature(feature);
        // 增加删除按钮
        that.addDeleteIcon(feature);
      });
      // 单机空白取消编辑
      gMap.events.on('featureUnselected', () => {
        // 取消featureSelected
        that.editId = '';
        that.deIcon();
        gMap.setActiveFeature(null);
      });
      // 更新完
      gMap.events.on('featureUpdated', (feature, shape) => {
        console.log(feature);
        // 更新或者移动需要重新设置删除图标
        that.deIcon();
        feature.updateShape(shape);
        that.addDeleteIcon(feature);
      });
      // 删除
      gMap.events.on('FeatureDeleted', () => {
        console.log(2222222);
        // that.gFirstFeatureLayer.removeFeatureById(that.editId);
      });
    },
    // 获取坐标
    getPoints(feature) {
      switch (feature.type) {
        case 'RECT':
          return feature.getPoints();
        case 'LINE':
          return [
            feature.shape.start,
            feature.shape.end,
          ];
        case 'POLYLINE':
          return feature.shape.points;
        case 'POLYGON':
          return feature.shape.points;
        default:
          return [];
      }
    }
  },
  mounted() {
    let that = this;
    const gMap = new AILabel.Map('map', {
      center: {x: 250, y: 150}, // 为了让图片居中
      zoom: 500,
      mode: 'PAN', // 绘制线段
      refreshDelayWhenZooming: true, // 缩放时是否允许刷新延时,性能更优
      zoomWhenDrawing: true,
      panWhenDrawing: true,
      zoomWheelRatio: 5, // 控制滑轮缩放缩率[0, 10), 值越小,则缩放越快,反之越慢
      withHotKeys: true // 关闭快捷键
    });
    that.gMap = gMap;
    this.addEvent();
    // 图片层添加
    const gFirstImageLayer = new AILabel.Layer.Image(
      'first-layer-image', // id
      {
        src: that.imgUrl,
        width: 500,
        height: 300,
        crossOrigin: false, // 如果跨域图片,需要设置为true
        position: { // 左上角相对中心点偏移量
          x: 0,
          y: 0
        },
        grid: { // 3 * 3
          columns: [{color: '#9370DB'}, {color: '#FF6347'}],
          rows: [{color: '#9370DB'}, {color: '#FF6347'}]
        }
      }, // imageInfo
      {name: '第一个图片图层'}, // props
      {zIndex: 5} // style
    );
    // 添加到gMap对象
    gMap.addLayer(gFirstImageLayer);
    // 添加矢量图层
    const gFirstFeatureLayer = new AILabel.Layer.Feature(
      'first-layer-feature', // id
      {name: '第一个矢量图层'}, // props
      {zIndex: 10} // style
    );
    this.gFirstFeatureLayer = gFirstFeatureLayer;
    gMap.addLayer(gFirstFeatureLayer);
    // 历史
    // this.getHistoryData();
    window.onresize = function () {
      this.gMap && this.gMap.resize();
    }
  },
  beforeDestroy() {
    this.gMap.destroy();
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
body {
}

.button-wrap {
  padding-bottom: 10px;
  position: relative;
  z-index: 99;
}

#map {
  margin: 0 auto;
  overflow: hidden;
  position: relative;
  height: 600px;
  width: 800px;
  border: 1px dashed #ccc;
}

.zoom-icon-wrapper {
  position: absolute;
  left: 20px;
  top: 20px;
  z-index: 1000;
}

.zoom-icon-plus {
  width: 30px;
  height: 30px;
  line-height: 20px;
  text-align: center;
  border: 3px solid #6495ED;
  font-size: 20px;
  border-top-left-radius: 6px;
  border-top-right-radius: 6px;
  color: #FF8C00;
  cursor: pointer;
}

.zoom-icon-plus:hover {
  border-color: #4169E1;
}

.zoom-icon-minus {
  margin-top: 6px;
  width: 30px;
  height: 30px;
  line-height: 20px;
  text-align: center;
  border: 3px solid #6495ED;
  font-size: 25px;
  border-bottom-left-radius: 6px;
  border-bottom-right-radius: 6px;
  color: #FF8C00;
  cursor: pointer;
}

.zoom-icon-minus:hover {
  border-color: #4169E1;
}
</style>