在 Flutter App 中使用 GPS 定位

现代手机上,不论是苹果 iPhone 还是安卓 Android,都配备了强大的定位能力。

定位主要通过卫星和地面基站提供的信号,获得不同精度的定位信息。

通过手机的操作系统,可以获取这些定位信息。这是手机操作系统给应用层提供的能力。

在 Flutter App 中,我们可以调用手机的定位信息。

手机定位

隐私权限

我对安卓不太熟悉,但是在苹果手机上,定位是一项重要的隐私权限,如果你要通过 App 获得用户的定位,必须在 App 运行时取得用户的显式授权。否则,通过接口将无法获得定位的信息。

此外,用户仍然需要在使用 App 的时候,打开系统的定位服务功能开关,否则仍然无法获得用户当前的定位信息。

如果要在苹果 App 中使用定位权限,在开发的时候,需要在 info.plist 中配置很多的权限项,才能在不同的版本的操作系统中,正确获取定位信息。太过冗长,请参考《高德SDK:权限配置》。

安卓手机的权限配置,比苹果还要复杂,主要是因为安卓手机的操作系统和硬件的分化更加离开,给开发者带来了很大的困扰。《权限配置》

大厂的定位 SDK

我们在手机 App 的开发过程中,都会使用大厂的 SDK,比如百度地图,或者高德地图的 SDK,这些厂商都会提供 Flutter 的 SDK。

上次我撰文骂过这件事情,这些大厂都商量好了,开始对 SDK 进行收费,价格不菲。那么手机本来就带有 GPS 定位功能,为什么我们不直接使用操作系统的 API 获取定位信息,而是去使用大厂的 SDK 呢?

我想,可能有这么几方面的原因:

第一,操作系统差异。苹果手机的不同版本操作系统,获取定位的权限和方式略有不同。如果自己开发的话,需要考虑这些差异,逐一处理。而安卓不同厂商的差距之大,有时候可以认为是天壤之别。对付硬件和软件差异,操作系统差异的麻烦更大。如果使用了大厂的 SDK,等于 SDK 已经封装了这些差异。

第二,坐标标准。我后来才知道,你获得定位信息,经纬度坐标,竟然也是存在不同标准的。至少就有三种标准,国际标准 WGS84,从苹果手机默认获取到的坐标系统,但是在中国,为了安全等因素考量,我们不使用此标准,如果你使用中国的地图信息,则该坐标不能正确定位位置。火星坐标 GCJ-02,这是中国使用的经过混淆的坐标系统。还有百度坐标 BD-09 在 GCJ-02 的基础上,进行二次加密后,得到的坐标系统。如果没有大厂的 SDK,你需要自己去转换这些坐标系统,才能在地图上得到比较准确的定位位置。

我想,这些额外的开发成本,繁琐而且,非常的难以获得对应的更新信息,是每个开发者和小公司无法承担的。因此才会去依赖大厂的 SDK。

我以前就在自己的 App 中使用了高德的 SDK,不过后来被销售威胁付费,不胜其烦。

Flutter 三方包

我最近找到了一个 Flutter 热门的定位包,https://pub.dev/packages/location,看到很多人点 like,还以为会蛮好用,实际上发现太难用了。勉勉强强能用的水平。

Location location = new Location();

bool _serviceEnabled;
PermissionStatus _permissionGranted;
LocationData _locationData;

_serviceEnabled = await location.serviceEnabled();
if (!_serviceEnabled) {
  _serviceEnabled = await location.requestService();
  if (!_serviceEnabled) {
    return;
  }
}

_permissionGranted = await location.hasPermission();
if (_permissionGranted == PermissionStatus.denied) {
  _permissionGranted = await location.requestPermission();
  if (_permissionGranted != PermissionStatus.granted) {
    return;
  }
}

_locationData = await location.getLocation();

这个就是其调用定位信息的代码,看起来还挺简洁的。不过我实际使用的过程中发现,在 iOS 16 上,这个包会导致界面假死。远远不如高德的 SDK 好用。

我运行了这个包官方提供的 excample,发现没有假死现象,这让我百思不得其解。Log 里提示是在主线程运行了一个什么操作,导致假死,但是维护者又说,那个没关系的,我就完全不知所措了。

总之,我的结论是,这个包远没有看起来那么好。

这个包,对中国的 App 不友好的地方在于,其提供的坐标是 WGS84 标准的,如果在中国做举例估算,地区定位,或者地理围栏等,需要将坐标转换成 GCJ-02 标准,这个转换,我找到了一个库:https://github.com/JackZhouCn/JZLocationConverter

不过这个库里只提供了 Obj-C 的版本,我自己翻译了一个 Dart 版本,分享给大家:

class LocationUtils {
  ///用haversine公式计算经纬度两点间的距离,
  ///注意:这里将地球当做了一个正球体来计算距离,当经纬度跨度较大时,有轻微的距离误差
  static double distanceBetween(LatLng latLng1, LatLng latLng2) {
    //经纬度转换成弧度
    double lat1 = _convertDegreesToRadians(latLng1.latitude);
    double lon1 = _convertDegreesToRadians(latLng1.longitude);
    double lat2 = _convertDegreesToRadians(latLng2.latitude);
    double lon2 = _convertDegreesToRadians(latLng2.longitude);
    //差值
    double deltaLat = (lat1 - lat2).abs();
    double deltaLon = (lon1 - lon2).abs();
    //h is the great circle distance in radians, great circle
    //就是一个球体上的切面,它的圆心即是球心的一个周长最大的圆。
    double h = _haverSin(deltaLat) + cos(lat1) * cos(lat2) * _haverSin(deltaLon);
    return (2 * earthRadius * asin(sqrt(h)));
  }

  /// 将角度换算为弧度。
  static double _convertDegreesToRadians(double degrees) {
    return degrees * pi / 180;
  }

  static double _haverSin(double theta) {
    var v = sin(theta / 2);
    return v * v;
  }

  // 假设的中国大陆经纬度范围常量
  static const double chinaLongitudeMin = 72.004; // 示例值,需要根据实际情况调整
  static const double chinaLongitudeMax = 137.8347; // 示例值,需要根据实际情况调整
  static const double chinaLatitudeMin = 0.8293; // 示例值,需要根据实际情况调整
  static const double chinaLatitudeMax = 55.8271; // 示例值,需要根据实际情况调整

  static const double jzA = 6378245.0;
  static const double jzEE = 0.00669342162296594323;

  static bool _outOfChina(double lat, double lon) {
    if (lon < chinaLongitudeMin || lon > chinaLongitudeMax) return true;
    if (lat < chinaLatitudeMin || lat > chinaLatitudeMax) return true;
    return false;
  }

  static double latOffset0(double x, double y) {
    return -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(x.abs());
  }

  static double latOffset1(double x) {
    return (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
  }

  static double latOffset2(double y) {
    return (20.0 * sin(y * pi) + 40.0 * sin(y / 3.0 * pi)) * 2.0 / 3.0;
  }

  static double latOffset3(double y) {
    return (160.0 * sin(y / 12.0 * pi) + 320 * sin(y * pi / 30.0)) * 2.0 / 3.0;
  }

  static double lonOffset0(double x, double y) {
    return 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(x.abs());
  }

  static double lonOffset1(double x) {
    return (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
  }

  static double lonOffset2(double x) {
    return (20.0 * sin(x * pi) + 40.0 * sin(x / 3.0 * pi)) * 2.0 / 3.0;
  }

  static double lonOffset3(double x) {
    return (150.0 * sin(x / 12.0 * pi) + 300.0 * sin(x / 30.0 * pi)) * 2.0 / 3.0;
  }

  static double transformLat(double x, double y) {
    double ret = latOffset0(x, y);
    ret += latOffset1(x); // 假设应该传递x
    ret += latOffset2(y); // 假设应该传递y
    ret += latOffset3(y); // 假设应该传递y
    return ret;
  }

  static double transformLon(double x, double y) {
    double ret = lonOffset0(x, y);
    ret += lonOffset1(x); // 假设应该传递x
    ret += lonOffset2(x); // 假设应该传递x
    ret += lonOffset3(x); // 假设应该传递x
    return ret;
  }

  /// 将WGS84坐标转换为GCJ02坐标
  /// 实现来自 https://github.com/JackZhouCn/JZLocationConverter
  /// 介绍文章:https://blog.csdn.net/ZhengYanFeng1989/article/details/83787998
  static LatLng gcj02Encrypt(LatLng origin) {
    double mgLat;
    double mgLon;
    if (_outOfChina(origin.latitude, origin.longitude)) {
      return LatLng(origin.latitude, origin.longitude);
    }
    double dLat = transformLat(origin.longitude - 105.0, origin.latitude - 35.0);
    double dLon = transformLon(origin.longitude - 105.0, origin.latitude - 35.0);
    double radLat = origin.latitude / 180.0 * pi;
    double magic = sin(radLat);
    magic = 1 - jzEE * magic * magic;
    double sqrtMagic = sqrt(magic);
    dLat = (dLat * 180.0) / ((jzA * (1 - jzEE)) / (magic * sqrtMagic) * pi);
    dLon = (dLon * 180.0) / (jzA / sqrtMagic * cos(radLat) * pi);
    mgLat = origin.latitude + dLat;
    mgLon = origin.longitude + dLon;

    return LatLng(mgLat, mgLon);
  }
}

总结

作为个人开发者,或者小企业的开发者,在手机中使用定位信息,殊为不易。要学习的东西还有很多。虽然看起来是每个手机都有的一个服务,但是在 App 开发中却极难使用。不知大家是什么感想?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/545654.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Udio——革命性的AI音乐生成软件

Udio是一款革命性的AI音乐生成软件&#xff0c;由前谷歌DeepMind的顶尖AI研究人员和工程师共同创立&#xff0c;得到著名风险投资公司a16z的支持。它旨在为音乐爱好者和专业人士提供一个全新的音乐创作和分享平台。用户可以通过文本提示来生成音乐&#xff0c;支持广泛的音乐风…

numpy学习笔记(5),其他实用函数

8. 更多函数 8.1 随机数 8.1.1 常用随机数 8.1.1.1 numpy.random.rand(d0, d1, …, dn) 返回[0.0, 1.0)随机浮点数&#xff0c;即大于等于0.0&#xff0c;小于1.0。d0, d1, …, dn&#xff1a;返回的数组形状 # 使用numpy.random.rand函数 import numpy as np np.random.r…

百元内的运动蓝牙耳机哪个牌子好?五大高分品牌实测推荐

在追求健康生活的当下&#xff0c;运动已成为许多人日常生活的一部分&#xff0c;而音乐更是运动时的最佳伴侣&#xff0c;对于预算有限的学生党或普通消费者来说&#xff0c;如何在百元内挑选到一款性能优越、品质可靠的运动蓝牙耳机&#xff0c;确实是个不小的挑战&#xff0…

(六)C++自制植物大战僵尸游戏关卡数据讲解

植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/xjvbb 游戏关卡数据文件定义了游戏中每一个关卡的数据&#xff0c;包括游戏类型、关卡通关奖励的金币数量、僵尸出现的波数、每一波出现僵尸数量、每一波僵尸出现的类型等。根据不同的游戏类型&#xff0c;定义了不同的通…

ATA-214高压放大器用在哪些实验中使用的

高压放大器在科学实验和工程应用中扮演着关键角色。它是一种能够将低电压信号放大到高电压水平的设备。这种放大器通常用于需要处理高电压信号的实验和应用中。以下是高压放大器在各种实验中的应用范围。 粒子物理实验&#xff1a;在粒子物理实验中&#xff0c;科学家使用高压放…

【分享】3种方法取消Word文档的“打开密码”

我们知道&#xff0c;Word文档可以设置“打开密码”&#xff0c;防止文档被随意打开&#xff0c;那后续不需要密码保护了&#xff0c;要怎么取消呢&#xff1f;不小心把密码忘记了还可以取消吗&#xff1f;不清楚的小伙伴一起来看看吧&#xff01; 如果是Word文档不再需要密码…

Python Flask-Security- 构建安全而强大的Web应用

Flask-Security是一个基于Flask的安全扩展&#xff0c;为开发者提供了构建安全且强大的Web应用的工具。本文将深入探讨Flask- Security的核心功能、基本用法以及在实际应用中的一些高级特性&#xff0c;通过丰富的示例代码&#xff0c;助您更全面地了解和应用这一用于Web应用安…

windows的jar包开机自启动【搬代码】

感觉最方便的就是放到启动项目里操作步骤 winR 输入&#xff1a;shell:startup回车或点击确定 3.将自己jar包右键创建快捷方式 4.然后放进去 5.重启电脑&#xff0c;浏览器输入网址&#xff0c;就可以看到重启成功了 另外一个就是放入.exe文件的快捷方式 首先&#xff0c;…

docker安装nessus服务及使用

Nessus 是目前全世界最多人使用的系统漏洞扫描与分析软件&#xff0c;现在软件服务越来越多&#xff0c;越来越复杂&#xff0c;涉及的数据也更多&#xff1b;因此系统完成后对于系统漏洞的检测并对其进行修改十分有必要&#xff0c;本文介绍通过docker安装nessus服务及简单的使…

15 Python进阶: random和pyecharts

Python random 模块主要用于生成随机数。 random 模块实现了各种分布的伪随机数生成器。 要使用 random 函数必须先导入&#xff1a; import randompython random 模块的一般用法 Python中的random模块提供了生成伪随机数的功能&#xff0c;可以用于模拟、游戏开发、密码学…

关于centos8自带的apache2.4开启https后,XP系统的IE8无法显示网页的问题

经检验&#xff0c;是因为系统的apache和openssl版本太高导致的。 禁用系统默认的apache2.4&#xff0c;自己重新源码编译安装一套openssl-1.0.1fapache2.2.23php7.1.2即可。跟update-crypto-policies没有关系&#xff0c;可保持默认的DEFAULT状态。 关于centos8自带的apache2…

多无人机集群协同避障

matlab2020a正常运行 场景1规划结果 场景2规划结果 场景3规划结果 代码地址&#xff1a; 多无人机集群协同避障效果&#xff08;5架&#xff09;资源-CSDN文库

解锁生成式 AI 的力量:a16z 提供的 16 个企业指南

企业构建和采购生成式AI方面的16项改变 生成式 AI 领域趋势洞察&#xff1a;企业构建和采购生成式 AI 的方式正在发生重大转变&#xff0c;具体表现在&#xff1a;* 专注于可信度和安全性&#xff1a;75% 的企业将信任和安全性视为关键因素。* 优先考虑可扩展性和灵活性&#x…

unity shader学习练笔日记(三)

1、单张纹理 Shader "Unity Shaders Study/Day Three/SingleTexture" {Properties{_Color("Colot Tint", Color) (1, 1, 1, 1)//2D是纹理属性的声明方式。以一个字符串后跟一个花括号作为它的初始值&#xff0c;"white"是内置纹理的名字&#…

内网渗透-域环境的搭建

域环境的搭建 文章目录 域环境的搭建前言一、什么是域环境 什么是域内网基础知识点 二、域环境的搭建 1. 部署域结构2.如何加入域3.SRV出错及解决办法4.SRV记录注册不成功的可能原因 禁用域中的账户将计算机退出域添加域用户总结 前言 一、什么是域环境 什么是域 域是一种管…

5.Pytest自动化测试框架(1)

1.Pytest框架 Pytest框架是Python的一种单元测试框架&#xff0c;与Python自带的unittest框架类似&#xff0c;但比unittest框架更简洁&#xff0c;效率更高 2.设置 3.Pytest命名规范 &#xff08;1&#xff09;Pytest测试文件必须以test_开头&#xff0c;或以_test结尾 &am…

普乐蛙VR神州飞船设备VR太空舱体验馆VR博物馆

中国航天式浪漫知多少&#xff1f;千百年来古人对浩瀚宇宙有着无尽的浪漫想象&#xff0c;而在一代又一代中国航天事业奋斗者的努力中&#xff0c;远古神话不再是幻想&#xff0c;它终被照进现实——中国载人飞船“神舟”、中国载人空间站“天宫”、中国绕月人造卫星“嫦娥一号…

经验分享,京东平台如何实现一键下载商品图片

在京东平台&#xff0c;产品图片制作至关重要。精良的图片能瞬间抓住消费者眼球&#xff0c;激发购买兴趣&#xff0c;它是消费者无法触摸实物情况下对商品的第一感知源&#xff0c;高质量的图片配合适当的图文排版&#xff0c;既有利于提升店铺形象&#xff0c;又能通过视觉传…

通过注解实现接口入参检查

valid 通过注解实现接口入参检查 前言一、引入依赖二、使用步骤1.创建入参对象 request2.提供一个接口 controller3.全局异常捕获 GlobalExceptionHandler4.执行结果 总结 前言 作为一个后端开发&#xff0c;一般是不单独对接口参数的每个入参进行长度、最大值、最小值判断。 …

一款功能齐全的iOS混淆工具介绍及功能详解

机缘巧合偶遇iOS马甲包业务&#xff0c;前期也使用过目前市面上其他得工具&#xff0c;实际效果不太理想。经过大量实践&#xff0c;开发出一款功能齐全的混淆工具。工具的主要功能OC、C、Swift已封装成Mac应用&#xff0c;其他功能还在封装中&#xff0c;敬请期待。 马甲包的本…