目录
- 引言
- 功能设计与开发步骤
- 第一步:初始化项目与地图
- 第二步:动态切换城市地图
- 第三步:标记加油站位置
- 第四步:获取用户位置并计算最近加油站
- 第五步:城市名称解析
- 完整代码
- 总结
引言
在上一篇《加油站小程序实战05:地图加载》中,我们讲解了如何在小程序中加载地图,并实现了基础的地图显示功能。本篇,我们将进一步完善这一功能。通过一个城市切换地图的示例,展示如何根据用户选择或定位,实现动态加载城市地图,并标记城市中的加油站信息。这篇文章将详细拆解代码实现,让你理解每一步的开发思路和过程。
功能设计与开发步骤
在本次示例中,我们的目标是实现以下功能:
- 城市选择器:允许用户从列表中选择城市。
- 地图初始化:基于用户选择或定位加载对应城市的地图。
- 标记加油站:在地图上动态标记城市内的加油站位置。
- 定位与最近加油站:通过用户的定位,计算并高亮显示最近的加油站。
第一步:初始化项目与地图
在代码中,我们首先需要确保地图组件加载正确。为了使用高德地图的 API,我们需要引入相应的 JS 文件,并在页面初始化时完成地图的加载:
useEffect(() => {const loadAMap = async () => {if (!window.AMap) {const script = document.createElement("script");script.src = "https://webapi.amap.com/maps?v=2.0&key=你的key";script.async = true;script.onload = () => {initMap();};document.body.appendChild(script);} else {initMap();}};const initMap = () => {if (window.AMap) {const map = new window.AMap.Map(mapContainerRef.current, {center: defaultCityLocation, // 默认中心位置zoom: 12,mapStyle: "amap://styles/normal", // 地图样式});setMapInstance(map);}};loadAMap();
}, []);
代码解析:
- 高德地图 SDK 加载:通过动态插入 script 标签引入高德地图 API。
- 地图初始化:默认加载北京地图作为示例。
第二步:动态切换城市地图
为了实现城市切换功能,我们使用了一个下拉选择框(select),并通过状态管理来更新地图中心点和标记。
useEffect(() => {if (mapInstance) {const selectedCityData = cities.find((city) => city.name === selectedCity);if (selectedCityData) {mapInstance.setCenter(selectedCityData.location);addMarkers(selectedCityData.stations);calculateNearestStation(selectedCityData.stations);}}
}, [selectedCity, mapInstance]);
代码解析:
- selectedCity 状态监听:当用户选择的城市发生变化时,触发地图中心点的更新。
- 标记加油站:通过 addMarkers 函数将城市加油站位置标记在地图上。
第三步:标记加油站位置
为每个城市的加油站添加标记,并在标记上显示信息窗口,展示距离用户的距离:
const addMarkers = (stations) => {mapInstance.clearMap(); // 清空现有标记stations.forEach((station) => {const marker = new window.AMap.Marker({position: station.location,map: mapInstance,});const distance = calculateDistance(userLocation, station.location);const infoWindow = new window.AMap.InfoWindow({content: `<div style="padding: 10px; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"><div><strong>${station.name}</strong></div><div style="color: #666;">距当前站点 ${distance.toFixed(1)} km</div></div>`,offset: new window.AMap.Pixel(0, -40),});marker.on("click", () => {infoWindow.open(mapInstance, marker.getPosition());});if (nearestStation && nearestStation.name === station.name) {infoWindow.open(mapInstance, marker.getPosition());}});
};
代码解析:
- 清空标记:切换城市时,移除旧标记。
- 添加标记:为每个加油站生成一个 Marker。
- 信息窗口:显示加油站名称和与用户的距离。
第四步:获取用户位置并计算最近加油站
为了提升用户体验,我们通过浏览器定位功能获取用户的当前位置,并基于当前位置计算最近的加油站:
const getUserLocation = () => {if (navigator.geolocation) {navigator.geolocation.getCurrentPosition((position) => {const { latitude, longitude } = position.coords;setUserLocation([longitude, latitude]);fetchCityNameFromCoords(longitude, latitude);},() => {setUserLocation([111.75199, 40.84211]); // 默认呼和浩特setSelectedCity("呼和浩特市");});}
};
const calculateNearestStation = (stations) => {let nearest = null;let minDistance = Infinity;stations.forEach((station) => {const distance = calculateDistance(userLocation, station.location);if (distance < minDistance) {minDistance = distance;nearest = station;}});setNearestStation(nearest);
};
代码解析:
- 浏览器定位:调用 navigator.geolocation 获取用户位置。
- 最近加油站计算:逐一比较加油站距离,找到最近的标记。
第五步:城市名称解析
当获取到用户位置后,我们调用高德地图的逆地理编码 API,解析用户所在的城市并自动切换城市地图:
const fetchCityNameFromCoords = async (lng, lat) => {try {const response = await fetch(`https://restapi.amap.com/v3/geocode/regeo?key=你的key&location=${lng},${lat}`);const data = await response.json();if (data.status === "1" && data.regeocode) {const cityName = data.regeocode.addressComponent.city || data.regeocode.addressComponent.province;setSelectedCity(cityName);}} catch {console.error("获取城市名称失败");}
};
完整代码
import React, { useRef, useEffect, useState } from "react";export default function CitySwitcherMap(props) {const { style } = props;const mapContainerRef = useRef(null);const [selectedCity, setSelectedCity] = useState("");const [defaultCityLocation, setDefaultCityLocation] = useState([116.397428, 39.90923]); // 默认北京const [mapInstance, setMapInstance] = useState(null);const [userLocation, setUserLocation] = useState([116.397428, 39.90923]); // 默认用户位置为北京const [nearestStation, setNearestStation] = useState(null);const cities = [{ name: "北京", location: [116.397428, 39.90923], stations: [{ name: "天安门", location: [116.397428, 39.90923] }, { name: "鸟巢", location: [116.397524, 39.992424] }] },{ name: "上海", location: [121.473701, 31.230416], stations: [{ name: "外滩", location: [121.490317, 31.241638] }, { name: "陆家嘴", location: [121.502771, 31.238068] }] },{ name: "广州", location: [113.264385, 23.129112], stations: [{ name: "广州塔", location: [113.330863, 23.113455] }, { name: "白云山", location: [113.28848, 23.168778] }] },{ name: "深圳市", location: [114.057868, 22.543099], stations: [{ name: "世界之窗", location: [113.976373, 22.53332] }, { name: "欢乐谷", location: [113.998048, 22.546054] }] },{ name: "成都", location: [104.066541, 30.572269], stations: [{ name: "宽窄巷子", location: [104.062837, 30.667493] }, { name: "大熊猫基地", location: [104.138817, 30.735778] }] },{ name: "呼和浩特市", location: [111.75199, 40.84211], stations: [{ name: "大召寺", location: [111.692018, 40.812225] },{ name: "昭君墓", location: [111.930514, 40.718719] },{ name: "呼和浩特火车站", location: [111.75199, 40.841939] },{ name: "五塔寺", location: [111.695302, 40.809052] },{ name: "敕勒川草原", location: [111.81666, 40.88189] },{ name: "内蒙古博物院", location: [111.704164, 40.818445] },] },];useEffect(() => {const loadAMap = async () => {if (!window.AMap) {const script = document.createElement("script");script.src = "https://webapi.amap.com/maps?v=2.0&key=你的key";script.async = true;script.onload = () => {initMap();};document.body.appendChild(script);} else {initMap();}};const initMap = () => {if (window.AMap) {const map = new window.AMap.Map(mapContainerRef.current, {center: defaultCityLocation,zoom: 12,mapStyle: "amap://styles/normal",});setMapInstance(map);}};loadAMap();}, []);useEffect(() => {if (mapInstance) {const selectedCityData = cities.find((city) => city.name === selectedCity);if (selectedCityData) {mapInstance.setCenter(selectedCityData.location);addMarkers(selectedCityData.stations);calculateNearestStation(selectedCityData.stations);}}}, [selectedCity, mapInstance]);const addMarkers = (stations) => {mapInstance.clearMap();stations.forEach((station) => {const marker = new window.AMap.Marker({position: station.location,map: mapInstance,});const distance = calculateDistance(userLocation, station.location);const infoWindow = new window.AMap.InfoWindow({content: `<div style="padding: 10px; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"><div><strong>${station.name}</strong></div><div style="color: #666;">距当前站点 ${distance.toFixed(1)} km</div></div>`,offset: new window.AMap.Pixel(0, -40), // 调整偏移,避免遮挡标记
});marker.on("click", () => {infoWindow.open(mapInstance, marker.getPosition());});if (nearestStation && nearestStation.name === station.name) {infoWindow.open(mapInstance, marker.getPosition());}});};const calculateNearestStation = (stations) => {let nearest = null;let minDistance = Infinity;stations.forEach((station) => {const distance = calculateDistance(userLocation, station.location);if (distance < minDistance) {minDistance = distance;nearest = station;}});setNearestStation(nearest);};const calculateDistance = (start, end) => {const startLngLat = new window.AMap.LngLat(start[0], start[1]);const endLngLat = new window.AMap.LngLat(end[0], end[1]);return startLngLat.distance(endLngLat) / 1000; // 返回公里数};const getUserLocation = () => {if (navigator.geolocation) {navigator.geolocation.getCurrentPosition((position) => {const { latitude, longitude } = position.coords;setUserLocation([longitude, latitude]);fetchCityNameFromCoords(longitude, latitude);},() => {setUserLocation([111.75199, 40.84211]); // 呼和浩特setSelectedCity("呼和浩特市");});}};const fetchCityNameFromCoords = async (lng, lat) => {try {const response = await fetch(`https://restapi.amap.com/v3/geocode/regeo?key=你的key&location=${lng},${lat}`);const data = await response.json();if (data.status === "1" && data.regeocode) {const cityName = data.regeocode.addressComponent.city || data.regeocode.addressComponent.province;setSelectedCity(cityName);}} catch {console.error("获取城市名称失败");}};useEffect(() => {getUserLocation();}, []);return (<div><div style={{ marginBottom: "10px", zIndex: 999 }}><selectvalue={selectedCity}onChange={(e) => setSelectedCity(e.target.value)}style={{padding: "8px",fontSize: "14px",borderRadius: "4px",border: "1px solid #ccc",}}><option value="">请选择城市</option>{cities.map((city) => (<option key={city.name} value={city.name}>{city.name}</option>))}</select></div><divref={mapContainerRef}style={{position: "relative",width: "100%",height: "500px",marginTop: "0px",...style,}}/></div>);
}
注意事项是,地图加载和逆地址解析需要不同的key
在浏览器中可以看到多站点信息,点击某个站点的时候弹出窗口显示站点名称和距离信息
总结
本篇文章详细介绍了如何基于高德地图实现城市切换与加油站标记功能,主要涵盖了地图加载、城市切换、加油站标记以及定位等功能模块。这不仅是地图功能的进一步完善,也为后续的加油站小程序开发奠定了坚实的基础。
下一篇文章将继续完善小程序功能,探索如何实现动态数据加载与交互优化。希望这篇文章能为你提供开发思路与实践参考!