Ticket #9274: GeoImageLayer.patch
| File GeoImageLayer.patch, 22.8 KB (added by , 12 years ago) |
|---|
-
src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
1208 1208 curImg.tmp.setSpeed(speed); 1209 1209 curImg.tmp.setElevation(curElevation); 1210 1210 curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset)); 1211 curImg.flagNewGPSData(); 1211 1212 ret++; 1212 1213 } 1213 1214 i--; … … 1237 1238 curImg.tmp.setElevation(prevElevation + (curElevation - prevElevation) * timeDiff); 1238 1239 } 1239 1240 curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset)); 1241 curImg.flagNewGPSData(); 1240 1242 1241 1243 ret++; 1242 1244 } -
src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
22 22 import java.text.ParseException; 23 23 import java.util.ArrayList; 24 24 import java.util.Arrays; 25 import java.util.Calendar; 25 26 import java.util.Collection; 26 27 import java.util.Collections; 28 import java.util.Date; 29 import java.util.GregorianCalendar; 27 30 import java.util.HashSet; 28 31 import java.util.LinkedHashSet; 29 32 import java.util.LinkedList; 30 33 import java.util.List; 31 34 import java.util.Set; 35 import java.util.TimeZone; 32 36 33 37 import javax.swing.Action; 34 38 import javax.swing.Icon; … … 269 273 * @param gpxLayer The associated GPX layer 270 274 */ 271 275 public GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer) { 272 super(tr("Geotagged Images")); 276 this(data, gpxLayer, null, false); 277 } 273 278 279 /** 280 * Constructs a new {@code GeoImageLayer}. 281 * @param data The list of images to display 282 * @param gpxLayer The associated GPX layer 283 * @param name Layer name 284 */ 285 public GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer, 286 final String name) { 287 this(data, gpxLayer, name, false); 288 } 289 290 /** 291 * Constructs a new {@code GeoImageLayer}. 292 * @param data The list of images to display 293 * @param gpxLayer The associated GPX layer 294 * @param useThumbs Thumbnail display flag 295 */ 296 public GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer, 297 boolean useThumbs) { 298 this(data, gpxLayer, null, useThumbs); 299 } 300 301 /** 302 * Constructs a new {@code GeoImageLayer}. 303 * @param data The list of images to display 304 * @param gpxLayer The associated GPX layer 305 * @param name Layer name 306 * @param useThumbs Thumbnail display flag 307 */ 308 public GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer, 309 final String name, boolean useThumbs) { 310 super(name != null ? name : tr("Geotagged Images")); 274 311 Collections.sort(data); 275 312 this.data = data; 276 313 this.gpxLayer = gpxLayer; 314 this.useThumbs = useThumbs; 277 315 } 278 316 279 317 @Override … … 293 331 entries.add(LayerListDialog.getInstance().createShowHideLayerAction()); 294 332 entries.add(LayerListDialog.getInstance().createDeleteLayerAction()); 295 333 entries.add(new RenameLayerAction(null, this)); 334 //entries.add(LayerListDialog.getInstance().createMergeLayerAction(this)); 296 335 entries.add(SeparatorLayerAction.INSTANCE); 297 336 entries.add(new CorrelateGpxWithImages(this)); 298 337 if (!menuAdditions.isEmpty()) { … … 402 441 int height = mv.getHeight(); 403 442 Rectangle clip = g.getClipBounds(); 404 443 if (useThumbs) { 444 if (!thumbsLoaded) { 445 loadThumbs(); 446 } 447 405 448 if (null == offscreenBuffer || offscreenBuffer.getWidth() != width // reuse the old buffer if possible 406 449 || offscreenBuffer.getHeight() != height) { 407 450 offscreenBuffer = new BufferedImage(width, height, … … 457 500 if (e.getPos() != null) { 458 501 Point p = mv.getPoint(e.getPos()); 459 502 460 if ( e.thumbnail != null) {503 if (useThumbs && e.thumbnail != null) { 461 504 Dimension d = scaledDimension(e.thumbnail); 462 505 g.setColor(new Color(128, 0, 0, 122)); 463 506 g.fillRect(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height); … … 571 614 } catch (Exception ex) { // (CompoundException and other exceptions, e.g. #5271) 572 615 // Do nothing 573 616 } 617 618 /* Time and date. 619 * We can have these cases: 620 * 1) GPS_TIME_STAMP not set -> date/time will be null 621 * 2) GPS_DATE_STAMP not set -> use EXIF date or set to default 622 * 3) GPS_TIME_STAMP and GPS_DATE_STAMP are set 623 */ 624 int[] timeStampComps = dirGps.getIntArray(GpsDirectory.TAG_GPS_TIME_STAMP); 625 if (timeStampComps != null) { 626 int gpsHour = timeStampComps[0]; 627 int gpsMin = timeStampComps[1]; 628 int gpsSec = timeStampComps[2]; 629 Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); 630 631 /* We have the time. Next step is to check if the GPS 632 * date stamp is set. dirGps.getString() always succeeds, 633 * but the return value might be null. 634 */ 635 String dateStampStr = dirGps.getString(GpsDirectory.TAG_GPS_DATE_STAMP); 636 if (dateStampStr != null && dateStampStr.matches("^\\d+:\\d+:\\d+$")) { 637 String[] dateStampComps = dateStampStr.split(":"); 638 cal.set(Calendar.YEAR, Integer.parseInt(dateStampComps[0])); 639 cal.set(Calendar.MONTH, Integer.parseInt(dateStampComps[1]) - 1); 640 cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStampComps[2])); 641 } 642 else { 643 /* No GPS date stamp in EXIF data. Copy it from EXIF 644 * time. Date is not set if EXIF time is not 645 * available. 646 */ 647 Date exifTime = e.getExifTime(); 648 if (exifTime != null) { 649 /* Time not set yet, so we can copy everything, 650 * not just date. 651 */ 652 cal.setTime(exifTime); 653 } 654 } 655 656 cal.set(Calendar.HOUR_OF_DAY, gpsHour); 657 cal.set(Calendar.MINUTE, gpsMin); 658 cal.set(Calendar.SECOND, gpsSec); 659 660 e.setExifGpsTime(cal.getTime()); 661 } 574 662 } 575 663 576 664 public void showNextPhoto() { … … 667 755 } 668 756 } 669 757 758 /** 759 * Remove a photo from the list of images by index. 760 * @param idx Image index 761 */ 762 public void removePhotoByIdx(int idx) { 763 if (idx >= 0 && data != null && idx < data.size()) { 764 data.remove(idx); 765 } 766 } 767 768 /** 769 * Return the image that matches the position of the mouse event. 770 * @param evt Mouse event 771 * @return Image at mouse position, or {@code null} if there is no 772 * image at the mouse position 773 */ 774 public ImageEntry getPhotoUnderMouse(MouseEvent evt) { 775 if (data != null) { 776 for (int idx = data.size() - 1; idx >= 0; --idx) { 777 ImageEntry img = data.get(idx); 778 if (img.getPos() == null) { 779 continue; 780 } 781 Point p = Main.map.mapView.getPoint(img.getPos()); 782 Rectangle r; 783 if (useThumbs && img.thumbnail != null) { 784 Dimension d = scaledDimension(img.thumbnail); 785 r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height); 786 } else { 787 r = new Rectangle(p.x - icon.getIconWidth() / 2, 788 p.y - icon.getIconHeight() / 2, 789 icon.getIconWidth(), 790 icon.getIconHeight()); 791 } 792 if (r.contains(evt.getPoint())) { 793 return img; 794 } 795 } 796 } 797 return null; 798 } 799 800 /** 801 * Clear the currentPhoto, i.e. remove select marker, and 802 * optionally repaint. 803 * @param repaint Repaint flag 804 */ 805 public void clearCurrentPhoto(boolean repaint) { 806 currentPhoto = -1; 807 if (repaint) { 808 updateBufferAndRepaint(); 809 } 810 } 811 812 /** 813 * Clear the currentPhoto of the other GeoImageLayer's. Otherwise 814 * there could be multiple selected photos. 815 */ 816 private void clearOtherCurrentPhotos() { 817 for (GeoImageLayer layer: 818 Main.map.mapView.getLayersOfType(GeoImageLayer.class)) { 819 if (layer != this) { 820 layer.clearCurrentPhoto(false); 821 } 822 } 823 } 824 825 private static List<MapMode> supportedMapModes = null; 826 827 /** 828 * Register a map mode for which the functionality of this layer 829 * should be available. 830 * @param mapMode Map mode to be registered 831 */ 832 public static void registerSupportedMapMode(MapMode mapMode) { 833 if (supportedMapModes == null) { 834 supportedMapModes = new ArrayList<MapMode>(); 835 } 836 supportedMapModes.add(mapMode); 837 } 838 839 /** 840 * Determine if the functionality of this layer is available in 841 * the specified map mode. SelectAction is supported by default, 842 * other map modes can be registered. 843 * @param mapMode Map mode to be checked 844 * @return {@code true} if the map mode is supported, 845 * {@code false} otherwise 846 */ 847 private static final boolean isSupportedMapMode(MapMode mapMode) { 848 if (mapMode instanceof SelectAction) return true; 849 if (supportedMapModes != null) { 850 for (MapMode supmmode: supportedMapModes) { 851 if (mapMode == supmmode) { 852 return true; 853 } 854 } 855 } 856 return false; 857 } 858 670 859 private MouseAdapter mouseAdapter = null; 671 860 private MapModeChangeListener mapModeListener = null; 672 861 … … 674 863 public void hookUpMapView() { 675 864 mouseAdapter = new MouseAdapter() { 676 865 private final boolean isMapModeOk() { 677 return Main.map.mapMode == null || Main.map.mapMode instanceof SelectAction;866 return Main.map.mapMode == null || isSupportedMapMode(Main.map.mapMode); 678 867 } 679 868 @Override public void mousePressed(MouseEvent e) { 680 869 … … 698 887 } 699 888 Point p = Main.map.mapView.getPoint(e.getPos()); 700 889 Rectangle r; 701 if ( e.thumbnail != null) {890 if (useThumbs && e.thumbnail != null) { 702 891 Dimension d = scaledDimension(e.thumbnail); 703 892 r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height); 704 893 } else { … … 708 897 icon.getIconHeight()); 709 898 } 710 899 if (r.contains(ev.getPoint())) { 900 clearOtherCurrentPhotos(); 711 901 currentPhoto = i; 712 902 ImageViewerDialog.showImage(GeoImageLayer.this, e); 713 903 Main.map.repaint(); … … 720 910 mapModeListener = new MapModeChangeListener() { 721 911 @Override 722 912 public void mapModeChange(MapMode oldMapMode, MapMode newMapMode) { 723 if (newMapMode == null || (newMapMode instanceof org.openstreetmap.josm.actions.mapmode.SelectAction)) {913 if (newMapMode == null || isSupportedMapMode(newMapMode)) { 724 914 Main.map.mapView.addMouseListener(mouseAdapter); 725 915 } else { 726 916 Main.map.mapView.removeMouseListener(mouseAdapter); … … 815 1005 public void jumpToPreviousMarker() { 816 1006 showPreviousPhoto(); 817 1007 } 1008 1009 /** 1010 * Get the current thumbnail display status. {@code true}: 1011 * thumbnails are displayed, {@code false}: an icon is displayed 1012 * instead of thumbnails. 1013 * @return Current thumbnail display status 1014 */ 1015 public boolean isUseThumbs() { 1016 return useThumbs; 1017 } 1018 1019 /** 1020 * Enable or disable the display of thumbnails. Does not update 1021 * the display. 1022 * @param useThumbs New thumbnail display status 1023 */ 1024 public void setUseThumbs(boolean useThumbs) { 1025 this.useThumbs = useThumbs; 1026 if (useThumbs && !thumbsLoaded) { 1027 loadThumbs(); 1028 } 1029 } 818 1030 } -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java
17 17 private LatLon exifCoor; 18 18 private Double exifImgDir; 19 19 private Date exifTime; 20 /** 21 * Flag isNewGpsData indicates that the GPS data of the image is 22 * new or has changed. GPS data includes the position, speed, 23 * elevation, time (e.g. as extracted from the GPS track). The 24 * flag can used to decide for which image file the EXIF GPS data 25 * is (re-)written. 26 */ 27 private boolean isNewGpsData = false; 28 /** Temporary source of GPS time if not correlated with GPX track. */ 29 private Date exifGpsTime = null; 20 30 Image thumbnail; 21 31 22 /** The following values are computed from the correlation with the gpx track */ 32 /** 33 * The following values are computed from the correlation with the 34 * gpx track or extracted from the image EXIF data. 35 */ 23 36 private CachedLatLon pos; 24 37 /** Speed in kilometer per second */ 25 38 private Double speed; … … 74 87 public Date getExifTime() { 75 88 return exifTime; 76 89 } 90 public Date getExifGpsTime() { 91 return exifGpsTime; 92 } 77 93 public LatLon getExifCoor() { 78 94 return exifCoor; 79 95 } … … 109 125 public void setExifTime(Date exifTime) { 110 126 this.exifTime = exifTime; 111 127 } 128 public void setExifGpsTime(Date exifGpsTime) { 129 this.exifGpsTime = exifGpsTime; 130 } 112 131 public void setGpsTime(Date gpsTime) { 113 132 this.gpsTime = gpsTime; 114 133 } … … 183 202 " [tmp] pos = "+tmp.pos+""); 184 203 return result; 185 204 } 205 206 /** 207 * Indicate that the image has new GPS data. That flag is used 208 * e.g. by the photo_geotagging plug-in to decide for which image 209 * file the EXIF GPS data needs to be (re-)written. 210 */ 211 public void flagNewGpsData() { 212 isNewGpsData = true; 213 /* We need to set the GPS time to tell the system (mainly the 214 * photo_geotagging plug-in) that the GPS data has changed. 215 * Check for existing GPS time and take EXIF time otherwise. 216 * This can be removed once isNewGpsData is used instead of 217 * the GPS time. 218 */ 219 if (gpsTime == null) { 220 Date gpsTime = getExifGpsTime(); 221 if (gpsTime == null) { 222 gpsTime = getExifTime(); 223 if (gpsTime == null) { 224 /* Time still not set, take the current time. */ 225 gpsTime = new Date(); 226 } 227 } 228 setGpsTime(gpsTime); 229 } 230 if (tmp != null && tmp.getGpsTime() == null) { 231 /* tmp.gpsTime overrides gpsTime, so we set it too. */ 232 tmp.setGpsTime(getGpsTime()); 233 } 234 } 235 236 /** 237 * Query whether the GPS data changed. 238 * @return {@code true} if GPS data changed, {@code false} otherwise 239 */ 240 public boolean hasNewGpsData() { 241 return isNewGpsData; 242 } 186 243 } -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
213 213 private ImageEntry currentEntry = null; 214 214 215 215 public void displayImage(GeoImageLayer layer, ImageEntry entry) { 216 boolean imageChanged; 217 216 218 synchronized(this) { 217 219 // TODO: pop up image dialog but don't load image again 218 220 221 imageChanged = currentEntry != entry; 222 219 223 if (centerView && Main.isDisplayingMapView() && entry != null && entry.getPos() != null) { 220 224 Main.map.mapView.zoomTo(entry.getPos()); 221 225 } … … 225 229 } 226 230 227 231 if (entry != null) { 228 imgDisplay.setImage(entry.getFile(), entry.getExifOrientation()); 232 if (imageChanged) { 233 /* Set only if the image is new to preserve zoom and 234 * position if the same image is redisplayed (e.g. to 235 * update the OSD). */ 236 imgDisplay.setImage(entry.getFile(), entry.getExifOrientation()); 237 } 229 238 setTitle("Geotagged Images" + (entry.getFile() != null ? " - " + entry.getFile().getName() : "")); 230 239 StringBuffer osd = new StringBuffer(entry.getFile() != null ? entry.getFile().getName() : ""); 231 240 if (entry.getElevation() != null) { … … 301 310 public boolean hasImage() { 302 311 return currentEntry != null; 303 312 } 313 314 /** 315 * Returns the currently displayed image. 316 * @return Currently displayed image or {@code null} 317 */ 318 public static ImageEntry getCurrentImage() { 319 return getInstance().currentEntry; 320 } 321 322 /** 323 * Returns the layer associated with the image. 324 * @return Layer associated with the image 325 */ 326 public static GeoImageLayer getCurrentLayer() { 327 return getInstance().currentLayer; 328 } 304 329 } -
src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java
65 65 Element layerElem = support.createElement("layer"); 66 66 layerElem.setAttribute("type", "geoimage"); 67 67 layerElem.setAttribute("version", "0.1"); 68 addAttr("show-thumbnails", Boolean.toString(layer.isUseThumbs()), layerElem, support); 68 69 69 70 for (ImageEntry entry : layer.getImages()) { 70 71 … … 100 101 if (entry.getExifTime() != null) { 101 102 addAttr("exif-time", Long.toString(entry.getExifTime().getTime()), imgElem, support); 102 103 } 104 if (entry.getExifGpsTime() != null) { 105 addAttr("exif-gps-time", Long.toString(entry.getExifGpsTime().getTime()), imgElem, support); 106 } 103 107 if (entry.getExifCoor() != null) { 104 108 Element posElem = support.createElement("exif-coordinates"); 105 109 posElem.setAttribute("lat", Double.toString(entry.getExifCoor().lat())); … … 109 113 if (entry.getExifImgDir() != null) { 110 114 addAttr("exif-image-direction", entry.getExifImgDir().toString(), imgElem, support); 111 115 } 116 if (entry.hasNewGpsData()) { 117 addAttr("is-new-gps-data", Boolean.toString(entry.hasNewGpsData()), imgElem, support); 118 } 112 119 113 120 layerElem.appendChild(imgElem); 114 121 } -
src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java
31 31 32 32 List<ImageEntry> entries = new ArrayList<ImageEntry>(); 33 33 NodeList imgNodes = elem.getChildNodes(); 34 boolean useThumbs = false; 34 35 for (int i=0; i<imgNodes.getLength(); ++i) { 35 36 Node imgNode = imgNodes.item(i); 36 37 if (imgNode.getNodeType() == Node.ELEMENT_NODE) { … … 55 56 entry.setElevation(Double.parseDouble(attrElem.getTextContent())); 56 57 } else if (attrElem.getTagName().equals("gps-time")) { 57 58 entry.setGpsTime(new Date(Long.parseLong(attrElem.getTextContent()))); 58 } else if (attrElem.getTagName().equals(" gps-orientation")) {59 } else if (attrElem.getTagName().equals("exif-orientation")) { 59 60 entry.setExifOrientation(Integer.parseInt(attrElem.getTextContent())); 60 61 } else if (attrElem.getTagName().equals("exif-time")) { 61 62 entry.setExifTime(new Date(Long.parseLong(attrElem.getTextContent()))); 63 } else if (attrElem.getTagName().equals("exif-gps-time")) { 64 entry.setExifGpsTime(new Date(Long.parseLong(attrElem.getTextContent()))); 62 65 } else if (attrElem.getTagName().equals("exif-coordinates")) { 63 66 double lat = Double.parseDouble(attrElem.getAttribute("lat")); 64 67 double lon = Double.parseDouble(attrElem.getAttribute("lon")); 65 68 entry.setExifCoor(new LatLon(lat, lon)); 66 69 } else if (attrElem.getTagName().equals("exif-image-direction")) { 67 70 entry.setExifImgDir(Double.parseDouble(attrElem.getTextContent())); 71 } else if (attrElem.getTagName().equals("is-new-gps-data")) { 72 if (Boolean.parseBoolean(attrElem.getTextContent())) { 73 entry.flagNewGpsData(); 74 } 68 75 } 69 76 // TODO: handle thumbnail loading 70 77 } catch (NumberFormatException e) { … … 73 80 } 74 81 } 75 82 entries.add(entry); 83 } else if (imgElem.getTagName().equals("show-thumbnails")) { 84 useThumbs = Boolean.parseBoolean(imgElem.getTextContent()); 76 85 } 77 86 } 78 87 } … … 86 95 } 87 96 } 88 97 89 return new GeoImageLayer(entries, gpxLayer );98 return new GeoImageLayer(entries, gpxLayer, useThumbs); 90 99 } 91 100 92 101 }
