6

デバイスの位置を追跡し、後で使用するためにデータを保持しようとしているプロジェクトに取り組んでいます。この問題について話す前に、背景を説明したいと思います。

StackExchange や Google などあらゆる場所を検索した結果、Fused Location API を使用して衛星に関する情報を取得することは事実上不可能であるという結論に達しました (Google ではよくできました)。

ほとんどの人が使用している方法は、GPS ステータスを取得するために Fused ロケーションと一緒に LocationManager を実際に使用することです。私の最初の質問は次のとおりです。LocationManager によって提供された数値が、Fused Location によって提供された数値と同期していることを 100% 確実にするにはどうすればよいでしょうか? Fused Location は Manager を内部的に使用しますか?

そして今問題。アプリは「常時オン」のスティッキー サービスを使用して、何があっても位置を取得します。衛星がない場合、すべてが意図したとおりに機能します。デバイスを衛星が見える位置に置くと、ロックされていないようです。デバッガーを使用すると、GpsStatus.getSatellites() によって空のリストが表示されます。ここで、デバイスを移動せずに、GPS タイプのコンパス スキームを備えたアプリ Compass (Catch.com が多数あるため) を起動します。それは衛星を非常に速くロックし、その瞬間から私のアプリでも衛星を報告します. コンパスが閉じている場合、アプリはコンパスが提供していた最後の数字で動かなくなります!!! 私が個人的にテストに使用しているデバイスは、最新の公式アップデート (Android 6.0.1) を適用した Nexus 7 2013 です。

ここにいくつかのコードがあります:

public class BackgroundLocationService extends Service implements
    GoogleApiClient.ConnectionCallbacks,
    GoogleApiClient.OnConnectionFailedListener,
    GpsStatus.Listener,
    LocationListener {

// Constants here....

private GoogleApiClient mGoogleApiClient;
private LocationRequest mLocationRequest;
private LocationManager locationManager;
// Flag that indicates if a request is underway.
private boolean mInProgress;

private NotificationManagement myNotificationManager;
private Boolean servicesAvailable = false;

//And other variables here...

@Override
public void onCreate()
{
    super.onCreate();

    myNotificationManager = new NotificationManagement(getApplicationContext());
    myNotificationManager.displayMainNotification();

    mInProgress = false;
    // Create the LocationRequest object
    mLocationRequest = LocationRequest.create();
    // Use high accuracy
    mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    // Set the update interval
    mLocationRequest.setInterval(PREFERRED_INTERVAL);
    // Set the fastest update interval
    mLocationRequest.setFastestInterval(FASTEST_INTERVAL);

    servicesAvailable = servicesConnected();

    locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    locationManager.addGpsStatusListener(this);

    setUpLocationClientIfNeeded();
}

/**
 * Create a new location client, using the enclosing class to
 * handle callbacks.
 */
protected synchronized void buildGoogleApiClient()
{
    this.mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .addApi(LocationServices.API)
            .build();
}

private boolean servicesConnected()
{

    // Check that Google Play services is available
    int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);

    // If Google Play services is available
    if (ConnectionResult.SUCCESS == resultCode)
    {
        return true;
    }
    else
    {
        return false;
    }
}

public int onStartCommand(Intent intent, int flags, int startId)
{
    super.onStartCommand(intent, flags, startId);

    if (!servicesAvailable || mGoogleApiClient.isConnected() || mInProgress)
        return START_STICKY;

    setUpLocationClientIfNeeded();
    if (!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting() && !mInProgress)
    {
        mInProgress = true;
        mGoogleApiClient.connect();
    }
    return START_STICKY;
}


private void setUpLocationClientIfNeeded()
{
    if (mGoogleApiClient == null)
        buildGoogleApiClient();
}

public void onGpsStatusChanged(int event)
{

}

// Define the callback method that receives location updates
@Override
public void onLocationChanged(Location location)
{
    simpleGPSFilter(location);
}

// Other fancy and needed stuff here...

/**
 * "Stupid" filter that utilizes experience data to filter out location noise.
 * @param location Location object carrying all the needed information
 */
private void simpleGPSFilter(Location location)
{
    //Loading all the required variables
    int signalPower = 0;
    satellites = 0;
    // Getting the satellites
    mGpsStatus = locationManager.getGpsStatus(mGpsStatus);
    Iterable<GpsSatellite> sats = mGpsStatus.getSatellites();
    if (sats != null)
    {
        for (GpsSatellite sat : sats)
        {
            if (sat.usedInFix())
            {
                satellites++;
                signalPower += sat.getSnr();
            }
        }
    }
    if (satellites != 0)
        signalPower = signalPower/satellites;
    mySpeed = (location.getSpeed() * 3600) / 1000;
    myAccuracy = location.getAccuracy();
    myBearing = location.getBearing();
    latitude = location.getLatitude();
    longitude = location.getLongitude();
    Log.i("START OF CYCLE", "START OF CYCLE");
    Log.i("Sat Strength", Integer.toString(signalPower));
    Log.i("Locked Sats", Integer.toString(satellites));

    // Do the math for the coordinates distance
    /*
     * Earth's radius at given Latitude.
     * Formula: Radius = sqrt( ((equatorR^2 * cos(latitude))^2 + (poleR^2 * sin(latitude))^2 ) / ((equatorR * cos(latitude))^2 + (poleR * sin(latitude))^2)
     * IMPORTANT: Math lib uses radians for the trigonometry equations so do not forget to use toRadians()
     */
    Log.i("Lat for Radius", Double.toString(latitude));
    double earthRadius = Math.sqrt((Math.pow((EARTH_RADIUS_EQUATOR * EARTH_RADIUS_EQUATOR * Math.cos(Math.toRadians(latitude))), 2)
            + Math.pow((EARTH_RADIUS_POLES * EARTH_RADIUS_POLES * Math.cos(Math.toRadians(latitude))), 2))
            / (Math.pow((EARTH_RADIUS_EQUATOR * Math.cos(Math.toRadians(latitude))), 2)
            + Math.pow((EARTH_RADIUS_POLES * Math.cos(Math.toRadians(latitude))), 2)));
    Log.i("Earth Radius", Double.toString(earthRadius));

    /*
     * Calculating distance between 2 points on map using the Haversine formula (arctangent writing) with the following algorithm
     * latDifference = latitude - lastLatitude;
     * lngDifference = longitude - lastLongitude;
     * a = (sin(latDifference/2))^2 + cos(lastLatitude) * cos(latitude) * (sin(lngDifference/2))^2
     * c = 2 * atan2( sqrt(a), sqrt(1-a) )
     * distance = earthRadius * c
     */
    double latDifference = latitude - lastLatitude;
    double lngDifference = longitude - lastLongitude;
    double a = Math.pow((Math.sin(Math.toRadians(latDifference / 2))), 2) + (Math.cos(Math.toRadians(lastLatitude))
            * Math.cos(Math.toRadians(latitude))
            * Math.pow((Math.sin(Math.toRadians(lngDifference / 2))), 2));
    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    double distance = earthRadius * c;
    Log.i("New point distance", Double.toString(distance));

    // Filter logic
    // Make an initial location log
    if ((!isInit) && (myAccuracy < ACCEPTED_ACCURACY))
    {
        isInit = true;
        lastLatitude = latitude;
        lastLongitude = longitude;
        logLocations(location);
    }
    else
    {
        // Satellite lock (use of GPS) on the higher level
        if (satellites == 0)
        {
            // Accuracy filtering at the second level
            if (myAccuracy < ACCEPTED_ACCURACY)
            {
                if ((distance > ACCEPTED_DISTANCE))
                {
                    lastLatitude = latitude;
                    lastLongitude = longitude;
                    logLocations(location);
                    Log.i("Location Logged", "No Sats");
                    /*
                    // Calculate speed in correlation to perceived movement
                    double speed = distance / (PREFERRED_INTERVAL / 1000);  // TODO: Need to make actual time dynamic as the fused location does not have fixed timing
                    if (speed < ACCEPTED_SPEED)
                    {
                        lastLatitude = latitude;
                        lastLongitude = longitude;
                        logLocations(location);
                    } */
                }
            }
        }
        else if ((satellites < 4) && (signalPower > ACCEPTED_SIGNAL))
        {
            if (myAccuracy < (ACCEPTED_ACCURACY + 50))
            {
                logLocations(location);
                Log.i("Location Logged", "With Sats");
            }
        }
        else
        {
            if (myAccuracy < (ACCEPTED_ACCURACY + 100))
            {
                lastSpeed = mySpeed;
                lastBearing = myBearing;
                lastLatitude = latitude;
                lastLongitude = longitude;
                logLocations(location);
                Log.i("Location Logged", "With Good Sats");
            }
        }
    }
    Log.i("END OF CYCLE", "END OF CYCLE");
}

private void logLocations(Location location)
{
    String myprovider = "false";

    String temp = timestampFormat.format(location.getTime());
    MySQLiteHelper dbHelper = new MySQLiteHelper(getApplicationContext());

    try
    {
        dbHelper.createEntry(latitude, longitude, allschemes, temp, mySpeed, myAccuracy, myBearing, myprovider, satellites);
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }

    CheckAutoArrive(String.valueOf(latitude), String.valueOf(longitude));

}

これは、必要と思われるコードの一部です。地図上の 2 点間の緯度と距離から地球の半径を計算するための数学とともに、すべてのフィルタリング コードをそこに残します。必要な方はご自由にお使いください。

コンパスアプリは実際にシステムに衛星を取得させることができますが、私のアプリはできません. 実際に位置情報サービスを強制的に読み取る方法はありますか? Fused Location が実際に GPS を使用しているのに、Location Manager がそれを認識していない可能性はありますか?

最後に、このアプリケーションは異なるバージョンの Android を搭載した他のデバイス (タブレットではなくスマートフォン) でテストされており、正常に動作しているようです。

どんなアイデアでも大歓迎です。そしてもちろん、言い忘れたことは何でも聞いてください。

編集:私の実際の質問はテキストに隠されているので、それらをレイアウトします:

1) Fused Location から取得した位置データと、同期している Location Manager からのみ取得できる残りの GPS データは同期しているのか、それとも、位置を取得できる可能性があるが、特定のポイントのロックされた衛星の数が間違っているのか? ?

2) アプリケーションがサテライトへのロックを取得できないが、ロックが別のアプリケーションからのものである場合、アプリケーションによって適切に使用されているように見えるという奇妙な動作の背後にある理由は何ですか? これをさらに奇妙にするために、これは Nexus 7 (Android 6.0.1) で発生しますが、異なる Android バージョンでテストされた他のデバイスでは発生しません。

4

1 に答える 1

15

私の理解から:

1)

FusedLocationApi は、クライアント デバイス (WiFi、CellTower、GPS、Bluetooth) 上の関連するプロバイダーから新しい測定値があるたびに、新しい場所を返します。この読み取り値は、以前の位置推定値と融合されます (おそらく拡張カルマン フィルターなどを使用します)。結果として得られる場所の更新は、いくつかのソースの融合された推定値です。これが、個々のプロバイダーからのメタデータが添付されていない理由です。

そのため、API から取得した位置データは、LocationManagerから取得した純粋な GPS の読み取り値と一致する場合があります(GPS が最新の関連する位置情報源である場合) が、そうである必要はありません。したがって、最後の純粋な GPS 読み取りから取得された衛星の数は、FusedLocationApi によって返される最新の Location に適用される場合と適用されない場合があります。

要するに:

LocationManager から取得した位置情報が FusedLocationApi からの位置情報と同期しているという保証はありません

2)

最初に: この問題の根本原因を特定するには、複数の場所で複数のデバイスをテストする必要があります。お願いして以来

奇妙な行動の背後にある理由はですか?

そこで 1 つの理論を提示します。LocationManager と FusedLocationApi が完全に別々に機能すると仮定すると、GPS のみに依存しているため、LocationManager は修正を取得するのに苦労している可能性があります。GPS に加えてNETWORK_PROVIDERを使用して、最初の修正までの時間を短縮してみてください(したがって、LocationManager がAssisted GPSを利用できるようになります)。他のアプリ (Compass アプリなど) はほぼ確実にこれを行っており、これがより迅速な修正が得られる理由を説明しています. 注: GPS データの受信を開始したら、もちろんネットワーク プロバイダーの登録を解除できます。または、そのままにしておくが、単にその更新を無視します。

これは、奇妙な動作の 1 つの考えられる説明です。位置情報の動作は、デバイス、OS、GPS チップセット、ファームウェア、および現在の位置に依存することにおそらく気付いているでしょう。そのため、手動で行う (つまり、FusedLocationApi を使用しない) 場合は、かなり実験する必要があります。


答えに加えて、あなたの問題に対する意見を述べさせてください(一粒の塩で解釈してください;-):非常に異なるユースケース用に作成されたものではない2つのものを組み合わせようとしていると思います組み合わせるという意味です。

衛星の数を取得することは、まったく技術的なことです。アプリケーションが GNSS について教えない限り、エンド ユーザーはこの種の情報に興味を持つことはありません。内部分析目的で記録したい場合は問題ありませんが、この情報が利用できない状況に対処できる必要があります。

シナリオ 1 : 何らかの理由で、GPS 測定値の詳細な (技術的な) 仕様が絶対に必要であると判断しました。その場合は、昔ながらの方法で自分でロジックを構築してください。つまり、LocationManager を介して GPS の読み取り値を要求し (ネットワーク プロバイダーを使用してこのプロセスを高速化することもできます)、それを独自に融合します。ただし、このシナリオでは FusedLocationApi に触れないでください。今日では時代遅れで難解に思えるかもしれませんが、DIY フュージョン ロジックで LocationManager を使用することは、少数のユース ケースでは依然として完全に理にかなっています。API がまだ残っているのはそのためです。

シナリオ 2 : クライアントの場所に関する迅速かつ正確な最新情報を取得したいだけです。その場合は、希望する更新頻度と精度を指定して、FusedLocationApi に任せてください。FusedLocationApi は過去数年間で長い道のりを歩んできました。今日では、DIY ロジックよりも位置情報を取得する方法をより迅速かつ適切に把握できるようになる可能性があります。これは、位置情報を取得することは、クライアント デバイスの機能 (チップセット、ファームウェア、OS、GPS、WiFi、GSM/LTE、Bluetooth など) と、物理的な環境 (WiFi/CellTower /近く、屋内または屋外、晴天または都会の峡谷などの Bluetooth 信号)。このシナリオでは、手動プロバイダーに触れないでください. その場合、個々のプロバイダーの読み取り値と融合された結果との関係について意味のある推論を行うことを期待しないでください。


最後に 2 つのコメント:

于 2016-10-21T09:45:43.840 に答える