Sunday 7 February 2016

It's all in the application: Recon Instruments Snow 2 vs Paragliding: Part 6

Progressing along - next up, thermal core location - using GPS positioning coupled with variometer readings to determine the most likely position of the thermal core in relation to the pilot.

The most correct version would probably entail a least fit squares/orthogonal linear regression ala Eberly. Two challenges emerge if one goes this route:

  1. Implementation Complexity - the operations involved (including a descending Eigen vector sort) are complex and would be a nightmare to debug.
  2. Execution Complexity - the operations are math intensive. Either reduce the frequency of execution (and the accuracy of the result) or shuttle off to the GPU via shader computation.
The decision -> implement first, optimize second. Find a simpler implementation to begin with, evaluate, then decide if a more robust solution is needed.

That said, it doesn't mean one needs to completely ignore how the OLR solution goes about accomplishing its goal. In particular, the use of a centroid that acts as the point through which the best fit vector passes. 

The centroid is just the average of the points. Unmodified it would simply demarcate the middle of the sample set. But it we were to weight it with, oh, the vario data we could bias the position towards the side of the sample set with the strongest climb values. 

Weighting is a matter of determining the strongest vario value in the set:

for (ThermalSample sample : mSamples.values()) {
    if(sample.mVario > mVario)
        mVario = sample.mVario;
}


... and then reducing the weight of all other points by a geometric factor based on the difference. Samples with a vario reading equal to or less than zero are ignored and not added to the sample set.

Double weight = 1.0 - (mVario - sample.mVario) / mVario;
weight *= weight;

The resulting centroid calculation:
for(ThermalSample sample: mSamples.values()) {
    Double weight = 1.0 - (mVario - sample.mVario) / mVario;
    weight *= weight;
    weightSum += weight;
    altitudeSum += weight * sample.mLocation.getAltitude();
    latitudeSum += weight * sample.mLocation.getLatitude();
    longitudeSum += weight * sample.mLocation.getLongitude();
}

altitudeSum /= weightSum;
latitudeSum /= weightSum;
longitudeSum /= weightSum;

This calculation is done frequently as the sample dataset continually updates. To prevent data from going stale, pruning is done as new elements are added - currently based on age (> 60 s) and distance (> 200 m):


for(Iterator<Map.Entry<Double, ThermalSample>> it = mSamples.entrySet().iterator(); it.hasNext(); ) {
    Map.Entry<Double, ThermalSample> entry = it.next();
    Location location = entry.getValue().mLocation;
    if(location.distanceTo(currentLoc) > MAX_SAMPLE_DISTANCE || time - location.getTime() > MAX_SAMPLE_AGE) {
        it.remove();
    }

A far less complex solution vs. OLR and appears to give reasonable results with the test data. Displaying the result is done where the MFD used to sit. This new 'map' display is rendered using simple Android Canvas operations and can be purposed beyond simply displaying the current core guesstimate. The glide ratio and vario reading now have a permanent display just above the old MFD location - though smaller in size.

Thermal core is just ahead and to the right. Green means core strength is less than 2x current climb rate. Battery remaining in lower right corner.
Core further away. Red means core strength is more than 4x current climb rate. Yellow is used for 2 to 4 x. Vario is just above (currently reading 0.0 m/s).

The Refactor begins...

2 comments:

  1. Hi Mark,
    thanks for publishing your code - I was toying with the idea of doing something similar. I have the code compiled and running on a Recon Jet. Are you changing screen views via the buttons on the Snow?
    Cheers,
    Peter

    ReplyDelete
  2. Hi Peter,

    I had toyed with the idea of button support but came to a pair of realizations -> the Snow2 uses a wrist mount remote that is too easy to lose (unlike the Jet) and in flight I want to be as hands off as possible with the device (minimize distraction).

    Lacking buttons, I tried having the MFD switch display values based on context (climb rate during thermaling, glide ratio while on glide) but it never quite got it right.

    That said ->
    Implementing button support would be easy and should done via a new Recon specific Provider coupled with a new Data Channel (values enumerating the various button states). That way when the Data Logger is completed, it will capture and be able to pay back the events (it allow for PiP video compositing) - switching views as the user switched them in flight.

    PS. as of today in the Git code there are a couple of bugs in:
    VarioGlideRatioDisplay::displayVario -> the comparison with min value should be looking at the abs of the vario.
    MapDisplay::doInBackground -> the relationship of thermal to pilot is backwards. Will be out testing fixes today/tomorrow.

    PPS. as a Jet user, you will likely want to disable the BootReciever - as you can start the app using the on device buttons.

    ReplyDelete