在 Android 中计算罗盘方位/方向到位置

我想显示一个箭头在我的位置在谷歌地图视图,显示我的方向相对于一个目的地位置(而不是北)。

A)我利用磁强计和加速度计的传感器数值计算了北方。我知道这是正确的,因为它与谷歌地图视图中使用的指南针一致。

B)我使用 myLocation.BearingTo (destLocation)计算了从我的位置到目标位置的初始方位;

我遗漏了最后一步; 从这两个值(a & b)中,我应该使用什么公式来得到手机相对于目的地所指向的方向?

感谢任何帮助一个混乱的头脑!

134453 次浏览

我不是地图阅读/导航等方面的专家,但可以肯定的是,“方向”是绝对的,而不是相对的,或者在现实中,它们是相对于 N 或 S 的,而 N 或 S 本身是固定的/绝对的。

例如: 假设在你和你的目的地之间画一条虚线,对应“绝对”SE (一个相对于磁性 N 135度的方位)。现在假设你的手机指向西北方向——如果你从地平线上的一个想象物体画一条虚线到你的目的地,它将穿过你的位置,并且有一个180度的角度。在罗盘的意义上,180度实际上指的是 S,但是目的地并不是你手机指向的虚拟物体的“应有的 S”,而且,如果你旅行到那个虚拟点,你的目的地仍然是你移动到的地方的 SE。

在现实中,180度线实际上告诉你的目的地是“在你身后”相对于方式的手机(假设你)是指向。

话虽如此,但是,如果计算从虚点到目的地(通过你的位置)的直线的角度来画一个指向你的目的地的指针是你想要的... ... 只需从虚对象的绝对方位中减去目的地的(绝对)方位,忽略一个否定(如果存在的话)。例如,NW-SE 是315-135 = 180,所以画出指针指向屏幕底部,表示“在你后面”。

编辑: 我的数学稍微有点错误... ... 从较大的轴承中减去较小的轴承,然后从360减去结果,得到在屏幕上画指针的角度。

好吧,我明白了。对于其他想要这样做的人,你需要:

A)航向: 你从硬件罗盘上的航向。这是在 有磁性以东的北纬度

B)方位: 从你的位置到目的地的方位。这是在北纬 没错度以东。

myLocation.bearingTo(destLocation);

(c)赤纬: 真北和磁北的区别

从磁力计 + 加速度计返回的方向是在真(磁)北(- 180至 + 180)以东的度数,所以你需要得到的差异之间的北和磁北为您的位置。这种差异是可变的,取决于你在地球上的位置。可以通过使用 GeomagneticField 类获取。

GeomagneticField geoField;


private final LocationListener locationListener = new LocationListener() {
public void onLocationChanged(Location location) {
geoField = new GeomagneticField(
Double.valueOf(location.getLatitude()).floatValue(),
Double.valueOf(location.getLongitude()).floatValue(),
Double.valueOf(location.getAltitude()).floatValue(),
System.currentTimeMillis()
);
...
}
}

有了这些,你就可以计算箭头在地图上的角度,从而显示出你面对的目标物体的位置,而不是真正的北方。

首先调整你的方向与倾斜度:

heading += geoField.getDeclination();

其次,你需要偏移手机面向目标目的地的方向,而不是正北方向。这就是我被卡住的部分。从指南针返回的航向值为您提供了一个值,该值描述了与手机指向相关的磁北方位(以正北方向以东的度数为单位)。例如,如果这个值是 -10,你就知道磁北方向是你左边的10度。这个方位给出了你的目的地的角度,以正北偏东的度数为单位。因此,在你补偿了偏差之后,你可以使用下面的公式来得到你想要的结果:

heading = myBearing - (myBearing + heading);

然后你需要将正北偏东的度数(- 180到 + 180)转换为正常的度数(0到360) :

Math.round(-heading / 360 + 180)

如果你们在同一个时区

转换 GPS 到 UTM

Http://www.ibm.com/developerworks/java/library/j-coordconvert/ Http://stackoverflow.com/questions/176137/java-convert-lat-lon-to-utm

UTM 坐标给你一个简单的 XY2D

计算两个 UTM 位置之间的角度

Http://forums.groundspeak.com/gc/index.php?showtopic=146917

这给出了方向,就好像你在向北看

所以无论你旋转什么,北减去这个角度

如果两点都有一个 UTM 45度角,而你在北纬5度以东,你的箭头将指向北纬40度

我是这样做的:

Canvas g = new Canvas( compass );
Paint p = new Paint( Paint.ANTI_ALIAS_FLAG );


float rotation = display.getOrientation() * 90;


g.translate( -box.left, -box.top );
g.rotate( -bearing - rotation, box.exactCenterX(), box.exactCenterY() );
drawCompass( g, p );
drawNeedle( g, p );

术语: TRUE 北极和磁北极之间的差异被称为“变异”,而不是赤纬。罗盘读数和磁航向之间的差异被称为“偏差”,并随航向的不同而变化。指南针摆动识别设备错误,并允许在设备内置修正功能的情况下应用修正功能。一个磁罗盘将有一个偏差卡,描述任何航向的设备错误。

赤纬: 天文导航中使用的一个术语: 赤纬与纬度相似。它报告恒星离天球赤道有多远。要找到一颗恒星的赤纬,需要沿着恒星到天球赤道的“直线”走一个小时。从恒星到天球赤道沿着小时圆的角度就是恒星的赤经。

我现在正在弄清楚这个问题的过程中,但是看起来数学似乎取决于你和你的目标在地球上相对于真实和磁性北方的位置。例如:

float thetaMeThem = 0.0;
if (myLocation.bearingTo(targetLocation) > myLocation.getBearing()){
thetaMeThem = myLocation.bearingTo(targetLocation) - azimuth + declination;}

有关方位角,请参见 Sensor.TYPE _ ORIENTATION。

有关赤字,请参见 getDeclination ()

这里假设赤纬为负(正北偏西) ,它们的方位 > 你的方位。

如果倾角为正,你的轴承 > 它们的轴承另一种选择:

float thetaMeThem = 0.0;
if (myLocation.bearingTo(targetLocation) < myLocation.getBearing()){
thetaMeThem = azimuth - (myLocation.bearingTo(targetLocation) - declination);}

我还没有完全测试过这个,但是玩弄纸上的角度让我来到了这里。

@ Damian-这个想法非常好,我同意回答,但是当我使用你的代码时,我有错误的值,所以我自己写了这个(有人在你的评论中说了同样的话)。我认为,用下降来计算航向是好的,但后来我使用了类似的方法:

heading = (bearing - heading) * -1;

而不是达米安的密码:

heading = myBearing - (myBearing + heading);

并在 -180到180之间变化0到360:

      private float normalizeDegree(float value){
if(value >= 0.0f && value <= 180.0f){
return value;
}else{
return 180 + (180 + value);
}

然后当你想旋转你的箭头时,你可以使用这样的代码:

      private void rotateArrow(float angle){


Matrix matrix = new Matrix();
arrowView.setScaleType(ScaleType.MATRIX);
matrix.postRotate(angle, 100f, 100f);
arrowView.setImageMatrix(matrix);
}

其中 arrowView是带箭头图片的 ImageView,而 postRotate中的100f 参数是 pivX 和 pivY)。

我希望我能帮助别人。

我知道这有点老套,但是为了像我这样的人,从谷歌谁没有找到一个完整的答案在这里。下面是我的应用程序的一些摘录,它们把箭头放在一个自定义的列表视图中... ..。

Location loc;   //Will hold lastknown location
Location wptLoc = new Location("");    // Waypoint location
float dist = -1;
float bearing = 0;
float heading = 0;
float arrow_rotation = 0;


LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
loc = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);


if(loc == null) {   //No recent GPS fix
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setAltitudeRequired(false);
criteria.setBearingRequired(true);
criteria.setCostAllowed(true);
criteria.setSpeedRequired(false);
loc = lm.getLastKnownLocation(lm.getBestProvider(criteria, true));
}


if(loc != null) {
wptLoc.setLongitude(cursor.getFloat(2));    //Cursor is from SimpleCursorAdapter
wptLoc.setLatitude(cursor.getFloat(3));
dist = loc.distanceTo(wptLoc);
bearing = loc.bearingTo(wptLoc);    // -180 to 180
heading = loc.getBearing();         // 0 to 360
// *** Code to calculate where the arrow should point ***
arrow_rotation = (360+((bearing + 360) % 360)-heading) % 360;
}

我敢打赌,它可以被简化,但它的工作! 由于此代码来自新的 SimpleCursorAdapter.ViewBinder () ,因此使用了 LastKnownLocation

OnLocationChanged 包含对 notifyDataSetChanged ()的调用;

代码也来自新的 SimpleCursorAdapter。ViewBinder ()用于设置图像旋转和列表颜色(只应用于单列索引) ..。

LinearLayout ll = ((LinearLayout)view.getParent());
ll.setBackgroundColor(bc);
int childcount = ll.getChildCount();
for (int i=0; i < childcount; i++){
View v = ll.getChildAt(i);
if(v instanceof TextView) ((TextView)v).setTextColor(fc);
if(v instanceof ImageView) {
ImageView img = (ImageView)v;
img.setImageResource(R.drawable.ic_arrow);
Matrix matrix = new Matrix();
img.setScaleType(ScaleType.MATRIX);
matrix.postRotate(arrow_rotation, img.getWidth()/2, img.getHeight()/2);
img.setImageMatrix(matrix);
}

如果你想知道我为什么不用磁力传感器的话,我觉得这不值得。 我希望有人发现这是有用的,因为我通常做的时候谷歌带我堆栈溢出!

这是在 Google 地图上检测位置对象的最佳方法:->

 float targetBearing=90;


Location endingLocation=new Location("ending point");


Location
startingLocation=new Location("starting point");
startingLocation.setLatitude(mGoogleMap.getCameraPosition().target.latitude);
startingLocation.setLongitude(mGoogleMap.getCameraPosition().target.longitude);
endingLocation.setLatitude(mLatLng.latitude);
endingLocation.setLongitude(mLatLng.longitude);
targetBearing =
startingLocation.bearingTo(endingLocation);

该公式将使用起点到终点 的坐标给出轴承

下面的代码将为您提供方位(0-360之间的角度)

private double bearing(Location startPoint, Location endPoint) {
double longitude1 = startPoint.getLongitude();
double latitude1 = Math.toRadians(startPoint.getLatitude());
    

double longitude2 = endPoint.getLongitude();
double latitude2 = Math.toRadians(endPoint.getLatitude());
    

double longDiff = Math.toRadians(longitude2 - longitude1);
    

double y = Math.sin(longDiff) * Math.cos(latitude2);
double x = Math.cos(latitude1) * Math.sin(latitude2) - Math.sin(latitude1) * Math.cos(latitude2) * Math.cos(longDiff);
    

return Math.toDegrees(Math.atan2(y, x));
}

这对我有用,希望对其他人也有用

在这个指南针箭头显示的方向从你的位置到 Kaaba(目的地位置)

你可以通过这种方式简单地使用轴承。轴承会给你从你的位置到目的地的直接角度

  Location userLoc=new Location("service Provider");
//get longitudeM Latitude and altitude of current location with gps class and  set in userLoc
userLoc.setLongitude(longitude);
userLoc.setLatitude(latitude);
userLoc.setAltitude(altitude);


Location destinationLoc = new Location("service Provider");
destinationLoc.setLatitude(21.422487); //kaaba latitude setting
destinationLoc.setLongitude(39.826206); //kaaba longitude setting
float bearTo=userLoc.bearingTo(destinationLoc);

轴承将给你一个范围从 -180到180,这将有点混淆。我们需要将这个值转换为0到360的范围,以获得正确的旋转。

这张表列出了我们真正想要的东西,和方位给予我们的东西相比较

+-----------+--------------+
| bearingTo | Real bearing |
+-----------+--------------+
| 0         | 0            |
+-----------+--------------+
| 90        | 90           |
+-----------+--------------+
| 180       | 180          |
+-----------+--------------+
| -90       | 270          |
+-----------+--------------+
| -135      | 225          |
+-----------+--------------+
| -180      | 180          |
+-----------+--------------+

所以我们必须在 bear 后面加上这段代码

// If the bearTo is smaller than 0, add 360 to get the rotation clockwise.


if (bearTo < 0) {
bearTo = bearTo + 360;
//bearTo = -100 + 360  = 260;
}

您需要实现 SensorEventListener 及其函数(onSensorChanged、 onAcurracyChabge) ,并编写 onSensorChanged 中的所有代码

齐布拉方向指南针的完整密码在这里

 public class QiblaDirectionCompass extends Service implements SensorEventListener{
public static ImageView image,arrow;


// record the compass picture angle turned
private float currentDegree = 0f;
private float currentDegreeNeedle = 0f;
Context context;
Location userLoc=new Location("service Provider");
// device sensor manager
private static SensorManager mSensorManager ;
private Sensor sensor;
public static TextView tvHeading;
public QiblaDirectionCompass(Context context, ImageView compass, ImageView needle,TextView heading, double longi,double lati,double alti ) {


image = compass;
arrow = needle;




// TextView that will tell the user what degree is he heading
tvHeading = heading;
userLoc.setLongitude(longi);
userLoc.setLatitude(lati);
userLoc.setAltitude(alti);


mSensorManager =  (SensorManager) context.getSystemService(SENSOR_SERVICE);
sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
if(sensor!=null) {
// for the system's orientation sensor registered listeners
mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME);//SensorManager.SENSOR_DELAY_Fastest
}else{
Toast.makeText(context,"Not Supported", Toast.LENGTH_SHORT).show();
}
// initialize your android device sensor capabilities
this.context =context;
@Override
public void onCreate() {
// TODO Auto-generated method stub
Toast.makeText(context, "Started", Toast.LENGTH_SHORT).show();
mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME); //SensorManager.SENSOR_DELAY_Fastest
super.onCreate();
}


@Override
public void onDestroy() {
mSensorManager.unregisterListener(this);
Toast.makeText(context, "Destroy", Toast.LENGTH_SHORT).show();


super.onDestroy();


}
@Override
public void onSensorChanged(SensorEvent sensorEvent) {




Location destinationLoc = new Location("service Provider");


destinationLoc.setLatitude(21.422487); //kaaba latitude setting
destinationLoc.setLongitude(39.826206); //kaaba longitude setting
float bearTo=userLoc.bearingTo(destinationLoc);


//bearTo = The angle from true north to the destination location from the point we're your currently standing.(asal image k N se destination taak angle )


//head = The angle that you've rotated your phone from true north. (jaise image lagi hai wo true north per hai ab phone jitne rotate yani jitna image ka n change hai us ka angle hai ye)






GeomagneticField geoField = new GeomagneticField( Double.valueOf( userLoc.getLatitude() ).floatValue(), Double
.valueOf( userLoc.getLongitude() ).floatValue(),
Double.valueOf( userLoc.getAltitude() ).floatValue(),
System.currentTimeMillis() );
head -= geoField.getDeclination(); // converts magnetic north into true north


if (bearTo < 0) {
bearTo = bearTo + 360;
//bearTo = -100 + 360  = 260;
}


//This is where we choose to point it
float direction = bearTo - head;


// If the direction is smaller than 0, add 360 to get the rotation clockwise.
if (direction < 0) {
direction = direction + 360;
}
tvHeading.setText("Heading: " + Float.toString(degree) + " degrees" );


RotateAnimation raQibla = new RotateAnimation(currentDegreeNeedle, direction, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
raQibla.setDuration(210);
raQibla.setFillAfter(true);


arrow.startAnimation(raQibla);


currentDegreeNeedle = direction;


// create a rotation animation (reverse turn degree degrees)
RotateAnimation ra = new RotateAnimation(currentDegree, -degree, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);


// how long the animation will take place
ra.setDuration(210);




// set the animation after the end of the reservation status
ra.setFillAfter(true);


// Start the animation
image.startAnimation(ra);


currentDegree = -degree;
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {


}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

Xml 代码在这里

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/flag_pakistan">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/heading"
android:textColor="@color/colorAccent"
android:layout_centerHorizontal="true"
android:layout_marginBottom="100dp"
android:layout_marginTop="20dp"
android:text="Heading: 0.0" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/heading"
android:scaleType="centerInside"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true">


<ImageView
android:id="@+id/imageCompass"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerInside"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:src="@drawable/images_compass"/>


<ImageView
android:id="@+id/needle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:scaleType="centerInside"
android:src="@drawable/arrow2"/>
</RelativeLayout>
</RelativeLayout>