Changeset 14205 in josm


Ignore:
Timestamp:
2018-08-30T23:15:06+02:00 (3 weeks ago)
Author:
Don-vip
Message:

fix #16681 - Various enhancements for GPX correlation (patch by Bjoeni, modified)

Location:
trunk
Files:
3 added
4 edited
1 copied
4 moved

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java

    r14204 r14205  
    11// License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.gui.layer.geoimage;
    3 
    4 import java.awt.Image;
     2package org.openstreetmap.josm.data.gpx;
     3
    54import java.io.File;
    65import java.io.IOException;
    7 import java.util.Collections;
    86import java.util.Date;
    97
     
    2523/**
    2624 * Stores info about each image
     25 * @since 14205 (extracted from gui.layer.geoimage.ImageEntry)
    2726 */
    28 public final class ImageEntry implements Comparable<ImageEntry>, Cloneable {
     27public class GpxImageEntry implements Comparable<GpxImageEntry>, Cloneable {
    2928    private File file;
    3029    private Integer exifOrientation;
     
    4039    /** Temporary source of GPS time if not correlated with GPX track. */
    4140    private Date exifGpsTime;
    42     private Image thumbnail;
    4341
    4442    /**
     
    6563     * solution for this, but it works.)
    6664     */
    67     ImageEntry tmp;
    68 
    69     /**
    70      * Constructs a new {@code ImageEntry}.
    71      */
    72     public ImageEntry() {}
    73 
    74     /**
    75      * Constructs a new {@code ImageEntry}.
     65    private GpxImageEntry tmp;
     66
     67    /**
     68     * Constructs a new {@code GpxImageEntry}.
     69     */
     70    public GpxImageEntry() {}
     71
     72    /**
     73     * Constructs a new {@code GpxImageEntry}.
    7674     * @param file Path to image file on disk
    7775     */
    78     public ImageEntry(File file) {
     76    public GpxImageEntry(File file) {
    7977        setFile(file);
    8078    }
    8179
    8280    /**
    83      * Returns width of the image this ImageEntry represents.
    84      * @return width of the image this ImageEntry represents
     81     * Returns width of the image this GpxImageEntry represents.
     82     * @return width of the image this GpxImageEntry represents
    8583     * @since 13220
    8684     */
     
    9088
    9189    /**
    92      * Returns height of the image this ImageEntry represents.
    93      * @return height of the image this ImageEntry represents
     90     * Returns height of the image this GpxImageEntry represents.
     91     * @return height of the image this GpxImageEntry represents
    9492     * @since 13220
    9593     */
     
    219217
    220218    /**
    221      * Determines whether a thumbnail is set
    222      * @return {@code true} if a thumbnail is set
    223      */
    224     public boolean hasThumbnail() {
    225         return thumbnail != null;
    226     }
    227 
    228     /**
    229      * Returns the thumbnail.
    230      * @return the thumbnail
    231      */
    232     public Image getThumbnail() {
    233         return thumbnail;
    234     }
    235 
    236     /**
    237      * Sets the thumbnail.
    238      * @param thumbnail thumbnail
    239      */
    240     public void setThumbnail(Image thumbnail) {
    241         this.thumbnail = thumbnail;
    242     }
    243 
    244     /**
    245      * Loads the thumbnail if it was not loaded yet.
    246      * @see ThumbsLoader
    247      */
    248     public void loadThumbnail() {
    249         if (thumbnail == null) {
    250             new ThumbsLoader(Collections.singleton(this)).run();
    251         }
    252     }
    253 
    254     /**
    255      * Sets the width of this ImageEntry.
    256      * @param width set the width of this ImageEntry
     219     * Sets the width of this GpxImageEntry.
     220     * @param width set the width of this GpxImageEntry
    257221     * @since 13220
    258222     */
     
    262226
    263227    /**
    264      * Sets the height of this ImageEntry.
    265      * @param height set the height of this ImageEntry
     228     * Sets the height of this GpxImageEntry.
     229     * @param height set the height of this GpxImageEntry
    266230     * @since 13220
    267231     */
     
    348312
    349313    @Override
    350     public ImageEntry clone() {
    351         try {
    352             return (ImageEntry) super.clone();
     314    public GpxImageEntry clone() {
     315        try {
     316            return (GpxImageEntry) super.clone();
    353317        } catch (CloneNotSupportedException e) {
    354318            throw new IllegalStateException(e);
     
    357321
    358322    @Override
    359     public int compareTo(ImageEntry image) {
     323    public int compareTo(GpxImageEntry image) {
    360324        if (exifTime != null && image.exifTime != null)
    361325            return exifTime.compareTo(image.exifTime);
     
    385349     * @return temporary variable
    386350     */
    387     public ImageEntry getTmp() {
     351    public GpxImageEntry getTmp() {
    388352        if (tmp == null) {
    389353            createTmp();
  • trunk/src/org/openstreetmap/josm/data/gpx/GpxTimeOffset.java

    r14204 r14205  
    11// License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.gui.layer.geoimage;
     2package org.openstreetmap.josm.data.gpx;
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
     
    1414/**
    1515 * Time offset of GPX correlation.
    16  * @since 11914 (extracted from {@link CorrelateGpxWithImages})
     16 * @since 14205 (extracted from {@code CorrelateGpxWithImages})
    1717 */
    18 public final class Offset {
     18public final class GpxTimeOffset {
    1919
    20     static final Offset ZERO = new Offset(0);
     20    /**
     21     * The time offset 0.
     22     */
     23    public static final GpxTimeOffset ZERO = new GpxTimeOffset(0);
    2124    private final long milliseconds;
    2225
    23     private Offset(long milliseconds) {
     26    private GpxTimeOffset(long milliseconds) {
    2427        this.milliseconds = milliseconds;
    2528    }
    2629
    27     static Offset milliseconds(long milliseconds) {
    28         return new Offset(milliseconds);
     30    /**
     31     * Constructs a new {@code GpxTimeOffset} from milliseconds.
     32     * @param milliseconds time offset in milliseconds.
     33     * @return new {@code GpxTimeOffset}
     34     */
     35    public static GpxTimeOffset milliseconds(long milliseconds) {
     36        return new GpxTimeOffset(milliseconds);
    2937    }
    3038
    31     static Offset seconds(long seconds) {
    32         return new Offset(1000 * seconds);
     39    /**
     40     * Constructs a new {@code GpxTimeOffset} from seconds.
     41     * @param seconds time offset in seconds.
     42     * @return new {@code GpxTimeOffset}
     43     */
     44    public static GpxTimeOffset seconds(long seconds) {
     45        return new GpxTimeOffset(1000 * seconds);
    3346    }
    3447
    35     long getMilliseconds() {
     48    /**
     49     * Get time offset in milliseconds.
     50     * @return time offset in milliseconds
     51     */
     52    public long getMilliseconds() {
    3653        return milliseconds;
    3754    }
    3855
    39     long getSeconds() {
     56    /**
     57     * Get time offset in seconds.
     58     * @return time offset in seconds
     59     */
     60    public long getSeconds() {
    4061        return milliseconds / 1000;
    4162    }
    4263
    43     String formatOffset() {
     64    /**
     65     * Formats time offset.
     66     * @return formatted time offset. Format: decimal number
     67     */
     68    public String formatOffset() {
    4469        if (milliseconds % 1000 == 0) {
    4570            return Long.toString(milliseconds / 1000);
     
    5176    }
    5277
    53     static Offset parseOffset(String offset) throws ParseException {
     78    /**
     79     * Parses time offset.
     80     * @param offset time offset. Format: decimal number
     81     * @return time offset
     82     * @throws ParseException if time offset can't be parsed
     83     */
     84    public static GpxTimeOffset parseOffset(String offset) throws ParseException {
    5485        String error = tr("Error while parsing offset.\nExpected format: {0}", "number");
    5586
     
    5990                    offset = offset.substring(1);
    6091                }
    61                 return Offset.milliseconds(Math.round(JosmDecimalFormatSymbolsProvider.parseDouble(offset) * 1000));
     92                return GpxTimeOffset.milliseconds(Math.round(JosmDecimalFormatSymbolsProvider.parseDouble(offset) * 1000));
    6293            } catch (NumberFormatException nfe) {
    6394                throw (ParseException) new ParseException(error, 0).initCause(nfe);
    6495            }
    6596        } else {
    66             return Offset.ZERO;
     97            return GpxTimeOffset.ZERO;
    6798        }
    6899    }
    69100
    70     int getDayOffset() {
     101    /**
     102     * Returns the day difference.
     103     * @return the day difference
     104     */
     105    public int getDayOffset() {
    71106        // Find day difference
    72107        return (int) Math.round(((double) getMilliseconds()) / TimeUnit.DAYS.toMillis(1));
    73108    }
    74109
    75     Offset withoutDayOffset() {
     110    /**
     111     * Returns offset without day difference.
     112     * @return offset without day difference
     113     */
     114    public GpxTimeOffset withoutDayOffset() {
    76115        return milliseconds(getMilliseconds() - TimeUnit.DAYS.toMillis(getDayOffset()));
    77116    }
    78117
    79     Pair<Timezone, Offset> splitOutTimezone() {
     118    /**
     119     * Split out timezone and offset.
     120     * @return pair of timezone and offset
     121     */
     122    public Pair<GpxTimezone, GpxTimeOffset> splitOutTimezone() {
    80123        // In hours
    81124        final double tz = ((double) withoutDayOffset().getSeconds()) / TimeUnit.HOURS.toSeconds(1);
     
    85128        final double timezone = (double) Math.round(tz * 2) / 2; // hours, rounded to one decimal place
    86129        final long delta = Math.round(getMilliseconds() - timezone * TimeUnit.HOURS.toMillis(1));
    87         return Pair.create(new Timezone(timezone), Offset.milliseconds(delta));
     130        return Pair.create(new GpxTimezone(timezone), GpxTimeOffset.milliseconds(delta));
    88131    }
    89132
     
    91134    public boolean equals(Object o) {
    92135        if (this == o) return true;
    93         if (!(o instanceof Offset)) return false;
    94         Offset offset = (Offset) o;
     136        if (!(o instanceof GpxTimeOffset)) return false;
     137        GpxTimeOffset offset = (GpxTimeOffset) o;
    95138        return milliseconds == offset.milliseconds;
    96139    }
  • trunk/src/org/openstreetmap/josm/data/gpx/GpxTimezone.java

    r14204 r14205  
    11// License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.gui.layer.geoimage;
     2package org.openstreetmap.josm.data.gpx;
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
     
    66import java.text.ParseException;
    77import java.util.Objects;
     8import java.util.regex.Matcher;
     9import java.util.regex.Pattern;
    810
    911/**
    1012 * Timezone in hours.<p>
    1113 * TODO: should probably be replaced by {@link java.util.TimeZone}.
    12  * @since 11914 (extracted from {@link CorrelateGpxWithImages})
     14 * @since 14205 (extracted from {@code CorrelateGpxWithImages})
    1315 */
    14 public final class Timezone {
     16public final class GpxTimezone {
    1517
    16     static final Timezone ZERO = new Timezone(0.0);
     18    /**
     19     * The timezone 0.
     20     */
     21    public static final GpxTimezone ZERO = new GpxTimezone(0.0);
    1722    private final double timezone;
    1823
    19     Timezone(double hours) {
     24    /**
     25     * Construcs a new {@code GpxTimezone}.
     26     * @param hours timezone in hours
     27     */
     28    public GpxTimezone(double hours) {
    2029        this.timezone = hours;
    2130    }
     
    2938    }
    3039
    31     String formatTimezone() {
     40    /**
     41     * Formats time zone.
     42     * @return formatted time zone. Format: ±HH:MM
     43     */
     44    public String formatTimezone() {
    3245        StringBuilder ret = new StringBuilder();
    3346
     
    4962    }
    5063
    51     static Timezone parseTimezone(String timezone) throws ParseException {
    52 
     64    /**
     65     * Parses timezone.
     66     * @param timezone timezone. Expected format: ±HH:MM
     67     * @return timezone
     68     * @throws ParseException if timezone can't be parsed
     69     */
     70    public static GpxTimezone parseTimezone(String timezone) throws ParseException {
    5371        if (timezone.isEmpty())
    5472            return ZERO;
    5573
    56         String error = tr("Error while parsing timezone.\nExpected format: {0}", "+H:MM");
     74        Matcher m = Pattern.compile("^([\\+\\-]?)(\\d{1,2})(?:\\:([0-5]\\d))?$").matcher(timezone);
    5775
    58         char sgnTimezone = '+';
    59         StringBuilder hTimezone = new StringBuilder();
    60         StringBuilder mTimezone = new StringBuilder();
    61         int state = 1; // 1=start/sign, 2=hours, 3=minutes.
    62         for (int i = 0; i < timezone.length(); i++) {
    63             char c = timezone.charAt(i);
    64             switch (c) {
    65                 case ' ':
    66                     if (state != 2 || hTimezone.length() != 0)
    67                         throw new ParseException(error, i);
    68                     break;
    69                 case '+':
    70                 case '-':
    71                     if (state == 1) {
    72                         sgnTimezone = c;
    73                         state = 2;
    74                     } else
    75                         throw new ParseException(error, i);
    76                     break;
    77                 case ':':
    78                 case '.':
    79                     if (state == 2) {
    80                         state = 3;
    81                     } else
    82                         throw new ParseException(error, i);
    83                     break;
    84                 case '0':
    85                 case '1':
    86                 case '2':
    87                 case '3':
    88                 case '4':
    89                 case '5':
    90                 case '6':
    91                 case '7':
    92                 case '8':
    93                 case '9':
    94                     switch (state) {
    95                         case 1:
    96                         case 2:
    97                             state = 2;
    98                             hTimezone.append(c);
    99                             break;
    100                         case 3:
    101                             mTimezone.append(c);
    102                             break;
    103                         default:
    104                             throw new ParseException(error, i);
    105                     }
    106                     break;
    107                 default:
    108                     throw new ParseException(error, i);
     76        ParseException pe = new ParseException(tr("Error while parsing timezone.\nExpected format: {0}", "±HH:MM"), 0);
     77        try {
     78            if (m.find()) {
     79                int sign = "-".equals(m.group(1)) ? -1 : 1;
     80                int hour = Integer.parseInt(m.group(2));
     81                int min = m.group(3) == null ? 0 : Integer.parseInt(m.group(3));
     82                return new GpxTimezone(sign * (hour + min / 60.0));
    10983            }
     84        } catch (IndexOutOfBoundsException | NumberFormatException ex) {
     85            pe.initCause(ex);
    11086        }
    111 
    112         int h = 0;
    113         int m = 0;
    114         try {
    115             h = Integer.parseInt(hTimezone.toString());
    116             if (mTimezone.length() > 0) {
    117                 m = Integer.parseInt(mTimezone.toString());
    118             }
    119         } catch (NumberFormatException nfe) {
    120             // Invalid timezone
    121             throw (ParseException) new ParseException(error, 0).initCause(nfe);
    122         }
    123 
    124         if (h > 12 || m > 59)
    125             throw new ParseException(error, 0);
    126         else
    127             return new Timezone((h + m / 60.0) * (sgnTimezone == '-' ? -1 : 1));
     87        throw pe;
    12888    }
    12989
     
    13191    public boolean equals(Object o) {
    13292        if (this == o) return true;
    133         if (!(o instanceof Timezone)) return false;
    134         Timezone timezone1 = (Timezone) o;
     93        if (!(o instanceof GpxTimezone)) return false;
     94        GpxTimezone timezone1 = (GpxTimezone) o;
    13595        return Double.compare(timezone1.timezone, timezone) == 0;
    13696    }
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java

    r14153 r14205  
    66
    77import java.awt.BorderLayout;
     8import java.awt.Component;
    89import java.awt.Cursor;
    910import java.awt.Dimension;
     
    3536import java.util.Hashtable;
    3637import java.util.List;
     38import java.util.Objects;
    3739import java.util.Optional;
    3840import java.util.TimeZone;
     
    4446import javax.swing.JButton;
    4547import javax.swing.JCheckBox;
     48import javax.swing.JComponent;
    4649import javax.swing.JFileChooser;
    4750import javax.swing.JLabel;
     
    5255import javax.swing.JSeparator;
    5356import javax.swing.JSlider;
     57import javax.swing.JSpinner;
    5458import javax.swing.ListSelectionModel;
    5559import javax.swing.MutableComboBoxModel;
     60import javax.swing.SpinnerNumberModel;
    5661import javax.swing.SwingConstants;
     62import javax.swing.border.Border;
    5763import javax.swing.event.ChangeEvent;
    5864import javax.swing.event.ChangeListener;
     
    6268import org.openstreetmap.josm.actions.DiskAccessAction;
    6369import org.openstreetmap.josm.actions.ExtensionFileFilter;
    64 import org.openstreetmap.josm.data.gpx.GpxConstants;
    6570import org.openstreetmap.josm.data.gpx.GpxData;
     71import org.openstreetmap.josm.data.gpx.GpxImageCorrelation;
     72import org.openstreetmap.josm.data.gpx.GpxTimeOffset;
     73import org.openstreetmap.josm.data.gpx.GpxTimezone;
    6674import org.openstreetmap.josm.data.gpx.GpxTrack;
    6775import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
     
    7583import org.openstreetmap.josm.gui.layer.GpxLayer;
    7684import org.openstreetmap.josm.gui.layer.Layer;
     85import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
     86import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
     87import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
     88import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
    7789import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
    7890import org.openstreetmap.josm.gui.widgets.FileChooserManager;
     
    8496import org.openstreetmap.josm.io.nmea.NmeaReader;
    8597import org.openstreetmap.josm.spi.preferences.Config;
     98import org.openstreetmap.josm.spi.preferences.IPreferences;
    8699import org.openstreetmap.josm.tools.GBC;
    87100import org.openstreetmap.josm.tools.ImageProvider;
     
    101114
    102115    private final transient GeoImageLayer yLayer;
    103     private transient Timezone timezone;
    104     private transient Offset delta;
     116    private transient GpxTimezone timezone;
     117    private transient GpxTimeOffset delta;
     118    private static boolean forceTags = false;
    105119
    106120    /**
     
    112126        new ImageProvider("dialogs/geoimage/gpx2img").getResource().attachImageIcon(this, true);
    113127        this.yLayer = layer;
     128        MainApplication.getLayerManager().addLayerChangeListener(new GpxLayerAddedListener());
    114129    }
    115130
     
    130145            // Parse values again, to display an error if the format is not recognized
    131146            try {
    132                 timezone = Timezone.parseTimezone(tfTimezone.getText().trim());
     147                timezone = GpxTimezone.parseTimezone(tfTimezone.getText().trim());
    133148            } catch (ParseException e) {
    134149                JOptionPane.showMessageDialog(MainApplication.getMainFrame(), e.getMessage(),
     
    138153
    139154            try {
    140                 delta = Offset.parseOffset(tfOffset.getText().trim());
     155                delta = GpxTimeOffset.parseOffset(tfOffset.getText().trim());
    141156            } catch (ParseException e) {
    142157                JOptionPane.showMessageDialog(MainApplication.getMainFrame(), e.getMessage(),
     
    323338    }
    324339
     340    private class AdvancedSettingsActionListener implements ActionListener {
     341
     342        private class CheckBoxActionListener implements ActionListener {
     343            private final JComponent[] comps;
     344
     345            CheckBoxActionListener(JComponent... c) {
     346                comps = Objects.requireNonNull(c);
     347            }
     348
     349            @Override
     350            public void actionPerformed(ActionEvent e) {
     351                setEnabled((JCheckBox) e.getSource());
     352            }
     353
     354            public void setEnabled(JCheckBox cb) {
     355                for (JComponent comp : comps) {
     356                    if (comp instanceof JSpinner) {
     357                        comp.setEnabled(cb.isSelected());
     358                    } else if (comp instanceof JPanel) {
     359                        boolean en = cb.isSelected();
     360                        for (Component c : comp.getComponents()) {
     361                            if (c instanceof JSpinner) {
     362                                c.setEnabled(en);
     363                            } else {
     364                                c.setEnabled(cb.isSelected());
     365                                if (en && c instanceof JCheckBox) {
     366                                    en = ((JCheckBox) c).isSelected();
     367                                }
     368                            }
     369                        }
     370                    }
     371                }
     372            }
     373        }
     374
     375        private void addCheckBoxActionListener(JCheckBox cb, JComponent... c) {
     376            CheckBoxActionListener listener = new CheckBoxActionListener(c);
     377            cb.addActionListener(listener);
     378            listener.setEnabled(cb);
     379        }
     380
     381        @Override
     382        public void actionPerformed(ActionEvent e) {
     383
     384            IPreferences s = Config.getPref();
     385            JPanel p = new JPanel(new GridBagLayout());
     386
     387            Border border1 = BorderFactory.createEmptyBorder(0, 20, 0, 0);
     388            Border border2 = BorderFactory.createEmptyBorder(10, 0, 5, 0);
     389            Border border = BorderFactory.createEmptyBorder(0, 40, 0, 0);
     390            FlowLayout layout = new FlowLayout();
     391
     392            JLabel l = new JLabel(tr("Segment settings"));
     393            l.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
     394            p.add(l, GBC.eol());
     395            JCheckBox cInterpolSeg = new JCheckBox(tr("Interpolate between segments"), s.getBoolean("geoimage.seg.int", true));
     396            cInterpolSeg.setBorder(border1);
     397            p.add(cInterpolSeg, GBC.eol());
     398
     399            JCheckBox cInterpolSegTime = new JCheckBox(tr("only when the segments are less than # minutes apart:"),
     400                    s.getBoolean("geoimage.seg.int.time", true));
     401            JSpinner sInterpolSegTime = new JSpinner(
     402                    new SpinnerNumberModel(s.getInt("geoimage.seg.int.time.val", 60), 0, Integer.MAX_VALUE, 1));
     403            ((JSpinner.DefaultEditor) sInterpolSegTime.getEditor()).getTextField().setColumns(3);
     404            JPanel pInterpolSegTime = new JPanel(layout);
     405            pInterpolSegTime.add(cInterpolSegTime);
     406            pInterpolSegTime.add(sInterpolSegTime);
     407            pInterpolSegTime.setBorder(border);
     408            p.add(pInterpolSegTime, GBC.eol());
     409
     410            JCheckBox cInterpolSegDist = new JCheckBox(tr("only when the segments are less than # meters apart:"),
     411                    s.getBoolean("geoimage.seg.int.dist", true));
     412            JSpinner sInterpolSegDist = new JSpinner(
     413                    new SpinnerNumberModel(s.getInt("geoimage.seg.int.dist.val", 50), 0, Integer.MAX_VALUE, 1));
     414            ((JSpinner.DefaultEditor) sInterpolSegDist.getEditor()).getTextField().setColumns(3);
     415            JPanel pInterpolSegDist = new JPanel(layout);
     416            pInterpolSegDist.add(cInterpolSegDist);
     417            pInterpolSegDist.add(sInterpolSegDist);
     418            pInterpolSegDist.setBorder(border);
     419            p.add(pInterpolSegDist, GBC.eol());
     420
     421            JCheckBox cTagSeg = new JCheckBox(tr("Tag images at the closest end of a segment, when not interpolated"),
     422                    s.getBoolean("geoimage.seg.tag", true));
     423            cTagSeg.setBorder(border1);
     424            p.add(cTagSeg, GBC.eol());
     425
     426            JCheckBox cTagSegTime = new JCheckBox(tr("only within # minutes of the closest trackpoint:"),
     427                    s.getBoolean("geoimage.seg.tag.time", true));
     428            JSpinner sTagSegTime = new JSpinner(
     429                    new SpinnerNumberModel(s.getInt("geoimage.seg.tag.time.val", 2), 0, Integer.MAX_VALUE, 1));
     430            ((JSpinner.DefaultEditor) sTagSegTime.getEditor()).getTextField().setColumns(3);
     431            JPanel pTagSegTime = new JPanel(layout);
     432            pTagSegTime.add(cTagSegTime);
     433            pTagSegTime.add(sTagSegTime);
     434            pTagSegTime.setBorder(border);
     435            p.add(pTagSegTime, GBC.eol());
     436
     437            l = new JLabel(tr("Track settings (note that multiple tracks can be in one GPX file)"));
     438            l.setBorder(border2);
     439            p.add(l, GBC.eol());
     440            JCheckBox cInterpolTrack = new JCheckBox(tr("Interpolate between tracks"), s.getBoolean("geoimage.trk.int", false));
     441            cInterpolTrack.setBorder(border1);
     442            p.add(cInterpolTrack, GBC.eol());
     443
     444            JCheckBox cInterpolTrackTime = new JCheckBox(tr("only when the tracks are less than # minutes apart:"),
     445                    s.getBoolean("geoimage.trk.int.time", false));
     446            JSpinner sInterpolTrackTime = new JSpinner(
     447                    new SpinnerNumberModel(s.getInt("geoimage.trk.int.time.val", 60), 0, Integer.MAX_VALUE, 1));
     448            ((JSpinner.DefaultEditor) sInterpolTrackTime.getEditor()).getTextField().setColumns(3);
     449            JPanel pInterpolTrackTime = new JPanel(layout);
     450            pInterpolTrackTime.add(cInterpolTrackTime);
     451            pInterpolTrackTime.add(sInterpolTrackTime);
     452            pInterpolTrackTime.setBorder(border);
     453            p.add(pInterpolTrackTime, GBC.eol());
     454
     455            JCheckBox cInterpolTrackDist = new JCheckBox(tr("only when the tracks are less than # meters apart:"),
     456                    s.getBoolean("geoimage.trk.int.dist", false));
     457            JSpinner sInterpolTrackDist = new JSpinner(
     458                    new SpinnerNumberModel(s.getInt("geoimage.trk.int.dist.val", 50), 0, Integer.MAX_VALUE, 1));
     459            ((JSpinner.DefaultEditor) sInterpolTrackDist.getEditor()).getTextField().setColumns(3);
     460            JPanel pInterpolTrackDist = new JPanel(layout);
     461            pInterpolTrackDist.add(cInterpolTrackDist);
     462            pInterpolTrackDist.add(sInterpolTrackDist);
     463            pInterpolTrackDist.setBorder(border);
     464            p.add(pInterpolTrackDist, GBC.eol());
     465
     466            JCheckBox cTagTrack = new JCheckBox("<html>" +
     467                    tr("Tag images at the closest end of a track, when not interpolated<br>" +
     468                    "(also applies before the first and after the last track)") + "</html>",
     469                    s.getBoolean("geoimage.trk.tag", true));
     470            cTagTrack.setBorder(border1);
     471            p.add(cTagTrack, GBC.eol());
     472
     473            JCheckBox cTagTrackTime = new JCheckBox(tr("only within # minutes of the closest trackpoint:"),
     474                    s.getBoolean("geoimage.trk.tag.time", true));
     475            JSpinner sTagTrackTime = new JSpinner(
     476                    new SpinnerNumberModel(s.getInt("geoimage.trk.tag.time.val", 2), 0, Integer.MAX_VALUE, 1));
     477            ((JSpinner.DefaultEditor) sTagTrackTime.getEditor()).getTextField().setColumns(3);
     478            JPanel pTagTrackTime = new JPanel(layout);
     479            pTagTrackTime.add(cTagTrackTime);
     480            pTagTrackTime.add(sTagTrackTime);
     481            pTagTrackTime.setBorder(border);
     482            p.add(pTagTrackTime, GBC.eol());
     483
     484            l = new JLabel(tr("Advanced"));
     485            l.setBorder(border2);
     486            p.add(l, GBC.eol());
     487            JCheckBox cForce = new JCheckBox("<html>" +
     488                    tr("Force tagging of all pictures (temporarily overrides the settings above).") + "<br>" +
     489                    tr("This option will not be saved permanently.") + "</html>", forceTags);
     490            cForce.setBorder(BorderFactory.createEmptyBorder(0, 20, 10, 0));
     491            p.add(cForce, GBC.eol());
     492
     493            addCheckBoxActionListener(cInterpolSegTime, sInterpolSegTime);
     494            addCheckBoxActionListener(cInterpolSegDist, sInterpolSegDist);
     495            addCheckBoxActionListener(cInterpolSeg, pInterpolSegTime, pInterpolSegDist);
     496
     497            addCheckBoxActionListener(cTagSegTime, sTagSegTime);
     498            addCheckBoxActionListener(cTagSeg, pTagSegTime);
     499
     500            addCheckBoxActionListener(cInterpolTrackTime, sInterpolTrackTime);
     501            addCheckBoxActionListener(cInterpolTrackDist, sInterpolTrackDist);
     502            addCheckBoxActionListener(cInterpolTrack, pInterpolTrackTime, pInterpolTrackDist);
     503
     504            addCheckBoxActionListener(cTagTrackTime, sTagTrackTime);
     505            addCheckBoxActionListener(cTagTrack, pTagTrackTime);
     506
     507
     508            ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(), tr("Advanced settings"), tr("OK"), tr("Cancel"))
     509                            .setButtonIcons("ok", "cancel").setContent(p);
     510            if (ed.showDialog().getValue() == 1) {
     511
     512                s.putBoolean("geoimage.seg.int", cInterpolSeg.isSelected());
     513                s.putBoolean("geoimage.seg.int.dist", cInterpolSegDist.isSelected());
     514                s.putInt("geoimage.seg.int.dist.val", (int) sInterpolSegDist.getValue());
     515                s.putBoolean("geoimage.seg.int.time", cInterpolSegTime.isSelected());
     516                s.putInt("geoimage.seg.int.time.val", (int) sInterpolSegTime.getValue());
     517                s.putBoolean("geoimage.seg.tag", cTagSeg.isSelected());
     518                s.putBoolean("geoimage.seg.tag.time", cTagSegTime.isSelected());
     519                s.putInt("geoimage.seg.tag.time.val", (int) sTagSegTime.getValue());
     520
     521                s.putBoolean("geoimage.trk.int", cInterpolTrack.isSelected());
     522                s.putBoolean("geoimage.trk.int.dist", cInterpolTrackDist.isSelected());
     523                s.putInt("geoimage.trk.int.dist.val", (int) sInterpolTrackDist.getValue());
     524                s.putBoolean("geoimage.trk.int.time", cInterpolTrackTime.isSelected());
     525                s.putInt("geoimage.trk.int.time.val", (int) sInterpolTrackTime.getValue());
     526                s.putBoolean("geoimage.trk.tag", cTagTrack.isSelected());
     527                s.putBoolean("geoimage.trk.tag.time", cTagTrackTime.isSelected());
     528                s.putInt("geoimage.trk.tag.time.val", (int) sTagTrackTime.getValue());
     529
     530                forceTags = cForce.isSelected(); // This setting is not supposed to be saved permanently
     531
     532                statusBarUpdater.updateStatusBar();
     533                yLayer.updateBufferAndRepaint();
     534            }
     535        }
     536    }
     537
    325538    /**
    326539     * This action listener is called when the user has a photo of the time of his GPS receiver. It
     
    399612
    400613                String tzDesc = tzStr + " (" +
    401                         new Timezone(((double) tz.getRawOffset()) / TimeUnit.HOURS.toMillis(1)).formatTimezone() +
     614                        new GpxTimezone(((double) tz.getRawOffset()) / TimeUnit.HOURS.toMillis(1)).formatTimezone() +
    402615                        ')';
    403616                vtTimezones.add(tzDesc);
     
    417630
    418631            cbTimezones.setSelectedItem(defaultTz.getID() + " (" +
    419                     new Timezone(((double) defaultTz.getRawOffset()) / TimeUnit.HOURS.toMillis(1)).formatTimezone() +
     632                    new GpxTimezone(((double) defaultTz.getRawOffset()) / TimeUnit.HOURS.toMillis(1)).formatTimezone() +
    420633                    ')');
    421634
     
    515728
    516729                Config.getPref().put("geoimage.timezoneid", tzId);
    517                 tfOffset.setText(Offset.milliseconds(delta).formatOffset());
     730                tfOffset.setText(GpxTimeOffset.milliseconds(delta).formatOffset());
    518731                tfTimezone.setText(tzValue);
    519732
     
    524737            yLayer.updateBufferAndRepaint();
    525738        }
     739    }
     740
     741    private class GpxLayerAddedListener implements LayerChangeListener {
     742        @Override
     743        public void layerAdded(LayerAddEvent e) {
     744            if (syncDialog != null && syncDialog.isVisible()) {
     745                Layer layer = e.getAddedLayer();
     746                if (layer instanceof GpxLayer) {
     747                    GpxLayer gpx = (GpxLayer) layer;
     748                    GpxDataWrapper gdw = new GpxDataWrapper(gpx.getName(), gpx.data, gpx.data.storageFile);
     749                    gpxLst.add(gdw);
     750                    MutableComboBoxModel<GpxDataWrapper> model = (MutableComboBoxModel<GpxDataWrapper>) cbGpx.getModel();
     751                    if (gpxLst.get(0).file == null) {
     752                        gpxLst.remove(0);
     753                        model.removeElementAt(0);
     754                    }
     755                    model.addElement(gdw);
     756                }
     757            }
     758        }
     759
     760        @Override
     761        public void layerRemoving(LayerRemoveEvent e) {}
     762
     763        @Override
     764        public void layerOrderChanged(LayerOrderChangeEvent e) {}
    526765    }
    527766
     
    578817
    579818        try {
    580             timezone = Timezone.parseTimezone(Optional.ofNullable(Config.getPref().get("geoimage.timezone", "0:00")).orElse("0:00"));
     819            timezone = GpxTimezone.parseTimezone(Optional.ofNullable(Config.getPref().get("geoimage.timezone", "0:00")).orElse("0:00"));
    581820        } catch (ParseException e) {
    582             timezone = Timezone.ZERO;
     821            timezone = GpxTimezone.ZERO;
     822            Logging.trace(e);
    583823        }
    584824
     
    587827
    588828        try {
    589             delta = Offset.parseOffset(Config.getPref().get("geoimage.delta", "0"));
     829            delta = GpxTimeOffset.parseOffset(Config.getPref().get("geoimage.delta", "0"));
    590830        } catch (ParseException e) {
    591             delta = Offset.ZERO;
     831            delta = GpxTimeOffset.ZERO;
     832            Logging.trace(e);
    592833        }
    593834
     
    606847        JButton buttonAdjust = new JButton(tr("Manual adjust"));
    607848        buttonAdjust.addActionListener(new AdjustActionListener());
     849
     850        JButton buttonAdvanced = new JButton(tr("Advanced settings..."));
     851        buttonAdvanced.addActionListener(new AdvancedSettingsActionListener());
    608852
    609853        JLabel labelPosition = new JLabel(tr("Override position for: "));
     
    668912
    669913        gbc = GBC.std().fill(GBC.BOTH).insets(5, 5, 5, 5);
    670         gbc.gridx = 2;
     914        gbc.gridx = 1;
    671915        gbc.gridy = y++;
    672916        gbc.weightx = 0.5;
     917        panelTf.add(buttonAdvanced, gbc);
     918
     919        gbc.gridx = 2;
    673920        panelTf.add(buttonAutoGuess, gbc);
    674921
     
    716963
    717964        statusBarUpdater.updateStatusBar();
     965        yLayer.updateBufferAndRepaint();
    718966
    719967        outerPanel = new JPanel(new BorderLayout());
     
    7821030        private String statusText() {
    7831031            try {
    784                 timezone = Timezone.parseTimezone(tfTimezone.getText().trim());
    785                 delta = Offset.parseOffset(tfOffset.getText().trim());
     1032                timezone = GpxTimezone.parseTimezone(tfTimezone.getText().trim());
     1033                delta = GpxTimeOffset.parseOffset(tfOffset.getText().trim());
    7861034            } catch (ParseException e) {
    7871035                return e.getMessage();
     
    8011049            for (ImageEntry ie : dateImgLst) {
    8021050                ie.createTmp();
    803                 ie.tmp.setPos(null);
     1051                ie.getTmp().setPos(null);
    8041052            }
    8051053
     
    8081056                return tr("No gpx selected");
    8091057
    810             final long offsetMs = ((long) (timezone.getHours() * TimeUnit.HOURS.toMillis(1))) + delta.getMilliseconds(); // in milliseconds
    811             lastNumMatched = matchGpxTrack(dateImgLst, selGpx.data, offsetMs);
     1058            final long offsetMs = ((long) (timezone.getHours() * TimeUnit.HOURS.toMillis(-1))) + delta.getMilliseconds(); // in milliseconds
     1059            lastNumMatched = GpxImageCorrelation.matchGpxTrack(dateImgLst, selGpx.data, offsetMs, forceTags);
    8121060
    8131061            return trn("<html>Matched <b>{0}</b> of <b>{1}</b> photo to GPX track.</html>",
     
    8381086        public void actionPerformed(ActionEvent arg0) {
    8391087
    840             final Offset offset = Offset.milliseconds(
     1088            final GpxTimeOffset offset = GpxTimeOffset.milliseconds(
    8411089                    delta.getMilliseconds() + Math.round(timezone.getHours() * TimeUnit.HOURS.toMillis(1)));
    8421090            final int dayOffset = offset.getDayOffset();
    843             final Pair<Timezone, Offset> timezoneOffsetPair = offset.withoutDayOffset().splitOutTimezone();
     1091            final Pair<GpxTimezone, GpxTimeOffset> timezoneOffsetPair = offset.withoutDayOffset().splitOutTimezone();
    8441092
    8451093            // Info Labels
     
    8541102            // CHECKSTYLE.OFF: ParenPad
    8551103            for (int i = -12; i <= 12; i += 6) {
    856                 labelTable.put(i * 2, new JLabel(new Timezone(i).formatTimezone()));
     1104                labelTable.put(i * 2, new JLabel(new GpxTimezone(i).formatTimezone()));
    8571105            }
    8581106            // CHECKSTYLE.ON: ParenPad
     
    8721120            // CHECKSTYLE.OFF: ParenPad
    8731121            for (int i = -60; i <= 60; i += 30) {
    874                 labelTable.put(i * 10, new JLabel(Offset.seconds(i).formatOffset()));
     1122                labelTable.put(i * 10, new JLabel(GpxTimeOffset.seconds(i).formatOffset()));
    8751123            }
    8761124            // CHECKSTYLE.ON: ParenPad
     
    8831131                @Override
    8841132                public void stateChanged(ChangeEvent e) {
    885                     timezone = new Timezone(sldTimezone.getValue() / 2.);
     1133                    timezone = new GpxTimezone(sldTimezone.getValue() / 2.);
    8861134
    8871135                    lblTimezone.setText(tr("Timezone: {0}", timezone.formatTimezone()));
    8881136                    lblMinutes.setText(tr("Minutes: {0}", sldMinutes.getValue()));
    889                     lblSeconds.setText(tr("Seconds: {0}", Offset.milliseconds(100L * sldSeconds.getValue()).formatOffset()));
    890 
    891                     delta = Offset.milliseconds(100L * sldSeconds.getValue()
     1137                    lblSeconds.setText(tr("Seconds: {0}", GpxTimeOffset.milliseconds(100L * sldSeconds.getValue()).formatOffset()));
     1138
     1139                    delta = GpxTimeOffset.milliseconds(100L * sldSeconds.getValue()
    8921140                            + TimeUnit.MINUTES.toMillis(sldMinutes.getValue())
    8931141                            + TimeUnit.DAYS.toMillis(dayOffset));
     
    9681216     * @throws NoGpxTimestamps when the gpx track does not contain a timestamp
    9691217     */
    970     static Pair<Timezone, Offset> autoGuess(List<ImageEntry> imgs, GpxData gpx) throws NoGpxTimestamps {
     1218    static Pair<GpxTimezone, GpxTimeOffset> autoGuess(List<ImageEntry> imgs, GpxData gpx) throws NoGpxTimestamps {
    9711219
    9721220        // Init variables
     
    9911239        }
    9921240
    993         return Offset.milliseconds(firstExifDate - firstGPXDate).splitOutTimezone();
     1241        return GpxTimeOffset.milliseconds(firstExifDate - firstGPXDate).splitOutTimezone();
    9941242    }
    9951243
     
    10061254
    10071255            try {
    1008                 final Pair<Timezone, Offset> r = autoGuess(imgs, gpx);
     1256                final Pair<GpxTimezone, GpxTimeOffset> r = autoGuess(imgs, gpx);
    10091257                timezone = r.a;
    10101258                delta = r.b;
     
    10881336    }
    10891337
    1090     /**
    1091      * Match a list of photos to a gpx track with a given offset.
    1092      * All images need a exifTime attribute and the List must be sorted according to these times.
    1093      * @param images images to match
    1094      * @param selectedGpx selected GPX data
    1095      * @param offset offset
    1096      * @return number of matched points
    1097      */
    1098     static int matchGpxTrack(List<ImageEntry> images, GpxData selectedGpx, long offset) {
    1099         int ret = 0;
    1100 
    1101         for (GpxTrack trk : selectedGpx.tracks) {
    1102             for (GpxTrackSegment segment : trk.getSegments()) {
    1103 
    1104                 long prevWpTime = 0;
    1105                 WayPoint prevWp = null;
    1106 
    1107                 for (WayPoint curWp : segment.getWayPoints()) {
    1108                     final Date parsedTime = curWp.setTimeFromAttribute();
    1109                     if (parsedTime != null) {
    1110                         final long curWpTime = parsedTime.getTime() + offset;
    1111                         ret += matchPoints(images, prevWp, prevWpTime, curWp, curWpTime, offset);
    1112 
    1113                         prevWp = curWp;
    1114                         prevWpTime = curWpTime;
    1115                         continue;
    1116                     }
    1117                     prevWp = null;
    1118                     prevWpTime = 0;
    1119                 }
    1120             }
    1121         }
    1122         return ret;
    1123     }
    1124 
    1125     private static Double getElevation(WayPoint wp) {
    1126         String value = wp.getString(GpxConstants.PT_ELE);
    1127         if (value != null && !value.isEmpty()) {
    1128             try {
    1129                 return Double.valueOf(value);
    1130             } catch (NumberFormatException e) {
    1131                 Logging.warn(e);
    1132             }
    1133         }
    1134         return null;
    1135     }
    1136 
    1137     static int matchPoints(List<ImageEntry> images, WayPoint prevWp, long prevWpTime,
    1138             WayPoint curWp, long curWpTime, long offset) {
    1139         // Time between the track point and the previous one, 5 sec if first point, i.e. photos take
    1140         // 5 sec before the first track point can be assumed to be take at the starting position
    1141         long interval = prevWpTime > 0 ? Math.abs(curWpTime - prevWpTime) : TimeUnit.SECONDS.toMillis(5);
    1142         int ret = 0;
    1143 
    1144         // i is the index of the timewise last photo that has the same or earlier EXIF time
    1145         int i = getLastIndexOfListBefore(images, curWpTime);
    1146 
    1147         // no photos match
    1148         if (i < 0)
    1149             return 0;
    1150 
    1151         Double speed = null;
    1152         Double prevElevation = null;
    1153 
    1154         if (prevWp != null) {
    1155             double distance = prevWp.getCoor().greatCircleDistance(curWp.getCoor());
    1156             // This is in km/h, 3.6 * m/s
    1157             if (curWpTime > prevWpTime) {
    1158                 speed = 3600 * distance / (curWpTime - prevWpTime);
    1159             }
    1160             prevElevation = getElevation(prevWp);
    1161         }
    1162 
    1163         Double curElevation = getElevation(curWp);
    1164 
    1165         // First trackpoint, then interval is set to five seconds, i.e. photos up to five seconds
    1166         // before the first point will be geotagged with the starting point
    1167         if (prevWpTime == 0 || curWpTime <= prevWpTime) {
    1168             while (i >= 0) {
    1169                 final ImageEntry curImg = images.get(i);
    1170                 long time = curImg.getExifTime().getTime();
    1171                 if (time > curWpTime || time < curWpTime - interval) {
    1172                     break;
    1173                 }
    1174                 if (curImg.tmp.getPos() == null) {
    1175                     curImg.tmp.setPos(curWp.getCoor());
    1176                     curImg.tmp.setSpeed(speed);
    1177                     curImg.tmp.setElevation(curElevation);
    1178                     curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
    1179                     curImg.tmp.flagNewGpsData();
    1180                     ret++;
    1181                 }
    1182                 i--;
    1183             }
    1184             return ret;
    1185         }
    1186 
    1187         // This code gives a simple linear interpolation of the coordinates between current and
    1188         // previous track point assuming a constant speed in between
    1189         while (i >= 0) {
    1190             ImageEntry curImg = images.get(i);
    1191             long imgTime = curImg.getExifTime().getTime();
    1192             if (imgTime < prevWpTime) {
    1193                 break;
    1194             }
    1195 
    1196             if (prevWp != null && curImg.tmp.getPos() == null) {
    1197                 // The values of timeDiff are between 0 and 1, it is not seconds but a dimensionless variable
    1198                 double timeDiff = (double) (imgTime - prevWpTime) / interval;
    1199                 curImg.tmp.setPos(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff));
    1200                 curImg.tmp.setSpeed(speed);
    1201                 if (curElevation != null && prevElevation != null) {
    1202                     curImg.tmp.setElevation(prevElevation + (curElevation - prevElevation) * timeDiff);
    1203                 }
    1204                 curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
    1205                 curImg.tmp.flagNewGpsData();
    1206 
    1207                 ret++;
    1208             }
    1209             i--;
    1210         }
    1211         return ret;
    1212     }
    1213 
    1214     private static int getLastIndexOfListBefore(List<ImageEntry> images, long searchedTime) {
    1215         int lstSize = images.size();
    1216 
    1217         // No photos or the first photo taken is later than the search period
    1218         if (lstSize == 0 || searchedTime < images.get(0).getExifTime().getTime())
    1219             return -1;
    1220 
    1221         // The search period is later than the last photo
    1222         if (searchedTime > images.get(lstSize - 1).getExifTime().getTime())
    1223             return lstSize-1;
    1224 
    1225         // The searched index is somewhere in the middle, do a binary search from the beginning
    1226         int curIndex;
    1227         int startIndex = 0;
    1228         int endIndex = lstSize-1;
    1229         while (endIndex - startIndex > 1) {
    1230             curIndex = (endIndex + startIndex) / 2;
    1231             if (searchedTime > images.get(curIndex).getExifTime().getTime()) {
    1232                 startIndex = curIndex;
    1233             } else {
    1234                 endIndex = curIndex;
    1235             }
    1236         }
    1237         if (searchedTime < images.get(endIndex).getExifTime().getTime())
    1238             return startIndex;
    1239 
    1240         // This final loop is to check if photos with the exact same EXIF time follows
    1241         while ((endIndex < (lstSize-1)) && (images.get(endIndex).getExifTime().getTime()
    1242                 == images.get(endIndex + 1).getExifTime().getTime())) {
    1243             endIndex++;
    1244         }
    1245         return endIndex;
    1246     }
    12471338}
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java

    r13256 r14205  
    44import java.awt.Image;
    55import java.io.File;
    6 import java.io.IOException;
    76import java.util.Collections;
    8 import java.util.Date;
    97
    10 import org.openstreetmap.josm.data.coor.CachedLatLon;
    11 import org.openstreetmap.josm.data.coor.LatLon;
    12 import org.openstreetmap.josm.tools.ExifReader;
    13 import org.openstreetmap.josm.tools.JosmRuntimeException;
    14 import org.openstreetmap.josm.tools.Logging;
    15 
    16 import com.drew.imaging.jpeg.JpegMetadataReader;
    17 import com.drew.lang.CompoundException;
    18 import com.drew.metadata.Directory;
    19 import com.drew.metadata.Metadata;
    20 import com.drew.metadata.MetadataException;
    21 import com.drew.metadata.exif.ExifIFD0Directory;
    22 import com.drew.metadata.exif.GpsDirectory;
    23 import com.drew.metadata.jpeg.JpegDirectory;
     8import org.openstreetmap.josm.data.gpx.GpxImageEntry;
    249
    2510/**
    26  * Stores info about each image
     11 * Stores info about each image, with an optional thumbnail
     12 * @since 2662
    2713 */
    28 public final class ImageEntry implements Comparable<ImageEntry>, Cloneable {
    29     private File file;
    30     private Integer exifOrientation;
    31     private LatLon exifCoor;
    32     private Double exifImgDir;
    33     private Date exifTime;
    34     /**
    35      * Flag isNewGpsData indicates that the GPS data of the image is new or has changed.
    36      * GPS data includes the position, speed, elevation, time (e.g. as extracted from the GPS track).
    37      * The flag can used to decide for which image file the EXIF GPS data is (re-)written.
    38      */
    39     private boolean isNewGpsData;
    40     /** Temporary source of GPS time if not correlated with GPX track. */
    41     private Date exifGpsTime;
     14public final class ImageEntry extends GpxImageEntry {
     15
    4216    private Image thumbnail;
    43 
    44     /**
    45      * The following values are computed from the correlation with the gpx track
    46      * or extracted from the image EXIF data.
    47      */
    48     private CachedLatLon pos;
    49     /** Speed in kilometer per hour */
    50     private Double speed;
    51     /** Elevation (altitude) in meters */
    52     private Double elevation;
    53     /** The time after correlation with a gpx track */
    54     private Date gpsTime;
    55 
    56     private int width;
    57     private int height;
    58 
    59     /**
    60      * When the correlation dialog is open, we like to show the image position
    61      * for the current time offset on the map in real time.
    62      * On the other hand, when the user aborts this operation, the old values
    63      * should be restored. We have a temporary copy, that overrides
    64      * the normal values if it is not null. (This may be not the most elegant
    65      * solution for this, but it works.)
    66      */
    67     ImageEntry tmp;
    6817
    6918    /**
    7019     * Constructs a new {@code ImageEntry}.
    7120     */
    72     public ImageEntry() {}
     21    public ImageEntry() {
     22    }
    7323
    7424    /**
     
    7727     */
    7828    public ImageEntry(File file) {
    79         setFile(file);
    80     }
    81 
    82     /**
    83      * Returns width of the image this ImageEntry represents.
    84      * @return width of the image this ImageEntry represents
    85      * @since 13220
    86      */
    87     public int getWidth() {
    88         return width;
    89     }
    90 
    91     /**
    92      * Returns height of the image this ImageEntry represents.
    93      * @return height of the image this ImageEntry represents
    94      * @since 13220
    95      */
    96     public int getHeight() {
    97         return height;
    98     }
    99 
    100     /**
    101      * Returns the position value. The position value from the temporary copy
    102      * is returned if that copy exists.
    103      * @return the position value
    104      */
    105     public CachedLatLon getPos() {
    106         if (tmp != null)
    107             return tmp.pos;
    108         return pos;
    109     }
    110 
    111     /**
    112      * Returns the speed value. The speed value from the temporary copy is
    113      * returned if that copy exists.
    114      * @return the speed value
    115      */
    116     public Double getSpeed() {
    117         if (tmp != null)
    118             return tmp.speed;
    119         return speed;
    120     }
    121 
    122     /**
    123      * Returns the elevation value. The elevation value from the temporary
    124      * copy is returned if that copy exists.
    125      * @return the elevation value
    126      */
    127     public Double getElevation() {
    128         if (tmp != null)
    129             return tmp.elevation;
    130         return elevation;
    131     }
    132 
    133     /**
    134      * Returns the GPS time value. The GPS time value from the temporary copy
    135      * is returned if that copy exists.
    136      * @return the GPS time value
    137      */
    138     public Date getGpsTime() {
    139         if (tmp != null)
    140             return getDefensiveDate(tmp.gpsTime);
    141         return getDefensiveDate(gpsTime);
    142     }
    143 
    144     /**
    145      * Convenient way to determine if this entry has a GPS time, without the cost of building a defensive copy.
    146      * @return {@code true} if this entry has a GPS time
    147      * @since 6450
    148      */
    149     public boolean hasGpsTime() {
    150         return (tmp != null && tmp.gpsTime != null) || gpsTime != null;
    151     }
    152 
    153     /**
    154      * Returns associated file.
    155      * @return associated file
    156      */
    157     public File getFile() {
    158         return file;
    159     }
    160 
    161     /**
    162      * Returns EXIF orientation
    163      * @return EXIF orientation
    164      */
    165     public Integer getExifOrientation() {
    166         return exifOrientation != null ? exifOrientation : 1;
    167     }
    168 
    169     /**
    170      * Returns EXIF time
    171      * @return EXIF time
    172      */
    173     public Date getExifTime() {
    174         return getDefensiveDate(exifTime);
    175     }
    176 
    177     /**
    178      * Convenient way to determine if this entry has a EXIF time, without the cost of building a defensive copy.
    179      * @return {@code true} if this entry has a EXIF time
    180      * @since 6450
    181      */
    182     public boolean hasExifTime() {
    183         return exifTime != null;
    184     }
    185 
    186     /**
    187      * Returns the EXIF GPS time.
    188      * @return the EXIF GPS time
    189      * @since 6392
    190      */
    191     public Date getExifGpsTime() {
    192         return getDefensiveDate(exifGpsTime);
    193     }
    194 
    195     /**
    196      * Convenient way to determine if this entry has a EXIF GPS time, without the cost of building a defensive copy.
    197      * @return {@code true} if this entry has a EXIF GPS time
    198      * @since 6450
    199      */
    200     public boolean hasExifGpsTime() {
    201         return exifGpsTime != null;
    202     }
    203 
    204     private static Date getDefensiveDate(Date date) {
    205         if (date == null)
    206             return null;
    207         return new Date(date.getTime());
    208     }
    209 
    210     public LatLon getExifCoor() {
    211         return exifCoor;
    212     }
    213 
    214     public Double getExifImgDir() {
    215         if (tmp != null)
    216             return tmp.exifImgDir;
    217         return exifImgDir;
     29        super(file);
    21830    }
    21931
     
    25163        }
    25264    }
    253 
    254     /**
    255      * Sets the width of this ImageEntry.
    256      * @param width set the width of this ImageEntry
    257      * @since 13220
    258      */
    259     public void setWidth(int width) {
    260         this.width = width;
    261     }
    262 
    263     /**
    264      * Sets the height of this ImageEntry.
    265      * @param height set the height of this ImageEntry
    266      * @since 13220
    267      */
    268     public void setHeight(int height) {
    269         this.height = height;
    270     }
    271 
    272     /**
    273      * Sets the position.
    274      * @param pos cached position
    275      */
    276     public void setPos(CachedLatLon pos) {
    277         this.pos = pos;
    278     }
    279 
    280     /**
    281      * Sets the position.
    282      * @param pos position (will be cached)
    283      */
    284     public void setPos(LatLon pos) {
    285         setPos(pos != null ? new CachedLatLon(pos) : null);
    286     }
    287 
    288     /**
    289      * Sets the speed.
    290      * @param speed speed
    291      */
    292     public void setSpeed(Double speed) {
    293         this.speed = speed;
    294     }
    295 
    296     /**
    297      * Sets the elevation.
    298      * @param elevation elevation
    299      */
    300     public void setElevation(Double elevation) {
    301         this.elevation = elevation;
    302     }
    303 
    304     /**
    305      * Sets associated file.
    306      * @param file associated file
    307      */
    308     public void setFile(File file) {
    309         this.file = file;
    310     }
    311 
    312     /**
    313      * Sets EXIF orientation.
    314      * @param exifOrientation EXIF orientation
    315      */
    316     public void setExifOrientation(Integer exifOrientation) {
    317         this.exifOrientation = exifOrientation;
    318     }
    319 
    320     /**
    321      * Sets EXIF time.
    322      * @param exifTime EXIF time
    323      */
    324     public void setExifTime(Date exifTime) {
    325         this.exifTime = getDefensiveDate(exifTime);
    326     }
    327 
    328     /**
    329      * Sets the EXIF GPS time.
    330      * @param exifGpsTime the EXIF GPS time
    331      * @since 6392
    332      */
    333     public void setExifGpsTime(Date exifGpsTime) {
    334         this.exifGpsTime = getDefensiveDate(exifGpsTime);
    335     }
    336 
    337     public void setGpsTime(Date gpsTime) {
    338         this.gpsTime = getDefensiveDate(gpsTime);
    339     }
    340 
    341     public void setExifCoor(LatLon exifCoor) {
    342         this.exifCoor = exifCoor;
    343     }
    344 
    345     public void setExifImgDir(Double exifDir) {
    346         this.exifImgDir = exifDir;
    347     }
    348 
    349     @Override
    350     public ImageEntry clone() {
    351         try {
    352             return (ImageEntry) super.clone();
    353         } catch (CloneNotSupportedException e) {
    354             throw new IllegalStateException(e);
    355         }
    356     }
    357 
    358     @Override
    359     public int compareTo(ImageEntry image) {
    360         if (exifTime != null && image.exifTime != null)
    361             return exifTime.compareTo(image.exifTime);
    362         else if (exifTime == null && image.exifTime == null)
    363             return 0;
    364         else if (exifTime == null)
    365             return -1;
    366         else
    367             return 1;
    368     }
    369 
    370     /**
    371      * Make a fresh copy and save it in the temporary variable. Use
    372      * {@link #applyTmp()} or {@link #discardTmp()} if the temporary variable
    373      * is not needed anymore.
    374      */
    375     public void createTmp() {
    376         tmp = clone();
    377         tmp.tmp = null;
    378     }
    379 
    380     /**
    381      * Get temporary variable that is used for real time parameter
    382      * adjustments. The temporary variable is created if it does not exist
    383      * yet. Use {@link #applyTmp()} or {@link #discardTmp()} if the temporary
    384      * variable is not needed anymore.
    385      * @return temporary variable
    386      */
    387     public ImageEntry getTmp() {
    388         if (tmp == null) {
    389             createTmp();
    390         }
    391         return tmp;
    392     }
    393 
    394     /**
    395      * Copy the values from the temporary variable to the main instance. The
    396      * temporary variable is deleted.
    397      * @see #discardTmp()
    398      */
    399     public void applyTmp() {
    400         if (tmp != null) {
    401             pos = tmp.pos;
    402             speed = tmp.speed;
    403             elevation = tmp.elevation;
    404             gpsTime = tmp.gpsTime;
    405             exifImgDir = tmp.exifImgDir;
    406             isNewGpsData = tmp.isNewGpsData;
    407             tmp = null;
    408         }
    409     }
    410 
    411     /**
    412      * Delete the temporary variable. Temporary modifications are lost.
    413      * @see #applyTmp()
    414      */
    415     public void discardTmp() {
    416         tmp = null;
    417     }
    418 
    419     /**
    420      * If it has been tagged i.e. matched to a gpx track or retrieved lat/lon from exif
    421      * @return {@code true} if it has been tagged
    422      */
    423     public boolean isTagged() {
    424         return pos != null;
    425     }
    426 
    427     /**
    428      * String representation. (only partial info)
    429      */
    430     @Override
    431     public String toString() {
    432         return file.getName()+": "+
    433         "pos = "+pos+" | "+
    434         "exifCoor = "+exifCoor+" | "+
    435         (tmp == null ? " tmp==null" :
    436             " [tmp] pos = "+tmp.pos);
    437     }
    438 
    439     /**
    440      * Indicates that the image has new GPS data.
    441      * That flag is set by new GPS data providers.  It is used e.g. by the photo_geotagging plugin
    442      * to decide for which image file the EXIF GPS data needs to be (re-)written.
    443      * @since 6392
    444      */
    445     public void flagNewGpsData() {
    446         isNewGpsData = true;
    447    }
    448 
    449     /**
    450      * Remove the flag that indicates new GPS data.
    451      * The flag is cleared by a new GPS data consumer.
    452      */
    453     public void unflagNewGpsData() {
    454         isNewGpsData = false;
    455     }
    456 
    457     /**
    458      * Queries whether the GPS data changed. The flag value from the temporary
    459      * copy is returned if that copy exists.
    460      * @return {@code true} if GPS data changed, {@code false} otherwise
    461      * @since 6392
    462      */
    463     public boolean hasNewGpsData() {
    464         if (tmp != null)
    465             return tmp.isNewGpsData;
    466         return isNewGpsData;
    467     }
    468 
    469     /**
    470      * Extract GPS metadata from image EXIF. Has no effect if the image file is not set
    471      *
    472      * If successful, fills in the LatLon, speed, elevation, image direction, and other attributes
    473      * @since 9270
    474      */
    475     public void extractExif() {
    476 
    477         Metadata metadata;
    478 
    479         if (file == null) {
    480             return;
    481         }
    482 
    483         try {
    484             metadata = JpegMetadataReader.readMetadata(file);
    485         } catch (CompoundException | IOException ex) {
    486             Logging.error(ex);
    487             setExifTime(null);
    488             setExifCoor(null);
    489             setPos(null);
    490             return;
    491         }
    492 
    493         // Changed to silently cope with no time info in exif. One case
    494         // of person having time that couldn't be parsed, but valid GPS info
    495         try {
    496             setExifTime(ExifReader.readTime(metadata));
    497         } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException ex) {
    498             Logging.warn(ex);
    499             setExifTime(null);
    500         }
    501 
    502         final Directory dir = metadata.getFirstDirectoryOfType(JpegDirectory.class);
    503         final Directory dirExif = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
    504         final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
    505 
    506         try {
    507             if (dirExif != null) {
    508                 int orientation = dirExif.getInt(ExifIFD0Directory.TAG_ORIENTATION);
    509                 setExifOrientation(orientation);
    510             }
    511         } catch (MetadataException ex) {
    512             Logging.debug(ex);
    513         }
    514 
    515         try {
    516             if (dir != null) {
    517                 // there are cases where these do not match width and height stored in dirExif
    518                 int width = dir.getInt(JpegDirectory.TAG_IMAGE_WIDTH);
    519                 int height = dir.getInt(JpegDirectory.TAG_IMAGE_HEIGHT);
    520                 setWidth(width);
    521                 setHeight(height);
    522             }
    523         } catch (MetadataException ex) {
    524             Logging.debug(ex);
    525         }
    526 
    527         if (dirGps == null) {
    528             setExifCoor(null);
    529             setPos(null);
    530             return;
    531         }
    532 
    533         final Double speed = ExifReader.readSpeed(dirGps);
    534         if (speed != null) {
    535             setSpeed(speed);
    536         }
    537 
    538         final Double ele = ExifReader.readElevation(dirGps);
    539         if (ele != null) {
    540             setElevation(ele);
    541         }
    542 
    543         try {
    544             final LatLon latlon = ExifReader.readLatLon(dirGps);
    545             setExifCoor(latlon);
    546             setPos(getExifCoor());
    547         } catch (MetadataException | IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271)
    548             Logging.error("Error reading EXIF from file: " + ex);
    549             setExifCoor(null);
    550             setPos(null);
    551         }
    552 
    553         try {
    554             final Double direction = ExifReader.readDirection(dirGps);
    555             if (direction != null) {
    556                 setExifImgDir(direction);
    557             }
    558         } catch (IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271)
    559             Logging.debug(ex);
    560         }
    561 
    562         final Date gpsDate = dirGps.getGpsDate();
    563         if (gpsDate != null) {
    564             setExifGpsTime(gpsDate);
    565         }
    566     }
    56765}
  • trunk/src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java

    r12620 r14205  
    1111
    1212import org.openstreetmap.josm.data.coor.LatLon;
     13import org.openstreetmap.josm.data.gpx.GpxImageEntry;
    1314import org.openstreetmap.josm.gui.layer.GpxLayer;
    1415import org.openstreetmap.josm.gui.layer.Layer;
     
    7172    }
    7273
    73     private static void handleElement(ImageEntry entry, Element attrElem) {
     74    private static void handleElement(GpxImageEntry entry, Element attrElem) {
    7475        try {
    7576            switch(attrElem.getTagName()) {
  • trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxOffsetTest.java

    r14204 r14205  
    11// License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.gui.layer.geoimage;
     2package org.openstreetmap.josm.data.gpx;
    33
    44import static org.junit.Assert.assertEquals;
     
    1616
    1717/**
    18  * Unit tests of {@link Offset} class.
     18 * Unit tests of {@link GpxTimeOffset} class.
    1919 */
    20 public class OffsetTest {
     20public class GpxOffsetTest {
    2121
    2222    /**
     
    3636
    3737    /**
    38      * Unit test of {@link Offset#formatOffset}.
     38     * Unit test of {@link GpxTimeOffset#formatOffset}.
    3939     */
    4040    @Test
    4141    public void testFormatOffset() {
    42         assertEquals("0", Offset.seconds(0).formatOffset());
    43         assertEquals("123", Offset.seconds(123).formatOffset());
    44         assertEquals("-4242", Offset.seconds(-4242).formatOffset());
    45         assertEquals("0.1", Offset.milliseconds(100).formatOffset());
    46         assertEquals("0.120", Offset.milliseconds(120).formatOffset());
    47         assertEquals("0.123", Offset.milliseconds(123).formatOffset());
    48         assertEquals("1.2", Offset.milliseconds(1200).formatOffset());
    49         assertEquals("1.234", Offset.milliseconds(1234).formatOffset());
     42        assertEquals("0", GpxTimeOffset.seconds(0).formatOffset());
     43        assertEquals("123", GpxTimeOffset.seconds(123).formatOffset());
     44        assertEquals("-4242", GpxTimeOffset.seconds(-4242).formatOffset());
     45        assertEquals("0.1", GpxTimeOffset.milliseconds(100).formatOffset());
     46        assertEquals("0.120", GpxTimeOffset.milliseconds(120).formatOffset());
     47        assertEquals("0.123", GpxTimeOffset.milliseconds(123).formatOffset());
     48        assertEquals("1.2", GpxTimeOffset.milliseconds(1200).formatOffset());
     49        assertEquals("1.234", GpxTimeOffset.milliseconds(1234).formatOffset());
    5050    }
    5151
    5252    /**
    53      * Unit test of {@link Offset#parseOffset}.
     53     * Unit test of {@link GpxTimeOffset#parseOffset}.
    5454     * @throws ParseException in case of parsing error
    5555     */
    5656    @Test
    5757    public void testParseOffest() throws ParseException {
    58         assertEquals(0, Offset.parseOffset("0").getSeconds());
    59         assertEquals(4242L, Offset.parseOffset("4242").getSeconds());
    60         assertEquals(-4242L, Offset.parseOffset("-4242").getSeconds());
    61         assertEquals(0L, Offset.parseOffset("-0").getSeconds());
    62         assertEquals(100L, Offset.parseOffset("0.1").getMilliseconds());
    63         assertEquals(123L, Offset.parseOffset("0.123").getMilliseconds());
    64         assertEquals(-42420L, Offset.parseOffset("-42.42").getMilliseconds());
     58        assertEquals(0, GpxTimeOffset.parseOffset("0").getSeconds());
     59        assertEquals(4242L, GpxTimeOffset.parseOffset("4242").getSeconds());
     60        assertEquals(-4242L, GpxTimeOffset.parseOffset("-4242").getSeconds());
     61        assertEquals(0L, GpxTimeOffset.parseOffset("-0").getSeconds());
     62        assertEquals(100L, GpxTimeOffset.parseOffset("0.1").getMilliseconds());
     63        assertEquals(123L, GpxTimeOffset.parseOffset("0.123").getMilliseconds());
     64        assertEquals(-42420L, GpxTimeOffset.parseOffset("-42.42").getMilliseconds());
    6565    }
    6666
    6767    /**
    68      * Unit test of {@link Offset#splitOutTimezone}.
     68     * Unit test of {@link GpxTimeOffset#splitOutTimezone}.
    6969     */
    7070    @Test
    7171    public void testSplitOutTimezone() {
    72         assertEquals("+1:00", Offset.seconds(3602).splitOutTimezone().a.formatTimezone());
    73         assertEquals("2", Offset.seconds(3602).splitOutTimezone().b.formatOffset());
    74         assertEquals("-7:00", Offset.seconds(-7 * 3600 + 123).splitOutTimezone().a.formatTimezone());
    75         assertEquals("123", Offset.seconds(-7 * 3600 + 123).splitOutTimezone().b.formatOffset());
    76         assertEquals(1, Offset.seconds(35 * 3600 + 421).getDayOffset());
    77         assertEquals(11 * 3600 + 421, Offset.seconds(35 * 3600 + 421).withoutDayOffset().getSeconds());
    78         assertEquals("+11:00", Offset.seconds(35 * 3600 + 421).splitOutTimezone().a.formatTimezone());
    79         assertEquals(86400 + 421, Offset.seconds(35 * 3600 + 421).splitOutTimezone().b.getSeconds());
    80         assertEquals(421, Offset.seconds(35 * 3600 + 421).withoutDayOffset().splitOutTimezone().b.getSeconds());
    81         assertEquals("+1:00", Offset.milliseconds(3602987).splitOutTimezone().a.formatTimezone());
    82         assertEquals("2.987", Offset.milliseconds(3602987).splitOutTimezone().b.formatOffset());
     72        assertEquals("+1:00", GpxTimeOffset.seconds(3602).splitOutTimezone().a.formatTimezone());
     73        assertEquals("2", GpxTimeOffset.seconds(3602).splitOutTimezone().b.formatOffset());
     74        assertEquals("-7:00", GpxTimeOffset.seconds(-7 * 3600 + 123).splitOutTimezone().a.formatTimezone());
     75        assertEquals("123", GpxTimeOffset.seconds(-7 * 3600 + 123).splitOutTimezone().b.formatOffset());
     76        assertEquals(1, GpxTimeOffset.seconds(35 * 3600 + 421).getDayOffset());
     77        assertEquals(11 * 3600 + 421, GpxTimeOffset.seconds(35 * 3600 + 421).withoutDayOffset().getSeconds());
     78        assertEquals("+11:00", GpxTimeOffset.seconds(35 * 3600 + 421).splitOutTimezone().a.formatTimezone());
     79        assertEquals(86400 + 421, GpxTimeOffset.seconds(35 * 3600 + 421).splitOutTimezone().b.getSeconds());
     80        assertEquals(421, GpxTimeOffset.seconds(35 * 3600 + 421).withoutDayOffset().splitOutTimezone().b.getSeconds());
     81        assertEquals("+1:00", GpxTimeOffset.milliseconds(3602987).splitOutTimezone().a.formatTimezone());
     82        assertEquals("2.987", GpxTimeOffset.milliseconds(3602987).splitOutTimezone().b.formatOffset());
    8383    }
    8484}
  • trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxTimezoneTest.java

    r14204 r14205  
    11// License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.gui.layer.geoimage;
     2package org.openstreetmap.josm.data.gpx;
    33
    44import static org.junit.Assert.assertEquals;
     
    1616
    1717/**
    18  * Unit tests of {@link Timezone} class.
     18 * Unit tests of {@link GpxTimezone} class.
    1919 */
    20 public class TimezoneTest {
     20public class GpxTimezoneTest {
    2121
    2222    /**
     
    3636
    3737    /**
    38      * Unit test of {@link Timezone#formatTimezone}.
     38     * Unit test of {@link GpxTimezone#formatTimezone}.
    3939     */
    4040    @Test
    4141    public void testFormatTimezone() {
    42         assertEquals("+1:00", new Timezone(1).formatTimezone());
    43         assertEquals("+6:30", new Timezone(6.5).formatTimezone());
    44         assertEquals("-6:30", new Timezone(-6.5).formatTimezone());
    45         assertEquals("+3:08", new Timezone(Math.PI).formatTimezone());
    46         assertEquals("+2:43", new Timezone(Math.E).formatTimezone());
     42        assertEquals("+1:00", new GpxTimezone(1).formatTimezone());
     43        assertEquals("+6:30", new GpxTimezone(6.5).formatTimezone());
     44        assertEquals("-6:30", new GpxTimezone(-6.5).formatTimezone());
     45        assertEquals("+3:08", new GpxTimezone(Math.PI).formatTimezone());
     46        assertEquals("+2:43", new GpxTimezone(Math.E).formatTimezone());
    4747    }
    4848
    4949    /**
    50      * Unit test of {@link Timezone#parseTimezone}.
     50     * Unit test of {@link GpxTimezone#parseTimezone}.
    5151     * @throws ParseException in case of parsing error
    5252     */
    5353    @Test
    5454    public void testParseTimezone() throws ParseException {
    55         assertEquals(1, Timezone.parseTimezone("+01:00").getHours(), 1e-3);
    56         assertEquals(1, Timezone.parseTimezone("+1:00").getHours(), 1e-3);
    57         assertEquals(1.5, Timezone.parseTimezone("+01:30").getHours(), 1e-3);
    58         assertEquals(11.5, Timezone.parseTimezone("+11:30").getHours(), 1e-3);
     55        assertEquals(1, GpxTimezone.parseTimezone("+01:00").getHours(), 1e-3);
     56        assertEquals(1, GpxTimezone.parseTimezone("+1:00").getHours(), 1e-3);
     57        assertEquals(1.5, GpxTimezone.parseTimezone("+01:30").getHours(), 1e-3);
     58        assertEquals(11.5, GpxTimezone.parseTimezone("+11:30").getHours(), 1e-3);
     59        assertEquals(-11.5, GpxTimezone.parseTimezone("-11:30").getHours(), 1e-3);
    5960    }
    6061}
  • trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImagesTest.java

    r14079 r14205  
    33
    44import static org.junit.Assert.assertEquals;
    5 import static org.junit.Assert.assertFalse;
    6 import static org.junit.Assert.assertTrue;
    75
    8 import java.util.Arrays;
    96import java.util.Collections;
    107
     
    129import org.junit.Rule;
    1310import org.junit.Test;
    14 import org.openstreetmap.josm.data.coor.CachedLatLon;
    1511import org.openstreetmap.josm.data.gpx.GpxData;
     12import org.openstreetmap.josm.data.gpx.GpxTimeOffset;
     13import org.openstreetmap.josm.data.gpx.GpxTimezone;
    1614import org.openstreetmap.josm.io.GpxReaderTest;
    1715import org.openstreetmap.josm.testutils.JOSMTestRules;
     
    4341
    4442    /**
    45      * Tests matching of images to a GPX track.
    46      * @throws Exception if the track cannot be parsed
    47      */
    48     @Test
    49     public void testMatchGpxTrack() throws Exception {
    50         final GpxData gpx = GpxReaderTest.parseGpxData("data_nodist/2094047.gpx");
    51         assertEquals(4, gpx.tracks.size());
    52         assertEquals(1, gpx.tracks.iterator().next().getSegments().size());
    53         assertEquals(185, gpx.tracks.iterator().next().getSegments().iterator().next().getWayPoints().size());
    54 
    55         final ImageEntry ib = new ImageEntry();
    56         ib.setExifTime(DateUtils.fromString("2016:01:03 11:54:58")); // 5 minutes before start of GPX
    57         ib.createTmp();
    58         final ImageEntry i0 = new ImageEntry();
    59         i0.setExifTime(DateUtils.fromString("2016:01:03 11:59:54")); // 4 sec before start of GPX
    60         i0.createTmp();
    61         final ImageEntry i1 = new ImageEntry();
    62         i1.setExifTime(DateUtils.fromString("2016:01:03 12:04:01"));
    63         i1.createTmp();
    64         final ImageEntry i2 = new ImageEntry();
    65         i2.setExifTime(DateUtils.fromString("2016:01:03 12:04:57"));
    66         i2.createTmp();
    67         final ImageEntry i3 = new ImageEntry();
    68         i3.setExifTime(DateUtils.fromString("2016:01:03 12:05:05"));
    69         i3.createTmp();
    70 
    71         assertEquals(4, CorrelateGpxWithImages.matchGpxTrack(Arrays.asList(ib, i0, i1, i2, i3), gpx, 0));
    72         assertEquals(new CachedLatLon(47.19286847859621, 8.79732714034617), i0.getPos()); // start of track
    73         assertEquals(new CachedLatLon(47.196979885920882, 8.79541271366179), i1.getPos()); // exact match
    74         assertEquals(new CachedLatLon(47.197319911792874, 8.792139580473304), i3.getPos()); // exact match
    75         assertEquals(new CachedLatLon((47.197131179273129 + 47.197186248376966) / 2, (8.792974585667253 + 8.792809881269932) / 2),
    76                 i2.getPos()); // interpolated
    77         assertFalse(ib.hasNewGpsData());
    78         assertTrue(i0.hasNewGpsData());
    79         assertTrue(i1.hasNewGpsData());
    80         assertTrue(i2.hasNewGpsData());
    81         assertTrue(i3.hasNewGpsData());
    82         // First waypoint has no speed in matchGpxTrack(). Speed is calculated
    83         // and not taken from GPX track.
    84         assertEquals(null, ib.getSpeed());
    85         assertEquals(null, i0.getSpeed());
    86         assertEquals(Double.valueOf(11.675317966018756), i1.getSpeed(), 0.000001);
    87         assertEquals(Double.valueOf(24.992418392716967), i2.getSpeed(), 0.000001);
    88         assertEquals(Double.valueOf(27.307968754679223), i3.getSpeed(), 0.000001);
    89         assertEquals(null, ib.getElevation());
    90         assertEquals(Double.valueOf(471.86), i0.getElevation(), 0.000001);
    91         assertEquals(Double.valueOf(489.29), i1.getElevation(), 0.000001);
    92         assertEquals(Double.valueOf((490.40 + 489.75) / 2), i2.getElevation(), 0.000001);
    93         assertEquals(Double.valueOf(486.368333333), i3.getElevation(), 0.000001);
    94         assertEquals(null, ib.getGpsTime());
    95         assertEquals(DateUtils.fromString("2016:01:03 11:59:54"), i0.getGpsTime()); // original time is kept
    96         assertEquals(DateUtils.fromString("2016:01:03 12:04:01"), i1.getGpsTime());
    97         assertEquals(DateUtils.fromString("2016:01:03 12:04:57"), i2.getGpsTime());
    98         assertEquals(DateUtils.fromString("2016:01:03 12:05:05"), i3.getGpsTime());
    99     }
    100 
    101     /**
    10243     * Tests automatic guessing of timezone/offset
    10344     * @throws Exception if an error occurs
     
    10950        i0.setExifTime(DateUtils.fromString("2016:01:03 11:59:54")); // 4 sec before start of GPX
    11051        i0.createTmp();
    111         assertEquals(Pair.create(Timezone.ZERO, Offset.seconds(-4)),
     52        assertEquals(Pair.create(GpxTimezone.ZERO, GpxTimeOffset.seconds(-4)),
    11253                CorrelateGpxWithImages.autoGuess(Collections.singletonList(i0), gpx));
    11354    }
Note: See TracChangeset for help on using the changeset viewer.