/**
Copyright 2015 Tim Engler, Rareventure LLC
This file is part of Tiny Travel Tracker.
Tiny Travel Tracker 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.
Tiny Travel Tracker 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 Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>.
*/
package com.rareventure.gps2.database;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import junit.framework.Assert;
import com.rareventure.android.Util;
public class SlopeChooser {
private HashSet<Point> pointsHash = new HashSet<SlopeChooser.Point>();
private double calcSlope;
private int calcMinX;
private int calcDist;
private long calcStartTimeMs, calcEndTimeMs;
public SlopeChooser()
{
}
public void add(int point, long time) {
pointsHash.add(new Point(point, time));
}
public void finishCalculation() {
finishCalculation(null);
}
public void finishCalculation(Integer lonmOffset) {
ArrayList<Point> points = new ArrayList<SlopeChooser.Point>(pointsHash);
Collections.sort(points);
Point bestP1=null, bestP2=null;
boolean bestIsLeft = false;
double minDist = Double.MAX_VALUE;
long minTimeMs = Long.MAX_VALUE, maxTimeMs = Long.MIN_VALUE;
if(lonmOffset != null)
{
//fix the points for the lonm offset
for(Point p : points)
{
p.x = Util.makeContinuousFromStartLonm(lonmOffset, p.x);
}
}
//go through all the points
for(int i1 = 0; i1 < points.size(); i1++)
{
Point p1 = points.get(i1);
minTimeMs = Math.min(minTimeMs, p1.y);
maxTimeMs = Math.max(maxTimeMs, p1.y);
//find the slope between the point and all the points after it
for(int i2 = i1+1; i2 < points.size(); i2++)
{
Point p2 = points.get(i2);
boolean leftSlopeFailed = false, rightSlopeFailed = false;
//a completely horizontal slope is invalid (dist, no time)
if(isSlopeInvalid(p1,p2))
continue;
//check if the slope line is valid for the left side or the right side. As long as all the other points
//are on the right, then it can be the left side, and vice versa.
// also find the point which should be the maximum, ie the other side. This is basically where the
// horizontal distance is the highest given any timeslice along the slope line.
Point rightSideMaxPoint = p1;
Point leftSideMinPoint = p1;
for(Point p3: points)
{
int side = calculateSideOfSlope(p3,p2,p1);
if(side < 0)
leftSlopeFailed = true;
else if(side > 0)
rightSlopeFailed = true;
if(!leftSlopeFailed)
{
if(calculateSideOfSlope(p3,p2,p1,rightSideMaxPoint)>0)
rightSideMaxPoint = p3;
}
if(!rightSlopeFailed)
{
if(calculateSideOfSlope(p3,p2,p1,leftSideMinPoint)<0)
leftSideMinPoint = p3;
}
if(leftSlopeFailed && rightSlopeFailed)
break;
}
//if on the left side of all points
if(!leftSlopeFailed)
{
double dist = calculateDist(p1,rightSideMaxPoint,p2,p1);
if(dist < minDist)
{
bestP2 = p2;
bestP1 = p1;
minDist = dist;
bestIsLeft = true;
}
if(dist < 0)
TAssert.fail("dist is "+dist);
}
//if on the right side of all the points
if(!rightSlopeFailed)
{
double dist = calculateDist(leftSideMinPoint,p1,p2,p1);
if(dist < minDist)
{
bestP2 = p2;
bestP1 = p1;
minDist = dist;
bestIsLeft = false;
}
if(dist < 0)
TAssert.fail("dist is "+dist);
}
}
}
calcSlope = calculateSlope(bestP2,bestP1);
double doubleMinX = calcSlope * (minTimeMs - bestP1.y ) + bestP1.x;
if(bestIsLeft)
{
calcMinX = (int)Math.floor(doubleMinX);
calcDist = (int)Math.ceil(doubleMinX + minDist) - calcMinX;
}
else
{
calcMinX = (int)Math.floor(doubleMinX - minDist);
calcDist = (int)Math.ceil(doubleMinX) - calcMinX;
}
calcStartTimeMs = minTimeMs;
calcEndTimeMs = maxTimeMs;
}
/**
* Calculates the distance as follows.
* Takes basispoint2 and adjusts it to where it is at the same time of basisPoint1
* from the line defined by slopeP2 to slopeP1.
* Then subtracts the difference.
* @param basisPoint1
* @param basisPoint2
* @param slopeP1
* @param slopeP2
* @return
*/
private double calculateDist(Point basisPoint1, Point basisPoint2, Point slopeP1, Point slopeP2) {
double basisPoint2XAtPoint1Time = calculateSlope(slopeP2, slopeP1) * (basisPoint1.y - basisPoint2.y) + basisPoint2.x;
return basisPoint2XAtPoint1Time - basisPoint1.x;
}
/**
*
* @param p3
* @param p2
* @param p1
* @return 1 if p3 is on the right side of the slope, starting from basisPoint, -1 if on the left and 0 if on the slope
*/
private int calculateSideOfSlope(Point p3, Point p2, Point p1, Point basisPoint) {
double slope1 = calculateSlope(p2,p1);
double pointAtP3 = slope1 * (p3.y - basisPoint.y) + basisPoint.x;
if(pointAtP3 < p3.x)
return 1;
if(pointAtP3 > p3.x)
return -1;
return 0;
}
/**
*
* @param p3
* @param p2
* @param p1
* @return 1 if p3 is on the right side of the slope, -1 if on the left and 0 if on the slope
*/
private int calculateSideOfSlope(Point p3, Point p2, Point p1) {
double slope1 = calculateSlope(p2,p1);
double pointAtP3 = slope1 * (p3.y - p1.y) + p1.x;
if(pointAtP3 < p3.x)
return 1;
if(pointAtP3 > p3.x)
return -1;
return 0;
}
private double calculateSlope(Point p2, Point p1) {
return ((double)p2.x - p1.x)/(p2.y - p1.y);
}
private boolean isSlopeInvalid(Point p1, Point p2) {
return p1.y == p2.y;
}
private static class Point implements Comparable<Point>
{
int x; //dist
long y; //time
public Point(int point, long time) {
this.x = point;
this.y = time;
}
@Override
public int compareTo(Point another) {
long v = this.y - another.y;
if(v < 0)
return -1;
if(v > 0)
return 1;
return this.x - another.x;
}
public boolean equals(Object another)
{
return x == ((Point)another).x && y == ((Point)another).y;
}
public int hashCode()
{
return (int) (x ^ y);
}
}
public int getMinX() {
return calcMinX;
}
public int getDist() {
return calcDist;
}
public double getSlope()
{
return calcSlope;
}
public long getStartTimeMs() {
return calcStartTimeMs;
}
public long getEndTimeMs() {
return calcEndTimeMs;
}
}