Рассчитать компас, несущий/заголовок для местоположения в Android

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

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

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

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

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

Ответ 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)

Ответ 2

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

heading = (bearing - heading) * -1;

вместо кода Damian:

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

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

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

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

Полный код здесь для направления компаса 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 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 code здесь

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

Ответ 4

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

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

В действительности, линия на 180 градусов фактически говорит вам, что пункт назначения находится "позади" относительно способа указания телефона (и предположительно вас).

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

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

Ответ 5

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

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

Это дает направление, как если бы вы смотрели на север

Итак, независимо от того, что вы вращаете, сделайте, чтобы North просто вычитал этот угол

Если обе точки имеют угол UTM 45º, а вы на 5º к востоку от севера, ваша стрелка укажет на 40º северного

Ответ 6

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

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

Ответ 7

Я знаю, что это немного старо, но ради таких людей, как я, от 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() для установки поворота изображения и цветов списка (применяется только в одном столбцеIndex)...

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!

Ответ 8

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

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

Ответ 9

Я сейчас разбираюсь в этом, но кажется, что математика зависит от того, где вы и ваша цель находятся на земле относительно истинного и магнитного Севера. Например:

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

См. Sensor.TYPE_ORIENTATION для азимута.

См. getDeclination() для склонения

Это предполагает, что склонение отрицательное (к западу от истинного севера) и theirBearing > yourBearing.

Если склонение положительное и yourBearing > theirBearing другой вариант:

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

Я не тестировал это полностью, но игра с углами на бумаге привела меня сюда.

Ответ 10

Это лучший способ обнаружить Bearing from Location Object на Карте 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);

Ответ 11

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

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

}

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

Ответ 12

Я знаю, что это старый вопрос, но я думаю, что проверка нашей библиотеки с открытым исходным кодом https://github.com/netguru/ar-localizer-view-android очень поможет вам, ребята. Мы рассчитываем все необходимые данные и используем их для отображения места назначения в виде метки с расстоянием в окне предварительного просмотра камеры. Не стесняйтесь проверить это, используйте это и оставьте некоторые предложения! :)

Ответ 13

Терминология: Разница между ИСТИННЫМ севером и Магнитным Севером называется "изменением", а не склонением. Разница между чтением вашего компаса и магнитным заголовком называется "отклонение" и изменяется с заголовком. Перекос компаса идентифицирует ошибки устройства и позволяет применять поправки, если устройство имеет встроенную коррекцию. Магнитный компас будет иметь карту отклонения, которая описывает ошибку устройства для любого заголовка.

Склонение: термин, используемый в астрологической навигации: склонение подобно широте. Он сообщает, как далеко звезда от небесного экватора. Чтобы найти склонение звезды, следуйте часовой круг "прямо вниз" от звезды до небесного экватора. Угол от звезды до небесного экватора по кругу часов - это склонение звезды.