// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.elevation;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.gpx.WayPoint;
import org.openstreetmap.josm.plugins.elevation.gpx.GeoidCorrectionKind;
/**
* Provides methods to access way point attributes and some utility methods regarding elevation stuff (
* e. g. special text formats, unit conversion, geoid calc).
* @author Oliver Wieland <oliver.wieland@online.de>
*/
public final class ElevationHelper {
private ElevationHelper() {
// Hide default constructor for utilities classes
}
public static double METER_TO_FEET = 3.280948;
/* Countries which use the imperial system instead of the metric system. */
private static String[] IMPERIAL_SYSTEM_COUNTRIES = {
"en_US", /* USA */
"en_CA", /* Canada */
"en_AU", /* Australia */
"en_NZ", /* New Zealand */
// "de_DE", /* for testing only */
"en_ZA" /* South Africa */
};
/** The 'no elevation' data magic. */
public static double NO_ELEVATION = Double.NaN;
/**
* The name of the elevation height of a way point.
*/
public static final String HEIGHT_ATTRIBUTE = "ele";
private static UnitMode unitMode = UnitMode.NotSelected;
private static GeoidCorrectionKind geoidKind = GeoidCorrectionKind.None;
/** The HGT reader instance. */
private static HgtReader hgt = new HgtReader();
/**
* Gets the current mode of GEOID correction.
*/
public static GeoidCorrectionKind getGeoidKind() {
return geoidKind;
}
public static void setGeoidKind(GeoidCorrectionKind geoidKind) {
ElevationHelper.geoidKind = geoidKind;
}
/**
* Gets the current unit mode (metric or imperial).
*/
public static UnitMode getUnitMode() {
//TODO: Use this until /JOSM/src/org/openstreetmap/josm/gui/NavigatableComponent.java
// has a an appropriate method
// unit mode already determined?
if (unitMode != UnitMode.NotSelected) {
return unitMode;
}
// Set default
unitMode = UnitMode.Metric;
// Check if user could prefer imperial system
Locale l = Locale.getDefault();
for (int i = 0; i < IMPERIAL_SYSTEM_COUNTRIES.length; i++) {
String ctry = l.toString();
if (IMPERIAL_SYSTEM_COUNTRIES[i].equals(ctry)) {
unitMode = UnitMode.Imperial;
}
}
return unitMode;
}
/**
* Gets the unit string for elevation ("m" or "ft").
*/
public static String getUnit() {
switch (getUnitMode()) {
case Metric:
return "m";
case Imperial:
return "ft";
default:
throw new RuntimeException("Invalid or unsupported unit mode: " + unitMode);
}
}
/**
* Checks if given value is a valid elevation value.
*
* @param ele the ele
* @return true, if is valid elevation
*/
public static boolean isValidElevation(double ele) {
return !Double.isNaN(ele);
}
/**
* Gets the elevation (Z coordinate) of a GPX way point in meter or feet (for
* US, UK, ZA, AU, NZ and CA).
*
* @param wpt
* The way point instance.
* @return The x coordinate or <code>NO_ELEVATION</code>, if the given way point is null or contains
* not height attribute.
*/
public static double getElevation(WayPoint wpt) {
if (wpt == null) return NO_ELEVATION;
// try to get elevation from HGT file
double eleInt = getSrtmElevation(wpt.getCoor());
if (isValidElevation(eleInt)) {
return convert(eleInt);
}
// no HGT, check for elevation data in GPX
if (!wpt.attr.containsKey(HEIGHT_ATTRIBUTE)) {
// GPX has no elevation data :-(
return NO_ELEVATION;
}
// Parse elevation from GPX data
String height = wpt.getString(ElevationHelper.HEIGHT_ATTRIBUTE);
try {
double z = Double.parseDouble(height);
return convert(z);
} catch (NumberFormatException e) {
System.err.println(String.format(
"Cannot parse double from '%s': %s", height, e
.getMessage()));
return NO_ELEVATION;
}
}
private static double getElevation(LatLon ll) {
double ele = getSrtmElevation(ll);
//System.out.println("Get elevation " + ll + " => " + ele);
return convert(ele);
}
/**
* Converts the value to feet, if required.
*
* @param ele the elevation to convert
* @return the double
*/
private static double convert(double ele) {
if (isValidElevation(ele)) {
if (getUnitMode() == UnitMode.Imperial) {
// translate to feet
return meter2Feet(ele);
} else {
// keep 'as is'
return ele;
}
}
return NO_ELEVATION;
}
/**
* Computes the slope <b>in percent</b> between two way points. E. g. an elevation gain of 12m
* within a distance of 100m is equal to a slope of 12%.
*
* @param w1 the first way point
* @param w2 the second way point
* @return the slope in percent
*/
public static double computeSlope(LatLon w1, LatLon w2) {
// same coordinates? -> return 0, if yes
if (w1.equals(w2)) return 0;
// get distance in meters and divide it by 100 in advance
double distInMeter = convert(w1.greatCircleDistance(w2) / 100.0);
// get elevation (difference) - is converted automatically to feet
int ele1 = (int) ElevationHelper.getElevation(w1);
int ele2 = (int) ElevationHelper.getElevation(w2);
int dH = ele2 - ele1;
// Slope in percent is define as elevation gain/loss in meters related to a distance of 100m
return dH / distInMeter;
}
/**
* Converts meter into feet
*
* @param meter the meter
* @return the double
*/
public static double meter2Feet(double meter) {
return meter * METER_TO_FEET;
}
/**
* Gets the elevation string for a given elevation, e. g "300m" or "800ft".
*/
public static String getElevationText(int elevation) {
return String.format("%d %s", elevation, getUnit());
}
/**
* Gets the elevation string for a given elevation, e. g "300m" or "800ft".
*/
public static String getElevationText(double elevation) {
return String.format("%d %s", (int) Math.round(elevation), getUnit());
}
/**
* Gets the elevation string for a given way point, e. g "300m" or "800ft".
*
* @param wpt the way point
* @return the elevation text
*/
public static String getElevationText(WayPoint wpt) {
if (wpt == null) return "-";
int elevation = (int) Math.round(ElevationHelper.getElevation(wpt));
return String.format("%d %s", elevation, getUnit());
}
/**
* Get the time string for a given way point.
*/
public static String getTimeText(WayPoint wpt) {
if (wpt == null) return null;
int hour = ElevationHelper.getHourOfWayPoint(wpt);
int min = ElevationHelper.getMinuteOfWayPoint(wpt);
return String.format("%02d:%02d", hour, min);
}
/**
* Gets the SRTM elevation (Z coordinate) of the given coordinate.
*
* @param ll
* The coordinate.
* @return The z coordinate or {@link Double#NaN}, if elevation value could not be obtained
* not height attribute.
*/
public static double getSrtmElevation(LatLon ll) {
if (ll != null) {
// Try to read data from SRTM file
// TODO: Option to switch this off
double eleHgt = hgt.getElevationFromHgt(ll);
//System.out.println("Get elevation from HGT " + ll + " => " + eleHgt);
if (isValidElevation(eleHgt)) {
return eleHgt;
}
}
return NO_ELEVATION;
}
/**
* Checks given area for SRTM data.
*
* @param bounds the bounds/area to check
* @return true, if SRTM data are present; otherwise false
*/
public static boolean hasSrtmData(Bounds bounds) {
if (bounds == null) return false;
LatLon tl = bounds.getMin();
LatLon br = bounds.getMax();
return isValidElevation(getSrtmElevation(tl)) &&
isValidElevation(getSrtmElevation(br));
}
/*
* Gets the geoid height for the given way point. See also {@link
* GeoidData}.
*/
public static byte getGeoidCorrection(WayPoint wpt) {
/*
int lat = (int)Math.round(wpt.getCoor().lat());
int lon = (int)Math.round(wpt.getCoor().lon());
byte geoid = GeoidData.getGeoid(lat, lon);
System.out.println(
String.format("Geoid(%d, %d) = %d", lat, lon, geoid));
*/
return 0;
}
/**
* Reduces a given list of way points to the specified target size.
*
* @param origList
* The original list containing the way points.
* @param targetSize
* The desired target size of the list. The resulting list may
* contain fewer items, so targetSize should be considered as
* maximum.
* @return A list containing the reduced list.
*/
public static List<WayPoint> downsampleWayPoints(List<WayPoint> origList,
int targetSize) {
if (origList == null)
return null;
if (targetSize <= 0)
throw new IllegalArgumentException(
"targetSize must be greater than zero");
int origSize = origList.size();
if (origSize <= targetSize) {
return origList;
}
int delta = (int) Math.max(Math.ceil(origSize / targetSize), 2);
List<WayPoint> res = new ArrayList<>(targetSize);
for (int i = 0; i < origSize; i += delta) {
res.add(origList.get(i));
}
return res;
}
/**
* Gets the hour value of a way point in 24h format.
*/
public static int getHourOfWayPoint(WayPoint wpt) {
if (wpt == null) return -1;
Calendar calendar = GregorianCalendar.getInstance(); // creates a new calendar instance
calendar.setTime(wpt.getTime()); // assigns calendar to given date
return calendar.get(Calendar.HOUR_OF_DAY);
}
/**
* Gets the minute value of a way point in 24h format.
*/
public static int getMinuteOfWayPoint(WayPoint wpt) {
if (wpt == null) return -1;
Calendar calendar = GregorianCalendar.getInstance(); // creates a new calendar instance
calendar.setTime(wpt.getTime()); // assigns calendar to given date
return calendar.get(Calendar.MINUTE);
}
}