- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
在 Android 设备上似乎有很多获取当前主方向的旧示例,但 Google 提供的官方解决方案似乎没有出现在他们的文档中。
最古老的引用资料 Sensor.TYPE_ORIENTATION
已被弃用,最近的引用资料提到了 Sensor.TYPE_ACCELEROMETER
和 Sensor.TYPE_MAGNETIC_FIELD
(我试过收效甚微——精度会根据设备方向迅速变化)。我一直在尝试使用这两个实现,例如 this.我什至见过一些带有 TYPE.GRAVITY
的。
most recent seem to suggest TYPE_ROTATION_VECTOR这显然是一个融合传感器(reference),但示例实现似乎并不容易获得。
我需要使用这些位置/运动传感器,而不是 GPS,因为用户在需要测量时不会移动。还需要测量稳定,无论手机是平的还是竖的(就像你在拍照一样)
在我们以某种方式提取度数测量之后,转换为基本方向似乎是容易的部分。( https://stackoverflow.com/a/25349774/1238737 )
以前的解决方案
最佳答案
之前从事过OsmAnd、MapsWithMe、MapBox等开源 map 项目。我认为这些项目是 map 和导航领域中最好的 android 开源项目。我检查了他们的代码,发现当手机垂直然后绕垂直轴 (y) 旋转时,MapBox 显示指南针的方法是稳定的。如果旋转 vector 传感器可用,它使用 TYPE_ROTATION_VECTOR
。否则它使用 TYPE_ORIENTATION
传感器或 TYPE_ACCELEROMETER
和 TYPE_MAGNETIC_FIELD
的组合。在使用 TYPE_ACCELEROMETER
和 TYPE_MAGNETIC_FIELD
的情况下,可以通过低通滤波器减少结果的振荡以获得更平滑的值。
这里是MapBox的罗盘引擎及其使用方法。
.
位置组件CompassEngine.java:
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.Surface;
import android.view.WindowManager;
import timber.log.Timber;
import java.util.ArrayList;
import java.util.List;
/**
* This manager class handles compass events such as starting the tracking of device bearing, or
* when a new compass update occurs.
*/
public class LocationComponentCompassEngine implements SensorEventListener {
// The rate sensor events will be delivered at. As the Android documentation states, this is only
// a hint to the system and the events might actually be received faster or slower then this
// specified rate. Since the minimum Android API levels about 9, we are able to set this value
// ourselves rather than using one of the provided constants which deliver updates too quickly for
// our use case. The default is set to 100ms
private static final int SENSOR_DELAY_MICROS = 100 * 1000;
// Filtering coefficient 0 < ALPHA < 1
private static final float ALPHA = 0.45f;
// Controls the compass update rate in milliseconds
private static final int COMPASS_UPDATE_RATE_MS = 500;
private final WindowManager windowManager;
private final SensorManager sensorManager;
private final List<CompassListener> compassListeners = new ArrayList<>();
// Not all devices have a compassSensor
@Nullable
private Sensor compassSensor;
@Nullable
private Sensor gravitySensor;
@Nullable
private Sensor magneticFieldSensor;
private float[] truncatedRotationVectorValue = new float[4];
private float[] rotationMatrix = new float[9];
private float[] rotationVectorValue;
private float lastHeading;
private int lastAccuracySensorStatus;
private long compassUpdateNextTimestamp;
private float[] gravityValues = new float[3];
private float[] magneticValues = new float[3];
/**
* Construct a new instance of the this class. A internal compass listeners needed to separate it
* from the cleared list of public listeners.
*/
LocationComponentCompassEngine(WindowManager windowManager, SensorManager sensorManager) {
this.windowManager = windowManager;
this.sensorManager = sensorManager;
compassSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
if (compassSensor == null) {
if (isGyroscopeAvailable()) {
Timber.d("Rotation vector sensor not supported on device, falling back to orientation.");
compassSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
} else {
Timber.d("Rotation vector sensor not supported on device, falling back to accelerometer and magnetic field.");
gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magneticFieldSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}
}
}
public void addCompassListener(@NonNull CompassListener compassListener) {
if (compassListeners.isEmpty()) {
onStart();
}
compassListeners.add(compassListener);
}
public void removeCompassListener(@NonNull CompassListener compassListener) {
compassListeners.remove(compassListener);
if (compassListeners.isEmpty()) {
onStop();
}
}
public int getLastAccuracySensorStatus() {
return lastAccuracySensorStatus;
}
public float getLastHeading() {
return lastHeading;
}
public void onStart() {
registerSensorListeners();
}
public void onStop() {
unregisterSensorListeners();
}
@Override
public void onSensorChanged(SensorEvent event) {
// check when the last time the compass was updated, return if too soon.
long currentTime = SystemClock.elapsedRealtime();
if (currentTime < compassUpdateNextTimestamp) {
return;
}
if (lastAccuracySensorStatus == SensorManager.SENSOR_STATUS_UNRELIABLE) {
Timber.d("Compass sensor is unreliable, device calibration is needed.");
return;
}
if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
rotationVectorValue = getRotationVectorFromSensorEvent(event);
updateOrientation();
// Update the compassUpdateNextTimestamp
compassUpdateNextTimestamp = currentTime + COMPASS_UPDATE_RATE_MS;
} else if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
notifyCompassChangeListeners((event.values[0] + 360) % 360);
} else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
gravityValues = lowPassFilter(getRotationVectorFromSensorEvent(event), gravityValues);
updateOrientation();
} else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
magneticValues = lowPassFilter(getRotationVectorFromSensorEvent(event), magneticValues);
updateOrientation();
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
if (lastAccuracySensorStatus != accuracy) {
for (CompassListener compassListener : compassListeners) {
compassListener.onCompassAccuracyChange(accuracy);
}
lastAccuracySensorStatus = accuracy;
}
}
private boolean isGyroscopeAvailable() {
return sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null;
}
@SuppressWarnings("SuspiciousNameCombination")
private void updateOrientation() {
if (rotationVectorValue != null) {
SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVectorValue);
} else {
// Get rotation matrix given the gravity and geomagnetic matrices
SensorManager.getRotationMatrix(rotationMatrix, null, gravityValues, magneticValues);
}
final int worldAxisForDeviceAxisX;
final int worldAxisForDeviceAxisY;
// Remap the axes as if the device screen was the instrument panel,
// and adjust the rotation matrix for the device orientation.
switch (windowManager.getDefaultDisplay().getRotation()) {
case Surface.ROTATION_90:
worldAxisForDeviceAxisX = SensorManager.AXIS_Z;
worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_X;
break;
case Surface.ROTATION_180:
worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_X;
worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_Z;
break;
case Surface.ROTATION_270:
worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_Z;
worldAxisForDeviceAxisY = SensorManager.AXIS_X;
break;
case Surface.ROTATION_0:
default:
worldAxisForDeviceAxisX = SensorManager.AXIS_X;
worldAxisForDeviceAxisY = SensorManager.AXIS_Z;
break;
}
float[] adjustedRotationMatrix = new float[9];
SensorManager.remapCoordinateSystem(rotationMatrix, worldAxisForDeviceAxisX,
worldAxisForDeviceAxisY, adjustedRotationMatrix);
// Transform rotation matrix into azimuth/pitch/roll
float[] orientation = new float[3];
SensorManager.getOrientation(adjustedRotationMatrix, orientation);
// The x-axis is all we care about here.
notifyCompassChangeListeners((float) Math.toDegrees(orientation[0]));
}
private void notifyCompassChangeListeners(float heading) {
for (CompassListener compassListener : compassListeners) {
compassListener.onCompassChanged(heading);
}
lastHeading = heading;
}
private void registerSensorListeners() {
if (isCompassSensorAvailable()) {
// Does nothing if the sensors already registered.
sensorManager.registerListener(this, compassSensor, SENSOR_DELAY_MICROS);
} else {
sensorManager.registerListener(this, gravitySensor, SENSOR_DELAY_MICROS);
sensorManager.registerListener(this, magneticFieldSensor, SENSOR_DELAY_MICROS);
}
}
private void unregisterSensorListeners() {
if (isCompassSensorAvailable()) {
sensorManager.unregisterListener(this, compassSensor);
} else {
sensorManager.unregisterListener(this, gravitySensor);
sensorManager.unregisterListener(this, magneticFieldSensor);
}
}
private boolean isCompassSensorAvailable() {
return compassSensor != null;
}
/**
* Helper function, that filters newValues, considering previous values
*
* @param newValues array of float, that contains new data
* @param smoothedValues array of float, that contains previous state
* @return float filtered array of float
*/
private float[] lowPassFilter(float[] newValues, float[] smoothedValues) {
if (smoothedValues == null) {
return newValues;
}
for (int i = 0; i < newValues.length; i++) {
smoothedValues[i] = smoothedValues[i] + ALPHA * (newValues[i] - smoothedValues[i]);
}
return smoothedValues;
}
/**
* Pulls out the rotation vector from a SensorEvent, with a maximum length
* vector of four elements to avoid potential compatibility issues.
*
* @param event the sensor event
* @return the events rotation vector, potentially truncated
*/
@NonNull
private float[] getRotationVectorFromSensorEvent(@NonNull SensorEvent event) {
if (event.values.length > 4) {
// On some Samsung devices SensorManager.getRotationMatrixFromVector
// appears to throw an exception if rotation vector has length > 4.
// For the purposes of this class the first 4 values of the
// rotation vector are sufficient (see crbug.com/335298 for details).
// Only affects Android 4.3
System.arraycopy(event.values, 0, truncatedRotationVectorValue, 0, 4);
return truncatedRotationVectorValue;
} else {
return event.values;
}
}
public static float shortestRotation(float heading, float previousHeading) {
double diff = previousHeading - heading;
if (diff > 180.0f) {
heading += 360.0f;
} else if (diff < -180.0f) {
heading -= 360.f;
}
return heading;
}
}
CompassListener.java:
/**
* Callbacks related to the compass
*/
public interface CompassListener {
/**
* Callback's invoked when a new compass update occurs. You can listen into the compass updates
* using {@link LocationComponent#addCompassListener(CompassListener)} and implementing these
* callbacks. Note that this interface is also used internally to to update the UI chevron/arrow.
*
* @param userHeading the new compass heading
*/
void onCompassChanged(float userHeading);
/**
* This gets invoked when the compass accuracy status changes from one value to another. It
* provides an integer value which is identical to the {@code SensorManager} class constants:
* <ul>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_NO_CONTACT}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_UNRELIABLE}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_LOW}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_MEDIUM}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_HIGH}</li>
* </ul>
*
* @param compassStatus the new accuracy of this sensor, one of
* {@code SensorManager.SENSOR_STATUS_*}
*/
void onCompassAccuracyChange(int compassStatus);
}
MainActivity.java:
import android.content.Context;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.WindowManager;
import android.widget.TextView;
import java.util.Locale;
public class MainActivity extends AppCompatActivity {
private LocationComponentCompassEngine compassEngine;
private float previousCompassBearing = -1f;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView textView = findViewById(R.id.textView);
CompassListener compassListener = new CompassListener() {
@Override
public void onCompassChanged(float targetCompassBearing) {
if (previousCompassBearing < 0) {
previousCompassBearing = targetCompassBearing;
}
float normalizedBearing =
LocationComponentCompassEngine.shortestRotation(targetCompassBearing, previousCompassBearing);
previousCompassBearing = targetCompassBearing;
String status = "NO_CONTACT";
switch (compassEngine.getLastAccuracySensorStatus()) {
case SensorManager.SENSOR_STATUS_NO_CONTACT:
status = "NO_CONTACT";
break;
case SensorManager.SENSOR_STATUS_UNRELIABLE:
status = "UNRELIABLE";
break;
case SensorManager.SENSOR_STATUS_ACCURACY_LOW:
status = "ACCURACY_LOW";
break;
case SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM:
status = "ACCURACY_MEDIUM";
break;
case SensorManager.SENSOR_STATUS_ACCURACY_HIGH:
status = "ACCURACY_HIGH";
break;
}
textView.setText(String.format(Locale.getDefault(),
"CompassBearing: %f\nAccuracySensorStatus: %s", normalizedBearing, status));
}
@Override
public void onCompassAccuracyChange(int compassStatus) {
}
};
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
compassEngine = new LocationComponentCompassEngine(windowManager, sensorManager);
compassEngine.addCompassListener(compassListener);
compassEngine.onStart();
}
@Override
protected void onDestroy() {
super.onDestroy();
compassEngine.onStop();
}
}
关于java - 静止不动时使用 TYPE_ROTATION_VECTOR 实现当前基本方向方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52894089/
SQLite、Content provider 和 Shared Preference 之间的所有已知区别。 但我想知道什么时候需要根据情况使用 SQLite 或 Content Provider 或
警告:我正在使用一个我无法完全控制的后端,所以我正在努力解决 Backbone 中的一些注意事项,这些注意事项可能在其他地方更好地解决......不幸的是,我别无选择,只能在这里处理它们! 所以,我的
我一整天都在挣扎。我的预输入搜索表达式与远程 json 数据完美配合。但是当我尝试使用相同的 json 数据作为预取数据时,建议为空。点击第一个标志后,我收到预定义消息“无法找到任何内容...”,结果
我正在制作一个模拟 NHL 选秀彩票的程序,其中屏幕右侧应该有一个 JTextField,并且在左侧绘制弹跳的选秀球。我创建了一个名为 Ball 的类,它实现了 Runnable,并在我的主 Draf
这个问题已经有答案了: How can I calculate a time span in Java and format the output? (18 个回答) 已关闭 9 年前。 这是我的代码
我有一个 ASP.NET Web API 应用程序在我的本地 IIS 实例上运行。 Web 应用程序配置有 CORS。我调用的 Web API 方法类似于: [POST("/API/{foo}/{ba
我将用户输入的时间和日期作为: DatePicker dp = (DatePicker) findViewById(R.id.datePicker); TimePicker tp = (TimePic
放宽“邻居”的标准是否足够,或者是否有其他标准行动可以采取? 最佳答案 如果所有相邻解决方案都是 Tabu,则听起来您的 Tabu 列表的大小太长或您的释放策略太严格。一个好的 Tabu 列表长度是
我正在阅读来自 cppreference 的代码示例: #include #include #include #include template void print_queue(T& q)
我快疯了,我试图理解工具提示的行为,但没有成功。 1. 第一个问题是当我尝试通过插件(按钮 1)在点击事件中使用它时 -> 如果您转到 Fiddle,您会在“内容”内看到该函数' 每次点击都会调用该属
我在功能组件中有以下代码: const [ folder, setFolder ] = useState([]); const folderData = useContext(FolderContex
我在使用预签名网址和 AFNetworking 3.0 从 S3 获取图像时遇到问题。我可以使用 NSMutableURLRequest 和 NSURLSession 获取图像,但是当我使用 AFHT
我正在使用 Oracle ojdbc 12 和 Java 8 处理 Oracle UCP 管理器的问题。当 UCP 池启动失败时,我希望关闭它创建的连接。 当池初始化期间遇到 ORA-02391:超过
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 9 年前。 Improve
引用这个plunker: https://plnkr.co/edit/GWsbdDWVvBYNMqyxzlLY?p=preview 我在 styles.css 文件和 src/app.ts 文件中指定
为什么我的条形这么细?我尝试将宽度设置为 1,它们变得非常厚。我不知道还能尝试什么。默认厚度为 0.8,这是应该的样子吗? import matplotlib.pyplot as plt import
当我编写时,查询按预期执行: SELECT id, day2.count - day1.count AS diff FROM day1 NATURAL JOIN day2; 但我真正想要的是右连接。当
我有以下时间数据: 0 08/01/16 13:07:46,335437 1 18/02/16 08:40:40,565575 2 14/01/16 22:2
一些背景知识 -我的 NodeJS 服务器在端口 3001 上运行,我的 React 应用程序在端口 3000 上运行。我在 React 应用程序 package.json 中设置了一个代理来代理对端
我面临着一个愚蠢的问题。我试图在我的 Angular 应用程序中延迟加载我的图像,我已经尝试过这个2: 但是他们都设置了 src attr 而不是 data-src,我在这里遗漏了什么吗?保留 d
我是一名优秀的程序员,十分优秀!