Skip to content

导航功能

expo-gaode-map-navigation 是高德地图"导航一体化"模块,内置完整的地图渲染和导航能力。

安装

导航包是独立的一体化解决方案,不需要同时安装 expo-gaode-map

bash
bun add expo-gaode-map-navigation
# 或
yarn add expo-gaode-map-navigation
# 或
npm install expo-gaode-map-navigation

二进制冲突警告

导航包与核心包(expo-gaode-map)存在二进制冲突,不能同时安装。导航包已内置完整的地图功能。

特性

  • 🗺️ 内置地图:包含 MapView 和所有覆盖物组件
  • 🧭 路径规划:驾车、步行、骑行、货车、摩托车等多种方式
  • 🚗 实时导航:NaviView 组件提供完整的导航界面
  • ⚙️ 策略丰富:最快、最短、避拥堵、少收费等多种策略
  • 🔄 独立服务:支持无地图的独立路径规划

配置

使用 Config Plugin(推荐)

app.json 中配置:

json
{
  "expo": {
    "plugins": [
      [
        "expo-gaode-map",
        {
          "iosKey": "your-ios-api-key",
          "androidKey": "your-android-api-key",
          "enableLocation": true
        }
      ]
    ]
  }
}

然后重新构建:

bash
npx expo prebuild --clean
npx expo run:ios
# 或
npx expo run:android

初始化

在应用启动时初始化 SDK:

typescript
import { ExpoGaodeMapModule } from 'expo-gaode-map-navigation';

// 使用 Config Plugin 时,原生 Key 已自动配置
ExpoGaodeMapModule.initSDK({
  webKey: 'your-web-api-key', // 仅在使用 Web API 时需要
});

// 不使用 Config Plugin 时
ExpoGaodeMapModule.initSDK({
  androidKey: 'your-android-key',
  iosKey: 'your-ios-key',
  webKey: 'your-web-api-key', // 可选
});

基础用法

驾车路径规划

typescript
import { calculateDriveRoute, DriveStrategy } from 'expo-gaode-map-navigation';

const result = await calculateDriveRoute({
  type: 'drive',
  from: { latitude: 39.9, longitude: 116.4 },
  to: { latitude: 39.91, longitude: 116.41 },
  strategy: DriveStrategy.FASTEST, // 速度优先
  waypoints: [                      // 途经点(可选)
    { latitude: 39.905, longitude: 116.405 },
  ],
  carNumber: '京A12345',            // 车牌号(可选)
});

// 查看结果
console.log(`找到 ${result.count} 条路线`);
result.routes.forEach((route, index) => {
  console.log(`路线 ${index + 1}:`);
  console.log(`  距离: ${(route.distance / 1000).toFixed(2)}公里`);
  console.log(`  时间: ${Math.floor(route.duration / 60)}分钟`);
  console.log(`  收费: ${route.tollCost}元`);
  console.log(`  红绿灯: ${route.trafficLightCount}个`);
});

DriveStrategy 策略:

typescript
enum DriveStrategy {
  FASTEST = 0,                         // 速度优先(时间最短)
  FEE_FIRST = 1,                       // 费用优先(不走收费)
  SHORTEST = 2,                        // 距离优先
  NO_EXPRESSWAYS = 3,                  // 不走快速路
  AVOID_CONGESTION = 4,                // 躲避拥堵
  NO_HIGHWAY = 5,                      // 不走高速
  NO_HIGHWAY_AVOID_CONGESTION = 6,     // 不走高速且避免拥堵
  AVOID_COST_CONGESTION = 7,           // 躲避收费和拥堵
  NO_HIGHWAY_AVOID_COST_CONGESTION = 8,// 不走高速且躲避收费拥堵
  AVOID_CONGESTION_COST = 9,           // 躲避拥堵和收费
}

步行路径规划

typescript
import { calculateWalkRoute } from 'expo-gaode-map-navigation';

const result = await calculateWalkRoute({
  type: 'walk',
  from: { latitude: 39.9, longitude: 116.4 },
  to: { latitude: 39.91, longitude: 116.41 },
  multiple: true, // 返回多条路线
});

console.log(`步行距离: ${result.distance}米`);
console.log(`预计时间: ${Math.floor(result.duration / 60)}分钟`);

骑行路径规划

typescript
import { calculateRideRoute, RideStrategy } from 'expo-gaode-map-navigation';

const result = await calculateRideRoute({
  type: 'ride',
  from: { latitude: 39.9, longitude: 116.4 },
  to: { latitude: 39.91, longitude: 116.41 },
  strategy: RideStrategy.FASTEST, // 速度优先
  multiple: true,
});

RideStrategy 策略:

typescript
enum RideStrategy {
  DEFAULT = 0,   // 推荐路线
  FASTEST = 1,   // 速度优先
  SHORTEST = 2,  // 距离优先
}

货车路径规划

typescript
import { calculateTruckRoute, TruckSize } from 'expo-gaode-map-navigation';

const result = await calculateTruckRoute({
  type: 'truck',
  from: { latitude: 39.9, longitude: 116.4 },
  to: { latitude: 39.91, longitude: 116.41 },
  size: TruckSize.MEDIUM,  // 货车尺寸
  height: 3.5,             // 高度(米)
  width: 2.5,              // 宽度(米)
  load: 5,                 // 载重(吨)
  weight: 10,              // 总重(吨)
  axis: 2,                 // 轴数
});

TruckSize 尺寸:

typescript
enum TruckSize {
  MINI = 1,    // 微型货车
  LIGHT = 2,   // 轻型货车
  MEDIUM = 3,  // 中型货车
  HEAVY = 4,   // 重型货车
}

电动车路径规划

typescript
import { calculateEBikeRoute } from 'expo-gaode-map-navigation';

const result = await calculateEBikeRoute({
  from: { latitude: 39.9, longitude: 116.4 },
  to: { latitude: 39.91, longitude: 116.41 },
  multiple: true,
});

摩托车路径规划

typescript
import { calculateMotorcycleRoute } from 'expo-gaode-map-navigation';

const result = await calculateMotorcycleRoute({
  from: { latitude: 39.9, longitude: 116.4 },
  to: { latitude: 39.91, longitude: 116.41 },
  motorcycleCC: 250,          // 排量(cc)
  carNumber: '京A12345',      // 车牌号(可选)
  strategy: DriveStrategy.FASTEST,
});

独立路径规划

独立路径规划不会影响当前导航状态,适用于路线预览、对比和切换场景。

基础用法

typescript
import { independentDriveRoute } from 'expo-gaode-map-navigation';

// 1. 规划多条路线
const routes = await independentDriveRoute({
  from: {
    latitude: 39.9,
    longitude: 116.4,
    name: '起点名称',      // 可选
    poiId: 'B000A8VE1H',  // 可选
  },
  to: {
    latitude: 39.91,
    longitude: 116.41,
    name: '终点名称',
    poiId: 'B000A8VE2I',
  },
  waypoints: [
    { latitude: 39.905, longitude: 116.405, name: '途经点1' },
  ],
  strategy: DriveStrategy.FASTEST,
  carNumber: '京A12345',
  restriction: true, // 考虑限行
});

console.log(`找到 ${routes.count} 条路线`);
console.log(`Token: ${routes.token}`); // 用于后续操作

选择路线

typescript
import { selectIndependentRoute } from 'expo-gaode-map-navigation';

// 方式1:使用路线索引
await selectIndependentRoute({
  token: routes.token,
  routeIndex: 1, // 从0开始
});

// 方式2:使用路线ID
await selectIndependentRoute({
  token: routes.token,
  routeId: routes.routeIds[1],
});

启动导航

typescript
import { startNaviWithIndependentPath } from 'expo-gaode-map-navigation';

await startNaviWithIndependentPath({
  token: routes.token,
  naviType: 0,      // 0=GPS导航, 1=模拟导航
  routeIndex: 0,    // 可选,不传则使用当前主路线
});

清理路线

typescript
import { clearIndependentRoute } from 'expo-gaode-map-navigation';

await clearIndependentRoute({
  token: routes.token,
});

完整示例:路线预览与切换

typescript
import React, { useState } from 'react';
import { View, Button, FlatList, Text, TouchableOpacity } from 'react-native';
import {
  independentDriveRoute,
  selectIndependentRoute,
  startNaviWithIndependentPath,
  clearIndependentRoute,
  DriveStrategy,
} from 'expo-gaode-map-navigation';

export default function RoutePreviewScreen() {
  const [routes, setRoutes] = useState(null);
  const [selectedIndex, setSelectedIndex] = useState(0);

  const planRoutes = async () => {
    const result = await independentDriveRoute({
      from: { latitude: 39.9, longitude: 116.4, name: '起点' },
      to: { latitude: 39.91, longitude: 116.41, name: '终点' },
      strategy: DriveStrategy.FASTEST,
    });
    setRoutes(result);
  };

  const handleSelectRoute = async (index: number) => {
    if (!routes) return;
    
    await selectIndependentRoute({
      token: routes.token,
      routeIndex: index,
    });
    setSelectedIndex(index);
  };

  const startNavigation = async () => {
    if (!routes) return;
    
    await startNaviWithIndependentPath({
      token: routes.token,
      naviType: 0, // GPS导航
    });
  };

  const cleanup = async () => {
    if (!routes) return;
    
    await clearIndependentRoute({
      token: routes.token,
    });
    setRoutes(null);
  };

  return (
    <View style={{ flex: 1, padding: 20 }}>
      <Button title="规划路线" onPress={planRoutes} />

      {routes && (
        <>
          <FlatList
            data={routes.routes}
            keyExtractor={(item, index) => index.toString()}
            renderItem={({ item, index }) => (
              <TouchableOpacity
                onPress={() => handleSelectRoute(index)}
                style={{
                  padding: 15,
                  marginTop: 10,
                  backgroundColor:
                    selectedIndex === index ? '#e8f5e9' : '#fff',
                  borderRadius: 8,
                  borderWidth: 1,
                  borderColor: '#ccc',
                }}
              >
                <Text style={{ fontSize: 18, fontWeight: 'bold' }}>
                  路线 {index + 1}
                  {index === routes.mainPathIndex && ' (推荐)'}
                </Text>
                <Text style={{ marginTop: 5 }}>
                  距离: {(item.distance / 1000).toFixed(2)}公里
                </Text>
                <Text>时间: {Math.floor(item.duration / 60)}分钟</Text>
                <Text>收费: {item.tollCost}元</Text>
                <Text>红绿灯: {item.trafficLightCount}个</Text>
              </TouchableOpacity>
            )}
          />

          <View style={{ marginTop: 20 }}>
            <Button title="开始导航" onPress={startNavigation} />
            <Button title="清理路线" onPress={cleanup} color="red" />
          </View>
        </>
      )}
    </View>
  );
}

NaviView 是高德官方提供的完整导航界面组件。

基础用法

typescript
import React, { useRef } from 'react';
import { View, Button } from 'react-native';
import { NaviView, type NaviViewRef } from 'expo-gaode-map-navigation';

function NavigationScreen() {
  const naviViewRef = useRef<NaviViewRef>(null);

  const startNavigation = async () => {
    await naviViewRef.current?.startNavigation(
      { latitude: 39.9, longitude: 116.4 },   // 起点(null=当前位置)
      { latitude: 39.91, longitude: 116.41 }, // 终点
      1  // 0=GPS导航, 1=模拟导航
    );
  };

  const stopNavigation = async () => {
    await naviViewRef.current?.stopNavigation();
  };

  return (
    <View style={{ flex: 1 }}>
      <NaviView
        ref={naviViewRef}
        style={{ flex: 1 }}
        naviType={1}
        enableVoice={true}
        showCamera={true}
        onNaviStart={(e) => console.log('导航开始')}
        onNaviEnd={(e) => console.log('导航结束')}
        onArrive={(e) => console.log('到达目的地')}
        onNaviInfoUpdate={(e) => {
          const { pathRetainDistance, pathRetainTime } = e.nativeEvent;
          console.log(`剩余: ${pathRetainDistance}米, ${pathRetainTime}秒`);
        }}
      />
      
      <View style={{ position: 'absolute', bottom: 20, right: 20 }}>
        <Button title="开始" onPress={startNavigation} />
        <Button title="停止" onPress={stopNavigation} color="red" />
      </View>
    </View>
  );
}

核心属性

属性类型默认值说明
naviTypenumber0导航类型:0=GPS导航, 1=模拟导航
enableVoicebooleantrue是否启用语音播报
showCamerabooleantrue是否显示摄像头提示
autoLockCarbooleantrue是否自动锁车
autoChangeZoombooleantrue是否自动缩放地图
trafficLayerEnabledbooleantrue是否显示交通路况
realCrossDisplaybooleantrue是否显示路口放大图
naviModenumber0视角模式:0=车头朝上, 1=正北朝上
showModenumber1显示模式:1=锁车态, 2=全览态, 3=普通态
isNightModebooleanfalse是否开启夜间模式

onNaviStart - 导航开始

typescript
onNaviStart={(e) => {
  const { type } = e.nativeEvent;
  console.log(`导航开始,类型: ${type === 1 ? '模拟' : 'GPS'}`);
}}

onNaviEnd - 导航结束

typescript
onNaviEnd={(e) => {
  const { reason } = e.nativeEvent;
  console.log(`导航结束: ${reason}`);
}}

onArrive - 到达目的地

typescript
onArrive={(e) => {
  console.log('已到达目的地');
}}

onNaviInfoUpdate - 实时导航信息

typescript
onNaviInfoUpdate={(e) => {
  const {
    pathRetainDistance,  // 剩余距离(米)
    pathRetainTime,      // 剩余时间(秒)
    currentRoadName,     // 当前道路名称
    nextRoadName,        // 下一道路名称
    currentSpeed,        // 当前速度(米/秒)
    iconType,            // 转向图标类型
    iconDirection,       // 转向方向
  } = e.nativeEvent;
}}

onCalculateRouteSuccess - 路径规划成功

typescript
onCalculateRouteSuccess={(e) => {
  const { routeIds } = e.nativeEvent;
  console.log('路径规划成功');
}}

onCalculateRouteFailure - 路径规划失败

typescript
onCalculateRouteFailure={(e) => {
  const { error, errorCode } = e.nativeEvent;
  console.error(`规划失败: ${error}`);
}}

完整示例

typescript
import React, { useRef, useState } from 'react';
import { View, Button, Text, StyleSheet } from 'react-native';
import { NaviView, type NaviViewRef } from 'expo-gaode-map-navigation';

export default function FullNavigationScreen() {
  const naviViewRef = useRef<NaviViewRef>(null);
  const [naviInfo, setNaviInfo] = useState({
    distance: 0,
    time: 0,
    road: '',
  });

  const startNavigation = async () => {
    await naviViewRef.current?.startNavigation(
      null, // 使用当前位置
      { latitude: 39.91, longitude: 116.41 },
      0 // GPS导航
    );
  };

  return (
    <View style={{ flex: 1 }}>
      <NaviView
        ref={naviViewRef}
        style={{ flex: 1 }}
        naviType={0}
        enableVoice={true}
        showCamera={true}
        trafficLayerEnabled={true}
        onNaviStart={() => console.log('导航开始')}
        onNaviEnd={(e) => console.log('导航结束:', e.nativeEvent.reason)}
        onArrive={() => console.log('到达目的地')}
        onNaviInfoUpdate={(e) => {
          const { pathRetainDistance, pathRetainTime, currentRoadName } =
            e.nativeEvent;
          setNaviInfo({
            distance: pathRetainDistance,
            time: pathRetainTime,
            road: currentRoadName,
          });
        }}
        onCalculateRouteFailure={(e) => {
          console.error('路径规划失败:', e.nativeEvent.error);
        }}
      />

      <View style={styles.infoPanel}>
        <Text style={styles.infoText}>
          剩余: {(naviInfo.distance / 1000).toFixed(2)}公里
        </Text>
        <Text style={styles.infoText}>
          时间: {Math.floor(naviInfo.time / 60)}分钟
        </Text>
        <Text style={styles.infoText}>当前道路: {naviInfo.road}</Text>
      </View>

      <View style={styles.buttonPanel}>
        <Button title="开始导航" onPress={startNavigation} />
        <Button
          title="停止导航"
          onPress={() => naviViewRef.current?.stopNavigation()}
          color="red"
        />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  infoPanel: {
    position: 'absolute',
    top: 20,
    left: 20,
    right: 20,
    backgroundColor: 'rgba(255, 255, 255, 0.9)',
    padding: 15,
    borderRadius: 10,
  },
  infoText: {
    fontSize: 16,
    marginVertical: 2,
  },
  buttonPanel: {
    position: 'absolute',
    bottom: 20,
    left: 20,
    right: 20,
  },
});

地图功能

导航包内置了完整的地图功能,可以直接使用 MapView 和覆盖物组件:

typescript
import { MapView, Marker, Polyline } from 'expo-gaode-map-navigation';

export default function MapScreen() {
  return (
    <MapView
      style={{ flex: 1 }}
      initialCameraPosition={{
        target: { latitude: 39.9, longitude: 116.4 },
        zoom: 12,
      }}
    >
      <Marker
        position={{ latitude: 39.9, longitude: 116.4 }}
        title="起点"
      />
      
      <Polyline
        points={[
          { latitude: 39.9, longitude: 116.4 },
          { latitude: 39.91, longitude: 116.41 },
        ]}
        strokeColor="#FF0000"
        strokeWidth={5}
      />
    </MapView>
  );
}

更多地图功能请参考 地图 API 文档

常见问题

1. 与 expo-gaode-map 冲突

问题:同时安装导致编译错误。

解决

bash
npm uninstall expo-gaode-map
npm install expo-gaode-map-navigation

2. "Another route calculation is in progress"

问题:连续规划路径时报错。

解决:页面卸载时清理资源。

typescript
import { destroyAllCalculators } from 'expo-gaode-map-navigation';

useEffect(() => {
  return () => {
    destroyAllCalculators();
  };
}, []);

3. NaviView 启动导航无反应

问题:startNavigation 调用后没有效果。

解决

  • 确保视图已渲染(使用 setTimeout 延迟)
  • 检查坐标是否正确
  • 查看 onCalculateRouteFailure 错误信息
typescript
setShowNavi(true);
setTimeout(async () => {
  await naviViewRef.current?.startNavigation(start, end, 1);
}, 500);

4. 如何使用当前位置作为起点

typescript
// 方式1:传 null
await naviViewRef.current?.startNavigation(
  null,  // 使用当前位置
  destination,
  0
);

// 方式2:先获取当前位置
const location = await ExpoGaodeMapModule.getCurrentLocation();
await naviViewRef.current?.startNavigation(
  { latitude: location.latitude, longitude: location.longitude },
  destination,
  0
);

5. 路径规划失败怎么办

检查以下几点:

  • API Key 是否正确配置
  • 起终点坐标是否有效
  • 网络连接是否正常
  • 查看错误信息:
typescript
onCalculateRouteFailure={(e) => {
  const { error, errorCode } = e.nativeEvent;
  console.error(`错误代码: ${errorCode}, 错误信息: ${error}`);
}}

6. 如何自定义导航界面

NaviView 提供了丰富的属性来控制界面元素:

typescript
<NaviView
  showCamera={false}          // 隐藏摄像头提示
  showMode={3}                // 普通态(可自由操作地图)
  trafficLayerEnabled={false} // 隐藏路况
/>

相关文档

Released under the MIT License.