67

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

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

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

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

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

4

11 回答 11

69

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

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)
于 2010-11-30T17:43:18.453 回答
19

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

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

我希望我能帮助别人。

于 2013-03-12T15:02:22.920 回答
15

在此指南针上的箭头显示从您所在位置到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>
于 2017-05-29T19:14:39.637 回答
3

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

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 时所做的一样有用!

于 2014-11-09T08:54:23.990 回答
2

I'm no expert in map-reading / navigation and so on but surely 'directions' are absolute and not relative or in reality, they are relative to N or S which themselves are fixed/absolute.

Example: Suppose an imaginary line drawn between you and your destination corresponds with 'absolute' SE (a bearing of 135 degrees relative to magnetic N). Now suppose your phone is pointing NW - if you draw an imaginary line from an imaginary object on the horizon to your destination, it will pass through your location and have an angle of 180 degrees. Now 180 degrees in the sense of a compass actually refers to S but the destination is not 'due S' of the imaginary object your phone is pointing at and, moreover, if you travelled to that imaginary point, your destination would still be SE of where you moved to.

In reality, the 180 degree line actually tells you the destination is 'behind you' relative to the way the phone (and presumably you) are pointing.

Having said that, however, if calculating the angle of a line from the imaginary point to your destination (passing through your location) in order to draw a pointer towards your destination is what you want...simply subtract the (absolute) bearing of the destination from the absolute bearing of the imaginary object and ignore a negation (if present). e.g., NW - SE is 315 - 135 = 180 so draw the pointer to point at the bottom of the screen indicating 'behind you'.

EDIT: I got the Maths slightly wrong...subtract the smaller of the bearings from the larger then subtract the result from 360 to get the angle in which to draw the pointer on the screen.

于 2010-11-29T23:41:41.167 回答
1

如果你在同一个时区

将 GPS 转换为 UTM

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

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

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

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

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

所以无论你旋转相关的东西,北方都减去这个角度

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

于 2011-06-30T17:08:59.710 回答
1

以下是我的做法:

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 );
于 2011-09-11T16:32:00.150 回答
1

这是从谷歌地图上的位置对象检测方位的最佳方法:->

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

于 2016-06-13T11:50:19.693 回答
1

该公式将使用起点到终点的坐标给出方位角,请参见

以下代码将为您提供方位角(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));

}

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

于 2016-09-29T16:22:07.550 回答
0

我现在正在弄清楚它,但似乎数学取决于你和你的目标在地球上相对于真实和磁北的位置。例如:

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

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

偏角见 getDeclination()

这假设磁偏角为负(真北以西)并且它们的方位角 > 你的方位角。

如果偏角为正且 yourBearing > theirBearing 另一个选项:

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

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

于 2012-08-17T23:52:58.867 回答
-1

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

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

于 2012-08-08T12:59:39.023 回答