React 高德地图实战指南:动态路线绘制、交互控件与深色模式切换

发布于:2025-03-03 ⋅ 阅读:(39) ⋅ 点赞:(0)

地图的基本展示

首先实现地图的基本展示

在这里插入图片描述

组件定义与状态初始化

  const mapContainerRef = useRef<HTMLDivElement>(null);
  const [isDarkMode, setIsDarkMode] = useState<boolean>(false);
  • mapContainerRef: 使用useRef创建一个引用,指向地图容器的DOM元素。
  • isDarkMode: 使用useState创建一个状态变量,用于跟踪当前是否处于深色模式。

初始化地图函数

const initMap = () => {
  if (mapContainerRef.current) {
    const map = new window.AMap.Map(mapContainerRef.current, {
      zoom: 5,
      center: [110, 35],
      mapStyle: isDarkMode ? "amap://styles/dark" : "",
    });
    map.setFitView();
  }
};
  • initMap函数负责初始化地图实例:

    • 检查mapContainerRef.current是否存在以确保DOM元素已经挂载。
    • 创建一个新的地图实例,设置缩放级别、中心点以及根据isDarkMode状态选择地图样式。
    • 调用map.setFitView()方法调整地图视图以适应所有覆盖物(在这个例子中没有具体添加覆盖物)。

使用useEffect处理副作用

useEffect(() => {
  window._AMapSecurityConfig = {
    securityJsCode: "api",
  };

  const script = document.createElement("script");
  script.src = "https://webapi.amap.com/maps?v=1.4.15&key=api";
  script.async = true;

  document.body.appendChild(script);

  script.onload = initMap;

  return () => {
    document.body.removeChild(script);
  };
}, [isDarkMode]);
  • 在组件挂载时执行以下操作:

    • 配置高德地图的安全密钥。
    • 动态创建并插入一个<script>标签来加载高德地图的JS API。
    • 设置脚本加载完成后调用initMap函数来初始化地图。
    • 返回一个清理函数,在组件卸载时移除之前插入的脚本标签,防止内存泄漏。
  • [isDarkMode]作为依赖数组,意味着每当isDarkMode状态改变时,都会重新执行useEffect中的逻辑,从而更新地图样式。

渲染组件

return (
  <div className="box">
    <h2>重庆到成都的路线</h2>
    <button onClick={() => setIsDarkMode(!isDarkMode)}>
      {isDarkMode ? "切换到浅色模式" : "切换到深色模式"}
    </button>
    <div ref={mapContainerRef} style={{ width: "100%", height: "500px" }}></div>
  </div>
);
  • 显示标题“重庆到成都的路线”。
  • 提供一个按钮,点击可以切换浅色模式和深色模式。

注意:切换浅色模式和深色模式功能,要配置安全密钥、替换为你的 API Key后才会生效

  • 使用ref属性将mapContainerRef绑定到一个div元素上,这个div作为地图的容器,并设置了宽度和高度

注意:div 一定要设置宽度和高度才能看到

完整代码:

import React, { useEffect, useRef, useState } from "react";

const DataPage = () => {
  // 使用 useRef 来获取容器元素
  const mapContainerRef = useRef<HTMLDivElement>(null);
  const [isDarkMode, setIsDarkMode] = useState<boolean>(false);

  // 初始化地图
  const initMap = () => {
    if (mapContainerRef.current) {
      // 初始化地图实例
      const map = new window.AMap.Map(mapContainerRef.current, {
        zoom: 5, // 缩放级别调整为适合显示两地的距离
        center: [110, 35], // 中心点设置为一个中间位置,以便同时看到两个位置
        mapStyle: isDarkMode ? "amap://styles/dark" : "", // 根据是否是深色模式选择地图样式
      });

      // 缩放到包含整个折线的视图
      map.setFitView();
    }
  };

  useEffect(() => {
    // 设置代理服务器地址
    window._AMapSecurityConfig = {
      securityJsCode: "api", // 配置安全密钥
    };

    // 创建并插入地图 JSAPI 脚本
    const script = document.createElement("script");
    script.src = "https://webapi.amap.com/maps?v=1.4.15&key=api"; // 替换为你的 API Key
    script.async = true;

    // 将脚本插入到 body 中
    document.body.appendChild(script);

    // 初始化地图
    script.onload = initMap;

    // 清理函数,在组件卸载时移除脚本标签
    return () => {
      document.body.removeChild(script);
    };
  }, [isDarkMode]);

  return (
    <div className="box">
      <h2>重庆到成都的路线</h2>

      <button onClick={() => setIsDarkMode(!isDarkMode)}>
        {isDarkMode ? "切换到浅色模式" : "切换到深色模式"}
      </button>

      <div
        ref={mapContainerRef}
        style={{ width: "100%", height: "500px" }}
      ></div>
    </div>
  );
};

export default DataPage;


添加控件

在这里插入图片描述

按钮控制显示/隐藏缩放控件

<button onClick={() => setToolbarVisible(!toolbarVisible)}>
  {toolbarVisible ? "隐藏缩放控件" : "显示缩放控件"}
</button>
  • onClick事件处理函数:

    • 当用户点击按钮时,触发一个匿名函数,该函数调用setToolbarVisible来切换toolbarVisible状态变量的值。
    • !toolbarVisible表示取反当前的状态值。如果toolbarVisibletrue,则设置为false;反之亦然。
  • 按钮文本:

    • 使用三元运算符根据toolbarVisible的状态决定按钮上显示的文本:

      • 如果toolbarVisibletrue,则显示“隐藏缩放控件”。
      • 如果toolbarVisiblefalse,则显示“显示缩放控件”。

添加控件函数

const addControls = (map, toolbarVisible) => {
  if (!toolbarVisible) return;

  window.AMap.plugin(["AMap.ToolBar", "AMap.Scale"], () => {
    const controls = [
      new window.AMap.ToolBar(), // 缩放工具条实例化
      new window.AMap.Scale(), // 比例尺实例化
    ];

    controls.forEach((control) => {
      map.addControl(control); // 添加控件
    });
  });
};
  • addControls函数:

    • 接受两个参数:map(地图实例)和toolbarVisible(布尔值,指示是否显示控件)。
    • 首先检查toolbarVisible是否为false。如果是,则直接返回,不执行后续逻辑。
  • 加载插件:

    • 使用window.AMap.plugin方法加载高德地图的插件AMap.ToolBarAMap.Scale
    • 这些插件提供了地图上的缩放控件和比例尺功能。
    • 回调函数会在插件加载完成后执行。
  • 创建并添加控件:

    • 创建一个包含缩放工具条和比例尺实例的数组controls
    • 使用forEach遍历controls数组,并调用map.addControl(control)将每个控件添加到地图上。

完整代码:

import React, { useEffect, useRef, useState } from "react";

const DataPage= () => {
  // 使用 useRef 来获取容器元素
  const mapContainerRef = useRef<HTMLDivElement>(null);
  const [isDarkMode, setIsDarkMode] = useState<boolean>(false);
  const [toolbarVisible, setToolbarVisible] = useState<boolean>(true);

  // 初始化地图
  const initMap = () => {
    if (mapContainerRef.current) {
      // 初始化地图实例
      const map = new window.AMap.Map(mapContainerRef.current, {
        zoom: 5, // 缩放级别调整为适合显示两地的距离
        center: [110, 35], // 中心点设置为一个中间位置,以便同时看到两个位置
        mapStyle: isDarkMode ? "amap://styles/dark" : "", // 根据是否是深色模式选择地图样式
      });

      // 缩放到包含整个折线的视图
      map.setFitView();

      addControls(map, toolbarVisible);
    }
  };

  // 添加控件
  const addControls = (map, toolbarVisible) => {
    if (!toolbarVisible) return;

    window.AMap.plugin(["AMap.ToolBar", "AMap.Scale"], () => {
      const controls = [
        new window.AMap.ToolBar(), // 缩放工具条实例化
        new window.AMap.Scale(), // 比例尺实例化
      ];

      controls.forEach((control) => {
        map.addControl(control); // 添加控件
      });
    });
  };

  useEffect(() => {
    // 设置代理服务器地址
    window._AMapSecurityConfig = {
      securityJsCode: "api", // 配置安全密钥
    };

    // 创建并插入地图 JSAPI 脚本
    const script = document.createElement("script");
    script.src = "https://webapi.amap.com/maps?v=1.4.15&key=api"; // 替换为你的 API Key
    script.async = true;

    // 将脚本插入到 body 中
    document.body.appendChild(script);

    // 初始化地图
    script.onload = initMap;

    // 清理函数,在组件卸载时移除脚本标签
    return () => {
      document.body.removeChild(script);
    };
  }, [isDarkMode, toolbarVisible]);

  return (
    <div className="box">
      <h2>重庆到成都的路线</h2>

      <button onClick={() => setIsDarkMode(!isDarkMode)}>
        {isDarkMode ? "切换到浅色模式" : "切换到深色模式"}
      </button>

      <button onClick={() => setToolbarVisible(!toolbarVisible)}>
        {toolbarVisible ? "隐藏缩放控件" : "显示缩放控件"}
      </button>

      <div
        ref={mapContainerRef}
        style={{ width: "100%", height: "500px" }}
      ></div>
    </div>
  );
};

export default DataPage;


定义坐标并创建折线

在这里插入图片描述

定义坐标并创建折线

const beijing: [number, number] = [106.5516, 29.563]; // 重庆市中心坐标
const chengdu: [number, number] = [104.06579, 30.570462]; // 成都市中心坐标

if (showPolyline) {
  // 创建折线路径
  const path = [beijing, chengdu];

  // 创建折线对象
  const polyline = new window.AMap.Polyline({
    path: path, // 折线路径
    strokeColor: "#FF0000", // 线条颜色
    strokeWeight: 5, // 线条宽度
    strokeOpacity: 0.8, // 线条透明度
    map: map, // 将折线添加到地图实例中
  });

  // 缩放到包含整个折线的视图
  map.setFitView([polyline]);

  addFlightAnimation(map, polyline);
}
  • 定义坐标:

    • beijingchengdu 分别存储了重庆和成都的地理坐标(经度和纬度)。
  • 创建折线路径:

    • 如果 showPolylinetrue,则创建一个由两个坐标点组成的路径数组 path
  • 创建折线对象:

    • 使用 window.AMap.Polyline 创建一个新的折线对象,并设置其属性:

      • path: 折线路径,即从重庆到成都的坐标点。
      • strokeColor: 折线的颜色,这里是红色 (#FF0000)。
      • strokeWeight: 折线的宽度,单位是像素。
      • strokeOpacity: 折线的透明度,范围是 [0, 1]
      • map: 将折线添加到的地图实例。
  • 缩放地图以适应折线:

    • 调用 map.setFitView([polyline]) 方法,调整地图的缩放级别和中心点,使得折线完全显示在地图视图中。
  • 添加飞行路径动画:

    • 调用 addFlightAnimation(map, polyline) 函数,为折线路径添加飞行动画效果。

完整代码:

import React, { useEffect, useRef, useState } from "react";
import "./index.scss";

// 定义 AMap 相关类型
declare global {
  interface Window {
    AMap: any;
    _AMapSecurityConfig: {
      serviceHost: string;
    };
  }
}

interface MapPageProps {}

const MapPage: React.FC<MapPageProps> = () => {
  // 使用 useRef 来获取容器元素
  const mapContainerRef = useRef<HTMLDivElement>(null);
  const [showPolyline, setShowPolyline] = useState<boolean>(true);
  const [isDarkMode, setIsDarkMode] = useState<boolean>(false);
  const [toolbarVisible, setToolbarVisible] = useState<boolean>(true);

  // 初始化地图
  const initMap = () => {
    if (mapContainerRef.current) {
      // 初始化地图实例
      const map = new window.AMap.Map(mapContainerRef.current, {
        zoom: 5, // 缩放级别调整为适合显示两地的距离
        center: [110, 35], // 中心点设置为一个中间位置,以便同时看到两个位置
        mapStyle: isDarkMode ? "amap://styles/dark" : "", // 根据是否是深色模式选择地图样式
      });

      // 定义重庆和成都的坐标
      const beijing: [number, number] = [106.5516, 29.563]; // 重庆市中心坐标
      const chengdu: [number, number] = [104.06579, 30.570462]; // 成都市中心坐标

      if (showPolyline) {
        // 创建折线路径
        const path = [beijing, chengdu];

        // 创建折线对象
        const polyline = new window.AMap.Polyline({
          path: path, // 折线路径
          strokeColor: "#FF0000", // 线条颜色
          strokeWeight: 5, // 线条宽度
          strokeOpacity: 0.8, // 线条透明度
          map: map, // 将折线添加到地图实例中
        });

        // 缩放到包含整个折线的视图
        map.setFitView([polyline]);
        addFlightAnimation(map, polyline);
      }
      addControls(map, toolbarVisible);
    }
  };

  // 添加控件
  const addControls = (map: any, toolbarVisible: boolean) => {
    if (!toolbarVisible) return;

    window.AMap.plugin(["AMap.ToolBar", "AMap.Scale"], () => {
      const controls = [
        new window.AMap.ToolBar(), // 缩放工具条实例化
        new window.AMap.Scale(), // 比例尺实例化
      ];

      controls.forEach((control) => {
        map.addControl(control); // 添加控件
      });
    });
  };

  // 添加飞行路径动画
  const addFlightAnimation = (map: any, polyline: any) => {
    // 创建飞机标记
    const marker = new window.AMap.Marker({
      icon: "https://webapi.amap.com/images/car.png", // 使用有效的图片URL
      size: new window.AMap.Size(32, 32), // 设置图标的大小,单位为像素,默认值是图片的实际大小
      offset: new window.AMap.Pixel(-16, -16), // 图标偏移量,通常是-size.width/2, -size.height
      autoRotation: true, // 自动旋转
      angle: -90, // 初始角度
    });

    // 添加标记到地图
    marker.setMap(map);

    // 获取路径点数组
    const path = polyline.getPath();

    if (path.length < 2) {
      console.error("路径点不足,无法进行动画");
      return;
    }

    let count = 0;
    const length = path.length;

    // 动画函数
    const animateMarker = () => {
      count = (count + 1) % length;
      const lnglat = path[count];
      marker.setPosition(lnglat); // 更新标记位置

      // 计算下一个点的方向并设置图标旋转角度
      if (count < length - 1) {
        const nextLnglat = path[count + 1];
        const angle = getAngle(lnglat, nextLnglat);
        marker.setRotation(angle);
      } else {
        // 如果到达最后一个点,可以选择停止动画或重新开始
        console.log("动画完成,可以在这里处理结束逻辑");
        // 例如重新开始动画:
        count = 0;
      }

      // 使用 requestAnimationFrame 实现平滑动画
      window.requestAnimationFrame(animateMarker);
    };

    // 开始动画
    window.requestAnimationFrame(animateMarker);
  };

  // 计算两点之间的方向角
  const getAngle = (start: [number, number], end: [number, number]) => {
    const lat1 = start[1],
      lng1 = start[0];
    const lat2 = end[1],
      lng2 = end[0];
    const y =
      Math.sin(((lng2 - lng1) * Math.PI) / 180) *
      Math.cos((lat2 * Math.PI) / 180);
    const x =
      Math.cos((lat1 * Math.PI) / 180) * Math.sin((lat2 * Math.PI) / 180) -
      Math.sin((lat1 * Math.PI) / 180) *
        Math.cos((lat2 * Math.PI) / 180) *
        Math.cos(((lng2 - lng1) * Math.PI) / 180);
    return ((Math.atan2(y, x) * 180) / Math.PI + 360) % 360;
  };

  useEffect(() => {
    // 设置代理服务器地址
    window._AMapSecurityConfig = {
      securityJsCode: "api", // 配置安全密钥
    };

    // 创建并插入地图 JSAPI 脚本
    const script = document.createElement("script");
    script.src =
      "https://webapi.amap.com/maps?v=1.4.15&key=api"; // 替换为你的 API Key
    script.async = true;

    // 将脚本插入到 body 中
    document.body.appendChild(script);

    // 初始化地图
    script.onload = initMap;

    // 清理函数,在组件卸载时移除脚本标签
    return () => {
      document.body.removeChild(script);
    };
  }, [showPolyline, isDarkMode, toolbarVisible]);

  return (
    <div className="box">
      <h2>重庆到成都的路线</h2>

      <button onClick={() => setShowPolyline(!showPolyline)}>
        {showPolyline ? "隐藏路线" : "显示路线"}
      </button>

      <button onClick={() => setIsDarkMode(!isDarkMode)}>
        {isDarkMode ? "切换到浅色模式" : "切换到深色模式"}
      </button>

      <button onClick={() => setToolbarVisible(!toolbarVisible)}>
        {toolbarVisible ? "隐藏缩放控件" : "显示缩放控件"}
      </button>

      <div
        ref={mapContainerRef}
        style={{ width: "100%", height: "500px" }}
      ></div>
    </div>
  );
};

export default MapPage;