当前位置: 首页 > news >正文

React (react-amap)高德地图使用(加标记、缩放、缩略图)

React 高德地图使用指南

本文档基于实际项目经验,详细介绍如何在 React 项目中集成和使用高德地图。
在这里插入图片描述

目录

  1. 环境准备
  2. 基础配置
  3. 地图组件实现
  4. 标记点功能
  5. 信息窗口
  6. 事件处理
  7. 样式定制
  8. 常见问题

环境准备

1. 安装依赖

npm install react-amap
# 或
yarn add react-amap

2. 获取高德地图 API Key

  1. 访问 高德开放平台
  2. 注册账号并创建应用
  3. 获取 Web 端 API Key

基础配置

1. 在 HTML 中引入高德地图 API

public/index.html 中添加:

<script src="https://webapi.amap.com/maps?v=1.4.15&key=YOUR_API_KEY"></script>

2. TypeScript 类型定义

interface MarkerData {ID: string;AS_NAME: string;AS_ADDRESS: string;AS_LONGITUDE_LATITUDE: string;AREA: string | number;ROOM_COUNT: number;IS_OPERATING?: number;[key: string]: any;
}interface FilterParams {company: string;department: string;assetType: string;
}

地图组件实现

1. 基础地图组件

import React, { useEffect, useState } from 'react';
import { Map, Marker, InfoWindow } from 'react-amap';const ReactAmapComponent: React.FC = () => {const [mapCenter, setMapCenter] = useState([116.397428, 39.90923]);const [zoom, setZoom] = useState(10);const [markers, setMarkers] = useState<MarkerData[]>([]);return (<div style={{ width: '100%', height: '600px' }}><Mapamapkey="YOUR_API_KEY"center={mapCenter}zoom={zoom}loading={<div>地图加载中...</div>}events={{created: (mapInstance) => {console.log('地图创建成功', mapInstance);},click: (e) => {console.log('地图点击', e.lnglat);}}}>{/* 标记点将在这里渲染 */}</Map></div>);
};export default ReactAmapComponent;

2. 地图配置选项

const mapOptions = {amapkey: "YOUR_API_KEY",center: [116.397428, 39.90923], // 地图中心点zoom: 10,                       // 缩放级别mapStyle: 'amap://styles/normal', // 地图样式features: ['bg', 'point', 'road', 'building'], // 地图要素viewMode: '3D',                 // 地图模式pitch: 0,                       // 地图俯仰角度rotation: 0,                    // 地图旋转角度animateEnable: true,            // 地图平移过程中是否使用动画keyboardEnable: true,           // 地图是否可通过键盘控制dragEnable: true,               // 地图是否可通过鼠标拖拽平移zoomEnable: true,               // 地图是否可缩放doubleClickZoom: true,          // 地图是否可通过双击鼠标放大地图scrollWheel: true,              // 地图是否可通过鼠标滚轮缩放浏览
};

标记点功能

1. 渲染标记点

const renderMarkers = () => {return markers.map((marker) => {const [lng, lat] = marker.AS_LONGITUDE_LATITUDE.split(',').map(Number);return (<Markerkey={marker.ID}position={[lng, lat]}title={marker.AS_NAME}events={{click: () => handleMarkerClick(marker)}}/>);});
};const handleMarkerClick = (marker: MarkerData) => {console.log('标记点击', marker);setActiveInfoWindow(marker.ID);
};

2. 自定义标记点图标

const getMarkerIcon = (isOperating: number) => {return {image: isOperating === 1 ? '/icons/marker-operating.png' : '/icons/marker-non-operating.png',size: [32, 32],imageSize: [32, 32]};
};<Markerposition={[lng, lat]}icon={getMarkerIcon(marker.IS_OPERATING)}events={{click: () => handleMarkerClick(marker)}}
/>

信息窗口

1. 基础信息窗口

const [activeInfoWindow, setActiveInfoWindow] = useState<string | null>(null);const renderInfoWindow = () => {if (!activeInfoWindow) return null;const activeMarker = markers.find(m => m.ID === activeInfoWindow);if (!activeMarker) return null;const [lng, lat] = activeMarker.AS_LONGITUDE_LATITUDE.split(',').map(Number);const position = [lng, lat];return (<InfoWindowposition={position}visible={true}closeWhenClickMap={false}showShadow={false}isCustom={true}events={{close: () => setActiveInfoWindow(null)}}><div style={{width: '380px',maxHeight: '480px',overflow: 'auto',padding: '0',margin: '0'}}><CardInfo assetData={activeMarker} onClose={() => setActiveInfoWindow(null)} /></div></InfoWindow>);
};

2. 自定义信息窗口组件

interface CardInfoProps {assetData: MarkerData;onClose?: () => void;
}const CardInfo: React.FC<CardInfoProps> = ({ assetData, onClose }) => {return (<div className="map-info-window"><div className="info-window-header"><div className="info-window-title">{assetData.AS_NAME || '资产详情'}</div><button className="info-window-close" type="button" aria-label="关闭"onClick={onClose}>×</button></div><div className="info-window-content"><div className="info-section"><div className="section-title">基础信息</div><div className="info-grid"><div className="info-row"><div className="info-label">资产编号:</div><div className="info-value">{assetData.AS_CODE || '-'}</div></div><div className="info-row"><div className="info-label">建筑面积:</div><div className="info-value">{assetData.AREA || '0'}</div></div></div></div></div><style jsx>{`.map-info-window {background: white;border-radius: 8px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);overflow: hidden;}.info-window-header {background: #f8f9fa;padding: 15px;border-bottom: 1px solid #e9ecef;position: relative;}.info-window-title {font-size: 16px;font-weight: bold;color: #333;margin: 0;}.info-window-close {position: absolute;top: 18px;right: 15px;background: none;border: none;font-size: 18px;cursor: pointer;color: #999;}`}</style></div>);
};

事件处理

1. 地图事件

const mapEvents = {created: (mapInstance: any) => {console.log('地图实例创建', mapInstance);},click: (e: any) => {console.log('地图点击', e.lnglat);setActiveInfoWindow(null); // 点击地图关闭信息窗口},zoomchange: (e: any) => {console.log('缩放级别改变', e.zoom);},moveend: (e: any) => {console.log('地图移动结束', e.target.getCenter());}
};

2. 标记点事件

const markerEvents = {click: (marker: MarkerData) => {handleMarkerClick(marker);},mouseover: (e: any) => {console.log('鼠标悬停在标记上');},mouseout: (e: any) => {console.log('鼠标离开标记');}
};

样式定制

1. 信息窗口样式

.map-info-window {background: white;border-radius: 8px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);overflow: hidden;font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}.info-window-header {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);padding: 15px;color: white;position: relative;
}.info-window-title {font-size: 16px;font-weight: bold;margin: 0;
}.info-window-close {position: absolute;top: 18px;right: 15px;background: none;border: none;font-size: 18px;cursor: pointer;color: rgba(255, 255, 255, 0.8);transition: color 0.3s;
}.info-window-close:hover {color: white;
}.info-window-content {padding: 18px 15px;max-height: 450px;overflow-y: auto;
}.info-section {margin-bottom: 20px;
}.section-title {font-size: 15px;font-weight: bold;color: #1890ff;margin-bottom: 15px;
}.info-grid {display: grid;grid-template-columns: 1fr 1fr;gap: 10px;
}.info-row {display: flex;margin-bottom: 10px;font-size: 14px;
}.info-label {color: #666;width: 85px;flex-shrink: 0;
}.info-value {color: #333;flex-grow: 1;
}

2. 地图容器样式

.map-container {width: 100%;height: 600px;border-radius: 8px;overflow: hidden;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}.map-loading {display: flex;align-items: center;justify-content: center;height: 100%;background: #f5f5f5;color: #666;
}

数据处理

1. 获取地图数据

const fetchMapData = async (filters: FilterParams) => {try {setLoading(true);const params = {tableName: 'AS_ASSET_INFO',condition: buildCondition(filters),pageSize: 1000};const response = await getDataByCondition(params);if (response.success && response.data) {const validMarkers = response.data.filter((item: any) => item.AS_LONGITUDE_LATITUDE && item.AS_LONGITUDE_LATITUDE.includes(','));setMarkers(validMarkers);updateMapCenter(validMarkers);}} catch (error) {console.error('获取地图数据失败:', error);} finally {setLoading(false);}
};

2. 构建查询条件

const buildCondition = (filters: FilterParams) => {const conditions = [];if (filters.company) {conditions.push(`COM_ID = '${filters.company}'`);}if (filters.department) {conditions.push(`AS_CORE_ORG_ID = '${filters.department}'`);}if (filters.assetType) {conditions.push(`ASSET_TYPE = '${filters.assetType}'`);}return conditions.length > 0 ? conditions.join(' AND ') : '';
};

3. 更新地图中心点

const updateMapCenter = (markers: MarkerData[]) => {if (markers.length === 0) return;const validCoords = markers.filter(marker => marker.AS_LONGITUDE_LATITUDE).map(marker => {const [lng, lat] = marker.AS_LONGITUDE_LATITUDE.split(',').map(Number);return [lng, lat];});if (validCoords.length > 0) {const avgLng = validCoords.reduce((sum, coord) => sum + coord[0], 0) / validCoords.length;const avgLat = validCoords.reduce((sum, coord) => sum + coord[1], 0) / validCoords.length;setMapCenter([avgLng, avgLat]);}
};

性能优化

1. 标记点聚合

import { MarkerClusterer } from 'react-amap';const renderClusteredMarkers = () => {return (<MarkerClusterergridSize={60}maxZoom={18}averageCenter={true}styles={[{url: '/icons/cluster-small.png',size: [40, 40],textColor: '#fff',textSize: 12},{url: '/icons/cluster-medium.png',size: [50, 50],textColor: '#fff',textSize: 14},{url: '/icons/cluster-large.png',size: [60, 60],textColor: '#fff',textSize: 16}]}>{renderMarkers()}</MarkerClusterer>);
};

2. 懒加载和虚拟化

const [visibleMarkers, setVisibleMarkers] = useState<MarkerData[]>([]);const updateVisibleMarkers = useCallback((bounds: any) => {const visible = markers.filter(marker => {const [lng, lat] = marker.AS_LONGITUDE_LATITUDE.split(',').map(Number);return bounds.contains([lng, lat]);});setVisibleMarkers(visible);
}, [markers]);const mapEvents = {moveend: (e: any) => {const bounds = e.target.getBounds();updateVisibleMarkers(bounds);},zoomend: (e: any) => {const bounds = e.target.getBounds();updateVisibleMarkers(bounds);}
};

常见问题

1. 地图不显示

问题: 地图容器为空或显示空白

解决方案:

  • 检查 API Key 是否正确
  • 确保地图容器有明确的宽高
  • 检查网络连接和控制台错误
// 确保容器有明确尺寸
<div style={{ width: '100%', height: '600px' }}><Map amapkey="YOUR_API_KEY" />
</div>

2. 标记点不显示

问题: 标记点无法在地图上显示

解决方案:

  • 检查坐标格式是否正确
  • 确保坐标在有效范围内
  • 检查标记点数据是否正确传递
// 坐标验证
const isValidCoordinate = (lngLat: string) => {const [lng, lat] = lngLat.split(',').map(Number);return !isNaN(lng) && !isNaN(lat) && lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90;
};

3. 信息窗口样式问题

问题: 信息窗口样式不生效或显示异常

解决方案:

  • 使用 isCustom={true} 启用自定义样式
  • 确保 CSS 样式正确加载
  • 检查 z-index 层级问题
<InfoWindowisCustom={true}closeWhenClickMap={false}showShadow={false}
>{/* 自定义内容 */}
</InfoWindow>

4. React 18 兼容性

问题: React 18 中出现 ReactDOM.render 警告

解决方案:

  • 避免使用 ReactDOMServer.renderToString
  • 直接在 InfoWindow 中渲染 React 组件
  • 使用最新版本的 react-amap

5. 性能优化建议

  • 使用标记点聚合减少渲染数量
  • 实现视口内标记点的懒加载
  • 避免在 render 方法中创建新对象
  • 使用 React.memo 优化组件重渲染
const MarkerComponent = React.memo(({ marker, onClick }) => {const [lng, lat] = marker.AS_LONGITUDE_LATITUDE.split(',').map(Number);return (<Markerposition={[lng, lat]}events={{ click: () => onClick(marker) }}/>);
});

总结

本文档涵盖了在 React 项目中使用高德地图的主要场景和最佳实践。通过合理的组件设计、事件处理和性能优化,可以构建出功能丰富、用户体验良好的地图应用。

在实际开发中,建议根据具体需求选择合适的功能模块,并注意处理边界情况和错误状态,确保应用的稳定性和可用性。

http://www.xdnf.cn/news/1287451.html

相关文章:

  • 荣耀手机无法连接win11电脑,错误消息:“无法在此设备上加载驱动程序 (hn_usbccgpfilter.sys)。”解决方案
  • OBOO鸥柏丨智能会议平板教学查询一体机交互式触摸终端招标投标核心标底参数要求
  • SQL Server增加对UTF-8的支持
  • Baumer高防护相机如何通过YoloV8深度学习模型实现纸箱的实时检测计数(C#代码UI界面版)
  • 谷歌ADK接入文件操作MCP
  • 力扣47:全排列Ⅱ
  • 基于Python的《红楼梦》文本分析与机器学习应用
  • 力扣 hot100 Day71
  • vivo Pulsar 万亿级消息处理实践(2)-从0到1建设 Pulsar 指标监控链路
  • [激光原理与应用-254]:理论 - 几何光学 - 自动对焦的原理
  • 数据结构:中缀到后缀的转换(Infix to Postfix Conversion)
  • Flutter GridView的基本使用
  • Java 工厂方法模式
  • 【项目设计】高并发内存池
  • 北京-4年功能测试2年空窗-报培训班学测开-第七十四天-线下面试-聊的很满意但可能有风险-等信吧
  • cuda排序算法--双调排序(Bitonic_Sort)
  • web前端第二次作业
  • 开发避坑指南(23):Tomcat高版本URL特殊字符限制问题解决方案(RFC 7230 RFC 3986)
  • TF-IDF:信息检索与文本挖掘的统计权重基石
  • 多奥电梯智能化解决方案的深度解读与结构化总结,内容涵盖系统架构、功能模块、应用场景与社会价值四大维度,力求全面展示该方案的技术先进性与应用前景。
  • Agent智能体基础
  • vue3大事件
  • Linux随记(二十二)
  • 本地(macOS)和服务器时间不同步导致的 Bug排查及解决
  • 从裸机到云原生:Linux 操作系统实战进阶的“四维跃迁”
  • 【Linux】程序地址空间
  • CTO如何通过录音转写和音频降噪,提升企业远程协作效率?
  • 定制客车系统线上购票系统功能设计
  • springboot+JPA
  • 机械臂的智能升维:当传统机械臂遇见Deepoc具身智能大模型从自动化工具到具身智能体的范式革命