Changeset 30737 in osm for applications/editors/josm/plugins/ElevationProfile/src/org/openstreetmap
- Timestamp:
- 2014-10-18T23:07:52+02:00 (11 years ago)
- Location:
- applications/editors/josm/plugins/ElevationProfile/src/org/openstreetmap/josm/plugins/elevation
- Files:
-
- 10 edited
-
ColorMap.java (modified) (1 diff)
-
ElevationHelper.java (modified) (2 diffs)
-
HgtReader.java (modified) (1 diff)
-
gpx/ElevationModel.java (modified) (1 diff)
-
gpx/ElevationProfile.java (modified) (1 diff)
-
grid/EleVertex.java (modified) (1 diff)
-
grid/ElevationGridTile.java (modified) (1 diff)
-
gui/DefaultElevationProfileRenderer.java (modified) (1 diff)
-
gui/ElevationProfileDialog.java (modified) (1 diff)
-
gui/ElevationProfilePanel.java (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
applications/editors/josm/plugins/ElevationProfile/src/org/openstreetmap/josm/plugins/elevation/ColorMap.java
r30344 r30737 16 16 */ 17 17 public class ColorMap { 18 private List<ColorMapEntry> colorList;19 private String name;20 private static HashMap<String, ColorMap> colorMaps;18 private List<ColorMapEntry> colorList; 19 private String name; 20 private static HashMap<String, ColorMap> colorMaps; 21 21 22 static {23 colorMaps = new HashMap<String, ColorMap>();24 }22 static { 23 colorMaps = new HashMap<>(); 24 } 25 25 26 // Private ctor to enforce use of create27 private ColorMap() {28 }26 // Private ctor to enforce use of create 27 private ColorMap() { 28 } 29 29 30 public String getName() {31 return name;32 }30 public String getName() { 31 return name; 32 } 33 33 34 public void setName(String name) {35 this.name = name;36 }34 public void setName(String name) { 35 this.name = name; 36 } 37 37 38 /**39 * Gets the color according to the given elevation value.40 *41 * @param elevation the elevation42 * @return the color43 */44 public Color getColor(int elevation) {45 // empty color map?46 if (colorList == null || colorList.size() == 0) {47 return Color.white;48 }38 /** 39 * Gets the color according to the given elevation value. 40 * 41 * @param elevation the elevation 42 * @return the color 43 */ 44 public Color getColor(int elevation) { 45 // empty color map? 46 if (colorList == null || colorList.size() == 0) { 47 return Color.white; 48 } 49 49 50 // out of range?51 if (elevation < colorList.get(0).ele) {52 return colorList.get(0).getColor();53 }50 // out of range? 51 if (elevation < colorList.get(0).ele) { 52 return colorList.get(0).getColor(); 53 } 54 54 55 int last = colorList.size() - 1;56 if (elevation > colorList.get(last).ele) {57 return colorList.get(last).getColor();58 }55 int last = colorList.size() - 1; 56 if (elevation > colorList.get(last).ele) { 57 return colorList.get(last).getColor(); 58 } 59 59 60 // find elevation section61 for (int i = 0; i < last; i++) {62 ColorMapEntry e1 = colorList.get(i);63 ColorMapEntry e2 = colorList.get(i + 1);60 // find elevation section 61 for (int i = 0; i < last; i++) { 62 ColorMapEntry e1 = colorList.get(i); 63 ColorMapEntry e2 = colorList.get(i + 1); 64 64 65 // elevation within range?66 if (e1.getEle() <= elevation && e2.getEle() >= elevation) {65 // elevation within range? 66 if (e1.getEle() <= elevation && e2.getEle() >= elevation) { 67 67 68 // interpolate color between both69 double val = (elevation - e1.getEle()) / (double)(e2.getEle() - e1.getEle());70 return interpolate(e1.getColor(), e2.getColor(), val);71 }72 }68 // interpolate color between both 69 double val = (elevation - e1.getEle()) / (double)(e2.getEle() - e1.getEle()); 70 return interpolate(e1.getColor(), e2.getColor(), val); 71 } 72 } 73 73 74 // here we should never end!75 throw new RuntimeException("Inconsistent color map - found no entry for elevation " + elevation);76 }74 // here we should never end! 75 throw new RuntimeException("Inconsistent color map - found no entry for elevation " + elevation); 76 } 77 77 78 78 79 /**80 * Gets the color map with the given name.81 *82 * @param name the name83 * @return the map or <code>null</code>, if no such map exists84 */85 public static ColorMap getMap(String name) {86 if (colorMaps.containsKey(name)) {87 return colorMaps.get(name);88 }89 return null;90 }79 /** 80 * Gets the color map with the given name. 81 * 82 * @param name the name 83 * @return the map or <code>null</code>, if no such map exists 84 */ 85 public static ColorMap getMap(String name) { 86 if (colorMaps.containsKey(name)) { 87 return colorMaps.get(name); 88 } 89 return null; 90 } 91 91 92 /**93 * Gets the number of available color maps.94 *95 * @return the int96 */97 public static int size() {98 return colorMaps != null ? colorMaps.size() : 0;99 }92 /** 93 * Gets the number of available color maps. 94 * 95 * @return the int 96 */ 97 public static int size() { 98 return colorMaps != null ? colorMaps.size() : 0; 99 } 100 100 101 101 102 /**103 * Gets the available color map names.104 *105 * @param name the name106 * @return the map or <code>null</code>, if no such map exists107 */108 public static String[] getNames() {109 return colorMaps.keySet().toArray(new String[size()]);110 }102 /** 103 * Gets the available color map names. 104 * 105 * @param name the name 106 * @return the map or <code>null</code>, if no such map exists 107 */ 108 public static String[] getNames() { 109 return colorMaps.keySet().toArray(new String[size()]); 110 } 111 111 112 private static void registerColorMap(ColorMap newMap) {113 CheckParameterUtil.ensureParameterNotNull(newMap);114 colorMaps.put(newMap.getName(), newMap);115 }112 private static void registerColorMap(ColorMap newMap) { 113 CheckParameterUtil.ensureParameterNotNull(newMap); 114 colorMaps.put(newMap.getName(), newMap); 115 } 116 116 117 public static void unregisterColorMap(String name) {118 if (colorMaps.containsKey(name)) {119 colorMaps.remove(name);120 }121 }117 public static void unregisterColorMap(String name) { 118 if (colorMaps.containsKey(name)) { 119 colorMaps.remove(name); 120 } 121 } 122 122 123 public static Color interpolate(java.awt.Color c1, java.awt.Color c2, double ratio) {124 double r1 = 1 -ratio;125 // clip126 if (r1 < 0) r1 = 0d;127 if (r1 > 1) r1 = 1d;128 double r2 = 1 - r1;123 public static Color interpolate(java.awt.Color c1, java.awt.Color c2, double ratio) { 124 double r1 = 1 -ratio; 125 // clip 126 if (r1 < 0) r1 = 0d; 127 if (r1 > 1) r1 = 1d; 128 double r2 = 1 - r1; 129 129 130 int r = (int) Math.round((r1 * c1.getRed()) + (r2 * c2.getRed()));131 int g = (int) Math.round((r1 * c1.getGreen()) + (r2 * c2.getGreen()));132 int b = (int) Math.round((r1 * c1.getBlue()) + (r2 * c2.getBlue()));133 return new Color(r, g, b);134 }130 int r = (int) Math.round((r1 * c1.getRed()) + (r2 * c2.getRed())); 131 int g = (int) Math.round((r1 * c1.getGreen()) + (r2 * c2.getGreen())); 132 int b = (int) Math.round((r1 * c1.getBlue()) + (r2 * c2.getBlue())); 133 return new Color(r, g, b); 134 } 135 135 136 /**137 * Creates a color map using the given colors/elevation values.138 * Both arrays must have same length.139 *140 * @param name the name of the color map141 * @param colors the array containing the colors142 * @param ele the elevation values143 * @return the color map144 */145 public static ColorMap create(String name, Color[] colors, int[] ele) {146 CheckParameterUtil.ensureParameterNotNull(colors);147 CheckParameterUtil.ensureParameterNotNull(ele);136 /** 137 * Creates a color map using the given colors/elevation values. 138 * Both arrays must have same length. 139 * 140 * @param name the name of the color map 141 * @param colors the array containing the colors 142 * @param ele the elevation values 143 * @return the color map 144 */ 145 public static ColorMap create(String name, Color[] colors, int[] ele) { 146 CheckParameterUtil.ensureParameterNotNull(colors); 147 CheckParameterUtil.ensureParameterNotNull(ele); 148 148 149 if (colors.length != ele.length) {150 throw new IllegalArgumentException("Arrays colors and ele must have same length: " + colors.length + " vs " + ele.length);151 }149 if (colors.length != ele.length) { 150 throw new IllegalArgumentException("Arrays colors and ele must have same length: " + colors.length + " vs " + ele.length); 151 } 152 152 153 ColorMap map = new ColorMap();154 map.colorList = new ArrayList<ColorMap.ColorMapEntry>();155 map.name = name;156 for (int i = 0; i < ele.length; i++) {157 map.colorList.add(map.new ColorMapEntry(colors[i], ele[i]));158 }153 ColorMap map = new ColorMap(); 154 map.colorList = new ArrayList<>(); 155 map.name = name; 156 for (int i = 0; i < ele.length; i++) { 157 map.colorList.add(map.new ColorMapEntry(colors[i], ele[i])); 158 } 159 159 160 // sort by elevation161 Collections.sort(map.colorList);160 // sort by elevation 161 Collections.sort(map.colorList); 162 162 163 registerColorMap(map);164 return map;165 }163 registerColorMap(map); 164 return map; 165 } 166 166 167 167 168 class ColorMapEntry implements Comparable<ColorMapEntry> {169 private final int ele; // limit170 private final Color color;168 class ColorMapEntry implements Comparable<ColorMapEntry> { 169 private final int ele; // limit 170 private final Color color; 171 171 172 public ColorMapEntry(Color color, int ele) {173 super();174 this.color = color;175 this.ele = ele;176 }172 public ColorMapEntry(Color color, int ele) { 173 super(); 174 this.color = color; 175 this.ele = ele; 176 } 177 177 178 public int getEle() {179 return ele;180 }178 public int getEle() { 179 return ele; 180 } 181 181 182 public Color getColor() {183 return color;184 }182 public Color getColor() { 183 return color; 184 } 185 185 186 @Override187 public int compareTo(ColorMapEntry o) {188 return this.ele - o.ele;189 }190 }186 @Override 187 public int compareTo(ColorMapEntry o) { 188 return this.ele - o.ele; 189 } 190 } 191 191 } -
applications/editors/josm/plugins/ElevationProfile/src/org/openstreetmap/josm/plugins/elevation/ElevationHelper.java
r30344 r30737 19 19 */ 20 20 public class ElevationHelper { 21 public static double METER_TO_FEET = 3.280948;22 23 /* Countries which use the imperial system instead of the metric system. */24 private static String IMPERIAL_SYSTEM_COUNTRIES[] = {25 "en_US", /* USA */26 "en_CA", /* Canada */27 "en_AU", /* Australia */28 "en_NZ", /* New Zealand */29 // "de_DE", /* for testing only */30 "en_ZA" /* South Africa */31 };32 33 /** The 'no elevation' data magic. */34 public static double NO_ELEVATION = Double.NaN;35 36 /**37 * The name of the elevation height of a way point.38 */39 public static final String HEIGHT_ATTRIBUTE = "ele";40 41 private static UnitMode unitMode = UnitMode.NotSelected;42 43 private static GeoidCorrectionKind geoidKind = GeoidCorrectionKind.None;44 45 /** The HGT reader instance. */46 private static HgtReader hgt = new HgtReader();47 48 /**49 * Gets the current mode of GEOID correction.50 * @return51 */52 public static GeoidCorrectionKind getGeoidKind() {53 return geoidKind;54 }55 56 public static void setGeoidKind(GeoidCorrectionKind geoidKind) {57 ElevationHelper.geoidKind = geoidKind;58 }59 60 /**61 * Gets the current unit mode (metric or imperial).62 * @return63 */64 public static UnitMode getUnitMode() {65 //TODO: Use this until /JOSM/src/org/openstreetmap/josm/gui/NavigatableComponent.java66 // has a an appropriate method67 68 // unit mode already determined?69 if (unitMode != UnitMode.NotSelected) {70 return unitMode;71 }72 73 // Set default74 unitMode = UnitMode.Metric;75 76 // Check if user could prefer imperial system77 Locale l = Locale.getDefault();78 for (int i = 0; i < IMPERIAL_SYSTEM_COUNTRIES.length; i++) {79 String ctry = l.toString();80 if (IMPERIAL_SYSTEM_COUNTRIES[i].equals(ctry)) {81 unitMode = UnitMode.Imperial;82 }83 }84 85 return unitMode;86 }87 88 /**89 * Gets the unit string for elevation ("m" or "ft").90 * @return91 */92 public static String getUnit() {93 switch (getUnitMode()) {94 case Metric:95 return "m";96 case Imperial:97 return "ft";98 default:99 throw new RuntimeException("Invalid or unsupported unit mode: " + unitMode);100 }101 }102 103 /**104 * Checks if given value is a valid elevation value.105 *106 * @param ele the ele107 * @return true, if is valid elevation108 */109 public static boolean isValidElevation(double ele) {110 return !Double.isNaN(ele);111 }112 113 /**114 * Gets the elevation (Z coordinate) of a GPX way point in meter or feet (for115 * US, UK, ZA, AU, NZ and CA).116 * 117 * @param wpt118 * The way point instance.119 * @return The x coordinate or <code>NO_ELEVATION</code>, if the given way point is null or contains120 * not height attribute.121 */122 public static double getElevation(WayPoint wpt) {123 if (wpt == null) return NO_ELEVATION;124 125 // try to get elevation from HGT file126 double eleInt = getSrtmElevation(wpt.getCoor());127 if (isValidElevation(eleInt)) {128 return convert(eleInt);129 }130 131 // no HGT, check for elevation data in GPX132 if (!wpt.attr.containsKey(HEIGHT_ATTRIBUTE)) {133 // GPX has no elevation data :-(134 return NO_ELEVATION;135 }136 137 // Parse elevation from GPX data138 String height = wpt.getString(ElevationHelper.HEIGHT_ATTRIBUTE);139 try {140 double z = Double.parseDouble(height);141 142 return convert(z);143 } catch (NumberFormatException e) {144 System.err.println(String.format(145 "Cannot parse double from '%s': %s", height, e146 .getMessage()));147 return NO_ELEVATION;148 }149 }150 151 152 private static double getElevation(LatLon ll) {153 double ele = getSrtmElevation(ll);154 //System.out.println("Get elevation " + ll + " => " + ele);155 return convert(ele);156 }157 158 /**159 * Converts the value to feet, if required.160 *161 * @param ele the elevation to convert162 * @return the double163 */164 private static double convert(double ele) {165 if (isValidElevation(ele)) {166 if (getUnitMode() == UnitMode.Imperial) {167 // translate to feet168 return meter2Feet(ele);169 } else {170 // keep 'as is'171 return ele;172 }173 }174 return NO_ELEVATION;175 }176 177 /**178 * Computes the slope <b>in percent</b> between two way points. E. g. an elevation gain of 12m179 * within a distance of 100m is equal to a slope of 12%.180 *181 * @param w1 the first way point182 * @param w2 the second way point183 * @return the slope in percent184 */185 public static double computeSlope(LatLon w1, LatLon w2) {186 // same coordinates? -> return 0, if yes187 if (w1.equals(w2)) return 0;188 189 // get distance in meters and divide it by 100 in advance190 double distInMeter = convert(w1.greatCircleDistance(w2) / 100.0);191 192 // get elevation (difference) - is converted automatically to feet193 int ele1 = (int) ElevationHelper.getElevation(w1);194 int ele2 = (int) ElevationHelper.getElevation(w2);195 int dH = ele2 - ele1;196 197 // Slope in percent is define as elevation gain/loss in meters related to a distance of 100m198 return dH / distInMeter;199 }200 201 /**202 * Converts meter into feet203 *204 * @param meter the meter205 * @return the double206 */207 public static double meter2Feet(double meter) {208 return meter * METER_TO_FEET;209 }210 211 /**212 * Gets the elevation string for a given elevation, e. g "300m" or "800ft".213 * @param elevation214 * @return215 */216 public static String getElevationText(int elevation) {217 return String.format("%d %s", elevation, getUnit());218 }219 220 /**221 * Gets the elevation string for a given elevation, e. g "300m" or "800ft".222 * @param elevation223 * @return224 */225 public static String getElevationText(double elevation) {226 return String.format("%d %s", (int)Math.round(elevation), getUnit());227 }228 229 /**230 * Gets the elevation string for a given way point, e. g "300m" or "800ft".231 *232 * @param wpt the way point233 * @return the elevation text234 */235 public static String getElevationText(WayPoint wpt) {236 if (wpt == null) return "-";237 238 int elevation = (int)Math.round(ElevationHelper.getElevation(wpt));239 return String.format("%d %s", elevation, getUnit());240 }241 242 /**243 * Get the time string for a given way point.244 * @param wpt245 * @return246 */247 public static String getTimeText(WayPoint wpt) {248 if (wpt == null) return null;249 250 int hour = ElevationHelper.getHourOfWayPoint(wpt);251 int min = ElevationHelper.getMinuteOfWayPoint(wpt);252 return String.format("%02d:%02d", hour, min);253 }254 255 /**256 * Gets the SRTM elevation (Z coordinate) of the given coordinate.257 * 258 * @param ll259 * The coordinate.260 * @return The z coordinate or {@link Double#NaN}, if elevation value could not be obtained261 * not height attribute.262 */263 public static double getSrtmElevation(LatLon ll) {264 if (ll != null) {265 // Try to read data from SRTM file266 // TODO: Option to switch this off267 double eleHgt = hgt.getElevationFromHgt(ll);268 269 //System.out.println("Get elevation from HGT " + ll + " => " + eleHgt);270 if (isValidElevation(eleHgt)) {271 return eleHgt;272 }273 }274 return NO_ELEVATION;275 }276 277 /**278 * Checks given area for SRTM data.279 *280 * @param bounds the bounds/area to check281 * @return true, if SRTM data are present; otherwise false282 */283 public static boolean hasSrtmData(Bounds bounds) {284 if (bounds == null) return false;285 286 LatLon tl = bounds.getMin();287 LatLon br = bounds.getMax();288 289 return isValidElevation(getSrtmElevation(tl)) &&290 isValidElevation(getSrtmElevation(br));291 }292 293 /*294 * Gets the geoid height for the given way point. See also {@link295 * GeoidData}.296 */297 public static byte getGeoidCorrection(WayPoint wpt) {298 /*21 public static double METER_TO_FEET = 3.280948; 22 23 /* Countries which use the imperial system instead of the metric system. */ 24 private static String IMPERIAL_SYSTEM_COUNTRIES[] = { 25 "en_US", /* USA */ 26 "en_CA", /* Canada */ 27 "en_AU", /* Australia */ 28 "en_NZ", /* New Zealand */ 29 // "de_DE", /* for testing only */ 30 "en_ZA" /* South Africa */ 31 }; 32 33 /** The 'no elevation' data magic. */ 34 public static double NO_ELEVATION = Double.NaN; 35 36 /** 37 * The name of the elevation height of a way point. 38 */ 39 public static final String HEIGHT_ATTRIBUTE = "ele"; 40 41 private static UnitMode unitMode = UnitMode.NotSelected; 42 43 private static GeoidCorrectionKind geoidKind = GeoidCorrectionKind.None; 44 45 /** The HGT reader instance. */ 46 private static HgtReader hgt = new HgtReader(); 47 48 /** 49 * Gets the current mode of GEOID correction. 50 * @return 51 */ 52 public static GeoidCorrectionKind getGeoidKind() { 53 return geoidKind; 54 } 55 56 public static void setGeoidKind(GeoidCorrectionKind geoidKind) { 57 ElevationHelper.geoidKind = geoidKind; 58 } 59 60 /** 61 * Gets the current unit mode (metric or imperial). 62 * @return 63 */ 64 public static UnitMode getUnitMode() { 65 //TODO: Use this until /JOSM/src/org/openstreetmap/josm/gui/NavigatableComponent.java 66 // has a an appropriate method 67 68 // unit mode already determined? 69 if (unitMode != UnitMode.NotSelected) { 70 return unitMode; 71 } 72 73 // Set default 74 unitMode = UnitMode.Metric; 75 76 // Check if user could prefer imperial system 77 Locale l = Locale.getDefault(); 78 for (int i = 0; i < IMPERIAL_SYSTEM_COUNTRIES.length; i++) { 79 String ctry = l.toString(); 80 if (IMPERIAL_SYSTEM_COUNTRIES[i].equals(ctry)) { 81 unitMode = UnitMode.Imperial; 82 } 83 } 84 85 return unitMode; 86 } 87 88 /** 89 * Gets the unit string for elevation ("m" or "ft"). 90 * @return 91 */ 92 public static String getUnit() { 93 switch (getUnitMode()) { 94 case Metric: 95 return "m"; 96 case Imperial: 97 return "ft"; 98 default: 99 throw new RuntimeException("Invalid or unsupported unit mode: " + unitMode); 100 } 101 } 102 103 /** 104 * Checks if given value is a valid elevation value. 105 * 106 * @param ele the ele 107 * @return true, if is valid elevation 108 */ 109 public static boolean isValidElevation(double ele) { 110 return !Double.isNaN(ele); 111 } 112 113 /** 114 * Gets the elevation (Z coordinate) of a GPX way point in meter or feet (for 115 * US, UK, ZA, AU, NZ and CA). 116 * 117 * @param wpt 118 * The way point instance. 119 * @return The x coordinate or <code>NO_ELEVATION</code>, if the given way point is null or contains 120 * not height attribute. 121 */ 122 public static double getElevation(WayPoint wpt) { 123 if (wpt == null) return NO_ELEVATION; 124 125 // try to get elevation from HGT file 126 double eleInt = getSrtmElevation(wpt.getCoor()); 127 if (isValidElevation(eleInt)) { 128 return convert(eleInt); 129 } 130 131 // no HGT, check for elevation data in GPX 132 if (!wpt.attr.containsKey(HEIGHT_ATTRIBUTE)) { 133 // GPX has no elevation data :-( 134 return NO_ELEVATION; 135 } 136 137 // Parse elevation from GPX data 138 String height = wpt.getString(ElevationHelper.HEIGHT_ATTRIBUTE); 139 try { 140 double z = Double.parseDouble(height); 141 142 return convert(z); 143 } catch (NumberFormatException e) { 144 System.err.println(String.format( 145 "Cannot parse double from '%s': %s", height, e 146 .getMessage())); 147 return NO_ELEVATION; 148 } 149 } 150 151 152 private static double getElevation(LatLon ll) { 153 double ele = getSrtmElevation(ll); 154 //System.out.println("Get elevation " + ll + " => " + ele); 155 return convert(ele); 156 } 157 158 /** 159 * Converts the value to feet, if required. 160 * 161 * @param ele the elevation to convert 162 * @return the double 163 */ 164 private static double convert(double ele) { 165 if (isValidElevation(ele)) { 166 if (getUnitMode() == UnitMode.Imperial) { 167 // translate to feet 168 return meter2Feet(ele); 169 } else { 170 // keep 'as is' 171 return ele; 172 } 173 } 174 return NO_ELEVATION; 175 } 176 177 /** 178 * Computes the slope <b>in percent</b> between two way points. E. g. an elevation gain of 12m 179 * within a distance of 100m is equal to a slope of 12%. 180 * 181 * @param w1 the first way point 182 * @param w2 the second way point 183 * @return the slope in percent 184 */ 185 public static double computeSlope(LatLon w1, LatLon w2) { 186 // same coordinates? -> return 0, if yes 187 if (w1.equals(w2)) return 0; 188 189 // get distance in meters and divide it by 100 in advance 190 double distInMeter = convert(w1.greatCircleDistance(w2) / 100.0); 191 192 // get elevation (difference) - is converted automatically to feet 193 int ele1 = (int) ElevationHelper.getElevation(w1); 194 int ele2 = (int) ElevationHelper.getElevation(w2); 195 int dH = ele2 - ele1; 196 197 // Slope in percent is define as elevation gain/loss in meters related to a distance of 100m 198 return dH / distInMeter; 199 } 200 201 /** 202 * Converts meter into feet 203 * 204 * @param meter the meter 205 * @return the double 206 */ 207 public static double meter2Feet(double meter) { 208 return meter * METER_TO_FEET; 209 } 210 211 /** 212 * Gets the elevation string for a given elevation, e. g "300m" or "800ft". 213 * @param elevation 214 * @return 215 */ 216 public static String getElevationText(int elevation) { 217 return String.format("%d %s", elevation, getUnit()); 218 } 219 220 /** 221 * Gets the elevation string for a given elevation, e. g "300m" or "800ft". 222 * @param elevation 223 * @return 224 */ 225 public static String getElevationText(double elevation) { 226 return String.format("%d %s", (int)Math.round(elevation), getUnit()); 227 } 228 229 /** 230 * Gets the elevation string for a given way point, e. g "300m" or "800ft". 231 * 232 * @param wpt the way point 233 * @return the elevation text 234 */ 235 public static String getElevationText(WayPoint wpt) { 236 if (wpt == null) return "-"; 237 238 int elevation = (int)Math.round(ElevationHelper.getElevation(wpt)); 239 return String.format("%d %s", elevation, getUnit()); 240 } 241 242 /** 243 * Get the time string for a given way point. 244 * @param wpt 245 * @return 246 */ 247 public static String getTimeText(WayPoint wpt) { 248 if (wpt == null) return null; 249 250 int hour = ElevationHelper.getHourOfWayPoint(wpt); 251 int min = ElevationHelper.getMinuteOfWayPoint(wpt); 252 return String.format("%02d:%02d", hour, min); 253 } 254 255 /** 256 * Gets the SRTM elevation (Z coordinate) of the given coordinate. 257 * 258 * @param ll 259 * The coordinate. 260 * @return The z coordinate or {@link Double#NaN}, if elevation value could not be obtained 261 * not height attribute. 262 */ 263 public static double getSrtmElevation(LatLon ll) { 264 if (ll != null) { 265 // Try to read data from SRTM file 266 // TODO: Option to switch this off 267 double eleHgt = hgt.getElevationFromHgt(ll); 268 269 //System.out.println("Get elevation from HGT " + ll + " => " + eleHgt); 270 if (isValidElevation(eleHgt)) { 271 return eleHgt; 272 } 273 } 274 return NO_ELEVATION; 275 } 276 277 /** 278 * Checks given area for SRTM data. 279 * 280 * @param bounds the bounds/area to check 281 * @return true, if SRTM data are present; otherwise false 282 */ 283 public static boolean hasSrtmData(Bounds bounds) { 284 if (bounds == null) return false; 285 286 LatLon tl = bounds.getMin(); 287 LatLon br = bounds.getMax(); 288 289 return isValidElevation(getSrtmElevation(tl)) && 290 isValidElevation(getSrtmElevation(br)); 291 } 292 293 /* 294 * Gets the geoid height for the given way point. See also {@link 295 * GeoidData}. 296 */ 297 public static byte getGeoidCorrection(WayPoint wpt) { 298 /* 299 299 int lat = (int)Math.round(wpt.getCoor().lat()); 300 300 int lon = (int)Math.round(wpt.getCoor().lon()); … … 303 303 System.out.println( 304 304 String.format("Geoid(%d, %d) = %d", lat, lon, geoid)); 305 */306 return 0;307 }308 309 /**310 * Reduces a given list of way points to the specified target size.311 * 312 * @param origList313 * The original list containing the way points.314 * @param targetSize315 * The desired target size of the list. The resulting list may316 * contain fewer items, so targetSize should be considered as317 * maximum.318 * @return A list containing the reduced list.319 */320 public static List<WayPoint> downsampleWayPoints(List<WayPoint> origList,321 int targetSize) {322 if (origList == null)323 return null;324 if (targetSize <= 0)325 throw new IllegalArgumentException(326 "targetSize must be greater than zero");327 328 int origSize = origList.size();329 if (origSize <= targetSize) {330 return origList;331 }332 333 int delta = (int) Math.max(Math.ceil(origSize / targetSize), 2);334 335 List<WayPoint> res = new ArrayList<WayPoint>(targetSize);336 for (int i = 0; i < origSize; i += delta) {337 res.add(origList.get(i));338 }339 340 return res;341 }342 343 /**344 * Gets the hour value of a way point in 24h format.345 * @param wpt346 * @return347 */348 public static int getHourOfWayPoint(WayPoint wpt) {349 if (wpt == null) return -1;350 351 Calendar calendar = GregorianCalendar.getInstance(); // creates a new calendar instance352 calendar.setTime(wpt.getTime()); // assigns calendar to given date353 return calendar.get(Calendar.HOUR_OF_DAY);354 }355 356 /**357 * Gets the minute value of a way point in 24h format.358 * @param wpt359 * @return360 */361 public static int getMinuteOfWayPoint(WayPoint wpt) {362 if (wpt == null) return -1;363 364 Calendar calendar = GregorianCalendar.getInstance(); // creates a new calendar instance365 calendar.setTime(wpt.getTime()); // assigns calendar to given date366 return calendar.get(Calendar.MINUTE);367 }305 */ 306 return 0; 307 } 308 309 /** 310 * Reduces a given list of way points to the specified target size. 311 * 312 * @param origList 313 * The original list containing the way points. 314 * @param targetSize 315 * The desired target size of the list. The resulting list may 316 * contain fewer items, so targetSize should be considered as 317 * maximum. 318 * @return A list containing the reduced list. 319 */ 320 public static List<WayPoint> downsampleWayPoints(List<WayPoint> origList, 321 int targetSize) { 322 if (origList == null) 323 return null; 324 if (targetSize <= 0) 325 throw new IllegalArgumentException( 326 "targetSize must be greater than zero"); 327 328 int origSize = origList.size(); 329 if (origSize <= targetSize) { 330 return origList; 331 } 332 333 int delta = (int) Math.max(Math.ceil(origSize / targetSize), 2); 334 335 List<WayPoint> res = new ArrayList<>(targetSize); 336 for (int i = 0; i < origSize; i += delta) { 337 res.add(origList.get(i)); 338 } 339 340 return res; 341 } 342 343 /** 344 * Gets the hour value of a way point in 24h format. 345 * @param wpt 346 * @return 347 */ 348 public static int getHourOfWayPoint(WayPoint wpt) { 349 if (wpt == null) return -1; 350 351 Calendar calendar = GregorianCalendar.getInstance(); // creates a new calendar instance 352 calendar.setTime(wpt.getTime()); // assigns calendar to given date 353 return calendar.get(Calendar.HOUR_OF_DAY); 354 } 355 356 /** 357 * Gets the minute value of a way point in 24h format. 358 * @param wpt 359 * @return 360 */ 361 public static int getMinuteOfWayPoint(WayPoint wpt) { 362 if (wpt == null) return -1; 363 364 Calendar calendar = GregorianCalendar.getInstance(); // creates a new calendar instance 365 calendar.setTime(wpt.getTime()); // assigns calendar to given date 366 return calendar.get(Calendar.MINUTE); 367 } 368 368 } -
applications/editors/josm/plugins/ElevationProfile/src/org/openstreetmap/josm/plugins/elevation/HgtReader.java
r30344 r30737 17 17 /** 18 18 * Class HgtReader reads data from SRTM HGT files. Currently this class is restricted to a resolution of 3 arc seconds. 19 * 19 * 20 20 * SRTM data files are available at the <a href="http://dds.cr.usgs.gov/srtm/version2_1/SRTM3">NASA SRTM site</a> 21 21 * @author Oliver Wieland <oliver.wieland@online.de> 22 22 */ 23 23 public class HgtReader { 24 private static final int SECONDS_PER_MINUTE = 60;24 private static final int SECONDS_PER_MINUTE = 60; 25 25 26 public static final String HGT_EXT = ".hgt";26 public static final String HGT_EXT = ".hgt"; 27 27 28 // alter these values for different SRTM resolutions29 public static final int HGT_RES = 3; // resolution in arc seconds30 public static final int HGT_ROW_LENGTH = 1201; // number of elevation values per line31 public static final int HGT_VOID = -32768; // magic number which indicates 'void data' in HGT file28 // alter these values for different SRTM resolutions 29 public static final int HGT_RES = 3; // resolution in arc seconds 30 public static final int HGT_ROW_LENGTH = 1201; // number of elevation values per line 31 public static final int HGT_VOID = -32768; // magic number which indicates 'void data' in HGT file 32 32 33 private final HashMap<String, ShortBuffer> cache = new HashMap<String, ShortBuffer>();33 private final HashMap<String, ShortBuffer> cache = new HashMap<>(); 34 34 35 public double getElevationFromHgt(LatLon coor) {36 try {37 String file = getHgtFileName(coor);38 // given area in cache?39 if (!cache.containsKey(file)) {35 public double getElevationFromHgt(LatLon coor) { 36 try { 37 String file = getHgtFileName(coor); 38 // given area in cache? 39 if (!cache.containsKey(file)) { 40 40 41 // fill initial cache value. If no file is found, then42 // we use it as a marker to indicate 'file has been searched43 // but is not there'44 cache.put(file, null);45 // Try all resource directories46 for (String location : Main.pref.getAllPossiblePreferenceDirs()) {47 String fullPath = new File(location + File.separator + "elevation", file).getPath();48 File f = new File(fullPath);49 if (f.exists()) {50 // found something: read HGT file...51 ShortBuffer data = readHgtFile(fullPath);52 // ... and store result in cache53 cache.put(file, data);54 break;55 }56 }57 }41 // fill initial cache value. If no file is found, then 42 // we use it as a marker to indicate 'file has been searched 43 // but is not there' 44 cache.put(file, null); 45 // Try all resource directories 46 for (String location : Main.pref.getAllPossiblePreferenceDirs()) { 47 String fullPath = new File(location + File.separator + "elevation", file).getPath(); 48 File f = new File(fullPath); 49 if (f.exists()) { 50 // found something: read HGT file... 51 ShortBuffer data = readHgtFile(fullPath); 52 // ... and store result in cache 53 cache.put(file, data); 54 break; 55 } 56 } 57 } 58 58 59 // read elevation value60 return readElevation(coor);61 } catch (FileNotFoundException e) {62 System.err.println("Get elevation from HGT " + coor + " failed: => " + e.getMessage());63 // no problem... file not there64 return ElevationHelper.NO_ELEVATION;65 } catch (Exception ioe) {66 // oops...67 ioe.printStackTrace(System.err);68 // fallback69 return ElevationHelper.NO_ELEVATION;70 }71 }59 // read elevation value 60 return readElevation(coor); 61 } catch (FileNotFoundException e) { 62 System.err.println("Get elevation from HGT " + coor + " failed: => " + e.getMessage()); 63 // no problem... file not there 64 return ElevationHelper.NO_ELEVATION; 65 } catch (Exception ioe) { 66 // oops... 67 ioe.printStackTrace(System.err); 68 // fallback 69 return ElevationHelper.NO_ELEVATION; 70 } 71 } 72 72 73 @SuppressWarnings("resource")74 private ShortBuffer readHgtFile(String file) throws Exception {75 CheckParameterUtil.ensureParameterNotNull(file);73 @SuppressWarnings("resource") 74 private ShortBuffer readHgtFile(String file) throws Exception { 75 CheckParameterUtil.ensureParameterNotNull(file); 76 76 77 FileChannel fc = null;78 ShortBuffer sb = null;79 try {80 // Eclipse complains here about resource leak on 'fc' - even with 'finally' clause???81 fc = new FileInputStream(file).getChannel();82 // choose the right endianness77 FileChannel fc = null; 78 ShortBuffer sb = null; 79 try { 80 // Eclipse complains here about resource leak on 'fc' - even with 'finally' clause??? 81 fc = new FileInputStream(file).getChannel(); 82 // choose the right endianness 83 83 84 ByteBuffer bb = ByteBuffer.allocateDirect((int) fc.size());85 while (bb.remaining() > 0) fc.read(bb);84 ByteBuffer bb = ByteBuffer.allocateDirect((int) fc.size()); 85 while (bb.remaining() > 0) fc.read(bb); 86 86 87 bb.flip();88 //sb = bb.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();89 sb = bb.order(ByteOrder.BIG_ENDIAN).asShortBuffer();90 } finally {91 if (fc != null) fc.close();92 }87 bb.flip(); 88 //sb = bb.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); 89 sb = bb.order(ByteOrder.BIG_ENDIAN).asShortBuffer(); 90 } finally { 91 if (fc != null) fc.close(); 92 } 93 93 94 return sb;95 }94 return sb; 95 } 96 96 97 /**98 * Reads the elevation value for the given coordinate.99 *100 * See also <a href="http://gis.stackexchange.com/questions/43743/how-to-extract-elevation-from-hgt-file">stackexchange.com</a>101 * @param coor the coordinate to get the elevation data for102 * @return the elevation value or <code>Double.NaN</code>, if no value is present103 */104 public double readElevation(LatLon coor) {105 String tag = getHgtFileName(coor);97 /** 98 * Reads the elevation value for the given coordinate. 99 * 100 * See also <a href="http://gis.stackexchange.com/questions/43743/how-to-extract-elevation-from-hgt-file">stackexchange.com</a> 101 * @param coor the coordinate to get the elevation data for 102 * @return the elevation value or <code>Double.NaN</code>, if no value is present 103 */ 104 public double readElevation(LatLon coor) { 105 String tag = getHgtFileName(coor); 106 106 107 ShortBuffer sb = cache.get(tag);107 ShortBuffer sb = cache.get(tag); 108 108 109 if (sb == null) {110 return ElevationHelper.NO_ELEVATION;111 }109 if (sb == null) { 110 return ElevationHelper.NO_ELEVATION; 111 } 112 112 113 // see http://gis.stackexchange.com/questions/43743/how-to-extract-elevation-from-hgt-file114 double fLat = frac(coor.lat()) * SECONDS_PER_MINUTE;115 double fLon = frac(coor.lon()) * SECONDS_PER_MINUTE;113 // see http://gis.stackexchange.com/questions/43743/how-to-extract-elevation-from-hgt-file 114 double fLat = frac(coor.lat()) * SECONDS_PER_MINUTE; 115 double fLon = frac(coor.lon()) * SECONDS_PER_MINUTE; 116 116 117 // compute offset within HGT file118 int row = (int)Math.round(fLat * SECONDS_PER_MINUTE / HGT_RES);119 int col = (int)Math.round(fLon * SECONDS_PER_MINUTE / HGT_RES);117 // compute offset within HGT file 118 int row = (int)Math.round(fLat * SECONDS_PER_MINUTE / HGT_RES); 119 int col = (int)Math.round(fLon * SECONDS_PER_MINUTE / HGT_RES); 120 120 121 row = HGT_ROW_LENGTH - row;122 int cell = (HGT_ROW_LENGTH* (row - 1)) + col;121 row = HGT_ROW_LENGTH - row; 122 int cell = (HGT_ROW_LENGTH* (row - 1)) + col; 123 123 124 //System.out.println("Read SRTM elevation data from row/col/cell " + row + "," + col + ", " + cell + ", " + sb.limit());124 //System.out.println("Read SRTM elevation data from row/col/cell " + row + "," + col + ", " + cell + ", " + sb.limit()); 125 125 126 // valid position in buffer?127 if (cell < sb.limit()) {128 short ele = sb.get(cell);129 //System.out.println("==> Read SRTM elevation data from row/col/cell " + row + "," + col + ", " + cell + " = " + ele);130 // check for data voids131 if (ele == HGT_VOID) {132 return ElevationHelper.NO_ELEVATION;133 } else {134 return ele;135 }136 } else {137 return ElevationHelper.NO_ELEVATION;138 }139 }126 // valid position in buffer? 127 if (cell < sb.limit()) { 128 short ele = sb.get(cell); 129 //System.out.println("==> Read SRTM elevation data from row/col/cell " + row + "," + col + ", " + cell + " = " + ele); 130 // check for data voids 131 if (ele == HGT_VOID) { 132 return ElevationHelper.NO_ELEVATION; 133 } else { 134 return ele; 135 } 136 } else { 137 return ElevationHelper.NO_ELEVATION; 138 } 139 } 140 140 141 /**142 * Gets the associated HGT file name for the given way point. Usually the143 * format is <tt>[N|S]nn[W|E]mmm.hgt</tt> where <i>nn</i> is the integral latitude144 * without decimals and <i>mmm</i> is the longitude.145 *146 * @param latLon the coordinate to get the filename for147 * @return the file name of the HGT file148 */149 public String getHgtFileName(LatLon latLon) {150 int lat = (int) latLon.lat();151 int lon = (int) latLon.lon();141 /** 142 * Gets the associated HGT file name for the given way point. Usually the 143 * format is <tt>[N|S]nn[W|E]mmm.hgt</tt> where <i>nn</i> is the integral latitude 144 * without decimals and <i>mmm</i> is the longitude. 145 * 146 * @param latLon the coordinate to get the filename for 147 * @return the file name of the HGT file 148 */ 149 public String getHgtFileName(LatLon latLon) { 150 int lat = (int) latLon.lat(); 151 int lon = (int) latLon.lon(); 152 152 153 String latPref = "N";154 if (lat < 0) latPref = "S";153 String latPref = "N"; 154 if (lat < 0) latPref = "S"; 155 155 156 String lonPref = "E";157 if (lon < 0) {158 lonPref = "W";159 }156 String lonPref = "E"; 157 if (lon < 0) { 158 lonPref = "W"; 159 } 160 160 161 return String.format("%s%02d%s%03d%s", latPref, lat, lonPref, lon, HGT_EXT);162 }161 return String.format("%s%02d%s%03d%s", latPref, lat, lonPref, lon, HGT_EXT); 162 } 163 163 164 public static double frac(double d) {165 long iPart;166 double fPart;164 public static double frac(double d) { 165 long iPart; 166 double fPart; 167 167 168 // Get user input169 iPart = (long) d;170 fPart = d - iPart;171 return fPart;172 }168 // Get user input 169 iPart = (long) d; 170 fPart = d - iPart; 171 return fPart; 172 } 173 173 } -
applications/editors/josm/plugins/ElevationProfile/src/org/openstreetmap/josm/plugins/elevation/gpx/ElevationModel.java
r30344 r30737 19 19 * Represents the top-level part of the elevation model. The elevation model 20 20 * breaks done into the tracks/routes of a GPX file. 21 * 21 * 22 22 * @see IElevationModelTrackListener 23 23 * @author Oliver Wieland <oliver.wieland@online.de> 24 24 */ 25 25 public class ElevationModel implements IGpxVisitor, IElevationModel { 26 // private int sliceSize;27 private int trackCounter;28 private final GpxData gpxData;29 private final String name;30 private final WayPointMap profiles = new WayPointMap();31 private final List<IElevationModelListener> listeners = new ArrayList<IElevationModelListener>();32 private final List<WayPoint> buffer = new ArrayList<WayPoint>();33 private int currentProfileIndex = 0;34 private ElevationProfile curProfile = null;35 36 /**37 * Instantiates a new elevation model.38 */39 public ElevationModel() {40 this("", null);41 }42 43 /**44 * Instantiates a new elevation model.45 *46 * @param name the name of the model47 * @param data the GPX data48 */49 public ElevationModel(String name, GpxData data) {50 gpxData = data;51 this.name = name;52 GpxIterator.visit(data, this);53 }54 55 /**56 * Gets the GPX data instance used by this model.57 * 58 * @return59 */60 public GpxData getGpxData() {61 return gpxData;62 }63 64 /**65 * @return the tracks66 */67 protected WayPointMap getTracks() {68 return profiles;69 }70 71 /**72 * Fires the 'model changed' event to all listeners.73 */74 protected void fireModelChanged() {75 for (IElevationModelListener listener : listeners) {76 if (profiles != null && profiles.size() > 0)77 listener.elevationProfileChanged(getCurrentProfile());78 }79 }80 81 @Override82 public void addModelListener(IElevationModelListener listener) {83 this.listeners.add(listener);84 }85 86 @Override87 public void removeModelListener(IElevationModelListener listener) {88 this.listeners.remove(listener);89 }90 91 @Override92 public void removeAllListeners() {93 this.listeners.clear();94 }95 96 @Override97 public List<IElevationProfile> getProfiles() {98 return profiles;99 }100 101 @Override102 public IElevationProfile getCurrentProfile() {103 if (currentProfileIndex < 0 || currentProfileIndex >= profileCount()) return null;104 105 return profiles.get(currentProfileIndex);106 }107 108 @Override109 public void setCurrentProfile(IElevationProfile newProfile) {110 CheckParameterUtil.ensureParameterNotNull(newProfile);111 112 if (!profiles.contains(newProfile)) {113 profiles.add(newProfile);114 }115 116 setCurrentProfile(profiles.indexOf(newProfile));117 }118 119 @Override120 public void setCurrentProfile(int index) {121 if (index < 0 || index >= profileCount()) throw new RuntimeException("Invalid arg for setCurrentProfile: " + index + ", value must be 0.." + profileCount());122 123 currentProfileIndex = index;124 fireModelChanged();125 }126 127 @Override128 public int profileCount() {129 return profiles != null ? profiles.size() : 0;130 }131 132 // Visitor stuff starts here...133 134 @Override135 public void beginWayPoints() {136 // we ignore single way points (elevation profile is quite meaningless...)137 }138 139 @Override140 public void endWayPoints() {141 // we ignore single way points (elevation profile is quite meaningless...)142 }143 144 @Override145 public void visitWayPoint(WayPoint wp) {146 // we ignore single way points (elevation profile is quite meaningless...)147 }148 149 150 @Override151 public void beginTrack(GpxTrack track) {152 createProfile(track);153 }154 155 @Override156 public void endTrack(GpxTrack track) {157 if (curProfile == null) throw new RuntimeException("Internal error: No elevation profile");158 159 curProfile.setDistance(track.length());160 commitProfile();161 }162 163 @Override164 public void beginTrackSegment(GpxTrack track, GpxTrackSegment segment) {165 // Nothing to do here for now166 }167 168 @Override169 public void endTrackSegment(GpxTrack track, GpxTrackSegment segment) {170 // Nothing to do here for now171 }172 173 @Override174 public void visitTrackPoint(WayPoint wp, GpxTrack track,175 GpxTrackSegment segment) {176 177 processWayPoint(wp);178 }179 180 @Override181 public void beginRoute(GpxRoute route) {182 createProfile(route);183 }184 185 @Override186 public void endRoute(GpxRoute route) {187 if (curProfile == null) throw new RuntimeException("Internal error: No elevation profile");188 // a GpxRoute has no 'length' property189 curProfile.setDistance(0);190 commitProfile();191 }192 193 @Override194 public void visitRoutePoint(WayPoint wp, GpxRoute route) {195 processWayPoint(wp);196 }197 198 /**199 * Creates a new profile.200 *201 * @param trackOrRoute the track or route202 */203 private void createProfile(IWithAttributes trackOrRoute) {204 // check GPX data205 String trackName = (String) trackOrRoute.get("name");206 207 if (trackName == null) {208 trackName = (String) trackOrRoute.get(GpxData.META_NAME);209 if (trackName == null) {210 // no name given, build artificial one211 trackName = name + "." + trackCounter;212 }213 }214 215 curProfile = new ElevationProfile(trackName);216 }217 218 /**219 * Adds a track or route to the internal track list.220 *221 * @param trackName the track name222 */223 private void commitProfile() {224 if (buffer.size() > 0) {225 // assign way points to profile...226 curProfile.setWayPoints(buffer);227 // ... and add to profile list228 profiles.add(curProfile);229 buffer.clear();230 }231 }232 233 /**234 * Adds the given way point to the current buffer.235 *236 * @param wp the wp237 */238 private void processWayPoint(WayPoint wp) {239 if (wp == null) {240 throw new RuntimeException("WPT must not be null!");241 }242 243 buffer.add(wp);244 }26 // private int sliceSize; 27 private int trackCounter; 28 private final GpxData gpxData; 29 private final String name; 30 private final WayPointMap profiles = new WayPointMap(); 31 private final List<IElevationModelListener> listeners = new ArrayList<>(); 32 private final List<WayPoint> buffer = new ArrayList<>(); 33 private int currentProfileIndex = 0; 34 private ElevationProfile curProfile = null; 35 36 /** 37 * Instantiates a new elevation model. 38 */ 39 public ElevationModel() { 40 this("", null); 41 } 42 43 /** 44 * Instantiates a new elevation model. 45 * 46 * @param name the name of the model 47 * @param data the GPX data 48 */ 49 public ElevationModel(String name, GpxData data) { 50 gpxData = data; 51 this.name = name; 52 GpxIterator.visit(data, this); 53 } 54 55 /** 56 * Gets the GPX data instance used by this model. 57 * 58 * @return 59 */ 60 public GpxData getGpxData() { 61 return gpxData; 62 } 63 64 /** 65 * @return the tracks 66 */ 67 protected WayPointMap getTracks() { 68 return profiles; 69 } 70 71 /** 72 * Fires the 'model changed' event to all listeners. 73 */ 74 protected void fireModelChanged() { 75 for (IElevationModelListener listener : listeners) { 76 if (profiles != null && profiles.size() > 0) 77 listener.elevationProfileChanged(getCurrentProfile()); 78 } 79 } 80 81 @Override 82 public void addModelListener(IElevationModelListener listener) { 83 this.listeners.add(listener); 84 } 85 86 @Override 87 public void removeModelListener(IElevationModelListener listener) { 88 this.listeners.remove(listener); 89 } 90 91 @Override 92 public void removeAllListeners() { 93 this.listeners.clear(); 94 } 95 96 @Override 97 public List<IElevationProfile> getProfiles() { 98 return profiles; 99 } 100 101 @Override 102 public IElevationProfile getCurrentProfile() { 103 if (currentProfileIndex < 0 || currentProfileIndex >= profileCount()) return null; 104 105 return profiles.get(currentProfileIndex); 106 } 107 108 @Override 109 public void setCurrentProfile(IElevationProfile newProfile) { 110 CheckParameterUtil.ensureParameterNotNull(newProfile); 111 112 if (!profiles.contains(newProfile)) { 113 profiles.add(newProfile); 114 } 115 116 setCurrentProfile(profiles.indexOf(newProfile)); 117 } 118 119 @Override 120 public void setCurrentProfile(int index) { 121 if (index < 0 || index >= profileCount()) throw new RuntimeException("Invalid arg for setCurrentProfile: " + index + ", value must be 0.." + profileCount()); 122 123 currentProfileIndex = index; 124 fireModelChanged(); 125 } 126 127 @Override 128 public int profileCount() { 129 return profiles != null ? profiles.size() : 0; 130 } 131 132 // Visitor stuff starts here... 133 134 @Override 135 public void beginWayPoints() { 136 // we ignore single way points (elevation profile is quite meaningless...) 137 } 138 139 @Override 140 public void endWayPoints() { 141 // we ignore single way points (elevation profile is quite meaningless...) 142 } 143 144 @Override 145 public void visitWayPoint(WayPoint wp) { 146 // we ignore single way points (elevation profile is quite meaningless...) 147 } 148 149 150 @Override 151 public void beginTrack(GpxTrack track) { 152 createProfile(track); 153 } 154 155 @Override 156 public void endTrack(GpxTrack track) { 157 if (curProfile == null) throw new RuntimeException("Internal error: No elevation profile"); 158 159 curProfile.setDistance(track.length()); 160 commitProfile(); 161 } 162 163 @Override 164 public void beginTrackSegment(GpxTrack track, GpxTrackSegment segment) { 165 // Nothing to do here for now 166 } 167 168 @Override 169 public void endTrackSegment(GpxTrack track, GpxTrackSegment segment) { 170 // Nothing to do here for now 171 } 172 173 @Override 174 public void visitTrackPoint(WayPoint wp, GpxTrack track, 175 GpxTrackSegment segment) { 176 177 processWayPoint(wp); 178 } 179 180 @Override 181 public void beginRoute(GpxRoute route) { 182 createProfile(route); 183 } 184 185 @Override 186 public void endRoute(GpxRoute route) { 187 if (curProfile == null) throw new RuntimeException("Internal error: No elevation profile"); 188 // a GpxRoute has no 'length' property 189 curProfile.setDistance(0); 190 commitProfile(); 191 } 192 193 @Override 194 public void visitRoutePoint(WayPoint wp, GpxRoute route) { 195 processWayPoint(wp); 196 } 197 198 /** 199 * Creates a new profile. 200 * 201 * @param trackOrRoute the track or route 202 */ 203 private void createProfile(IWithAttributes trackOrRoute) { 204 // check GPX data 205 String trackName = (String) trackOrRoute.get("name"); 206 207 if (trackName == null) { 208 trackName = (String) trackOrRoute.get(GpxData.META_NAME); 209 if (trackName == null) { 210 // no name given, build artificial one 211 trackName = name + "." + trackCounter; 212 } 213 } 214 215 curProfile = new ElevationProfile(trackName); 216 } 217 218 /** 219 * Adds a track or route to the internal track list. 220 * 221 * @param trackName the track name 222 */ 223 private void commitProfile() { 224 if (buffer.size() > 0) { 225 // assign way points to profile... 226 curProfile.setWayPoints(buffer); 227 // ... and add to profile list 228 profiles.add(curProfile); 229 buffer.clear(); 230 } 231 } 232 233 /** 234 * Adds the given way point to the current buffer. 235 * 236 * @param wp the wp 237 */ 238 private void processWayPoint(WayPoint wp) { 239 if (wp == null) { 240 throw new RuntimeException("WPT must not be null!"); 241 } 242 243 buffer.add(wp); 244 } 245 245 } -
applications/editors/josm/plugins/ElevationProfile/src/org/openstreetmap/josm/plugins/elevation/gpx/ElevationProfile.java
r30344 r30737 17 17 * full way point set and then reduces the number of way points to a given 18 18 * amount, if necessary. 19 * 19 * 20 20 * The computation is done via implementing {@link IGpxWaypointVisitor}, 21 21 * subclasses may override the {@link ElevationProfile#visitWayPoint(WayPoint)} 22 22 * method to compute own values or run specific actions. The computation is 23 23 * triggered by calling {@link ElevationProfile#updateValues()}. 24 * 24 * 25 25 * Elevation profiles can break down into further child profiles. This is 26 26 * intended to show different levels of details, if the number of way points 27 27 * exceed the display space (which is usually the case). 28 * 28 * 29 29 * {@link IElevationProfile} {@link IGpxWaypointVisitor} {@link GpxIterator} 30 * 30 * 31 31 * @author Oliver Wieland <oliver.wieland@online.de> 32 * 32 * 33 33 */ 34 34 public class ElevationProfile implements IElevationProfile, 35 35 IGpxWaypointVisitor { 36 public static final int WAYPOINT_START = 0;37 public static final int WAYPOINT_END = 1;38 public static final int WAYPOINT_MIN = 2;39 public static final int WAYPOINT_MAX = 3;40 41 private String name;42 private int minHeight;43 private int maxHeight;44 private int avrgHeight;45 private double dist;46 private Date start = new Date();47 private Date end = new Date();48 private final WayPoint[] importantWayPoints = new WayPoint[4];49 private IElevationProfile parent;50 private int sumEle; // temp var for average height51 private List<WayPoint> wayPoints;52 private int numWayPoints; // cached value53 private int gain;54 private int lastEle;55 private Bounds bounds;56 57 private static boolean ignoreZeroHeight = true;58 59 /**60 * Creates a name elevation profile without any way points.61 * 62 * @param name63 */64 public ElevationProfile(String name) {65 this(name, null, null, 0);66 }67 68 /**69 * Creates a name elevation profile with a given set of way points.70 * 71 * @param name72 * The name of the profile.73 * @param parent74 * The (optional) parent profile.75 * @param wayPoints76 * The list containing the way points of the profile.77 * @param sliceSize78 * The requested target size of the profile.79 */80 public ElevationProfile(String name, IElevationProfile parent,81 List<WayPoint> wayPoints, int sliceSize) {82 super();83 this.name = name;84 this.parent = parent;85 86 setWayPoints(wayPoints);87 }88 89 /**90 * Checks if zero elevation should be ignored or not.91 *92 * @return true, if is ignore zero height93 */94 public static boolean isIgnoreZeroHeight() {95 return ignoreZeroHeight;96 }97 98 /**99 * Sets the ignore zero height.100 *101 * @param ignoreZeroHeight the new ignore zero height102 */103 public static void setIgnoreZeroHeight(boolean ignoreZeroHeight) {104 ElevationProfile.ignoreZeroHeight = ignoreZeroHeight;105 }106 107 @Override108 public void updateElevationData() {109 updateValues();110 }111 112 /**113 * Revisits all way points and recomputes the characteristic values like114 * min/max elevation.115 */116 protected void updateValues() {117 if (wayPoints == null)118 return;119 120 int n = this.wayPoints.size();121 if (n == 0)122 return;123 124 start = new Date();125 end = new Date(0L);126 this.minHeight = Integer.MAX_VALUE;127 this.maxHeight = Integer.MIN_VALUE;128 sumEle = 0;129 gain = 0;130 lastEle = 0;131 132 for (WayPoint wayPoint : this.wayPoints) {133 visitWayPoint(wayPoint);134 }135 136 if (this.minHeight == Integer.MAX_VALUE && this.maxHeight == Integer.MIN_VALUE) {137 // file does not contain elevation data at all138 minHeight = 0;139 maxHeight = 0;140 setMinWayPoint(wayPoints.get(0));141 setMaxWayPoint(wayPoints.get(n-1));142 }143 144 //if (start.after(end) || start.equals(end)) {145 // GPX does not contain time stamps -> use sequential order146 setStart(wayPoints.get(0));147 setEnd(wayPoints.get(n-1));148 //}149 150 avrgHeight = sumEle / n;151 }152 153 /**154 * Gets the name of the profile.155 */156 @Override157 public String getName() {158 return name;159 }160 161 /**162 * Sets the name of the profile.163 * @param name The new name of the profile.164 */165 public void setName(String name) {166 this.name = name;167 }168 169 /**170 * Sets the way point with the lowest elevation.171 * @param wp The way point instance having the lowest elevation.172 */173 protected void setMinWayPoint(WayPoint wp) {174 importantWayPoints[WAYPOINT_MIN] = wp;175 this.minHeight = (int) ElevationHelper.getElevation(wp);176 }177 178 /**179 * Sets the way point with the highest elevation.180 * @param wp The way point instance having the highest elevation.181 */182 protected void setMaxWayPoint(WayPoint wp) {183 importantWayPoints[WAYPOINT_MAX] = wp;184 this.maxHeight = (int) ElevationHelper.getElevation(wp);185 }186 187 /**188 * Sets the average height.189 * @param avrgHeight190 */191 protected void setAvrgHeight(int avrgHeight) {192 this.avrgHeight = avrgHeight;193 }194 195 /**196 * Sets the very first way point.197 * @param wp198 */199 protected void setStart(WayPoint wp) {200 importantWayPoints[WAYPOINT_START] = wp;201 this.start = wp.getTime();202 }203 204 /**205 * Sets the very last way point.206 * @param wp207 */208 protected void setEnd(WayPoint wp) {209 importantWayPoints[WAYPOINT_END] = wp;210 this.end = wp.getTime();211 }212 213 public void setParent(IElevationProfile parent) {214 this.parent = parent;215 }216 217 /**218 * Sets the way points of this profile.219 * 220 * @param wayPoints221 */222 public void setWayPoints(List<WayPoint> wayPoints) {223 if (this.wayPoints != wayPoints) {224 this.wayPoints = new ArrayList<WayPoint>(wayPoints);225 numWayPoints = wayPoints != null ? wayPoints.size() : 0;226 updateValues();227 228 }229 }230 231 /**232 * Checks if the given index is valid or not.233 * 234 * @param index235 * The index to check.236 * @return true, if the given index is valid; otherwise false.237 */238 protected boolean checkIndex(int index) {239 return index >= 0 && index < getNumberOfWayPoints();240 }241 242 @Override243 public int elevationValueAt(int i) {244 if (checkIndex(i)) {245 return (int) ElevationHelper.getElevation(wayPoints.get(i));246 } else {247 throw new IndexOutOfBoundsException(String.format(248 "Invalid index: %d, expected 0..%d", i,249 getNumberOfWayPoints()));250 }251 }252 253 @Override254 public int getAverageHeight() {255 return avrgHeight;256 }257 258 @Override259 public List<IElevationProfile> getChildren() {260 return null;261 }262 263 @Override264 public Date getEnd() {265 return end;266 }267 268 @Override269 public int getMaxHeight() {270 return maxHeight;271 }272 273 @Override274 public int getMinHeight() {275 return minHeight;276 }277 278 /**279 * Gets the difference between min and max elevation.280 * 281 * @return282 */283 @Override284 public int getHeightDifference() {285 return maxHeight - minHeight;286 }287 288 /**289 * Gets the elevation gain.290 * 291 * @return292 */293 @Override294 public int getGain() {295 return gain;296 }297 298 @Override299 public double getDistance() {300 return dist; // dist is in meters301 }302 303 /**304 * Sets the distance of the elevation profile.305 * @param dist306 */307 protected void setDistance(double dist) {308 this.dist = dist;309 }310 311 /**312 * Returns the time between start and end of the track.313 * @return314 */315 @Override316 public long getTimeDifference() {317 WayPoint wp1 = getStartWayPoint();318 WayPoint wp2 = getEndWayPoint();319 320 if (wp1 != null && wp2 != null) {321 long diff = wp2.getTime().getTime() - wp1.getTime().getTime();322 return diff;323 }324 325 return 0L;326 }327 328 @Override329 public IElevationProfile getParent() {330 return parent;331 }332 333 @Override334 public Date getStart() {335 return start;336 }337 338 @Override339 public WayPoint getEndWayPoint() {340 return importantWayPoints[WAYPOINT_END];341 }342 343 @Override344 public WayPoint getMaxWayPoint() {345 return importantWayPoints[WAYPOINT_MAX];346 }347 348 @Override349 public WayPoint getMinWayPoint() {350 return importantWayPoints[WAYPOINT_MIN];351 }352 353 @Override354 public WayPoint getStartWayPoint() {355 return importantWayPoints[WAYPOINT_START];356 }357 358 @Override359 public List<WayPoint> getWayPoints() {360 return wayPoints;361 }362 363 @Override364 public int getNumberOfWayPoints() {365 return numWayPoints;// wayPoints != null ? wayPoints.size() : 0;366 }367 368 /**369 * Gets the coordinate bounds of this profile. See {@link Bounds} for details.370 *371 * @return the bounds of this elevation profile372 */373 @Override374 public Bounds getBounds() {375 return bounds;376 }377 378 /**379 * Gets a flag indicating whether the associated way points contained380 * elevation data or not. This is the case if min and max height or both381 * zero.382 * 383 * @return384 */385 @Override386 public boolean hasElevationData() {387 return minHeight != maxHeight;388 }389 390 /**391 * Visits a way point in order to update statistical values about the given392 * way point list.393 */394 @Override395 public void visitWayPoint(WayPoint wp) {396 if (wp.getTime().after(end)) {397 setEnd(wp);398 }399 400 if (wp.getTime().before(start)) {401 setStart(wp);402 }403 404 // update boundaries405 if (bounds == null) {406 bounds = new Bounds(wp.getCoor());407 } else {408 bounds.extend(wp.getCoor());409 }410 411 int ele = (int) ElevationHelper.getElevation(wp);412 413 if (!isIgnoreZeroHeight() || ele > 0) {414 if (ele > maxHeight) {415 setMaxWayPoint(wp);416 }417 if (ele < minHeight) {418 setMinWayPoint(wp);419 }420 421 if (ele > lastEle) {422 gain += ele - lastEle;423 }424 425 sumEle += ele;426 lastEle = ele;427 }428 }429 430 @Override431 public String toString() {432 return name; /*"ElevationProfileBase [start=" + getStart() + ", end=" + getEnd()36 public static final int WAYPOINT_START = 0; 37 public static final int WAYPOINT_END = 1; 38 public static final int WAYPOINT_MIN = 2; 39 public static final int WAYPOINT_MAX = 3; 40 41 private String name; 42 private int minHeight; 43 private int maxHeight; 44 private int avrgHeight; 45 private double dist; 46 private Date start = new Date(); 47 private Date end = new Date(); 48 private final WayPoint[] importantWayPoints = new WayPoint[4]; 49 private IElevationProfile parent; 50 private int sumEle; // temp var for average height 51 private List<WayPoint> wayPoints; 52 private int numWayPoints; // cached value 53 private int gain; 54 private int lastEle; 55 private Bounds bounds; 56 57 private static boolean ignoreZeroHeight = true; 58 59 /** 60 * Creates a name elevation profile without any way points. 61 * 62 * @param name 63 */ 64 public ElevationProfile(String name) { 65 this(name, null, null, 0); 66 } 67 68 /** 69 * Creates a name elevation profile with a given set of way points. 70 * 71 * @param name 72 * The name of the profile. 73 * @param parent 74 * The (optional) parent profile. 75 * @param wayPoints 76 * The list containing the way points of the profile. 77 * @param sliceSize 78 * The requested target size of the profile. 79 */ 80 public ElevationProfile(String name, IElevationProfile parent, 81 List<WayPoint> wayPoints, int sliceSize) { 82 super(); 83 this.name = name; 84 this.parent = parent; 85 86 setWayPoints(wayPoints); 87 } 88 89 /** 90 * Checks if zero elevation should be ignored or not. 91 * 92 * @return true, if is ignore zero height 93 */ 94 public static boolean isIgnoreZeroHeight() { 95 return ignoreZeroHeight; 96 } 97 98 /** 99 * Sets the ignore zero height. 100 * 101 * @param ignoreZeroHeight the new ignore zero height 102 */ 103 public static void setIgnoreZeroHeight(boolean ignoreZeroHeight) { 104 ElevationProfile.ignoreZeroHeight = ignoreZeroHeight; 105 } 106 107 @Override 108 public void updateElevationData() { 109 updateValues(); 110 } 111 112 /** 113 * Revisits all way points and recomputes the characteristic values like 114 * min/max elevation. 115 */ 116 protected void updateValues() { 117 if (wayPoints == null) 118 return; 119 120 int n = this.wayPoints.size(); 121 if (n == 0) 122 return; 123 124 start = new Date(); 125 end = new Date(0L); 126 this.minHeight = Integer.MAX_VALUE; 127 this.maxHeight = Integer.MIN_VALUE; 128 sumEle = 0; 129 gain = 0; 130 lastEle = 0; 131 132 for (WayPoint wayPoint : this.wayPoints) { 133 visitWayPoint(wayPoint); 134 } 135 136 if (this.minHeight == Integer.MAX_VALUE && this.maxHeight == Integer.MIN_VALUE) { 137 // file does not contain elevation data at all 138 minHeight = 0; 139 maxHeight = 0; 140 setMinWayPoint(wayPoints.get(0)); 141 setMaxWayPoint(wayPoints.get(n-1)); 142 } 143 144 //if (start.after(end) || start.equals(end)) { 145 // GPX does not contain time stamps -> use sequential order 146 setStart(wayPoints.get(0)); 147 setEnd(wayPoints.get(n-1)); 148 //} 149 150 avrgHeight = sumEle / n; 151 } 152 153 /** 154 * Gets the name of the profile. 155 */ 156 @Override 157 public String getName() { 158 return name; 159 } 160 161 /** 162 * Sets the name of the profile. 163 * @param name The new name of the profile. 164 */ 165 public void setName(String name) { 166 this.name = name; 167 } 168 169 /** 170 * Sets the way point with the lowest elevation. 171 * @param wp The way point instance having the lowest elevation. 172 */ 173 protected void setMinWayPoint(WayPoint wp) { 174 importantWayPoints[WAYPOINT_MIN] = wp; 175 this.minHeight = (int) ElevationHelper.getElevation(wp); 176 } 177 178 /** 179 * Sets the way point with the highest elevation. 180 * @param wp The way point instance having the highest elevation. 181 */ 182 protected void setMaxWayPoint(WayPoint wp) { 183 importantWayPoints[WAYPOINT_MAX] = wp; 184 this.maxHeight = (int) ElevationHelper.getElevation(wp); 185 } 186 187 /** 188 * Sets the average height. 189 * @param avrgHeight 190 */ 191 protected void setAvrgHeight(int avrgHeight) { 192 this.avrgHeight = avrgHeight; 193 } 194 195 /** 196 * Sets the very first way point. 197 * @param wp 198 */ 199 protected void setStart(WayPoint wp) { 200 importantWayPoints[WAYPOINT_START] = wp; 201 this.start = wp.getTime(); 202 } 203 204 /** 205 * Sets the very last way point. 206 * @param wp 207 */ 208 protected void setEnd(WayPoint wp) { 209 importantWayPoints[WAYPOINT_END] = wp; 210 this.end = wp.getTime(); 211 } 212 213 public void setParent(IElevationProfile parent) { 214 this.parent = parent; 215 } 216 217 /** 218 * Sets the way points of this profile. 219 * 220 * @param wayPoints 221 */ 222 public void setWayPoints(List<WayPoint> wayPoints) { 223 if (this.wayPoints != wayPoints) { 224 this.wayPoints = new ArrayList<>(wayPoints); 225 numWayPoints = wayPoints != null ? wayPoints.size() : 0; 226 updateValues(); 227 228 } 229 } 230 231 /** 232 * Checks if the given index is valid or not. 233 * 234 * @param index 235 * The index to check. 236 * @return true, if the given index is valid; otherwise false. 237 */ 238 protected boolean checkIndex(int index) { 239 return index >= 0 && index < getNumberOfWayPoints(); 240 } 241 242 @Override 243 public int elevationValueAt(int i) { 244 if (checkIndex(i)) { 245 return (int) ElevationHelper.getElevation(wayPoints.get(i)); 246 } else { 247 throw new IndexOutOfBoundsException(String.format( 248 "Invalid index: %d, expected 0..%d", i, 249 getNumberOfWayPoints())); 250 } 251 } 252 253 @Override 254 public int getAverageHeight() { 255 return avrgHeight; 256 } 257 258 @Override 259 public List<IElevationProfile> getChildren() { 260 return null; 261 } 262 263 @Override 264 public Date getEnd() { 265 return end; 266 } 267 268 @Override 269 public int getMaxHeight() { 270 return maxHeight; 271 } 272 273 @Override 274 public int getMinHeight() { 275 return minHeight; 276 } 277 278 /** 279 * Gets the difference between min and max elevation. 280 * 281 * @return 282 */ 283 @Override 284 public int getHeightDifference() { 285 return maxHeight - minHeight; 286 } 287 288 /** 289 * Gets the elevation gain. 290 * 291 * @return 292 */ 293 @Override 294 public int getGain() { 295 return gain; 296 } 297 298 @Override 299 public double getDistance() { 300 return dist; // dist is in meters 301 } 302 303 /** 304 * Sets the distance of the elevation profile. 305 * @param dist 306 */ 307 protected void setDistance(double dist) { 308 this.dist = dist; 309 } 310 311 /** 312 * Returns the time between start and end of the track. 313 * @return 314 */ 315 @Override 316 public long getTimeDifference() { 317 WayPoint wp1 = getStartWayPoint(); 318 WayPoint wp2 = getEndWayPoint(); 319 320 if (wp1 != null && wp2 != null) { 321 long diff = wp2.getTime().getTime() - wp1.getTime().getTime(); 322 return diff; 323 } 324 325 return 0L; 326 } 327 328 @Override 329 public IElevationProfile getParent() { 330 return parent; 331 } 332 333 @Override 334 public Date getStart() { 335 return start; 336 } 337 338 @Override 339 public WayPoint getEndWayPoint() { 340 return importantWayPoints[WAYPOINT_END]; 341 } 342 343 @Override 344 public WayPoint getMaxWayPoint() { 345 return importantWayPoints[WAYPOINT_MAX]; 346 } 347 348 @Override 349 public WayPoint getMinWayPoint() { 350 return importantWayPoints[WAYPOINT_MIN]; 351 } 352 353 @Override 354 public WayPoint getStartWayPoint() { 355 return importantWayPoints[WAYPOINT_START]; 356 } 357 358 @Override 359 public List<WayPoint> getWayPoints() { 360 return wayPoints; 361 } 362 363 @Override 364 public int getNumberOfWayPoints() { 365 return numWayPoints;// wayPoints != null ? wayPoints.size() : 0; 366 } 367 368 /** 369 * Gets the coordinate bounds of this profile. See {@link Bounds} for details. 370 * 371 * @return the bounds of this elevation profile 372 */ 373 @Override 374 public Bounds getBounds() { 375 return bounds; 376 } 377 378 /** 379 * Gets a flag indicating whether the associated way points contained 380 * elevation data or not. This is the case if min and max height or both 381 * zero. 382 * 383 * @return 384 */ 385 @Override 386 public boolean hasElevationData() { 387 return minHeight != maxHeight; 388 } 389 390 /** 391 * Visits a way point in order to update statistical values about the given 392 * way point list. 393 */ 394 @Override 395 public void visitWayPoint(WayPoint wp) { 396 if (wp.getTime().after(end)) { 397 setEnd(wp); 398 } 399 400 if (wp.getTime().before(start)) { 401 setStart(wp); 402 } 403 404 // update boundaries 405 if (bounds == null) { 406 bounds = new Bounds(wp.getCoor()); 407 } else { 408 bounds.extend(wp.getCoor()); 409 } 410 411 int ele = (int) ElevationHelper.getElevation(wp); 412 413 if (!isIgnoreZeroHeight() || ele > 0) { 414 if (ele > maxHeight) { 415 setMaxWayPoint(wp); 416 } 417 if (ele < minHeight) { 418 setMinWayPoint(wp); 419 } 420 421 if (ele > lastEle) { 422 gain += ele - lastEle; 423 } 424 425 sumEle += ele; 426 lastEle = ele; 427 } 428 } 429 430 @Override 431 public String toString() { 432 return name; /*"ElevationProfileBase [start=" + getStart() + ", end=" + getEnd() 433 433 + ", minHeight=" + getMinHeight() + ", maxHeight=" 434 434 + getMaxHeight() + "]";*/ 435 }435 } 436 436 } -
applications/editors/josm/plugins/ElevationProfile/src/org/openstreetmap/josm/plugins/elevation/grid/EleVertex.java
r30701 r30737 87 87 System.out.println(newP); 88 88 */ 89 List<EleVertex> res = new ArrayList< EleVertex>();89 List<EleVertex> res = new ArrayList<>(); 90 90 res.add(new EleVertex(pI, pK, newP)); 91 91 res.add(new EleVertex(pJ, pK, newP)); -
applications/editors/josm/plugins/ElevationProfile/src/org/openstreetmap/josm/plugins/elevation/grid/ElevationGridTile.java
r30344 r30737 25 25 26 26 public class ElevationGridTile extends Tile { 27 private final BlockingDeque<EleVertex> toDo = new LinkedBlockingDeque<EleVertex>();28 private final BlockingDeque<EleVertex> vertices = new LinkedBlockingDeque<EleVertex>();27 private final BlockingDeque<EleVertex> toDo = new LinkedBlockingDeque<>(); 28 private final BlockingDeque<EleVertex> vertices = new LinkedBlockingDeque<>(); 29 29 30 private Bounds box;30 private Bounds box; 31 31 32 public ElevationGridTile(TileSource source, int xtile, int ytile, int zoom) {33 super(source, xtile, ytile, zoom);32 public ElevationGridTile(TileSource source, int xtile, int ytile, int zoom) { 33 super(source, xtile, ytile, zoom); 34 34 35 box = tile2Bounds(xtile, ytile, zoom);36 initQueue();37 }35 box = tile2Bounds(xtile, ytile, zoom); 36 initQueue(); 37 } 38 38 39 public ElevationGridTile(TileSource source, int xtile, int ytile, int zoom,40 BufferedImage image) {41 super(source, xtile, ytile, zoom, image);39 public ElevationGridTile(TileSource source, int xtile, int ytile, int zoom, 40 BufferedImage image) { 41 super(source, xtile, ytile, zoom, image); 42 42 43 43 44 }44 } 45 45 46 @Override47 public void loadPlaceholderFromCache(TileCache cache) {48 // TODO Auto-generated method stub49 super.loadPlaceholderFromCache(cache);46 @Override 47 public void loadPlaceholderFromCache(TileCache cache) { 48 // TODO Auto-generated method stub 49 super.loadPlaceholderFromCache(cache); 50 50 51 //System.out.println("loadPlaceholderFromCache");52 }51 //System.out.println("loadPlaceholderFromCache"); 52 } 53 53 54 @Override55 public String getUrl() throws IOException {56 // TODO Auto-generated method stub57 return super.getUrl();58 }54 @Override 55 public String getUrl() throws IOException { 56 // TODO Auto-generated method stub 57 return super.getUrl(); 58 } 59 59 60 /**61 * Use {@link ElevationGridTile#paintTile(Graphics2D, MapView, IVertexRenderer)} to render the tile as grid. This method just issues a debug text.62 */63 @Override64 public void paint(Graphics g, int x, int y) {65 super.paint(g, x, y);60 /** 61 * Use {@link ElevationGridTile#paintTile(Graphics2D, MapView, IVertexRenderer)} to render the tile as grid. This method just issues a debug text. 62 */ 63 @Override 64 public void paint(Graphics g, int x, int y) { 65 super.paint(g, x, y); 66 66 67 //g.drawString(String.format("EGT %d/%d ", getXtile(), getYtile()), x, y);68 g.drawString(getStatus(), x, y);69 }67 //g.drawString(String.format("EGT %d/%d ", getXtile(), getYtile()), x, y); 68 g.drawString(getStatus(), x, y); 69 } 70 70 71 /**72 * Paints the vertices of this tile.73 *74 * @param g the graphics context75 * @param mv the map view76 * @param vertexRenderer the vertex renderer77 */78 public void paintTile(Graphics2D g, MapView mv, IVertexRenderer vertexRenderer) {79 BlockingDeque<EleVertex> list = getVertices();80 for (EleVertex eleVertex : list) {81 Point p0 = mv.getPoint(eleVertex.get(0));82 Point p1 = mv.getPoint(eleVertex.get(1));83 Point p2 = mv.getPoint(eleVertex.get(2));84 Triangle shape = new Triangle(p0, p1, p2);71 /** 72 * Paints the vertices of this tile. 73 * 74 * @param g the graphics context 75 * @param mv the map view 76 * @param vertexRenderer the vertex renderer 77 */ 78 public void paintTile(Graphics2D g, MapView mv, IVertexRenderer vertexRenderer) { 79 BlockingDeque<EleVertex> list = getVertices(); 80 for (EleVertex eleVertex : list) { 81 Point p0 = mv.getPoint(eleVertex.get(0)); 82 Point p1 = mv.getPoint(eleVertex.get(1)); 83 Point p2 = mv.getPoint(eleVertex.get(2)); 84 Triangle shape = new Triangle(p0, p1, p2); 85 85 86 // obtain vertex color87 g.setColor(vertexRenderer.getElevationColor(eleVertex));88 // TODO: Move to renderer89 g.fill(shape);90 }91 }86 // obtain vertex color 87 g.setColor(vertexRenderer.getElevationColor(eleVertex)); 88 // TODO: Move to renderer 89 g.fill(shape); 90 } 91 } 92 92 93 @Override94 public void loadImage(InputStream input) throws IOException {95 if (isLoaded()) return;93 @Override 94 public void loadImage(InputStream input) throws IOException { 95 if (isLoaded()) return; 96 96 97 // TODO: Save97 // TODO: Save 98 98 99 // We abuse the loadImage method to render the vertices...100 //101 while (toDo.size() > 0) {102 EleVertex vertex = toDo.poll();99 // We abuse the loadImage method to render the vertices... 100 // 101 while (toDo.size() > 0) { 102 EleVertex vertex = toDo.poll(); 103 103 104 if (vertex.isFinished()) {105 vertices.add(vertex);106 } else {107 List<EleVertex> newV = vertex.divide();108 for (EleVertex eleVertex : newV) {109 toDo.add(eleVertex);110 }111 }112 }113 setLoaded(true);114 }104 if (vertex.isFinished()) { 105 vertices.add(vertex); 106 } else { 107 List<EleVertex> newV = vertex.divide(); 108 for (EleVertex eleVertex : newV) { 109 toDo.add(eleVertex); 110 } 111 } 112 } 113 setLoaded(true); 114 } 115 115 116 public BlockingDeque<EleVertex> getVertices() {117 return vertices;118 }116 public BlockingDeque<EleVertex> getVertices() { 117 return vertices; 118 } 119 119 120 /**121 * See also <a href="http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_bounding_box">OSM Wiki</a>122 * @param x the x123 * @param y the y124 * @param zoom the zoom125 * @return the bounds126 */127 private Bounds tile2Bounds(final int x, final int y, final int zoom) {128 Bounds bb = new Bounds(129 new LatLon(source.tileYToLat(y, zoom), source.tileXToLon(x, zoom)),130 new LatLon(source.tileYToLat(y + 1, zoom), source.tileXToLon(x + 1, zoom)));120 /** 121 * See also <a href="http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_bounding_box">OSM Wiki</a> 122 * @param x the x 123 * @param y the y 124 * @param zoom the zoom 125 * @return the bounds 126 */ 127 private Bounds tile2Bounds(final int x, final int y, final int zoom) { 128 Bounds bb = new Bounds( 129 new LatLon(source.tileYToLat(y, zoom), source.tileXToLon(x, zoom)), 130 new LatLon(source.tileYToLat(y + 1, zoom), source.tileXToLon(x + 1, zoom))); 131 131 132 return bb;133 }132 return bb; 133 } 134 134 135 /**136 * Inits the 'todo' queue with the initial vertices.137 */138 private void initQueue() {139 LatLon min = box.getMin();140 LatLon max = box.getMax();135 /** 136 * Inits the 'todo' queue with the initial vertices. 137 */ 138 private void initQueue() { 139 LatLon min = box.getMin(); 140 LatLon max = box.getMax(); 141 141 142 // compute missing coordinates143 LatLon h1 = new LatLon(min.lat(), max.lon());144 LatLon h2 = new LatLon(max.lat(), min.lon());142 // compute missing coordinates 143 LatLon h1 = new LatLon(min.lat(), max.lon()); 144 LatLon h2 = new LatLon(max.lat(), min.lon()); 145 145 146 double eleMin = ElevationHelper.getSrtmElevation(min);147 double eleMax = ElevationHelper.getSrtmElevation(max);146 double eleMin = ElevationHelper.getSrtmElevation(min); 147 double eleMax = ElevationHelper.getSrtmElevation(max); 148 148 149 // SRTM files present?150 if (!ElevationHelper.isValidElevation(eleMax) || !ElevationHelper.isValidElevation(eleMin)) {151 setError(tr("No SRTM data"));152 return;153 }149 // SRTM files present? 150 if (!ElevationHelper.isValidElevation(eleMax) || !ElevationHelper.isValidElevation(eleMin)) { 151 setError(tr("No SRTM data")); 152 return; 153 } 154 154 155 // compute elevation coords156 EleCoordinate p0 = new EleCoordinate(min, eleMin);157 EleCoordinate p1 = new EleCoordinate(h1, ElevationHelper.getSrtmElevation(h1));158 EleCoordinate p2 = new EleCoordinate(max, eleMax);159 EleCoordinate p3 = new EleCoordinate(h2, ElevationHelper.getSrtmElevation(h2));155 // compute elevation coords 156 EleCoordinate p0 = new EleCoordinate(min, eleMin); 157 EleCoordinate p1 = new EleCoordinate(h1, ElevationHelper.getSrtmElevation(h1)); 158 EleCoordinate p2 = new EleCoordinate(max, eleMax); 159 EleCoordinate p3 = new EleCoordinate(h2, ElevationHelper.getSrtmElevation(h2)); 160 160 161 // compute initial vertices162 EleVertex v1 = new EleVertex(p0, p1, p2);163 EleVertex v2 = new EleVertex(p2, p3, p0);164 // enqueue vertices165 toDo.add(v1);166 toDo.add(v2);167 }161 // compute initial vertices 162 EleVertex v1 = new EleVertex(p0, p1, p2); 163 EleVertex v2 = new EleVertex(p2, p3, p0); 164 // enqueue vertices 165 toDo.add(v1); 166 toDo.add(v2); 167 } 168 168 169 @Override170 public String toString() {171 return "ElevationGridTile [box=" + box + ", xtile=" + xtile172 + ", ytile=" + ytile + "]";173 }169 @Override 170 public String toString() { 171 return "ElevationGridTile [box=" + box + ", xtile=" + xtile 172 + ", ytile=" + ytile + "]"; 173 } 174 174 175 175 -
applications/editors/josm/plugins/ElevationProfile/src/org/openstreetmap/josm/plugins/elevation/gui/DefaultElevationProfileRenderer.java
r30344 r30737 33 33 IElevationProfileRenderer { 34 34 35 private static final int ROUND_RECT_RADIUS = 6;36 /**37 * 38 */39 private static final int TRIANGLE_BASESIZE = 24;40 /**41 * 42 */43 private static final int BASIC_WPT_RADIUS = 1;44 private static final int BIG_WPT_RADIUS = BASIC_WPT_RADIUS * 16;45 46 // predefined colors47 private static final Color HIGH_COLOR = ElevationColors.EPMidBlue;48 private static final Color LOW_COLOR = ElevationColors.EPMidBlue;49 private static final Color START_COLOR = Color.GREEN;50 private static final Color END_POINT = Color.RED;51 private static final Color LEVEL_GAIN_COLOR = Color.GREEN;52 private static final Color LEVEL_LOSS_COLOR = Color.RED;53 private static final Color MARKER_POINT = Color.YELLOW;54 // Predefined radians55 private static final double RAD_180 = Math.PI;56 // private static final double RAD_270 = Math.PI * 1.5;57 private static final double RAD_90 = Math.PI * 0.5;58 59 private final List<Rectangle> forbiddenRects = new ArrayList<Rectangle>();60 61 @Override62 public Color getColorForWaypoint(IElevationProfile profile, WayPoint wpt,63 ElevationWayPointKind kind) {64 65 if (wpt == null || profile == null) {66 System.err.println(String.format(67 "Cannot determine color: prof=%s, wpt=%s", profile, wpt));68 return null;69 }70 71 switch (kind) {72 case Plain:73 return Color.LIGHT_GRAY;74 case ElevationLevelLoss:75 return LEVEL_LOSS_COLOR;76 case ElevationLevelGain:77 return LEVEL_GAIN_COLOR;78 case Highlighted:79 return Color.ORANGE;80 case ElevationGainHigh:81 return Color.getHSBColor(0.3f, 1.0f, 1.0f); // green82 case ElevationLossHigh:83 return Color.getHSBColor(0, 1.0f, 1.0f); // red84 case ElevationGainLow:85 return Color.getHSBColor(0.3f, 0.5f, 1.0f); // green with low sat86 case ElevationLossLow:87 return Color.getHSBColor(0, 0.5f, 1.0f); // red with low sat88 case FullHour:89 return MARKER_POINT;90 case MaxElevation:91 return HIGH_COLOR;92 case MinElevation:93 return LOW_COLOR;94 case StartPoint:95 return START_COLOR;96 case EndPoint:97 return END_POINT;98 default:99 break;100 }101 102 throw new RuntimeException("Unknown way point kind: " + kind);103 }104 105 @Override106 public void renderWayPoint(Graphics g, IElevationProfile profile,107 MapView mv, WayPoint wpt, ElevationWayPointKind kind) {108 109 CheckParameterUtil.ensureParameterNotNull(g, "graphics");110 CheckParameterUtil.ensureParameterNotNull(profile, "profile");111 CheckParameterUtil.ensureParameterNotNull(mv, "map view");112 113 if (wpt == null) {114 System.err.println(String.format(115 "Cannot paint: mv=%s, prof=%s, wpt=%s", mv, profile, wpt));116 return;117 }118 119 switch (kind) {120 case MinElevation:121 case MaxElevation:122 renderMinMaxPoint(g, profile, mv, wpt, kind);123 break;124 case EndPoint:125 case StartPoint:126 renderStartEndPoint(g, profile, mv, wpt, kind);127 break;128 default:129 renderRegularWayPoint(g, profile, mv, wpt, kind);130 break;131 }132 }133 134 @Override135 public void renderLine(Graphics g, IElevationProfile profile,136 MapView mv, WayPoint wpt1, WayPoint wpt2, ElevationWayPointKind kind) {137 138 CheckParameterUtil.ensureParameterNotNull(g, "graphics");139 CheckParameterUtil.ensureParameterNotNull(profile, "profile");140 CheckParameterUtil.ensureParameterNotNull(mv, "map view");141 142 if (wpt1 == null || wpt2 == null) {143 System.err.println(String.format(144 "Cannot paint line: mv=%s, prof=%s, kind = %s", mv, profile, kind));145 return;146 }147 148 // obtain and set color149 g.setColor(getColorForWaypoint(profile, wpt2, kind));150 151 // transform to view152 Point pnt1 = mv.getPoint(wpt1.getEastNorth());153 Point pnt2 = mv.getPoint(wpt2.getEastNorth());154 155 // use thick line, if possible156 if (g instanceof Graphics2D) {157 Graphics2D g2 = (Graphics2D) g;158 Stroke oldS = g2.getStroke();159 try {160 g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));161 g2.drawLine(pnt1.x, pnt1.y, pnt2.x, pnt2.y);162 } finally {163 // must be restored; otherwise other layers may using this style, too164 g2.setStroke(oldS);165 }166 } else {167 // only poor man's graphics168 g.drawLine(pnt1.x, pnt1.y, pnt2.x, pnt2.y);169 }170 }171 172 /**173 * Renders a regular way point.174 * 175 * @param g176 * The graphics context.177 * @param profile178 * The elevation profile.179 * @param mv180 * The map view instance.181 * @param wpt182 * The way point to render.183 * @param kind184 * The way point kind (start, end, max,...).185 */186 private void renderRegularWayPoint(Graphics g, IElevationProfile profile,187 MapView mv, WayPoint wpt, ElevationWayPointKind kind) {188 189 Color c = getColorForWaypoint(profile, wpt, kind);190 Point pnt = mv.getPoint(wpt.getEastNorth());191 192 /* Paint full hour label */193 if (kind == ElevationWayPointKind.FullHour) {194 int hour = ElevationHelper.getHourOfWayPoint(wpt);195 drawLabel(String.format("%02d:00", hour), pnt.x, pnt.y196 + g.getFontMetrics().getHeight(), g);197 }198 199 /* Paint label for elevation levels */200 if (kind == ElevationWayPointKind.ElevationLevelGain || kind == ElevationWayPointKind.ElevationLevelLoss) {201 int ele = ((int) Math.rint(ElevationHelper.getElevation(wpt) / 100.0)) * 100;202 drawLabelWithTriangle(ElevationHelper.getElevationText(ele), pnt.x, pnt.y203 + g.getFontMetrics().getHeight(), g, Color.darkGray, 8,204 getColorForWaypoint(profile, wpt, kind),205 kind == ElevationWayPointKind.ElevationLevelGain ? TriangleDir.Up : TriangleDir.Down);206 }207 208 /* Paint cursor labels */209 if (kind == ElevationWayPointKind.Highlighted) {210 drawSphere(g, Color.WHITE, c, pnt.x, pnt.y, BIG_WPT_RADIUS);211 drawLabel(ElevationHelper.getTimeText(wpt), pnt.x, pnt.y212 - g.getFontMetrics().getHeight() - 5, g);213 drawLabel(ElevationHelper.getElevationText(wpt), pnt.x, pnt.y214 + g.getFontMetrics().getHeight() + 5, g);215 }216 }217 218 /**219 * Renders a min/max point220 * 221 * @param g222 * The graphics context.223 * @param profile224 * The elevation profile.225 * @param mv226 * The map view instance.227 * @param wpt228 * The way point to render.229 * @param kind230 * The way point kind (start, end, max,...).231 */232 private void renderMinMaxPoint(Graphics g, IElevationProfile profile,233 MapView mv, WayPoint wpt, ElevationWayPointKind kind) {234 235 Color c = getColorForWaypoint(profile, wpt, kind);236 int eleH = (int) ElevationHelper.getElevation(wpt);237 Point pnt = mv.getPoint(wpt.getEastNorth());238 239 TriangleDir td = TriangleDir.Up;240 241 switch (kind) {242 case MaxElevation:243 td = TriangleDir.Up;244 break;245 case MinElevation:246 td = TriangleDir.Down;247 break;248 case EndPoint:249 td = TriangleDir.Left;250 break;251 case StartPoint:252 td = TriangleDir.Right;253 break;254 default:255 return; // nothing to do256 }257 258 drawRegularTriangle(g, c, td, pnt.x, pnt.y,259 DefaultElevationProfileRenderer.TRIANGLE_BASESIZE);260 261 drawLabel(ElevationHelper.getElevationText(eleH), pnt.x, pnt.y262 + g.getFontMetrics().getHeight(), g, c);263 }264 265 /**266 * Draws a regular triangle.267 * 268 * @param g269 * The graphics context.270 * @param c271 * The fill color of the triangle.272 * @param dir273 * The direction of the triangle274 * @param x275 * The x coordinate in the graphics context.276 * @param y277 * The y coordinate in the graphics context.278 * @param baseLength279 * The side length in pixel of the triangle.280 */281 private void drawRegularTriangle(Graphics g, Color c, TriangleDir dir,282 int x, int y, int baseLength) {283 if (baseLength < 2)284 return; // cannot render triangle285 286 int b2 = baseLength >> 1;287 288 // coordinates for upwards directed triangle289 Point p[] = new Point[3];290 291 for (int i = 0; i < p.length; i++) {292 p[i] = new Point();293 }294 295 p[0].x = -b2;296 p[0].y = b2;297 298 p[1].x = b2;299 p[1].y = b2;300 301 p[2].x = 0;302 p[2].y = -b2;303 304 Triangle t = new Triangle(p[0], p[1], p[2]);305 306 // rotation angle in rad307 double theta = 0.0;308 309 switch (dir) {310 case Up:311 theta = 0.0;312 break;313 case Down:314 theta = RAD_180;315 break;316 case Left:317 theta = -RAD_90;318 break;319 case Right:320 theta = RAD_90;321 break;322 }323 324 // rotate shape325 AffineTransform at = AffineTransform.getRotateInstance(theta);326 Shape tRot = at.createTransformedShape(t);327 // translate shape328 AffineTransform at2 = AffineTransform.getTranslateInstance(x, y);329 Shape ts = at2.createTransformedShape(tRot);330 331 // draw the shape332 Graphics2D g2 = (Graphics2D) g;333 if (g2 != null) {334 Color oldC = g2.getColor();335 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,336 RenderingHints.VALUE_ANTIALIAS_ON);337 g2.setColor(c);338 g2.fill(ts);339 g2.setColor(oldC);340 }341 }342 343 /**344 * Renders a start/end point.345 * 346 * @param g347 * The graphics context.348 * @param profile349 * The elevation profile.350 * @param mv351 * The map view instance.352 * @param wpt353 * The way point to render.354 * @param kind355 * The way point kind (start, end, max,...).356 */357 private void renderStartEndPoint(Graphics g, IElevationProfile profile,358 MapView mv, WayPoint wpt, ElevationWayPointKind kind) {359 360 Color c = getColorForWaypoint(profile, wpt, kind);361 Point pnt = mv.getPoint(wpt.getEastNorth());362 drawSphere(g, Color.WHITE, c, pnt.x, pnt.y, BIG_WPT_RADIUS);363 }364 365 /**366 * Draws a shaded sphere.367 * 368 * @param g369 * The graphics context.370 * @param firstCol371 * The focus color (usually white).372 * @param secondCol373 * The sphere color.374 * @param x375 * The x coordinate of the sphere center.376 * @param y377 * The y coordinate of the sphere center.378 * @param radius379 * The radius of the sphere.380 */381 private void drawSphere(Graphics g, Color firstCol, Color secondCol, int x,382 int y, int radius) {383 Point2D center = new Point2D.Float(x, y);384 Point2D focus = new Point2D.Float(x - (radius * 0.6f), y385 - (radius * 0.6f));386 float[] dist = { 0.1f, 0.2f, 1.0f };387 Color[] colors = { firstCol, secondCol, Color.DARK_GRAY };388 RadialGradientPaint p = new RadialGradientPaint(center, radius, focus,389 dist, colors, CycleMethod.NO_CYCLE);390 391 Graphics2D g2 = (Graphics2D) g;392 if (g2 != null) {393 g2.setPaint(p);394 int r2 = radius / 2;395 g2.fillOval(x - r2, y - r2, radius, radius);396 }397 }398 399 /**400 * Draws a label within a filled rounded rectangle with standard gradient colors.401 * 402 * @param s403 * The text to draw.404 * @param x405 * The x coordinate of the label.406 * @param y407 * The y coordinate of the label.408 * @param g409 * The graphics context.410 */411 private void drawLabel(String s, int x, int y, Graphics g) {412 drawLabel(s, x, y, g, Color.GRAY);413 }414 415 /**416 * Draws a label within a filled rounded rectangle with the specified second gradient color (first color is <tt>Color.WHITE<tt>).417 * 418 * @param s419 * The text to draw.420 * @param x421 * The x coordinate of the label.422 * @param y423 * The y coordinate of the label.424 * @param g425 * The graphics context.426 * @param secondGradColor427 * The second color of the gradient.428 */429 private void drawLabel(String s, int x, int y, Graphics g,430 Color secondGradColor) {431 Graphics2D g2d = (Graphics2D) g;432 433 int width = g.getFontMetrics(g.getFont()).stringWidth(s) + 10;434 int height = g.getFont().getSize() + g.getFontMetrics().getLeading()435 + 5;436 437 Rectangle r = new Rectangle(x - (width / 2), y - (height / 2), width,438 height);439 440 if (isForbiddenArea(r)) {441 return; // no space left, skip this label442 }443 444 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,445 RenderingHints.VALUE_ANTIALIAS_ON);446 GradientPaint gradient = new GradientPaint(x, y, Color.WHITE, x, y447 + (height / 2), secondGradColor, false);448 g2d.setPaint(gradient);449 450 g2d.fillRoundRect(r.x, r.y, r.width, r.height, ROUND_RECT_RADIUS,451 ROUND_RECT_RADIUS);452 453 g2d.setColor(Color.BLACK);454 455 g2d.drawRoundRect(r.x, r.y, r.width, r.height, ROUND_RECT_RADIUS,456 ROUND_RECT_RADIUS);457 g2d.drawString(s, x - (width / 2) + 5, y + (height / 2) - 3);458 459 forbiddenRects.add(r);460 }461 462 /**463 * Draws a label with an additional triangle on the left side.464 * 465 * @param s466 * The text to draw.467 * @param x468 * The x coordinate of the label.469 * @param y470 * The y coordinate of the label.471 * @param g472 * The graphics context.473 * @param secondGradColor474 * The second color of the gradient.475 * @param baseLength476 * The base length of the triangle in pixels.477 * @param triangleColor478 * The color of the triangle.479 * @param triangleDir480 * The direction of the triangle.481 */482 private void drawLabelWithTriangle(String s, int x, int y, Graphics g,483 Color secondGradColor, int baseLength, Color triangleColor,484 TriangleDir triangleDir) {485 Graphics2D g2d = (Graphics2D) g;486 487 int width = g.getFontMetrics(g.getFont()).stringWidth(s) + 10 + baseLength + 5;488 int height = g.getFont().getSize() + g.getFontMetrics().getLeading() + 5;489 490 Rectangle r = new Rectangle(x - (width / 2), y - (height / 2), width, height);491 492 if (isForbiddenArea(r)) {493 return; // no space left, skip this label494 }495 496 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,497 RenderingHints.VALUE_ANTIALIAS_ON);498 GradientPaint gradient = new GradientPaint(x, y, Color.WHITE, x, y499 + (height / 2), secondGradColor, false);500 g2d.setPaint(gradient);501 502 g2d.fillRoundRect(r.x, r.y, r.width, r.height, ROUND_RECT_RADIUS,503 ROUND_RECT_RADIUS);504 505 g2d.setColor(Color.BLACK);506 507 g2d.drawRoundRect(r.x, r.y, r.width, r.height, ROUND_RECT_RADIUS,508 ROUND_RECT_RADIUS);509 g2d.drawString(s, x - (width / 2) + 8 + baseLength, y + (height / 2) - 3);510 drawRegularTriangle(g2d, triangleColor, triangleDir, r.x + baseLength,511 r.y + baseLength, baseLength);512 513 forbiddenRects.add(r);514 }515 516 /**517 * Checks, if the rectangle has been 'reserved' by an previous draw action.518 * 519 * @param r520 * The area to check for.521 * @return true, if area is already occupied by another rectangle.522 */523 private boolean isForbiddenArea(Rectangle r) {524 525 for (Rectangle rTest : forbiddenRects) {526 if (r.intersects(rTest))527 return true;528 }529 return false;530 }531 532 @Override533 public void beginRendering() {534 forbiddenRects.clear();535 }536 537 @Override538 public void finishRendering() {539 // nothing to do currently540 }35 private static final int ROUND_RECT_RADIUS = 6; 36 /** 37 * 38 */ 39 private static final int TRIANGLE_BASESIZE = 24; 40 /** 41 * 42 */ 43 private static final int BASIC_WPT_RADIUS = 1; 44 private static final int BIG_WPT_RADIUS = BASIC_WPT_RADIUS * 16; 45 46 // predefined colors 47 private static final Color HIGH_COLOR = ElevationColors.EPMidBlue; 48 private static final Color LOW_COLOR = ElevationColors.EPMidBlue; 49 private static final Color START_COLOR = Color.GREEN; 50 private static final Color END_POINT = Color.RED; 51 private static final Color LEVEL_GAIN_COLOR = Color.GREEN; 52 private static final Color LEVEL_LOSS_COLOR = Color.RED; 53 private static final Color MARKER_POINT = Color.YELLOW; 54 // Predefined radians 55 private static final double RAD_180 = Math.PI; 56 // private static final double RAD_270 = Math.PI * 1.5; 57 private static final double RAD_90 = Math.PI * 0.5; 58 59 private final List<Rectangle> forbiddenRects = new ArrayList<>(); 60 61 @Override 62 public Color getColorForWaypoint(IElevationProfile profile, WayPoint wpt, 63 ElevationWayPointKind kind) { 64 65 if (wpt == null || profile == null) { 66 System.err.println(String.format( 67 "Cannot determine color: prof=%s, wpt=%s", profile, wpt)); 68 return null; 69 } 70 71 switch (kind) { 72 case Plain: 73 return Color.LIGHT_GRAY; 74 case ElevationLevelLoss: 75 return LEVEL_LOSS_COLOR; 76 case ElevationLevelGain: 77 return LEVEL_GAIN_COLOR; 78 case Highlighted: 79 return Color.ORANGE; 80 case ElevationGainHigh: 81 return Color.getHSBColor(0.3f, 1.0f, 1.0f); // green 82 case ElevationLossHigh: 83 return Color.getHSBColor(0, 1.0f, 1.0f); // red 84 case ElevationGainLow: 85 return Color.getHSBColor(0.3f, 0.5f, 1.0f); // green with low sat 86 case ElevationLossLow: 87 return Color.getHSBColor(0, 0.5f, 1.0f); // red with low sat 88 case FullHour: 89 return MARKER_POINT; 90 case MaxElevation: 91 return HIGH_COLOR; 92 case MinElevation: 93 return LOW_COLOR; 94 case StartPoint: 95 return START_COLOR; 96 case EndPoint: 97 return END_POINT; 98 default: 99 break; 100 } 101 102 throw new RuntimeException("Unknown way point kind: " + kind); 103 } 104 105 @Override 106 public void renderWayPoint(Graphics g, IElevationProfile profile, 107 MapView mv, WayPoint wpt, ElevationWayPointKind kind) { 108 109 CheckParameterUtil.ensureParameterNotNull(g, "graphics"); 110 CheckParameterUtil.ensureParameterNotNull(profile, "profile"); 111 CheckParameterUtil.ensureParameterNotNull(mv, "map view"); 112 113 if (wpt == null) { 114 System.err.println(String.format( 115 "Cannot paint: mv=%s, prof=%s, wpt=%s", mv, profile, wpt)); 116 return; 117 } 118 119 switch (kind) { 120 case MinElevation: 121 case MaxElevation: 122 renderMinMaxPoint(g, profile, mv, wpt, kind); 123 break; 124 case EndPoint: 125 case StartPoint: 126 renderStartEndPoint(g, profile, mv, wpt, kind); 127 break; 128 default: 129 renderRegularWayPoint(g, profile, mv, wpt, kind); 130 break; 131 } 132 } 133 134 @Override 135 public void renderLine(Graphics g, IElevationProfile profile, 136 MapView mv, WayPoint wpt1, WayPoint wpt2, ElevationWayPointKind kind) { 137 138 CheckParameterUtil.ensureParameterNotNull(g, "graphics"); 139 CheckParameterUtil.ensureParameterNotNull(profile, "profile"); 140 CheckParameterUtil.ensureParameterNotNull(mv, "map view"); 141 142 if (wpt1 == null || wpt2 == null) { 143 System.err.println(String.format( 144 "Cannot paint line: mv=%s, prof=%s, kind = %s", mv, profile, kind)); 145 return; 146 } 147 148 // obtain and set color 149 g.setColor(getColorForWaypoint(profile, wpt2, kind)); 150 151 // transform to view 152 Point pnt1 = mv.getPoint(wpt1.getEastNorth()); 153 Point pnt2 = mv.getPoint(wpt2.getEastNorth()); 154 155 // use thick line, if possible 156 if (g instanceof Graphics2D) { 157 Graphics2D g2 = (Graphics2D) g; 158 Stroke oldS = g2.getStroke(); 159 try { 160 g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); 161 g2.drawLine(pnt1.x, pnt1.y, pnt2.x, pnt2.y); 162 } finally { 163 // must be restored; otherwise other layers may using this style, too 164 g2.setStroke(oldS); 165 } 166 } else { 167 // only poor man's graphics 168 g.drawLine(pnt1.x, pnt1.y, pnt2.x, pnt2.y); 169 } 170 } 171 172 /** 173 * Renders a regular way point. 174 * 175 * @param g 176 * The graphics context. 177 * @param profile 178 * The elevation profile. 179 * @param mv 180 * The map view instance. 181 * @param wpt 182 * The way point to render. 183 * @param kind 184 * The way point kind (start, end, max,...). 185 */ 186 private void renderRegularWayPoint(Graphics g, IElevationProfile profile, 187 MapView mv, WayPoint wpt, ElevationWayPointKind kind) { 188 189 Color c = getColorForWaypoint(profile, wpt, kind); 190 Point pnt = mv.getPoint(wpt.getEastNorth()); 191 192 /* Paint full hour label */ 193 if (kind == ElevationWayPointKind.FullHour) { 194 int hour = ElevationHelper.getHourOfWayPoint(wpt); 195 drawLabel(String.format("%02d:00", hour), pnt.x, pnt.y 196 + g.getFontMetrics().getHeight(), g); 197 } 198 199 /* Paint label for elevation levels */ 200 if (kind == ElevationWayPointKind.ElevationLevelGain || kind == ElevationWayPointKind.ElevationLevelLoss) { 201 int ele = ((int) Math.rint(ElevationHelper.getElevation(wpt) / 100.0)) * 100; 202 drawLabelWithTriangle(ElevationHelper.getElevationText(ele), pnt.x, pnt.y 203 + g.getFontMetrics().getHeight(), g, Color.darkGray, 8, 204 getColorForWaypoint(profile, wpt, kind), 205 kind == ElevationWayPointKind.ElevationLevelGain ? TriangleDir.Up : TriangleDir.Down); 206 } 207 208 /* Paint cursor labels */ 209 if (kind == ElevationWayPointKind.Highlighted) { 210 drawSphere(g, Color.WHITE, c, pnt.x, pnt.y, BIG_WPT_RADIUS); 211 drawLabel(ElevationHelper.getTimeText(wpt), pnt.x, pnt.y 212 - g.getFontMetrics().getHeight() - 5, g); 213 drawLabel(ElevationHelper.getElevationText(wpt), pnt.x, pnt.y 214 + g.getFontMetrics().getHeight() + 5, g); 215 } 216 } 217 218 /** 219 * Renders a min/max point 220 * 221 * @param g 222 * The graphics context. 223 * @param profile 224 * The elevation profile. 225 * @param mv 226 * The map view instance. 227 * @param wpt 228 * The way point to render. 229 * @param kind 230 * The way point kind (start, end, max,...). 231 */ 232 private void renderMinMaxPoint(Graphics g, IElevationProfile profile, 233 MapView mv, WayPoint wpt, ElevationWayPointKind kind) { 234 235 Color c = getColorForWaypoint(profile, wpt, kind); 236 int eleH = (int) ElevationHelper.getElevation(wpt); 237 Point pnt = mv.getPoint(wpt.getEastNorth()); 238 239 TriangleDir td = TriangleDir.Up; 240 241 switch (kind) { 242 case MaxElevation: 243 td = TriangleDir.Up; 244 break; 245 case MinElevation: 246 td = TriangleDir.Down; 247 break; 248 case EndPoint: 249 td = TriangleDir.Left; 250 break; 251 case StartPoint: 252 td = TriangleDir.Right; 253 break; 254 default: 255 return; // nothing to do 256 } 257 258 drawRegularTriangle(g, c, td, pnt.x, pnt.y, 259 DefaultElevationProfileRenderer.TRIANGLE_BASESIZE); 260 261 drawLabel(ElevationHelper.getElevationText(eleH), pnt.x, pnt.y 262 + g.getFontMetrics().getHeight(), g, c); 263 } 264 265 /** 266 * Draws a regular triangle. 267 * 268 * @param g 269 * The graphics context. 270 * @param c 271 * The fill color of the triangle. 272 * @param dir 273 * The direction of the triangle 274 * @param x 275 * The x coordinate in the graphics context. 276 * @param y 277 * The y coordinate in the graphics context. 278 * @param baseLength 279 * The side length in pixel of the triangle. 280 */ 281 private void drawRegularTriangle(Graphics g, Color c, TriangleDir dir, 282 int x, int y, int baseLength) { 283 if (baseLength < 2) 284 return; // cannot render triangle 285 286 int b2 = baseLength >> 1; 287 288 // coordinates for upwards directed triangle 289 Point p[] = new Point[3]; 290 291 for (int i = 0; i < p.length; i++) { 292 p[i] = new Point(); 293 } 294 295 p[0].x = -b2; 296 p[0].y = b2; 297 298 p[1].x = b2; 299 p[1].y = b2; 300 301 p[2].x = 0; 302 p[2].y = -b2; 303 304 Triangle t = new Triangle(p[0], p[1], p[2]); 305 306 // rotation angle in rad 307 double theta = 0.0; 308 309 switch (dir) { 310 case Up: 311 theta = 0.0; 312 break; 313 case Down: 314 theta = RAD_180; 315 break; 316 case Left: 317 theta = -RAD_90; 318 break; 319 case Right: 320 theta = RAD_90; 321 break; 322 } 323 324 // rotate shape 325 AffineTransform at = AffineTransform.getRotateInstance(theta); 326 Shape tRot = at.createTransformedShape(t); 327 // translate shape 328 AffineTransform at2 = AffineTransform.getTranslateInstance(x, y); 329 Shape ts = at2.createTransformedShape(tRot); 330 331 // draw the shape 332 Graphics2D g2 = (Graphics2D) g; 333 if (g2 != null) { 334 Color oldC = g2.getColor(); 335 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 336 RenderingHints.VALUE_ANTIALIAS_ON); 337 g2.setColor(c); 338 g2.fill(ts); 339 g2.setColor(oldC); 340 } 341 } 342 343 /** 344 * Renders a start/end point. 345 * 346 * @param g 347 * The graphics context. 348 * @param profile 349 * The elevation profile. 350 * @param mv 351 * The map view instance. 352 * @param wpt 353 * The way point to render. 354 * @param kind 355 * The way point kind (start, end, max,...). 356 */ 357 private void renderStartEndPoint(Graphics g, IElevationProfile profile, 358 MapView mv, WayPoint wpt, ElevationWayPointKind kind) { 359 360 Color c = getColorForWaypoint(profile, wpt, kind); 361 Point pnt = mv.getPoint(wpt.getEastNorth()); 362 drawSphere(g, Color.WHITE, c, pnt.x, pnt.y, BIG_WPT_RADIUS); 363 } 364 365 /** 366 * Draws a shaded sphere. 367 * 368 * @param g 369 * The graphics context. 370 * @param firstCol 371 * The focus color (usually white). 372 * @param secondCol 373 * The sphere color. 374 * @param x 375 * The x coordinate of the sphere center. 376 * @param y 377 * The y coordinate of the sphere center. 378 * @param radius 379 * The radius of the sphere. 380 */ 381 private void drawSphere(Graphics g, Color firstCol, Color secondCol, int x, 382 int y, int radius) { 383 Point2D center = new Point2D.Float(x, y); 384 Point2D focus = new Point2D.Float(x - (radius * 0.6f), y 385 - (radius * 0.6f)); 386 float[] dist = { 0.1f, 0.2f, 1.0f }; 387 Color[] colors = { firstCol, secondCol, Color.DARK_GRAY }; 388 RadialGradientPaint p = new RadialGradientPaint(center, radius, focus, 389 dist, colors, CycleMethod.NO_CYCLE); 390 391 Graphics2D g2 = (Graphics2D) g; 392 if (g2 != null) { 393 g2.setPaint(p); 394 int r2 = radius / 2; 395 g2.fillOval(x - r2, y - r2, radius, radius); 396 } 397 } 398 399 /** 400 * Draws a label within a filled rounded rectangle with standard gradient colors. 401 * 402 * @param s 403 * The text to draw. 404 * @param x 405 * The x coordinate of the label. 406 * @param y 407 * The y coordinate of the label. 408 * @param g 409 * The graphics context. 410 */ 411 private void drawLabel(String s, int x, int y, Graphics g) { 412 drawLabel(s, x, y, g, Color.GRAY); 413 } 414 415 /** 416 * Draws a label within a filled rounded rectangle with the specified second gradient color (first color is <tt>Color.WHITE<tt>). 417 * 418 * @param s 419 * The text to draw. 420 * @param x 421 * The x coordinate of the label. 422 * @param y 423 * The y coordinate of the label. 424 * @param g 425 * The graphics context. 426 * @param secondGradColor 427 * The second color of the gradient. 428 */ 429 private void drawLabel(String s, int x, int y, Graphics g, 430 Color secondGradColor) { 431 Graphics2D g2d = (Graphics2D) g; 432 433 int width = g.getFontMetrics(g.getFont()).stringWidth(s) + 10; 434 int height = g.getFont().getSize() + g.getFontMetrics().getLeading() 435 + 5; 436 437 Rectangle r = new Rectangle(x - (width / 2), y - (height / 2), width, 438 height); 439 440 if (isForbiddenArea(r)) { 441 return; // no space left, skip this label 442 } 443 444 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 445 RenderingHints.VALUE_ANTIALIAS_ON); 446 GradientPaint gradient = new GradientPaint(x, y, Color.WHITE, x, y 447 + (height / 2), secondGradColor, false); 448 g2d.setPaint(gradient); 449 450 g2d.fillRoundRect(r.x, r.y, r.width, r.height, ROUND_RECT_RADIUS, 451 ROUND_RECT_RADIUS); 452 453 g2d.setColor(Color.BLACK); 454 455 g2d.drawRoundRect(r.x, r.y, r.width, r.height, ROUND_RECT_RADIUS, 456 ROUND_RECT_RADIUS); 457 g2d.drawString(s, x - (width / 2) + 5, y + (height / 2) - 3); 458 459 forbiddenRects.add(r); 460 } 461 462 /** 463 * Draws a label with an additional triangle on the left side. 464 * 465 * @param s 466 * The text to draw. 467 * @param x 468 * The x coordinate of the label. 469 * @param y 470 * The y coordinate of the label. 471 * @param g 472 * The graphics context. 473 * @param secondGradColor 474 * The second color of the gradient. 475 * @param baseLength 476 * The base length of the triangle in pixels. 477 * @param triangleColor 478 * The color of the triangle. 479 * @param triangleDir 480 * The direction of the triangle. 481 */ 482 private void drawLabelWithTriangle(String s, int x, int y, Graphics g, 483 Color secondGradColor, int baseLength, Color triangleColor, 484 TriangleDir triangleDir) { 485 Graphics2D g2d = (Graphics2D) g; 486 487 int width = g.getFontMetrics(g.getFont()).stringWidth(s) + 10 + baseLength + 5; 488 int height = g.getFont().getSize() + g.getFontMetrics().getLeading() + 5; 489 490 Rectangle r = new Rectangle(x - (width / 2), y - (height / 2), width, height); 491 492 if (isForbiddenArea(r)) { 493 return; // no space left, skip this label 494 } 495 496 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 497 RenderingHints.VALUE_ANTIALIAS_ON); 498 GradientPaint gradient = new GradientPaint(x, y, Color.WHITE, x, y 499 + (height / 2), secondGradColor, false); 500 g2d.setPaint(gradient); 501 502 g2d.fillRoundRect(r.x, r.y, r.width, r.height, ROUND_RECT_RADIUS, 503 ROUND_RECT_RADIUS); 504 505 g2d.setColor(Color.BLACK); 506 507 g2d.drawRoundRect(r.x, r.y, r.width, r.height, ROUND_RECT_RADIUS, 508 ROUND_RECT_RADIUS); 509 g2d.drawString(s, x - (width / 2) + 8 + baseLength, y + (height / 2) - 3); 510 drawRegularTriangle(g2d, triangleColor, triangleDir, r.x + baseLength, 511 r.y + baseLength, baseLength); 512 513 forbiddenRects.add(r); 514 } 515 516 /** 517 * Checks, if the rectangle has been 'reserved' by an previous draw action. 518 * 519 * @param r 520 * The area to check for. 521 * @return true, if area is already occupied by another rectangle. 522 */ 523 private boolean isForbiddenArea(Rectangle r) { 524 525 for (Rectangle rTest : forbiddenRects) { 526 if (r.intersects(rTest)) 527 return true; 528 } 529 return false; 530 } 531 532 @Override 533 public void beginRendering() { 534 forbiddenRects.clear(); 535 } 536 537 @Override 538 public void finishRendering() { 539 // nothing to do currently 540 } 541 541 542 542 -
applications/editors/josm/plugins/ElevationProfile/src/org/openstreetmap/josm/plugins/elevation/gui/ElevationProfileDialog.java
r30532 r30737 47 47 public class ElevationProfileDialog extends ToggleDialog implements LayerChangeListener, ComponentListener { 48 48 49 private static final String EMPTY_DATA_STRING = "-";50 private static final long serialVersionUID = -868463893732535577L;51 /* Elevation profile instance */52 private IElevationModel model;53 /* GPX data */54 private GpxLayer activeLayer = null;55 private final HashMap<GpxLayer, ElevationModel> layerMap = new HashMap<GpxLayer, ElevationModel>();56 57 /* UI elements */58 private final ElevationProfilePanel profPanel;59 private final JLabel minHeightLabel;60 private final JLabel maxHeightLabel;61 private final JLabel avrgHeightLabel;62 private final JLabel elevationGainLabel;63 private final JLabel totalTimeLabel;64 private final JLabel distLabel;65 private final JComboBox<IElevationProfile> trackCombo;66 private final JButton zoomButton;67 68 /* Listener to the elevation model */69 private final List<IElevationModelListener> listeners = new ArrayList<IElevationModelListener>();70 71 /**72 * Corresponding layer instance within map view.73 */74 private ElevationProfileLayer profileLayer;75 76 /**77 * Default constructor78 */79 public ElevationProfileDialog() {80 this(tr("Elevation Profile"), "elevation",81 tr("Open the elevation profile window."), null, 200, true);82 }83 84 /**85 * Constructor (see below)86 */87 public ElevationProfileDialog(String name, String iconName, String tooltip,88 Shortcut shortcut, int preferredHeight) {89 this(name, iconName, tooltip, shortcut, preferredHeight, false);90 }91 92 /**93 * Constructor94 * 95 * @param name96 * the name of the dialog97 * @param iconName98 * the name of the icon to be displayed99 * @param tooltip100 * the tool tip101 * @param shortcut102 * the shortcut103 * @param preferredHeight104 * the preferred height for the dialog105 * @param defShow106 * if the dialog should be shown by default, if there is no107 * preference108 */109 public ElevationProfileDialog(String name, String iconName, String tooltip,110 Shortcut shortcut, int preferredHeight, boolean defShow) {111 super(name, iconName, tooltip, shortcut, preferredHeight, defShow);112 113 // create model114 model = new ElevationModel();115 116 // top panel117 JPanel rootPanel = new JPanel();118 GridLayout gridLayout1 = new GridLayout(2, 1);119 rootPanel.setLayout(gridLayout1);120 121 // statistics panel122 JPanel statPanel = new JPanel();123 GridLayout gridLayoutStat = new GridLayout(2, 6);124 statPanel.setLayout(gridLayoutStat);125 126 // first row: Headlines with bold font127 String[] labels = new String[]{tr("Min"), tr("Avrg"), tr("Max"), tr("Dist"), tr("Gain"), tr("Time")};128 for (int i = 0; i < labels.length; i++) {129 JLabel lbl = new JLabel(labels[i]);130 lbl.setFont(getFont().deriveFont(Font.BOLD));131 statPanel.add(lbl);132 }133 134 // second row135 minHeightLabel = new JLabel("0 m");136 statPanel.add(minHeightLabel);137 avrgHeightLabel = new JLabel("0 m");138 statPanel.add(avrgHeightLabel);139 maxHeightLabel = new JLabel("0 m");140 statPanel.add(maxHeightLabel);141 distLabel = new JLabel("0 km");142 statPanel.add(distLabel);143 elevationGainLabel = new JLabel("0 m");144 statPanel.add(elevationGainLabel);145 totalTimeLabel = new JLabel("0");146 statPanel.add(totalTimeLabel);147 148 // track selection panel149 JPanel trackPanel = new JPanel();150 FlowLayout fl = new FlowLayout(FlowLayout.LEFT);151 trackPanel.setLayout(fl);152 153 JLabel lbTrack = new JLabel(tr("Tracks"));154 lbTrack.setFont(getFont().deriveFont(Font.BOLD));155 trackPanel.add(lbTrack);156 157 zoomButton = new JButton(tr("Zoom"));158 zoomButton.addActionListener(new ActionListener() {159 @Override160 public void actionPerformed(ActionEvent arg0) {161 if (model != null) {162 IElevationProfile profile = model.getCurrentProfile();163 if (profile != null) {164 Main.map.mapView.zoomTo(profile.getBounds());165 }166 }167 168 }169 });170 zoomButton.setEnabled(false);171 172 trackCombo = new JComboBox<>(new TrackModel());173 trackCombo.setPreferredSize(new Dimension(200, 24)); // HACK!174 trackCombo.setEnabled(false); // we have no model on startup175 176 trackPanel.add(trackCombo);177 trackPanel.add(zoomButton);178 179 // assemble root panel180 rootPanel.add(statPanel);181 rootPanel.add(trackPanel);182 183 JPanel mainPanel = new JPanel(new BorderLayout());184 mainPanel.add(rootPanel, BorderLayout.PAGE_END);185 186 // add chart component187 profPanel = new ElevationProfilePanel(null);188 mainPanel.add(profPanel, BorderLayout.CENTER);189 profPanel.addComponentListener(this);190 191 createLayout(mainPanel, true, null);192 }193 194 @Override195 public void showNotify() {196 MapView.addLayerChangeListener(this);197 if (Main.isDisplayingMapView()) {198 Layer layer = Main.map.mapView.getActiveLayer();199 if (layer instanceof GpxLayer) {200 setActiveLayer((GpxLayer) layer);201 }202 }203 }204 205 @Override206 public void hideNotify() {207 MapView.removeLayerChangeListener(this);208 }209 210 /**211 * Gets the elevation model instance.212 * @return213 */214 public IElevationModel getModel() {215 return model;216 }217 218 /**219 * Sets the elevation model instance.220 * @param model The new model.221 */222 public void setModel(IElevationModel model) {223 if (this.model != model) {224 this.model = model;225 profPanel.setElevationModel(model);226 updateView();227 }228 }229 230 /**231 * Gets the associated layer instance of the elevation profile.232 * @return233 */234 public ElevationProfileLayer getProfileLayer() {235 return profileLayer;236 }237 238 /**239 * Sets the associated layer instance of the elevation profile.240 * @param profileLayer The elevation profile layer.241 */242 public void setProfileLayer(ElevationProfileLayer profileLayer) {243 if (this.profileLayer != profileLayer) {244 if (this.profileLayer != null) {245 profPanel.removeSelectionListener(this.profileLayer);246 }247 this.profileLayer = profileLayer;248 profPanel.addSelectionListener(this.profileLayer);249 }250 }251 252 /**253 * Refreshes the dialog when model data have changed and notifies clients254 * that the model has changed.255 */256 private void updateView() {257 if (model == null) {258 disableView();259 return;260 }261 262 IElevationProfile profile = model.getCurrentProfile();263 if (profile != null) {264 // Show name of profile in title265 setTitle(String.format("%s: %s", tr("Elevation Profile"), profile.getName()));266 267 if (profile.hasElevationData()) {268 // Show elevation data269 minHeightLabel.setText(270 ElevationHelper.getElevationText(profile.getMinHeight()));271 maxHeightLabel.setText(272 ElevationHelper.getElevationText(profile.getMaxHeight()));273 avrgHeightLabel.setText(274 ElevationHelper.getElevationText(profile.getAverageHeight()));275 elevationGainLabel.setText(276 ElevationHelper.getElevationText(profile.getGain()));277 }278 279 // compute values for time and distance280 long diff = profile.getTimeDifference();281 long minutes = diff / (1000 * 60);282 long hours = minutes / 60;283 minutes = minutes % 60;284 285 double dist = profile.getDistance();286 287 totalTimeLabel.setText(String.format("%d:%02d h", hours, minutes));288 distLabel.setText(NavigatableComponent.getSystemOfMeasurement().getDistText(dist));289 trackCombo.setEnabled(model.profileCount() > 1);290 trackCombo.setModel(new TrackModel());291 zoomButton.setEnabled(true);292 } else { // no elevation data, -> switch back to empty view293 disableView();294 }295 296 fireModelChanged();297 repaint();298 }299 300 private void disableView() {301 setTitle(String.format("%s: (No data)", tr("Elevation Profile")));302 303 minHeightLabel.setText(EMPTY_DATA_STRING);304 maxHeightLabel.setText(EMPTY_DATA_STRING);305 avrgHeightLabel.setText(EMPTY_DATA_STRING);306 elevationGainLabel.setText(EMPTY_DATA_STRING);307 totalTimeLabel.setText(EMPTY_DATA_STRING);308 distLabel.setText(EMPTY_DATA_STRING);309 trackCombo.setEnabled(false);310 zoomButton.setEnabled(false);311 }312 313 /**314 * Fires the 'model changed' event to all listeners.315 */316 protected void fireModelChanged() {317 for (IElevationModelListener listener : listeners) {318 listener.elevationProfileChanged(getModel().getCurrentProfile());319 }320 }321 322 /**323 * Adds a model listener to this instance.324 * 325 * @param listener326 * The listener to add.327 */328 public void addModelListener(IElevationModelListener listener) {329 this.listeners.add(listener);330 }331 332 /**333 * Removes a model listener from this instance.334 * 335 * @param listener336 * The listener to remove.337 */338 public void removeModelListener(IElevationModelListener listener) {339 this.listeners.remove(listener);340 }341 342 /**343 * Removes all listeners from this instance.344 */345 public void removeAllListeners() {346 this.listeners.clear();347 }348 349 @Override350 public void activeLayerChange(Layer oldLayer, Layer newLayer) {351 if (newLayer instanceof GpxLayer) {352 setActiveLayer((GpxLayer) newLayer);353 }354 }355 356 private void setActiveLayer(GpxLayer newLayer) {357 if (activeLayer != newLayer) {358 activeLayer = newLayer;359 360 // layer does not exist -> create361 if (!layerMap.containsKey(newLayer)) {362 GpxData gpxData = newLayer.data;363 ElevationModel newEM = new ElevationModel(newLayer.getName(),364 gpxData);365 layerMap.put(newLayer, newEM);366 }367 368 ElevationModel em = layerMap.get(newLayer);369 setModel(em);370 }371 }372 373 @Override374 public void layerAdded(Layer newLayer) {375 if (newLayer instanceof GpxLayer) {376 GpxLayer gpxLayer = (GpxLayer) newLayer;377 setActiveLayer(gpxLayer);378 }379 }380 381 @Override382 public void layerRemoved(Layer oldLayer) {383 if (layerMap.containsKey(oldLayer)) {384 layerMap.remove(oldLayer);385 }386 387 if (layerMap.size() == 0) {388 setModel(null);389 if (profileLayer != null) {390 profileLayer.setProfile(null);391 }392 }393 }394 395 @Override396 public void componentHidden(ComponentEvent e) {397 }398 399 @Override400 public void componentMoved(ComponentEvent e) {401 }402 403 @Override404 public void componentResized(ComponentEvent e) {405 }406 407 @Override408 public void componentShown(ComponentEvent e) {409 }410 411 class TrackModel implements ComboBoxModel<IElevationProfile> {412 private Collection<ListDataListener> listeners;413 414 @Override415 public void addListDataListener(ListDataListener arg0) {416 if (listeners == null) {417 listeners = new ArrayList<ListDataListener>();418 }419 listeners.add(arg0);420 }421 422 @Override423 public IElevationProfile getElementAt(int index) {424 if (model == null) return null;425 426 IElevationProfile ep = model.getProfiles().get(index);427 return ep;428 }429 430 @Override431 public int getSize() {432 if (model == null) return 0;433 434 return model.profileCount();435 }436 437 @Override438 public void removeListDataListener(ListDataListener listener) {439 if (listeners == null) return;440 441 listeners.remove(listener);442 }443 444 @Override445 public IElevationProfile getSelectedItem() {446 if (model == null) return null;447 448 return model.getCurrentProfile();449 }450 451 @Override452 public void setSelectedItem(Object selectedObject) {453 if (model != null && selectedObject instanceof IElevationProfile) {454 model.setCurrentProfile((IElevationProfile) selectedObject);455 profileLayer.setProfile(model.getCurrentProfile());456 457 repaint();458 }459 }460 }49 private static final String EMPTY_DATA_STRING = "-"; 50 private static final long serialVersionUID = -868463893732535577L; 51 /* Elevation profile instance */ 52 private IElevationModel model; 53 /* GPX data */ 54 private GpxLayer activeLayer = null; 55 private final HashMap<GpxLayer, ElevationModel> layerMap = new HashMap<>(); 56 57 /* UI elements */ 58 private final ElevationProfilePanel profPanel; 59 private final JLabel minHeightLabel; 60 private final JLabel maxHeightLabel; 61 private final JLabel avrgHeightLabel; 62 private final JLabel elevationGainLabel; 63 private final JLabel totalTimeLabel; 64 private final JLabel distLabel; 65 private final JComboBox<IElevationProfile> trackCombo; 66 private final JButton zoomButton; 67 68 /* Listener to the elevation model */ 69 private final List<IElevationModelListener> listeners = new ArrayList<>(); 70 71 /** 72 * Corresponding layer instance within map view. 73 */ 74 private ElevationProfileLayer profileLayer; 75 76 /** 77 * Default constructor 78 */ 79 public ElevationProfileDialog() { 80 this(tr("Elevation Profile"), "elevation", 81 tr("Open the elevation profile window."), null, 200, true); 82 } 83 84 /** 85 * Constructor (see below) 86 */ 87 public ElevationProfileDialog(String name, String iconName, String tooltip, 88 Shortcut shortcut, int preferredHeight) { 89 this(name, iconName, tooltip, shortcut, preferredHeight, false); 90 } 91 92 /** 93 * Constructor 94 * 95 * @param name 96 * the name of the dialog 97 * @param iconName 98 * the name of the icon to be displayed 99 * @param tooltip 100 * the tool tip 101 * @param shortcut 102 * the shortcut 103 * @param preferredHeight 104 * the preferred height for the dialog 105 * @param defShow 106 * if the dialog should be shown by default, if there is no 107 * preference 108 */ 109 public ElevationProfileDialog(String name, String iconName, String tooltip, 110 Shortcut shortcut, int preferredHeight, boolean defShow) { 111 super(name, iconName, tooltip, shortcut, preferredHeight, defShow); 112 113 // create model 114 model = new ElevationModel(); 115 116 // top panel 117 JPanel rootPanel = new JPanel(); 118 GridLayout gridLayout1 = new GridLayout(2, 1); 119 rootPanel.setLayout(gridLayout1); 120 121 // statistics panel 122 JPanel statPanel = new JPanel(); 123 GridLayout gridLayoutStat = new GridLayout(2, 6); 124 statPanel.setLayout(gridLayoutStat); 125 126 // first row: Headlines with bold font 127 String[] labels = new String[]{tr("Min"), tr("Avrg"), tr("Max"), tr("Dist"), tr("Gain"), tr("Time")}; 128 for (int i = 0; i < labels.length; i++) { 129 JLabel lbl = new JLabel(labels[i]); 130 lbl.setFont(getFont().deriveFont(Font.BOLD)); 131 statPanel.add(lbl); 132 } 133 134 // second row 135 minHeightLabel = new JLabel("0 m"); 136 statPanel.add(minHeightLabel); 137 avrgHeightLabel = new JLabel("0 m"); 138 statPanel.add(avrgHeightLabel); 139 maxHeightLabel = new JLabel("0 m"); 140 statPanel.add(maxHeightLabel); 141 distLabel = new JLabel("0 km"); 142 statPanel.add(distLabel); 143 elevationGainLabel = new JLabel("0 m"); 144 statPanel.add(elevationGainLabel); 145 totalTimeLabel = new JLabel("0"); 146 statPanel.add(totalTimeLabel); 147 148 // track selection panel 149 JPanel trackPanel = new JPanel(); 150 FlowLayout fl = new FlowLayout(FlowLayout.LEFT); 151 trackPanel.setLayout(fl); 152 153 JLabel lbTrack = new JLabel(tr("Tracks")); 154 lbTrack.setFont(getFont().deriveFont(Font.BOLD)); 155 trackPanel.add(lbTrack); 156 157 zoomButton = new JButton(tr("Zoom")); 158 zoomButton.addActionListener(new ActionListener() { 159 @Override 160 public void actionPerformed(ActionEvent arg0) { 161 if (model != null) { 162 IElevationProfile profile = model.getCurrentProfile(); 163 if (profile != null) { 164 Main.map.mapView.zoomTo(profile.getBounds()); 165 } 166 } 167 168 } 169 }); 170 zoomButton.setEnabled(false); 171 172 trackCombo = new JComboBox<>(new TrackModel()); 173 trackCombo.setPreferredSize(new Dimension(200, 24)); // HACK! 174 trackCombo.setEnabled(false); // we have no model on startup 175 176 trackPanel.add(trackCombo); 177 trackPanel.add(zoomButton); 178 179 // assemble root panel 180 rootPanel.add(statPanel); 181 rootPanel.add(trackPanel); 182 183 JPanel mainPanel = new JPanel(new BorderLayout()); 184 mainPanel.add(rootPanel, BorderLayout.PAGE_END); 185 186 // add chart component 187 profPanel = new ElevationProfilePanel(null); 188 mainPanel.add(profPanel, BorderLayout.CENTER); 189 profPanel.addComponentListener(this); 190 191 createLayout(mainPanel, true, null); 192 } 193 194 @Override 195 public void showNotify() { 196 MapView.addLayerChangeListener(this); 197 if (Main.isDisplayingMapView()) { 198 Layer layer = Main.map.mapView.getActiveLayer(); 199 if (layer instanceof GpxLayer) { 200 setActiveLayer((GpxLayer) layer); 201 } 202 } 203 } 204 205 @Override 206 public void hideNotify() { 207 MapView.removeLayerChangeListener(this); 208 } 209 210 /** 211 * Gets the elevation model instance. 212 * @return 213 */ 214 public IElevationModel getModel() { 215 return model; 216 } 217 218 /** 219 * Sets the elevation model instance. 220 * @param model The new model. 221 */ 222 public void setModel(IElevationModel model) { 223 if (this.model != model) { 224 this.model = model; 225 profPanel.setElevationModel(model); 226 updateView(); 227 } 228 } 229 230 /** 231 * Gets the associated layer instance of the elevation profile. 232 * @return 233 */ 234 public ElevationProfileLayer getProfileLayer() { 235 return profileLayer; 236 } 237 238 /** 239 * Sets the associated layer instance of the elevation profile. 240 * @param profileLayer The elevation profile layer. 241 */ 242 public void setProfileLayer(ElevationProfileLayer profileLayer) { 243 if (this.profileLayer != profileLayer) { 244 if (this.profileLayer != null) { 245 profPanel.removeSelectionListener(this.profileLayer); 246 } 247 this.profileLayer = profileLayer; 248 profPanel.addSelectionListener(this.profileLayer); 249 } 250 } 251 252 /** 253 * Refreshes the dialog when model data have changed and notifies clients 254 * that the model has changed. 255 */ 256 private void updateView() { 257 if (model == null) { 258 disableView(); 259 return; 260 } 261 262 IElevationProfile profile = model.getCurrentProfile(); 263 if (profile != null) { 264 // Show name of profile in title 265 setTitle(String.format("%s: %s", tr("Elevation Profile"), profile.getName())); 266 267 if (profile.hasElevationData()) { 268 // Show elevation data 269 minHeightLabel.setText( 270 ElevationHelper.getElevationText(profile.getMinHeight())); 271 maxHeightLabel.setText( 272 ElevationHelper.getElevationText(profile.getMaxHeight())); 273 avrgHeightLabel.setText( 274 ElevationHelper.getElevationText(profile.getAverageHeight())); 275 elevationGainLabel.setText( 276 ElevationHelper.getElevationText(profile.getGain())); 277 } 278 279 // compute values for time and distance 280 long diff = profile.getTimeDifference(); 281 long minutes = diff / (1000 * 60); 282 long hours = minutes / 60; 283 minutes = minutes % 60; 284 285 double dist = profile.getDistance(); 286 287 totalTimeLabel.setText(String.format("%d:%02d h", hours, minutes)); 288 distLabel.setText(NavigatableComponent.getSystemOfMeasurement().getDistText(dist)); 289 trackCombo.setEnabled(model.profileCount() > 1); 290 trackCombo.setModel(new TrackModel()); 291 zoomButton.setEnabled(true); 292 } else { // no elevation data, -> switch back to empty view 293 disableView(); 294 } 295 296 fireModelChanged(); 297 repaint(); 298 } 299 300 private void disableView() { 301 setTitle(String.format("%s: (No data)", tr("Elevation Profile"))); 302 303 minHeightLabel.setText(EMPTY_DATA_STRING); 304 maxHeightLabel.setText(EMPTY_DATA_STRING); 305 avrgHeightLabel.setText(EMPTY_DATA_STRING); 306 elevationGainLabel.setText(EMPTY_DATA_STRING); 307 totalTimeLabel.setText(EMPTY_DATA_STRING); 308 distLabel.setText(EMPTY_DATA_STRING); 309 trackCombo.setEnabled(false); 310 zoomButton.setEnabled(false); 311 } 312 313 /** 314 * Fires the 'model changed' event to all listeners. 315 */ 316 protected void fireModelChanged() { 317 for (IElevationModelListener listener : listeners) { 318 listener.elevationProfileChanged(getModel().getCurrentProfile()); 319 } 320 } 321 322 /** 323 * Adds a model listener to this instance. 324 * 325 * @param listener 326 * The listener to add. 327 */ 328 public void addModelListener(IElevationModelListener listener) { 329 this.listeners.add(listener); 330 } 331 332 /** 333 * Removes a model listener from this instance. 334 * 335 * @param listener 336 * The listener to remove. 337 */ 338 public void removeModelListener(IElevationModelListener listener) { 339 this.listeners.remove(listener); 340 } 341 342 /** 343 * Removes all listeners from this instance. 344 */ 345 public void removeAllListeners() { 346 this.listeners.clear(); 347 } 348 349 @Override 350 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 351 if (newLayer instanceof GpxLayer) { 352 setActiveLayer((GpxLayer) newLayer); 353 } 354 } 355 356 private void setActiveLayer(GpxLayer newLayer) { 357 if (activeLayer != newLayer) { 358 activeLayer = newLayer; 359 360 // layer does not exist -> create 361 if (!layerMap.containsKey(newLayer)) { 362 GpxData gpxData = newLayer.data; 363 ElevationModel newEM = new ElevationModel(newLayer.getName(), 364 gpxData); 365 layerMap.put(newLayer, newEM); 366 } 367 368 ElevationModel em = layerMap.get(newLayer); 369 setModel(em); 370 } 371 } 372 373 @Override 374 public void layerAdded(Layer newLayer) { 375 if (newLayer instanceof GpxLayer) { 376 GpxLayer gpxLayer = (GpxLayer) newLayer; 377 setActiveLayer(gpxLayer); 378 } 379 } 380 381 @Override 382 public void layerRemoved(Layer oldLayer) { 383 if (layerMap.containsKey(oldLayer)) { 384 layerMap.remove(oldLayer); 385 } 386 387 if (layerMap.size() == 0) { 388 setModel(null); 389 if (profileLayer != null) { 390 profileLayer.setProfile(null); 391 } 392 } 393 } 394 395 @Override 396 public void componentHidden(ComponentEvent e) { 397 } 398 399 @Override 400 public void componentMoved(ComponentEvent e) { 401 } 402 403 @Override 404 public void componentResized(ComponentEvent e) { 405 } 406 407 @Override 408 public void componentShown(ComponentEvent e) { 409 } 410 411 class TrackModel implements ComboBoxModel<IElevationProfile> { 412 private Collection<ListDataListener> listeners; 413 414 @Override 415 public void addListDataListener(ListDataListener arg0) { 416 if (listeners == null) { 417 listeners = new ArrayList<>(); 418 } 419 listeners.add(arg0); 420 } 421 422 @Override 423 public IElevationProfile getElementAt(int index) { 424 if (model == null) return null; 425 426 IElevationProfile ep = model.getProfiles().get(index); 427 return ep; 428 } 429 430 @Override 431 public int getSize() { 432 if (model == null) return 0; 433 434 return model.profileCount(); 435 } 436 437 @Override 438 public void removeListDataListener(ListDataListener listener) { 439 if (listeners == null) return; 440 441 listeners.remove(listener); 442 } 443 444 @Override 445 public IElevationProfile getSelectedItem() { 446 if (model == null) return null; 447 448 return model.getCurrentProfile(); 449 } 450 451 @Override 452 public void setSelectedItem(Object selectedObject) { 453 if (model != null && selectedObject instanceof IElevationProfile) { 454 model.setCurrentProfile((IElevationProfile) selectedObject); 455 profileLayer.setProfile(model.getCurrentProfile()); 456 457 repaint(); 458 } 459 } 460 } 461 461 } -
applications/editors/josm/plugins/ElevationProfile/src/org/openstreetmap/josm/plugins/elevation/gui/ElevationProfilePanel.java
r30344 r30737 36 36 */ 37 37 public class ElevationProfilePanel extends JPanel implements ComponentListener, MouseMotionListener { 38 /**39 * Serial version UID40 */41 private static final long serialVersionUID = -7343429725259575319L;42 private static final int BOTTOM_TEXT_Y_OFFSET = 7;43 44 private IElevationModel model;45 private Rectangle plotArea;46 private final IElevationProfileRenderer renderer = new DefaultElevationProfileRenderer();47 private int selectedIndex = -1;48 private final List<IElevationProfileSelectionListener> selectionChangedListeners = new ArrayList<IElevationProfileSelectionListener>();49 private boolean isPainting;50 private int step = 0;51 52 /**53 * Constructs a new ElevationProfilePanel with the given elevation profile.54 * @param profile The elevation profile to show in the panel.55 */56 public ElevationProfilePanel(IElevationModel profile) {57 super();58 this.model = profile;59 setDoubleBuffered(true);60 setBackground(Color.WHITE);61 createOrUpdatePlotArea();62 addComponentListener(this);63 addMouseMotionListener(this);64 65 Font lFont = getFont().deriveFont(9.0f);66 setFont(lFont);67 }68 69 /**70 * Gets the elevation profile instance.71 * @return72 */73 public IElevationModel getProfile() {74 return model;75 }76 77 /**78 * Sets the new elevation profile instance.79 * @param model80 */81 public void setElevationModel(IElevationModel model) {82 if (this.model != model) {83 this.model = model;84 invalidate();85 }86 }87 88 /**89 * Gets the plot area coordinates.90 * @return91 */92 public Rectangle getPlotArea() {93 return plotArea;94 }95 96 /**97 * Sets the plot area coordinates.98 * @param plotArea99 */100 public void setPlotArea(Rectangle plotArea) {101 this.plotArea = plotArea;102 }103 104 /**105 * Gets the selected index of the bar.106 * @return107 */108 public int getSelectedIndex() {109 return selectedIndex;110 }111 112 /**113 * Sets the selected index of the bar.114 * @param selectedIndex115 */116 public void setSelectedIndex(int selectedIndex) {117 this.selectedIndex = selectedIndex;118 119 if (model != null) {120 model.setCurrentProfile(selectedIndex);121 }122 }123 124 /**125 * Gets the selected (highlighted) way point.126 * @return The selected way point or null, if no way point is selected.127 */128 public WayPoint getSelectedWayPoint() {129 if (model == null) return null;130 131 IElevationProfile profile = model.getCurrentProfile();132 133 int selWp = this.selectedIndex * step;134 if (profile != null && profile.getWayPoints() != null && selWp > 0 && profile.getWayPoints().size() > selWp) {135 return profile.getWayPoints().get(selWp);136 } else {137 return null;138 }139 }140 141 /**142 * Adds a selection listener.143 * @param listener The listener instance to add.144 */145 public void addSelectionListener(IElevationProfileSelectionListener listener) {146 if (listener == null) return;147 148 selectionChangedListeners.add(listener);149 }150 151 /**152 * Removes a selection listener from the list.153 * @param listener The listener instance to remove.154 */155 public void removeSelectionListener(IElevationProfileSelectionListener listener) {156 if (listener == null) return;157 158 selectionChangedListeners.remove(listener);159 }160 161 /**162 * Removes all selection listeners.163 */164 public void removeAllSelectionListeners() {165 selectionChangedListeners.clear();166 }167 168 protected void fireSelectionChanged(WayPoint selWayPoint) {169 for (IElevationProfileSelectionListener listener : selectionChangedListeners) {170 listener.selectedWayPointChanged(selWayPoint);171 }172 }173 174 @Override175 public void paint(Graphics g) {176 isPainting = true;177 178 try {179 super.paint(g);180 createOrUpdatePlotArea();181 int y1 = getPlotBottom();182 183 g.setColor(Color.DARK_GRAY);184 g.drawLine(plotArea.x, plotArea.y, plotArea.x, plotArea.y185 + plotArea.height);186 g.drawLine(plotArea.x, plotArea.y + plotArea.height, plotArea.x187 + plotArea.width, plotArea.y + plotArea.height);188 189 190 if (model != null) {191 IElevationProfile profile = model.getCurrentProfile();192 if (profile != null && profile.hasElevationData()) {193 // Draw start and end date194 drawAlignedString(formatDate(profile.getStart()), 5, y1 + BOTTOM_TEXT_Y_OFFSET,195 TextAlignment.Left, g);196 drawAlignedString(formatDate(profile.getEnd()),197 getPlotRight(), y1 + BOTTOM_TEXT_Y_OFFSET, TextAlignment.Right, g);198 199 // Show SRTM indicator200 if (ElevationHelper.hasSrtmData(profile.getBounds())) {201 String txt = "SRTM";202 drawAlignedString(txt, getPlotHCenter(), y1 + BOTTOM_TEXT_Y_OFFSET, TextAlignment.Centered, g);203 }204 drawProfile(g);205 drawElevationLines(g);206 } else {207 // No profile or profile supports no elevation data208 drawAlignedString(tr("(No elevation data)"), getPlotHCenter(),209 getPlotVCenter(), TextAlignment.Centered, g);210 }211 }212 } finally {213 isPainting = false;214 }215 }216 217 /**218 * Draw a string with a specified alignment.219 * @param s The text to display.220 * @param x The x coordinate.221 * @param y The y coordinate.222 * @param align The text alignment.223 * @param g The graphics context.224 * @return The resulting rectangle of the drawn string.225 */226 private Rectangle drawAlignedString(String s, int x, int y,227 TextAlignment align, Graphics g) {228 FontMetrics fm = g.getFontMetrics();229 int w = fm.stringWidth(s);230 int h = fm.getHeight();231 232 int xoff = w / 2;233 int yoff = h / 2;234 235 if (align == TextAlignment.Left) {236 xoff = 0;237 }238 if (align == TextAlignment.Right) {239 xoff = w;240 }241 242 g.drawString(s, x - xoff, y + yoff);243 244 return new Rectangle(x - xoff, y - yoff, w, h);245 }246 247 /**248 * Draw a string which is horizontally centered around (x,y).249 * @param s The text to display.250 * @param x The x coordinate.251 * @param y The y coordinate.252 * @param g The graphics context.253 * @return The resulting rectangle of the drawn string.38 /** 39 * Serial version UID 40 */ 41 private static final long serialVersionUID = -7343429725259575319L; 42 private static final int BOTTOM_TEXT_Y_OFFSET = 7; 43 44 private IElevationModel model; 45 private Rectangle plotArea; 46 private final IElevationProfileRenderer renderer = new DefaultElevationProfileRenderer(); 47 private int selectedIndex = -1; 48 private final List<IElevationProfileSelectionListener> selectionChangedListeners = new ArrayList<>(); 49 private boolean isPainting; 50 private int step = 0; 51 52 /** 53 * Constructs a new ElevationProfilePanel with the given elevation profile. 54 * @param profile The elevation profile to show in the panel. 55 */ 56 public ElevationProfilePanel(IElevationModel profile) { 57 super(); 58 this.model = profile; 59 setDoubleBuffered(true); 60 setBackground(Color.WHITE); 61 createOrUpdatePlotArea(); 62 addComponentListener(this); 63 addMouseMotionListener(this); 64 65 Font lFont = getFont().deriveFont(9.0f); 66 setFont(lFont); 67 } 68 69 /** 70 * Gets the elevation profile instance. 71 * @return 72 */ 73 public IElevationModel getProfile() { 74 return model; 75 } 76 77 /** 78 * Sets the new elevation profile instance. 79 * @param model 80 */ 81 public void setElevationModel(IElevationModel model) { 82 if (this.model != model) { 83 this.model = model; 84 invalidate(); 85 } 86 } 87 88 /** 89 * Gets the plot area coordinates. 90 * @return 91 */ 92 public Rectangle getPlotArea() { 93 return plotArea; 94 } 95 96 /** 97 * Sets the plot area coordinates. 98 * @param plotArea 99 */ 100 public void setPlotArea(Rectangle plotArea) { 101 this.plotArea = plotArea; 102 } 103 104 /** 105 * Gets the selected index of the bar. 106 * @return 107 */ 108 public int getSelectedIndex() { 109 return selectedIndex; 110 } 111 112 /** 113 * Sets the selected index of the bar. 114 * @param selectedIndex 115 */ 116 public void setSelectedIndex(int selectedIndex) { 117 this.selectedIndex = selectedIndex; 118 119 if (model != null) { 120 model.setCurrentProfile(selectedIndex); 121 } 122 } 123 124 /** 125 * Gets the selected (highlighted) way point. 126 * @return The selected way point or null, if no way point is selected. 127 */ 128 public WayPoint getSelectedWayPoint() { 129 if (model == null) return null; 130 131 IElevationProfile profile = model.getCurrentProfile(); 132 133 int selWp = this.selectedIndex * step; 134 if (profile != null && profile.getWayPoints() != null && selWp > 0 && profile.getWayPoints().size() > selWp) { 135 return profile.getWayPoints().get(selWp); 136 } else { 137 return null; 138 } 139 } 140 141 /** 142 * Adds a selection listener. 143 * @param listener The listener instance to add. 144 */ 145 public void addSelectionListener(IElevationProfileSelectionListener listener) { 146 if (listener == null) return; 147 148 selectionChangedListeners.add(listener); 149 } 150 151 /** 152 * Removes a selection listener from the list. 153 * @param listener The listener instance to remove. 154 */ 155 public void removeSelectionListener(IElevationProfileSelectionListener listener) { 156 if (listener == null) return; 157 158 selectionChangedListeners.remove(listener); 159 } 160 161 /** 162 * Removes all selection listeners. 163 */ 164 public void removeAllSelectionListeners() { 165 selectionChangedListeners.clear(); 166 } 167 168 protected void fireSelectionChanged(WayPoint selWayPoint) { 169 for (IElevationProfileSelectionListener listener : selectionChangedListeners) { 170 listener.selectedWayPointChanged(selWayPoint); 171 } 172 } 173 174 @Override 175 public void paint(Graphics g) { 176 isPainting = true; 177 178 try { 179 super.paint(g); 180 createOrUpdatePlotArea(); 181 int y1 = getPlotBottom(); 182 183 g.setColor(Color.DARK_GRAY); 184 g.drawLine(plotArea.x, plotArea.y, plotArea.x, plotArea.y 185 + plotArea.height); 186 g.drawLine(plotArea.x, plotArea.y + plotArea.height, plotArea.x 187 + plotArea.width, plotArea.y + plotArea.height); 188 189 190 if (model != null) { 191 IElevationProfile profile = model.getCurrentProfile(); 192 if (profile != null && profile.hasElevationData()) { 193 // Draw start and end date 194 drawAlignedString(formatDate(profile.getStart()), 5, y1 + BOTTOM_TEXT_Y_OFFSET, 195 TextAlignment.Left, g); 196 drawAlignedString(formatDate(profile.getEnd()), 197 getPlotRight(), y1 + BOTTOM_TEXT_Y_OFFSET, TextAlignment.Right, g); 198 199 // Show SRTM indicator 200 if (ElevationHelper.hasSrtmData(profile.getBounds())) { 201 String txt = "SRTM"; 202 drawAlignedString(txt, getPlotHCenter(), y1 + BOTTOM_TEXT_Y_OFFSET, TextAlignment.Centered, g); 203 } 204 drawProfile(g); 205 drawElevationLines(g); 206 } else { 207 // No profile or profile supports no elevation data 208 drawAlignedString(tr("(No elevation data)"), getPlotHCenter(), 209 getPlotVCenter(), TextAlignment.Centered, g); 210 } 211 } 212 } finally { 213 isPainting = false; 214 } 215 } 216 217 /** 218 * Draw a string with a specified alignment. 219 * @param s The text to display. 220 * @param x The x coordinate. 221 * @param y The y coordinate. 222 * @param align The text alignment. 223 * @param g The graphics context. 224 * @return The resulting rectangle of the drawn string. 225 */ 226 private Rectangle drawAlignedString(String s, int x, int y, 227 TextAlignment align, Graphics g) { 228 FontMetrics fm = g.getFontMetrics(); 229 int w = fm.stringWidth(s); 230 int h = fm.getHeight(); 231 232 int xoff = w / 2; 233 int yoff = h / 2; 234 235 if (align == TextAlignment.Left) { 236 xoff = 0; 237 } 238 if (align == TextAlignment.Right) { 239 xoff = w; 240 } 241 242 g.drawString(s, x - xoff, y + yoff); 243 244 return new Rectangle(x - xoff, y - yoff, w, h); 245 } 246 247 /** 248 * Draw a string which is horizontally centered around (x,y). 249 * @param s The text to display. 250 * @param x The x coordinate. 251 * @param y The y coordinate. 252 * @param g The graphics context. 253 * @return The resulting rectangle of the drawn string. 254 254 255 255 private void drawHCenteredString(String s, int x, int y, Graphics g) { … … 257 257 }*/ 258 258 259 /**260 * Formats the date in a predefined manner: "21. Oct 2010, 12:10".261 * @param date262 * @return263 */264 private String formatDate(Date date) {265 Format formatter = new SimpleDateFormat("d MMM yy, HH:mm");266 267 return formatter.format(date);268 }269 270 /**271 * Helper function to draw elevation axes.272 * @param g273 */274 private void drawElevationLines(Graphics g) {275 IElevationProfile profile = model.getCurrentProfile();276 277 double diff = profile.getHeightDifference();278 279 if (diff == 0.0) {280 return;281 }282 283 double z10 = Math.floor(Math.log10(diff));284 double scaleUnit = Math.pow(10, z10); // scale unit, e. g. 100 for285 // values below 1000286 287 int upperLimit = (int) (Math.round(Math.ceil(profile.getMaxHeight()288 / scaleUnit)) * scaleUnit);289 int lowerLimit = (int) (Math.round(Math.floor(profile.getMinHeight()290 / scaleUnit)) * scaleUnit);291 int su = (int) scaleUnit;292 293 for (int i = lowerLimit; i <= upperLimit; i += su) {294 int yLine = getYForEelevation(i);295 296 // check bounds297 if (yLine <= getPlotBottom() && yLine >= getPlotTop()) {298 String txt = ElevationHelper.getElevationText(i);299 300 Rectangle r = drawAlignedString(txt, getPlotHCenter(), yLine - 2,301 TextAlignment.Right, g);302 r.grow(2, 2);303 304 // Draw left and right line segment305 g.drawLine(getPlotLeftAxis(), yLine, r.x,306 yLine);307 g.drawLine(r.x + r.width, yLine, getPlotRight(),308 yLine);309 // Draw label with shadow310 g.setColor(Color.WHITE);311 drawAlignedString(txt, getPlotHCenter() + 1, yLine - 1,312 TextAlignment.Right, g);313 g.setColor(Color.BLACK);314 drawAlignedString(txt, getPlotHCenter(), yLine - 2,315 TextAlignment.Right, g);316 }317 }318 }319 320 /**321 * Gets the x value of the left border for axes (slightly smaller than the322 * left x).323 * 324 * @return325 */326 private int getPlotLeftAxis() {327 return plotArea.x - 3;328 }329 330 /**331 * Gets the x value of the left border.332 * 333 * @return334 */335 private int getPlotLeft() {336 return plotArea.x + 1;337 }338 339 /**340 * Gets the horizontal center coordinate (mid between left and right x).341 * 342 * @return343 */344 private int getPlotHCenter() {345 return (getPlotLeft() + getPlotRight()) / 2;346 }347 348 /**349 * Gets the vertical center coordinate (mid between top and bottom y).350 * 351 * @return352 */353 private int getPlotVCenter() {354 return (getPlotTop() + getPlotBottom()) / 2;355 }356 357 /**358 * Gets the x value of the right border.359 * 360 * @return361 */362 private int getPlotRight() {363 return plotArea.x + plotArea.width - 1;364 }365 366 private int getPlotBottom() {367 return plotArea.y + plotArea.height - 1;368 }369 370 private int getPlotTop() {371 return plotArea.y + 1;372 }373 374 /**375 * Gets for an elevation value the according y coordinate in the plot area.376 * 377 * @param elevation378 * @return The y coordinate in the plot area.379 */380 private int getYForEelevation(int elevation) {381 int y1 = getPlotBottom();382 383 IElevationProfile profile = model.getCurrentProfile();384 385 if (!profile.hasElevationData()) {386 return y1;387 }388 389 double diff = profile.getHeightDifference();390 391 return y1 - (int) Math.round(((elevation - profile.getMinHeight()) / diff * plotArea.height));392 }393 394 /**395 * Draws the elevation profile396 * 397 * @param g398 */399 private void drawProfile(Graphics g) {400 IElevationProfile profile = model.getCurrentProfile();401 402 int nwp = profile.getNumberOfWayPoints();403 int n = Math.min(plotArea.width, nwp);404 405 if (n == 0) return; // nothing to draw406 // compute step size in panel (add 1 to make sure that407 // the complete range fits into panel408 step = (nwp / n) + 1;409 410 int yBottom = getPlotBottom();411 Color oldC = g.getColor();412 413 for (int i = 0, ip = 0; i < n && ip < nwp; i++, ip += step) {414 WayPoint wpt = profile.getWayPoints().get(ip);415 int eleVal = (int) ElevationHelper.getElevation(wpt);416 Color c = renderer.getColorForWaypoint(profile, wpt,417 ElevationWayPointKind.Plain);418 419 // draw cursor420 if (i == this.selectedIndex) {421 g.setColor(Color.BLACK);422 drawAlignedString(ElevationHelper.getElevationText(eleVal),423 (getPlotRight() + getPlotLeft()) / 2,424 getPlotBottom() + 6,425 TextAlignment.Centered,426 g);427 428 c = renderer.getColorForWaypoint(profile, wpt, ElevationWayPointKind.Highlighted);429 }430 431 int yEle = getYForEelevation(eleVal);432 int x = getPlotLeft() + i;433 434 g.setColor(c);435 g.drawLine(x, yBottom, x, yEle);436 g.setColor(ElevationColors.EPLightBlue);437 }438 439 g.setColor(oldC);440 }441 442 @Override443 protected void paintBorder(Graphics g) {444 super.paintBorder(g);445 446 Border loweredbevel = BorderFactory.createLoweredBevelBorder();447 this.setBorder(loweredbevel);448 }449 450 /**451 * Determines the size of the plot area depending on the panel size.452 */453 private void createOrUpdatePlotArea() {454 Dimension caSize = getSize();455 456 if (plotArea == null) {457 plotArea = new Rectangle(0, 0, caSize.width, caSize.height);458 } else {459 plotArea.width = caSize.width;460 plotArea.height = caSize.height;461 }462 463 plotArea.setLocation(0, 0);464 plotArea.grow(-10, -15);465 }466 467 @Override468 public void componentHidden(ComponentEvent arg0) {469 // TODO Auto-generated method stub470 }471 472 @Override473 public void componentMoved(ComponentEvent arg0) {474 // TODO Auto-generated method stub475 }476 477 @Override478 public void componentResized(ComponentEvent arg0) {479 createOrUpdatePlotArea();480 }481 482 @Override483 public void componentShown(ComponentEvent arg0) {484 // TODO Auto-generated method stub485 }486 487 @Override488 public void mouseDragged(MouseEvent arg0) {489 // TODO Auto-generated method stub490 491 }492 493 @Override494 public void mouseMoved(MouseEvent arg0) {495 if (isPainting || arg0.isControlDown() || arg0.isAltDown() || arg0.isShiftDown()) arg0.consume();496 497 int x = arg0.getX();498 int l = this.getX();499 int pl = this.getPlotLeft();500 int newIdx = x - l - pl;501 502 if (newIdx != this.selectedIndex && newIdx >= 0) {503 this.selectedIndex = newIdx;504 this.repaint();505 fireSelectionChanged(getSelectedWayPoint());506 }507 }508 509 @Override510 public String getToolTipText() {511 WayPoint wpt = getSelectedWayPoint();512 if (wpt != null) {513 return String.format("%s: %s", ElevationHelper.getTimeText(wpt), ElevationHelper.getElevationText(wpt));514 }515 516 return super.getToolTipText();517 }259 /** 260 * Formats the date in a predefined manner: "21. Oct 2010, 12:10". 261 * @param date 262 * @return 263 */ 264 private String formatDate(Date date) { 265 Format formatter = new SimpleDateFormat("d MMM yy, HH:mm"); 266 267 return formatter.format(date); 268 } 269 270 /** 271 * Helper function to draw elevation axes. 272 * @param g 273 */ 274 private void drawElevationLines(Graphics g) { 275 IElevationProfile profile = model.getCurrentProfile(); 276 277 double diff = profile.getHeightDifference(); 278 279 if (diff == 0.0) { 280 return; 281 } 282 283 double z10 = Math.floor(Math.log10(diff)); 284 double scaleUnit = Math.pow(10, z10); // scale unit, e. g. 100 for 285 // values below 1000 286 287 int upperLimit = (int) (Math.round(Math.ceil(profile.getMaxHeight() 288 / scaleUnit)) * scaleUnit); 289 int lowerLimit = (int) (Math.round(Math.floor(profile.getMinHeight() 290 / scaleUnit)) * scaleUnit); 291 int su = (int) scaleUnit; 292 293 for (int i = lowerLimit; i <= upperLimit; i += su) { 294 int yLine = getYForEelevation(i); 295 296 // check bounds 297 if (yLine <= getPlotBottom() && yLine >= getPlotTop()) { 298 String txt = ElevationHelper.getElevationText(i); 299 300 Rectangle r = drawAlignedString(txt, getPlotHCenter(), yLine - 2, 301 TextAlignment.Right, g); 302 r.grow(2, 2); 303 304 // Draw left and right line segment 305 g.drawLine(getPlotLeftAxis(), yLine, r.x, 306 yLine); 307 g.drawLine(r.x + r.width, yLine, getPlotRight(), 308 yLine); 309 // Draw label with shadow 310 g.setColor(Color.WHITE); 311 drawAlignedString(txt, getPlotHCenter() + 1, yLine - 1, 312 TextAlignment.Right, g); 313 g.setColor(Color.BLACK); 314 drawAlignedString(txt, getPlotHCenter(), yLine - 2, 315 TextAlignment.Right, g); 316 } 317 } 318 } 319 320 /** 321 * Gets the x value of the left border for axes (slightly smaller than the 322 * left x). 323 * 324 * @return 325 */ 326 private int getPlotLeftAxis() { 327 return plotArea.x - 3; 328 } 329 330 /** 331 * Gets the x value of the left border. 332 * 333 * @return 334 */ 335 private int getPlotLeft() { 336 return plotArea.x + 1; 337 } 338 339 /** 340 * Gets the horizontal center coordinate (mid between left and right x). 341 * 342 * @return 343 */ 344 private int getPlotHCenter() { 345 return (getPlotLeft() + getPlotRight()) / 2; 346 } 347 348 /** 349 * Gets the vertical center coordinate (mid between top and bottom y). 350 * 351 * @return 352 */ 353 private int getPlotVCenter() { 354 return (getPlotTop() + getPlotBottom()) / 2; 355 } 356 357 /** 358 * Gets the x value of the right border. 359 * 360 * @return 361 */ 362 private int getPlotRight() { 363 return plotArea.x + plotArea.width - 1; 364 } 365 366 private int getPlotBottom() { 367 return plotArea.y + plotArea.height - 1; 368 } 369 370 private int getPlotTop() { 371 return plotArea.y + 1; 372 } 373 374 /** 375 * Gets for an elevation value the according y coordinate in the plot area. 376 * 377 * @param elevation 378 * @return The y coordinate in the plot area. 379 */ 380 private int getYForEelevation(int elevation) { 381 int y1 = getPlotBottom(); 382 383 IElevationProfile profile = model.getCurrentProfile(); 384 385 if (!profile.hasElevationData()) { 386 return y1; 387 } 388 389 double diff = profile.getHeightDifference(); 390 391 return y1 - (int) Math.round(((elevation - profile.getMinHeight()) / diff * plotArea.height)); 392 } 393 394 /** 395 * Draws the elevation profile 396 * 397 * @param g 398 */ 399 private void drawProfile(Graphics g) { 400 IElevationProfile profile = model.getCurrentProfile(); 401 402 int nwp = profile.getNumberOfWayPoints(); 403 int n = Math.min(plotArea.width, nwp); 404 405 if (n == 0) return; // nothing to draw 406 // compute step size in panel (add 1 to make sure that 407 // the complete range fits into panel 408 step = (nwp / n) + 1; 409 410 int yBottom = getPlotBottom(); 411 Color oldC = g.getColor(); 412 413 for (int i = 0, ip = 0; i < n && ip < nwp; i++, ip += step) { 414 WayPoint wpt = profile.getWayPoints().get(ip); 415 int eleVal = (int) ElevationHelper.getElevation(wpt); 416 Color c = renderer.getColorForWaypoint(profile, wpt, 417 ElevationWayPointKind.Plain); 418 419 // draw cursor 420 if (i == this.selectedIndex) { 421 g.setColor(Color.BLACK); 422 drawAlignedString(ElevationHelper.getElevationText(eleVal), 423 (getPlotRight() + getPlotLeft()) / 2, 424 getPlotBottom() + 6, 425 TextAlignment.Centered, 426 g); 427 428 c = renderer.getColorForWaypoint(profile, wpt, ElevationWayPointKind.Highlighted); 429 } 430 431 int yEle = getYForEelevation(eleVal); 432 int x = getPlotLeft() + i; 433 434 g.setColor(c); 435 g.drawLine(x, yBottom, x, yEle); 436 g.setColor(ElevationColors.EPLightBlue); 437 } 438 439 g.setColor(oldC); 440 } 441 442 @Override 443 protected void paintBorder(Graphics g) { 444 super.paintBorder(g); 445 446 Border loweredbevel = BorderFactory.createLoweredBevelBorder(); 447 this.setBorder(loweredbevel); 448 } 449 450 /** 451 * Determines the size of the plot area depending on the panel size. 452 */ 453 private void createOrUpdatePlotArea() { 454 Dimension caSize = getSize(); 455 456 if (plotArea == null) { 457 plotArea = new Rectangle(0, 0, caSize.width, caSize.height); 458 } else { 459 plotArea.width = caSize.width; 460 plotArea.height = caSize.height; 461 } 462 463 plotArea.setLocation(0, 0); 464 plotArea.grow(-10, -15); 465 } 466 467 @Override 468 public void componentHidden(ComponentEvent arg0) { 469 // TODO Auto-generated method stub 470 } 471 472 @Override 473 public void componentMoved(ComponentEvent arg0) { 474 // TODO Auto-generated method stub 475 } 476 477 @Override 478 public void componentResized(ComponentEvent arg0) { 479 createOrUpdatePlotArea(); 480 } 481 482 @Override 483 public void componentShown(ComponentEvent arg0) { 484 // TODO Auto-generated method stub 485 } 486 487 @Override 488 public void mouseDragged(MouseEvent arg0) { 489 // TODO Auto-generated method stub 490 491 } 492 493 @Override 494 public void mouseMoved(MouseEvent arg0) { 495 if (isPainting || arg0.isControlDown() || arg0.isAltDown() || arg0.isShiftDown()) arg0.consume(); 496 497 int x = arg0.getX(); 498 int l = this.getX(); 499 int pl = this.getPlotLeft(); 500 int newIdx = x - l - pl; 501 502 if (newIdx != this.selectedIndex && newIdx >= 0) { 503 this.selectedIndex = newIdx; 504 this.repaint(); 505 fireSelectionChanged(getSelectedWayPoint()); 506 } 507 } 508 509 @Override 510 public String getToolTipText() { 511 WayPoint wpt = getSelectedWayPoint(); 512 if (wpt != null) { 513 return String.format("%s: %s", ElevationHelper.getTimeText(wpt), ElevationHelper.getElevationText(wpt)); 514 } 515 516 return super.getToolTipText(); 517 } 518 518 }
Note:
See TracChangeset
for help on using the changeset viewer.
