HarmonyOS中开发高德地图第八篇:路线规划功能

99 阅读5分钟

第八篇:路线规划功能

本篇教程将学习如何使用高德地图实现驾车、步行、骑行等路线规划功能。

学习目标

  • 实现驾车路线规划
  • 实现步行路线规划
  • 实现骑行路线规划
  • 在地图上绑制规划路线
  • 显示路线详情信息

1. 路线规划类型

类型类名说明
驾车DriveRouteQuery汽车导航路线
步行WalkRouteQuery步行路线
骑行RideRouteQuery骑行路线
公交BusRouteQuery公共交通路线
货车TruckRouteQuery货车路线

2. 核心类说明

类名说明
RouteSearch路线搜索核心类
FromAndTo起终点配置
LatLonPoint坐标点
DriveRouteResult驾车结果
WalkRouteResult步行结果
DrivePath驾车路径
DriveStep驾车步骤

3. 完整代码示例

创建文件 entry/src/main/ets/pages/Demo07_Route.ets

import {
  AMap,
  MapView,
  MapViewComponent,
  MapViewManager,
  MapViewCreateCallback,
  CameraUpdateFactory,
  LatLng,
  LatLngBounds,
  Marker,
  MarkerOptions,
  Polyline,
  PolylineOptions,
  BitmapDescriptorFactory
} from '@amap/amap_lbs_map3d';
import {
  RouteSearch,
  DriveRouteQuery,
  DriveRouteResult,
  WalkRouteQuery,
  WalkRouteResult,
  RideRouteQuery,
  RideRouteResult,
  BusRouteResult,
  FromAndTo,
  LatLonPoint,
  OnRouteSearchListener,
  AMapException,
  DrivePath,
  DriveStep,
  WalkPath,
  WalkStep,
  RidePath,
  RideStep
} from '@amap/amap_lbs_search';

const MAP_VIEW_NAME = 'RouteDemo';

/**
 * 路线类型
 */
type RouteType = 'drive' | 'walk' | 'ride';

/**
 * 路线步骤信息
 */
interface RouteStepInfo {
  instruction: string;
  distance: number;
  duration: number;
}

@Entry
@Component
struct Demo07_Route {
  private mapView: MapView | undefined = undefined;
  private aMap: AMap | undefined = undefined;
  private routeSearch: RouteSearch | undefined = undefined;
  private routePolyline: Polyline | undefined = undefined;
  private startMarker: Marker | undefined = undefined;
  private endMarker: Marker | undefined = undefined;
  
  @State isMapReady: boolean = false;
  @State routeType: RouteType = 'drive';
  @State isSearching: boolean = false;
  @State routeInfo: string = '';
  @State routeSteps: RouteStepInfo[] = [];
  
  // 起终点坐标
  private startPoint: LatLonPoint = new LatLonPoint(39.942295, 116.335891);  // 北京西站
  private endPoint: LatLonPoint = new LatLonPoint(39.995576, 116.481288);    // 望京SOHO

  /**
   * 路线搜索回调
   */
  private routeSearchListener: OnRouteSearchListener = {
    // 驾车路线回调
    onDriveRouteSearched: (result: DriveRouteResult | undefined, errorCode: number) => {
      this.isSearching = false;
      
      if (errorCode === AMapException.CODE_AMAP_SUCCESS && result) {
        const paths = result.getPaths();
        if (paths && paths.length > 0) {
          const path = paths[0] as DrivePath;
          this.showDriveRoute(path, result);
        } else {
          this.routeInfo = '未找到驾车路线';
        }
      } else {
        this.routeInfo = `驾车路线查询失败: ${errorCode}`;
      }
    },
    
    // 步行路线回调
    onWalkRouteSearched: (result: WalkRouteResult | undefined, errorCode: number) => {
      this.isSearching = false;
      
      if (errorCode === AMapException.CODE_AMAP_SUCCESS && result) {
        const paths = result.getPaths();
        if (paths && paths.length > 0) {
          const path = paths[0] as WalkPath;
          this.showWalkRoute(path);
        } else {
          this.routeInfo = '未找到步行路线';
        }
      } else {
        this.routeInfo = `步行路线查询失败: ${errorCode}`;
      }
    },
    
    // 骑行路线回调
    onRideRouteSearched: (result: RideRouteResult | undefined, errorCode: number) => {
      this.isSearching = false;
      
      if (errorCode === AMapException.CODE_AMAP_SUCCESS && result) {
        const paths = result.getPaths();
        if (paths && paths.length > 0) {
          const path = paths[0] as RidePath;
          this.showRideRoute(path);
        } else {
          this.routeInfo = '未找到骑行路线';
        }
      } else {
        this.routeInfo = `骑行路线查询失败: ${errorCode}`;
      }
    },
    
    // 公交路线回调
    onBusRouteSearched: (result: BusRouteResult | undefined, errorCode: number) => {
      this.isSearching = false;
      this.routeInfo = '公交路线功能待实现';
    }
  };

  private mapViewCreateCallback: MapViewCreateCallback = 
    (mapview: MapView | undefined, mapViewName: string | undefined) => {
      if (!mapview || mapViewName !== MAP_VIEW_NAME) return;

      this.mapView = mapview;
      this.mapView.onCreate();
      
      this.mapView.getMapAsync((map: AMap) => {
        this.aMap = map;
        this.isMapReady = true;
        
        // 设置初始视野
        const center = new LatLng(39.97, 116.41);
        map.moveCamera(CameraUpdateFactory.newLatLngZoom(center, 11));
        
        // 启用控件
        map.getUiSettings()?.setZoomControlsEnabled(true);
        
        // 添加起终点标记
        this.addStartEndMarkers();
        
        // 地图长按设置终点
        map.setOnMapLongClickListener((point: LatLng) => {
          this.endPoint = new LatLonPoint(point.latitude, point.longitude);
          this.updateEndMarker();
          this.routeInfo = '终点已更新,点击搜索查看路线';
        });
      });
    };

  /**
   * 添加起终点标记
   */
  private addStartEndMarkers(): void {
    if (!this.aMap) return;
    
    // 起点标记
    const startOptions = new MarkerOptions();
    startOptions.setPosition(new LatLng(this.startPoint.getLatitude(), this.startPoint.getLongitude()));
    startOptions.setTitle('起点');
    startOptions.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN));
    startOptions.setAnchor(0.5, 1.0);
    this.startMarker = this.aMap.addMarker(startOptions);
    
    // 终点标记
    const endOptions = new MarkerOptions();
    endOptions.setPosition(new LatLng(this.endPoint.getLatitude(), this.endPoint.getLongitude()));
    endOptions.setTitle('终点');
    endOptions.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED));
    endOptions.setAnchor(0.5, 1.0);
    this.endMarker = this.aMap.addMarker(endOptions);
  }

  /**
   * 更新终点标记
   */
  private updateEndMarker(): void {
    if (this.endMarker) {
      this.endMarker.setPosition(new LatLng(this.endPoint.getLatitude(), this.endPoint.getLongitude()));
    }
  }

  /**
   * 搜索路线
   */
  private searchRoute(): void {
    if (!this.routeSearch) return;
    
    this.isSearching = true;
    this.routeInfo = '正在搜索路线...';
    this.routeSteps = [];
    
    // 清除之前的路线
    this.clearRouteLine();
    
    // 创建起终点
    const fromAndTo = new FromAndTo(this.startPoint, this.endPoint);
    
    switch (this.routeType) {
      case 'drive':
        this.searchDriveRoute(fromAndTo);
        break;
      case 'walk':
        this.searchWalkRoute(fromAndTo);
        break;
      case 'ride':
        this.searchRideRoute(fromAndTo);
        break;
    }
  }

  /**
   * 搜索驾车路线
   */
  private searchDriveRoute(fromAndTo: FromAndTo): void {
    const query = new DriveRouteQuery(
      fromAndTo,
      RouteSearch.DrivingDefault,   // 驾车策略
      undefined,                     // 途经点
      undefined,                     // 避让区域
      ''                            // 避让道路
    );
    query.setExtensions(RouteSearch.EXTENSIONS_ALL);
    
    this.routeSearch?.calculateDriveRouteAsyn(query);
    console.info('[Route] Searching drive route...');
  }

  /**
   * 搜索步行路线
   */
  private searchWalkRoute(fromAndTo: FromAndTo): void {
    const query = new WalkRouteQuery(fromAndTo);
    
    this.routeSearch?.calculateWalkRouteAsyn(query);
    console.info('[Route] Searching walk route...');
  }

  /**
   * 搜索骑行路线
   */
  private searchRideRoute(fromAndTo: FromAndTo): void {
    const query = new RideRouteQuery(fromAndTo);
    
    this.routeSearch?.calculateRideRouteAsyn(query);
    console.info('[Route] Searching ride route...');
  }

  /**
   * 显示驾车路线
   */
  private showDriveRoute(path: DrivePath, result: DriveRouteResult): void {
    const distance = path.getDistance();
    const duration = path.getDuration();
    const taxiCost = result.getTaxiCost();
    
    this.routeInfo = `驾车路线\n距离: ${this.formatDistance(distance)}\n时间: ${this.formatDuration(duration)}\n预估打车费: ¥${Math.floor(taxiCost)}`;
    
    // 解析路线步骤
    const steps = path.getSteps();
    if (steps) {
      this.routeSteps = [];
      for (let i = 0; i < steps.length; i++) {
        const step = steps[i] as DriveStep;
        this.routeSteps.push({
          instruction: step.getInstruction() || '',
          distance: step.getDistance(),
          duration: step.getDuration()
        });
      }
    }
    
    // 绘制路线
    this.drawRouteLine(path.getSteps(), '#4CAF50');
  }

  /**
   * 显示步行路线
   */
  private showWalkRoute(path: WalkPath): void {
    const distance = path.getDistance();
    const duration = path.getDuration();
    
    this.routeInfo = `步行路线\n距离: ${this.formatDistance(distance)}\n时间: ${this.formatDuration(duration)}`;
    
    // 解析步骤
    const steps = path.getSteps();
    if (steps) {
      this.routeSteps = [];
      for (let i = 0; i < steps.length; i++) {
        const step = steps[i] as WalkStep;
        this.routeSteps.push({
          instruction: step.getInstruction() || '',
          distance: step.getDistance(),
          duration: step.getDuration()
        });
      }
    }
    
    // 绘制路线
    this.drawRouteLine(steps, '#2196F3');
  }

  /**
   * 显示骑行路线
   */
  private showRideRoute(path: RidePath): void {
    const distance = path.getDistance();
    const duration = path.getDuration();
    
    this.routeInfo = `骑行路线\n距离: ${this.formatDistance(distance)}\n时间: ${this.formatDuration(duration)}`;
    
    // 解析步骤
    const steps = path.getSteps();
    if (steps) {
      this.routeSteps = [];
      for (let i = 0; i < steps.length; i++) {
        const step = steps[i] as RideStep;
        this.routeSteps.push({
          instruction: step.getInstruction() || '',
          distance: step.getDistance(),
          duration: step.getDuration()
        });
      }
    }
    
    // 绘制路线
    this.drawRouteLine(steps, '#FF9800');
  }

  /**
   * 绘制路线
   */
  private drawRouteLine(steps: Object[] | undefined, color: string): void {
    if (!this.aMap || !steps) return;
    
    // 收集所有坐标点
    const points: LatLng[] = [];
    
    for (const step of steps) {
      // 获取polyline
      const polyline = (step as DriveStep).getPolyline?.() || 
                       (step as WalkStep).getPolyline?.() ||
                       (step as RideStep).getPolyline?.();
      
      if (polyline) {
        for (const point of polyline) {
          points.push(new LatLng(point.getLatitude(), point.getLongitude()));
        }
      }
    }
    
    if (points.length > 0) {
      // 绘制折线
      const options = new PolylineOptions();
      options.setPoints(points);
      options.setWidth(12);
      options.setColor(this.parseColor(color));
      
      this.routePolyline = this.aMap.addPolyline(options);
      
      // 调整视野
      this.fitRouteBounds(points);
    }
  }

  /**
   * 清除路线
   */
  private clearRouteLine(): void {
    if (this.routePolyline) {
      this.routePolyline.remove();
      this.routePolyline = undefined;
    }
  }

  /**
   * 调整地图视野以显示完整路线
   */
  private fitRouteBounds(points: LatLng[]): void {
    if (!this.aMap || points.length === 0) return;
    
    let minLat = 90, maxLat = -90, minLng = 180, maxLng = -180;
    
    for (const point of points) {
      minLat = Math.min(minLat, point.latitude);
      maxLat = Math.max(maxLat, point.latitude);
      minLng = Math.min(minLng, point.longitude);
      maxLng = Math.max(maxLng, point.longitude);
    }
    
    const bounds = new LatLngBounds(
      new LatLng(minLat, minLng),
      new LatLng(maxLat, maxLng)
    );
    
    this.aMap.animateCamera(
      CameraUpdateFactory.newLatLngBounds(bounds, 80),
      500
    );
  }

  /**
   * 格式化距离
   */
  private formatDistance(meters: number): string {
    if (meters < 1000) {
      return `${meters}米`;
    }
    return `${(meters / 1000).toFixed(1)}公里`;
  }

  /**
   * 格式化时间
   */
  private formatDuration(seconds: number): string {
    if (seconds < 60) {
      return `${seconds}秒`;
    }
    const minutes = Math.floor(seconds / 60);
    if (minutes < 60) {
      return `${minutes}分钟`;
    }
    const hours = Math.floor(minutes / 60);
    const remainMinutes = minutes % 60;
    return `${hours}小时${remainMinutes}分钟`;
  }

  /**
   * 解析颜色值
   */
  private parseColor(hex: string): number {
    const r = parseInt(hex.slice(1, 3), 16);
    const g = parseInt(hex.slice(3, 5), 16);
    const b = parseInt(hex.slice(5, 7), 16);
    return 0xFF000000 | (r << 16) | (g << 8) | b;
  }

  aboutToAppear(): void {
    MapViewManager.getInstance()
      .registerMapViewCreatedCallback(this.mapViewCreateCallback);
    
    // 初始化路线搜索
    const context = getContext(this);
    this.routeSearch = new RouteSearch(context);
    this.routeSearch.setRouteSearchListener(this.routeSearchListener);
  }

  aboutToDisappear(): void {
    this.clearRouteLine();
    
    MapViewManager.getInstance()
      .unregisterMapViewCreatedCallback(this.mapViewCreateCallback);
    
    if (this.mapView) {
      this.mapView.onDestroy();
      this.mapView = undefined;
      this.aMap = undefined;
    }
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('路线规划')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor(Color.White)
      }
      .width('100%')
      .height(50)
      .padding({ left: 16 })
      .backgroundColor('#673AB7')

      // 路线类型选择
      Row() {
        ForEach(['drive', 'walk', 'ride'] as RouteType[], (type: RouteType) => {
          Button(type === 'drive' ? '驾车' : type === 'walk' ? '步行' : '骑行')
            .fontSize(13)
            .height(36)
            .layoutWeight(1)
            .margin({ right: type !== 'ride' ? 8 : 0 })
            .backgroundColor(this.routeType === type ? '#673AB7' : '#e0e0e0')
            .fontColor(this.routeType === type ? Color.White : '#333')
            .onClick(() => { this.routeType = type; })
        })
        
        Button('搜索')
          .fontSize(13)
          .height(36)
          .width(70)
          .margin({ left: 8 })
          .enabled(!this.isSearching)
          .onClick(() => this.searchRoute())
      }
      .width('100%')
      .padding(12)
      .backgroundColor('#f5f5f5')

      // 地图区域
      Stack() {
        MapViewComponent({ mapViewName: MAP_VIEW_NAME })
          .width('100%')
          .height('100%')
        
        // 路线信息
        if (this.routeInfo) {
          Column() {
            Text(this.routeInfo)
              .fontSize(12)
              .fontColor('#333')
          }
          .padding(10)
          .backgroundColor('rgba(255,255,255,0.95)')
          .borderRadius(8)
          .position({ x: 10, y: 10 })
        }
        
        // 操作提示
        Text('长按地图设置终点')
          .fontSize(11)
          .fontColor('#fff')
          .backgroundColor('rgba(0,0,0,0.6)')
          .padding(6)
          .borderRadius(4)
          .position({ x: 10, y: 100 })
      }
      .width('100%')
      .layoutWeight(1)

      // 路线步骤
      if (this.routeSteps.length > 0) {
        Column() {
          Text('路线详情')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .padding(8)
            .width('100%')
            .backgroundColor('#e0e0e0')
          
          List() {
            ForEach(this.routeSteps, (step: RouteStepInfo, index: number) => {
              ListItem() {
                Row() {
                  Text(`${index + 1}`)
                    .fontSize(12)
                    .fontColor(Color.White)
                    .width(24)
                    .height(24)
                    .textAlign(TextAlign.Center)
                    .backgroundColor('#673AB7')
                    .borderRadius(12)
                  
                  Column() {
                    Text(step.instruction)
                      .fontSize(12)
                      .fontColor('#333')
                      .maxLines(2)
                    Text(`${this.formatDistance(step.distance)} | ${this.formatDuration(step.duration)}`)
                      .fontSize(10)
                      .fontColor('#999')
                      .margin({ top: 2 })
                  }
                  .layoutWeight(1)
                  .alignItems(HorizontalAlign.Start)
                  .margin({ left: 8 })
                }
                .width('100%')
                .padding(8)
              }
            })
          }
          .height(120)
          .divider({ strokeWidth: 1, color: '#eee' })
        }
        .backgroundColor(Color.White)
      }
    }
    .width('100%')
    .height('100%')
  }
}

4. 驾车策略说明

// 驾车策略常量
RouteSearch.DrivingDefault          // 默认(速度优先)
RouteSearch.DrivingNoHighway        // 不走高速
RouteSearch.DrivingNoFare           // 避免收费
RouteSearch.DrivingShortest         // 距离最短
RouteSearch.DrivingHighway          // 高速优先
RouteSearch.DrivingAvoidCongestion  // 躲避拥堵
RouteSearch.DrivingMultiStrategy    // 多策略

5. 设置途经点

// 添加途经点
const wayPoints: LatLonPoint[] = [
  new LatLonPoint(39.95, 116.40),
  new LatLonPoint(39.96, 116.42)
];

const query = new DriveRouteQuery(
  fromAndTo,
  RouteSearch.DrivingDefault,
  wayPoints,  // 途经点数组
  undefined,
  ''
);

6. 设置避让区域

// 避让区域(矩形)
const avoidArea: LatLonPoint[] = [
  new LatLonPoint(39.90, 116.38),  // 左下角
  new LatLonPoint(39.92, 116.40)   // 右上角
];

const avoidPolygons: LatLonPoint[][] = [avoidArea];

const query = new DriveRouteQuery(
  fromAndTo,
  RouteSearch.DrivingDefault,
  undefined,
  avoidPolygons,  // 避让区域
  ''
);

7. 路线绘制优化

7.1 使用路况颜色

// 根据路况设置不同颜色
private getTrafficColor(trafficStatus: number): number {
  switch (trafficStatus) {
    case 0: return 0xFF4CAF50;  // 畅通-绿色
    case 1: return 0xFFFFEB3B;  // 缓行-黄色
    case 2: return 0xFFFF9800;  // 拥堵-橙色
    case 3: return 0xFFF44336;  // 严重拥堵-红色
    default: return 0xFF2196F3; // 未知-蓝色
  }
}

7.2 绘制带方向的路线

// 使用带箭头的纹理
options.setLineTexture(arrowTexture);
options.setUseTexture(true);

本篇小结

本篇教程我们学习了:

  • ✅ 驾车路线规划实现
  • ✅ 步行路线规划实现
  • ✅ 骑行路线规划实现
  • ✅ 路线绘制和视野调整
  • ✅ 路线详情展示
  • ✅ 途经点和避让区域设置

下一篇我们将学习绑图与测距功能。 班级 developer.huawei.com/consumer/cn…

源码地址 gitcode.com/daleishen/g…