资讯详情

资讯详情

IoT-For-Beginners 交通项目第14课实战:Azure Maps 地理围栏的 GeoJSON 定义、点测试与无服务器 GPS 触发器

IoT-For-Beginners 交通项目第14课实战Azure Maps 地理围栏的 GeoJSON 定义、点测试与无服务器 GPS 触发器【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners本文基于 IoT-For-Beginners 仓库中交通项目Transport的最后一课——地理围栏Geofences课程展开原文档位于 translations/ar/3-transport/lessons/4-geofences/README.md英文原版见 3-transport/lessons/4-geofences/README.md。前 3 课你已经用 IoT 采集卡车 GPS 数据、上云存储并可视化到地图上本课进一步教你用 GeoJSON 多边形定义地理围栏并上传 Azure Maps、通过 Spatial API 判断 GPS 坐标在围栏内还是外再为 Azure Functions 应用创建独立消费者组的新触发器让服务端代码实时判断车辆是否驶入加工中心从而在卡车到达前提前通知装卸团队做好准备。读完并动手完成全部任务后你将掌握围栏上传—点位测试—无服务器消费 GPS 事件的完整链路。应用场景与本课目标在运输项目的前 3 课中你用 IoT 定位了把农产品从农场运往加工中心processing hub的卡车采集 GPS 数据、发送到云端存储、并在地图上展示。提高供应链效率的下一步是在卡车即将到达加工中心时收到告警让装卸团队在车辆到达前就用好叉车等设备快速卸货避免为等待的卡车和司机白白付费。地理围栏正是解决这一问题的工具它可以是一段行驶范围内例如距加工中心两分钟车程的地理区域通过测试 GPS 坐标是否在围栏内/外你就能知道 GPS 传感器是否到达或离开了某区域。本课覆盖 4 个主题什么是地理围栏用 GeoJSON 定义地理围栏测试点与地理围栏的相对关系从服务器端Serverless代码中使用地理围栏⚠️ 这是运输项目的最后一课。完成本课和配套作业通过 Twilio 发送通知之后别忘了清理你的云服务。由于完成作业还需要这些服务请务必先完成作业再清理可参考 clean-up.md 中的清理指南。什么是地理围栏地理围栏是真实世界地理区域的虚拟边界。它可以是圆形由一个中心点和半径定义例如环绕某栋建筑 100 米的圆多边形覆盖一片区域例如学区、城市边界、大学或办公园区。你可能早已用过地理围栏而不自知如果曾用 iOS 提醒事项或 Google Keep 按位置设置过提醒App 就会基于该位置创建一个围栏并在你的手机进入围栏时提醒你。在运输/物流场景中判断车辆是否在围栏内/外有很多实际用途为卸货做准备收到车辆到达的通知后装卸团队可以提前就位缩短车辆等待时间司机一天内能完成更多配送。税务合规部分国家如新西兰只对柴油车辆在公共道路上行驶按车重征收道路税。用地理围栏可以区分车辆在公共道路与农场、林区等私有道路上分别行驶了多少里程。防盗监控如果车辆本应停留在农场等特定区域内一旦离开围栏就可能已被盗。位置合规工地、农场或工厂的部分区域对特定车辆禁入例如运送化肥和农药的车辆必须远离种植有机作物的田地。车辆一旦驶入围栏即视为违规可以立即通知司机。Azure Maps——你在上一课用来可视化 GPS 数据的服务——允许你定义地理围栏然后测试某个点是否在其内部。用 GeoJSON 多边形定义地理围栏地理围栏使用 GeoJSON 定义与上一课添加到地图上的点Point格式相同。区别在于这里不再是包含Point值的FeatureCollection而是包含Polygon的FeatureCollection{ type: FeatureCollection, features: [ { type: Feature, geometry: { type: Polygon, coordinates: [ [ [ -122.13393688201903, 47.63829579223815 ], [ -122.13389128446579, 47.63782047131512 ], [ -122.13240802288054, 47.63783312249837 ], [ -122.13238388299942, 47.63829037035086 ], [ -122.13393688201903, 47.63829579223815 ] ] ] }, properties: { geometryId: 1 } } ] }理解这份结构时有三个关键点坐标顺序多边形上的每个点都是经度、纬度longitude, latitude对组成二维数组整体再作为coordinates的值。上一课的Point的coordinates是一个含两个值的数组而Polygon的coordinates是数组的数组。请注意 GeoJSON 约定使用longitude, latitude而不是latitude, longitude。闭合规则coordinates永远比多边形的顶点数多一个条目——最后一个条目必须与第一个相同用来闭合多边形。例如矩形有 4 个角但需要 5 个点。geometryId 必填properties元素与geometry位于同一层级必须包含geometryIdproperties: { geometryId: 1 }以一个矩形为例多边形从左上角 47,-122 开始向右到 47,-121向下到 46,-121向左到 46,-122再回到起点 47,-122 闭合——共 5 个点左上、右上、右下、左下、再回到左上。练习尝试用 GeoJSON.io 这类工具在家或学校周围画一个多边形亲手生成一份 GeoJSON。任务一将地理围栏上传到 Azure Maps要在 Azure Maps 中使用围栏必须先把它上传到你的 Azure Maps 账户。上传成功后会获得一个唯一 IDUDID之后用它来测试点位。上传需要使用 Azure Maps 的 Web API而调用它可以用curl这个命令行工具如果你更熟悉 Postman也可以用 Postman 替代。第 1 步确认 curl 可用Linux、macOS 或较新的 Windows 10 通常已预装 curl在终端运行curl --version如果没输出版本信息需要从 curl 官方下载页安装。第 2 步创建包含多边形的 GeoJSON 文件这个多边形稍后会与你的 GPS 传感器一起测试所以请围绕你当前的位置画多边形。可以手工编辑上面的示例 GeoJSON也可以用 GeoJSON.io 绘制。文件必须满足顶层是FeatureCollection其中包含Feature其geometry类型为PolygonFeature带有与geometry同级的properties元素且包含geometryId。使用 GeoJSON.io 时需要手动把geometryId加进properties空对象中可以在应用内的 JSON 编辑器或下载 JSON 文件后编辑。geometryId在同一个文件内必须唯一你可以在同一个 GeoJSON 文件中上传多个围栏作为多个Feature只要各自的geometryId不同。不同文件在不同时间上传的多边形可以使用相同的geometryId。第 3 步保存为geofence.json并在终端中cd到该文件所在目录。第 4 步用 curl 的 POST 请求上传围栏curl --request POST https://atlas.microsoft.com/mapData/upload?api-version1.0dataFormatgeojsonsubscription-keysubscription_key \ --header Content-Type: application/json \ --include \ --data geofence.json将 URL 中的subscription_key替换为你的 Azure Maps 账户 API 密钥。URL 说明上传走https://atlas.microsoft.com/mapData/upload端点api-version参数指定使用哪个版本的 Azure Maps API以便服务随时间演进并保持向后兼容dataFormatgeojson指定上传数据的格式。这个 POST 请求会返回一个带头部列表的响应其中包含一个名为location的头content-type: application/json location: https://us.atlas.microsoft.com/mapData/operations/1560ced6-3a80-46f2-84b2-5b1531820eab?api-version1.0 x-ms-azuremaps-region: West US 2 x-content-type-options: nosniff strict-transport-security: max-age31536000; includeSubDomains x-cache: CONFIG_NOCACHE date: Sat, 22 May 2021 21:34:57 GMT content-length: 0小知识调用 Web 端点时可以在 URL 中加?后跟keyvalue形式的键值对传参多个参数之间用分隔。第 5 步轮询上传状态Azure Maps 不会立即处理该请求需要用location头中的 URL 轮询。注意要在该 URL 末尾追加subscription-keysubscription_key同样替换为你的 API 密钥curl --request GET locationsubscription-keysubscription_key其中location换成响应头中location的实际值。第 6 步检查status字段如果返回的status不是Succeeded等待约 1 分钟再重试。第 7 步提取 UDID当状态变为Succeeded后查看响应中的resourceLocation其中包含该 GeoJSON 对象的唯一标识UDID。UDID 是metadata/之后的那一段值不包含api-version。例如{ resourceLocation: https://us.atlas.microsoft.com/mapData/metadata/7c3776eb-da87-4c52-ae83-caadf980323a?api-version1.0 }则 UDID 为7c3776eb-da87-4c52-ae83-caadf980323a。请把它保存下来后面测试围栏时要用。测试点与地理围栏上传完成后你可以传入围栏 UDID 和待测点的经纬度发起一个 Web API 请求来判断点在内还是在外。searchBuffer 与 distance 的语义请求中还可以传一个searchBuffer参数决定 API 返回结果的精度范围。之所以需要它是因为 GPS 并不完全精确位置有时会有数米甚至更大的偏差。searchBuffer默认 50 米可设置为 0 到 500 米之间的值。响应中与判定相关的是distance字段它表示待测点到围栏边缘最近点的距离点在外为正点在内为负如果该距离小于searchBuffer返回的是实际距离米否则返回哨兵值999 表示点在围栏外且超出 searchBuffer 范围-999 表示点在围栏内且深入超过 searchBuffer 范围。以默认 50 米搜索区为例有四类典型情况位于围栏中心、深入搜索区内的点distance -999远在搜索区之外的点distance 999在围栏内、且距围栏 6 米的点distance -66 米负号表内在在围栏外、且距围栏 39 米的点distance 39distance 的意义知道到围栏边缘的距离后你可以把它与 GPS 历史读数、速度、道路数据等信息结合再对车辆位置做决策。例如想象 GPS 读数显示车辆正行驶在一条紧邻围栏的公路上。如果某一次读数不精确把车辆放进了围栏——尽管车辆根本没有入口可进——这个异常读数就可以被合理忽略。上图中的地理围栏覆盖了 Microsoft 园区的一部分红线显示卡车沿 520 公路行驶圆点为 GPS 读数大多数读数都落在公路上只有一个不精确读数落进了围栏。由于卡车不可能从 520 突然拐进园区又拐回来这个读数不可能是真的。因此检查围栏的代码在依据围栏测试结果采取行动前必须参考此前的读数。思考一下你还应该核对哪些额外数据如速度、时间连续性来判断一条 GPS 读数是否可信任务二用 curl 测试点第 1 步构造查询 URL格式为https://atlas.microsoft.com/spatial/geofence/json?api-version1.0deviceIdgps-sensorsubscription-keysubscription-keyudidUDIDlatlatlonlonsubscription-key替换为你的 Azure Maps API 密钥UDID替换为任务一中得到的围栏 UDIDlat与lon替换为待测点的纬度和经度。该 URL 使用https://atlas.microsoft.com/spatial/geofence/json端点查询用 GeoJSON 定义的围栏API 版本为1.0。参数deviceId是必填项应为发出该经纬度的设备名称。默认搜索半径 50 米可追加searchBufferdistance参数修改distance为 0 到 500 的米数。第 2 步用 curl 发起 GET 请求curl --request GET URL如果收到BadRequest响应且错误信息为Invalid GeoJSON: All feature properties should contain a geometryId, which is used for identifying the geofence.说明你的 GeoJSON 文件缺少含geometryId的properties段。修复文件后需要重新执行前面的上传步骤获取新的 UDID。第 3 步解读响应。响应包含geometries数组其中每个对象对应 GeoJSON 中定义的一个多边形每个对象有 3 个关键字段{ geometries: [ { deviceId: gps-sensor, udId: 7c3776eb-da87-4c52-ae83-caadf980323a, geometryId: 1, distance: 999.0, nearestLat: 47.645875, nearestLon: -122.142713 } ], expiredGeofenceGeometryId: [], invalidPeriodGeofenceGeometryId: [] }nearestLat与nearestLon围栏边缘上距待测点最近的点的纬度与经度distance待测点到围栏边缘最近点的距离负数表示在围栏内正数表示在外其值要么小于 50默认搜索距离要么为哨兵值 999对应 -999。第 4 步用围栏内、外的多个坐标点重复上述过程验证不同判定结果。从服务器端代码使用地理围栏现在可以把围栏判定加入你的 Functions 应用新增一个触发器把 IoT Hub 的 GPS 事件与围栏做实时比对。消费者组Consumer Groups回顾前面的课程IoT Hub 允许重放已接收但未处理的事件。但如果多个触发器同时连接它们如何知道哪个事件已经被处理过答案是它们不知道。正确的做法是为每个消费者定义一条独立的连接各自独立管理未读消息的重放——这就是消费者组。连接端点时可以指定要连接的消费者组应用中的每个组件连接一个不同的消费者组。理论上每个消费者组最多可有 5 个应用连接它们都能在消息到达时收到该消息。但最佳实践是每个应用只连接一个消费者组以避免重复处理消息并保证重放时所有挂起消息都能被正确处理。例如如果你的 Functions 应用同时在本地和云端运行两边都会处理消息导致存储账户中出现重复的 Blob。检查你在之前课程创建的 IoT Hub 触发器的function.jsonEvent Hub 绑定部分就有消费者组配置consumerGroup: $Default创建 IoT Hub 时会默认创建$Default消费者组。如果要添加新触发器可以为其创建并使用一个新的消费者组。 本课中围栏测试与 GPS 数据存储使用两个不同的 Function目的是演示消费者组的用法并分离代码使其更易读易理解。生产环境中设计方式很多——两个逻辑放同一个 Function、用存储账户上的触发器驱动围栏检查、或用多个 Function——没有唯一正确方式取决于你的应用整体设计。任务三创建新的消费者组第 1 步为你的 IoT Hub 创建名为geofence的消费者组az iot hub consumer-group create --name geofence \ --hub-name hub_name把hub_name替换为你的 IoT Hub 名称。第 2 步可选列出该 IoT Hub 的所有消费者组az iot hub consumer-group list --output table \ --hub-name hub_name输出类似Name ResourceGroup -------- --------------- $Default gps-sensor geofence gps-sensor 之前课程运行 IoT Hub 事件监视器时它连接的是$Default消费者组——所以监视器与事件触发器无法同时运行。若希望两者并存可以让所有 Functions 应用改用其他消费者组把$Default留给事件监视器。任务四创建新的 IoT Hub 触发器第 1 步在之前课程创建的gps-triggerFunctions 应用中新增一个 IoT Hub 事件触发器命名为geofence-trigger。若忘记操作细节可回到 2-farm 项目第 5 课 中创建 IoT Hub 事件触发器的说明。第 2 步在function.json中配置 IoT Hub 连接串。注意local.settings.json是所有触发器共享的IOT_HUB_CONNECTION_STRING已存在于其中。第 3 步把新触发器function.json中的consumerGroup更新为新的geofenceconsumerGroup: geofence第 4 步该触发器需要使用 Azure Maps 的订阅密钥因此在local.settings.json中新增一项MAPS_KEY。第 5 步运行 Functions 应用确认它成功连接并处理消息。此时上一课的iot-hub-trigger也会同时运行、继续把 Blob 写入存储。为避免本地和云端重复读取 GPS 数据可以先把云端已部署的 Functions 应用停掉az functionapp stop --resource-group gps-sensor \ --name functions_app_name把functions_app_name替换为你的 Functions 应用名。之后可用如下命令重新启动az functionapp start --resource-group gps-sensor \ --name functions_app_name任务五在触发器中测试地理围栏第 1 步把任务一得到的围栏 UDID 以GEOFENCE_UDID为键加入local.settings.json。第 2 步打开新触发器geofence-trigger的__init__.py在文件顶部添加导入import json import os import requestsrequests库用于发起 API 调用——Azure Maps 没有 Python SDK所以从 Python 代码中使用它只能走 API 调用。第 3 步在main方法开头添加两行从环境变量读取配置maps_key os.environ[MAPS_KEY] geofence_udid os.environ[GEOFENCE_UDID]第 4 步在for event in events循环内从每个事件中提取经纬度event_body json.loads(event.get_body().decode(utf-8)) lat event_body[gps][lat] lon event_body[gps][lon]该代码把事件体的 JSON 解析为字典再从gps字段取出lat与lon。第 5 步使用requests时不必像 curl 那样拼接长 URL只需给出 URL 主体并把参数作为字典传入url https://atlas.microsoft.com/spatial/geofence/json params { api-version: 1.0, deviceId: gps-sensor, subscription-key: maps_key, udid : geofence_udid, lat : lat, lon : lon }params字典中的键值对与 curl 调用时 URL 中的参数一一对应。第 6 步发起 API 调用response requests.get(url, paramsparams) response_body json.loads(response.text)第 7 步解析distance并记录日志distance response_body[geometries][0][distance] if distance 999: logging.info(Point is outside geofence) elif distance 0: logging.info(fPoint is just outside geofence by a distance of {distance}m) elif distance -999: logging.info(fPoint is inside geofence) else: logging.info(fPoint is just inside geofence by a distance of {distance}m)这段代码假设只有 1 个多边形直接取唯一多边形的distance然后按 4 种情况输出不同日志远在外999、紧邻外围0 distance、深入内-999、紧邻内部distance 0。第 8 步运行代码在日志中观察 GPS 坐标是进入还是离开围栏若在 50 米搜索范围内还会看到具体米数。可以换一个基于 GPS 传感器位置划定的围栏再移动传感器例如让它跟随手机 WiFi 位置或在 IoT 虚拟设备上喂入不同坐标观察日志变化。第 9 步准备就绪后把代码发布到云端的 Functions 应用——别忘了同时发布新的应用设置。发布设置与部署应用的步骤可参考 2-farm 项目第 5 课 中的说明。完整的示例代码可以在 3-transport/lessons/4-geofences/code/functions 目录中找到。源码级实现解析两个触发器如何共享 IoT Hub仓库中本课程的完整示例代码位于code/functions/gps-trigger从源码结构可以清晰地看到消费者组隔离这一设计的落地方式。围栏触发器geofence-trigger/function.json 声明了一个eventHubTrigger绑定eventHubName为samples-workitemscardinality为many批量消费关键在于 第 11 行consumerGroup: geofence存储触发器作为对照iot-hub-trigger/function.json 连接的是同一个 Event Hub同为samples-workitems但消费者组是$Default。从源码结构看两个触发器并行订阅同一个 IoT Hub 事件流却互不干扰地各自维护消费位置——这正是课程强调的每个组件连接不同消费者组的实现证据。围栏判定逻辑geofence-trigger/init.py 与课程任务代码完全一致main函数先从环境变量读取MAPS_KEY与GEOFENCE_UDIDL10-L11在事件循环中解析出lat/lonL13-L16组装params字典L20-L27发起requests.get调用L29-L30最后按 4 个分支记录日志L32-L41。配置与依赖local.settings.json 中同时定义了MAPS_KEY和GEOFENCE_UDID两个新增应用设置requirements.txt 声明了azure-functions与azure-storage-blob两个依赖——后者服务于iot-hub-trigger从 iot-hub-trigger/init.py 的源码可以看到它把每条 GPS 事件按{device_id}/{uuid}.json的 Blob 名称写入gps-data容器。从源码结构看存储侧与围栏侧正是同一个事件流上的两个独立消费者职责单一、互不阻塞。挑战多个多边形围栏本课的围栏文件只含一个多边形。事实上只要不同多边形在properties中带有不同的geometryId就可以在同一份 GeoJSON 中同时上传多个多边形。挑战任务上传一份含多个多边形的 GeoJSON 文件并修改你的代码判断 GPS 坐标最靠近或位于哪一个多边形——提示响应中geometries数组会包含每个多边形的distance与nearestLat/nearestLon可用于逐一比较。小结本课完成了运输项目的最后一环用 GeoJSONPolygon定义地理围栏注意longitude, latitude顺序与闭合点规则通过mapData/upload端点上传并以location头轮询状态获取 UDID用spatial/geofence/json端点结合searchBuffer默认 50 米0–500 米可调与distance正负及 ±999 哨兵值判定点位关系最后借助独立消费者组geofence与新增的geofence-trigger函数把围栏判定接入实时 GPS 事件流。完成配套作业通过 Twilio 发送通知后请按 clean-up.md 清理本项目的全部云资源。【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →