如何计算距离 phone 抛出的高度
How to calculate distance from a phone thrown in height
在 Flutter 中,有传感器包 https://pub.dev/packages/sensors 可以知道速度 X、Y 和 Z。
我的问题是:如何计算 phone 抛出高度的距离?
示例:您在距离地面 0.5 米处投掷您的望远镜phone。
phone 距离您的手 1 米(因此距离地面 1.5 米)。
如何获取 1 米值?
谢谢大家!
这是我现在的代码(您需要安装传感器包):
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:sensors/sensors.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List _velocityY = [];
DateTime time;
List<double> distances = [];
List<StreamSubscription<dynamic>> _streamSubscriptions =
<StreamSubscription<dynamic>>[];
@override
void initState() {
super.initState();
_streamSubscriptions
.add(userAccelerometerEvents.listen((UserAccelerometerEvent event)
{
setState(() {
if (event.y.abs() > 0.1) {
if (time != null) {
_velocityY.add(event.y);
}
//print((new DateTime.now().difference(time).inSeconds));
if (_velocityY.length > 0) {
distances.add(_velocityY[_velocityY.length - 1] * (new DateTime.now().difference(time).inMicroseconds) / 1000);
}
time = new DateTime.now();
}
//print('time' + time.toString());
});
}));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
children: [
for(double distance in distances.reversed.toList())
Text(
distance.toStringAsFixed(2),
style: Theme.of(context).textTheme.headline4,
),
],
),// This trailing comma makes auto-formatting nicer for build methods.
);
}
}
如果我错了请纠正我,但是如果你试图在任何时刻直接从(过去和现在的)加速度计计算 phone 在 space 中的绝对位置数据,实际上是 very complex,主要是因为 phone 的加速度计在 x、y 和 z 方面的参考系是 phone 本身……并且 phones 没有固定的方向,尤其是在被抛来抛去的时候,此外......无论如何,它在空中的加速度为零。
这有点像被蒙上眼睛并被带到一个装有火箭的吊舱中进行 space 旅行,这些火箭随机向不同方向发射,并且被期望知道你最后在哪里。如果您在开始时知道自己的位置,并且您有能力跟踪沿途感觉到的每个加速度矢量,这在技术上是可行的……并将其与陀螺仪数据集成……将所有这些转换成一个单一的路径。
但是,幸运的是,我们仍然可以间接获得加速度计抛出的高度,以及其他一些测量值。
此解决方案假设:
- 传感器包提供加速度值,而不是速度值(即使它声称提供速度,奇怪的是),因为accelerometers本身提供加速度.
- 无论 phone 方向如何,总加速度等于 sqrt(x^2 + y^2 + z^2)。
- 在投掷过程中加速度计读数为零(或仅重力读数)
- This article in wired 是正确的,因为高度 = (重力 * 时间^2) / 8
我的代码的工作方式是:
- 您(用户)按住“开始”按钮。
- 当你把phone扔上去的时候,你自然会松开按钮,这会启动定时器,phone开始监听加速度计事件。
- 我们假设 phone 在空中的总加速度为零(或仅为重力,取决于所选的加速度计数据类型)...所以我们实际上并没有尝试直接从加速度计数据:
- 相反,我们仅使用加速度计来检测您何时捕捉到 phone... 通过使用阈值检测加速度的突然变化。
- 达到此阈值时,计时器停止。
- 现在我们有了从开始到结束的总时间值,可以计算高度了。
旁注:
- 我使用的是 AccelerometerEvent(包括重力),而不是 UserAccelerometer 事件(不包括重力),因为我使用 UserAccelerometerEvent 在我的测试设备上得到了奇怪的数字(静止时非零)。
- 它有助于轻轻抓住 phone ***
- 我的数学可能已经完成了......我还没有让其他人看过这个......但至少这个答案让你开始了解一个有效的基本理论。
- 我的 phone 落在了狗屎里所以我希望你接受这个答案。
准确性限制:
- 放的高度和接的高度自然是不一致的。
- 阈值是实验性的....您自己测试不同的值。我选择了 10。
- 按下 GO 按钮和计时器开始之间可能有一些延迟。
- *** 由于传感器包提供的加速度计更新频率非常低,因此可能无法始终准确检测到阈值,或者如果减速结束得太快则根本检测不到阈值。也许有一种方法可以使用不同的包以更高的频率获取更新。
- 总是有可能过早按下 GO 按钮(而 phone 仍在您的手中),因此此时加速度将不为零,也许足以触发阈值。
- 可能还没有考虑其他事情。
代码:
import 'package:flutter/material.dart';
import 'package:sensors/sensors.dart';
import 'dart:async';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Phone Throw Height',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Phone Throw Height'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<StreamSubscription<dynamic>> _streamSubscriptions =
<StreamSubscription<dynamic>>[];
DateTime? startTime;
DateTime? endTime;
bool isBeingThrown = false;
final double GRAVITATIONAL_FORCE = 9.80665;
final double DECELERATION_THRESHOLD = 10; // <---- experimental
List<double> accelValuesForAnalysis = <double>[];
@override
void initState() {
super.initState();
_streamSubscriptions
.add(accelerometerEvents.listen((AccelerometerEvent event) {
if (isBeingThrown) {
double x_total = pow(event.x, 2).toDouble();
double y_total = pow(event.y, 2).toDouble();
double z_total = pow(event.z, 2).toDouble();
double totalXYZAcceleration = sqrt(x_total + y_total + z_total);
// only needed because we are not using UserAccelerometerEvent
// (because it was acting weird on my test phone Galaxy S5)
double accelMinusGravity = totalXYZAcceleration - GRAVITATIONAL_FORCE;
accelValuesForAnalysis.add(accelMinusGravity);
if (accelMinusGravity > DECELERATION_THRESHOLD) {
_throwHasEnded();
}
}
}));
}
void _throwHasEnded() {
isBeingThrown = false;
endTime = DateTime.now();
Duration totalTime = DateTime.now().difference(startTime!);
double totalTimeInSeconds = totalTime.inMilliseconds / 1000;
// this is the equation from the wired article
double heightInMeters =
(GRAVITATIONAL_FORCE * pow(totalTimeInSeconds, 2)) / 8;
Widget resetButton = TextButton(
child: Text("LONG PRESS TO RESET"),
onPressed: () {},
onLongPress: () {
startTime = null;
endTime = null;
print(accelValuesForAnalysis.toString());
accelValuesForAnalysis.clear();
Navigator.pop(context);
setState(() {
isBeingThrown = false;
});
},
);
AlertDialog alert = AlertDialog(
title: Text("Throw End Detected"),
content: Text("total throw time in seconds was: " +
totalTimeInSeconds.toString() +
"\n" +
"Total height was: " +
heightInMeters.toString() +
" meters. \n"),
actions: [
resetButton,
],
);
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SizedBox.expand(
child: Container(
color: Colors.green,
//alignment: Alignment.center,
child: SizedBox.expand(
child: (!isBeingThrown)
? TextButton(
child: Text("GO!",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 40)),
onPressed: () {
setState(() {
isBeingThrown = true;
startTime = DateTime.now();
});
},
)
: Center(
child: Text("weeeeeeeeee!",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 40)),
),
),
),
),
);
}
@override
void dispose() {
// cancel the stream from the accelerometer somehow!! ugh!!!
for (StreamSubscription<dynamic> subscription in _streamSubscriptions) {
subscription.cancel();
}
super.dispose();
}
}
您可能需要考虑使用设备的气压计来根据压力变化估算设备的高度。虽然不完全准确,但海拔变化是根据对海平面海拔差异的合理估计计算得出的。这应该有助于检测设备是否从高处坠落。
这是一个example posted on GitHub, using enviro_sensors plugin. You can also try using a similar plugin with support to null-safety: flutter_barometer_plugin
在 Flutter 中,有传感器包 https://pub.dev/packages/sensors 可以知道速度 X、Y 和 Z。
我的问题是:如何计算 phone 抛出高度的距离?
示例:您在距离地面 0.5 米处投掷您的望远镜phone。 phone 距离您的手 1 米(因此距离地面 1.5 米)。
如何获取 1 米值?
谢谢大家!
这是我现在的代码(您需要安装传感器包):
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:sensors/sensors.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List _velocityY = [];
DateTime time;
List<double> distances = [];
List<StreamSubscription<dynamic>> _streamSubscriptions =
<StreamSubscription<dynamic>>[];
@override
void initState() {
super.initState();
_streamSubscriptions
.add(userAccelerometerEvents.listen((UserAccelerometerEvent event)
{
setState(() {
if (event.y.abs() > 0.1) {
if (time != null) {
_velocityY.add(event.y);
}
//print((new DateTime.now().difference(time).inSeconds));
if (_velocityY.length > 0) {
distances.add(_velocityY[_velocityY.length - 1] * (new DateTime.now().difference(time).inMicroseconds) / 1000);
}
time = new DateTime.now();
}
//print('time' + time.toString());
});
}));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
children: [
for(double distance in distances.reversed.toList())
Text(
distance.toStringAsFixed(2),
style: Theme.of(context).textTheme.headline4,
),
],
),// This trailing comma makes auto-formatting nicer for build methods.
);
}
}
如果我错了请纠正我,但是如果你试图在任何时刻直接从(过去和现在的)加速度计计算 phone 在 space 中的绝对位置数据,实际上是 very complex,主要是因为 phone 的加速度计在 x、y 和 z 方面的参考系是 phone 本身……并且 phones 没有固定的方向,尤其是在被抛来抛去的时候,此外......无论如何,它在空中的加速度为零。
这有点像被蒙上眼睛并被带到一个装有火箭的吊舱中进行 space 旅行,这些火箭随机向不同方向发射,并且被期望知道你最后在哪里。如果您在开始时知道自己的位置,并且您有能力跟踪沿途感觉到的每个加速度矢量,这在技术上是可行的……并将其与陀螺仪数据集成……将所有这些转换成一个单一的路径。
但是,幸运的是,我们仍然可以间接获得加速度计抛出的高度,以及其他一些测量值。
此解决方案假设:
- 传感器包提供加速度值,而不是速度值(即使它声称提供速度,奇怪的是),因为accelerometers本身提供加速度.
- 无论 phone 方向如何,总加速度等于 sqrt(x^2 + y^2 + z^2)。
- 在投掷过程中加速度计读数为零(或仅重力读数)
- This article in wired 是正确的,因为高度 = (重力 * 时间^2) / 8
我的代码的工作方式是:
- 您(用户)按住“开始”按钮。
- 当你把phone扔上去的时候,你自然会松开按钮,这会启动定时器,phone开始监听加速度计事件。
- 我们假设 phone 在空中的总加速度为零(或仅为重力,取决于所选的加速度计数据类型)...所以我们实际上并没有尝试直接从加速度计数据:
- 相反,我们仅使用加速度计来检测您何时捕捉到 phone... 通过使用阈值检测加速度的突然变化。
- 达到此阈值时,计时器停止。
- 现在我们有了从开始到结束的总时间值,可以计算高度了。
旁注:
- 我使用的是 AccelerometerEvent(包括重力),而不是 UserAccelerometer 事件(不包括重力),因为我使用 UserAccelerometerEvent 在我的测试设备上得到了奇怪的数字(静止时非零)。
- 它有助于轻轻抓住 phone ***
- 我的数学可能已经完成了......我还没有让其他人看过这个......但至少这个答案让你开始了解一个有效的基本理论。
- 我的 phone 落在了狗屎里所以我希望你接受这个答案。
准确性限制:
- 放的高度和接的高度自然是不一致的。
- 阈值是实验性的....您自己测试不同的值。我选择了 10。
- 按下 GO 按钮和计时器开始之间可能有一些延迟。
- *** 由于传感器包提供的加速度计更新频率非常低,因此可能无法始终准确检测到阈值,或者如果减速结束得太快则根本检测不到阈值。也许有一种方法可以使用不同的包以更高的频率获取更新。
- 总是有可能过早按下 GO 按钮(而 phone 仍在您的手中),因此此时加速度将不为零,也许足以触发阈值。
- 可能还没有考虑其他事情。
代码:
import 'package:flutter/material.dart';
import 'package:sensors/sensors.dart';
import 'dart:async';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Phone Throw Height',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Phone Throw Height'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<StreamSubscription<dynamic>> _streamSubscriptions =
<StreamSubscription<dynamic>>[];
DateTime? startTime;
DateTime? endTime;
bool isBeingThrown = false;
final double GRAVITATIONAL_FORCE = 9.80665;
final double DECELERATION_THRESHOLD = 10; // <---- experimental
List<double> accelValuesForAnalysis = <double>[];
@override
void initState() {
super.initState();
_streamSubscriptions
.add(accelerometerEvents.listen((AccelerometerEvent event) {
if (isBeingThrown) {
double x_total = pow(event.x, 2).toDouble();
double y_total = pow(event.y, 2).toDouble();
double z_total = pow(event.z, 2).toDouble();
double totalXYZAcceleration = sqrt(x_total + y_total + z_total);
// only needed because we are not using UserAccelerometerEvent
// (because it was acting weird on my test phone Galaxy S5)
double accelMinusGravity = totalXYZAcceleration - GRAVITATIONAL_FORCE;
accelValuesForAnalysis.add(accelMinusGravity);
if (accelMinusGravity > DECELERATION_THRESHOLD) {
_throwHasEnded();
}
}
}));
}
void _throwHasEnded() {
isBeingThrown = false;
endTime = DateTime.now();
Duration totalTime = DateTime.now().difference(startTime!);
double totalTimeInSeconds = totalTime.inMilliseconds / 1000;
// this is the equation from the wired article
double heightInMeters =
(GRAVITATIONAL_FORCE * pow(totalTimeInSeconds, 2)) / 8;
Widget resetButton = TextButton(
child: Text("LONG PRESS TO RESET"),
onPressed: () {},
onLongPress: () {
startTime = null;
endTime = null;
print(accelValuesForAnalysis.toString());
accelValuesForAnalysis.clear();
Navigator.pop(context);
setState(() {
isBeingThrown = false;
});
},
);
AlertDialog alert = AlertDialog(
title: Text("Throw End Detected"),
content: Text("total throw time in seconds was: " +
totalTimeInSeconds.toString() +
"\n" +
"Total height was: " +
heightInMeters.toString() +
" meters. \n"),
actions: [
resetButton,
],
);
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SizedBox.expand(
child: Container(
color: Colors.green,
//alignment: Alignment.center,
child: SizedBox.expand(
child: (!isBeingThrown)
? TextButton(
child: Text("GO!",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 40)),
onPressed: () {
setState(() {
isBeingThrown = true;
startTime = DateTime.now();
});
},
)
: Center(
child: Text("weeeeeeeeee!",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 40)),
),
),
),
),
);
}
@override
void dispose() {
// cancel the stream from the accelerometer somehow!! ugh!!!
for (StreamSubscription<dynamic> subscription in _streamSubscriptions) {
subscription.cancel();
}
super.dispose();
}
}
您可能需要考虑使用设备的气压计来根据压力变化估算设备的高度。虽然不完全准确,但海拔变化是根据对海平面海拔差异的合理估计计算得出的。这应该有助于检测设备是否从高处坠落。
这是一个example posted on GitHub, using enviro_sensors plugin. You can also try using a similar plugin with support to null-safety: flutter_barometer_plugin