Ticket #24238: ticket_24238.patch
| File ticket_24238.patch, 154.7 KB (added by , 12 months ago) |
|---|
-
new file .vscode/launch.json
diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..58807032f3
- + 1 { 2 // Use IntelliSense to learn about possible attributes. 3 // Hover to view descriptions of existing attributes. 4 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 "version": "0.2.0", 6 "configurations": [ 7 { 8 "type": "java", 9 "name": "Current File", 10 "request": "launch", 11 "mainClass": "${file}" 12 }, 13 { 14 "type": "java", 15 "name": "ProjectionCLI", 16 "request": "launch", 17 "mainClass": "org.openstreetmap.josm.data.projection.ProjectionCLI", 18 "projectName": "josm" 19 }, 20 { 21 "type": "java", 22 "name": "ProjectionRefTest", 23 "request": "launch", 24 "mainClass": "org.openstreetmap.josm.data.projection.ProjectionRefTest", 25 "projectName": "josm" 26 }, 27 { 28 "type": "java", 29 "name": "ProjectionRegressionTest", 30 "request": "launch", 31 "mainClass": "org.openstreetmap.josm.data.projection.ProjectionRegressionTest", 32 "projectName": "josm" 33 }, 34 { 35 "type": "java", 36 "name": "MainApplication", 37 "request": "launch", 38 "mainClass": "org.openstreetmap.josm.gui.MainApplication", 39 "projectName": "josm", 40 "vmArgs": "-Djosm.dir.name=JOSM-dev --add-exports=java.base/sun.security.action=ALL-UNNAMED --add-exports=java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED --add-exports=java.desktop/com.sun.imageio.spi=ALL-UNNAMED", 41 } 42 ] 43 } 44 No newline at end of file -
new file nodist/data/exif-position-error.jpg
diff --git a/nodist/data/exif-position-error.jpg b/nodist/data/exif-position-error.jpg new file mode 100644 index 0000000000000000000000000000000000000000..db8edf752efa8b0274e7f89a20534da20e122219 GIT binary patch literal 47012 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 Sd`idhJ9(nE<}muSkN?>xYR41+ -
src/org/openstreetmap/josm/data/ImageData.java
literal 0 HcmV?d00001 diff --git a/src/org/openstreetmap/josm/data/ImageData.java b/src/org/openstreetmap/josm/data/ImageData.java index 232ecc6588..7a0ae4a3f4 100644
a b public class ImageData implements Data { 365 365 afterImageUpdated(img); 366 366 } 367 367 368 /** 369 * Update the GPS track direction of the image and trigger update. 370 * @param img the image to update 371 * @param trackDirection the new GPS track direction 372 * @since xxx 373 */ 374 public void updateImageGpsTrack(ImageEntry img, double trackDirection) { 375 img.setExifGpsTrack(trackDirection); 376 afterImageUpdated(img); 377 } 378 379 /** 380 * Update the image horizontal positioning error and trigger update. 381 * @param img the image to update 382 * @param hposerr the new horizontal positionning error 383 * @since xxx 384 */ 385 public void updateImageHPosErr(ImageEntry img, double hposerr) { 386 img.setExifHPosErr(hposerr); 387 afterImageUpdated(img); 388 } 389 390 /** 391 * Update the image GPS differential mode and trigger update. 392 * @param img the image to update 393 * @param gpsDiffMode the new GPS differential mode 394 * @since xxx 395 */ 396 public void updateImageGpsDiffMode(ImageEntry img, Integer gpsDiffMode) { 397 img.setGpsDiffMode(gpsDiffMode); 398 afterImageUpdated(img); 399 } 400 401 /** 402 * Update the image GPS 2d/3d mode value and trigger update. 403 * @param img the image to update 404 * @param gps2d3dMode the new 2d/3d GPS mode 405 * @since xxx 406 */ 407 public void updateImageGps2d3dMode(ImageEntry img, Integer gps2d3dMode) { 408 img.setGps2d3dMode(gps2d3dMode); 409 afterImageUpdated(img); 410 } 411 412 /** 413 * Update the image GPS DOP value and trigger update. 414 * @param img the image to update 415 * @param exifGpsDop the new GPS DOP value 416 * @since xxx 417 */ 418 public void updateImageExifGpsDop(ImageEntry img, Double exifGpsDop) { 419 img.setExifGpsDop(exifGpsDop); 420 afterImageUpdated(img); 421 } 422 423 /** 424 * Update the image GPS datum and trigger update. 425 * @param img the image to update 426 * @param exifGpsDatum the new datum string value 427 * @since xxx 428 */ 429 public void updateImageExifGpsDatum(ImageEntry img, String exifGpsDatum) { 430 img.setExifGpsDatum(exifGpsDatum); 431 afterImageUpdated(img); 432 } 433 434 /** 435 * Update the image GPS processing method and trigger update. 436 * @param img the image to update 437 * @param exifGpsProcMethod the new GPS processing method 438 * @since xxx 439 */ 440 public void updateImageExifGpsProcMethod(ImageEntry img, String exifGpsProcMethod) { 441 img.setExifGpsProcMethod(exifGpsProcMethod); 442 afterImageUpdated(img); 443 } 444 368 445 /** 369 446 * Manually trigger the {@link ImageDataUpdateListener#imageDataUpdated(ImageData)} 370 447 */ -
src/org/openstreetmap/josm/data/gpx/GpxConstants.java
diff --git a/src/org/openstreetmap/josm/data/gpx/GpxConstants.java b/src/org/openstreetmap/josm/data/gpx/GpxConstants.java index 543cdfebce..bfaff9f7e2 100644
a b public interface GpxConstants { 145 145 * Fractional seconds are allowed for millisecond timing in tracklogs. */ 146 146 String PT_TIME = "time"; 147 147 148 /** True Course/Bearing angle over ground. 149 * @since xxx 150 */ 151 String PT_COURSE = "course"; 152 148 153 /** Magnetic variation (in degrees) at the point. 0.0 <= value < 360.0 */ 149 154 String PT_MAGVAR = "magvar"; 150 155 … … public interface GpxConstants { 188 193 * Ordered list of all possible waypoint keys. 189 194 */ 190 195 List<String> WPT_KEYS = Collections.unmodifiableList(Arrays.asList(PT_ELE, PT_TIME, PT_MAGVAR, PT_GEOIDHEIGHT, 191 GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, PT_SYM, PT_TYPE, 192 PT_ FIX, PT_SAT, PT_HDOP, PT_VDOP, PT_PDOP, PT_AGEOFDGPSDATA, PT_DGPSID, PT_STD_HDEV, PT_STD_VDEV));196 GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, PT_SYM, PT_TYPE, PT_FIX, PT_SAT, PT_COURSE, 197 PT_HDOP, PT_VDOP, PT_PDOP, PT_AGEOFDGPSDATA, PT_DGPSID, PT_STD_HDEV, PT_STD_VDEV)); 193 198 194 199 /** 195 200 * Ordered list of all possible route and track keys. -
src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java
diff --git a/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java b/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java index 023c495be3..be3f29beaf 100644
a b 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.data.gpx; 3 3 4 import java.util.Arrays; 4 5 import java.util.ArrayList; 5 6 import java.util.Collection; 6 7 import java.util.List; … … public final class GpxImageCorrelation { 72 73 } 73 74 74 75 final GpxImageDirectionPositionSettings dirpos = settings.getDirectionPositionSettings(); 76 final GpxImageExtendedSettings extSettings = settings.getExtendedSettings(); 75 77 final long offset = settings.getOffset(); 76 78 77 79 boolean isFirst = true; … … public final class GpxImageCorrelation { 141 143 } 142 144 } 143 145 WayPoint nextWp = i < size - 1 ? wps.get(i + 1) : null; 144 ret += matchPoints(images, prevWp, prevWpTime, curWp, curWpTime, offset, interpolate, tagTime, nextWp, dirpos );146 ret += matchPoints(images, prevWp, prevWpTime, curWp, curWpTime, offset, interpolate, tagTime, nextWp, dirpos, extSettings); 145 147 prevWp = curWp; 146 148 prevWpTime = curWpTime; 147 149 } 148 150 } 149 151 } 150 152 if (trkTag && prevWp != null) { 151 ret += matchPoints(images, prevWp, prevWpTime, prevWp, prevWpTime, offset, false, trkTagTime, null, dirpos );153 ret += matchPoints(images, prevWp, prevWpTime, prevWp, prevWpTime, offset, false, trkTagTime, null, dirpos, extSettings); 152 154 } 153 155 Logging.debug("Correlated {0} total points", ret); 154 156 return ret; … … public final class GpxImageCorrelation { 209 211 return null; 210 212 } 211 213 212 private static int matchPoints(List<? extends GpxImageEntry> images, WayPoint prevWp, long prevWpTime, WayPoint curWp, long curWpTime, 213 long offset, boolean interpolate, int tagTime, WayPoint nextWp, GpxImageDirectionPositionSettings dirpos) { 214 static Double getHPosErr(WayPoint wp) { 215 if (wp != null) { 216 if (wp.attr.get(GpxConstants.PT_STD_HDEV) instanceof Float) { 217 Float hposerr = (Float) wp.attr.get(GpxConstants.PT_STD_HDEV); 218 if (hposerr != null) { 219 return hposerr.doubleValue(); 220 } 221 } else if (wp.attr.get(GpxConstants.PT_STD_HDEV) instanceof Double) { 222 Double hposerr = (Double) wp.attr.get(GpxConstants.PT_STD_HDEV); 223 if (hposerr != null) { 224 return hposerr; 225 } 226 } 227 } 228 return null; 229 } 230 231 static Double getGpsDop(WayPoint wp) { 232 if (wp != null) { 233 if (wp.attr.get(GpxConstants.PT_PDOP) != null) { 234 if (wp.attr.get(GpxConstants.PT_PDOP) instanceof Float) { 235 Float pdopvalue = (Float) wp.attr.get(GpxConstants.PT_PDOP); 236 return pdopvalue.doubleValue(); 237 } else if (wp.attr.get(GpxConstants.PT_PDOP) instanceof Double) { 238 Double pdopvalue = (Double) wp.attr.get(GpxConstants.PT_PDOP); 239 return pdopvalue.doubleValue(); 240 } 241 } else if (wp.attr.get(GpxConstants.PT_HDOP) != null) { 242 if (wp.attr.get(GpxConstants.PT_HDOP) instanceof Float) { 243 Float hdopvalue = (Float) wp.attr.get(GpxConstants.PT_HDOP); 244 return hdopvalue.doubleValue(); 245 } else if (wp.attr.get(GpxConstants.PT_HDOP) instanceof Double) { 246 Double hdopvalue = (Double) wp.attr.get(GpxConstants.PT_HDOP); 247 return hdopvalue.doubleValue(); 248 } 249 } 250 } 251 return null; 252 } 253 254 static Double getGpsTrack(WayPoint wp) { 255 if (wp != null) { 256 String trackvalue = wp.getString(GpxConstants.PT_COURSE); 257 Logging.debug("track angle value: {0}", trackvalue); 258 if (!Utils.isEmpty(trackvalue)) { 259 try { 260 return Double.valueOf(trackvalue); 261 } catch (NumberFormatException e) { 262 Logging.warn(e); 263 } 264 } 265 } 266 return null; 267 } 268 269 static String getGpsProcMethod(String prevGpsFixMode, final String curGpsFixMode, 270 final List<String> positioningModes) { 271 String gpsProcMethod = null; 272 Integer lowestProcIndex = null; 273 int lowestGnssModeIdx = 3; // 2d or higher index in positioningModes list are Gnss methods 274 try { 275 lowestProcIndex = Math.min(positioningModes.indexOf(prevGpsFixMode), positioningModes.indexOf(curGpsFixMode)); 276 if (lowestProcIndex < 0) { 277 return null; 278 } 279 gpsProcMethod = "GNSS" + " " + positioningModes.get(lowestProcIndex).toUpperCase() + " " + "CORRELATION"; 280 if (lowestProcIndex < lowestGnssModeIdx) { 281 gpsProcMethod = positioningModes.get(lowestProcIndex).toUpperCase() + " " + "CORRELATION"; 282 } else { 283 gpsProcMethod = "GNSS" + " " + positioningModes.get(lowestProcIndex).toUpperCase() + " " + "CORRELATION"; 284 } 285 gpsProcMethod = gpsProcMethod.replace("FLOAT RTK", "RTK_FLOAT"); 286 gpsProcMethod = gpsProcMethod.replace(" RTK ", " RTK_FIX "); 287 } catch (ArrayIndexOutOfBoundsException ex) { 288 Logging.warn(ex); 289 } 290 return gpsProcMethod; 291 } 292 293 static Integer getGps2d3dMode(String prevGpsFixMode, final String curGpsFixMode, 294 final List<String> positioningModes) { 295 Integer lowestMode = null; 296 lowestMode = Math.min(positioningModes.indexOf(prevGpsFixMode), positioningModes.indexOf(curGpsFixMode)); 297 if (lowestMode > 3) { 298 return 3; 299 } 300 if (lowestMode > 2) { 301 return 2; 302 } 303 return null; 304 } 305 306 private static int matchPoints(List<? extends GpxImageEntry> 307 images, 308 WayPoint prevWp, 309 long prevWpTime, 310 WayPoint curWp, 311 long curWpTime, 312 long offset, 313 boolean interpolate, 314 int tagTime, 315 WayPoint nextWp, 316 GpxImageDirectionPositionSettings dirpos, 317 GpxImageExtendedSettings extSetting) { 214 318 215 319 final boolean isLast = nextWp == null; 216 320 … … public final class GpxImageCorrelation { 236 340 int ret = 0; 237 341 Double speed = null; 238 342 Double prevElevation = null; 343 Double prevHPosErr = null; 344 Double prevGpsDop = null; 345 Double prevGpsTrack = null; 346 String prevGpsFixMode = null; 347 //list of differential GPS mode 348 //TODO move these lists in Gpx.Constants? 349 final List<String> diffMode = Arrays.asList("dgps", "float rtk", "rtk"); 350 final List<String> positioningModes = Arrays.asList("none", "manual", "estimated", "2d", "3d", "dgps", "float rtk", "rtk"); 351 239 352 240 353 if (prevWp != null && interpolate) { 241 354 double distance = prevWp.greatCircleDistance(curWp); … … public final class GpxImageCorrelation { 244 357 speed = 3600 * distance / (curWpTime - prevWpTime); 245 358 } 246 359 prevElevation = getElevation(prevWp); 360 prevHPosErr = getHPosErr(prevWp); 361 prevGpsDop = getGpsDop(prevWp); 362 prevGpsTrack = getGpsTrack(prevWp); 363 prevGpsFixMode = prevWp.getString(GpxConstants.PT_FIX); 247 364 } 248 365 249 366 final Double curElevation = getElevation(curWp); 367 final Double curHPosErr = getHPosErr(curWp); 368 final Double curGpsDop = getGpsDop(curWp); 369 final Double curGpsTrack = getGpsTrack(curWp); 370 final String curGpsFixMode = curWp.getString(GpxConstants.PT_FIX); 250 371 251 372 if (!interpolate || isLast) { 252 373 final long half = Math.abs(curWpTime - prevWpTime) / 2; … … public final class GpxImageCorrelation { 266 387 } else { 267 388 curTmp.setPos(curWp.getCoor()); 268 389 } 390 //TODO fix this, nextWp doesn't exist here 269 391 if (nextWp != null && dirpos.isSetImageDirection()) { 270 392 double direction = curWp.bearing(nextWp); 271 393 curTmp.setExifImgDir(computeDirection(direction, dirpos.getImageDirectionAngleOffset())); … … public final class GpxImageCorrelation { 317 439 if (curElevation != null && prevElevation != null) { 318 440 curTmp.setElevation(prevElevation + (curElevation - prevElevation) * timeDiff + dirpos.getElevationShift()); 319 441 } 442 443 // Add exif GpsHPositioningerror interpolated value 444 if (curHPosErr != null && prevHPosErr != null) { 445 Double interpolatedValue = prevHPosErr + (curHPosErr - prevHPosErr) * timeDiff; 446 curTmp.setExifHPosErr(Math.round(interpolatedValue*10000)/10000.0); 447 } 448 449 // Add exif GpsDifferentialMode 450 // Get previous and current waypoint differential. As no interpolation is possible, 451 // set differential mode to 0 if any waypoint isn't in differential mode. 452 if (prevGpsFixMode != null) { 453 if (diffMode.contains(prevGpsFixMode) && diffMode.contains(curGpsFixMode)) { 454 curTmp.setGpsDiffMode(1); 455 } else { 456 curTmp.setGpsDiffMode(0); 457 } 458 } 459 460 // Add exif GpsMeasureMode 461 if (prevGpsFixMode != null && curGpsFixMode != null) { 462 Integer gps2d3dMode = getGps2d3dMode(prevGpsFixMode, curGpsFixMode, positioningModes); 463 if (gps2d3dMode != null) { 464 curTmp.setGps2d3dMode(gps2d3dMode); 465 } 466 } 467 468 // Add exif GpsProcessingMethod. As no interpolation is possible, 469 // set processing method to the "lowest" previous and current processing method value. 470 if (prevGpsFixMode != null && curGpsFixMode != null) { 471 String gpsProcMethod = getGpsProcMethod(prevGpsFixMode, curGpsFixMode, positioningModes); 472 if (gpsProcMethod != null) { 473 curTmp.setExifGpsProcMethod(gpsProcMethod); 474 } 475 } 476 477 // Add Exif GpsDop with interpolated GPS DOP value 478 if (curGpsDop != null && prevGpsDop != null) { 479 Double interpolatedValue = prevGpsDop + (curGpsDop - prevGpsDop) * timeDiff; 480 curTmp.setExifGpsDop(Math.round(interpolatedValue*100)/100.0); 481 } 482 483 // Add Exif GpsTrack tag 484 if (dirpos.isSetGpxTrackDirection()) { 485 if (curGpsTrack != null && prevGpsTrack != null) { 486 curTmp.setExifGpsTrack(prevGpsTrack + (curGpsTrack - prevGpsTrack) * timeDiff); 487 } 488 } 489 490 // Add GpsDatum tag 491 if (extSetting.isSetImageGpsDatum()) { 492 if (diffMode.contains(prevGpsFixMode) && diffMode.contains(curGpsFixMode)) { 493 curTmp.setExifGpsDatum(extSetting.getImageGpsDatum()); 494 } else //without differential mode, datum is WGS-84 495 curTmp.setExifGpsDatum("WGS-84"); 496 } 497 320 498 curTmp.setGpsTime(curImg.getExifInstant().minusMillis(offset)); 321 499 curTmp.flagNewGpsData(); 322 500 curImg.tmpUpdated(); -
src/org/openstreetmap/josm/data/gpx/GpxImageCorrelationSettings.java
diff --git a/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelationSettings.java b/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelationSettings.java index 079d69f99c..aa08ec4f7e 100644
a b public class GpxImageCorrelationSettings { 12 12 private final long offset; 13 13 private final boolean forceTags; 14 14 private final GpxImageDirectionPositionSettings directionPositionSettings; 15 private final GpxImageExtendedSettings extendedSettings; 15 16 16 17 /** 17 18 * Constructs a new {@code GpxImageCorrelationSettings}. … … public class GpxImageCorrelationSettings { 19 20 * @param forceTags force tagging of all photos, otherwise prefs are used 20 21 */ 21 22 public GpxImageCorrelationSettings(long offset, boolean forceTags) { 22 this(offset, forceTags, new GpxImageDirectionPositionSettings(false, 0, 0, 0, 0)); 23 this(offset, forceTags, 24 new GpxImageDirectionPositionSettings(false, 0, false, 0, 0, 0), 25 new GpxImageExtendedSettings(false, null) 26 ); 23 27 } 24 28 25 29 /** … … public class GpxImageCorrelationSettings { 30 34 */ 31 35 public GpxImageCorrelationSettings(long offset, boolean forceTags, 32 36 GpxImageDirectionPositionSettings directionPositionSettings) { 37 this(offset, forceTags, directionPositionSettings, 38 new GpxImageExtendedSettings(false, null)); 39 } 40 41 /** 42 * Constructs a new {@code GpxImageCorrelationSettings}. 43 * @param offset offset in milliseconds 44 * @param forceTags force tagging of all photos, otherwise prefs are used 45 * @param directionPositionSettings direction/position settings 46 * @param extendedSettings GPS datum settings 47 * @since xxx @extendedSettings was added 48 */ 49 public GpxImageCorrelationSettings(long offset, boolean forceTags, 50 GpxImageDirectionPositionSettings directionPositionSettings, 51 GpxImageExtendedSettings extendedSettings) { 33 52 this.offset = offset; 34 53 this.forceTags = forceTags; 35 54 this.directionPositionSettings = Objects.requireNonNull(directionPositionSettings); 55 this.extendedSettings = Objects.requireNonNull(extendedSettings); 36 56 } 37 57 38 58 /** 39 59 * Returns the offset in milliseconds. 40 60 * @return the offset in milliseconds … … public class GpxImageCorrelationSettings { 59 79 return directionPositionSettings; 60 80 } 61 81 82 /** 83 * Returns the extended exif metadata settings. 84 * @return the extended exif metadata settings 85 * @since xxx 86 */ 87 public GpxImageExtendedSettings getExtendedSettings() { 88 return extendedSettings; 89 } 90 62 91 @Override 63 92 public String toString() { 64 93 return "[offset=" + offset + ", forceTags=" + forceTags 65 + ", directionPositionSettings=" + directionPositionSettings + ']'; 94 + ", directionPositionSettings=" + directionPositionSettings 95 + ", extendedSettings=" + extendedSettings + ']'; 66 96 } 67 97 } -
src/org/openstreetmap/josm/data/gpx/GpxImageDirectionPositionSettings.java
diff --git a/src/org/openstreetmap/josm/data/gpx/GpxImageDirectionPositionSettings.java b/src/org/openstreetmap/josm/data/gpx/GpxImageDirectionPositionSettings.java index 20dd5e0f73..53483c2e4f 100644
a b public class GpxImageDirectionPositionSettings { 9 9 10 10 private final boolean setImageDirection; 11 11 private final double imageDirectionAngleOffset; 12 private final boolean setGpxTrackDirection; 12 13 private final double shiftImageX; 13 14 private final double shiftImageY; 14 15 private final double elevationShift; … … public class GpxImageDirectionPositionSettings { 17 18 * Constructs a new {@code GpxImageDirectionPositionSettings}. 18 19 * @param setImageDirection determines if image direction must be set towards the next GPX waypoint 19 20 * @param imageDirectionAngleOffset direction angle offset in degrees 21 * @param setGpxTrackDirection determines if image course direction must be set 20 22 * @param shiftImageX image shift on X axis relative to the direction in meters 21 23 * @param shiftImageY image shift on Y axis relative to the direction in meters 22 24 * @param elevationShift image elevation shift in meters 25 * @since xxx @setGpsTrackDirection was added 23 26 */ 24 27 public GpxImageDirectionPositionSettings( 25 boolean setImageDirection, double imageDirectionAngleOffset, double shiftImageX, double shiftImageY, double elevationShift) { 28 boolean setImageDirection, double imageDirectionAngleOffset, boolean setGpxTrackDirection, 29 double shiftImageX, double shiftImageY, double elevationShift) { 26 30 this.setImageDirection = setImageDirection; 27 31 this.imageDirectionAngleOffset = imageDirectionAngleOffset; 32 this.setGpxTrackDirection = setGpxTrackDirection; 28 33 this.shiftImageX = shiftImageX; 29 34 this.shiftImageY = shiftImageY; 30 35 this.elevationShift = elevationShift; … … public class GpxImageDirectionPositionSettings { 46 51 return imageDirectionAngleOffset; 47 52 } 48 53 54 /** 55 * Determines if GPS course direction must be set. Angle value is from the gpx trace. 56 * @return {@code true} if image GPS course must be set 57 * @since xxx 58 */ 59 public boolean isSetGpxTrackDirection() { 60 return setGpxTrackDirection; 61 } 62 49 63 /** 50 64 * Returns image shift on X axis relative to the direction in meters 51 65 * @return image shift on X axis relative to the direction in meters … … public class GpxImageDirectionPositionSettings { 73 87 @Override 74 88 public String toString() { 75 89 return "[setImageDirection=" + setImageDirection 76 + ", imageDirectionAngleOffset=" + imageDirectionAngleOffset + ", shiftImageX=" + shiftImageX 77 + ", shiftImageY=" + shiftImageY + ", elevationShift=" + elevationShift + ']'; 90 + ", imageDirectionAngleOffset=" + imageDirectionAngleOffset 91 + ", setImageGpxTrackDirection=" + setGpxTrackDirection 92 + ", shiftImageX=" + shiftImageX 93 + ", shiftImageY=" + shiftImageY 94 + ", elevationShift=" + elevationShift + ']'; 78 95 } 79 96 } -
src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java
diff --git a/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java b/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java index f4161cd78f..4886980cdd 100644
a b public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType 32 32 private Integer exifOrientation; 33 33 private LatLon exifCoor; 34 34 private Double exifImgDir; 35 private Double exifGpsTrack; 36 private Double exifHPosErr; 37 private Double exifGpsDop; 35 38 private Instant exifTime; 36 39 private Projections cameraProjection = Projections.UNKNOWN; 37 40 /** … … public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType 57 60 private Double speed; 58 61 /** Elevation (altitude) in meters */ 59 62 private Double elevation; 63 /** GPS Differential mode */ 64 private Integer gpsDiffMode; 65 /** GPS Measure mode */ 66 private Integer gps2d3dMode; 67 /** GPS Datum */ 68 private String exifGpsDatum; 69 /** GPS processing method */ 70 private String exifGpsProcMethod; 60 71 /** The time after correlation with a gpx track */ 61 72 private Instant gpsTime; 62 73 … … public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType 88 99 exifOrientation = other.exifOrientation; 89 100 exifCoor = other.exifCoor; 90 101 exifImgDir = other.exifImgDir; 102 exifGpsTrack = other.exifGpsTrack; 103 exifHPosErr = other.exifHPosErr; 104 gpsDiffMode = other.gpsDiffMode; 105 gps2d3dMode = other.gps2d3dMode; 106 exifGpsDop = other.exifGpsDop; 107 exifGpsDatum = other.exifGpsDatum; 108 exifGpsProcMethod = other.exifGpsProcMethod; 91 109 exifTime = other.exifTime; 92 110 isNewGpsData = other.isNewGpsData; 93 111 exifGpsTime = other.exifGpsTime; … … public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType 169 187 return elevation; 170 188 } 171 189 190 /** 191 * Return the GPS Differential mode value. The GPS Differential mode value from the temporary 192 * copy is returned if that copy exists. 193 * @return the differential mode value 194 * @since xxx 195 */ 196 @Override 197 public Integer getGpsDiffMode() { 198 if (tmp != null) 199 return tmp.gpsDiffMode; 200 return gpsDiffMode; 201 } 202 203 /** 204 * Return the GPS 2d or 3d mode value. The GPS mode value form the temporary 205 * copy is returned if that copy exists. 206 * @return the GPS 2d/3d mode value 207 * @since xxx 208 */ 209 @Override 210 public Integer getGps2d3dMode() { 211 if (tmp != null) 212 return tmp.gps2d3dMode; 213 return gps2d3dMode; 214 } 215 216 /** 217 * Return the GPS DOP value. The GPS DOP value from the temporary 218 * copy is returned if that copy exists. 219 * @return the DOP value 220 * @since xxx 221 */ 222 @Override 223 public Double getExifGpsDop() { 224 if (tmp != null) 225 return tmp.exifGpsDop; 226 return exifGpsDop; 227 } 228 229 /** 230 * Return the exif GPS coordinates datum value. 231 * @return The datum value 232 * @since xxx 233 */ 234 @Override 235 public String getExifGpsDatum() { 236 if (tmp != null) 237 return tmp.exifGpsDatum; 238 return exifGpsDatum; 239 } 240 241 /** 242 * Return the exif GPS processing method string 243 * @return the processing method string 244 * @since xxx 245 */ 246 @Override 247 public String getExifGpsProcMethod() { 248 if (tmp != null) 249 return tmp.exifGpsProcMethod; 250 return exifGpsProcMethod; 251 } 252 172 253 /** 173 254 * Returns the GPS time value. The GPS time value from the temporary copy 174 255 * is returned if that copy exists. … … public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType 272 353 return tmp.exifImgDir; 273 354 return exifImgDir; 274 355 } 356 357 /** 358 * Convenient way to determine if this entry has a EXIF GPS track angle value, 359 * without the cost of building a defensive copy. 360 * @return {@code true} if this entry has a EXIF track angle value 361 * @since xxx 362 */ 363 @Override 364 public Double getExifGpsTrack() { 365 if (tmp != null) 366 return tmp.exifGpsTrack; 367 return exifGpsTrack; 368 } 369 370 /** 371 * Convenient way to determine if this entry has a EXIF GPS horizontal positionning error value, 372 * without the cost of building a defensive copy. 373 * @return {@code true} if this entry has a EXIF GPS horizontal positionning error value 374 * @since xxx 375 */ 376 @Override 377 public Double getExifHPosErr() { 378 if (tmp != null) 379 return tmp.exifHPosErr; 380 return exifHPosErr; 381 } 275 382 276 383 @Override 277 384 public Instant getLastModified() { … … public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType 345 452 this.elevation = elevation; 346 453 } 347 454 455 /** 456 * Sets GPS Differential mode. 457 * @param gpsDiffMode GPS Differential mode 458 * @since xxx 459 */ 460 @Override 461 public void setGpsDiffMode(Integer gpsDiffMode) { 462 this.gpsDiffMode = gpsDiffMode; 463 } 464 465 /** 466 * Sets GPS 2d/3d mode. 467 * @param gps2d3dMode GPS 2d/3d mode value 468 * @since xxx 469 */ 470 @Override 471 public void setGps2d3dMode(Integer gps2d3dMode) { 472 this.gps2d3dMode = gps2d3dMode; 473 } 474 475 /** 476 * Sets GPS DOP value. 477 * @param exifGpsDop GPS DOP value 478 * @since xxx 479 */ 480 @Override 481 public void setExifGpsDop(Double exifGpsDop) { 482 this.exifGpsDop = exifGpsDop; 483 } 484 485 /** 486 * Sets the GPS Datum. 487 * @param exifGpsDatum GPS Datum 488 * @since xxx 489 */ 490 @Override 491 public void setExifGpsDatum(String exifGpsDatum) { 492 this.exifGpsDatum = exifGpsDatum; 493 } 494 495 /** 496 * Sets the GPS Processing Method. 497 * @param exifGpsProcMethod GPS Processing Method 498 * @since xxx 499 */ 500 @Override 501 public void setExifGpsProcMethod(String exifGpsProcMethod) { 502 this.exifGpsProcMethod = exifGpsProcMethod; 503 } 504 348 505 /** 349 506 * Sets associated file. 350 507 * @param file associated file … … public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType 412 569 this.exifImgDir = exifDir; 413 570 } 414 571 572 /** 573 * Sets the exif GPS track (move direction angle) 574 * @param exifGpsTrack the exif GPS track angle 575 * @since xxx 576 */ 577 @Override 578 public void setExifGpsTrack(Double exifGpsTrack) { 579 this.exifGpsTrack = exifGpsTrack; 580 } 581 582 /** 583 * Sets the exif horizontal positioning error 584 * @param exifHposErr the Exif horizontal positionning error 585 * @since xxx 586 */ 587 @Override 588 public void setExifHPosErr(Double exifHPosErr) { 589 this.exifHPosErr = exifHPosErr; 590 } 591 415 592 /** 416 593 * Sets the IPTC caption. 417 594 * @param iptcCaption the IPTC caption … … public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType 507 684 @Override 508 685 public int hashCode() { 509 686 return Objects.hash(height, width, isNewGpsData, 510 elevation, exifCoor, exifGpsTime, exifImgDir, exifOrientation, exifTime, 511 iptcCaption, iptcHeadline, iptcKeywords, iptcObjectName, 512 file, gpsTime, pos, speed, tmp, cameraProjection); 687 elevation, exifCoor, exifGpsTime, exifImgDir, exifGpsTrack, exifHPosErr, 688 exifGpsDop, gpsDiffMode, gps2d3dMode, exifGpsDatum, exifGpsProcMethod, 689 exifOrientation, exifTime, iptcCaption, iptcHeadline, iptcKeywords, 690 iptcObjectName, file, gpsTime, pos, speed, tmp, cameraProjection); 513 691 } 514 692 515 693 @Override … … public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType 526 704 && Objects.equals(exifCoor, other.exifCoor) 527 705 && Objects.equals(exifGpsTime, other.exifGpsTime) 528 706 && Objects.equals(exifImgDir, other.exifImgDir) 707 && Objects.equals(exifGpsTrack, other.exifGpsTrack) 708 && Objects.equals(exifHPosErr, other.exifHPosErr) 709 && Objects.equals(gpsDiffMode, other.gpsDiffMode) 710 && Objects.equals(gps2d3dMode, other.gps2d3dMode) 711 && Objects.equals(exifGpsDop, other.exifGpsDop) 712 && Objects.equals(exifGpsDatum, other.exifGpsDatum) 713 && Objects.equals(exifGpsProcMethod, other.exifGpsProcMethod) 529 714 && Objects.equals(exifOrientation, other.exifOrientation) 530 715 && Objects.equals(exifTime, other.exifTime) 531 716 && Objects.equals(iptcCaption, other.iptcCaption) … … public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType 570 755 * Copy the values from the temporary variable to the main instance. The 571 756 * temporary variable is deleted. 572 757 * @see #discardTmp() 758 * @since xxx exifGpsTrack, exifHPosErr, gpsDiffMode, gps2d3dMode, exifGpsDop, exifGpsDatum, exifGpsProcMethod added 573 759 */ 574 760 public void applyTmp() { 575 761 if (tmp != null) { … … public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType 578 764 elevation = tmp.elevation; 579 765 gpsTime = tmp.gpsTime; 580 766 exifImgDir = tmp.exifImgDir; 767 exifGpsTrack = tmp.exifGpsTrack; 768 exifHPosErr = tmp.exifHPosErr; 769 gpsDiffMode = tmp.gpsDiffMode; 770 gps2d3dMode = tmp.gps2d3dMode; 771 exifGpsDop = tmp.exifGpsDop; 772 exifGpsDatum = tmp.exifGpsDatum; 773 exifGpsProcMethod = tmp.exifGpsProcMethod; 581 774 isNewGpsData = isNewGpsData || tmp.isNewGpsData; 582 775 tmp = null; 583 776 } -
new file src/org/openstreetmap/josm/data/gpx/GpxImageExtendedSettings.java
diff --git a/src/org/openstreetmap/josm/data/gpx/GpxImageExtendedSettings.java b/src/org/openstreetmap/josm/data/gpx/GpxImageExtendedSettings.java new file mode 100644 index 0000000000..b7e88ec695
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.gpx; 3 4 /** 5 * Image extended exif metadata settings used by {@link GpxImageCorrelationSettings}. 6 * @since xxx 7 */ 8 public class GpxImageExtendedSettings { 9 10 private final boolean setImageGpsDatum; 11 private final String imageGpsDatum; 12 13 /** 14 * Construcs a new {@code GpxImageExtendedSettings}. 15 * @param setImageGpsDatum determines if images GPS datum must be set 16 * @param imageGpsDatum determines the GPS coordinates datum value to be set 17 */ 18 public GpxImageExtendedSettings( 19 boolean setImageGpsDatum, String imageGpsDatum) { 20 this.setImageGpsDatum = setImageGpsDatum; 21 this.imageGpsDatum = imageGpsDatum; 22 } 23 24 /** 25 * Determines if image GPS datum must be set 26 * @return if the GPS datum must be set 27 */ 28 public boolean isSetImageGpsDatum() { 29 return setImageGpsDatum; 30 } 31 32 /** 33 * Return the GPS coordinates datum code. 34 * @return the datum code 35 */ 36 public String getImageGpsDatum() { 37 return imageGpsDatum; 38 } 39 } -
src/org/openstreetmap/josm/data/imagery/street_level/IImageEntry.java
diff --git a/src/org/openstreetmap/josm/data/imagery/street_level/IImageEntry.java b/src/org/openstreetmap/josm/data/imagery/street_level/IImageEntry.java index 3dcd79705d..04a9504e95 100644
a b public interface IImageEntry<I extends IImageEntry<I>> { 218 218 */ 219 219 Double getElevation(); 220 220 221 /** 222 * Return the GPS Differential mode value. The differential mode value from the temporary 223 * copy is returned if that copy exists. 224 * @return the fix mode value 225 * @since xxx 226 */ 227 Integer getGpsDiffMode(); 228 229 /** 230 * Return the GPS 2d/3d mode value. The 2d/3d mode value from the temporary 231 * copy is returned if that copy exists. 232 * @return the 2d/3d mode value 233 * @since xxx 234 */ 235 Integer getGps2d3dMode(); 236 237 /** 238 * Return the GPS DOP value. The GPS DOP value from the temporary 239 * copy is return if that copy exists. 240 * @return the GPS DOP value 241 * @since xxx 242 */ 243 Double getExifGpsDop(); 244 245 /** 246 * Return the GPS datum value. The GPS datum value from the temporary 247 * copy is return if that copy exists. 248 * @return the GPS datum value 249 * @since xxx 250 */ 251 String getExifGpsDatum(); 252 253 /** 254 * Return the GPS processing method. The processing method value from the temporary 255 * copy is return if that copy exists. 256 * @return the GPS processing method 257 * @since xxx 258 */ 259 String getExifGpsProcMethod(); 260 221 261 /** 222 262 * Returns the image direction. The image direction from the temporary 223 263 * copy is returned if that copy exists. 224 264 * @return The image camera angle 225 265 */ 226 266 Double getExifImgDir(); 267 268 /** 269 * Returns the image GPS track direction. The GPS track direction from the temporary 270 * copy is returned if that copy exists. 271 * @return the image GPS track direction angle 272 * @since xxx 273 */ 274 Double getExifGpsTrack(); 227 275 276 /** 277 * Returns the image horizontal positionning error. The image positionning error 278 * from the temporary copy is returned if that copy exists. 279 * @return the image horizontal positionning error 280 * @since xxx 281 */ 282 Double getExifHPosErr(); 283 228 284 /** 229 285 * Convenient way to determine if this entry has a EXIF time, without the cost of building a defensive copy. 230 286 * @return {@code true} if this entry has a EXIF time … … public interface IImageEntry<I extends IImageEntry<I>> { 251 307 */ 252 308 Instant getGpsInstant(); 253 309 310 /** 311 * Returns the Exif GPS Time value. The Exif GPS time value from the temporary copy 312 * is returned if that copy exists. 313 * @return the Exif GPS time value 314 * @since xxx 315 */ 316 Instant getExifGpsInstant(); 317 254 318 /** 255 319 * Returns the IPTC caption. 256 320 * @return the IPTC caption -
src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
diff --git a/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java b/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java index 92d16c1ed1..faaaf50268 100644
a b public class OsmDataLayer extends AbstractOsmDataLayer 978 978 addStringIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_SYM, "wpt_symbol", null); 979 979 addStringIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_TYPE, null, null); 980 980 981 // Angle info 982 addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_COURSE, "gps:course"); 983 981 984 // Accuracy info 982 985 addStringIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_FIX, "gps:fix", null); 983 986 addIntegerIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_SAT, "gps:sat"); 984 987 addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_HDOP, "gps:hdop"); 985 988 addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_VDOP, "gps:vdop"); 986 989 addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_PDOP, "gps:pdop"); 990 addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_STD_HDEV, "gps:stdhdev"); 991 addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_STD_VDEV, "gps:stdvdev"); 987 992 addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_AGEOFDGPSDATA, "gps:ageofdgpsdata"); 988 993 addIntegerIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_DGPSID, "gps:dgpsid"); 989 994 -
src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java b/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java index a47b4bb734..a3478fbea2 100644
a b import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeListener; 53 53 import org.openstreetmap.josm.data.gpx.GpxDataContainer; 54 54 import org.openstreetmap.josm.data.gpx.GpxImageCorrelation; 55 55 import org.openstreetmap.josm.data.gpx.GpxImageCorrelationSettings; 56 import org.openstreetmap.josm.data.gpx.GpxImageExtendedSettings; 56 57 import org.openstreetmap.josm.data.gpx.GpxTimeOffset; 57 58 import org.openstreetmap.josm.data.gpx.GpxTimezone; 58 59 import org.openstreetmap.josm.data.gpx.WayPoint; … … public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode 168 169 Config.getPref().put("geoimage.timezone", timezone.formatTimezone()); 169 170 Config.getPref().put("geoimage.delta", delta.formatOffset()); 170 171 Config.getPref().putBoolean("geoimage.showThumbs", yLayer.useThumbs); 172 Config.getPref().put("geoimage.datum", tfDatum.getText()); 171 173 172 174 yLayer.useThumbs = cbShowThumbs.isSelected(); 173 175 yLayer.startLoadThumbs(); … … public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode 246 248 247 249 private ExtendedDialog syncDialog; 248 250 private JPanel outerPanel; 251 private JPanel expertPanel; 249 252 private JosmComboBox<GpxDataWrapper> cbGpx; 250 253 private JButton buttonSupport; 251 254 private JosmTextField tfTimezone; … … public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode 253 256 private JCheckBox cbExifImg; 254 257 private JCheckBox cbTaggedImg; 255 258 private JCheckBox cbShowThumbs; 259 private JSeparator sepExtendedTags; 260 private JLabel labelExtTags; 261 private JLabel labelDatum; 256 262 private JLabel statusBarText; 257 263 private JSeparator sepDirectionPosition; 258 264 private ImageDirectionPositionPanel pDirectionPosition; 265 private JCheckBox cbAddGpsDatum; 266 private JosmTextField tfDatum; 259 267 260 268 // remember the last number of matched photos 261 269 private int lastNumMatched; … … public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode 472 480 } 473 481 } 474 482 483 static String loadGpsDatum() { 484 String gpsDatum = Config.getPref().get("geoimage.datum", "WGS-84"); 485 return gpsDatum; 486 } 487 475 488 @Override 476 489 public void actionPerformed(ActionEvent ae) { 477 490 NoGpxDataWrapper nogdw = new NoGpxDataWrapper(); … … public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode 619 632 gbc.gridy = y++; 620 633 panelTf.add(cbShowThumbs, gbc); 621 634 635 //Image direction and position offset GUI 622 636 gbc = GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(0, 12, 0, 0); 623 637 sepDirectionPosition = new JSeparator(SwingConstants.HORIZONTAL); 624 638 gbc.gridy = y++; … … public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode 630 644 pDirectionPosition = ImageDirectionPositionPanel.forGpxTrace(); 631 645 panelTf.add(pDirectionPosition, gbc); 632 646 647 //Extended tags GUI panel 648 expertPanel = new JPanel(new GridBagLayout()); 649 gbc = GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(0, 12, 0, 0); 650 sepExtendedTags = new JSeparator(SwingConstants.HORIZONTAL); 651 gbc.gridx = 0; 652 gbc.gridy = 0; 653 expertPanel.add(sepExtendedTags, gbc); 654 655 labelExtTags = new JLabel(tr("Extended tags")); 656 cbAddGpsDatum = new JCheckBox(tr("Set datum for images coordinates")); 657 cbAddGpsDatum.addActionListener(e -> tfDatum.setEnabled(!tfDatum.isEnabled())); 658 659 labelDatum = new JLabel(tr("Datum: ")); 660 //TODO An AutoCompComboBox would be nice to list the recent datum values. I don't have the skill to add it. 661 tfDatum = new JosmTextField(loadGpsDatum(), 8); 662 tfDatum.setToolTipText(tr("<html>Enter the datum for your images coordinates. Default value is WGS-84.<br>" + 663 "For RTK it could be your local CRS epsg code.<br>(e.g. EPSG:9782 for France mainland.)</html>")); 664 tfDatum.setEnabled(false); 665 666 gbc = GBC.eol(); 667 gbc.gridx = 0; 668 gbc.gridy = 1; 669 expertPanel.add(labelExtTags, gbc); 670 671 gbc = GBC.eol(); 672 gbc.gridx = 0; 673 gbc.gridy = 2; 674 expertPanel.add(cbAddGpsDatum, gbc); 675 676 //TODO move this line a little more to the right 677 gbc = GBC.std(); 678 gbc.gridx = 1; 679 gbc.gridy = 3; 680 expertPanel.add(labelDatum); 681 682 gbc = GBC.eol(); 683 gbc.gridx = 2; 684 gbc.gridy = 3; 685 expertPanel.add(tfDatum, gbc); 686 687 //Add expertPanel to panelTf 688 gbc = GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(0, 12, 0, 0); 689 gbc.gridy = y++; 690 panelTf.add(expertPanel, gbc); 691 633 692 final JPanel statusPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10)); 634 693 statusPanel.setBorder(BorderFactory.createLoweredBevelBorder()); 635 694 statusBarText = new JLabel(" "); … … public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode 651 710 tfOffset.getDocument().addDocumentListener(statusBarUpdater); 652 711 cbExifImg.addItemListener(statusBarUpdaterWithRepaint); 653 712 cbTaggedImg.addItemListener(statusBarUpdaterWithRepaint); 713 cbAddGpsDatum.addItemListener(statusBarUpdaterWithRepaint); 714 tfDatum.getDocument().addDocumentListener(statusBarUpdater); 654 715 pDirectionPosition.addChangeListenerOnComponents(statusBarUpdaterWithRepaint); 655 716 pDirectionPosition.addItemListenerOnComponents(statusBarUpdaterWithRepaint); 656 717 … … public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode 678 739 } 679 740 } 680 741 742 public GpxImageExtendedSettings getSettings() { 743 return new GpxImageExtendedSettings( 744 cbAddGpsDatum.isSelected(), 745 tfDatum.getText()); 746 } 747 681 748 @Override 682 749 public void expertChanged(boolean isExpert) { 683 750 if (buttonSupport != null) { … … public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode 689 756 if (pDirectionPosition != null) { 690 757 pDirectionPosition.setVisible(isExpert); 691 758 } 759 if (expertPanel != null) { 760 expertPanel.setVisible(isExpert); 761 } 692 762 if (syncDialog != null) { 693 763 syncDialog.pack(); 694 764 } … … public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode 786 856 final long offsetMs = ((long) (timezone.getHours() * TimeUnit.HOURS.toMillis(1))) + delta.getMilliseconds(); // in milliseconds 787 857 lastNumMatched = GpxImageCorrelation.matchGpxTrack(dateImgLst, selGpx.data, 788 858 pDirectionPosition.isVisible() ? 789 new GpxImageCorrelationSettings(offsetMs, forceTags, pDirectionPosition.getSettings()) : 859 new GpxImageCorrelationSettings(offsetMs, forceTags, pDirectionPosition.getSettings(), 860 new GpxImageExtendedSettings(cbAddGpsDatum.isSelected(), tfDatum.getText())) : 790 861 new GpxImageCorrelationSettings(offsetMs, forceTags)); 791 862 792 863 return trn("<html>Matched <b>{0}</b> of <b>{1}</b> photo to GPX track.</html>", -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageDirectionPositionPanel.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDirectionPositionPanel.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDirectionPositionPanel.java index 1d998b0795..9057240185 100644
a b public class ImageDirectionPositionPanel extends JPanel { 28 28 29 29 private final JCheckBox cChangeImageDirection = new JCheckBox(); 30 30 private final JSpinner sOffsetDegrees = new JSpinner(new SpinnerNumberModel(0, -360, 360, 1)); 31 private final JCheckBox cSetGpxTrackTag = new JCheckBox(); 31 32 32 33 private final JSpinner sX = new JSpinner(new SpinnerNumberModel(0.0, -50.0, 50.0, 0.1)); 33 34 private final JSpinner sY = new JSpinner(new SpinnerNumberModel(0.0, -50.0, 50.0, 0.1)); … … public class ImageDirectionPositionPanel extends JPanel { 37 38 * Constructs a new {@code ImageMetadataModificationPanel} 38 39 * @param changeDirectionText the text displayed for the change image direction combobox 39 40 */ 40 protected ImageDirectionPositionPanel(String changeDirectionText ) {41 protected ImageDirectionPositionPanel(String changeDirectionText, boolean hideGpxTrack) { 41 42 super(new GridBagLayout()); 42 43 43 44 cChangeImageDirection.setText(changeDirectionText); … … public class ImageDirectionPositionPanel extends JPanel { 45 46 cChangeImageDirection.addActionListener(e -> sOffsetDegrees.setEnabled(!sOffsetDegrees.isEnabled())); 46 47 addSetting(tr("Offset angle in degrees:"), sOffsetDegrees); 47 48 sOffsetDegrees.setEnabled(false); 49 if (!hideGpxTrack) { 50 cChangeImageDirection.addActionListener(e -> cSetGpxTrackTag.setEnabled(!cSetGpxTrackTag.isEnabled())); 51 cSetGpxTrackTag.setText(tr("Set image course direction (from gpx track)")); 52 add(cSetGpxTrackTag, GBC.eol().insets(0, 0, 0, 5)); 53 cSetGpxTrackTag.setEnabled(false); 54 } 48 55 49 56 add(new JSeparator(SwingConstants.HORIZONTAL), 50 57 GBC.eol().fill(GBC.HORIZONTAL).insets(0, 12, 0, 12)); … … public class ImageDirectionPositionPanel extends JPanel { 61 68 * @return a new {@code ImageMetadataModificationPanel} in a GPX trace context 62 69 */ 63 70 public static ImageDirectionPositionPanel forGpxTrace() { 64 return new ImageDirectionPositionPanel(tr("Set image direction towards the next GPX waypoint") );71 return new ImageDirectionPositionPanel(tr("Set image direction towards the next GPX waypoint"), false); 65 72 } 66 73 67 74 /** … … public class ImageDirectionPositionPanel extends JPanel { 69 76 * @return a new {@code ImageMetadataModificationPanel} in an image sequence context 70 77 */ 71 78 public static ImageDirectionPositionPanel forImageSequence() { 72 return new ImageDirectionPositionPanel(tr("Set image direction towards the next one") );79 return new ImageDirectionPositionPanel(tr("Set image direction towards the next one"), true); 73 80 } 74 81 75 82 protected void addSetting(String text, JComponent component) { … … public class ImageDirectionPositionPanel extends JPanel { 86 93 return new GpxImageDirectionPositionSettings( 87 94 cChangeImageDirection.isSelected(), 88 95 (Integer) sOffsetDegrees.getValue(), 96 cSetGpxTrackTag.isSelected(), 89 97 (Double) sX.getValue(), 90 98 (Double) sY.getValue(), 91 99 (Double) sZ.getValue()); … … public class ImageDirectionPositionPanel extends JPanel { 109 117 */ 110 118 public void addItemListenerOnComponents(ItemListener listener) { 111 119 cChangeImageDirection.addItemListener(listener); 120 cSetGpxTrackTag.addItemListener(listener); 112 121 } 113 122 114 123 /** -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageMetadata.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageMetadata.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageMetadata.java index 473e8e5752..c7eb8d3b77 100644
a b public interface ImageMetadata { 128 128 Double getExifImgDir(); 129 129 130 130 /** 131 * Get the last time the source was modified 131 * Get the exif GPS track direction. 132 * @return The GPS track direction 133 * @since xxx 134 */ 135 Double getExifGpsTrack(); 136 137 /** 138 * Get the exif Horizontal positioning error. 139 * @return The image horizontal positioning error 140 * @since xxx 141 */ 142 Double getExifHPosErr(); 143 144 /** 145 * Get the GPS Differential mode. 146 * @return The image gnss fix mode 147 * @since xxx 148 */ 149 Integer getGpsDiffMode(); 150 151 /** 152 * Get the GPS 2d/3d mode. 153 * @return The image gnss 2d/3d mode 154 * @since xxx 155 */ 156 Integer getGps2d3dMode(); 157 158 /** 159 * Get the exif GPS DOP value. 160 * @return The image GPS DOP value 161 * @since xxx 162 */ 163 Double getExifGpsDop(); 164 165 /** 166 * Get the GPS datum value. 167 * @return The image GPS datum value 168 * @since xxx 169 */ 170 String getExifGpsDatum(); 171 172 /** 173 * Get the exif GPS processing method. 174 * @return The image GPS processing method 175 * @since xxx 176 */ 177 String getExifGpsProcMethod(); 178 179 /** 180 * Get the last time the source was modified. 132 181 * @return The last time the source was modified 133 182 */ 134 183 Instant getLastModified(); … … public interface ImageMetadata { 193 242 void setGpsTime(Instant gpsTime); 194 243 195 244 /** 196 * Set the exif coordinates245 * Sets the exif coordinates 197 246 * @param exifCoor The exif coordinates 198 247 */ 199 248 void setExifCoor(ILatLon exifCoor); 200 249 201 250 /** 202 * Set the exif direction251 * Sets the exif direction 203 252 * @param exifDir The direction 204 253 */ 205 254 void setExifImgDir(Double exifDir); 206 255 256 /** 257 * Sets the exif GPS track direction. 258 * @param exifGpsTrack The GPS track direction 259 * @since xxx 260 */ 261 void setExifGpsTrack(Double exifGpsTrack); 262 263 /** 264 * Sets the exif horizontal positioning error. 265 * @param exifHposErr the exif horizontal positionning error 266 * @since xxx 267 */ 268 void setExifHPosErr(Double exifHPosErr); 269 270 /** 271 * Sets the exif GPS DOP value. 272 * @param exifGpsDop the exif GPS DOP value 273 * @since xxx 274 */ 275 void setExifGpsDop(Double exifGpsDop); 276 277 /** 278 * Sets the GPS Differential mode. 279 * @param gpsDiffMode GPS Differential mode 280 * @since xxx 281 */ 282 void setGpsDiffMode(Integer gpsDiffMode); 283 284 /** 285 * Sets the GPS 2d/3d mode. 286 * @param gps2d3dMode GPS 2d/3d mode 287 * @since xxx 288 */ 289 void setGps2d3dMode(Integer gps2d3dMode); 290 291 /** 292 * Sets the GPS datum value. 293 * @param exifGpsDatum GPS datum 294 * @since xxx 295 */ 296 void setExifGpsDatum(String exifGpsDatum); 297 298 /** 299 * Sets the GPS processing method. 300 * @param exifGpsProcMethod GPS processing method 301 * @since xxx 302 */ 303 void setExifGpsProcMethod(String exifGpsProcMethod); 304 207 305 /** 208 306 * Sets the IPTC caption. 209 307 * @param iptcCaption the IPTC caption -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageUtils.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageUtils.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageUtils.java index abe9e1f12c..19f0390c12 100644
a b public final class ImageUtils { 191 191 Logging.debug(ex); 192 192 } 193 193 194 try { 195 ifNotNull(ExifReader.readGpsTrackDirection(dirGps), image::setExifGpsTrack); 196 } catch (IndexOutOfBoundsException ex) { 197 Logging.debug(ex); 198 } 199 200 try { 201 ifNotNull(ExifReader.readHpositioningError(dirGps), image::setExifHPosErr); 202 } catch (IndexOutOfBoundsException ex) { 203 Logging.debug(ex); 204 } 205 206 try { 207 ifNotNull(ExifReader.readGpsDiffMode(dirGps), image::setGpsDiffMode); 208 } catch (IndexOutOfBoundsException ex) { 209 Logging.debug(ex); 210 } 211 212 try { 213 ifNotNull(ExifReader.readGpsMeasureMode(dirGps), image::setGps2d3dMode); 214 } catch (IndexOutOfBoundsException ex) { 215 Logging.debug(ex); 216 } 217 218 try { 219 ifNotNull(ExifReader.readGpsDop(dirGps), image::setExifGpsDop); 220 } catch (IndexOutOfBoundsException ex) { 221 Logging.debug(ex); 222 } 223 224 try { 225 ifNotNull(ExifReader.readGpsDatum(dirGps), image::setExifGpsDatum); 226 } catch (IndexOutOfBoundsException ex) { 227 Logging.debug(ex); 228 } 229 230 try { 231 ifNotNull(ExifReader.readGpsProcessingMethod(dirGps), image::setExifGpsProcMethod); 232 } catch (IndexOutOfBoundsException ex) { 233 Logging.debug(ex); 234 } 235 194 236 ifNotNull(dirGps.getGpsDate(), d -> image.setExifGpsTime(d.toInstant())); 195 237 } 196 238 -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java index d4b402296b..1ce375f9a4 100644
a b public final class ImageViewerDialog extends ToggleDialog implements LayerChange 88 88 89 89 private final ImageZoomAction imageZoomAction = new ImageZoomAction(); 90 90 private final ImageCenterViewAction imageCenterViewAction = new ImageCenterViewAction(); 91 private final ImageExtendedInfoAction imageExtendedInfoAction = new ImageExtendedInfoAction(); 91 92 private final ImageNextAction imageNextAction = new ImageNextAction(); 92 93 private final ImageRemoveAction imageRemoveAction = new ImageRemoveAction(); 93 94 private final ImageRemoveFromDiskAction imageRemoveFromDiskAction = new ImageRemoveFromDiskAction(); … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 103 104 private final ImageDisplay imgDisplay = new ImageDisplay(imageryFilterSettings); 104 105 private Future<?> imgLoadingFuture; 105 106 private boolean centerView; 107 private boolean extendedImgInfo; 106 108 107 109 // Only one instance of that class is present at one time 108 110 private static volatile ImageViewerDialog dialog; … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 163 165 private JButton btnOpenExternal; 164 166 private JButton btnDeleteFromDisk; 165 167 private JToggleButton tbCentre; 168 private JToggleButton tbImgExtInfo; 166 169 /** The layer tab (used to select images when multiple layers provide images, makes for easy switching) */ 167 170 private final HideableTabbedPane layers = new HideableTabbedPane(); 168 171 … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 234 237 tbCentre.setSelected(centerView); 235 238 tbCentre.setPreferredSize(buttonDim); 236 239 240 extendedImgInfo = Config.getPref().getBoolean("geoimage.viewer.extendedinfo", false); 241 tbImgExtInfo = new JToggleButton(imageExtendedInfoAction); 242 tbImgExtInfo.setSelected(extendedImgInfo); 243 tbImgExtInfo.setPreferredSize(buttonDim); 244 237 245 JButton btnZoomBestFit = new JButton(imageZoomAction); 238 246 btnZoomBestFit.setPreferredSize(buttonDim); 239 247 … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 242 250 243 251 JPanel buttons = new JPanel(); 244 252 addButtonGroup(buttons, this.btnFirst, this.btnPrevious, this.btnNext, this.btnLast); 245 addButtonGroup(buttons, this.tbCentre, btnZoomBestFit );253 addButtonGroup(buttons, this.tbCentre, btnZoomBestFit, this.tbImgExtInfo); 246 254 addButtonGroup(buttons, this.btnDelete, this.btnDeleteFromDisk); 247 255 addButtonGroup(buttons, this.btnCopyPath, this.btnOpenExternal); 248 256 addButtonGroup(buttons, createButton(visibilityAction, buttonDim)); … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 583 591 } 584 592 } 585 593 594 private class ImageExtendedInfoAction extends JosmAction { 595 ImageExtendedInfoAction() { 596 super(null, new ImageProvider("info"), tr("Display image extended metadata"), Shortcut.registerShortcut( 597 "geoimage:extendedinfos", tr(GEOIMAGE_FILLER, tr("Toggle Osd extended informations")), 598 KeyEvent.CHAR_UNDEFINED, Shortcut.NONE), 599 false, null, false); 600 } 601 602 @Override 603 public void actionPerformed(ActionEvent e) { 604 final JToggleButton button = (JToggleButton) e.getSource(); 605 extendedImgInfo = button.isEnabled() && button.isSelected(); 606 Config.getPref().putBoolean("geoimage.viewer.extendedinfo", extendedImgInfo); 607 refresh(false); 608 } 609 } 610 586 611 private class ImageRemoveAction extends JosmAction { 587 612 ImageRemoveAction() { 588 613 super(null, new ImageProvider(DIALOG_FOLDER, "delete"), tr("Remove photo from layer"), Shortcut.registerShortcut( … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 999 1024 if (entry.getSpeed() != null) { 1000 1025 osd.append(tr("\nSpeed: {0} km/h", Math.round(entry.getSpeed()))); 1001 1026 } 1002 if (entry.getExifImgDir() != null) {1003 osd.append(tr("\nDirection {0}\u00b0", Math.round(entry.getExifImgDir())));1004 }1005 1006 1027 DateTimeFormatter dtf = DateUtils.getDateTimeFormatter(FormatStyle.SHORT, FormatStyle.MEDIUM) 1007 // Set timezone to UTC since UTC is assumed when parsing the EXIF timestamp, 1008 // see see org.openstreetmap.josm.tools.ExifReader.readTime(com.drew.metadata.Metadata) 1009 .withZone(ZoneOffset.UTC); 1010 1028 // Set timezone to UTC since UTC is assumed when parsing the EXIF timestamp, 1029 // see see org.openstreetmap.josm.tools.ExifReader.readTime(com.drew.metadata.Metadata) 1030 .withZone(ZoneOffset.UTC); 1011 1031 if (entry.hasExifTime()) { 1012 osd.append(tr("\nEXIF time: {0}", dtf.format(entry.getExifInstant()))); 1032 if (Config.getPref().getBoolean("geoimage.viewer.extendedinfo", false)) { 1033 osd.append(tr("\nEXIF DTO time: {0}", dtf.format(entry.getExifInstant()))); 1034 } else { 1035 osd.append(tr("\nEXIF time: {0}", dtf.format(entry.getExifInstant()))); 1036 } 1013 1037 } 1014 if (entry.hasGpsTime()) { 1015 osd.append(tr("\nGPS time: {0}", dtf.format(entry.getGpsInstant()))); 1038 1039 if (Config.getPref().getBoolean("geoimage.viewer.extendedinfo", false)) { 1040 if (entry.getExifGpsInstant() != null) { 1041 osd.append(tr("\nEXIF GPS time: {0}", dtf.format(entry.getExifGpsInstant()))); 1042 } 1043 if (entry.hasGpsTime()) { 1044 osd.append(tr("\nCorr GPS time: {0}", dtf.format(entry.getGpsInstant()))); 1045 } 1046 if (entry.getExifImgDir() != null) { 1047 osd.append(tr("\nDirection {0}\u00b0", Math.round(entry.getExifImgDir()))); 1048 } 1049 if (entry.getExifGpsTrack() != null) { 1050 osd.append(tr("\nGPS direction: {0}\u00b0", Math.round(entry.getExifGpsTrack()))); 1051 } 1052 if (entry.getExifHPosErr() != null) { 1053 osd.append(tr("\nHpos errror: {0}m", entry.getExifHPosErr())); 1054 } 1055 if (entry.getGps2d3dMode() != null) { 1056 osd.append(tr("\n2d/3d mode: {0}d", entry.getGps2d3dMode())); 1057 } 1058 if (entry.getGpsDiffMode() != null) { 1059 osd.append(tr("\nDifferential: {0}", entry.getGpsDiffMode())); 1060 } 1061 if (entry.getExifGpsDop() != null) { 1062 osd.append(tr("\nDOP: {0}", entry.getExifGpsDop())); 1063 } 1064 if (entry.getExifGpsDatum() != null) { 1065 osd.append(tr("\nDatum: {0}", entry.getExifGpsDatum().toString())); 1066 } 1067 if (entry.getExifGpsProcMethod() != null) { 1068 osd.append(tr("\nProc. method: {0}", entry.getExifGpsProcMethod().toString())); 1069 } 1016 1070 } 1017 1071 Optional.ofNullable(entry.getIptcCaption()).map(s -> tr("\nCaption: {0}", s)).ifPresent(osd::append); 1018 1072 Optional.ofNullable(entry.getIptcHeadline()).map(s -> tr("\nHeadline: {0}", s)).ifPresent(osd::append); … … public final class ImageViewerDialog extends ToggleDialog implements LayerChange 1145 1199 } 1146 1200 } 1147 1201 1202 /** 1203 * Reload the image or reload only the image info. Call this if want to update the OSD. 1204 * @param imageChanged reload the image if true. Reload only the OSD if false. 1205 * @since xxx 1206 */ 1207 public void refresh(boolean imageChanged) { 1208 if (SwingUtilities.isEventDispatchThread()) { 1209 this.updateButtonsNonNullEntry(currentEntry, imageChanged); 1210 } else { 1211 GuiHelper.runInEDT(this::refresh); 1212 } 1213 } 1214 1148 1215 private void registerOnLayer(Layer layer) { 1149 1216 if (layer instanceof IGeoImageLayer) { 1150 1217 layer.addPropertyChangeListener(l -> { -
src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java b/src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java index ab87cd8c05..e2b5cd3c4c 100644
a b public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata { 37 37 private ILatLon pos; 38 38 private Integer exifOrientation; 39 39 private Double elevation; 40 private Integer gpsDiffMode; 41 private Integer gps2d3dMode; 42 private Double exifHPosErr; 43 private Double exifGpsDop; 44 private String exifGpsDatum; 45 private String exifGpsProcMethod; 40 46 private Double speed; 41 47 private Double exifImgDir; 48 private Double exifGpsTrack; 42 49 private ILatLon exifCoor; 43 50 private Instant exifTime; 44 51 private Instant exifGpsTime; … … public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata { 117 124 this.speed = speed; 118 125 } 119 126 127 /** 128 * @since xxx 129 */ 130 @Override 131 public void setGpsDiffMode(Integer gpsDiffMode) { 132 this.gpsDiffMode = gpsDiffMode; 133 } 134 135 /** 136 * @since xxx 137 */ 138 @Override 139 public void setGps2d3dMode(Integer gps2d3dMode) { 140 this.gps2d3dMode = gps2d3dMode; 141 } 142 143 /** 144 * @since xxx 145 */ 146 @Override 147 public void setExifGpsDatum(String exifGpsDatum) { 148 this.exifGpsDatum = exifGpsDatum; 149 } 150 151 /** 152 * @since xxx 153 */ 154 @Override 155 public void setExifGpsProcMethod(String exifGpsProcMethod) { 156 this.exifGpsProcMethod = exifGpsProcMethod; 157 } 158 120 159 @Override 121 160 public void setElevation(Double elevation) { 122 161 this.elevation = elevation; … … public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata { 152 191 this.exifImgDir = exifDir; 153 192 } 154 193 194 /** 195 * @since xxx 196 */ 197 @Override 198 public void setExifGpsTrack(Double exifGpsTrack) { 199 this.exifGpsTrack = exifGpsTrack; 200 } 201 202 /** 203 * @since xxx 204 */ 205 @Override 206 public void setExifHPosErr(Double exifHPosError) { 207 this.exifHPosErr = exifHPosError; 208 } 209 210 /** 211 * @since xxx 212 */ 213 @Override 214 public void setExifGpsDop(Double exifGpsDop) { 215 this.exifGpsDop = exifGpsDop; 216 } 217 155 218 @Override 156 219 public void setIptcCaption(String iptcCaption) { 157 220 this.iptcCaption = iptcCaption; … … public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata { 212 275 return this.elevation; 213 276 } 214 277 278 /** 279 * @since xxx 280 */ 281 @Override 282 public Integer getGpsDiffMode() { 283 return this.gpsDiffMode; 284 } 285 286 /** 287 * @since xxx 288 */ 289 @Override 290 public Integer getGps2d3dMode() { 291 return this.gps2d3dMode; 292 } 293 294 /** 295 * @since xxx 296 */ 297 @Override 298 public String getExifGpsDatum() { 299 return this.exifGpsDatum; 300 } 301 302 /** 303 * @since xxx 304 */ 305 @Override 306 public String getExifGpsProcMethod() { 307 return this.exifGpsProcMethod; 308 } 309 310 /** 311 * @since xxx 312 */ 215 313 @Override 216 314 public Double getExifImgDir() { 217 315 return this.exifImgDir; 218 316 } 219 317 318 /** 319 * @since xxx 320 */ 321 @Override 322 public Double getExifGpsTrack() { 323 return this.exifGpsTrack; 324 } 325 326 /** 327 * @since xxx 328 */ 329 @Override 330 public Double getExifHPosErr() { 331 return this.exifHPosErr; 332 } 333 334 /** 335 * @since xxx 336 */ 337 @Override 338 public Double getExifGpsDop() { 339 return this.exifGpsDop; 340 } 341 220 342 @Override 221 343 public Instant getLastModified() { 222 344 if ("file".equals(this.getImageURI().getScheme())) { … … public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata { 335 457 public int hashCode() { 336 458 return Objects.hash(this.uri, this.pos, 337 459 this.exifOrientation, this.elevation, this.speed, this.exifImgDir, 338 this.exifCoor, this.exifTime, this.exifGpsTime, this.gpsTime, 339 this.iptcObjectName, this.iptcCaption, this.iptcHeadline, this.iptcKeywords, 340 this.projection, this.title); 460 this.exifGpsTrack, this.exifCoor, this.exifTime, this.exifGpsTime, 461 this.gpsTime, this.exifHPosErr, this.exifGpsDop, this.gpsDiffMode, 462 this.gps2d3dMode, this.exifGpsDatum, this.exifGpsProcMethod, 463 this.iptcObjectName, this.iptcCaption, this.iptcHeadline, 464 this.iptcKeywords, this.projection, this.title); 341 465 } 342 466 343 467 @Override … … public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata { 352 476 && Objects.equals(this.exifCoor, other.exifCoor) 353 477 && Objects.equals(this.exifGpsTime, other.exifGpsTime) 354 478 && Objects.equals(this.exifImgDir, other.exifImgDir) 479 && Objects.equals(this.exifGpsTrack, other.exifGpsTrack) 480 && Objects.equals(this.exifHPosErr, other.exifHPosErr) 481 && Objects.equals(this.exifGpsDop, other.exifGpsDop) 355 482 && Objects.equals(this.exifOrientation, other.exifOrientation) 356 483 && Objects.equals(this.exifTime, other.exifTime) 357 484 && Objects.equals(this.gpsTime, other.gpsTime) … … public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata { 362 489 && Objects.equals(this.pos, other.pos) 363 490 && Objects.equals(this.projection, other.projection) 364 491 && Objects.equals(this.speed, other.speed) 492 && Objects.equals(this.gpsDiffMode, other.gpsDiffMode) 493 && Objects.equals(this.gps2d3dMode, other.gps2d3dMode) 494 && Objects.equals(this.exifGpsDatum, other.exifGpsDatum) 495 && Objects.equals(this.exifGpsProcMethod, other.exifGpsProcMethod) 365 496 && Objects.equals(this.title, other.title); 366 497 } 367 498 return false; -
src/org/openstreetmap/josm/io/nmea/NmeaParser.java
diff --git a/src/org/openstreetmap/josm/io/nmea/NmeaParser.java b/src/org/openstreetmap/josm/io/nmea/NmeaParser.java index 3c6b82e12c..9631df402e 100644
a b public class NmeaParser { 466 466 accu = e[VTG.COURSE.position]; 467 467 if (!accu.isEmpty() && currentwp != null) { 468 468 Double.parseDouble(accu); 469 currentwp.put( "course", accu);469 currentwp.put(GpxConstants.PT_COURSE, accu); 470 470 } 471 471 } 472 472 // SPEED … … public class NmeaParser { 542 542 accu = e[RMC.COURSE.position]; 543 543 if (!accu.isEmpty() && !currentwp.attr.containsKey("course")) { 544 544 Double.parseDouble(accu); 545 currentwp.put( "course", accu);545 currentwp.put(GpxConstants.PT_COURSE, accu); 546 546 } 547 547 548 548 // TODO fix? -
src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java
diff --git a/src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java b/src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java index fa24a4d66a..47ff8ebb8d 100644
a b import org.w3c.dom.Element; 22 22 /** 23 23 * Session exporter for {@link GeoImageLayer}. 24 24 * @since 5505 25 * @since xxx exifGpsTrack, exifHPosErr, gpsDiffMode, gps2d3dMode, exifGpsDop, exifGpsDatum, exifGpsProcMethod exporter added 25 26 */ 26 27 public class GeoImageSessionExporter extends AbstractSessionExporter<GeoImageLayer> { 27 28 … … public class GeoImageSessionExporter extends AbstractSessionExporter<GeoImageLay 105 106 if (entry.getExifImgDir() != null) { 106 107 addAttr("exif-image-direction", entry.getExifImgDir().toString(), imgElem, support); 107 108 } 109 if (entry.getExifGpsTrack() != null) { 110 addAttr("exif-gps-track", entry.getExifGpsTrack().toString(), imgElem, support); 111 } 112 if (entry.getExifHPosErr() != null) { 113 addAttr("exif-gps-hposerr", entry.getExifHPosErr().toString(), imgElem, support); 114 } 115 if (entry.getGpsDiffMode() != null) { 116 addAttr("exif-gps-diffmode", entry.getGpsDiffMode().toString(), imgElem, support); 117 } 118 if (entry.getGps2d3dMode() != null) { 119 addAttr("exif-gps-2d3dmode", entry.getGps2d3dMode().toString(), imgElem, support); 120 } 121 if (entry.getExifGpsDop() != null) { 122 addAttr("exif-gps-dop", entry.getExifGpsDop().toString(), imgElem, support); 123 } 124 if (entry.getExifGpsDatum() != null) { 125 addAttr("exif-gps-datum", entry.getExifGpsDatum().toString(), imgElem, support); 126 } 127 if (entry.getExifGpsProcMethod() != null) { 128 addAttr("exif-gps-procmethod", entry.getExifGpsProcMethod().toString(), imgElem, support); 129 } 108 130 if (entry.hasNewGpsData()) { 109 131 addAttr("is-new-gps-data", Boolean.toString(entry.hasNewGpsData()), imgElem, support); 110 132 } -
src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java
diff --git a/src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java b/src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java index 80f99b68ad..0914fe4c97 100644
a b import org.w3c.dom.NodeList; 25 25 /** 26 26 * Session importer for {@link GeoImageLayer}. 27 27 * @since 5505 28 * @since xxx exifGpsTrack, exifHPosErr, gpsDiffMode, gps2d3dMode, exifGpsDop, exifGpsDatum, exifGpsProcMethod importer added 28 29 */ 29 30 public class GeoImageSessionImporter implements SessionLayerImporter { 30 31 … … public class GeoImageSessionImporter implements SessionLayerImporter { 108 109 case "exif-image-direction": 109 110 entry.setExifImgDir(Double.valueOf(attrElem.getTextContent())); 110 111 break; 112 case "exif-gps-track": 113 entry.setExifGpsTrack(Double.valueOf(attrElem.getTextContent())); 114 break; 115 case "exif-gps-hposerr": 116 entry.setExifHPosErr(Double.valueOf(attrElem.getTextContent())); 117 break; 118 case "exif-gps-diffmode": 119 entry.setGpsDiffMode(Integer.valueOf(attrElem.getTextContent())); 120 break; 121 case "exif-gps-2d3dmode": 122 entry.setGps2d3dMode(Integer.valueOf(attrElem.getTextContent())); 123 break; 124 case "exif-gps-dop": 125 entry.setExifGpsDop(Double.valueOf(attrElem.getTextContent())); 126 break; 127 case "exif-gps-datum": 128 entry.setExifGpsDatum(attrElem.getTextContent()); 129 break; 130 case "exif-gps-procmethod": 131 entry.setExifGpsProcMethod(attrElem.getTextContent()); 132 break; 111 133 case "is-new-gps-data": 112 134 if (Boolean.parseBoolean(attrElem.getTextContent())) { 113 135 entry.flagNewGpsData(); -
src/org/openstreetmap/josm/tools/ExifReader.java
diff --git a/src/org/openstreetmap/josm/tools/ExifReader.java b/src/org/openstreetmap/josm/tools/ExifReader.java index ad9e361c4f..404481b3a3 100644
a b public final class ExifReader { 123 123 return null; 124 124 } 125 125 126 /** 127 * Returns the GPS date/time from the given JPEG file. 128 * @param filename The JPEG file to read 129 * @return The GPS date/time read in the EXIF section, or {@code null} if not found 130 * @since xxx 131 */ 132 public static Instant readGpsInstant(File filename) { 133 try { 134 final Metadata metadata = JpegMetadataReader.readMetadata(filename); 135 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class); 136 return readGpsInstant(dirGps); 137 } catch (JpegProcessingException | IOException e) { 138 Logging.error(e); 139 } 140 return null; 141 } 142 143 /** 144 * Returns the GPS date/time from the given JPEG file. 145 * @param dirGps The EXIF GPS directory 146 * @return The GPS date/time read in the EXIF section, or {@code null} if not found 147 * @since xxx 148 */ 149 public static Instant readGpsInstant(GpsDirectory dirGps) { 150 if (dirGps != null) { 151 try { 152 Instant dateTimeStamp = dirGps.getGpsDate().toInstant(); 153 return dateTimeStamp; 154 } catch (UncheckedParseException | DateTimeException e) { 155 Logging.error(e); 156 } 157 } 158 return null; 159 } 160 126 161 /** 127 162 * Returns the image orientation of the given JPEG file. 128 163 * @param filename The JPEG file to read … … public final class ExifReader { 218 253 return null; 219 254 } 220 255 256 /** 257 * Returns the GPS track direction of the given JPEG file. 258 * @param filename The JPEG file to read 259 * @return The GPS track direction of the image when it was captures (in degrees between 0.0 and 359.99), 260 * or {@code null} if not found 261 * @since xxx 262 */ 263 public static Double readGpsTrackDirection(File filename) { 264 try { 265 final Metadata metadata = JpegMetadataReader.readMetadata(filename); 266 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class); 267 return readGpsTrackDirection(dirGps); 268 } catch (JpegProcessingException | IOException e) { 269 Logging.error(e); 270 } 271 return null; 272 } 273 274 /** 275 * Returns the GPS track direction of the given EXIF GPS directory. 276 * @param dirGps The EXIF GPS directory 277 * @return The GPS track direction of the image when it was captured (in degrees between 0.0 and 359.99), 278 * or {@code null} if missing or if {@code dirGps} is null 279 * @since xxx 280 */ 281 public static Double readGpsTrackDirection(GpsDirectory dirGps) { 282 if (dirGps != null) { 283 Rational trackDirection = dirGps.getRational(GpsDirectory.TAG_TRACK); 284 if (trackDirection != null) { 285 return trackDirection.doubleValue(); 286 } 287 } 288 return null; 289 } 290 221 291 private static double readAxis(GpsDirectory dirGps, int gpsTag, int gpsTagRef, char cRef) throws MetadataException { 222 292 double value; 223 293 Rational[] components = dirGps.getRationalArray(gpsTag); … … public final class ExifReader { 323 393 return null; 324 394 } 325 395 396 /** 397 * Returns the GPS horizontal positionning error of the given JPEG file. 398 * @param filename The JPEG file to read 399 * @return The GPS horizontal positionning error of the camera when the image was captured (in m), 400 * or {@code null} if not found 401 * @since xxx 402 */ 403 public static Double readHpositioningError(File filename) { 404 try { 405 final Metadata metadata = JpegMetadataReader.readMetadata(filename); 406 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class); 407 return readHpositioningError(dirGps); 408 } catch (JpegProcessingException | IOException e) { 409 Logging.error(e); 410 } 411 return null; 412 } 413 414 /** 415 * Returns the GPS horizontal positionning error of the given EXIF GPS directory. 416 * @param dirGps The EXIF GPS directory 417 * @return The GPS horizontal positionning error of the camera when the image was captured (in m), 418 * or {@code null} if missing or if {@code dirGps} is null 419 * @since xxx 420 */ 421 public static Double readHpositioningError(GpsDirectory dirGps) { 422 if (dirGps != null) { 423 Double hposerr = dirGps.getDoubleObject(GpsDirectory.TAG_H_POSITIONING_ERROR); 424 if (hposerr != null) { 425 return hposerr.doubleValue(); 426 } 427 } 428 return null; 429 } 430 431 /** 432 * Returns the GPS differential mode of the given JPEG file. 433 * @param filename The JPEG file to read 434 * @return The GPS differential mode of the camera when the image was captured, 435 * <ul> 436 * <li>0 : no differential correction</li> 437 * <li>1 : differential correction</li> 438 * <li>or {@code null} if not found</li> 439 * </ul> 440 * @since xxx 441 */ 442 public static Integer readGpsDiffMode(File filename) { 443 try { 444 final Metadata metadata = JpegMetadataReader.readMetadata(filename); 445 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class); 446 return readGpsDiffMode(dirGps); 447 } catch (JpegProcessingException | IOException e) { 448 Logging.error(e); 449 } 450 return null; 451 } 452 453 /** 454 * Returns the GPS differential mode of the given EXIF GPS directory. 455 * @param dirGps The EXIF GPS directory 456 * @return The GPS differential mode of the camera when the image was captured, 457 * <ul> 458 * <li>0 : no differential correction</li> 459 * <li>1 : differential correction</li> 460 * <li>or {@code null} if missing or if {@code dirGps} is null</li> 461 * </ul> 462 * @since xxx 463 */ 464 public static Integer readGpsDiffMode(GpsDirectory dirGps) { 465 if (dirGps != null) { 466 Integer gpsDiffMode = dirGps.getInteger(GpsDirectory.TAG_DIFFERENTIAL); 467 if (gpsDiffMode != null) { 468 return gpsDiffMode.intValue(); 469 } 470 } 471 return null; 472 } 473 474 /** 475 * Returns the GPS 2d/3d mode of the given JPEG file. 476 * @param filename The JPEG file to read 477 * @return The GPS 2d/3d mode of the camera when the image was captured, 478 * <ul> 479 * <li>2 : 2d mode</li> 480 * <li>2 : 3d mode</li> 481 * <li>or {@code null} if not found</li> 482 * </ul> 483 * @since xxx 484 */ 485 public static Integer readGpsMeasureMode(File filename) { 486 try { 487 final Metadata metadata = JpegMetadataReader.readMetadata(filename); 488 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class); 489 return readGpsMeasureMode(dirGps); 490 } catch (JpegProcessingException | IOException e) { 491 Logging.error(e); 492 } 493 return null; 494 } 495 496 /** 497 * Returns the GPS 2d/3d mode of the given EXIF GPS directory. 498 * @param dirGps The EXIF GPS directory 499 * @return The 2d/3d mode of the camera when the image was captured, 500 * <ul> 501 * <li>2 : 2d mode</li> 502 * <li>3 : 3d mode</li> 503 * <li>or {@code null} if missing or if {@code dirGps} is null</li> 504 * </ul> 505 * @since xxx 506 */ 507 public static Integer readGpsMeasureMode(GpsDirectory dirGps) { 508 if (dirGps != null) { 509 Integer gps2d3dMode = dirGps.getInteger(GpsDirectory.TAG_MEASURE_MODE); 510 if (gps2d3dMode != null) { 511 return gps2d3dMode.intValue(); 512 } 513 } 514 return null; 515 } 516 517 /** 518 * Returns the GPS DOP value of the given JPEG file. 519 * @param filename The JPEG file to read 520 * @return The GPS DOP value of the camera when the image was captured, 521 * or {@code null} if not found 522 * @since xxx 523 */ 524 public static Double readGpsDop(File filename) { 525 try { 526 final Metadata metadata = JpegMetadataReader.readMetadata(filename); 527 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class); 528 return readGpsDop(dirGps); 529 } catch (JpegProcessingException | IOException e) { 530 Logging.error(e); 531 } 532 return null; 533 } 534 535 /** 536 * Returns the GPS DOP value of the given EXIF GPS directory. 537 * @param dirGps The EXIF GPS directory 538 * @return The GPS DOP value of the camera when the image was captured, 539 * or {@code null} if missing or if {@code dirGps} is null 540 * @since xxx 541 */ 542 public static Double readGpsDop(GpsDirectory dirGps) { 543 if (dirGps != null) { 544 Double gpsDop = dirGps.getDoubleObject(GpsDirectory.TAG_DOP); 545 if (gpsDop != null) { 546 return gpsDop.doubleValue(); 547 } 548 } 549 return null; 550 } 551 552 /** 553 * Returns the GPS datum value of the given JPEG file. 554 * @param filename The JPEG file to read 555 * @return The GPS datum value of the camera when the image was captured, 556 * or {@code null} if not found 557 * @since xxx 558 */ 559 public static String readGpsDatum(File filename) { 560 try { 561 final Metadata metadata = JpegMetadataReader.readMetadata(filename); 562 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class); 563 return readGpsDatum(dirGps); 564 } catch (JpegProcessingException | IOException e) { 565 Logging.error(e); 566 } 567 return null; 568 } 569 570 /** 571 * Returns the GPS datum value of the given EXIF GPS directory. 572 * @param dirGps The EXIF GPS directory 573 * @return The GPS datum value of the camera when the image was captured, 574 * or {@code null} if missing or if {@code dirGps} is null 575 * @since xxx 576 */ 577 public static String readGpsDatum(GpsDirectory dirGps) { 578 if (dirGps != null) { 579 String gpsDatum = dirGps.getString(GpsDirectory.TAG_MAP_DATUM); 580 if (gpsDatum != null) { 581 return gpsDatum.toString(); 582 } 583 } 584 return null; 585 } 586 587 /** 588 * Return the GPS processing method of the given JPEG file. 589 * @param filename The JPEG file to read 590 * @return The GPS processing method. Possible values from the EXIF specs are: 591 * <ul> 592 * <li>GPS</li> 593 * <li>QZSS</li> 594 * <li>GALILEO</li> 595 * <li>GLONASS</li> 596 * <li>BEIDOU</li> 597 * <li>NAVIC</li> 598 * <li>CELLID</li> 599 * <li>WLAN</li> 600 * <li>MANUAL</li> 601 * </ul> 602 * Other values, and combined space separated values are possible too. 603 * or {@code null} if missing 604 * @since xxx 605 */ 606 public static String readGpsProcessingMethod(File filename) { 607 try { 608 final Metadata metadata = JpegMetadataReader.readMetadata(filename); 609 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class); 610 return readGpsProcessingMethod(dirGps); 611 } catch (JpegProcessingException | IOException e) { 612 Logging.error(e); 613 } 614 return null; 615 } 616 617 /** 618 * Return the GPS processing method of the given EXIF GPS directory. 619 * @param dirGps The EXIF GPS directory 620 * @return The GPS processing method. Possible values from the EXIF specs are: 621 * <ul> 622 * <li>GPS</li> 623 * <li>QZSS</li> 624 * <li>GALILEO</li> 625 * <li>GLONASS</li> 626 * <li>BEIDOU</li> 627 * <li>NAVIC</li> 628 * <li>CELLID</li> 629 * <li>WLAN</li> 630 * <li>MANUAL</li> 631 * </ul> 632 * Other values, and combined space separated values are possible too. 633 * or {@code null} if missing or if {@code dirGps} is null 634 * @since xxx 635 */ 636 public static String readGpsProcessingMethod(GpsDirectory dirGps) { 637 if (dirGps != null) { 638 String gpsProcessingMethod = dirGps.getDescription(GpsDirectory.TAG_PROCESSING_METHOD); 639 if (gpsProcessingMethod != null) { 640 return gpsProcessingMethod.toString(); 641 } 642 } 643 return null; 644 } 645 326 646 /** 327 647 * Returns the caption of the given IPTC directory. 328 648 * @param dirIptc The IPTC directory -
test/unit/org/openstreetmap/josm/data/ImageDataTest.java
diff --git a/test/unit/org/openstreetmap/josm/data/ImageDataTest.java b/test/unit/org/openstreetmap/josm/data/ImageDataTest.java index 22dd7eff9f..3618721d8e 100644
a b class ImageDataTest { 444 444 data.updateImageDirection(list.get(0), 0); 445 445 } 446 446 447 @Test 448 void testUpdateHPosErr() { 449 List<ImageEntry> list = getOneImage(); 450 ImageData data = new ImageData(list); 451 452 new Expectations(list.get(0)) {{ 453 list.get(0).setExifHPosErr(1.23); 454 list.get(0).flagNewGpsData(); 455 }}; 456 data.updateImageHPosErr(list.get(0), 1.23); 457 } 458 459 @Test 460 void testUpdateGpsDatum() { 461 List<ImageEntry> list = getOneImage(); 462 ImageData data = new ImageData(list); 463 464 new Expectations(list.get(0)) {{ 465 list.get(0).setExifGpsDatum("WGS-84"); 466 list.get(0).flagNewGpsData(); 467 }}; 468 data.updateImageExifGpsDatum(list.get(0), "WGS-84"); 469 } 470 471 @Test 472 void testUpdateGpsTrack() { 473 List<ImageEntry> list = getOneImage(); 474 ImageData data = new ImageData(list); 475 476 new Expectations(list.get(0)) {{ 477 list.get(0).setExifGpsTrack(180.5); 478 list.get(0).flagNewGpsData(); 479 }}; 480 data.updateImageGpsTrack(list.get(0), 180.5); 481 } 482 483 @Test 484 void testUpdateGpsDop() { 485 List<ImageEntry> list = getOneImage(); 486 ImageData data = new ImageData(list); 487 488 new Expectations(list.get(0)) {{ 489 list.get(0).setExifGpsDop(1.9); 490 list.get(0).flagNewGpsData(); 491 }}; 492 data.updateImageExifGpsDop(list.get(0), 1.9); 493 } 494 495 @Test 496 void testUpdateGpsProcMethod() { 497 List<ImageEntry> list = getOneImage(); 498 ImageData data = new ImageData(list); 499 500 new Expectations(list.get(0)) {{ 501 list.get(0).setExifGpsProcMethod("GNSS RTK CORRELATION"); 502 list.get(0).flagNewGpsData(); 503 }}; 504 data.updateImageExifGpsProcMethod(list.get(0), "GNSS RTK CORRELATION"); 505 } 506 507 @Test 508 void testUpdateGpsDifferentialMode() { 509 List<ImageEntry> list = getOneImage(); 510 ImageData data = new ImageData(list); 511 512 new Expectations(list.get(0)) {{ 513 list.get(0).setGpsDiffMode(1); 514 list.get(0).flagNewGpsData(); 515 }}; 516 data.updateImageGpsDiffMode(list.get(0), 1); 517 } 518 519 @Test 520 void testUpdateGps2d3dMode() { 521 List<ImageEntry> list = getOneImage(); 522 ImageData data = new ImageData(list); 523 524 new Expectations(list.get(0)) {{ 525 list.get(0).setGps2d3dMode(3); 526 list.get(0).flagNewGpsData(); 527 }}; 528 data.updateImageGps2d3dMode(list.get(0), 3); 529 } 530 447 531 @Test 448 532 void testTriggerListenerOnUpdate() { 449 533 List<ImageEntry> list = getOneImage(); -
test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java
diff --git a/test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java b/test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java index ad5029fba3..74b9316a31 100644
a b class GpxImageCorrelationTest { 350 350 wp.put(GpxConstants.PT_ELE, "150.0"); 351 351 assertEquals(Double.valueOf(150.0d), GpxImageCorrelation.getElevation(wp)); 352 352 } 353 354 /** 355 * Unit test of {@link GpxImageCorrelation#getHPosErr} 356 */ 357 @Test 358 void testGetHorizontalPosError() { 359 assertNull(GpxImageCorrelation.getHPosErr(null)); 360 WayPoint wp = new WayPoint(LatLon.ZERO); 361 assertNull(GpxImageCorrelation.getHPosErr(wp)); 362 wp.put(GpxConstants.PT_STD_HDEV, 1.5f); 363 assertEquals(1.5f, GpxImageCorrelation.getHPosErr(wp)); 364 } 365 366 /** 367 * Unit test of {@link GpxImageCorrelation#getGpsDop} 368 */ 369 @Test 370 void testGetGpsDop() { 371 assertNull(GpxImageCorrelation.getGpsDop(null)); 372 WayPoint wp = new WayPoint(LatLon.ZERO); 373 assertNull(GpxImageCorrelation.getGpsDop(wp)); 374 wp.put(GpxConstants.PT_HDOP, 2.1f); 375 assertEquals(2.1f, GpxImageCorrelation.getGpsDop(wp)); 376 wp.put(GpxConstants.PT_PDOP, 0.9f); 377 assertEquals(0.9f, GpxImageCorrelation.getGpsDop(wp)); 378 wp.put(GpxConstants.PT_PDOP, 1.2d); 379 assertEquals(1.2d, GpxImageCorrelation.getGpsDop(wp)); 380 } 381 382 /** 383 * Unit test of {@link GpxImageCorrelation#getGpsTrack} 384 */ 385 @Test 386 void testGetGpsTrack() { 387 assertNull(GpxImageCorrelation.getHPosErr(null)); 388 WayPoint wp = new WayPoint(LatLon.ZERO); 389 assertNull(GpxImageCorrelation.getGpsTrack(wp)); 390 wp.put(GpxConstants.PT_COURSE, ""); 391 assertNull(GpxImageCorrelation.getGpsTrack(wp)); 392 wp.put(GpxConstants.PT_COURSE, "not a number"); 393 assertNull(GpxImageCorrelation.getGpsTrack(wp)); 394 wp.put(GpxConstants.PT_COURSE, "167.0"); 395 assertEquals(Double.valueOf(167.0d), GpxImageCorrelation.getGpsTrack(wp), 0.01d); 396 } 397 398 /** 399 * Unit test of {@link GpxImageCorrelation#getGpsProcMethod} 400 */ 401 @Test 402 void testGetGpsProcMethod() { 403 final List<String> positionningModes = Arrays.asList("none", "manual", "estimated", "2d", "3d", "dgps", "float rtk", "rtk"); 404 assertNull(GpxImageCorrelation.getGpsProcMethod(null, null, positionningModes)); 405 assertNull(GpxImageCorrelation.getGpsProcMethod("", "", positionningModes)); 406 assertNull(GpxImageCorrelation.getGpsProcMethod("3d", null, positionningModes)); 407 assertNull(GpxImageCorrelation.getGpsProcMethod("", "dgps", positionningModes)); 408 assertEquals("MANUAL CORRELATION", GpxImageCorrelation.getGpsProcMethod("manual", "rtk", positionningModes)); 409 assertEquals("ESTIMATED CORRELATION", GpxImageCorrelation.getGpsProcMethod("estimated", "2d", positionningModes)); 410 assertEquals("GNSS DGPS CORRELATION", GpxImageCorrelation.getGpsProcMethod("rtk", "dgps", positionningModes)); 411 assertEquals("GNSS RTK_FLOAT CORRELATION", GpxImageCorrelation.getGpsProcMethod("float rtk", "rtk", positionningModes)); 412 assertEquals("GNSS RTK_FIX CORRELATION", GpxImageCorrelation.getGpsProcMethod("rtk", "rtk", positionningModes)); 413 } 414 415 /** 416 * Unit test of {@link GpxImageCorrelation#getGps2d3dMode} 417 */ 418 @Test 419 void testGetGps2d3dMode() { 420 final List<String> positionningModes = Arrays.asList("none", "manual", "estimated", "2d", "3d", "dgps", "float rtk", "rtk"); 421 assertNull(GpxImageCorrelation.getGps2d3dMode(null, null, positionningModes)); 422 assertNull(GpxImageCorrelation.getGps2d3dMode("", "", positionningModes)); 423 assertNull(GpxImageCorrelation.getGps2d3dMode("3d", null, positionningModes)); 424 assertNull(GpxImageCorrelation.getGps2d3dMode("", "dgps", positionningModes)); 425 assertNull(GpxImageCorrelation.getGps2d3dMode("estimated", "rtk", positionningModes)); 426 assertEquals(2, GpxImageCorrelation.getGps2d3dMode("2d", "2d", positionningModes)); 427 assertEquals(2, GpxImageCorrelation.getGps2d3dMode("2d", "3d", positionningModes)); 428 assertEquals(3, GpxImageCorrelation.getGps2d3dMode("3d", "dgps", positionningModes)); 429 } 353 430 } -
test/unit/org/openstreetmap/josm/tools/ExifReaderTest.java
diff --git a/test/unit/org/openstreetmap/josm/tools/ExifReaderTest.java b/test/unit/org/openstreetmap/josm/tools/ExifReaderTest.java index 285acd9d55..9dcad6ca38 100644
a b import org.openstreetmap.josm.data.coor.conversion.DMSCoordinateFormat; 21 21 * @since 6209 22 22 */ 23 23 class ExifReaderTest { 24 private File orientationSampleFile, directionSampleFile ;24 private File orientationSampleFile, directionSampleFile, positionErrorSampleFile; 25 25 26 26 /** 27 27 * Setup test … … class ExifReaderTest { 30 30 public void setUp() { 31 31 directionSampleFile = new File("nodist/data/exif-example_direction.jpg"); 32 32 orientationSampleFile = new File("nodist/data/exif-example_orientation=6.jpg"); 33 positionErrorSampleFile = new File("nodist/data/exif-position-error.jpg"); 33 34 } 34 35 35 36 /** … … class ExifReaderTest { 55 56 assertEquals(Instant.parse(expectedDate), date); 56 57 } 57 58 59 /** 60 * Test reading GPS date and time 61 */ 62 @Test 63 void testReadGpsDateTime() { 64 Instant date = ExifReader.readGpsInstant(positionErrorSampleFile); 65 assertEquals(Instant.parse("2024-04-30T16:36:42Z"), date); 66 } 67 58 68 /** 59 69 * Test orientation extraction 60 70 */ … … class ExifReaderTest { 100 110 assertEquals(Double.valueOf(23.4), ExifReader.readElevation(new File("nodist/data/exif-example_speed_ele.jpg"))); 101 111 } 102 112 113 /** 114 * Test horizontal position error extraction 115 */ 116 @Test 117 void testReadHorPosError() { 118 assertEquals(Double.valueOf(0.014), ExifReader.readHpositioningError(positionErrorSampleFile)); 119 } 120 121 /** 122 * Test GPS track course extraction 123 */ 124 @Test 125 void testReadGpsTrack() { 126 assertEquals(Double.valueOf(298), ExifReader.readGpsTrackDirection(positionErrorSampleFile)); 127 } 128 129 /** 130 * Test GPS differential mode extraction 131 */ 132 @Test 133 void testReadGpsDiffmode() { 134 assertEquals(Integer.valueOf(1), ExifReader.readGpsDiffMode(positionErrorSampleFile)); 135 } 136 137 /** 138 * Test GPS DOP value extraction 139 */ 140 @Test 141 void testReadGpsDop() { 142 assertEquals(Double.valueOf(0.92), ExifReader.readGpsDop(positionErrorSampleFile)); 143 } 144 145 /** 146 * Test GPS measure mode (2D/3D) extraction 147 */ 148 @Test 149 void testReadGps2d3dMode() { 150 assertEquals(Integer.valueOf(3), ExifReader.readGpsMeasureMode(positionErrorSampleFile)); 151 } 152 153 /** 154 * Test GPS datum extraction 155 */ 156 @Test 157 void testReadGpsDatum() { 158 assertEquals(String.valueOf("EPSG:9782"), ExifReader.readGpsDatum(positionErrorSampleFile)); 159 } 160 161 /** 162 * Test GPS processing method extraction 163 */ 164 @Test 165 void testReadGpsProcMethod() { 166 assertEquals(String.valueOf("GNSS RTK_FIX CORRELATION"), ExifReader.readGpsProcessingMethod(positionErrorSampleFile)); 167 } 168 103 169 /** 104 170 * Non-regression test for ticket <a href="https://josm.openstreetmap.de/ticket/11685">#11685</a> 105 171 * @throws IOException if an error occurs during reading
