Vue3+天地图API,封装地图组件,并且添加多个坐标,点击坐标点弹窗

下面是一个基于 Vue3 + OpenLayers + 天地图 API 的地图组件实现,支持添加多个坐标点和点击弹窗功能。这个组件封装了地图初始化、点位添加和交互逻辑,方便在项目中直接使用。

<template>
  <div class="map-container">
    <!-- 地图容器 -->
    <div id="map" class="map"></div>
    
    <!-- 弹窗组件 -->
    <div 
      v-if="popupVisible" 
      :style="{ 
        position: 'absolute', 
        left: popupPosition.x + 'px', 
        top: popupPosition.y + 'px',
        zIndex: 1000
      }"
      class="popup"
    >
      <div class="popup-content">
        <div class="popup-header">
          <span>{{ popupData.name }}</span>
          <button class="popup-close" @click="closePopup">×</button>
        </div>
        <div class="popup-body">
          <p>坐标: {{ popupData.lng }}, {{ popupData.lat }}</p>
          <p v-if="popupData.desc">{{ popupData.desc }}</p>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
import Map from 'ol/Map';
import View from 'ol/View';
import Tile from 'ol/layer/Tile';
import { OSM, XYZ } from 'ol/source';
import { fromLonLat } from 'ol/proj';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import { Style, Icon } from 'ol/style';
import { Click } from 'ol/interaction';

// 弹窗状态
const popupVisible = ref(false);
const popupPosition = ref({ x: 0, y: 0 });
const popupData = ref({});

// 地图实例
let map = null;
// 矢量图层
let vectorLayer = null;

// 组件参数 - 可通过props传入
const props = defineProps({
  // 中心点坐标 [经度, 纬度]
  center: {
    type: Array,
    default: () => [116.39748, 39.90882] // 默认北京
  },
  // 缩放级别
  zoom: {
    type: Number,
    default: 12
  },
  // 坐标点数据
  points: {
    type: Array,
    default: () => [
      { id: 1, name: '点位1', lng: 116.39748, lat: 39.90882, desc: '这是第一个点位的描述信息' },
      { id: 2, name: '点位2', lng: 116.41748, lat: 39.91882, desc: '这是第二个点位的描述信息' },
      { id: 3, name: '点位3', lng: 116.43748, lat: 39.92882 }
    ]
  },
  // 天地图密钥 - 请替换为自己的密钥
  tdtKey: {
    type: String,
    default: '您的天地图密钥' // 需自行申请
  }
});

// 初始化地图
const initMap = () => {
  // 创建天地图图层 - 矢量底图
  const tdtVectorLayer = new Tile({
    source: new XYZ({
      url: `http://t0.tianditu.gov.cn/vec_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=vec&tileMatrixSet=w&TileMatrix={z}&TileRow={y}&TileCol={x}&style=default&format=tiles&tk=${props.tdtKey}`
    })
  });

  // 创建天地图图层 - 矢量注记
  const tdtLabelLayer = new Tile({
    source: new XYZ({
      url: `http://t0.tianditu.gov.cn/cva_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=cva&tileMatrixSet=w&TileMatrix={z}&TileRow={y}&TileCol={x}&style=default&format=tiles&tk=${props.tdtKey}`
    })
  });

  // 创建矢量图层用于显示点位
  vectorLayer = new VectorLayer({
    source: new VectorSource()
  });

  // 初始化地图
  map = new Map({
    layers: [tdtVectorLayer, tdtLabelLayer, vectorLayer],
    target: 'map',
    view: new View({
      center: fromLonLat(props.center),
      zoom: props.zoom
    })
  });

  // 添加点击交互
  addClickInteraction();
  
  // 添加点位
  addPointsToMap();
};

// 添加点位到地图
const addPointsToMap = () => {
  if (!vectorLayer || !props.points.length) return;
  
  const source = vectorLayer.getSource();
  source.clear(); // 清空现有点位
  
  // 遍历添加所有点位
  props.points.forEach(point => {
    // 创建要素
    const feature = new Feature({
      geometry: new Point(fromLonLat([point.lng, point.lat])),
      data: point // 存储自定义数据
    });
    
    // 设置样式 - 使用自定义图标
    feature.setStyle(new Style({
      image: new Icon({
        src: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMjU2IDhDMTE5IDggOCAxMTkgOCAyNTZzMTExIDI0OCAyNDggMjQ4IDI0OC0xMTEgMjQ4LTI0OFMzOTMgOCAyNTYgOHptMCA0MzJoLTMyVjM4NGgtNjR2LTQ4aDY0VjE5MmgyMnYxOTJoNjRWNDQwaC02NHY0OEgyNTZ6bTMyLTEyOEg1NlYzMkg4OFYxOTJINThWMThINTEyVjMyMEgzODRWMzIwem0tMjMyIDBoLTY0VjE5MmgyMnYzMkg4OFYzMjBIMTVWMTkySDU2VjB6Ii8+PC9zdmc+',
        anchor: [0.5, 1], // 图标锚点
        scale: 0.08 // 图标大小
      })
    }));
    
    source.addFeature(feature);
  });
};

// 添加点击交互
const addClickInteraction = () => {
  const click = new Click({
    handleSingleClick: true
  });
  
  map.addInteraction(click);
  
  // 监听点击事件
  click.on('singleclick', (evt) => {
    const features = [];
    map.forEachFeatureAtPixel(evt.pixel, (feature) => {
      features.push(feature);
      return true;
    });
    
    if (features.length > 0) {
      // 显示弹窗
      const feature = features[0];
      const data = feature.get('data');
      showPopup(evt.pixel, data);
    } else {
      // 点击空白处关闭弹窗
      closePopup();
    }
  });
};

// 显示弹窗
const showPopup = (pixel, data) => {
  popupData.value = data;
  popupVisible.value = true;
  
  // 计算弹窗位置,避免超出地图边界
  const mapEl = document.getElementById('map');
  const mapRect = mapEl.getBoundingClientRect();
  const popupWidth = 280;
  const popupHeight = 150;
  
  // 转换像素坐标为相对于地图容器的坐标
  let x = pixel[0];
  let y = pixel[1];
  
  // 调整弹窗位置,避免右侧溢出
  if (x + popupWidth > mapRect.width) {
    x = x - popupWidth;
  }
  
  // 调整弹窗位置,避免底部溢出
  if (y + popupHeight > mapRect.height) {
    y = y - popupHeight;
  }
  
  popupPosition.value = { x, y };
};

// 关闭弹窗
const closePopup = () => {
  popupVisible.value = false;
};

// 组件挂载时初始化地图
onMounted(() => {
  nextTick(() => {
    initMap();
  });
});

// 组件卸载时销毁地图
onUnmounted(() => {
  if (map) {
    map.dispose();
  }
});

// 监听点位数据变化,重新渲染点位
watch(() => props.points, (newVal) => {
  if (newVal && newVal.length) {
    addPointsToMap();
  }
});
</script>

<style scoped>
.map-container {
  position: relative;
  width: 100%;
  height: 100%;
}

.map {
  width: 100%;
  height: 100%;
}

.popup {
  width: 280px;
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
  overflow: hidden;
}

.popup-content {
  padding: 0;
}

.popup-header {
  padding: 12px 16px;
  background-color: #f5f5f5;
  border-bottom: 1px solid #eee;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-weight: bold;
}

.popup-close {
  background: none;
  border: none;
  font-size: 18px;
  cursor: pointer;
  color: #666;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  transition: background-color 0.2s;
}

.popup-close:hover {
  background-color: #e0e0e0;
}

.popup-body {
  padding: 16px;
  font-size: 14px;
}

.popup-body p {
  margin: 6px 0;
  color: #333;
}
</style>

使用说明:

  • 首先需要安装必要的依赖:
npm install ol
  • 在父组件中使用该地图组件:
<template>
  <div class="page-container">
    <MapComponent 
      :center="[116.39748, 39.90882]" 
      :zoom="13"
      :points="mapPoints"
      tdtKey="您的天地图密钥"
    />
  </div>
</template>

<script setup>
import MapComponent from './MapComponent.vue';
import { ref } from 'vue';

// 定义点位数据
const mapPoints = ref([
  { id: 1, name: '天安门', lng: 116.39748, lat: 39.90882, desc: '北京市中心地标' },
  { id: 2, name: '故宫', lng: 116.39128, lat: 39.91658, desc: '中国明清两代皇家宫殿' },
  { id: 3, name: '颐和园', lng: 116.27557, lat: 39.99909, desc: '著名皇家园林' }
]);
</script>

<style>
.page-container {
  width: 100vw;
  height: 100vh;
}
</style>
  • 天地图密钥申请:
    • 访问天地图官网(https://www.tianditu.gov.cn/
    • 注册账号并申请开发者密钥
    • 将申请到的密钥替换到组件中的 tdtKey 属性

组件特点:

  • 封装了完整的地图初始化逻辑,支持自定义中心点和缩放级别
  • 使用天地图作为底图,包含矢量图和注记层
  • 支持通过 props 动态传入多个坐标点数据
  • 点击坐标点显示弹窗,展示点位详细信息
  • 弹窗会自动调整位置,避免超出地图边界
  • 点击地图空白处会关闭弹窗
  • 当点位数据变化时,会自动更新地图上的点位

可以根据实际需求进一步扩展功能,如添加点位拖拽、编辑功能,或者自定义点位图标样式等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马卫斌

你的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值