方案背景
我们经常遇到一类终端设备模糊定位场景,这就是怎么在已知的有限的条件下,尽可能精确的获取终端的地理位置。以下是一些典型的场景:
- 在社交领域,在评论区需要显示用户大致的IP属地。
- 在IoT领域,大部分的IoT device没有GPS定位模块,我们只能获取到device的一些基础的联网信息,比如设备的公网IP、设备所连接的WiFi和设备附近的WiFi信息,我们需要这些最基本的信息尽量精确的感知设备的位置。
- 在安防领域,某个通过蜂窝网络(5G/4G、NB-IoT)连接到云端的环境感知设备(成本有限没有GPS模块);比如森林火灾传感器,野外空气质量检测器等,我们需要获取比较精确的位置来做地图标记。
- 在电商领域,需要在保护用户隐私的情况下收集app端在各个地区的匿名埋点数据,然后通过数据分析来大致了解区域性用户的画像和购物习惯。
架构设计
模糊定位功能适用于终端设备没有GPS模块或者用户不能主动提供准确地理位置的情况尽可能到获取位置信息。定位是操作是用户的敏感操作,我们需要在合法并取得用户同意的情况下进行;定位功能的核心是有准确而庞大的地理位置数据库,这对开发者而言是最难的地方,一般的开源/付费地理位置数据库方案往往会有不同程度的数据老旧和数据量不足的问题;面对这类问题我们可以使用AWS IoT Core Device Location功能来完成。AWS IoT Core Device Location通过与Semtech、HERE和MaxMind等AWS合作伙伴提供的解决方案集成,使客户能够使用云辅助GNSS、WiFi 扫描、蜂窝三角测量和IP反向查找技术来确定设备的地理坐标。
以下是被支持的几种定位方式和定位的原理,我们可以使用其中的一种或者多种方式组合来尽可能精确的实现模糊定位。
测量类型 | 第三方求解器 | 支持的设备 |
---|---|---|
Wi-Fi 接入点 | 基于 Wi-Fi 的求解器 | 一般 IoT 设备和 LoRa WAN 设备 |
蜂窝无线电发射塔:GSM、LTE、CDMA、SCDMA、WCMDA 和 TD-SCDMA 数据 | 基于蜂窝的求解器 | 一般 IoT 设备和 LoRa WAN 设备 |
IP 地址 | IP 反向查找求解器 | 一般 IoT 设备 |
GNSS 扫描数据(导航消息) | GNSS 求解器 | 一般 IoT 设备和 LoRa WAN 设备 |
模糊定位请求的QPS往往不可预测,有明显的时间周期性和突发性。比如网络社交网站上的一个热点新闻引发大量的讨论,评论区的评论需要大量的IP模糊定位、智能制造企业的给某个地域范围内的数万老旧设备进行定向OTA升级,而不影响其他地域的设备等。相比长期预置大量的服务器等待不可预知的事件发生,AWS Lambda作为计算层有明显的优势,首先是它避免了大量资源闲置造成的成本浪费,其次就是它可以由这些不可预知的事件触发执行,并自动的根据事件的规模进行近乎无限的即时扩展,在两位数毫秒的超短时间内做出响应,在数分钟内快速完成这批事件的处理后停止计费,兼顾了弹性和成本。
接下来我们假设2类典型定位场景:
- 移动用户所使用的手机没有授权定位权限(只知道IP)无法通过GPS获取准确的位置,实现基于IP模糊定位的功能;
- 一个硬件设备通过WiFi连接网络但是没有GPS模块(只知道WiFi信息),实现基于WiFi扫描的迷糊定位功能。
希望通过这2种场景帮助大家了解手机/桌面端、低成本硬件设备的模糊定位的可行性和无服务器架构的优势。
代码实现和定位测试
相关的代码实现可以参考GitHub location-service
处理HTTPS或者MQTT 定位请求
我们可以使用AWS Lambda运行模糊定位代码,并通过API-gateway暴露RESTFul HTTPS接口
首先我们假设3种接口
- 通过HTTPS请求根据IP查询查询终端设备的位置
- 通过HTTPS请求根据WiFi信息查询终端设备的位置
- 通过MQTT消息根据WiFi信息查询终端设备的位置
首先我们使用AWS SAM编排我们的lambda代码,代码样例如下,如果大家有不熟悉SAM的请看AWS SAM Doc
基于ip的模糊定位
r"""This module provides a function to get the geolocation of an IP address."""import boto3# Create an IoT Wireless clientclient = boto3.client('iotwireless')def lambda_handler(event, context):# get ip address from path parameteripv4_address = event["pathParameters"]["ipv4-address"]# get the position estimate by the ip addressresponse = client.get_position_estimate(Ip={'IpAddress': ipv4_address},)# get the geojson payload from the responseipv4_location = response["GeoJsonPayload"].read().decode('utf-8')# return the geojson payloadreturn {"statusCode": 200,"body": ipv4_location,}r""" This module provides a function to get the geolocation of an IP address. """ import boto3 # Create an IoT Wireless client client = boto3.client('iotwireless') def lambda_handler(event, context): # get ip address from path parameter ipv4_address = event["pathParameters"]["ipv4-address"] # get the position estimate by the ip address response = client.get_position_estimate( Ip={ 'IpAddress': ipv4_address }, ) # get the geojson payload from the response ipv4_location = response["GeoJsonPayload"].read().decode('utf-8') # return the geojson payload return { "statusCode": 200, "body": ipv4_location, }r""" This module provides a function to get the geolocation of an IP address. """ import boto3 # Create an IoT Wireless client client = boto3.client('iotwireless') def lambda_handler(event, context): # get ip address from path parameter ipv4_address = event["pathParameters"]["ipv4-address"] # get the position estimate by the ip address response = client.get_position_estimate( Ip={ 'IpAddress': ipv4_address }, ) # get the geojson payload from the response ipv4_location = response["GeoJsonPayload"].read().decode('utf-8') # return the geojson payload return { "statusCode": 200, "body": ipv4_location, }
根据设备所连接的WiFi信息模糊定位(核心代码)
def lambda_handler(event, context):# get the wifi info from the eventwifi_info = event["queryStringParameters"]# get the position estimateresponse = client.get_position_estimate(WiFiAccessPoints=[{'MacAddress': wifi_info["MacAddress"],'Rss': int(wifi_info["Rss"])},],)# get the position estimatewifi_location = response["GeoJsonPayload"].read().decode('utf-8')# return the position estimatereturn {"statusCode": 200,"body": wifi_location,}def lambda_handler(event, context): # get the wifi info from the event wifi_info = event["queryStringParameters"] # get the position estimate response = client.get_position_estimate( WiFiAccessPoints=[ { 'MacAddress': wifi_info["MacAddress"], 'Rss': int(wifi_info["Rss"]) }, ], ) # get the position estimate wifi_location = response["GeoJsonPayload"].read().decode('utf-8') # return the position estimate return { "statusCode": 200, "body": wifi_location, }def lambda_handler(event, context): # get the wifi info from the event wifi_info = event["queryStringParameters"] # get the position estimate response = client.get_position_estimate( WiFiAccessPoints=[ { 'MacAddress': wifi_info["MacAddress"], 'Rss': int(wifi_info["Rss"]) }, ], ) # get the position estimate wifi_location = response["GeoJsonPayload"].read().decode('utf-8') # return the position estimate return { "statusCode": 200, "body": wifi_location, }
根据设备MQTT消息定位(核心代码)
def lambda_handler(event, context):# get the position estimate by the eventresponse = client.get_position_estimate(**event)# get the geojson payload from the responselocation = response["GeoJsonPayload"].read().decode('utf-8')# return the geojson payloadreturn {"statusCode": 200,"body": location,}def lambda_handler(event, context): # get the position estimate by the event response = client.get_position_estimate(**event) # get the geojson payload from the response location = response["GeoJsonPayload"].read().decode('utf-8') # return the geojson payload return { "statusCode": 200, "body": location, }def lambda_handler(event, context): # get the position estimate by the event response = client.get_position_estimate(**event) # get the geojson payload from the response location = response["GeoJsonPayload"].read().decode('utf-8') # return the geojson payload return { "statusCode": 200, "body": location, }
定位测试
当我们部署好后,会类似的HTTP输出
我们可以用postman进行大致的测试,首先是IP定位测试,对于正确的公网IP都能获取到比较准确的位置(如果是局域网IP则会查询不到)
然后是wifi mac地址定位,我们可以先获取到自己电脑连接到WiFi的mac地址和信号强度,然后再去进行位置解析,注意:只有WiFi的位置已经被收录到数据库,才可以进行位置解析。
总结
我们可以看到对于移动/桌面用户我们可以先获取的其IP地址或者通过解析HTTP请求头获取IP,然后通过IP地址反向查找的方式,大致将位置精确到某个国家某个省份下的具体城市;对于低成本的硬件设备我们可以让其自己上报自己周围的WiFi信息,大致将位置定位到某个国家某个省份某个城市下的具体的街道。在这个架构中对开发者而言可以不用担心地理位置数据库的运维问题,也不用担心计算资源的闲置浪费、突发流量冲垮服务的问题。
此外我们也可以后面进一步的完善架构或者功能整合,比如加入ElastiCache缓存,开启API Gateway缓存避免重复请求造成的成本上升并提升服务质量;我们把终端的位置信息保存到DynamoDB等NoSQL数据库,然后进一步结合地图服务,实现用户/设备热力图、环境监控看板、异常感知看板等。