在 Flutter App 中使用 GPS 定位

news2024/11/25 2:33:11

现代手机上,不论是苹果 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 开发中却极难使用。不知大家是什么感想?

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#在后台自动化截图指定网站并保存图片

先安装PuppeteerSharp的库 然后调用如下方法 private async Task ScreenShotAsync(string url){//using var browserFetcher new BrowserFetcher();//await browserFetcher.DownloadAsync();await using var browser await Puppeteer.LaunchAsync(new LaunchOptions { Headle…

计算机网络-TCP连接建立阶段错误应对机制

错误现象 丢包 网络问题&#xff1a;网络不稳定可能导致丢包&#xff0c;例如信号弱或干扰强。带宽限制可能导致路由器或交换机丢弃包&#xff0c;尤其是在高流量时段。网络拥塞时&#xff0c;多个数据流竞争有限的资源&#xff0c;也可能导致丢包。缓冲区溢出&#xff1a;TC…

2024年【T电梯修理】证考试及T电梯修理模拟考试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 T电梯修理证考试考前必练&#xff01;安全生产模拟考试一点通每个月更新T电梯修理模拟考试题题目及答案&#xff01;多做几遍&#xff0c;其实通过T电梯修理作业模拟考试很简单。 1、【多选题】《特种设备安全法》规定…

YOLOv5算法进阶改进(20)— 更换主干网络之RepViT | 从ViT视角重新审视移动CNN

前言:Hello大家好,我是小哥谈。RepViT是一种基于Transformer的视觉模型,它的全称是Representation Learning with Visual Tokens。与传统的卷积神经网络不同,RepViT使用了Transformer的自注意力机制来提取图像中的特征。具体来说,RepViT将图像分成若干个视觉标记(visual …

股权融资成本GLS模型计算

一、模型公式 式中&#xff1a; r 为股权融资成本 P为股价 B为每股净资产 FROE为预测每股净资产收益率 目标&#xff1a;求解股权融资成本r 二、模型口径参考来源 PS&#xff1a;实际以代码为准 ①FROE&#xff08;预测每股净资产收益率&#xff09;: 资本市场开放与…

2024 MathorCup C 题 物流网络分拣中心货量预测及人员排班

一、问题重述 电商物流网络在订单履约中由多个环节组成&#xff0c;图1是一个简化的物流网络示意图。其中&#xff0c;分拣中心作为网络的中间环节&#xff0c;需要将包裹按照不同流向进行分拣并发往下一个场地&#xff0c;最终使包裹到达消费者手中。分拣中心管理效率的提升&…

一文搞懂OSI七层!

1.OSI OSI (Open System Interconnect&#xff09;&#xff0c;即开放式系统互联是一个完整的、完善的宏观模型。 TCP/IP协议提供点对点的链接机制&#xff0c;将数据应该如何封装、定址、传输、路由以及在目的地如何接收&#xff0c;都加以标准化。更加侧重的是互联网通信核…

动态代理 --java学习笔记

什么是动态代理&#xff1f; 当一个类的很多方法都存在重复冗杂的部分&#xff0c;就可以使用代理来处理那些重复部分的任务&#xff0c;到了各自的实现部分再丢回给原方法处理&#xff0c;同时也可以提高方法的扩展性&#xff0c;而动态则是指在运行时动态地创建代理对象&…

算法题解记录8+++爬楼梯(百日筑基)

题目描述&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1. 1 阶…

2024年【安全员-A证】最新解析及安全员-A证模拟考试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【安全员-A证】最新解析及安全员-A证模拟考试题&#xff0c;包含安全员-A证最新解析答案和解析及安全员-A证模拟考试题练习。安全生产模拟考试一点通结合国家安全员-A证考试最新大纲及安全员-A证考试真题汇总&a…

AI投研分析,模块化赛道可能会出现新的头部公链

随着比特币ETF的通过&#xff0c;再加上比特币第四次减半临近&#xff0c;备受期待的新一轮牛市周期已经开启了&#xff0c;然而对于那些刚穿越过熊市的新韭菜而言&#xff0c;因为总觉得没这么快涨起来&#xff0c;而对二级市场交易变得非常谨慎&#xff0c;导致了很多新韭菜在…

Unity之Unity面试题(六)

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之Unity面试题&#xff08;六&#xff09; TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取…

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)

情形&#xff1a;Spring配置类注解方式整合MyBatis 异常&#xff1a; org.apache.ibatis.binding.BindingException: Invalid bound statement (not found) 解决&#xff1a; 检查mapper的全类名和mapper.xml的namespace是否一致检查mapper下的方法名和mapper.xml的sql的id是否…

电脑离线调用微信 ocr.exe 使用 python 调用 WeChatOCR.exe 附python代码

微信的OCR识别能力还是可以的,并且可以得到位置,速度也快,我想要把微信的这个exe 单独提取出来,可以供其他项目使用,目前已有的应该都是需要依赖微信运行状态的,我这个独一份 相当于把微信运行它的OCR的环境完全剥离出来,只需要几个代码文件加上几个模型文件就可以离线…

react antd 实现修改密码(原密码,新密码,再次输入新密码,新密码增加正则复杂度校验)

先看样子 组件代码&#xff1a; import React, { useState, useEffect } from react import { Row, Col, Modal, Spin, Input, Button, message, Form } from antd import { LockOutlined, EyeTwoTone, EyeInvisibleOutlined } from ant-design/icons import * as Serve from …

如何申请做快团团购帮卖团长?免费试用教程一学就会!

新手小白想要自己做快团团&#xff0c;有两种方法。 1、自己提供货源&#xff0c;自己开团做团长。 这种方法流程非常简单&#xff0c;从微信小程序中找到快团团&#xff0c;点击右上角“一键开团”&#xff0c;然后“创建普通团购”&#xff0c;按照操作设置就可以开团了。 …

Java高频面试之JVM篇

说一下 Jvm 的主要组成部分&#xff1f;及其作用&#xff1f; 类加载器执行器运行时数据区域本地接口 谈谈对运行时数据区的理解&#xff1f; 堆和栈的区别是什么&#xff1f; 堆中存什么&#xff1f;栈中存什么&#xff1f; 堆总存对象,栈中存局部变量,引用 为什么要把堆…

静音检测电路芯片D3703F——工 作 电 压 范 围 宽 : 3.2V ~ 16.0V,可以用于汽 车 音 响 系 统

概 述 &#xff1a; D3703F 是 一 块 汽 车 音 响 静 音 检 测 电 路 。 用 于 音 响 系 统 检 测 在 放 音 或 快 进 / 退 时 进 行 静 音 检 测 。 D3703F 的 的 电 压 范 围 &#xff1a; 3.2V &#xff5e; 16V &#xff0c; 信 号 检 测 和 静 音 时 间 可 通 过 外 围…

参花期刊投稿发表论文

《参花》是由国家新闻出版总署批准&#xff0c;吉林省文化和旅游厅主管&#xff0c;吉林省文化馆主办的正规文学类期刊。文学是用语言塑造形象反映社会生活的一种语言艺术&#xff0c;是自觉、独立而又面向整个社会的艺术&#xff0c;是文化中极具强烈感染力的重要组成部分&…

Java List基础篇

目录 前言一、常用List1.1 List1.1.1 特点1.1.2 常用API 1.2 ArrayList1.2.1 特点1.2.2 使用 1.3 LinkedList1.3.1 特点1.3.2 使用 1.4 CopyOnWriteArrayList1.4.1 特点1.4.2 使用 1.5 Arrays.asList()1.5.1 特点1.5.2 使用 二、对比总结 前言 一、常用List 1.1 List List是…