Skip to content
返回

天地图、OpenLayers、Vue3实现地图标点出现弹窗

无人扶我青云志,我自踏雪至山巅

作者GitHub:https://github.com/gitboyzcf 有兴趣可关注!!!

目录

点击展开

效果图

请添加图片描述

准备

天地图 国家地理信息公共服务平台(天地图),开发需要申请key OpenLayers 是一个高性能、功能丰富的库,用于在 Web 上创建交互式地图。它可以在任何网页上显示从任何来源加载的地图图块。

pnpm install ol

iconfont网站中下载点位图标,方便下面使用

代码实现

Map.vue组件

<template>
  <div id="Map" ref="mapCon"></div>
  <VideoDialog ref="videoDialogRef" :map="map" :point-data="pointData" />
</template>

<script lang="ts" setup>
  import 'ol/ol.css';
  import { ref, onMounted } from 'vue';
  import { Map, View } from 'ol';
  import TileLayer from 'ol/layer/Tile';
  import XYZ from 'ol/source/XYZ';
  import { get } from 'ol/proj';
  import {
    defaults as Defaults,
    ScaleLine,
    FullScreen,
    ZoomSlider,
    // MousePosition,
  } from 'ol/control';
  // 显示一个小icon、多边形、线之类的需要使用矢量对象Feature;
  import Feature from 'ol/Feature';
  import Point from 'ol/geom/Point';
  import { vector, source } from './feature';
  import { Vector as VectorLayer } from 'ol/layer';
  import { Vector as VectorSource } from 'ol/source';
  import { Style, Icon } from 'ol/style';
  // 点位图标
  import position from '@/assets/images/position-icon.png';
  // 弹窗
  import VideoDialog from './videoDialog.vue';


  const videoDialogRef = ref();
  const mapCon = ref();
  const token = '你自己申请的key';
  const projection = get('EPSG:4326');

  const map = ref();
  const pointData = ref({});
 
 

  const initMap = () => {
    const tdtVectorLayer = new TileLayer({
      title: '天地图矢量图层',
      source: new XYZ({
        url: `http://t0.tianditu.gov.cn/vec_c/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=c&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${token}`,
        projection,
      }),
    });
    const tdtVectorLabelLayer = new TileLayer({
      title: '天地图矢量注记图层',
      source: new XYZ({
        url: `http://t0.tianditu.gov.cn/cva_c/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=c&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${token}`,
        projection,
      }),
    });
    const view = new View({
      center: [116.406393, 39.909006],
      projection,
      zoom: 12,
      maxZoom: 17,
      minZoom: 4,
    });
    map.value = new Map({
      layers: [tdtVectorLayer, tdtVectorLabelLayer],
      target: mapCon.value,
      view,
      controls: Defaults({ zoom: true }).extend([
        new ScaleLine({
          className: 'ol-scale-line custom-zoom-line',
        }),
        new ZoomSlider(),
        new FullScreen(),
        // new MousePosition({
        //   coordinateFormat(coordinate) {
        //     return `东经${coordinate?.[0]} 北纬${coordinate?.[1]}`;
        //   },
        // }),
      ]),
    });
    // 矢量源
 const source = new VectorSource({
   features: [],
 });
 // 矢量图层
    const vector = new VectorLayer({
   source,
   style: new Style({
     image: new Icon({
       anchor: [0.5, 1], // 显示位置
       size: [32, 32], // 尺寸
       src: position, // 图片url
     }),
   }),
 });
    // 模拟接口
 setTimeout(() => {
    // 点位数据
    const points = [
      [116.34281566666665, 39.9748055],
      [116.416393, 39.919006]
    ]
    points.forEach(item => {
   const feature = new Feature({
             geometry: new Point(item), // 地理几何图形选用点几何
         });

   // 存入当前点位信息 弹窗内部根据点位去判断点击的哪个点
         const key = feature?.getGeometry()?.getCoordinates().join(',') || '';
         pointData.value[key] = item;
         source.addFeatures([feature]);
       })
    }, 10000)
    map.value.addLayer(vector);

    videoDialogRef.value.addOverlay(map.value);
    videoDialogRef.value.singleclick(map.value);
    videoDialogRef.value.pointermove(map.value);
  };
  onMounted(() => {
    initMap();
  });
</script>

<style scoped lang="scss">
  .container {
    height: 100%;
    #Map {
      height: 100%;
    }
  }
</style>

VideoDialog.vue组件

<template>
  <div ref="popupRef" class="popup">
    <transition name="fade">
      <div v-if="shopPopup" class="popup-content">
        <div v-if="loading" class="loading"><span>加载中...</span></div>
        <video style="width: 100%; height: 100%" autoplay muted poster loop
          ><source
            src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm"
            type="video/webm"
          />

          <source
            src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
            type="video/mp4"
          />

          Download the
          <a href="/media/cc0-videos/flower.webm">WEBM</a>
          or
          <a href="/media/cc0-videos/flower.mp4">MP4</a>
          video.
        </video>
        <!-- 关闭按钮 -->
        <div class="popup-close" @click="shopPopup = false">
          <svg
            t="1729667484813"
            class="icon"
            viewBox="0 0 1026 1024"
            version="1.1"
            xmlns="http://www.w3.org/2000/svg"
            p-id="7739"
            width="25"
            height="25"
          >
            <path
              d="M603.52 512l274.56-274.56a64 64 0 0 0 16.64-64 64 64 0 0 0-45.44-45.44 64 64 0 0 0-64 16.64L512 421.76l-273.28-275.2a64 64 0 0 0-90.88 90.88L423.04 512l-275.2 274.56a64 64 0 0 0 0 90.24A64 64 0 0 0 192 896a64 64 0 0 0 45.44-19.2L512 602.24l275.2 274.56a64 64 0 0 0 44.8 19.2 64 64 0 0 0 45.44-109.44L603.52 512z"
              fill="#445365"
              p-id="7740"
            ></path>
          </svg>
        </div>
      </div>
    </transition>
  </div>
</template>

<script setup lang="ts">
  import { Overlay } from 'ol';
  import { ref, nextTick, watch } from 'vue';
  import useWorker from '@/assets/js/video-lib/omnimatrix-video-player';
  import { useAppStore } from '@/store';
  import { getPreview } from '@/api/system';

  const app = useAppStore();
  const props = defineProps({
    map: Object,
    pointData: Object,
  });
  const shopPopup = ref(false);
  const popupRef = ref();
  const popupOverlay = ref();
  const loading = ref(false);
  const addOverlay = (map: any) => {
    // 创建Overlay
    popupOverlay.value = new Overlay({
      element: popupRef.value,
      positioning: 'bottom-center',
      stopEvent: false,
      offset: [0, -40],
    });
    map.addOverlay(popupOverlay.value);
  };
  const singleclick = (map: any) => {
    // 点击
    map.on('click', async (e: any) => {
      e.stopPropagation();

      // 判断是否点击在点上
      const feature = map.forEachFeatureAtPixel(e.pixel, (f) => f);
      if (feature) {
        shopPopup.value = true;
        loading.value = true;
        // 设置弹窗位置
        const coordinates = feature.getGeometry().getCoordinates();
        // 获取刚刚存入的点位信息
        const data = props.pointData?.[coordinates.join(',')];
     const video = document.querySelector("video");
     video.addEventListener("loadeddata", (event) => {
       loading.value = false;
       console.log(
         "Yay! readyState just increased to  " +
           "HAVE_CURRENT_DATA or greater for first time.",
       );
     }); 
        popupOverlay.value.setPosition(coordinates);
      }
    });
  };
  const pointermove = (map: any) => {
    map.on('pointermove', (e: any) => {
      if (props.map?.hasFeatureAtPixel(e.pixel)) {
        map.getViewport().style.cursor = 'pointer';
      } else {
        map.getViewport().style.cursor = 'inherit';
      }
    });
  };

  watch(shopPopup, (newV) => {
    if (!newV) {
      workerObj.value?.close();
    }
  });

  defineExpose({
    addOverlay,
    singleclick,
    pointermove,
  });
</script>

<style lang="scss" scoped>
  /* we will explain what these classes do next! */
  .fade-enter-active,
  .fade-leave-active {
    transition: opacity 0.5s ease;
  }

  .fade-enter-from,
  .fade-leave-to {
    opacity: 0;
  }
  .popup {
    width: 400px;
    aspect-ratio: 16/9;
    transition: all 0.5s;
    position: relative;
    color: #fff;
    font-size: 18px;
    .popup-content {
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      background-color: #2e2a58;
    }
    .loading {
      position: absolute;
      top: 0;
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    &-close {
      position: absolute;
      top: 15px;
      right: 15px;
      cursor: pointer;
      color: #2f23da;
    }
  }
</style>





到这里就结束了,后续还会更新 前端 系列相关,还请持续关注! 感谢阅读,若有错误可以在下方评论区留言哦!!!



推荐文章👇

Vue3-Composition API 快速上手(简单易懂)


Share this post on:

上一篇文章
Vue3、Vite5、Primevue、Oxlint、Husky9 简单快速搭建最新的Web项目模板
下一篇文章
Vue3自定义指令实现鼠标拖拽、触摸拖拽弹窗