Рассчитать направление компаса / курс на местоположение в Android - PullRequest
52 голосов
/ 30 ноября 2010

Я хочу отобразить стрелку в моем местоположении на карте Google, которая отображает мое направление относительно места назначения (вместо севера).

a) Я рассчитал север, используя значения датчика измагнитометр и акселерометр.Я знаю, что это правильно, потому что он совпадает с компасом, используемым в представлении Google Map.

b) Я рассчитал начальный пеленг от моего местоположения к месту назначения, используя myLocation.bearingTo (destLocation);

Я пропускаю последний шаг;из этих двух значений (a и b) какую формулу я использую, чтобы получить направление, в котором телефон указывает относительно места назначения?

Цените любую помощь для ума!

Ответы [ 12 ]

64 голосов
/ 30 ноября 2010

Хорошо, я понял это. Для всех, кто пытается это сделать, вам нужно:

а) заголовок: ваш курс от аппаратного компаса. Это в градусах к востоку от магнитного северного

б) подшипник: подшипник от вашего местоположения до места назначения. Это в градусах к востоку от true север.

myLocation.bearingTo(destLocation);

в) склонение: разница между истинным севером и магнитным севером

Направление, возвращаемое от магнитометра + акселерометра, находится в градусах к востоку от истинного (магнитного) севера (от -180 до +180), поэтому вам нужно получить разницу между севером и магнитным севером для вашего местоположения. Эта разница является переменной в зависимости от того, где вы находитесь на земле. Вы можете получить с помощью класса GeomagnField.

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)
15 голосов
/ 12 марта 2013

@ Дамиан - Идея очень хорошая, и я согласен с ответом, но когда я использовал ваш код, у меня были неправильные значения, поэтому я написал это самостоятельно (кто-то сказал то же самое в ваших комментариях). Думаю, считать курс с отклонением хорошо, но позже я использовал что-то вроде этого:

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 со стрелкой и параметрами 100f в postRotate - это pivX и pivY).

Надеюсь, я кому-нибудь помогу.

3 голосов
/ 29 мая 2017

В этом стрелка на компасе показывает направление от вашего местоположения до Кааба ( Место назначения )

, вы можете просто использовать BearTo таким образом.даст вам прямой угол от вашего местоположения до места назначения

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

Bearing, даст вам диапазон от -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

Completeкод здесь для направления компаса Qibla

 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>
2 голосов
/ 30 ноября 2010

Я не эксперт в чтении карт / навигации и т. Д., Но, безусловно, «направления» являются абсолютными, а не относительными или, в действительности, они относительно N или S, которые сами по себе являются фиксированными / абсолютными.

Пример. Предположим, что воображаемая линия, проведенная между вами и пунктом назначения, соответствует «абсолютной» SE (азимут 135 градусов относительно магнитного N). Теперь предположим, что ваш телефон указывает на северо-запад - если вы нарисуете воображаемую линию от воображаемого объекта на горизонте до пункта назначения, он пройдет через ваше местоположение и будет иметь угол 180 градусов. Теперь 180 градусов в смысле компаса на самом деле относится к S, но пункт назначения не «из-за S» воображаемого объекта, на который указывает ваш телефон, и, более того, если вы дойдете до этой воображаемой точки, ваш пункт назначения по-прежнему будет SE куда вы переехали.

На самом деле, линия 180 градусов на самом деле говорит вам, что пункт назначения находится «позади вас» относительно того, как телефон (и, вероятно, вы) указываете.

Сказав, что, однако, если вы рассчитываете угол наклона линии от воображаемой точки до вашего пункта назначения (проходящего через ваше местоположение) для того, чтобы нарисовать указатель на ваш пункт назначения, это то, что вы хотите ... просто вычтите (абсолютный ) указание пункта назначения из абсолютного значения воображаемого объекта и игнорирование отрицания (если имеется). например, NW - SE равен 315 - 135 = 180, поэтому наведите указатель на точку внизу экрана, указывающую «позади вас».

РЕДАКТИРОВАТЬ: Я немного ошибся в математике ... вычтите меньшие из подшипников из большего, а затем вычтите результат из 360, чтобы получить угол, в котором можно нарисовать указатель на экране.

1 голос
/ 09 ноября 2014

Я знаю, что это немного старо, но ради людей вроде меня из Google, которые не нашли здесь полного ответа.Вот некоторые выдержки из моего приложения, которые помещают стрелки в собственный вид списка ...

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 был использован, поскольку этот код был из нового SimpleCursorAdapter.ViewBinder ()

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 переводит меня в stackoverflow!

1 голос
/ 11 сентября 2011

Вот как я это сделал:

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 );
1 голос
/ 30 июня 2011

Если вы находитесь в одном и том же часовом поясе

Преобразование 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º к северу

0 голосов
/ 16 мая 2018

Вот код для расчета угла подшипника между двумя точками:

public float CalculateBearingAngle(double lat1,double lon1, double lat2, double lon2){
    double Phi1 = Math.toRadians(lat1);
    double Phi2 = Math.toRadians(lat2);
    double DeltaLambda = Math.toRadians(lon2 - lon1);
    double Theta = atan2((sin(DeltaLambda)*cos(Phi2)),
        (cos(Phi1)*sin(Phi2) - sin(Phi1)*cos(Phi2)*cos(DeltaLambda)));
    return (float)Math.toDegrees(Theta);
}

Вызов функции:

float angle = CalculateBearingAngle(lat1, lon1, lat2, lon2);
0 голосов
/ 29 сентября 2016

Формула даст направление с использованием координат начальной точки к конечной точке см.

Следующий код даст вам направление (угол между 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));

}

Это работает для меня, надеюсь, это будет работать и для других

0 голосов
/ 13 июня 2016

Это лучший способ определения направления на объекте на карте 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);

...