// 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() + "]";*/ } }