/*
* Copyright (c) 2013, Will Szumski
* Copyright (c) 2013, Doug Szumski
*
* This file is part of Cyclismo.
*
* Cyclismo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Cyclismo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cyclismo. If not, see <http://www.gnu.org/licenses/>.
*/
package org.cowboycoders.turbotrainers;
import org.fluxoid.utils.Conversions;
import org.fluxoid.utils.LatLongAlt;
import org.fluxoid.utils.LocationUtils;
import org.fluxoid.utils.TrapezoidIntegrator;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CourseTracker {
public static final double ZERO_CUTOFF = 0.0001;
private final double resolution;
private Map<Double, LatLongAlt> distanceLocationMap = new HashMap<Double, LatLongAlt>();
private Double[] distanceMarkers;
private int lastKnownDistanceMarkerIndex = 0;
private double speed = 0.0;
private Double lastTimeStamp;
private Double currentTimeStamp;
private TrapezoidIntegrator distanceIntegrator = new TrapezoidIntegrator();
private LatLongAlt nearestLocation;
/**
* Maps absolute distance travelled to course points.
*
* @param coursePoints to map distance from.
* @param resolution for interpolation
*/
public CourseTracker(List<LatLongAlt> coursePoints, double resolution) {
this.resolution = resolution;
double totalDistance = 0.0;
distanceLocationMap.put(totalDistance, coursePoints.get(0));
for (int i = 1; i < coursePoints.size(); i++) {
double distanceBetweenPoints = LocationUtils.getGradientCorrectedDistance(
coursePoints.get(i - 1), coursePoints.get(i));
totalDistance += distanceBetweenPoints;
if (distanceBetweenPoints < resolution) {
// assume the same location
continue;
}
distanceLocationMap.put(totalDistance, coursePoints.get(i));
}
distanceMarkers = distanceLocationMap.keySet().toArray(new Double[0]);
Arrays.sort(distanceMarkers);
nearestLocation = distanceLocationMap.get(distanceMarkers[0]);
}
/**
* Must be polled at frequency >= 1Hz
*/
public void updateSpeed(double newSpeed) {
// we assume speed doesn't change much between updates
this.speed = newSpeed * Conversions.KM_PER_HOUR_TO_METRES_PER_SECOND;
lastTimeStamp = currentTimeStamp;
currentTimeStamp = System.nanoTime() / (Math.pow(10, 6));
distanceIntegrator.add(System.nanoTime() / (Math.pow(10, 9)), speed);
double distance = distanceIntegrator.getIntegral();
if (newSpeed < ZERO_CUTOFF) {
// assume haven't moved
return;
}
Double key;
Double nextKey;
double delta;
// loop gives us index of last marker passed
for (int i = lastKnownDistanceMarkerIndex; i < distanceMarkers.length; i++) {
key = distanceMarkers[i];
lastKnownDistanceMarkerIndex = i;
delta = distance - key;
if (delta < resolution) {
if (delta < 0) {
// we haven't yet reached the point, so pick the one previous
lastKnownDistanceMarkerIndex = i - 1;
if (lastKnownDistanceMarkerIndex < 0) {
//special case for the first point
lastKnownDistanceMarkerIndex = 0;
}
//key = distanceMarkers[lastKnownDistanceMarkerIndex];
}
break;
}
}
if (lastKnownDistanceMarkerIndex < distanceMarkers.length - 1) {
nextKey = distanceMarkers[lastKnownDistanceMarkerIndex + 1];
} else { // must have reached end
nearestLocation = distanceLocationMap.get(distanceMarkers[distanceMarkers.length - 1]);
return;
}
LatLongAlt previous = getNearestLocation();
LatLongAlt next = distanceLocationMap.get(nextKey);
if (lastTimeStamp == null) {
//only polled speed once
return;
}
double timeDelta = currentTimeStamp - lastTimeStamp;
LatLongAlt current;
while ((current = LocationUtils.getLocationBetweenPoints(previous, next, speed, timeDelta))
== null) {
lastKnownDistanceMarkerIndex++;
if (lastKnownDistanceMarkerIndex < distanceMarkers.length - 1) {
nextKey = distanceMarkers[lastKnownDistanceMarkerIndex + 1];
} else { // must have reached end
current = distanceLocationMap.get(nextKey);
break;
}
next = distanceLocationMap.get(nextKey);
}
//distance += LocationUtils.getDistance(previous, current);
nearestLocation = current;
}
public double getDistance() {
return distanceIntegrator.getIntegral();
}
/**
* @returns the nearest location on the course (that has been passed)
*/
public LatLongAlt getNearestLocation() {
return nearestLocation;
}
public boolean hasFinished() {
if (lastKnownDistanceMarkerIndex < distanceMarkers.length - 1) {
return false;
}
return true;
}
public double getCurrentGradient() {
if (hasFinished()) {
return 0.0;
}
final Double nextLocationKey = distanceMarkers[lastKnownDistanceMarkerIndex + 1];
final Double previousLocationKey = distanceMarkers[lastKnownDistanceMarkerIndex];
final LatLongAlt next = distanceLocationMap.get(nextLocationKey);
final LatLongAlt previous = distanceLocationMap.get(previousLocationKey);
// as we are interpolating elevation data between markers, we don't gain any precision
// by using the currentLocation (getCurrentLocation) compared to using the previous marker
double gradient = LocationUtils
.getLocalisedGradient(previous, next);
if (gradient == Double.NaN) {
//two points at same lat/long
return 0.0;
}
return gradient;
}
public static void main(String[] args) {
LatLongAlt l1 = new LatLongAlt(50.066389, 5.715, 1000);
LatLongAlt l2 = new LatLongAlt(58.643889, 3.07, 4000);
List<LatLongAlt> locations = LocationUtils.interpolateBetweenPoints(l1, l2, 1000);
locations.addAll(LocationUtils.interpolateBetweenPoints(l2, l1, 1000)); // Back to start
CourseTracker ct = new CourseTracker(locations, 0.001);
for (Double marker : ct.distanceMarkers) {
System.out.println(marker);
}
System.out.println("finished");
}
}