概述
App Inventor 2内置了 Map 组件(基于OpenStreetMap),可以实现:
- 地图显示和交互
- 地图标记(Marker)
- 绘制线和多边形
- 电子围栏(地理围栏)
- 路径追踪
Map组件位于组件面板的 Maps 分类下。
Map组件核心属性
| 属性 | 类型 | 说明 |
|---|---|---|
| CenterFromString | text | 地图中心点(”纬度,经度”格式) |
| ZoomLevel | number | 缩放级别(1-18,越大越详细) |
| MapType | number | 地图类型(1=道路图,2=卫星图) |
| ShowCompass | boolean | 显示指南针 |
| ShowUser | boolean | 显示用户位置 |
| ShowZoom | boolean | 显示缩放按钮 |
| EnableZoom | boolean | 允许缩放手势 |
| LocationSensor | component | 关联的位置传感器 |
基本地图使用
显示地图并定位到指定位置
when Screen1.Initialize() {
Map1.CenterFromString = "39.9042,116.4074" // 北京天安门
Map1.ZoomLevel = 15
Map1.ShowUser = True
}
关联LocationSensor自动定位
when Screen1.Initialize() {
Map1.LocationSensor = LocationSensor1 // 关联传感器
LocationSensor1.Enabled = True
Map1.ShowUser = True
}
when LocationSensor1.LocationChanged(latitude, longitude, altitude, speed) {
// 地图自动跟随用户位置
Map1.CenterFromString = join(latitude, ",", longitude)
}
地图标记(Marker)
添加标记
when Button1.Click() {
Map1.CreateMarker(39.9042, 116.4074, "天安门", "北京市中心", True)
}
动态添加多个标记
global placesList = createList(createDictionary(createPair("name", "天安门"), createPair("lat", 39.9042), createPair("lng", 116.4074)))
when Screen1.Initialize() {
var i = 1
while i <= lengthOfList(global_placesList) {
var place = selectListItem(global_placesList, i)
Map1.CreateMarker(getDictionaryValue(place, "lat"), getDictionaryValue(place, "lng"), getDictionaryValue(place, "name"), "", True)
i = i + 1
}
}
标记点击事件
when Map1.MarkerClick(marker) {
marker.ShowInfoWindow()
Label1.Text = join("选中:", marker.Title, "(", marker.Latitude, ", ", marker.Longitude, ")")
}
绘制线条和路径
绘制两点间的线
when Button2.Click() {
Map1.AddLineString(createList(global_startLat, global_endLat), createList(global_startLng, global_endLng), True, 3)
}
路径追踪
记录用户移动轨迹并在地图上绘制:
global latList = createList()
global lngList = createList()
when LocationSensor1.LocationChanged(latitude, longitude, altitude, speed) {
// 只在移动时记录(速度>0.5m/s)
if speed > 0.5 {
addItemsToList(global_latList, latitude)
addItemsToList(global_lngList, longitude)
// 更新路径线
if lengthOfList(global_latList) > 1 {
Map1.ClearLineStrings() // Placeholder
Map1.AddLineString(global_latList, global_lngList, True, 4)
}
}
}
电子围栏(Geofencing)
电子围栏是在地图上划定的虚拟边界,当用户进入或离开该区域时触发事件。
原理
使用 LocationSensor 持续获取位置,计算与围栏中心的距离,判断是否在围栏内。
计算两点间距离(Haversine公式)
function calculateDistance(lat1, lon1, lat2, lon2) {
var R = 6371000 // 地球平均半径(米)
var dLat = (lat2 - lat1) * 3.14159 / 180
var dLon = (lon2 - lon1) * 3.14159 / 180
var a = sin(dLat / 2) * sin(dLat / 2) + cos(lat1 * 3.14159 / 180) * cos(lat2 * 3.14159 / 180) * sin(dLon / 2) * sin(dLon / 2)
var c = 2 * atan2(sqrt(a), sqrt(1 - a))
return R * c
}
实现电子围栏
global fenceCenterLat = 39.9042
global fenceCenterLng = 116.4074
global fenceRadius = 500 // 500米
global inFence = False
when LocationSensor1.LocationChanged(latitude, longitude, altitude, speed) {
var distance = calculateDistance(latitude, longitude, global_fenceCenterLat, global_fenceCenterLng)
if distance <= global_fenceRadius {
if not(global_inFence) {
global_inFence = True
Notifier1.ShowMessageDialog(join("你已进入目标区域(距离中心", formatAsDecimal(distance, 0), "米)"), "📍 进入围栏", "确定")
}
} else {
if global_inFence {
global_inFence = False
Notifier1.ShowMessageDialog(join("你已离开目标区域(距离中心", formatAsDecimal(distance, 0), "米)"), "🚶 离开围栏", "确定")
}
}
Label2.Text = join("距离围栏中心:", formatAsDecimal(distance, 0), "米")
}
在地图上显示围栏
when Screen2.Initialize() {
// 画一个圆形围栏(用多边形近似)
var latPoints = createList()
var lngPoints = createList()
var i = 0
while i < 360 {
var angle = i * 3.14159 / 180
// 纬度偏移
var dLat = global_fenceRadius / 111320 // 1度纬度约111.32km
var dLon = global_fenceRadius / (111320 * cos(global_fenceCenterLat * 3.14159 / 180))
var ptLat = global_fenceCenterLat + dLat * cos(angle)
var ptLng = global_fenceCenterLng + dLon * sin(angle)
addItemsToList(latPoints, ptLat)
addItemsToList(lngPoints, ptLng)
i = i + 10 // 每10度一个点
}
Map1.AddPolygon(latPoints, lngPoints, True, True, 2)
}
实战案例:儿童安全围栏
功能需求
- 在地图上设定安全区域
- 实时监控儿童位置
- 离开安全区域时家长收到通知
- 显示儿童移动轨迹
积木块代码
global safeCenterLat = 39.9042
global safeCenterLng = 116.4074
global safeRadius = 300 // 300米
global parentPhone = "13800138000"
global isSafe = True
when Screen3.Initialize() {
LocationSensor1.Enabled = True
Map1.CenterFromString = join(global_safeCenterLat, ",", global_safeCenterLng)
Map1.ZoomLevel = 16
// 在地图上画安全区域
drawSafeZone()
// 添加安全区域中心标记
Map1.CreateMarker(global_safeCenterLat, global_safeCenterLng, "🏠 家", "安全区域中心", True)
}
when LocationSensor1.LocationChanged(latitude, longitude, altitude, speed) {
var distance = calculateDistance(latitude, longitude, global_safeCenterLat, global_safeCenterLng)
Label1.Text = join("距离家:", formatAsDecimal(distance, 0), "米")
// 更新儿童位置标记
Map1.CenterFromString = join(latitude, ",", longitude)
if distance > global_safeRadius {
if global_isSafe {
global_isSafe = False
Label1.TextColor = True // simplified
// 发送短信通知家长
Texting1.SendMessage(global_parentPhone, join("⚠️ 儿童已离开安全区域!当前位置:", latitude, ",", longitude))
}
} else {
if not(global_isSafe) {
global_isSafe = True
Label1.TextColor = True // simplified
Texting1.SendMessage(global_parentPhone, "✅ 儿童已回到安全区域")
}
}
}
常见问题
Q1: Map组件显示空白?
- 确保有网络连接(Map使用OpenStreetMap在线瓦片)
- 检查CenterFromString格式是否正确(”纬度,经度”,注意纬度在前)
- 确认ZoomLevel设置合理(建议10-16)
Q2: 位置更新不频繁?
- LocationSensor的
DistanceInterval设小一些(如1米) - 但注意频繁更新会增加耗电
Q3: 如何计算两个GPS点之间的距离?
使用上面提供的Haversine公式计算函数。短距离(<1km)精度足够。
Q4: 电子围栏后台运行?
App Inventor应用在后台时传感器会被系统暂停。解决方案:
- 设置
TimerAlwaysFires = true - 使用前台服务(需要扩展)
- 或接受App Inventor的限制,只在App前台运行时监控
总结
| 功能 | 组件 | 方法 |
|---|---|---|
| 地图显示 | Map | 设置中心点和缩放级别 |
| 定位 | LocationSensor | LocationChanged事件 |
| 标记 | Map.CreateMarker | 添加图标和信息窗口 |
| 路径绘制 | Map.AddPolyline | 纬度/经度列表 |
| 电子围栏 | LocationSensor + 数学 | Haversine距离计算 |
| 位置追踪 | LocationSensor + Map | 实时更新位置和路径 |
扫码添加客服咨询