// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.elevation.gpx;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.gpx.WayPoint;
import org.openstreetmap.josm.plugins.elevation.ElevationHelper;
import org.openstreetmap.josm.plugins.elevation.IElevationProfile;
/**
* Base class for an elevation profile. An elevation profile is constructed out
* of a set of way points. The profile computes min/max/average height from the
* full way point set and then reduces the number of way points to a given
* amount, if necessary.
*
* The computation is done via implementing {@link IGpxWaypointVisitor},
* subclasses may override the {@link ElevationProfile#visitWayPoint(WayPoint)}
* method to compute own values or run specific actions. The computation is
* triggered by calling {@link ElevationProfile#updateValues()}.
*
* Elevation profiles can break down into further child profiles. This is
* intended to show different levels of details, if the number of way points
* exceed the display space (which is usually the case).
*
* {@link IElevationProfile} {@link IGpxWaypointVisitor} {@link GpxIterator}
*
* @author Oliver Wieland <oliver.wieland@online.de>
*
*/
public class ElevationProfile implements IElevationProfile,
IGpxWaypointVisitor {
public static final int WAYPOINT_START = 0;
public static final int WAYPOINT_END = 1;
public static final int WAYPOINT_MIN = 2;
public static final int WAYPOINT_MAX = 3;
private String name;
private int minHeight;
private int maxHeight;
private int avrgHeight;
private double dist;
private Date start = new Date();
private Date end = new Date();
private final WayPoint[] importantWayPoints = new WayPoint[4];
private IElevationProfile parent;
private int sumEle; // temp var for average height
private List<WayPoint> wayPoints;
private int numWayPoints; // cached value
private int gain;
private int lastEle;
private Bounds bounds;
private static boolean ignoreZeroHeight = true;
/**
* Creates a name elevation profile without any way points.
*/
public ElevationProfile(String name) {
this(name, null, null, 0);
}
/**
* Creates a name elevation profile with a given set of way points.
*
* @param name
* The name of the profile.
* @param parent
* The (optional) parent profile.
* @param wayPoints
* The list containing the way points of the profile.
* @param sliceSize
* The requested target size of the profile.
*/
public ElevationProfile(String name, IElevationProfile parent,
List<WayPoint> wayPoints, int sliceSize) {
super();
this.name = name;
this.parent = parent;
setWayPoints(wayPoints);
}
/**
* Checks if zero elevation should be ignored or not.
*
* @return true, if is ignore zero height
*/
public static boolean isIgnoreZeroHeight() {
return ignoreZeroHeight;
}
/**
* Sets the ignore zero height.
*
* @param ignoreZeroHeight the new ignore zero height
*/
public static void setIgnoreZeroHeight(boolean ignoreZeroHeight) {
ElevationProfile.ignoreZeroHeight = ignoreZeroHeight;
}
@Override
public void updateElevationData() {
updateValues();
}
/**
* Revisits all way points and recomputes the characteristic values like
* min/max elevation.
*/
protected void updateValues() {
if (wayPoints == null)
return;
int n = this.wayPoints.size();
if (n == 0)
return;
start = new Date();
end = new Date(0L);
this.minHeight = Integer.MAX_VALUE;
this.maxHeight = Integer.MIN_VALUE;
sumEle = 0;
gain = 0;
lastEle = 0;
for (WayPoint wayPoint : this.wayPoints) {
visitWayPoint(wayPoint);
}
if (this.minHeight == Integer.MAX_VALUE && this.maxHeight == Integer.MIN_VALUE) {
// file does not contain elevation data at all
minHeight = 0;
maxHeight = 0;
setMinWayPoint(wayPoints.get(0));
setMaxWayPoint(wayPoints.get(n-1));
}
//if (start.after(end) || start.equals(end)) {
// GPX does not contain time stamps -> use sequential order
setStart(wayPoints.get(0));
setEnd(wayPoints.get(n-1));
//}
avrgHeight = sumEle / n;
}
/**
* Gets the name of the profile.
*/
@Override
public String getName() {
return name;
}
/**
* Sets the name of the profile.
* @param name The new name of the profile.
*/
public void setName(String name) {
this.name = name;
}
/**
* Sets the way point with the lowest elevation.
* @param wp The way point instance having the lowest elevation.
*/
protected void setMinWayPoint(WayPoint wp) {
importantWayPoints[WAYPOINT_MIN] = wp;
this.minHeight = (int) ElevationHelper.getElevation(wp);
}
/**
* Sets the way point with the highest elevation.
* @param wp The way point instance having the highest elevation.
*/
protected void setMaxWayPoint(WayPoint wp) {
importantWayPoints[WAYPOINT_MAX] = wp;
this.maxHeight = (int) ElevationHelper.getElevation(wp);
}
/**
* Sets the average height.
*/
protected void setAvrgHeight(int avrgHeight) {
this.avrgHeight = avrgHeight;
}
/**
* Sets the very first way point.
*/
protected void setStart(WayPoint wp) {
importantWayPoints[WAYPOINT_START] = wp;
this.start = wp.getTime();
}
/**
* Sets the very last way point.
*/
protected void setEnd(WayPoint wp) {
importantWayPoints[WAYPOINT_END] = wp;
this.end = wp.getTime();
}
public void setParent(IElevationProfile parent) {
this.parent = parent;
}
/**
* Sets the way points of this profile.
*/
public void setWayPoints(List<WayPoint> wayPoints) {
if (this.wayPoints != wayPoints) {
this.wayPoints = new ArrayList<>(wayPoints);
numWayPoints = wayPoints != null ? wayPoints.size() : 0;
updateValues();
}
}
/**
* Checks if the given index is valid or not.
*
* @param index
* The index to check.
* @return true, if the given index is valid; otherwise false.
*/
protected boolean checkIndex(int index) {
return index >= 0 && index < getNumberOfWayPoints();
}
@Override
public int elevationValueAt(int i) {
if (checkIndex(i)) {
return (int) ElevationHelper.getElevation(wayPoints.get(i));
} else {
throw new IndexOutOfBoundsException(String.format(
"Invalid index: %d, expected 0..%d", i,
getNumberOfWayPoints()));
}
}
@Override
public int getAverageHeight() {
return avrgHeight;
}
@Override
public List<IElevationProfile> getChildren() {
return null;
}
@Override
public Date getEnd() {
return end;
}
@Override
public int getMaxHeight() {
return maxHeight;
}
@Override
public int getMinHeight() {
return minHeight;
}
/**
* Gets the difference between min and max elevation.
*/
@Override
public int getHeightDifference() {
return maxHeight - minHeight;
}
/**
* Gets the elevation gain.
*/
@Override
public int getGain() {
return gain;
}
@Override
public double getDistance() {
return dist; // dist is in meters
}
/**
* Sets the distance of the elevation profile.
*/
protected void setDistance(double dist) {
this.dist = dist;
}
/**
* Returns the time between start and end of the track.
*/
@Override
public long getTimeDifference() {
WayPoint wp1 = getStartWayPoint();
WayPoint wp2 = getEndWayPoint();
if (wp1 != null && wp2 != null) {
long diff = wp2.getTime().getTime() - wp1.getTime().getTime();
return diff;
}
return 0L;
}
@Override
public IElevationProfile getParent() {
return parent;
}
@Override
public Date getStart() {
return start;
}
@Override
public WayPoint getEndWayPoint() {
return importantWayPoints[WAYPOINT_END];
}
@Override
public WayPoint getMaxWayPoint() {
return importantWayPoints[WAYPOINT_MAX];
}
@Override
public WayPoint getMinWayPoint() {
return importantWayPoints[WAYPOINT_MIN];
}
@Override
public WayPoint getStartWayPoint() {
return importantWayPoints[WAYPOINT_START];
}
@Override
public List<WayPoint> getWayPoints() {
return wayPoints;
}
@Override
public int getNumberOfWayPoints() {
return numWayPoints; // wayPoints != null ? wayPoints.size() : 0;
}
/**
* Gets the coordinate bounds of this profile. See {@link Bounds} for details.
*
* @return the bounds of this elevation profile
*/
@Override
public Bounds getBounds() {
return bounds;
}
/**
* Gets a flag indicating whether the associated way points contained
* elevation data or not. This is the case if min and max height or both
* zero.
*/
@Override
public boolean hasElevationData() {
return minHeight != maxHeight;
}
/**
* Visits a way point in order to update statistical values about the given
* way point list.
*/
@Override
public void visitWayPoint(WayPoint wp) {
if (wp.getTime().after(end)) {
setEnd(wp);
}
if (wp.getTime().before(start)) {
setStart(wp);
}
// update boundaries
if (bounds == null) {
bounds = new Bounds(wp.getCoor());
} else {
bounds.extend(wp.getCoor());
}
int ele = (int) ElevationHelper.getElevation(wp);
if (!isIgnoreZeroHeight() || ele > 0) {
if (ele > maxHeight) {
setMaxWayPoint(wp);
}
if (ele < minHeight) {
setMinWayPoint(wp);
}
if (ele > lastEle) {
gain += ele - lastEle;
}
sumEle += ele;
lastEle = ele;
}
}
@Override
public String toString() {
return name; /*"ElevationProfileBase [start=" + getStart() + ", end=" + getEnd()
+ ", minHeight=" + getMinHeight() + ", maxHeight="
+ getMaxHeight() + "]";*/
}
}