18 KiB
18 KiB
AMap Flutter Search - 高德地图搜索插件
模块概述
amap_flutter_search 是 OneApp 中基于高德地图 SDK 的搜索服务插件,提供了全面的地理信息搜索功能,包括 POI 搜索、路线规划、地址解析等服务。该插件为车辆导航、位置查找、路径规划等功能提供了强大的搜索能力支持。
核心功能
1. POI 搜索
- 关键词搜索:基于关键词的兴趣点搜索
- 周边搜索:指定位置周边的 POI 搜索
- 分类搜索:按类别筛选的 POI 搜索
- 详情查询:获取 POI 的详细信息
2. 路线规划
- 驾车路线:多种驾车路径规划策略
- 步行路线:行人路径规划
- 骑行路线:骑行路径规划
- 公交路线:公共交通路径规划
- 货车路线:货车专用路径规划
3. 地址解析
- 地理编码:地址转换为坐标
- 逆地理编码:坐标转换为地址
- 输入提示:地址输入智能提示
- 行政区查询:行政区域信息查询
4. 距离计算
- 直线距离:两点间直线距离计算
- 驾车距离:实际驾车距离和时间
- 步行距离:步行距离和时间
- 批量计算:批量距离矩阵计算
技术架构
架构设计
┌─────────────────────────────────────┐
│ 应用层 │
│ (app_navigation, app_car) │
├─────────────────────────────────────┤
│ AMap Flutter Search │
│ ┌──────────┬──────────┬──────────┐ │
│ │ POI搜索 │ 路线规划 │ 地址解析 │ │
│ ├──────────┼──────────┼──────────┤ │
│ │ 距离计算 │ 缓存管理 │ 数据处理 │ │
│ └──────────┴──────────┴──────────┘ │
├─────────────────────────────────────┤
│ 高德地图 Search SDK │
│ ┌──────────┬──────────┬──────────┐ │
│ │ Android │ iOS │ 搜索引擎 │ │
│ └──────────┴──────────┴──────────┘ │
├─────────────────────────────────────┤
│ 网络服务层 │
│ (REST API, WebService) │
└─────────────────────────────────────┘
核心组件
1. POI 搜索管理器 (PoiSearchManager)
class AMapPoiSearchManager {
// 关键词搜索
Future<PoiResult> searchByKeyword(PoiKeywordSearchOption option);
// 周边搜索
Future<PoiResult> searchNearby(PoiNearbySearchOption option);
// ID 搜索
Future<PoiItem> searchById(String poiId);
// 分类搜索
Future<PoiResult> searchByCategory(PoiCategorySearchOption option);
}
2. 路线规划管理器 (RouteSearchManager)
class AMapRouteSearchManager {
// 驾车路线规划
Future<DriveRouteResult> calculateDriveRoute(DriveRouteQuery query);
// 步行路线规划
Future<WalkRouteResult> calculateWalkRoute(WalkRouteQuery query);
// 骑行路线规划
Future<RideRouteResult> calculateRideRoute(RideRouteQuery query);
// 公交路线规划
Future<BusRouteResult> calculateBusRoute(BusRouteQuery query);
// 货车路线规划
Future<TruckRouteResult> calculateTruckRoute(TruckRouteQuery query);
}
3. 地理编码管理器 (GeocodeManager)
class AMapGeocodeManager {
// 地理编码
Future<GeocodeResult> geocode(GeocodeQuery query);
// 逆地理编码
Future<RegeocodeResult> reverseGeocode(RegeocodeQuery query);
// 输入提示
Future<List<TipItem>> inputTips(InputTipsQuery query);
// 行政区查询
Future<DistrictResult> searchDistrict(DistrictQuery query);
}
4. 距离计算器 (DistanceCalculator)
class AMapDistanceCalculator {
// 计算两点距离
Future<DistanceResult> calculateDistance(DistanceQuery query);
// 批量距离计算
Future<List<DistanceResult>> calculateDistances(List<DistanceQuery> queries);
// 路径距离计算
Future<RouteDistanceResult> calculateRouteDistance(RouteDistanceQuery query);
}
数据模型
POI 模型
class PoiItem {
final String poiId; // POI ID
final String title; // POI 名称
final String snippet; // POI 描述
final LatLng latLng; // POI 坐标
final String distance; // 距离
final String tel; // 电话
final String postcode; // 邮编
final String website; // 网站
final String email; // 邮箱
final String province; // 省份
final String city; // 城市
final String district; // 区县
final String address; // 地址
final String direction; // 方向
final String businessArea; // 商圈
final String typeCode; // 类型编码
final String typeDes; // 类型描述
final List<Photo> photos; // 照片列表
final List<String> shopID; // 商铺ID
final IndoorData indoorData; // 室内数据
}
class PoiResult {
final List<PoiItem> pois;
final int pageCount;
final int totalCount;
final SearchBound searchBound;
final PoiQuery query;
}
路线模型
class DriveRouteResult {
final List<DrivePath> paths;
final RouteQuery startPos;
final RouteQuery targetPos;
final List<RouteQuery> viaPoints;
final int taxiCost;
}
class DrivePath {
final String strategy; // 策略
final int distance; // 距离(米)
final int duration; // 时长(秒)
final String tolls; // 过路费
final String tollDistance; // 收费距离
final String totalTrafficLights; // 红绿灯数
final List<DriveStep> steps; // 路径段
final List<TMC> tmcs; // 路况信息
}
class DriveStep {
final String instruction; // 行驶指示
final String orientation; // 方向
final String road; // 道路名称
final int distance; // 距离
final int duration; // 时长
final List<LatLng> polyline; // 路径坐标
final String action; // 导航动作
final String assistantAction; // 辅助动作
}
地址模型
class RegeocodeResult {
final RegeocodeAddress regeocodeAddress;
final List<PoiItem> pois;
final List<Road> roads;
final List<RoadInter> roadInters;
final List<AoiItem> aois;
}
class RegeocodeAddress {
final String formattedAddress; // 格式化地址
final String country; // 国家
final String province; // 省份
final String city; // 城市
final String district; // 区县
final String township; // 乡镇
final String neighborhood; // 社区
final String building; // 建筑
final String adcode; // 区域编码
final String citycode; // 城市编码
final List<StreetNumber> streetNumbers; // 门牌信息
}
API 接口
POI 搜索接口
abstract class PoiSearchService {
// 关键词搜索
Future<ApiResponse<PoiResult>> searchByKeyword(PoiKeywordSearchRequest request);
// 周边搜索
Future<ApiResponse<PoiResult>> searchNearby(PoiNearbySearchRequest request);
// 多边形搜索
Future<ApiResponse<PoiResult>> searchInPolygon(PoiPolygonSearchRequest request);
// POI 详情
Future<ApiResponse<PoiItem>> getPoiDetail(String poiId);
}
路线规划接口
abstract class RouteSearchService {
// 驾车路线规划
Future<ApiResponse<DriveRouteResult>> planDriveRoute(DriveRouteRequest request);
// 步行路线规划
Future<ApiResponse<WalkRouteResult>> planWalkRoute(WalkRouteRequest request);
// 骑行路线规划
Future<ApiResponse<RideRouteResult>> planRideRoute(RideRouteRequest request);
// 公交路线规划
Future<ApiResponse<BusRouteResult>> planBusRoute(BusRouteRequest request);
}
地理编码接口
abstract class GeocodeService {
// 地理编码
Future<ApiResponse<GeocodeResult>> geocode(GeocodeRequest request);
// 逆地理编码
Future<ApiResponse<RegeocodeResult>> reverseGeocode(RegeocodeRequest request);
// 输入提示
Future<ApiResponse<List<TipItem>>> getInputTips(InputTipsRequest request);
// 行政区搜索
Future<ApiResponse<DistrictResult>> searchDistrict(DistrictRequest request);
}
配置管理
搜索配置
class SearchConfig {
final String apiKey; // API 密钥
final int timeoutMs; // 超时时间
final int maxRetries; // 最大重试次数
final bool enableCache; // 是否启用缓存
final int cacheExpireTime; // 缓存过期时间
final String language; // 语言设置
final String region; // 区域设置
static const SearchConfig defaultConfig = SearchConfig(
apiKey: '',
timeoutMs: 30000,
maxRetries: 3,
enableCache: true,
cacheExpireTime: 3600,
language: 'zh-CN',
region: 'CN',
);
}
POI 搜索配置
class PoiSearchConfig {
final int pageSize; // 每页结果数
final int maxPage; // 最大页数
final String city; // 搜索城市
final String types; // 搜索类型
final bool extensions; // 是否返回扩展信息
final String sortrule; // 排序规则
static const PoiSearchConfig defaultConfig = PoiSearchConfig(
pageSize: 20,
maxPage: 100,
city: '',
types: '',
extensions: false,
sortrule: 'distance',
);
}
使用示例
POI 搜索示例
// 关键词搜索
final searchOption = PoiKeywordSearchOption(
keyword: '加油站',
city: '北京',
pageSize: 20,
pageNum: 1,
extensions: true,
);
final poiResult = await AMapPoiSearchManager.instance.searchByKeyword(searchOption);
for (final poi in poiResult.pois) {
print('POI 名称: ${poi.title}');
print('POI 地址: ${poi.address}');
print('POI 距离: ${poi.distance}');
print('POI 坐标: ${poi.latLng.latitude}, ${poi.latLng.longitude}');
}
周边搜索示例
// 周边搜索
final nearbyOption = PoiNearbySearchOption(
keyword: '餐厅',
center: LatLng(39.9042, 116.4074), // 搜索中心点
radius: 1000, // 搜索半径 1000 米
pageSize: 10,
pageNum: 1,
);
final nearbyResult = await AMapPoiSearchManager.instance.searchNearby(nearbyOption);
print('附近餐厅数量: ${nearbyResult.pois.length}');
for (final restaurant in nearbyResult.pois) {
print('餐厅: ${restaurant.title} - ${restaurant.distance}');
}
路线规划示例
// 驾车路线规划
final driveQuery = DriveRouteQuery(
fromPoint: RouteQuery(39.9042, 116.4074), // 起点
toPoint: RouteQuery(39.9015, 116.3974), // 终点
mode: DriveMode.fastest, // 最快路线
avoidhighspeed: false, // 不避开高速
avoidtolls: false, // 不避开收费
avoidtrafficjam: true, // 避开拥堵
);
final driveResult = await AMapRouteSearchManager.instance.calculateDriveRoute(driveQuery);
if (driveResult.paths.isNotEmpty) {
final path = driveResult.paths.first;
print('驾车距离: ${path.distance} 米');
print('预计时间: ${path.duration} 秒');
print('过路费: ${path.tolls} 元');
for (final step in path.steps) {
print('导航指示: ${step.instruction}');
print('道路: ${step.road}');
print('距离: ${step.distance} 米');
}
}
地理编码示例
// 地址转坐标
final geocodeQuery = GeocodeQuery(
locationName: '北京市朝阳区望京SOHO',
city: '北京',
);
final geocodeResult = await AMapGeocodeManager.instance.geocode(geocodeQuery);
if (geocodeResult.geocodeAddressList.isNotEmpty) {
final address = geocodeResult.geocodeAddressList.first;
print('地址坐标: ${address.latLonPoint.latitude}, ${address.latLonPoint.longitude}');
}
// 坐标转地址
final regeocodeQuery = RegeocodeQuery(
latLonPoint: LatLng(39.9915, 116.4684),
radius: 200.0,
extensions: 'all',
);
final regeocodeResult = await AMapGeocodeManager.instance.reverseGeocode(regeocodeQuery);
print('格式化地址: ${regeocodeResult.regeocodeAddress.formattedAddress}');
输入提示示例
// 地址输入提示
final tipsQuery = InputTipsQuery(
keyword: '王府井',
city: '北京',
datatype: 'all',
);
final tipsList = await AMapGeocodeManager.instance.inputTips(tipsQuery);
for (final tip in tipsList) {
print('提示: ${tip.name}');
print('地址: ${tip.address}');
if (tip.point != null) {
print('坐标: ${tip.point!.latitude}, ${tip.point!.longitude}');
}
}
测试策略
单元测试
group('AMapSearch Tests', () {
test('should search POI by keyword', () async {
// Given
final searchOption = PoiKeywordSearchOption(
keyword: '测试POI',
city: '北京',
);
// When
final result = await AMapPoiSearchManager.instance.searchByKeyword(searchOption);
// Then
expect(result.pois, isNotEmpty);
expect(result.pois.first.title, contains('测试POI'));
});
test('should calculate drive route', () async {
// Given
final query = DriveRouteQuery(
fromPoint: RouteQuery(39.9042, 116.4074),
toPoint: RouteQuery(39.9015, 116.3974),
);
// When
final result = await AMapRouteSearchManager.instance.calculateDriveRoute(query);
// Then
expect(result.paths, isNotEmpty);
expect(result.paths.first.distance, greaterThan(0));
});
});
集成测试
group('Search Integration Tests', () {
testWidgets('complete search flow', (tester) async {
// 1. 搜索 POI
final pois = await searchNearbyPois('加油站', currentLocation);
// 2. 选择目标 POI
final targetPoi = pois.first;
// 3. 规划路线
final route = await planRouteToDestination(currentLocation, targetPoi.latLng);
// 4. 验证结果
expect(route.paths, isNotEmpty);
expect(route.paths.first.steps, isNotEmpty);
});
});
性能优化
搜索优化
- 搜索缓存:缓存常用搜索结果
- 预测搜索:基于历史预测用户搜索
- 分页加载:大结果集分页加载
- 搜索建议:智能搜索建议减少请求
路线优化
- 路线缓存:缓存常用路线规划结果
- 增量更新:只更新路线变化部分
- 压缩传输:压缩路线数据减少传输量
- 多线程处理:并行处理多个路线请求
错误处理
搜索错误类型
enum SearchErrorType {
networkError, // 网络错误
apiKeyInvalid, // API Key 无效
quotaExceeded, // 配额超限
invalidParameter, // 参数无效
noResult, // 无搜索结果
serverError, // 服务器错误
timeout // 请求超时
}
class SearchException implements Exception {
final SearchErrorType type;
final String message;
final int? errorCode;
const SearchException(this.type, this.message, [this.errorCode]);
}
错误处理示例
try {
final result = await AMapPoiSearchManager.instance.searchByKeyword(searchOption);
// 处理搜索结果
} on SearchException catch (e) {
switch (e.type) {
case SearchErrorType.networkError:
showErrorDialog('网络连接失败,请检查网络设置');
break;
case SearchErrorType.noResult:
showInfoDialog('未找到相关结果,请尝试其他关键词');
break;
case SearchErrorType.quotaExceeded:
showErrorDialog('搜索次数已达上限,请稍后再试');
break;
default:
showErrorDialog('搜索失败: ${e.message}');
}
} catch (e) {
showErrorDialog('未知错误: $e');
}
缓存策略
搜索结果缓存
class SearchCache {
static const int maxCacheSize = 1000;
static const Duration cacheExpiration = Duration(hours: 1);
// 缓存 POI 搜索结果
static Future<void> cachePoiResult(String key, PoiResult result);
// 获取缓存的 POI 结果
static Future<PoiResult?> getCachedPoiResult(String key);
// 缓存路线规划结果
static Future<void> cacheRouteResult(String key, DriveRouteResult result);
// 获取缓存的路线结果
static Future<DriveRouteResult?> getCachedRouteResult(String key);
// 清理过期缓存
static Future<void> cleanExpiredCache();
}
配额管理
API 配额监控
class QuotaManager {
// 获取当前配额使用情况
static Future<QuotaInfo> getCurrentQuota();
// 检查是否可以发起请求
static Future<bool> canMakeRequest(RequestType type);
// 记录请求使用
static Future<void> recordRequest(RequestType type);
// 获取配额重置时间
static Future<DateTime> getQuotaResetTime();
}
class QuotaInfo {
final int dailyLimit; // 日限额
final int dailyUsed; // 日使用量
final int monthlyLimit; // 月限额
final int monthlyUsed; // 月使用量
final DateTime resetTime; // 重置时间
}
版本历史
v3.1.22+1 (当前版本)
- 支持最新的高德地图 API
- 新增货车路线规划功能
- 优化搜索性能和准确性
- 修复路线规划稳定性问题
v3.1.21
- 支持室内 POI 搜索
- 新增批量距离计算
- 优化内存使用
- 改进错误处理机制
依赖关系
内部依赖
amap_flutter_base: 高德地图基础库basic_network: 网络请求服务basic_storage: 本地存储服务
外部依赖
dio: HTTP 客户端cached_network_image: 图片缓存sqflite: 本地数据库
总结
amap_flutter_search 作为高德地图搜索服务的 Flutter 插件,为 OneApp 提供了全面的地理信息搜索能力。通过集成 POI 搜索、路线规划、地理编码、距离计算等功能,该插件能够满足车辆导航、位置服务、路径规划等多种业务需求。
插件在设计上充分考虑了性能优化、缓存管理、错误处理、配额控制等关键因素,确保在提供高质量搜索服务的同时,保持良好的用户体验和系统稳定性。这为 OneApp 的车联网和导航服务提供了强大的技术支撑。