# 地图

> MapContainer、图钉、形状、路线与工具 —— 以及关于密钥、null 和线程的陷阱。

Source: https://crossfly.dev/zh/docs/data-and-services/maps — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 地图

基于 `Map` 接口，通过 **`MapContainer`**（一个 `Component`）内嵌的原生地图。支持 Google 和
OSM 作为引擎。

## ⚠️ 避免 bug 的四个陷阱
1. **配置密钥**（静态）应在创建地图之前完成：`MapContainer.googleMapsKey = "...";`。
2. **`map` 可能为 `null`** —— 仅在 `onMapLoad` **之后** 访问 `mc.map`（并处理 `null`：Web/TeaVM
   **不支持** 原生地图）。
3. **所有地图操作都在渲染（绘制）线程上** —— 如果来自后台，请通过 `onUI(...)` 返回。
4. 旧的 `phone.GoogleMaps`（外部查看器）**已被移除**。

## 创建并居中
```java
MapContainer.googleMapsKey = "SUA_CHAVE";

MapContainer mc = new MapContainer(Map.MapEngine.GOOGLE, 0, () -> {   // onMapLoad
   if (mc.map == null) return;                          // Web/TeaVM：无原生地图
   mc.map.centerOnLocation(new LatLon(-23.55, -46.63), 14);
   mc.map.setMapType(Map.LayerType.NORMAL);
});
tela.add(LAY_FILL, mc);
```
`MapEngine`：`GOOGLE` · `OSM`。`LayerType`：`NORMAL` · `TERRAIN` · `HYBRID` · `SATELLITE`。

## 图钉（流式 API）
```java
mc.map.add(new MapPin()
   .position(-23.55, -46.63)
   .title("São Paulo")
   .snippet("Centro")
   .draggable(false)
   .onClick(p -> { Hint.show(p.title, 2000); return true; }));   // true = 隐藏 info window
```

## 形状 —— `MapShape.Line/Polygon/Circle`
```java
// 500 米的圆（颜色为 ARGB 0xAARRGGBB）
MapShape.Circle c = new MapShape.Circle(2, 0xFF2196F3, 0x402196F3);
c.setPolygon(new LatLon(-23.55, -46.63), 500.0);   // 中心 + 半径（米）
mc.map.add(c);
```
`Line(thick, color)` 和 `Polygon(borderWidth, borderColor, fillColor)` 遵循相同的模式。

## 路线与工具
```java
double metros = MapUtils.distanceInMeters(new LatLon(-23.55,-46.63), new LatLon(-22.90,-43.20));
Image thumb = MapUtils.getMapImage(-23.55, -46.63, false, 13, 600, 400);   // 静态图像（出错/无密钥时为 null）
```
真实的路线导航（HTTP + **Jackson**）来自 `DirectionsFetcher`（回调 `onRouteReady(List<LatLon>)`），
并与 `MapRoute(lineShape, pins)` 结合 —— 通过 `onUI(...)` 整理结果。详见
第 25 章。

## 最佳实践
- 在实例化 **之前** 设置密钥；**仅** 在 `onMapLoad` 中访问 `mc.map`。
- 处理 `mc.map == null`（Web）和 `getMapImage(...) == null`。
- 地图操作在渲染线程上；网络结果通过 `onUI(...)`。

## 常见错误
- 在构造函数中访问 `mc.map`（仍为 `null`）。
- 忘记 `MapContainer.googleMapsKey`。
- 使用 `phone.GoogleMaps`（已移除）。

## 另请参阅
- [定位与 GPS](/docs/data-and-services/device-location-gps) · [LocationView](/docs/ui/widgets/locationview)
