vue通过图片获取geoJson数据(轮廓勾勒)

vue yekong

之前我们通过图片获取了图片的四角坐标
vue项目通过图片获取geoJson数据(图片配准),接下来我们通过四角坐标配置图片,然后勾勒数据。

我们通过四角坐标将图片渲染到地图上,这里我们使用的是地图不是高德地图而是leaflet,之所以不使用高德,是因为高德地图渲染图片的时候是通过左下角和右上角来进行渲染的,但是两个坐标渲染出来的地图,无法准确渲染出倾斜的图片,所以我该用leaflet来渲染图片。

vue通过图片获取geoJson数据(轮廓勾勒)

在线演示地址

在线演示地址

轮廓勾勒

图片渲染出来后,我们进行轨迹勾勒,完成勾勒后,我们就可以得到了我们的轨迹坐标,也就是我们的geoJson数据。

轮廓勾勒

完整实例代码

<template>
  <div class="map-overlay">
    <div class="container" ref="mapContainer" id="container"></div>
    <div class="operation">
      <h2>地图图片叠加工具</h2>
      <el-input
          v-model="coordinatesInput"
          type="textarea"
          :rows="4"
          placeholder="输入四个角坐标 [[经度,纬度],[经度,纬度],[经度,纬度],[经度,纬度]]"
          class="mt20"
      ></el-input>
      <el-button type="primary" @click="addOverlay" class="mt20">添加叠加图片</el-button>
      <div class="mt20">
        <span>透明度: {{ opacity }}</span>
        <el-slider v-model="opacity" :min="0" :max="1" :step="0.1" @change="updateOpacity"></el-slider>
      </div>
      <el-button type="success" @click="toggleDrawMode" class="mt20">{{ drawMode ? '停止绘制' : '开始绘制轨迹' }}</el-button>
      <el-button type="danger" @click="clearPath" class="mt20 ml0">清除轨迹</el-button>
      <div class="mt20">
        <h3>轨迹坐标:</h3>
        <el-input
            v-model="pathCoordinates"
            type="textarea"
            :rows="4"
            readonly
        ></el-input>
      </div>
    </div>
  </div>
</template>

<script>
import longli from './assets/longli2.jpg'
import { ElButton, ElMessage, ElInput, ElSlider } from 'element-plus'
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
import 'leaflet-imageoverlay-rotated'
import gcoord from 'gcoord'

export default {
  components: {
    ElButton,
    ElInput,
    ElSlider
  },
  data() {
    return {
      map: null,
      imageOverlay: null,
      coordinatesInput: '',
      opacity: 1,
      rectangle: null,
      drawMode: false,
      path: [],
      pathLayer: null,
      pathCoordinates: ''
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.initMap()
      window.addEventListener('keydown', this.handleKeyDown)
    })
  },
  beforeUnmount() {
    window.removeEventListener('keydown', this.handleKeyDown)
  },
  methods: {
    initMap() {
      this.map = L.map(this.$refs.mapContainer).setView([26.5, 106.88], 13)
      L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '© OpenStreetMap contributors'
      }).addTo(this.map)

      this.map.on('click', this.onMapClick)
    },
    addOverlay() {
      if (!this.coordinatesInput) {
        ElMessage.error('请输入坐标')
        return
      }

      let coordinates
      try {
        coordinates = JSON.parse(this.coordinatesInput)
        if (!Array.isArray(coordinates) || coordinates.length !== 4 ||
            !coordinates.every(coord => Array.isArray(coord) && coord.length === 2)) {
          throw new Error('坐标格式不正确')
        }
      } catch (error) {
        ElMessage.error('坐标格式不正确,请检查输入')
        return
      }

      // 转换坐标从GCJ-02到WGS84
      const wgs84Coordinates = coordinates.map(coord =>
          gcoord.transform(coord, gcoord.GCJ02, gcoord.WGS84)
      )

      if (this.imageOverlay) {
        this.map.removeLayer(this.imageOverlay)
      }
      if (this.rectangle) {
        this.map.removeLayer(this.rectangle)
      }

      // 调整坐标顺序为 [纬度, 经度]
      const adjustedCoordinates = wgs84Coordinates.map(coord => [coord[1], coord[0]])

      // 使用 L.ImageOverlay.Rotated
      this.imageOverlay = L.imageOverlay.rotated(
          longli,
          adjustedCoordinates[0],  // 左上角
          adjustedCoordinates[1],  // 右上角
          adjustedCoordinates[3],  // 左下角
          {
            opacity: this.opacity,
            interactive: true,
            zIndex: 10
          }
      ).addTo(this.map)

      // 添加矩形覆盖物
      this.rectangle = L.polygon(adjustedCoordinates, {
        color: "#FF33FF",
        weight: 2,
        opacity: 0.8,
        fillOpacity: 0
      }).addTo(this.map)

      this.map.fitBounds(this.rectangle.getBounds())

      ElMessage.success('图片已成功叠加到地图上')
    },
    updateOpacity(value) {
      if (this.imageOverlay) {
        this.imageOverlay.setOpacity(value)
      }
    },
    toggleDrawMode() {
      this.drawMode = !this.drawMode
      if (this.drawMode) {
        this.path = []
        this.pathCoordinates = ''
        if (this.pathLayer) {
          this.map.removeLayer(this.pathLayer)
        }
        this.pathLayer = L.polyline([], {color: 'red'}).addTo(this.map)
      }
    },
    onMapClick(e) {
      if (!this.drawMode) return

      const gcj02Coord = gcoord.transform([e.latlng.lng, e.latlng.lat], gcoord.WGS84, gcoord.GCJ02)
      this.path.push(gcj02Coord)
      this.pathLayer.addLatLng(e.latlng)

      this.updatePathCoordinates()
    },
    clearPath() {
      if (this.pathLayer) {
        this.map.removeLayer(this.pathLayer)
      }
      this.path = []
      this.pathCoordinates = ''
      this.drawMode = false
    },
    handleKeyDown(event) {
      if (this.drawMode && (event.key === 'Delete' || event.key === 'Backspace' || (event.ctrlKey && event.key === 'z'))) {
        this.removeLastPoint()
      }
    },
    removeLastPoint() {
      if (this.path.length > 0) {
        this.path.pop()
        if (this.pathLayer) {
          const latlngs = this.pathLayer.getLatLngs()
          latlngs.pop()
          this.pathLayer.setLatLngs(latlngs)
        }
        this.updatePathCoordinates()
      }
    },
    updatePathCoordinates() {
      this.pathCoordinates = JSON.stringify(this.path)
    }
  }
}
</script>

<style lang="scss" scoped>
@import 'leaflet/dist/leaflet.css';

.map-overlay {
  position: fixed;
  width: 100%;
  height: 100%;
  z-index: 0;

  .container {
    position: relative;
    width: 100%;
    height: 100%;
    z-index: 10;
  }

  .operation {
    position: absolute;
    top: 20px;
    right: 20px;
    z-index: 100;
    border-radius: 5px;
    background: rgba(0, 0, 0, 0.8);
    width: 300px;
    padding: 20px;
    color: #fff;

    h2 {
      margin-top: 0;
      margin-bottom: 20px;
      font-size: 18px;
    }
  }

  .mt20 {
    margin-top: 20px;
  }

  :deep(.el-button) {
    width: 100%;
  }

  :deep(.el-slider) {
    width: 100%;
  }
}
</style>

<style>
.leaflet-bottom {
  display: none !important;
}
.ml0{
  margin-left: 0 !important;
}
</style>

喜欢