【问题标题】:Calculate compass bearing / heading to location in Android在Android中计算指南针方位/航向
【发布时间】:2011-05-17 12:49:04
【问题描述】:

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

a) 我使用磁力计和加速度计的传感器值计算了北。我知道这是正确的,因为它与 Google 地图视图中使用的指南针对齐。

b) 我使用 myLocation.bearingTo(destLocation); 计算了从我所在位置到目的地位置的初始方位角;

我错过了最后一步;从这两个值 (a & b) 中,我使用什么公式来获取手机相对于目标位置指向的方向?

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

【问题讨论】:

标签: android location compass-geolocation heading bearing


【解决方案1】:

好的,我想通了。对于其他尝试这样做的人,您需要:

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)

【讨论】:

  • 当你说“heading = myBearing - (myBearing + heading);”时有错误吗?这就像在说标题 = 标题。
  • @Nikolas 实际上就像是在说标题 = -heading。
  • 对不起,我不明白你的代码@Damien 的意思。您的初始航向等效于 onOrientationChanged 的​​给定方位角(以度为单位)。要获得指向目标位置的方向,您只需使用 *-1 更改方位角?这应该如何工作?
  • @lespommes 你发现了吗?我也总是使用此代码获得 180 度的航向:-/!
  • 你究竟是如何获得航向的?是否与从 location.getBearing() 检索到的值相同?
【解决方案2】:

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

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);
      }

其中arrowViewImageViewpostRotate 中的箭头图片和 100f 参数是 pivX 和 pivY)。

我希望我能帮助别人。

【讨论】:

  • 我不明白你为什么在计算航向时使用 * -1
  • normalizeDegree 过于复杂。你也可以只用return (value + 360) % 360
  • "return 180 + (180 + value);"
  • 轴承和我的轴承有什么区别?
  • 为什么使用“heading = (bearing - heading) * -1” 而不仅仅是“heading -= bearing”
【解决方案3】:

在此指南针上的箭头显示从您所在位置到Kaaba的方向(目的地位置

您可以通过这种方式简单地使用bearingTo。bearing to 将为您提供从您所在位置到目的地位置的直接角度

  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);

bearingTo 会给你一个从 -180 到 180 的范围,这会让事情有点混乱。我们需要将此值转换为 0 到 360 的范围,以获得正确的旋转。

这是一张我们真正想要的表格,与bearingTo给我们的比较

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

所以我们必须在bearTo之后添加这段代码

// 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>

【讨论】:

  • 感谢您的回答,但在加拿大或阿根廷等某些地方,朝拜的头部方向显示错误
  • 我在巴基斯坦,一切正常。在世界各地进行实证检查是不可能的。可能你错过了什么。
  • 我完全按照你的代码,它在伊朗很好用,而且我检查的大部分地方,我在加拿大给了一个职位只是为了测试,方向不正确。我还检查了许多现有的 Qibla Finder 应用程序,它们在某些特定位置也存在此问题。
  • 内置函数会出现bug,或者你可能搞错了。如果有错误,那么它将在最新版本中解决。因为带来的是内置功能。
  • 我没有对此进行彻底调查,但仔细阅读the docs 会发现:“返回近似初始方位”。这有点像你必须开始走的方向,才能在跨越地球(即一个球体)的最短路径上到达目的地。
【解决方案4】:

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

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;
}

我敢打赌它可以被简化,但它确实有效! 使用 LastKnownLocation 是因为此代码来自 new SimpleCursorAdapter.ViewBinder()

onLocationChanged 包含对 notifyDataSetChanged() 的调用;

还来自 new SimpleCursorAdapter.ViewBinder() 的代码来设置图像旋转和列表行颜色(请注意,仅应用于单个 columnIndex)...

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 将我带到 stackoverflow 时所做的一样有用!

【讨论】:

    【解决方案5】:

    我不是地图阅读/导航等方面的专家,但“方向”肯定是绝对的,而不是相对的,或者实际上,它们与 N 或 S 相关,它们本身是固定/绝对的。

    示例:假设在您和目的地之间绘制的假想线对应于“绝对”SE(相对于磁 N 的 135 度方位角)。现在假设您的手机指向西北 - 如果您从地平线上的一个假想物体到您的目的地画一​​条假想线,它将穿过您的位置并具有 180 度角。现在,指南针意义上的 180 度实际上是指 S,但目的地不是您的手机指向的虚构对象的“由于 S”,此外,如果您前往那个虚构的点,您的目的地仍然是 SE你搬到哪里去了。

    实际上,180 度线实际上告诉您相对于手机(可能是您)指向的方式,目的地位于“您身后”。

    话虽如此,但是,如果计算从假想点到目的地(通过您的位置)的线的角度以绘制指向目的地的指针是您想要的...只需减去(绝对) 从想象对象的绝对方位到目的地的方位,并忽略否定(如果存在)。例如,NW - SE 为 315 - 135 = 180,因此将指针指向屏幕底部,指示“在您身后”。

    编辑:我的数学略有错误...从较大的方位中减去较小的方位,然后从 360 度中减去结果,以获得在屏幕上绘制指针的角度。

    【讨论】:

    • 感谢您的帮助。我设法弄清楚了,并为面临同样问题的其他人提供了下面的答案。
    • 只有 2 分与(不正确的)接受答案的 37 分相比?哦,好吧,感谢您发布 Squonk,帮了我很多忙。
    【解决方案6】:

    如果你在同一个时区

    将 GPS 转换为 UTM

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

    UTM 坐标为您提供简单的 X Y 2D

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

    http://forums.groundspeak.com/GC/index.php?showtopic=146917

    这就像你在看北方一样给出方向

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

    如果两个点的 UTM 角均为 45º,并且您位于北东 5º,则您的箭头将指向北纬 40º

    【讨论】:

      【解决方案7】:

      这是我的做法:

      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 );
      

      【讨论】:

        【解决方案8】:

        这是从 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);
        

        【讨论】:

          【解决方案9】:

          公式将使用起点到终点的坐标给出方位see

          下面的代码会给你方位(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));
          

          }

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

          【讨论】:

          • 您好,太好了!我将使用此解决方案并让您知道。同时你可以把结尾的大括号放在代码区!! ;-)
          【解决方案10】:

          我现在正在解决这个问题,但似乎数学取决于你和你的目标在地球上相对于真实和磁北极的位置。例如:

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

          有关方位角,请参见 Sensor.TYPE_ORIENTATION。

          查看 getDeclination() 了解偏角

          这假设赤纬为负(真北以西)并且它们的方位 > yourBearing。

          如果偏角为正且 yourBearing > theirBearing 另一种选择:

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

          我还没有完全测试过这个,但是在纸上玩角度让我在这里。

          【讨论】:

            【解决方案11】:

            术语:真北和磁北之间的差异称为“变化”而不是偏角。罗盘读数与磁航向之间的差异称为“偏差”,并随航向而变化。罗盘摆动可识别设备错误并允许在设备内置校正时应用校正。磁罗盘将有一个偏差卡,用于描述任何航向上的设备错误。

            偏角:天文导航中使用的术语:偏角就像纬度。它报告一颗恒星离天赤道有多远。要找到一颗恒星的偏角,沿着从恒星到天赤道的“直线向下”一个小时圈。恒星到天赤道沿小时圈的夹角就是恒星的赤纬。

            【讨论】:

            • 抛开术语不谈,Android 的 getDeclination() 方法返回:“磁场水平分量与真北的偏角,以度为单位(即正表示磁场从真北向东旋转那么多)。”
            • 对不起,磁偏角是正确的术语。检查ngdc.noaa.gov/geomagmodels/Declination.jsp。还是 NOAA 的人也错了?
            猜你喜欢
            • 1970-01-01
            • 2015-09-07
            • 1970-01-01
            • 2017-09-10
            • 2016-06-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多