Android物流地图核心技术:Google Maps SDK应用
本文讲述Google Maps SDK在物流地图中的能力应用,解析地图电子围栏、客户端地址搜索、(不同国家国际化)多级行政区划解析不同等关键功能的实现方案
一、Google Maps SDK能力全景图
物流地图核心SDK组件
SDK模块 | 功能 | 物流应用场景 |
---|---|---|
Maps SDK | 基础地图渲染 | 网点位置展示、轨迹绘制 |
Places SDK | 地点搜索与详情 | 智能地址解析、四级行政区匹配 |
Maps Utils | 地理工具库 | 围栏判断、距离计算 |
Directions API | 路径规划 | 配送路线优化(需网络请求) |
配置关键依赖
gradle
dependencies {
// 地图渲染核心
implementation 'com.google.android.gms:play-services-maps:18.1.0'
// 地点搜索与自动补全
implementation 'com.google.android.libraries.places:places:3.1.0'
// 地理计算工具库
implementation 'com.google.maps.android:android-maps-utils:2.3.0'
// 定位服务
implementation 'com.google.android.gms:play-services-location:20.0.0'
}
二、电子围栏核心技术实现
1. 多边形围栏绘制
kotlin
// 创建多边形选项
val polygonOptions = PolygonOptions().apply {
strokeColor("#FF865C".toColorInt()) // 边框颜色
strokeWidth(5f) // 边框宽度(像素)
fillColor(0x33FF4504) // 填充颜色(带透明度)
}
// 添加顶点坐标
fencePoints.forEach { point ->
polygonOptions.add(LatLng(point.latitude, point.longitude))
}
// 添加到地图
val deliveryPolygon = map.addPolygon(polygonOptions)
2. 位置围栏关系判断
kotlin
// 判断目标位置是否在围栏内
val isInside = PolyUtil.containsLocation(
targetLocation, // 目标位置(LatLng)
deliveryPolygon.points, // 围栏顶点列表
true // 考虑地球曲率
)
// 可视化结果
if (isInside) {
addMarker(targetLocation, R.drawable.ic_in_fence, "在配送范围内")
} else {
addMarker(targetLocation, R.drawable.ic_out_fence, "超出配送范围")
}
3. 围栏边界优化技巧
问题: 国际日期变更线附近显示异常
解决方案:
kotlin
// 经度归一化处理
private fun normalizeLongitude(lng: Double): Double {
return when {
lng > 180 -> lng - 360
lng < -180 -> lng + 360
else -> lng
}
}
三、智能地址搜索与解析
1. Places SDK初始化
java
public class AddressSearchFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
// 根据应用语言初始化
Locale appLocale = LocalManageUtil.getSetLanguageLocale(getContext());
Places.initialize(requireContext(), "API_KEY", appLocale);
// 创建Places客户端
placesClient = Places.createClient(requireContext());
}
}
2. 地址预测与自动补全
java
private void fetchPredictions(String query) {
// 构建请求
FindAutocompletePredictionsRequest request =
FindAutocompletePredictionsRequest.builder()
.setQuery(query)
.setSessionToken(AutocompleteSessionToken.newInstance())
.setOrigin(currentLocation) // 基于当前位置优化结果
.build();
// 执行搜索
placesClient.findAutocompletePredictions(request)
.addOnSuccessListener(response -> {
List<AutocompletePrediction> predictions = response.autocompletePredictions;
updateResultsUI(predictions);
})
.addOnFailureListener(exception -> {
// 错误处理
});
}
3. 多级行政区划解析
java
private PlaceDetails parsePlaceDetails(Place place) {
PlaceDetails details = new PlaceDetails();
// 获取国家代码
String countryCode = getCountryCode(place);
// 解析地址组件
for (AddressComponent component : place.getAddressComponents().asList()) {
for (String type : component.getTypes()) {
switch (type) {
case "administrative_area_level_1": // 省
details.province = component.getName();
break;
case "administrative_area_level_2": // 市
details.city = component.getName();
break;
case "administrative_area_level_3": // 区
details.district = component.getName();
break;
case "administrative_area_level_4": // 镇
case "sublocality_level_1":
details.town = component.getName();
break;
}
}
}
// 获取坐标
details.latLng = place.getLatLng();
return details;
}
四、自定义标记与信息窗口
1. 自定义标记视图
kotlin
private fun addCustomMarker(position: LatLng, @LayoutRes layoutId: Int, label: String) {
// 加载布局
val markerView = LayoutInflater.from(this).inflate(layoutId, null).apply {
findViewById<TextView>(R.id.marker_label).text = label
}
// 测量布局
markerView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
markerView.layout(0, 0, markerView.measuredWidth, markerView.measuredHeight)
// 转换为Bitmap
val bitmap = Bitmap.createBitmap(
markerView.measuredWidth,
markerView.measuredHeight,
Bitmap.Config.ARGB_8888
).apply {
val canvas = Canvas(this)
markerView.draw(canvas)
}
// 添加标记
map.addMarker(MarkerOptions()
.position(position)
.icon(BitmapDescriptorFactory.fromBitmap(bitmap))
.anchor(0.5f, 1.0f) // 锚点在底部中心
}
2. 自定义信息窗口
java
public class CustomInfoWindowAdapter implements GoogleMap.InfoWindowAdapter {
private final View mWindow;
public CustomInfoWindowAdapter(Context context) {
mWindow = LayoutInflater.from(context).inflate(R.layout.custom_info_window, null);
}
@Override
public View getInfoWindow(Marker marker) {
render(marker, mWindow);
return mWindow;
}
private void render(Marker marker, View view) {
TextView title = view.findViewById(R.id.title);
TextView snippet = view.findViewById(R.id.snippet);
title.setText(marker.getTitle());
snippet.setText(marker.getSnippet());
}
}
// 使用适配器
map.setInfoWindowAdapter(new CustomInfoWindowAdapter(this));
五、高级地图功能应用
1. 地图事件处理
kotlin
// 标记点击事件
override fun onMarkerClick(marker: Marker): Boolean {
// 显示信息窗口
marker.showInfoWindow()
// 移动到标记位置
map.animateCamera(CameraUpdateFactory.newLatLngZoom(marker.position, 15f))
return true // 阻止默认行为
}
// 地图长按事件
map.setOnMapLongClickListener { latLng ->
// 添加临时标记
addMarker(latLng, R.drawable.ic_temp_marker, "自定义位置")
}
2. 地图视角控制
kotlin
// 包含所有标记的视野
val bounds = LatLngBounds.Builder().apply {
markers.forEach { include(it.position) }
}.build()
// 添加50dp的边距
val padding = resources.getDimensionPixelSize(R.dimen.map_padding)
map.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, padding))
// 平滑移动到指定位置
map.animateCamera(CameraUpdateFactory.newLatLngZoom(targetLocation, 17f), 1000, null)
3. 离线地图支持(高级功能)
java
// 定义离线区域
OfflineTileProvider tileProvider = new OfflineTileProvider(
context,
new TileSourceFactory() {
@Override
public TileSource getTileSource() {
return TileSourceFactory.DEFAULT_TILE_SOURCE;
}
},
offlineRegion
);
// 设置离线瓦片
map.setTileOverlay(new TileOverlayOptions()
.tileProvider(tileProvider)
.zIndex(1));
六、性能优化实战
1. 标记聚合优化
java
// 初始化集群管理器
ClusterManager<MyClusterItem> clusterManager = new ClusterManager<>(this, map);
// 添加标记
for (Location location : locations) {
MyClusterItem item = new MyClusterItem(
location.getLatLng(),
location.getName()
);
clusterManager.addItem(item);
}
// 设置集群渲染器
clusterManager.setRenderer(new CustomClusterRenderer(this, map, clusterManager));
// 绑定地图事件
map.setOnCameraIdleListener(clusterManager);
map.setOnMarkerClickListener(clusterManager);
2. 地图生命周期管理
kotlin
override fun onResume() {
super.onResume()
mapView.onResume() // 恢复地图
}
override fun onPause() {
super.onPause()
mapView.onPause() // 暂停地图
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy() // 销毁地图资源
// 清理标记
markers.forEach { it.remove() }
map.clear()
}
3. 内存优化策略
kotlin
// 按缩放级别显示不同内容
map.setOnCameraMoveListener {
val zoomLevel = map.cameraPosition.zoom
// 低缩放级别显示聚合标记
if (zoomLevel < 10) {
showClusterMarkers()
}
// 中等缩放级别显示网点标记
else if (zoomLevel < 14) {
showNetworkMarkers()
}
// 高缩放级别显示详细标记
else {
showDetailMarkers()
}
}
七、总结与最佳实践
Google Maps SDK核心能力矩阵
能力 | 实现方法 | 物流应用价值 |
---|---|---|
地理围栏 | PolyUtil.containsLocation() | 配送范围可视化与判断 |
地址解析 | Places SDK地址组件 | 四级行政区自动匹配 |
标记聚合 | ClusterManager | 高性能网点展示 |
离线地图 | OfflineTileProvider | 弱网环境可用性 |
视角控制 | CameraUpdateFactory | 最佳区域展示 |
最佳实践建议
-
合理使用API配额
- Places API每日限额1500次
- 使用本地缓存减少API调用
- 重要操作添加重试机制
-
性能优化关键点
graph TD A[地图初始化] --> B[标记聚合] A --> C[按需加载] A --> D[生命周期管理] B --> E[低缩放级别] C --> F[高缩放级别] D --> G[及时释放资源] -
多语言适配策略
kotlin// 初始化时设置语言 Places.initialize(context, apiKey, Locale("id")) // 印尼语 // 地址解析结果匹配应用语言 override fun attachBaseContext(newBase: Context?) { super.attachBaseContext(LocalManageUtil.setLocal(newBase)) }
-
异常处理关键点
kotlintry { // 地图操作 } catch (e: SecurityException) { // 权限异常处理 } catch (e: ApiException) { // API调用异常 when (e.statusCode) { PlacesStatusCodes.NOT_FOUND -> // 地点未找到 PlacesStatusCodes.OVER_QUERY_LIMIT -> // 配额超限 } } catch (e: IOException) { // 网络异常 }