App Inventor 2 地图与电子围栏教程 - LocationSensor与地图组件

« 返回首页

概述

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 实时更新位置和路径
文档反馈