高德地图调研-前端文档

304 阅读9分钟

技术需求如下:

1、封装高德 vue3 hooks脚本

2、实现3D城市地图

  ① 点击具体位置可以获取对应的坐标信息及位置信息

  ② 轨迹动画效果,以3D地图为底,实现从起点到终点

3、实现点聚合效果

4、覆盖物、几何计算的了解

① 实现5公里,10公里范围内的圆形覆盖,并计算其面积和距离

5、高德地图和echarts的结合

技术实现如下:

一 封装高德 vue3 hooks脚本

首先注册高德地图,安装依赖,使用key,安装使用插件,加载使用AMapUI等然后绘制地图

useMap.js
import AMapLoader from "@amap/amap-jsapi-loader";
import { ref, onMounted, onUnmounted } from "vue";

/**
 * 返回地图相关内容
 * @param HTMLID 地图ID
 * @param options 地图初始值
 * @param callback 地图绘制完成之后的方法
 */
export default function useMap(HTMLID,options,callback) {
  const AMapItem = ref();
  const container = ref();

  // 注册 高德地图
  const init = () => {
    let mapItem = new Promise((resolve, reject) => {
      window._AMapSecurityConfig = {
        securityJsCode: "a780c3a6a5547aaca7b94205c563fadc",
      };
      AMapLoader.load({
        key: "522a23c47a02eb83559470d9326eeb9b", //备用测试key e1dedc6bdd765d46693986ff7ff969f4
        version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
        // 需要使用的的插件列表,如比例尺'AMap.Scale'等
        plugin: [
          "AMap.IndexCluster",
          "AMap.Autocomplete", // 输入提示插件
          "AMap.PlaceSearch", // POI搜索插件
          "AMap.Scale", // 右下角缩略图插件 比例尺
          "AMap.OverView", // 地图鹰眼插件
          "AMap.ToolBar", // 地图工具条
          "AMap.Geolocation", // 定位控件,用来获取和展示用户主机所在的经纬度位置
          "AMap.Geocoder",
          "AMap.MapType",
          "AMap.InfoWindow",
        ],
        AMapUI: {
          // 是否加载 AMapUI,缺省不加载
          version: "1.1", // AMapUI 版本
          plugins: ["overlay/SimpleMarker", "misc/PathSimplifier"], // 需要加载的 AMapUI ui插件
        },
      })
        .then((AMap) => {
          resolve(AMap);
        })
        .catch((e) => {
          reject(e);
          console.log(e);
        });
    });
    return mapItem;
  };

 
 

  onMounted(() => {
    init().then((AMap) => {
      AMapItem.value = AMap;
      // 创建地图
      container.value = new AMap.Map(HTMLID, options);
      callback(AMap)
    });
  });
  return {
    AMapItem,
    container,
  };
}

二 实现点击具体位置可以获取对应的坐标信息及位置信息

<template>
    <div id="container"></div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import useMap from "@/hooks/useMap";
const infoWindow = ref(); //选择的弹出框信息
const { AMapItem, container } = useMap(
  "container",
  {
    resizeEnable: true, //地图是否可旋转
    terrain: true, //开启地形图
    viewMode: "3D", //地图模式
    rotateEnable: true, //是否开启地图旋转交互 鼠标右键 + 鼠标画圈移动 或 键盘Ctrl + 鼠标左键画圈移动
    pitchEnable: true, //是否开启地图倾斜交互 鼠标右键 + 鼠标上下移动或键盘Ctrl + 鼠标左键上下移动
    zoom: 17, //初始化地图层级
    // rotation: -15, //初始地图顺时针旋转的角度
    zooms: [2, 30], //地图显示的缩放级别范围
    center: [120.469626, 36.094172], //初始地图中心经纬度
    pitch: 70, //地图俯仰角度,有效范围 0 度- 83 度
    isHotspot: true, //是否开启地图热点和标注的 hover 效果
  },
  (AMapItem) => {
    AMapItem.plugin(
        ["AMap.InfoWindow", "AMap.PlaceSearch"],
        function () {
            getPositionInformation(AMapItem)
         
        }
      );
  }
);

function getPositionInformation(AMap) {
   
  // 鼠标放上去搜索加弹框
  var placeSearch = new AMap.PlaceSearch(); //构造地点查询类
  infoWindow.value = new AMap.InfoWindow({});
  //  鼠标滑过热点时触发
  container.value.on("hotspotover", function (result) {
    placeSearch.getDetails(result.id, function (status, result) {
      // 根据PGUID 查询POI 详细信息
      console.log('result',result)
      var poiArr = result.poiList.pois;
      if (status === "complete" && result.info === "OK"&&poiArr.length!=0) {
      
        var location = poiArr[0].location;
        infoWindow.value.setContent(createContent(poiArr[0]));
        infoWindow.value.open(container.value, location);
      }
    });
  });
}
function createContent(poi) {
  //信息窗体内容
  var s = [];
  s.push(
    '<div class="info-title">' +
      poi.name +
      '</div><div class="info-content">' +
      "地址:" +
      poi.address
  );
  s.push("电话:" + poi.tel);
  s.push("经纬度:" + poi.location.pos + "");
  s.push("<div>");
  return s.join("<br>");
}
onUnmounted(() => {
  container.value?.destroy();
});
</script>
<style scoped>
#container {
  padding: 0px;
  margin: 0px;
  width: 100%;
  position: fixed;
  top: 0;
  bottom: 0;
}
#loadingTip {
  position: absolute;
  z-index: 9999;
  top: 0;
  left: 0;
  padding: 3px 10px;
  background: red;
  color: #fff;
  font-size: 14px;
}

</style>

鼠标放上后效果如下

image.png

三 轨迹动画效果,实现从起点到终点

引入UI组件,绘制路线的经纬度

<template>
  <div>
    <div id="container"></div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import useMap from "@/hooks/useMap";
const { AMapItem, container } = useMap(
  "container",
  {
    resizeEnable: true, //地图是否可旋转
    terrain: true, //开启地形图
    rotateEnable: true, //是否开启地图旋转交互 鼠标右键 + 鼠标画圈移动 或 键盘Ctrl + 鼠标左键画圈移动
    pitchEnable: true, //是否开启地图倾斜交互 鼠标右键 + 鼠标上下移动或键盘Ctrl + 鼠标左键上下移动
    zoom: 17, //初始化地图层级
    zooms: [2, 30], //地图显示的缩放级别范围
    center: [120.469626, 36.094172], //初始地图中心经纬度
    isHotspot: true, //是否开启地图热点和标注的 hover 效果
  },
  (AMap) => {
    console.log('AMapItem',AMap)
    AMapUI.load(["ui/misc/PathSimplifier"], function (PathSimplifier) {
      if (!PathSimplifier.supportCanvas) {
        alert("当前环境不支持 Canvas!");
        return;
      }
      initOrbitPage(PathSimplifier);
    });
  }
);
// 动画轨迹
function initOrbitPage(PathSimplifier) {
    console.log('PathSimplifier',PathSimplifier)
  //创建组件实例
  var pathSimplifierIns = new PathSimplifier({
    zIndex: 100,
    map: container.value, //所属的地图实例
    getPath: function (pathData, pathIndex) {
      //返回轨迹数据中的节点坐标信息,[AMap.LngLat, AMap.LngLat...] 或者 [[lng|number,lat|number],...]
      return pathData.path;
    },
    getHoverTitle: function (pathData, pathIndex, pointIndex) {
      //返回鼠标悬停时显示的信息
      if (pointIndex >= 0) {
        //鼠标悬停在某个轨迹节点上
        return (
          pathData.name + ",点:" + pointIndex + "/" + pathData.path.length
        );
      }
      //鼠标悬停在节点之间的连线上
      return pathData.name + ",点数量" + pathData.path.length;
    },
    renderOptions: {
      pathTolerance: 5,
      //轨迹线的样式
      pathLineStyle: {
        strokeStyle: "red",
        lineWidth: 5,
        dirArrowStyle: true,
      },
    },
  });
  window.pathSimplifierIns = pathSimplifierIns;
  //这里构建两条简单的轨迹,仅作示例
  pathSimplifierIns.setData([
    {
      name: "青岛路线",
      path: [
        [120.454872, 36.081169],
        [120.461817, 36.089068],
        [120.467918, 36.091224],
        [120.465185, 36.093182],
        [120.454872, 36.107663],
        [120.415399, 36.105078],
        [120.433611, 36.088984],
        [120.379194, 36.089942],
      ],
    },
  ]);

  //创建一个巡航器
  var navg0 = pathSimplifierIns.createPathNavigator(
    0, //关联第1条轨迹
    {
      loop: true, //循环播放
      speed: 10000,
    }
  );

  navg0.start();
}
onUnmounted(() => {
  container.value?.destroy();
});
</script>

效果如下

四 实现点聚合效果, 覆盖物、几何计算的了解

以北京为例 滑动缩小到小区后点击一个地名 绘制他的半径为500m或者1000m圆

实现5公里,10公里范围内的圆形覆盖,并计算其面积和距离

<template>
  <div>
    <div id="container"></div>
    <div class="input-card" style="width: 120px" v-if="circleInfo">
      <button
        class="btn"
        @click="AMapItemGet(500)"
        :class="AMapItemValue == 500 ? 'btnActive' : ''"
        style="margin-bottom: 5px"
      >
        500m
      </button>
      <button
        class="btn"
        @click="AMapItemGet(1000)"
        :class="AMapItemValue == 1000 ? 'btnActive' : ''"
      >
        1000m
      </button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import useMap from "@/hooks/useMap";
import { district } from "@/views/map/district.js";
import { points } from "@/views/map/community.js";
const AMapItemValue = ref(500);
const selectLatInfo =ref() //选择的经纬度
const circleInfo=ref()
const { AMapItem, container } = useMap(
  "container",
  {
    resizeEnable: true, //地图是否可旋转
    terrain: true, //开启地形图
    viewMode: "3D", //地图模式
    rotateEnable: true, //是否开启地图旋转交互 鼠标右键 + 鼠标画圈移动 或 键盘Ctrl + 鼠标左键画圈移动
    pitchEnable: true, //是否开启地图倾斜交互 鼠标右键 + 鼠标上下移动或键盘Ctrl + 鼠标左键上下移动
    zoom: 8, //初始化地图层级
    // rotation: -15, //初始地图顺时针旋转的角度
    zooms: [2, 30], //地图显示的缩放级别范围
    center: [116.405285,39.904989], //初始地图中心经纬度
    pitch: 70, //地图俯仰角度,有效范围 0 度- 83 度
    isHotspot: true, //是否开启地图热点和标注的 hover 效果
  },
  (AMapItem) => {
    AMapItem.plugin(
        ["AMap.IndexCluster"],
        function () {
          IndexClusterFun(AMapItem);
         
        }
      );
  }
);
  // 点聚合
  function IndexClusterFun(AMap) {
    var clusterIndexSet = {
      city: {
        minZoom: 2,
        maxZoom: 10,
      },
      district: {
        minZoom: 10,
        maxZoom: 12,
      },
      area: {
        minZoom: 12,
        maxZoom: 15,
      },
      community: {
        minZoom: 15,
        maxZoom: 22,
      },
    };
    // 聚合规则:通过定义的配置对数据内属性值相同的数据聚合到一起,比如:当地图级别为2到10的时候,将使用 city 属性聚合,数据中 city 值相同的将被聚合在一起。
    var cluster = new AMap.IndexCluster(container.value, points, {
      renderClusterMarker: _renderClusterMarker, //自定义聚合点样式
      clusterIndexSet: clusterIndexSet, //聚合规则
    });
    console.log("cluster", cluster);
  }
  // 自定义聚合点样式
  function _renderClusterMarker(context) {
    console.log("_renderClusterMarker-context1111", context);
    // 自定义点标记样式
    var styleObj = getStyle(context);
    var div = document.createElement("div");
    div.className = "amap-cluster";
    div.style.backgroundColor = styleObj.bgColor;
    div.style.width = styleObj.size + "px";
    if (styleObj.index <= 2) {
      div.style.height = styleObj.size + "px";
      // 自定义点击事件
      context.marker.on("click", function (e) {
        var curZoom = container.value.getZoom(); //获取当前地图缩放级别
        if (curZoom < 20) {
          curZoom += 3;
        }
        container.value.setZoomAndCenter(curZoom, e.lnglat);
      });
    } else if (context.index.mainKey == "community") {
      context.marker.on("click", function (e) {
        let lat = context.clusterData[0].lnglat.lat;
        let lng = context.clusterData[0].lnglat.lng;
        let data ={lat,lng}
        selectLatInfo.value=data
        clickCircle(data);
      });
    }
    div.style.border = "solid 1px " + styleObj.borderColor;
    div.style.borderRadius = styleObj.size + "px";
    div.innerHTML = styleObj.text;
    div.style.color = styleObj.color;
    div.style.textAlign = styleObj.textAlign;
    div.style.boxShadow = styleObj.boxShadow;
    div.style.fontSize = styleObj.index==3?'15px': Number(styleObj.size/3) + "px";
    context.marker.setContent(div); //设置点标记显示的自定义内容
    // 自定义聚合点标记显示位置
    var position = getPosition(context);
    if (position) {
      context.marker.setPosition(position);
    }
    // 设置覆盖物锚点设置点标记锚点。
    context.marker.setAnchor("center");
  }
  function clickCircle(e) {
    console.log("·································", e);
    if (!e) return;
    console.log(e.lat, e.lng);
    //设置圆形位置
    var center = new AMapItem.value.LngLat(e.lng, e.lat); //
    //设置圆的半径大小
    var radius = AMapItemValue.value
    if(circleInfo.value){
      container.value.remove(circleInfo.value)
    }
    
    //创建圆形 Circle 实例
    var circle = new AMapItem.value.Circle({
      center: center, //圆心
      radius: radius, //半径
      borderWeight: 3, //描边的宽度
      strokeColor: "#FF33FF", //轮廓线颜色
      strokeOpacity: 1, //轮廓线透明度
      strokeWeight: 1, //轮廓线宽度
      fillOpacity: 0.2, //圆形填充透明度
      strokeStyle: "dashed", //轮廓线样式
      strokeDasharray: [10, 10],
      fillColor: "#1791fc", //圆形填充颜色
      zIndex: 50, //圆形的叠加顺序
    });
    circleInfo.value=circle
    //圆形 Circle 对象添加到 Map
    console.log('circleInfo.value---circle',circle)
    container.value.add(circle);
    //根据覆盖物范围调整视野
    // container.value.setFitView([ circle ])
  }
  function getStyle(context) {
    var clusterData = context.clusterData; // 聚合中包含数据
    var index = context.index; // 聚合的条件
    var count = context.count; // 聚合中点的总数
    var color = ["8,60,156", "66,130,198", "107,174,214", "78,200,211"];
    var indexs = ["city", "district", "area", "community"];
    var i = indexs.indexOf(index["mainKey"]);
    var text = clusterData[0][index["mainKey"]];
    var size = Math.round(30 + Math.pow(count / points.length, 1 / 5) * 70);
     if (i==0){
      text = '<span class="showNameCity">' + text + "</span>";
      size = 60
    } else if (i <= 2) {
      text = '<span class="showName">' + text + "</span>";
      size = 50
    } else{
      size =100
    }
    var style = {
      bgColor: "rgba(" + color[i] + ",.8)",
      borderColor: "rgba(" + color[i] + ",1)",
      text: text,
      size: size,
      index: i,
      color: "#ffffff",
      textAlign: "center",
      boxShadow: "0px 0px 5px rgba(0,0,0,0.8)",
    };
    return style;
  }
  function getPosition(context) {
    var key = context.index.mainKey;
    var dataItem = context.clusterData && context.clusterData[0];
    var districtName = dataItem[key];
    if (!district[districtName]) {
      return null;
    }
    var center = district[districtName].center.split(",");
    var centerLnglat = new AMapItem.value.LngLat(center[0], center[1]); //点标记的经纬度信息
    console.log('center',center,centerLnglat)
    return centerLnglat;
  }
function AMapItemGet(e) {
  AMapItemValue.value = e;
  let opts = {
    center: selectLatInfo.value, //圆心
    radius: AMapItemValue.value, //半径
    borderWeight: 3, //描边的宽度
    strokeColor: "#FF33FF", //轮廓线颜色
    strokeOpacity: 1, //轮廓线透明度
    strokeWeight: 1, //轮廓线宽度
    fillOpacity: 0.2, //圆形填充透明度
    strokeStyle: "dashed", //轮廓线样式
    strokeDasharray: [10, 10],
    fillColor: "#1791fc", //圆形填充颜色
    zIndex: 50, //圆形的叠加顺序
  };
  container.value.remove(circleInfo.value);
  //创建圆形 Circle 实例
  var circle = new AMapItem.value.Circle(opts);
  circleInfo.value._opts.radius = AMapItemValue.value;
  container.value.add(circleInfo.value);
  container.value.setFitView([circle]);
}

onUnmounted(() => {
  container.value?.destroy();
});
</script>
<style scoped>
#container {
  padding: 0px;
  margin: 0px;
  width: 100%;
  position: fixed;
  top: 0;
  bottom: 0;
}
#loadingTip {
  position: absolute;
  z-index: 9999;
  top: 0;
  left: 0;
  padding: 3px 10px;
  background: red;
  color: #fff;
  font-size: 14px;
}
.amap-cluster {
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
  font-size: 6px;
}
.showName {
  font-size: 8px;
}
.showCount,
.showName {
  display: block;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
  width: 80%;
  line-height: 40px;
}
.showNameCity{
  width: 50px;
  height: 50px;
  line-height: 60px;
  background-color: #25a5f7;
}
.input-card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  word-wrap: break-word;
  background-color: #fff;
  background-clip: border-box;
  border-radius: 0.25rem;
  width: 22rem;
  border-width: 0;
  border-radius: 0.4rem;
  box-shadow: 0 2px 6px 0 rgba(114, 124, 245, 0.5);
  position: fixed;
  bottom: 1rem;
  right: 1rem;
  -ms-flex: 1 1 auto;
  flex: 1 1 auto;
  padding: 0.75rem 1.25rem;
  position: fixed;
  z-index: 99;
}
.btn {
  display: inline-block;
  font-weight: 400;
  text-align: center;
  white-space: nowrap;
  vertical-align: middle;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  border: 1px solid transparent;
  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out,
    border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
  background-color: transparent;
  background-image: none;
  color: #25a5f7;
  border-color: #25a5f7;
  padding: 0.25rem 0.5rem;
  line-height: 1.5;
  border-radius: 1rem;
  -webkit-appearance: button;
  cursor: pointer;
}
.btnActive {
  background-color: #25a5f7;
  color: #fff;
}
</style>

效果如下

使用数据如下

district.js

export const district = {
    '北京': {
        "adcode" : "110000",
        "center" : "116.405285,39.904989",
        // "center" : "117.939152,40.976204",
    },
    '亦庄开发区': {
        "center": "116.506647,39.795337",
    },
    '密云区': {
        "adcode" : "110118",
        "center" : "116.843352,40.377362",
    },
    '怀柔区': {
        "adcode" : "110116",
        "center" : "116.637122,40.324272",
    },
    '门头沟区': {
        "adcode" : "110109",
        "center" : "116.105381,39.937183",
    },
    '顺义区': {
        "adcode" : "110113",
        "center" : "116.653525,40.128936",
    },
    '朝阳区': {
        "adcode" : "110105",
        "center" : "116.486409,39.921489",
    },
    '通州区': {
        "adcode" : "110112",
        "center" : "116.658603,39.902486",
    },
    '大兴区': {
        "adcode" : "110115",
        "center" : "116.338033,39.728908",
    },
    '昌平区': {
        "adcode" : "110114",
        "center" : "116.235906,40.218085",
    },
    '西城区': {
        "adcode" : "110102",
        "center" : "116.366794,39.915309",
    },
    '东城区': {
        "adcode" : "110101",
        "center" : "116.418757,39.917544",
    },
    '房山区': {
        "adcode" : "110111",
        "center" : "116.139157,39.735535",
    },
    '石景山区': {
        "adcode" : "110107",
        "center" : "116.195445,39.914601",
    },
    '海淀区': {
        "adcode" : "110108",
        "center" : "116.310316,39.956074",
    },
    '丰台区': {
        "center": "116.286968,39.863642",
    },
};

community.js