手把手教学!Vue项目集成天地图,实现点聚合、位置搜索、逆地址解析

6,829 阅读5分钟

最近项目里面要集成天地图,实现地名搜索、逆地址解析、点聚合等一些功能。实战中遇到一些问题,都总结到这篇文章里面了,我也会持续继续深度开发天地图,也会在这篇文章继续更新的。

vue3项目中引入天地图

引入天地图主要有下面的四个步骤

  • 注册用户
  • 创建新应用
  • 获取服务许可(Key)
  • 使用天地图api服务

image.png

  1. 注册用户 按照流程注册即可,如果是单位注册,注册后需要工作人员审核(需要一定的时间) image.png
  2. 创建新应用 注意这里应用类型选择浏览器端(先不要填写域名白名单)

选择完浏览器端后域名白名单先不要填写,如果前期填写了域名白名单会出现只显示logo而不显示图层

只显示logo.png 35c86557c04f4c0bcaa6b182fda426a.png

  1. 获取Key 赋值key image.png
  2. 引入天地图api服务(方式一)

直接找到网页API导航,选择JavaScript API4.0入门指导 在index文件中直接引入。

<script src="http://api.tianditu.gov.cn/api?v=4.0&tk=您的密钥" type="text/javascript"></script>

image.png

<template>
  <div id="mapDiv" class="map-container"></div>
</template>

<script setup>
import { onMounted, ref } from "vue";
const zoom = ref(12);

onMounted(() => {
  initMap();
});

const initMap = () => {
  const map = new T.Map("mapDiv", {
    projection: "EPSG:4326",
  });
  // map.setMapType(TMAP_SATELLITE_MAP); // 设置卫星图
  map.centerAndZoom(new T.LngLat(116.40769, 39.90445), zoom.value); // 中心点坐标和缩放级别
};
</script>

<style lang="scss" scoped>
.map-container {
  width: 800px;
  height: 640px;
}
</style>
  1. 引入天地图api服务(方式二)

下载npm天地图的包,引用天地图vue组件库 官方文档:vue-tianditu

pnpm i vue-tianditu

引入:引入推荐使用全局引入,本人之前测试过按需引入结果导致坐点聚合时,不显示聚合点。修改成全局引入后即可解决,下面代码均可在官方文档中查看。

// main.js
import { createApp } from "vue";
import App from "./App.vue";
import VueTianditu from "vue-tianditu";

const app = createApp(App);
app.use(VueTianditu, {
  v: "4.0", //目前只支持4.0版本
  tk: "your map token"
});
app.mount("#app");
<!-- App.vue -->
<template>
  <div class="mapDiv">
    <tdt-map :center="state.center" :zoom="state.zoom"></tdt-map>
  </div>
</template>

<script lang="ts" setup>
  import { reactive } from "vue";
  import { TdtMap } from "vue-tianditu";
  const state = reactive({
    center: [113.280637, 23.125178],
    zoom: 12
  });
</script>

<style>
  .mapDiv {
    width: 100%;
    height: 100%;
  }
</style>

实现点聚合(基于方式二引入的天地图)

点聚合是使用的方式二进行的地图引入,在vue-tianditu文档中也有详细的文档。

点聚合示例.gif

在页面中我们需要引入天地图的组件以及对应的样式文件,并设置天地图的显示点聚合的坐标点。

我们先看一下天地图组件的属性

image.png

image.png

image.png 熟悉属性后还是比较简单的,直接参考文档中的代码即可正确的显示点聚合的功能了

<template>
    <div>
        <tdt-map style="width: 800px; height: 640px" :center="state.center" :zoom="state.zoom" :loadConfig="loadConfig" @init="mapInit">
            <tdt-marker-clusterer :markers="markers" :styles="styles"></tdt-marker-clusterer>
            <tdt-marker :position="state.center" :draggable="false" icon="https://soullyoko.github.io/vue-tianditu/marker_red.png"></tdt-marker>
            <tdt-infowindow v-model:target="target" :content="content"></tdt-infowindow>
        </tdt-map>
    </div>
</template>

<script setup>
import { reactive, onMounted } from 'vue';
import 'vue-tianditu/lib/style.css';
const loadConfig = { v: '4.0', tk: '申请下来的key' };
const target = ref(null);
const content = ref('');
const styles = ref([
    {
        // url: 'https://api.tianditu.gov.cn/img/map/markers/b1.png',
        size: [32, 32],
        offset: [-16, -32], // 居中偏移
        textColor: '#FFFFFF',
        textSize: 12,
        range: [0, 50],
    },
    {
        // url: 'https://api.tianditu.gov.cn/img/map/markers/b2.png',
        size: [48, 48],
        offset: [-24, -48],
        textColor: '#FF0000',
        textSize: 14,
        range: [50, 200],
    },
    {
        // url: 'https://api.tianditu.gov.cn/img/map/markers/b3.png',
        size: [64, 64],
        offset: [-32, -64],
        textColor: '#000000',
        textSize: 16,
        range: [200, 500],
    },
]);
const map = ref(null); // 地图组件
// 生成北京周边坐标(示例)
const markers = ref(
    Array.from({ length: 500 }, (_, i) => ({
        position: [116.3 + Math.random() * 0.5, 39.8 + Math.random() * 0.5],
        extData: `cluster-${i}`,
    }))
);
// 地图配置
const state = reactive({
    center: [116.404, 39.915], // 中心点
    zoom: 16, // 缩放比例
});
</script>
  • tdt-marker是天地图的点标记组件,如果希望导航或者点击地图更改中心点并标记,直接修改position属性即可。其中icon是标记的图片,需要我们自己去找,官方文档上的图片在网络不好时会不显示。
  • tdt-marker-clusterer是天地图实现点聚合的组件,markers属性就是标记的聚合点,style是聚合点的样式。
  • tdt-infowindow是当用户点击标记点时显示的文字信息。可供用户进行自定义显示。

设置当前位置为地图中心点

当用户首次进入地图页面需要手动获取用户的当前位置信息,需要调用浏览器的内置对象navigatorgeolocation方法。并设置中心点为当前位置。如果用户没有开启浏览器的位置权限信息给出相应的提示。

  • 开启位置信息: 开启定位服务.png
  • 提示: 未开启位置服务.gif
onMounted(() => {
    getLocation(); // 获取当前经纬度
});

// 获取当前位置信息
const getLocation = () => {
    if (!navigator.geolocation) {
        ElMessage.error('浏览器不支持地理位置服务');
        return;
    }
    navigator.geolocation.getCurrentPosition(
        (position) => {
            state.center = [position.coords.longitude, position.coords.latitude];
        },
        (err) => {
            ElMessageBox.alert('请开启浏览器/电脑位置服务!');
        }
    );
};

逆地址解析(点击切换位置)

切换地图中心点.gif

可以监听地图的点击事件,当用户点击地图时能够获取到点击经纬度,调用逆地址解析api获取位置详细信息,设置地图中心点即可实现地图点击位置切换。

需要调用天地图的逆地址解析api逆地理编码查询

image.png

<template>
    <div>
        <tdt-map style="width: 800px; height: 640px" :center="state.center" :zoom="state.zoom" :loadConfig="loadConfig" @init="mapInit" @click="onClick">
            <tdt-marker-clusterer :markers="markers" :styles="styles"></tdt-marker-clusterer>
            <tdt-marker :position="state.center" :draggable="false" icon="https://soullyoko.github.io/vue-tianditu/marker_red.png"></tdt-marker>
            <tdt-infowindow v-model:target="target" :content="content"></tdt-infowindow>
        </tdt-map>
    </div>
</template>

// 地图点击时触发
const onClick = (e) => {
    state.center = [e.lnglat.lng, e.lnglat.lat]; // 设置中心点坐标

    getTiandituAddress(e.lnglat.lng, e.lnglat.lat);
};
// 天地图逆地址解析
const getTiandituAddress = async (lng, lat) => {
    const response = await fetch(`https://api.tianditu.gov.cn/geocoder?postStr={'lon':${lng},'lat':${lat},'ver':1}&tk=自己申请的Key`);
    const data = await response.json();
    console.log('逆地址解析结果', data);
    return data.result;
};

image.png

地名搜索

官方文档中有地名搜索的组件,实测发现还是存在一些bug,具体如下:

搜索框bug.gif bug目前还没解决,于是自己封装了一个搜索地名,点击切换坐标的函数。下面是实现流程。

搜索位置后更新位置中心坐标.gif

基于天地图地名搜索API配合天地图自定义组件tdt-control来实现。

使用element-plus中的el-select组件支持输入框联想和自定义选项值,将地名搜索后的经过整合到页面上,仿照官方文档的样子进行排版。

image.png

<tdt-map style="width: 800px; height: 640px" :center="state.center" @click="onClick" :zoom="state.zoom" :loadConfig="loadConfig" @init="mapInit">
    <tdt-marker-clusterer :markers="markers" :styles="styles"></tdt-marker-clusterer>
    <tdt-marker :position="state.center" :draggable="false" icon=""></tdt-marker>
    <tdt-infowindow v-model:target="target" :content="content"></tdt-infowindow>

    <tdt-control position="topleft">
        <el-select
            v-model="hotPointID"
            filterable
            remote
            reserve-keyword
            placeholder="输入地点关键词"
            :remote-method="searchAddress"
            :loading="loading"
            clearable
            style="width: 380px; margin-bottom: 5px"
            @change="handleChange"
        >
            <el-option v-for="(item, index) in options" :key="item.hotPointID" :label="item.name" :value="item.lonlat + ',' + index">
                <span>{{ item.name }}</span>
                <span style="font-size: 12px; color: #a8abb2; margin-left: 10px">{{ item.address }}</span>
            </el-option>
            <template #loading>
                <svg class="circular" viewBox="0 0 50 50">
                    <circle class="path" cx="25" cy="25" r="20" fill="none" />
                </svg>
            </template>
        </el-select>
    </tdt-control>
</tdt-map>

// 自定义控件搜索周边信息
const searchAddress = async (queryString) => {
    if (!queryString) return;
    console.log('自定义控件搜索周边信息');
    try {
        const params = {
            postStr: JSON.stringify({
                keyWord: queryString,
                level: '12', // 搜索等级
                queryRadius: '10000',
                pointLonlat: state.center[0] + ',' + state.center[1],
                queryType: '3', // 查询类型 1:普通搜索3:周边搜索服务
                start: 0, // 起始记录
                count: 20, // 每页数量
            }),
            tk: loadConfig.tk,
            type: 'query',
        };
        const response = await fetch(`http://api.tianditu.gov.cn/v2/search?postStr=${encodeURIComponent(params.postStr)}&tk=${loadConfig.tk}&type=${params.type}`);
        const data = await response.json();
        console.log('搜索结果', data);

        if (data && data.pois) {
            options.value = data.pois;
        }
    } catch (error) {
        ElMessage.error('搜索失败');
    } finally {
    }
};

天地图提供了不同的地名搜索,可以根据需求来更换api

image.png

返回当前位置

最后做了一个高德腾讯地图都有的功能,点击返回当前位置。

返回当前位置.gif

也是使用了天地图自定义组件tdt-control,在地图的右下角增加一个返回当前位置的图标,点击这个图标获取当前位置信息,设置中心点,即可实现。

<tdt-map style="width: 800px; height: 640px" :center="state.center" @click="onClick" :zoom="state.zoom" :loadConfig="loadConfig" @init="mapInit">
    <tdt-marker-clusterer :markers="markers" :styles="styles"></tdt-marker-clusterer>
    <tdt-marker :position="state.center" :draggable="false" icon=""></tdt-marker>
    <tdt-infowindow v-model:target="target" :content="content"></tdt-infowindow>
    <!-- 自定义返回当前位置控件 -->
    <tdt-control position="bottomright">
        <div class="controlStyle" @click.stop="controlClick">
            <img style="width: 80%; height: 80%" src="../../assets/myAddress.png" alt="" />
        </div>
    </tdt-control>
</tdt-map>


// 点击自定义控件
const controlClick = () => {
    getLocation();
};

// 获取当前位置信息
const getLocation = () => {
    if (!navigator.geolocation) {
        ElMessage.error('浏览器不支持地理位置服务');
        return;
    }
    navigator.geolocation.getCurrentPosition(
        (position) => {
            state.center = [position.coords.longitude, position.coords.latitude];
        },
        (err) => {
            ElMessageBox.alert('请开启浏览器/电脑位置服务!');
        }
    );
};

最终完整代码

<template>
  <!-- 如果需要点击地图更改地图中心点请td-map添加事件监听 @click="onClick"  -->
  <tdt-map style="width: 800px; height: 640px" :center="state.center" @click="onClick" :zoom="state.zoom"
    :loadConfig="loadConfig" @init="mapInit">
    <!-- 点聚合组件 -->
    <tdt-marker-clusterer :markers="markers" :styles="styles"></tdt-marker-clusterer>
    <!-- 标记点组件 -->
    <tdt-marker :position="state.center" :draggable="false" icon=""></tdt-marker>
    <!-- 点击标记点显示文字 -->
    <tdt-infowindow v-model:target="target" :content="content"></tdt-infowindow>
    <tdt-control  position="topleft">
      <el-select v-model="hotPointID" filterable remote reserve-keyword placeholder="输入地点关键词"
        :remote-method="searchAddress" :loading="loading" clearable style="width: 380px; margin-bottom: 5px"
        @change="handleChange">
        <el-option v-for="(item, index) in options" :key="item.hotPointID" :label="item.name"
          :value="item.lonlat + ',' + index">
          <span>{{ item.name }}</span>
          <span style="font-size: 12px; color: #a8abb2; margin-left: 10px">{{
            item.address
          }}</span>
        </el-option>
        <template #loading>
          <svg class="circular" viewBox="0 0 50 50">
            <circle class="path" cx="25" cy="25" r="20" fill="none" />
          </svg>
        </template>
      </el-select>
    </tdt-control>

    <!-- 自定义返回当前位置控件 -->
    <tdt-control position="bottomright">
      <div class="controlStyle" @click.stop="controlClick">
        <img style="width: 80%; height: 80%" src="../../assets/myAddress.png" alt="" />
      </div>
    </tdt-control>
  </tdt-map>
</template>

<script setup>
import { reactive, onMounted } from "vue";
import "vue-tianditu/lib/style.css";
const loadConfig = { v: "4.0", tk: "申请的key" };
const target = ref(null);
const content = ref("");
const styles = ref([
  {
    // url: 'https://api.tianditu.gov.cn/img/map/markers/b1.png',
    size: [32, 32],
    offset: [-16, -32], // 居中偏移
    textColor: "#FFFFFF",
    textSize: 12,
    range: [0, 50],
  },
  {
    // url: 'https://api.tianditu.gov.cn/img/map/markers/b2.png',
    size: [48, 48],
    offset: [-24, -48],
    textColor: "#FF0000",
    textSize: 14,
    range: [50, 200],
  },
  {
    // url: 'https://api.tianditu.gov.cn/img/map/markers/b3.png',
    size: [64, 64],
    offset: [-32, -64],
    textColor: "#000000",
    textSize: 16,
    range: [200, 500],
  },
]);
const map = ref(null); // 地图组件
// 生成北京周边坐标(示例)
const markers = ref(
  Array.from({ length: 500 }, (_, i) => ({
    position: [116.3 + Math.random() * 0.5, 39.8 + Math.random() * 0.5],
    extData: `cluster-${i}`,
  }))
);
const loading = ref(false);
const options = ref([]); // 位置搜索选项
const hotPointID = ref(""); // 搜索选项id

// 地图配置
const state = reactive({
  center: [116.404, 39.915], // 中心点
  zoom: 16, // 缩放比例
});

onMounted(() => {
  getLocation(); // 获取当前经纬度
});
// 地图组件初始化
const mapInit = (map) => {
  console.log("地图初始化", map);
  map.value = map;
};
// 获取当前位置信息
const getLocation = () => {
  if (!navigator.geolocation) {
    ElMessage.error("浏览器不支持地理位置服务");
    return;
  }
  navigator.geolocation.getCurrentPosition(
    (position) => {
      state.center = [position.coords.longitude, position.coords.latitude];
    },
    (err) => {
      ElMessageBox.alert("请开启浏览器/电脑位置服务!");
    }
  );
};
// 地图点击时触发
const onClick = (e) => {
  state.center = [e.lnglat.lng, e.lnglat.lat];

  getTiandituAddress(e.lnglat.lng, e.lnglat.lat);
};
// 天地图逆地址解析
const getTiandituAddress = async (lng, lat) => {
  const response = await fetch(
    `https://api.tianditu.gov.cn/geocoder?postStr={'lon':${lng},'lat':${lat},'ver':1}&tk=申请的key`
  );
  const data = await response.json();
  return data.result;
};
// 点击自定义控件
const controlClick = () => {
  getLocation();
};

// 自定义控件搜索周边信息
const searchAddress = async (queryString) => {
  if (!queryString) return;
  console.log("自定义控件搜索周边信息");
  try {
    const params = {
      postStr: JSON.stringify({
        keyWord: queryString,
        level: "12", // 搜索等级
        queryRadius: "10000",
        pointLonlat: state.center[0] + "," + state.center[1],
        queryType: "3", // 查询类型 1:普通搜索3:周边搜索服务
        start: 0, // 起始记录
        count: 20, // 每页数量
      }),
      tk: loadConfig.tk,
      type: "query",
    };
    const response = await fetch(
      `http://api.tianditu.gov.cn/v2/search?postStr=${encodeURIComponent(
        params.postStr
      )}&tk=${loadConfig.tk}&type=${params.type}`
    );
    const data = await response.json();
    console.log("搜索结果", data);

    if (data && data.pois) {
      options.value = data.pois;
    }
  } catch (error) {
    ElMessage.error("搜索失败");
  } finally {
  }
};
// 下拉框发生变化
const handleChange = (val) => {
  console.log("选择的值", val);
  const arr = val.split(",");
  state.center = [arr[0], arr[1]];
};
</script>

<style lang="scss" scoped>
.controlStyle {
  width: 60px;
  height: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #fff;
  border-radius: 10px;
  margin: 0 20px 20px 0;
  cursor: pointer;
}
</style>

文章到此就结束了,由于项目还在谈论中后期还会有天地图的需求,我也会及时更新这篇文章的。有任何问题欢迎大家留言🤗🤗🤗