第15篇:地图导航与路线规划
📚 本篇导读
在智慧农业管理中,农户经常需要从当前位置前往农田、查找周边的农资店、农机租赁点等服务设施。本篇教程将带你实现完整的地图导航和POI(兴趣点)搜索功能,让应用更加实用。
本篇将实现:
- 🚗 驾车路线规划(从当前位置到目标地块)
- 🗺️ 路线可视化展示(在地图上绘制导航路线)
- 📍 POI周边搜索(农资店、农机租赁、加油站等)
- 🎯 搜索结果展示(列表+地图标记)
- 🌤️ 天气查询功能(基于当前位置)
🎯 学习目标
完成本篇教程后,你将掌握:
📐 功能架构
导航与搜索功能架构
地图导航与搜索模块
├── 路线规划
│ ├── RouteSearch(路线搜索服务)
│ ├── 驾车路线计算
│ ├── 路线结果处理
│ └── 路线可视化(Polyline绘制)
│
├── POI搜索
│ ├── PoiSearch(POI搜索服务)
│ ├── 周边圆形搜索
│ ├── 城市关键词搜索
│ └── 搜索结果标记
│
├── 天气查询
│ ├── WeatherSearch(天气搜索服务)
│ ├── 实时天气查询
│ └── 天气信息展示
│
└── 交互功能
├── 点击地块触发导航
├── 搜索半径调整
└── 搜索结果点击定位
一、高德导航SDK集成
1.1 依赖配置
在项目的 oh-package.json5 中,我们已经添加了导航和搜索SDK:
{
"dependencies": {
"@amap/amap_lbs_map3d": "^1.0.0", // 地图SDK
"@amap/amap_lbs_search": "^1.0.0", // 搜索SDK(包含路线规划)
"@amap/amap_lbs_navi": "^1.0.0" // 导航SDK
}
}
说明:
- @amap/amap_lbs_search:提供路线规划、POI搜索、天气查询等功能
- @amap/amap_lbs_navi:提供实时导航功能(本篇重点是路线规划)
1.2 导入必要的模块
在 FieldMapPage.ets 中导入搜索相关的类:
// 搜索SDK导入
import {
RouteSearch, // 路线搜索
DriveRouteQuery, // 驾车路线查询
FromAndTo, // 起点终点
LatLonPoint, // 经纬度点
OnRouteSearchListener, // 路线搜索监听器
DriveRouteResult, // 驾车路线结果
AMapException, // 异常码
PoiSearch, // POI搜索
PoiQuery, // POI查询
OnPoiSearchListener, // POI搜索监听器
PoiResult, // POI结果
PoiItem, // POI项
PoiSearchBound, // POI搜索范围
WeatherSearch, // 天气搜索
WeatherSearchQuery, // 天气查询
LocalWeatherLive, // 实时天气
LocalWeatherLiveResult,// 天气结果
OnWeatherSearchListener // 天气监听器
} from '@amap/amap_lbs_search';
二、路线规划功能实现
2.1 初始化路线搜索服务
在页面的 aboutToAppear() 生命周期中初始化搜索服务:
@Component
export struct FieldMapPage {
// 路线搜索相关
private routeSearch: RouteSearch | null = null;
private routePolyline: Polyline | null = null;
@State showRouteDialog: boolean = false;
@State routeDistance: string = '';
@State routeTime: string = '';
@State isRouteSearching: boolean = false;
aboutToAppear(): void {
// 初始化路线搜索服务
try {
const context = getContext(this) as Context;
// 创建RouteSearch实例
this.routeSearch = new RouteSearch(context);
console.info('[FieldMapPage] RouteSearch初始化成功');
} catch (error) {
console.error('[FieldMapPage] 搜索服务初始化失败:', error);
promptAction.showToast({
message: '路线规划功能初始化失败',
duration: 2000
});
}
}
}
关键点:
- RouteSearch 需要传入 Context 对象
- 初始化失败时要给用户友好的提示
2.2 实现路线规划方法
创建一个方法来计算从当前位置到目标地块的路线:
/**
* 规划到指定地块的路线
* @param fieldId 地块ID
*/
private planRouteToField(fieldId: string): void {
// 1. 检查是否有当前位置
if (!this.currentLocationInfo ||
!this.currentLocationInfo.latitude ||
!this.currentLocationInfo.longitude) {
promptAction.showToast({
message: '无法获取当前位置,请确保已开启定位权限',
duration: 2000
});
return;
}
// 2. 查找目标地块
const field = this.fields.find(f => f.id === fieldId);
if (!field || !field.latitude || !field.longitude) {
promptAction.showToast({
message: '地块位置信息不完整',
duration: 2000
});
return;
}
// 3. 获取起点和终点坐标
const startLat = this.currentLocationInfo.latitude;
const startLon = this.currentLocationInfo.longitude;
const endLat = field.latitude;
const endLon = field.longitude;
console.info(`[FieldMapPage] 规划路线: (${startLat},${startLon}) -> (${endLat},${endLon})`);
// 4. 调用路线搜索
this.searchDriveRoute(startLat, startLon, endLat, endLon);
}
/**
* 搜索驾车路线
*/
private searchDriveRoute(
startLat: number,
startLon: number,
endLat: number,
endLon: number
): void {
if (!this.routeSearch || !globalAMap) {
promptAction.showToast({
message: '路线搜索服务不可用',
duration: 2000
});
return;
}
try {
console.info('[FieldMapPage] 开始路线搜索…');
this.isRouteSearching = true;
// 1. 创建起点和终点
const startPoint = new LatLonPoint(startLat, startLon);
const endPoint = new LatLonPoint(endLat, endLon);
const fromAndTo = new FromAndTo(startPoint, endPoint);
// 2. 创建驾车路线查询
// mode参数: 0=速度优先, 1=费用优先, 2=距离优先, 3=不走高速
const passPoints = new ArrayList<LatLonPoint>(); // 途经点(空)
const avoidPolygons = new ArrayList<ArrayList<LatLonPoint>>(); // 避让区域(空)
const query = new DriveRouteQuery(
fromAndTo, // 起点终点
0, // 模式:速度优先
passPoints, // 途经点
avoidPolygons, // 避让区域
'' // 避让道路
);
// 3. 设置监听器
const listener: OnRouteSearchListener = {
// 驾车路线搜索回调
onDriveRouteSearched: (result: DriveRouteResult | undefined, errorCode: number): void => {
this.isRouteSearching = false;
this.handleRouteResult(result, errorCode);
},
// 其他路线类型(不使用)
onWalkRouteSearched: (): void => {},
onRideRouteSearched: (): void => {},
onBusRouteSearched: (): void => {}
};
// 4. 设置监听器并开始搜索
this.routeSearch.setRouteSearchListener(listener);
// 延时执行,确保监听器设置完成
setTimeout(() => {
if (this.routeSearch) {
this.routeSearch.calculateDriveRouteAsyn(query);
console.info('[FieldMapPage] 路线计算已启动');
}
}, 100);
promptAction.showToast({
message: '正在规划路线…',
duration: 1500
});
} catch (error) {
this.isRouteSearching = false;
console.error('[FieldMapPage] 路线搜索失败:', error);
promptAction.showToast({
message: '路线规划失败',
duration: 2000
});
}
}
代码说明:
| 1. 参数验证 | 检查当前位置和目标地块的坐标是否有效 |
| 2. 创建查询对象 | 使用 DriveRouteQuery 创建驾车路线查询 |
| 3. 设置监听器 | 通过 OnRouteSearchListener 接收搜索结果 |
| 4. 异步搜索 | 调用 calculateDriveRouteAsyn() 开始搜索 |
DriveRouteQuery参数说明:
- mode=0:速度优先(推荐用于农业场景)
- mode=1:费用优先(避开收费路段)
- mode=2:距离优先(最短路径)
- mode=3:不走高速(适合农村道路)
2.3 处理路线搜索结果
/**
* 处理路线搜索结果
*/
private handleRouteResult(result: DriveRouteResult | undefined, errorCode: number): void {
console.info('[FieldMapPage] 路线搜索回调, errorCode:', errorCode);
if (!globalAMap) {
console.error('[FieldMapPage] 地图对象为空');
return;
}
// 检查是否成功
if (errorCode === AMapException.CODE_AMAP_SUCCESS && result) {
console.info('[FieldMapPage] ✅ 路线搜索成功!');
// 获取路线列表(通常返回多条路线,取第一条)
const paths = result.getPaths();
if (paths && paths.length > 0) {
const path = paths[0]; // 取第一条路线
// 获取路线信息
const distance = path.getDistance(); // 距离(米)
const duration = path.getDuration(); // 时长(秒)
console.info(`[FieldMapPage] 路线信息 – 距离:${distance}米, 时长:${duration}秒`);
// 格式化距离显示
this.routeDistance = distance > 1000 ?
`${(distance / 1000).toFixed(2)}公里` :
`${distance.toFixed(0)}米`;
// 格式化时间显示
this.routeTime = duration > 3600 ?
`${Math.floor(duration / 3600)}小时${Math.floor((duration % 3600) / 60)}分钟` :
`${Math.floor(duration / 60)}分钟`;
// 绘制路线
const steps = path.getSteps(); // 获取路线分段
this.drawRoute(steps);
// 显示路线信息对话框
this.showRouteDialog = true;
promptAction.showToast({
message: `路线规划成功: ${this.routeDistance},用时约${this.routeTime}`,
duration: 3000
});
} else {
console.warn('[FieldMapPage] 未找到路线');
promptAction.showToast({
message: '未找到合适的路线',
duration: 2000
});
}
} else {
// 搜索失败
console.error('[FieldMapPage] 路线搜索失败, errorCode:', errorCode);
let errorMessage = '路线搜索失败';
switch (errorCode) {
case 1000: errorMessage = '请求超时,请重试'; break;
case 1001: errorMessage = '网络错误,请检查网络连接'; break;
case 1002: errorMessage = '无效的参数'; break;
case 2000: errorMessage = '无效的用户Key'; break;
case 3000: errorMessage = '服务请求超出限制'; break;
default: errorMessage = `路线搜索失败 (错误码: ${errorCode})`;
}
promptAction.showToast({
message: errorMessage,
duration: 3000
});
}
}
关键点:
- result.getPaths() 返回多条路线方案,通常取第一条
- path.getDistance() 返回距离(单位:米)
- path.getDuration() 返回预计时长(单位:秒)
- path.getSteps() 返回路线分段信息,用于绘制路线
2.4 在地图上绘制路线
/**
* 绘制路线到地图上
* @param steps 路线分段信息
*/
private drawRoute(steps: ESObject): void {
console.info('[FieldMapPage] 开始绘制路线…');
if (!globalAMap) {
console.error('[FieldMapPage] 地图对象为空,无法绘制路线');
return;
}
if (!steps) {
console.error('[FieldMapPage] 路线分段信息为空');
return;
}
// 1. 清除旧路线
if (this.routePolyline) {
console.info('[FieldMapPage] 清除旧路线');
this.routePolyline.remove();
this.routePolyline = null;
}
try {
// 2. 收集所有路线点
const polylineOptions: PolylineOptions = new PolylineOptions();
let pointCount = 0;
// 遍历所有分段
const stepsArray: ESObject = steps;
const length: number = stepsArray.length as number;
console.info(`[FieldMapPage] 处理 ${length} 个路线分段`);
for (let i = 0; i < length; i++) {
const step: ESObject = stepsArray[i] as ESObject;
const polyline: ESObject = step.getPolyline?.() as ESObject;
if (polyline) {
const polylineLength: number = polyline.length as number;
// 遍历分段中的所有点
for (let j = 0; j < polylineLength; j++) {
const point: ESObject = polyline[j] as ESObject;
const lat: number = point.getLatitude?.() as number;
const lon: number = point.getLongitude?.() as number;
if (lat && lon) {
polylineOptions.add(new LatLng(lat, lon));
pointCount++;
}
}
}
}
console.info(`[FieldMapPage] 收集到 ${pointCount} 个路线点`);
// 3. 绘制路线
if (pointCount > 0) {
polylineOptions.setWidth(12); // 线条宽度
polylineOptions.setColor(0xFF2196F3); // 蓝色
polylineOptions.setZIndex(50); // 层级(确保在地块标记之上)
const polyline = globalAMap.addPolyline(polylineOptions);
this.routePolyline = polyline ? polyline : null;
if (this.routePolyline) {
console.info('[FieldMapPage] ✅ 路线绘制成功');
} else {
console.error('[FieldMapPage] ❌ 添加路线到地图失败');
}
} else {
console.warn('[FieldMapPage] 没有有效的路线点');
promptAction.showToast({
message: '路线数据无效',
duration: 2000
});
}
} catch (error) {
console.error('[FieldMapPage] 绘制路线失败:', error);
promptAction.showToast({
message: '绘制路线失败',
duration: 2000
});
}
}
/**
* 清除路线
*/
private clearRoute(): void {
if (this.routePolyline) {
this.routePolyline.remove();
this.routePolyline = null;
}
this.showRouteDialog = false;
this.routeDistance = '';
this.routeTime = '';
}
绘制路线的关键步骤:
| 1. 清除旧路线 | 避免多条路线重叠显示 |
| 2. 遍历分段 | 路线由多个分段(step)组成 |
| 3. 收集坐标点 | 每个分段包含多个坐标点 |
| 4. 创建Polyline | 使用 PolylineOptions 配置样式 |
| 5. 添加到地图 | 调用 addPolyline() 显示路线 |
Polyline样式配置:
- setWidth(12):线条宽度(像素)
- setColor(0xFF2196F3):颜色(ARGB格式,蓝色)
- setZIndex(50):层级,数值越大越在上层
三、POI搜索功能实现
3.1 初始化POI搜索服务
@Component
export struct FieldMapPage {
// POI搜索相关
private poiSearch: PoiSearch | null = null;
private poiMarkers: Marker[] = [];
private searchCircle: MapCircle | null = null;
@State showPoiSearch: boolean = false;
@State poiKeyword: string = '农资店';
@State poiResults: PoiItem[] = [];
@State searchRadius: number = 5000; // 搜索半径(米)
aboutToAppear(): void {
try {
const context = getContext(this) as Context;
// 初始化POI搜索(需要context和undefined参数)
this.poiSearch = new PoiSearch(context, undefined);
console.info('[FieldMapPage] PoiSearch初始化成功');
} catch (error) {
console.error('[FieldMapPage] POI搜索初始化失败:', error);
}
}
}
3.2 实现周边POI搜索
/**
* 搜索周边POI
* @param keyword 搜索关键词(如:农资店、加油站)
*/
private searchNearbyPoi(keyword: string): void {
// 1. 检查当前位置
if (!this.poiSearch || !this.currentLocationInfo ||
!this.currentLocationInfo.latitude || !this.currentLocationInfo.longitude) {
promptAction.showToast({
message: '无法获取当前位置',
duration: 2000
});
return;
}
try {
// 2. 创建中心点
const centerPoint = new LatLonPoint(
this.currentLocationInfo.latitude,
this.currentLocationInfo.longitude
);
// 3. 创建圆形搜索区域
const searchBound = PoiSearchBound.createCircleSearchBound(
centerPoint, // 中心点
this.searchRadius // 半径(米)
);
// 4. 创建POI查询
const query = new PoiQuery(
keyword, // 关键词
'', // 类型(空表示所有类型)
'' // 城市(空表示全国)
);
query.setPageSize(20); // 每页结果数
query.setPageNum(0); // 页码(从0开始)
query.setExtensions('all'); // 获取完整信息
console.info(`[FieldMapPage] POI搜索: ${keyword}, 半径: ${this.searchRadius}米`);
// 5. 设置监听器
const listener: OnPoiSearchListener = {
onPoiSearched: (result: PoiResult | undefined, errorCode: number): void => {
this.handlePoiResult(result, errorCode);
},
onPoiItemSearched: () => {}
};
// 6. 执行搜索
this.poiSearch.setOnPoiSearchListener(listener);
this.poiSearch.setBound(searchBound);
this.poiSearch.setQuery(query);
this.poiSearch.searchPOIAsyn();
promptAction.showToast({
message: `正在搜索${keyword}…`,
duration: 1500
});
} catch (error) {
console.error('[FieldMapPage] POI搜索失败:', error);
promptAction.showToast({
message: 'POI搜索失败',
duration: 2000
});
}
}
POI搜索参数说明:
| keyword | 搜索关键词 | ‘农资店’、‘加油站’、‘农机租赁’ |
| category | POI类型码 | 留空表示所有类型 |
| city | 城市名称 | 留空表示全国范围 |
| searchRadius | 搜索半径 | 5000(5公里) |
| pageSize | 每页结果数 | 20(最大50) |
3.3 处理POI搜索结果
/**
* 处理POI搜索结果
*/
private handlePoiResult(result: PoiResult | undefined, errorCode: number): void {
console.info(`[FieldMapPage] POI搜索回调, errorCode: ${errorCode}`);
if (!globalAMap) {
console.error('[FieldMapPage] 地图对象为空');
return;
}
// 清除旧结果
this.clearPoiResults();
if (errorCode === AMapException.CODE_AMAP_SUCCESS && result) {
console.info('[FieldMapPage] ✅ POI搜索成功!');
const pois = result.getPois();
console.info(`[FieldMapPage] 找到 ${pois ? pois.length : 0} 个POI`);
if (pois && pois.length > 0) {
// 1. 将ArrayList转换为数组
this.poiResults = [];
for (let i = 0; i < pois.length; i++) {
this.poiResults.push(pois[i]);
}
// 2. 在地图上添加POI标记
for (const poi of this.poiResults) {
const latLon = poi.getLatLonPoint();
if (latLon) {
const markerOptions: MarkerOptions = new MarkerOptions();
markerOptions.setPosition(
new LatLng(latLon.getLatitude(), latLon.getLongitude())
);
markerOptions.setTitle(poi.getTitle());
markerOptions.setSnippet(poi.getSnippet());
markerOptions.setZIndex(15); // 层级高于地块标记
markerOptions.setAnchor(0.5, 1);
// 使用紫红色标记,区别于地块标记
markerOptions.setIcon(
BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)
);
const marker = globalAMap.addMarker(markerOptions);
if (marker) {
this.poiMarkers.push(marker);
}
}
}
// 3. 绘制搜索范围圆圈
if (this.currentLocationInfo?.latitude && this.currentLocationInfo?.longitude) {
const circleOptions: CircleOptions = new CircleOptions();
circleOptions.setCenter(
new LatLng(
this.currentLocationInfo.latitude,
this.currentLocationInfo.longitude
)
);
circleOptions.setRadius(this.searchRadius);
circleOptions.setStrokeColor(0x88FF5722); // 半透明橙色边框
circleOptions.setStrokeWidth(2);
circleOptions.setFillColor(0x22FF5722); // 半透明橙色填充
this.searchCircle = globalAMap.addCircle(circleOptions);
}
promptAction.showToast({
message: `找到 ${this.poiResults.length} 个结果`,
duration: 2000
});
} else {
promptAction.showToast({
message: '未找到相关POI',
duration: 2000
});
}
} else {
console.error('[FieldMapPage] POI搜索失败, errorCode:', errorCode);
promptAction.showToast({
message: 'POI搜索失败',
duration: 2000
});
}
}
/**
* 清除POI搜索结果
*/
private clearPoiResults(): void {
// 清除POI标记
for (const marker of this.poiMarkers) {
marker.remove();
}
this.poiMarkers = [];
// 清除搜索范围圆圈
if (this.searchCircle) {
this.searchCircle.remove();
this.searchCircle = null;
}
// 清除结果列表
this.poiResults = [];
}
POI结果处理要点:
| 1. 清除旧结果 | 避免多次搜索结果叠加 |
| 2. 转换数据 | 将ArrayList转为TypeScript数组 |
| 3. 添加标记 | 使用紫红色标记区分POI和地块 |
| 4. 绘制范围 | 用圆圈显示搜索范围 |
3.4 POI搜索UI实现
在地图页面添加POI搜索面板:
build() {
Stack({ alignContent: Alignment.TopStart }) {
// 地图组件
MapViewComponent({ mapOptions: this.mapOptions })
.width('100%')
.height('100%')
// POI搜索面板
if (this.showPoiSearch) {
Column() {
// 搜索关键词选择
Row() {
Text('搜索类型:')
.fontSize(14)
.fontColor('#333333')
// 快捷关键词按钮
Button('农资店')
.fontSize(12)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor(this.poiKeyword === '农资店' ? '#4CAF50' : '#E0E0E0')
.fontColor(this.poiKeyword === '农资店' ? '#FFFFFF' : '#666666')
.onClick(() => {
this.poiKeyword = '农资店';
this.searchNearbyPoi(this.poiKeyword);
})
Button('农机租赁')
.fontSize(12)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor(this.poiKeyword === '农机租赁' ? '#4CAF50' : '#E0E0E0')
.fontColor(this.poiKeyword === '农机租赁' ? '#FFFFFF' : '#666666')
.onClick(() => {
this.poiKeyword = '农机租赁';
this.searchNearbyPoi(this.poiKeyword);
})
Button('加油站')
.fontSize(12)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor(this.poiKeyword === '加油站' ? '#4CAF50' : '#E0E0E0')
.fontColor(this.poiKeyword === '加油站' ? '#FFFFFF' : '#666666')
.onClick(() => {
this.poiKeyword = '加油站';
this.searchNearbyPoi(this.poiKeyword);
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(12)
// 搜索半径调整
Row() {
Text('搜索半径:')
.fontSize(14)
.fontColor('#333333')
Slider({
value: this.searchRadius,
min: 1000,
max: 50000,
step: 1000
})
.width(150)
.onChange((value: number) => {
this.searchRadius = value;
})
Text(`${(this.searchRadius / 1000).toFixed(1)}公里`)
.fontSize(12)
.fontColor('#666666')
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({ left: 12, right: 12, bottom: 12 })
// 搜索结果列表
if (this.poiResults.length > 0) {
List() {
ForEach(this.poiResults, (poi: PoiItem, index: number) => {
ListItem() {
Row() {
Column() {
Text(poi.getTitle())
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
Text(poi.getSnippet())
.fontSize(12)
.fontColor('#999999')
.margin({ top: 4 })
// 距离信息
if (poi.getDistance()) {
Text(`距离: ${(poi.getDistance() / 1000).toFixed(2)}公里`)
.fontSize(11)
.fontColor('#4CAF50')
.margin({ top: 4 })
}
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
// 导航按钮
Button('导航')
.fontSize(12)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor('#2196F3')
.onClick(() => {
const latLon = poi.getLatLonPoint();
if (latLon && this.currentLocationInfo) {
this.searchDriveRoute(
this.currentLocationInfo.latitude!,
this.currentLocationInfo.longitude!,
latLon.getLatitude(),
latLon.getLongitude()
);
}
})
}
.width('100%')
.padding(12)
.backgroundColor('#FFFFFF')
}
}, (poi: PoiItem, index: number) => index.toString())
}
.width('100%')
.maxHeight(300)
.backgroundColor('#F5F5F5')
}
// 关闭按钮
Button('关闭')
.width('100%')
.fontSize(14)
.backgroundColor('#FF5722')
.onClick(() => {
this.showPoiSearch = false;
this.clearPoiResults();
})
.margin({ top: 8 })
}
.width('90%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 8, color: '#33000000' })
.padding(16)
.margin({ top: 80, left: '5%' })
}
}
.width('100%')
.height('100%')
}
UI设计要点:
- 提供快捷关键词按钮(农资店、农机租赁、加油站)
- 可调节搜索半径(1-50公里)
- 列表展示搜索结果,包含距离信息
- 每个结果提供导航按钮,可直接规划路线
四、天气查询功能
4.1 实现天气查询
/**
* 查询当前城市天气
* @param cityName 城市名称
*/
private searchWeather(cityName: string): void {
if (!cityName) {
console.warn('[FieldMapPage] 城市名称为空,无法查询天气');
return;
}
try {
const context = getContext(this) as Context;
// 创建天气搜索监听器
const weatherSearchListener: OnWeatherSearchListener = {
onWeatherLiveSearched: (
weatherLiveResult: LocalWeatherLiveResult | undefined,
errorCode: number
): void => {
if (errorCode === AMapException.CODE_AMAP_SUCCESS) {
if (weatherLiveResult && weatherLiveResult.getLiveResult()) {
const liveWeather: LocalWeatherLive = weatherLiveResult.getLiveResult();
// 更新天气信息
this.currentWeather = liveWeather.getWeather() || '';
this.currentTemperature = liveWeather.getTemperature() || '';
this.windDirection = liveWeather.getWindDirection() || '';
this.windPower = liveWeather.getWindPower() || '';
this.humidity = liveWeather.getHumidity() || '';
this.reportTime = liveWeather.getReportTime() || '';
console.info(`[FieldMapPage] ✅ 天气查询成功: ${this.currentWeather} ${this.currentTemperature}°C`);
}
} else {
console.error(`[FieldMapPage] ❌ 天气查询失败,错误码: ${errorCode}`);
}
},
onWeatherForecastSearched: (): void => {
// 不处理预报天气
}
};
// 创建天气查询
const query = new WeatherSearchQuery(
cityName,
WeatherSearchQuery.WEATHER_TYPE_LIVE // 实时天气
);
const weatherSearch = new WeatherSearch(context);
weatherSearch.setOnWeatherSearchListener(weatherSearchListener);
weatherSearch.setQuery(query);
weatherSearch.searchWeatherAsyn();
console.info(`[FieldMapPage] 开始查询天气: ${cityName}`);
} catch (error) {
console.error('[FieldMapPage] 天气查询异常:', error);
}
}
/**
* 根据天气描述返回对应图标
*/
private getWeatherIcon(weather: string): string {
if (weather.includes('晴')) return '☀️';
if (weather.includes('云') || weather.includes('阴')) return '☁️';
if (weather.includes('雨')) return '🌧️';
if (weather.includes('雪')) return '❄️';
if (weather.includes('雾') || weather.includes('霾')) return '🌫️';
if (weather.includes('雷')) return '⛈️';
if (weather.includes('风')) return '💨';
return '🌤️';
}
4.2 天气信息展示
在地图页面添加天气信息卡片:
// 天气信息卡片
if (this.currentWeather) {
Row() {
Text(this.getWeatherIcon(this.currentWeather))
.fontSize(24)
Column() {
Text(`${this.currentWeather} ${this.currentTemperature}°C`)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
Text(`${this.windDirection}风 ${this.windPower}级 | 湿度${this.humidity}%`)
.fontSize(11)
.fontColor('#999999')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 8 })
}
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.shadow({ radius: 4, color: '#22000000' })
.margin({ top: 80, left: 16 })
}
五、完整功能集成
5.1 地图工具栏
在地图页面添加工具栏,集成所有功能:
// 地图工具栏
Row() {
// POI搜索按钮
Button() {
Image($r('app.media.ic_search'))
.width(24)
.height(24)
.fillColor('#FFFFFF')
}
.width(48)
.height(48)
.backgroundColor('#4CAF50')
.borderRadius(24)
.onClick(() => {
this.showPoiSearch = !this.showPoiSearch;
})
// 定位按钮
Button() {
Image($r('app.media.ic_location'))
.width(24)
.height(24)
.fillColor('#FFFFFF')
}
.width(48)
.height(48)
.backgroundColor('#2196F3')
.borderRadius(24)
.margin({ left: 12 })
.onClick(() => {
this.moveToCurrentLocation();
})
// 清除路线按钮
if (this.routePolyline) {
Button() {
Image($r('app.media.ic_close'))
.width(24)
.height(24)
.fillColor('#FFFFFF')
}
.width(48)
.height(48)
.backgroundColor('#FF5722')
.borderRadius(24)
.margin({ left: 12 })
.onClick(() => {
this.clearRoute();
})
}
}
.position({ x: 16, y: '50%' })
5.2 地块标记点击触发导航
修改地块标记的点击事件,添加导航功能:
// 在addFieldMarkers方法中
globalAMap.setOnMarkerClickListener({
onMarkerClick: (marker: Marker): boolean => {
const title = marker.getTitle();
console.info('[FieldMapPage] 标记被点击:', title);
// 查找对应的地块
const field = this.fields.find(f => f.name === title);
if (field) {
// 显示地块详情对话框
AlertDialog.show({
title: field.name,
message: `面积: ${field.area}亩\\n${field.currentCrop ? '作物: ' + field.currentCrop.cropName : '状态: 闲置'}`,
primaryButton: {
value: '导航',
action: () => {
// 规划到该地块的路线
this.planRouteToField(field.id);
}
},
secondaryButton: {
value: '查看详情',
action: () => {
// 跳转到地块详情页
router.pushUrl({
url: 'pages/Field/FieldDetailPage',
params: { fieldId: field.id }
});
}
}
});
}
return true;
}
});
六、实操练习
练习1:实现路线规划
任务:点击地块标记,规划从当前位置到该地块的路线
步骤:
预期结果:
- 地图上显示蓝色路线
- 弹出提示显示路线距离和时长
- 路线从当前位置连接到目标地块
练习2:搜索周边POI
任务:搜索周边的农资店
步骤:
预期结果:
- 地图上显示紫红色POI标记
- 显示搜索范围的橙色圆圈
- 列表展示POI名称、地址、距离
- 可以规划到POI的路线
练习3:调整搜索半径
任务:尝试不同的搜索半径
步骤:
预期结果:
- 半径越大,搜索结果越多
- 地图上的圆圈范围相应变化
- 远距离的POI也会被搜索到
七、常见问题与解决方案
问题1:路线规划失败
现象:点击导航后提示"路线搜索失败"
可能原因:
解决方案:
// 1. 检查网络权限
// module.json5中添加:
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
// 2. 验证坐标有效性
if (!startLat || !startLon || !endLat || !endLon) {
console.error('坐标无效');
return;
}
// 3. 检查API Key
console.info('API Key:', MapConstants.AMAP_API_KEY);
// 4. 添加错误处理
try {
this.routeSearch.calculateDriveRouteAsyn(query);
} catch (error) {
console.error('路线搜索异常:', error);
}
问题2:POI搜索无结果
现象:搜索后提示"未找到相关POI"
可能原因:
解决方案:
// 1. 增大搜索半径
this.searchRadius = 10000; // 10公里
// 2. 使用更通用的关键词
this.poiKeyword = '商店'; // 而不是'农资店'
// 3. 添加城市范围搜索
const query = new PoiQuery(keyword, '', '武汉市');
问题3:路线不显示在地图上
现象:路线规划成功,但地图上看不到路线
可能原因:
解决方案:
// 1. 提高ZIndex
polylineOptions.setZIndex(100);
// 2. 使用更鲜艳的颜色
polylineOptions.setColor(0xFFFF0000); // 红色
polylineOptions.setWidth(15); // 增加宽度
// 3. 调整地图视角
const centerLat = (startLat + endLat) / 2;
const centerLon = (startLon + endLon) / 2;
const cameraUpdate = CameraUpdateFactory.newLatLngZoom(
new LatLng(centerLat, centerLon),
12
);
globalAMap.animateCamera(cameraUpdate);
问题4:定位权限未授权
现象:无法获取当前位置
解决方案:
// 在EntryAbility.ets的onCreate中请求权限
async requestPermissions() {
const permissions: Permissions[] = [
'ohos.permission.APPROXIMATELY_LOCATION',
'ohos.permission.LOCATION'
];
const atManager = abilityAccessCtrl.createAtManager();
const result = await atManager.requestPermissionsFromUser(
this.context,
permissions
);
console.info('权限申请结果:', result.authResults);
}
八、功能扩展建议
8.1 多种出行方式
除了驾车路线,还可以添加步行、骑行路线:
// 步行路线
private searchWalkRoute(startLat: number, startLon: number, endLat: number, endLon: number): void {
const startPoint = new LatLonPoint(startLat, startLon);
const endPoint = new LatLonPoint(endLat, endLon);
const fromAndTo = new FromAndTo(startPoint, endPoint);
const query = new WalkRouteQuery(fromAndTo);
const listener: OnRouteSearchListener = {
onWalkRouteSearched: (result: WalkRouteResult | undefined, errorCode: number): void => {
// 处理步行路线结果
},
onDriveRouteSearched: (): void => {},
onRideRouteSearched: (): void => {},
onBusRouteSearched: (): void => {}
};
this.routeSearch.setRouteSearchListener(listener);
this.routeSearch.calculateWalkRouteAsyn(query);
}
8.2 路线对比
显示多条路线方案供用户选择:
private handleRouteResult(result: DriveRouteResult | undefined, errorCode: number): void {
if (errorCode === AMapException.CODE_AMAP_SUCCESS && result) {
const paths = result.getPaths();
// 显示所有路线方案
for (let i = 0; i < paths.length; i++) {
const path = paths[i];
console.info(`方案${i + 1}: ${path.getDistance()}米, ${path.getDuration()}秒`);
// 可以用不同颜色绘制多条路线
this.drawRouteWithColor(path.getSteps(), i === 0 ? 0xFF2196F3 : 0xFF9E9E9E);
}
}
}
8.3 实时导航
集成导航SDK实现实时导航:
// 初始化导航实例
private naviInstance: AMapNavi | null = null;
// 开始导航
private startNavigation(endLat: number, endLon: number): void {
if (!this.naviInstance) {
const context = getContext(this) as Context;
this.naviInstance = AMapNaviFactory.getAMapNaviInstance(
context,
MapConstants.AMAP_API_KEY
);
}
// 设置终点
const endPoint = new NaviLatLng(endLat, endLon);
const endList = new ArrayList<NaviLatLng>();
endList.add(endPoint);
// 计算路线
this.naviInstance.calculateDriveRoute(
new ArrayList<NaviLatLng>(), // 起点(空表示当前位置)
endList, // 终点
new ArrayList<NaviLatLng>(), // 途经点
AMapNaviStrategy.DRIVING_DEFAULT
);
// 开始导航
this.naviInstance.startNavi(AMapNaviMode.GPS);
}
8.4 收藏常用地点
保存常用POI,方便快速导航:
interface FavoritePoi {
id: string;
name: string;
latitude: number;
longitude: number;
category: string;
}
private favoritePois: FavoritePoi[] = [];
// 添加收藏
private addFavorite(poi: PoiItem): void {
const latLon = poi.getLatLonPoint();
if (latLon) {
const favorite: FavoritePoi = {
id: Date.now().toString(),
name: poi.getTitle(),
latitude: latLon.getLatitude(),
longitude: latLon.getLongitude(),
category: this.poiKeyword
};
this.favoritePois.push(favorite);
// 保存到本地存储
StorageUtil.set('favoritePois', JSON.stringify(this.favoritePois));
promptAction.showToast({
message: '已添加到收藏',
duration: 2000
});
}
}
九、性能优化建议
9.1 路线缓存
缓存已计算的路线,避免重复计算:
private routeCache: Map<string, DriveRouteResult> = new Map();
private getCacheKey(startLat: number, startLon: number, endLat: number, endLon: number): string {
return `${startLat.toFixed(4)},${startLon.toFixed(4)}–${endLat.toFixed(4)},${endLon.toFixed(4)}`;
}
private searchDriveRoute(startLat: number, startLon: number, endLat: number, endLon: number): void {
const cacheKey = this.getCacheKey(startLat, startLon, endLat, endLon);
// 检查缓存
if (this.routeCache.has(cacheKey)) {
console.info('[FieldMapPage] 使用缓存的路线');
const cachedResult = this.routeCache.get(cacheKey);
this.handleRouteResult(cachedResult, AMapException.CODE_AMAP_SUCCESS);
return;
}
// 正常搜索…
}
9.2 POI标记优化
当POI数量较多时,使用聚合显示:
// 根据地图缩放级别决定是否显示标记
private updatePoiMarkers(zoom: number): void {
if (zoom < 12) {
// 缩放级别较小时,隐藏标记或显示聚合
for (const marker of this.poiMarkers) {
marker.setVisible(false);
}
} else {
// 缩放级别较大时,显示所有标记
for (const marker of this.poiMarkers) {
marker.setVisible(true);
}
}
}
9.3 搜索防抖
避免频繁搜索:
private searchTimer: number = –1;
private debouncedSearch(keyword: string): void {
// 清除之前的定时器
if (this.searchTimer !== –1) {
clearTimeout(this.searchTimer);
}
// 设置新的定时器
this.searchTimer = setTimeout(() => {
this.searchNearbyPoi(keyword);
}, 500); // 500ms延迟
}
十、总结
本篇教程完成了地图导航与路线规划功能的实现,主要包括:
✅ 已实现功能
| 驾车路线规划 | 从当前位置到目标地块的路线计算 |
| 路线可视化 | 在地图上绘制蓝色路线 |
| POI周边搜索 | 搜索农资店、农机租赁、加油站等 |
| 搜索结果展示 | 列表+地图标记双重展示 |
| 天气查询 | 基于当前位置的实时天气 |
| 交互优化 | 点击地块触发导航、POI导航 |
🎯 核心技术点
📊 数据流程
用户操作 → 搜索服务 → 异步回调 → 结果处理 → UI更新
↓
点击地块
↓
获取坐标 → RouteSearch → 路线结果 → 绘制Polyline
↓
点击搜索
↓
输入关键词 → PoiSearch → POI结果 → 添加Marker
🚀 下一步
在下一篇教程中,我们将学习:
- HarmonyOS AI能力集成
- Vision Kit图像识别
- 病虫害识别功能
- 作物健康检测
教程版本:v1.0 更新日期:2024-01 作者:高高种地开发团队
相关资源:
- 高德地图HarmonyOS SDK文档
- HarmonyOS开发者文档
- 项目源码
附录:完整代码示例
A. 路线规划完整代码
/**
* 地图页面 – 路线规划功能
*/
@Component
export struct FieldMapPage {
// 路线搜索相关
private routeSearch: RouteSearch | null = null;
private routePolyline: Polyline | null = null;
@State showRouteDialog: boolean = false;
@State routeDistance: string = '';
@State routeTime: string = '';
aboutToAppear(): void {
const context = getContext(this) as Context;
this.routeSearch = new RouteSearch(context);
}
// 规划路线
private planRouteToField(fieldId: string): void {
const field = this.fields.find(f => f.id === fieldId);
if (field && this.currentLocationInfo) {
this.searchDriveRoute(
this.currentLocationInfo.latitude!,
this.currentLocationInfo.longitude!,
field.latitude!,
field.longitude!
);
}
}
// 搜索驾车路线
private searchDriveRoute(
startLat: number, startLon: number,
endLat: number, endLon: number
): void {
if (!this.routeSearch) return;
const startPoint = new LatLonPoint(startLat, startLon);
const endPoint = new LatLonPoint(endLat, endLon);
const fromAndTo = new FromAndTo(startPoint, endPoint);
const query = new DriveRouteQuery(
fromAndTo, 0,
new ArrayList<LatLonPoint>(),
new ArrayList<ArrayList<LatLonPoint>>(),
''
);
const listener: OnRouteSearchListener = {
onDriveRouteSearched: (result, errorCode) => {
this.handleRouteResult(result, errorCode);
},
onWalkRouteSearched: () => {},
onRideRouteSearched: () => {},
onBusRouteSearched: () => {}
};
this.routeSearch.setRouteSearchListener(listener);
this.routeSearch.calculateDriveRouteAsyn(query);
}
// 处理路线结果
private handleRouteResult(result: DriveRouteResult | undefined, errorCode: number): void {
if (errorCode === AMapException.CODE_AMAP_SUCCESS && result) {
const paths = result.getPaths();
if (paths && paths.length > 0) {
const path = paths[0];
this.routeDistance = `${(path.getDistance() / 1000).toFixed(2)}公里`;
this.routeTime = `${Math.floor(path.getDuration() / 60)}分钟`;
this.drawRoute(path.getSteps());
this.showRouteDialog = true;
}
}
}
// 绘制路线
private drawRoute(steps: ESObject): void {
if (!globalAMap) return;
if (this.routePolyline) {
this.routePolyline.remove();
}
const polylineOptions = new PolylineOptions();
const stepsArray: ESObject = steps;
const length: number = stepsArray.length as number;
for (let i = 0; i < length; i++) {
const step: ESObject = stepsArray[i] as ESObject;
const polyline: ESObject = step.getPolyline?.() as ESObject;
if (polyline) {
const polylineLength: number = polyline.length as number;
for (let j = 0; j < polylineLength; j++) {
const point: ESObject = polyline[j] as ESObject;
const lat: number = point.getLatitude?.() as number;
const lon: number = point.getLongitude?.() as number;
if (lat && lon) {
polylineOptions.add(new LatLng(lat, lon));
}
}
}
}
polylineOptions.setWidth(12);
polylineOptions.setColor(0xFF2196F3);
polylineOptions.setZIndex(50);
this.routePolyline = globalAMap.addPolyline(polylineOptions);
}
}
B. POI搜索完整代码
/**
* 地图页面 – POI搜索功能
*/
@Component
export struct FieldMapPage {
// POI搜索相关
private poiSearch: PoiSearch | null = null;
private poiMarkers: Marker[] = [];
@State poiResults: PoiItem[] = [];
@State searchRadius: number = 5000;
aboutToAppear(): void {
const context = getContext(this) as Context;
this.poiSearch = new PoiSearch(context, undefined);
}
// 搜索周边POI
private searchNearbyPoi(keyword: string): void {
if (!this.poiSearch || !this.currentLocationInfo) return;
const centerPoint = new LatLonPoint(
this.currentLocationInfo.latitude!,
this.currentLocationInfo.longitude!
);
const searchBound = PoiSearchBound.createCircleSearchBound(
centerPoint,
this.searchRadius
);
const query = new PoiQuery(keyword, '', '');
query.setPageSize(20);
query.setPageNum(0);
const listener: OnPoiSearchListener = {
onPoiSearched: (result, errorCode) => {
this.handlePoiResult(result, errorCode);
},
onPoiItemSearched: () => {}
};
this.poiSearch.setOnPoiSearchListener(listener);
this.poiSearch.setBound(searchBound);
this.poiSearch.setQuery(query);
this.poiSearch.searchPOIAsyn();
}
// 处理POI结果
private handlePoiResult(result: PoiResult | undefined, errorCode: number): void {
if (!globalAMap) return;
this.clearPoiResults();
if (errorCode === AMapException.CODE_AMAP_SUCCESS && result) {
const pois = result.getPois();
if (pois && pois.length > 0) {
this.poiResults = [];
for (let i = 0; i < pois.length; i++) {
this.poiResults.push(pois[i]);
}
// 添加标记
for (const poi of this.poiResults) {
const latLon = poi.getLatLonPoint();
if (latLon) {
const markerOptions = new MarkerOptions();
markerOptions.setPosition(
new LatLng(latLon.getLatitude(), latLon.getLongitude())
);
markerOptions.setTitle(poi.getTitle());
markerOptions.setIcon(
BitmapDescriptorFactory.defaultMarker(
BitmapDescriptorFactory.HUE_MAGENTA
)
);
const marker = globalAMap.addMarker(markerOptions);
if (marker) {
this.poiMarkers.push(marker);
}
}
}
}
}
}
// 清除POI结果
private clearPoiResults(): void {
for (const marker of this.poiMarkers) {
marker.remove();
}
this.poiMarkers = [];
this.poiResults = [];
}
}
恭喜! 🎉 你已经完成了地图导航与路线规划功能的学习。现在你的应用具备了完整的导航能力,可以帮助农户快速找到农田和周边服务设施。
网硕互联帮助中心





评论前必须登录!
注册