Ticket #9274: GeoImageLayer.patch

File GeoImageLayer.patch, 22.8 KB (added by holgermappt, 12 years ago)

GeoImageLayer patch

  • src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java

     
    12081208                    curImg.tmp.setSpeed(speed);
    12091209                    curImg.tmp.setElevation(curElevation);
    12101210                    curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
     1211                    curImg.flagNewGPSData();
    12111212                    ret++;
    12121213                }
    12131214                i--;
     
    12371238                    curImg.tmp.setElevation(prevElevation + (curElevation - prevElevation) * timeDiff);
    12381239                }
    12391240                curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
     1241                curImg.flagNewGPSData();
    12401242
    12411243                ret++;
    12421244            }
  • src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java

     
    2222import java.text.ParseException;
    2323import java.util.ArrayList;
    2424import java.util.Arrays;
     25import java.util.Calendar;
    2526import java.util.Collection;
    2627import java.util.Collections;
     28import java.util.Date;
     29import java.util.GregorianCalendar;
    2730import java.util.HashSet;
    2831import java.util.LinkedHashSet;
    2932import java.util.LinkedList;
    3033import java.util.List;
    3134import java.util.Set;
     35import java.util.TimeZone;
    3236
    3337import javax.swing.Action;
    3438import javax.swing.Icon;
     
    269273     * @param gpxLayer The associated GPX layer
    270274     */
    271275    public GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer) {
    272         super(tr("Geotagged Images"));
     276        this(data, gpxLayer, null, false);
     277    }
    273278
     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"));
    274311        Collections.sort(data);
    275312        this.data = data;
    276313        this.gpxLayer = gpxLayer;
     314        this.useThumbs = useThumbs;
    277315    }
    278316
    279317    @Override
     
    293331        entries.add(LayerListDialog.getInstance().createShowHideLayerAction());
    294332        entries.add(LayerListDialog.getInstance().createDeleteLayerAction());
    295333        entries.add(new RenameLayerAction(null, this));
     334        //entries.add(LayerListDialog.getInstance().createMergeLayerAction(this));
    296335        entries.add(SeparatorLayerAction.INSTANCE);
    297336        entries.add(new CorrelateGpxWithImages(this));
    298337        if (!menuAdditions.isEmpty()) {
     
    402441        int height = mv.getHeight();
    403442        Rectangle clip = g.getClipBounds();
    404443        if (useThumbs) {
     444            if (!thumbsLoaded) {
     445                loadThumbs();
     446            }
     447
    405448            if (null == offscreenBuffer || offscreenBuffer.getWidth() != width  // reuse the old buffer if possible
    406449                    || offscreenBuffer.getHeight() != height) {
    407450                offscreenBuffer = new BufferedImage(width, height,
     
    457500            if (e.getPos() != null) {
    458501                Point p = mv.getPoint(e.getPos());
    459502
    460                 if (e.thumbnail != null) {
     503                if (useThumbs && e.thumbnail != null) {
    461504                    Dimension d = scaledDimension(e.thumbnail);
    462505                    g.setColor(new Color(128, 0, 0, 122));
    463506                    g.fillRect(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
     
    571614        } catch (Exception ex) { // (CompoundException and other exceptions, e.g. #5271)
    572615            // Do nothing
    573616        }
     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        }
    574662    }
    575663
    576664    public void showNextPhoto() {
     
    667755        }
    668756    }
    669757
     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
    670859    private MouseAdapter mouseAdapter = null;
    671860    private MapModeChangeListener mapModeListener = null;
    672861
     
    674863    public void hookUpMapView() {
    675864        mouseAdapter = new MouseAdapter() {
    676865            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);
    678867            }
    679868            @Override public void mousePressed(MouseEvent e) {
    680869
     
    698887                    }
    699888                    Point p = Main.map.mapView.getPoint(e.getPos());
    700889                    Rectangle r;
    701                     if (e.thumbnail != null) {
     890                    if (useThumbs && e.thumbnail != null) {
    702891                        Dimension d = scaledDimension(e.thumbnail);
    703892                        r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
    704893                    } else {
     
    708897                                icon.getIconHeight());
    709898                    }
    710899                    if (r.contains(ev.getPoint())) {
     900                        clearOtherCurrentPhotos();
    711901                        currentPhoto = i;
    712902                        ImageViewerDialog.showImage(GeoImageLayer.this, e);
    713903                        Main.map.repaint();
     
    720910        mapModeListener = new MapModeChangeListener() {
    721911            @Override
    722912            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)) {
    724914                    Main.map.mapView.addMouseListener(mouseAdapter);
    725915                } else {
    726916                    Main.map.mapView.removeMouseListener(mouseAdapter);
     
    8151005    public void jumpToPreviousMarker() {
    8161006        showPreviousPhoto();
    8171007    }
     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    }
    8181030}
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java

     
    1717    private LatLon exifCoor;
    1818    private Double exifImgDir;
    1919    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;
    2030    Image thumbnail;
    2131
    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     */
    2336    private CachedLatLon pos;
    2437    /** Speed in kilometer per second */
    2538    private Double speed;
     
    7487    public Date getExifTime() {
    7588        return exifTime;
    7689    }
     90    public Date getExifGpsTime() {
     91        return exifGpsTime;
     92    }
    7793    public LatLon getExifCoor() {
    7894        return exifCoor;
    7995    }
     
    109125    public void setExifTime(Date exifTime) {
    110126        this.exifTime = exifTime;
    111127    }
     128    public void setExifGpsTime(Date exifGpsTime) {
     129        this.exifGpsTime = exifGpsTime;
     130    }
    112131    public void setGpsTime(Date gpsTime) {
    113132        this.gpsTime = gpsTime;
    114133    }
     
    183202            " [tmp] pos = "+tmp.pos+"");
    184203        return result;
    185204    }
     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    }
    186243}
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java

     
    213213    private ImageEntry currentEntry = null;
    214214
    215215    public void displayImage(GeoImageLayer layer, ImageEntry entry) {
     216        boolean imageChanged;
     217
    216218        synchronized(this) {
    217219            // TODO: pop up image dialog but don't load image again
    218220
     221            imageChanged = currentEntry != entry;
     222
    219223            if (centerView && Main.isDisplayingMapView() && entry != null && entry.getPos() != null) {
    220224                Main.map.mapView.zoomTo(entry.getPos());
    221225            }
     
    225229        }
    226230
    227231        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            }
    229238            setTitle("Geotagged Images" + (entry.getFile() != null ? " - " + entry.getFile().getName() : ""));
    230239            StringBuffer osd = new StringBuffer(entry.getFile() != null ? entry.getFile().getName() : "");
    231240            if (entry.getElevation() != null) {
     
    301310    public boolean hasImage() {
    302311        return currentEntry != null;
    303312    }
     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    }
    304329}
  • src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java

     
    6565        Element layerElem = support.createElement("layer");
    6666        layerElem.setAttribute("type", "geoimage");
    6767        layerElem.setAttribute("version", "0.1");
     68        addAttr("show-thumbnails", Boolean.toString(layer.isUseThumbs()), layerElem, support);
    6869
    6970        for (ImageEntry entry : layer.getImages()) {
    7071
     
    100101            if (entry.getExifTime() != null) {
    101102                addAttr("exif-time", Long.toString(entry.getExifTime().getTime()), imgElem, support);
    102103            }
     104            if (entry.getExifGpsTime() != null) {
     105                addAttr("exif-gps-time", Long.toString(entry.getExifGpsTime().getTime()), imgElem, support);
     106            }
    103107            if (entry.getExifCoor() != null) {
    104108                Element posElem = support.createElement("exif-coordinates");
    105109                posElem.setAttribute("lat", Double.toString(entry.getExifCoor().lat()));
     
    109113            if (entry.getExifImgDir() != null) {
    110114                addAttr("exif-image-direction", entry.getExifImgDir().toString(), imgElem, support);
    111115            }
     116            if (entry.hasNewGpsData()) {
     117                addAttr("is-new-gps-data", Boolean.toString(entry.hasNewGpsData()), imgElem, support);
     118            }
    112119
    113120            layerElem.appendChild(imgElem);
    114121        }
  • src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java

     
    3131
    3232        List<ImageEntry> entries = new ArrayList<ImageEntry>();
    3333        NodeList imgNodes = elem.getChildNodes();
     34        boolean useThumbs = false;
    3435        for (int i=0; i<imgNodes.getLength(); ++i) {
    3536            Node imgNode = imgNodes.item(i);
    3637            if (imgNode.getNodeType() == Node.ELEMENT_NODE) {
     
    5556                                    entry.setElevation(Double.parseDouble(attrElem.getTextContent()));
    5657                                } else if (attrElem.getTagName().equals("gps-time")) {
    5758                                    entry.setGpsTime(new Date(Long.parseLong(attrElem.getTextContent())));
    58                                 } else if (attrElem.getTagName().equals("gps-orientation")) {
     59                                } else if (attrElem.getTagName().equals("exif-orientation")) {
    5960                                    entry.setExifOrientation(Integer.parseInt(attrElem.getTextContent()));
    6061                                } else if (attrElem.getTagName().equals("exif-time")) {
    6162                                    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())));
    6265                                } else if (attrElem.getTagName().equals("exif-coordinates")) {
    6366                                    double lat = Double.parseDouble(attrElem.getAttribute("lat"));
    6467                                    double lon = Double.parseDouble(attrElem.getAttribute("lon"));
    6568                                    entry.setExifCoor(new LatLon(lat, lon));
    6669                                } else if (attrElem.getTagName().equals("exif-image-direction")) {
    6770                                    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                                    }
    6875                                }
    6976                                // TODO: handle thumbnail loading
    7077                            } catch (NumberFormatException e) {
     
    7380                        }
    7481                    }
    7582                    entries.add(entry);
     83                } else if (imgElem.getTagName().equals("show-thumbnails")) {
     84                    useThumbs = Boolean.parseBoolean(imgElem.getTextContent());
    7685                }
    7786            }
    7887        }
     
    8695            }
    8796        }
    8897
    89         return new GeoImageLayer(entries, gpxLayer);
     98        return new GeoImageLayer(entries, gpxLayer, useThumbs);
    9099    }
    91100
    92101}