Changeset 36436 in osm


Ignore:
Timestamp:
2025-04-30T22:41:56+02:00 (6 days ago)
Author:
stoecker
Message:

see #24278 - add new EXIF metadata (patch by StephaneP, modified), fix mvn test function

Location:
applications/editors/josm/plugins/photo_geotagging
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/photo_geotagging/build.xml

    r36276 r36436  
    55    <property name="commit.message" value=""/>
    66    <!-- enter the *lowest* JOSM version this plugin is currently compatible with -->
    7     <property name="plugin.main.version" value="19044"/>
     7    <property name="plugin.main.version" value="19389"/>
    88
    99    <property name="plugin.author" value="Paul Hartmann"/>
  • applications/editors/josm/plugins/photo_geotagging/pom.xml

    r36422 r36436  
    1717    <properties>
    1818        <plugin.src.dir>src</plugin.src.dir>
    19         <plugin.main.version>19044</plugin.main.version>
     19        <plugin.main.version>19389</plugin.main.version>
    2020        <plugin.author>Paul Hartmann</plugin.author>
    2121        <plugin.class>org.openstreetmap.josm.plugins.photo_geotagging.GeotaggingPlugin</plugin.class>
     
    3232            <version>1.0-SNAPSHOT</version>
    3333            <scope>provided</scope>
     34        </dependency>
     35        <dependency>
     36            <groupId>org.junit.jupiter</groupId>
     37            <artifactId>junit-jupiter</artifactId>
     38            <scope>test</scope>
     39        </dependency>
     40        <dependency>
     41            <groupId>org.wiremock</groupId>
     42            <artifactId>wiremock</artifactId>
     43            <scope>test</scope>
     44        </dependency>
     45        <dependency>
     46            <groupId>org.junit.vintage</groupId>
     47            <artifactId>junit-vintage-engine</artifactId>
     48            <scope>test</scope>
    3449        </dependency>
    3550    </dependencies>
  • applications/editors/josm/plugins/photo_geotagging/src/org/openstreetmap/josm/plugins/photo_geotagging/ExifGPSTagger.java

    r36275 r36436  
    1010import java.io.IOException;
    1111import java.text.DecimalFormat;
    12 import java.time.Instant;
    1312import java.util.Calendar;
    1413import java.util.GregorianCalendar;
     
    2625import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
    2726import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
     27import org.openstreetmap.josm.gui.layer.geoimage.ImageEntry;
    2828
    2929/**
     
    4141     * @param imageFile A source image file.
    4242     * @param dst The output file.
    43      * @param lat latitude
    44      * @param lon longitude
    45      * @param gpsTime time - can be null if not available
    46      * @param speed speed in km/h - can be null if not available
    47      * @param ele elevation - can be null if not available
    48      * @param imgDir image direction in degrees (0..360) - can be null if not available
     43     * @param imageEntry the image object from Josm core
    4944     * @param lossy whether to use lossy approach when writing metadata (overwriting unknown tags)
    5045     * @throws IOException in case of I/O error
     46     * @since 36436 separate image parameters (lat, lon, gpsTime, speed, ele, imgDir), replaced by the whole ImageEntry object.
    5147     */
    52     public static void setExifGPSTag(File imageFile, File dst, double lat, double lon, Instant gpsTime, Double speed,
    53             Double ele, Double imgDir, boolean lossy) throws IOException {
     48
     49    public static void setExifGPSTag(File imageFile, File dst, ImageEntry imageEntry,
     50            boolean lossy) throws IOException {
    5451        try {
    55             setExifGPSTagWorker(imageFile, dst, lat, lon, gpsTime, speed, ele, imgDir, lossy);
     52            setExifGPSTagWorker(imageFile, dst, imageEntry, lossy);
    5653        } catch (ImagingException ire) {
    5754            // This used to be two separate exceptions; ImageReadException and imageWriteException
     
    6057    }
    6158
    62     public static void setExifGPSTagWorker(File imageFile, File dst, double lat, double lon, Instant gpsTime, Double speed,
    63             Double ele, Double imgDir, boolean lossy) throws IOException {
     59    public static void setExifGPSTagWorker(File imageFile, File dst, ImageEntry imageEntry,
     60            boolean lossy) throws IOException {
    6461
    6562        TiffOutputSet outputSet = null;
     
    8178        TiffOutputDirectory gpsDirectory = outputSet.getOrCreateGpsDirectory();
    8279        gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_VERSION_ID);
    83         gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_VERSION_ID, (byte)2, (byte)3, (byte)0, (byte)0);
    84 
    85         if (gpsTime != null) {
     80        gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_VERSION_ID, (byte) 2, (byte) 3, (byte) 0, (byte) 0);
     81
     82        if (imageEntry.getGpsInstant() != null) {
    8683            Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
    87             calendar.setTimeInMillis(gpsTime.toEpochMilli());
    88 
    89             final int year =   calendar.get(Calendar.YEAR);
    90             final int month =  calendar.get(Calendar.MONTH) + 1;
    91             final int day =    calendar.get(Calendar.DAY_OF_MONTH);
    92             final int hour =   calendar.get(Calendar.HOUR_OF_DAY);
     84            calendar.setTimeInMillis(imageEntry.getGpsInstant().toEpochMilli());
     85
     86            final int year = calendar.get(Calendar.YEAR);
     87            final int month = calendar.get(Calendar.MONTH) + 1;
     88            final int day = calendar.get(Calendar.DAY_OF_MONTH);
     89            final int hour = calendar.get(Calendar.HOUR_OF_DAY);
    9390            final int minute = calendar.get(Calendar.MINUTE);
    9491            final int second = calendar.get(Calendar.SECOND);
     
    118115        }
    119116
    120         outputSet.setGpsInDegrees(lon, lat);
    121 
    122         if (speed != null) {
     117        outputSet.setGpsInDegrees(imageEntry.getPos().lon(), imageEntry.getPos().lat());
     118
     119        if (imageEntry.getSpeed() != null) {
    123120            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_SPEED_REF);
    124121            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_SPEED_REF,
     
    126123
    127124            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_SPEED);
    128             gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_SPEED, RationalNumber.valueOf(speed));
    129         }
    130 
    131         if (ele != null) {
    132             byte eleRef =  ele >= 0 ? (byte) 0 : (byte) 1;
     125            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_SPEED, RationalNumber.valueOf(imageEntry.getSpeed()));
     126        }
     127
     128        if (imageEntry.getElevation() != null) {
     129            byte eleRef = imageEntry.getElevation() >= 0 ? (byte) 0 : (byte) 1;
    133130            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_ALTITUDE_REF);
    134131            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_ALTITUDE_REF, eleRef);
    135132
    136133            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_ALTITUDE);
    137             gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_ALTITUDE, RationalNumber.valueOf(Math.abs(ele)));
    138         }
    139 
    140         if (imgDir != null) {
     134            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_ALTITUDE, RationalNumber.valueOf(Math.abs(imageEntry.getElevation())));
     135        }
     136
     137        if (imageEntry.getExifImgDir() != null) {
    141138            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF);
    142139            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF,
    143140                             GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF_VALUE_TRUE_NORTH);
    144             // make sure the value is in the range 0.0...<360.0
    145             if (imgDir < 0.0) {
    146                 imgDir %= 360.0; // >-360.0...-0.0
    147                 imgDir += 360.0; // >0.0...360.0
    148             }
    149             if (imgDir >= 360.0) {
    150                 imgDir %= 360.0;
    151             }
     141            Double imgDir = checkAngle(imageEntry.getExifImgDir());
    152142            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION);
    153143            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION, RationalNumber.valueOf(imgDir));
     144        }
     145
     146        if (imageEntry.getExifGpsTrack() != null) {
     147            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_TRACK_REF);
     148            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_TRACK_REF,
     149                             GpsTagConstants.GPS_TAG_GPS_TRACK_REF_VALUE_TRUE_NORTH);
     150            // make sure the value is in the range 0.0...<360.0
     151            Double gpsTrack = checkAngle(imageEntry.getExifGpsTrack());
     152            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_TRACK);
     153            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_TRACK, RationalNumber.valueOf(gpsTrack));
     154        }
     155
     156        if (imageEntry.getGpsDiffMode() != null) {
     157            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_DIFFERENTIAL);
     158            // make sure the gpsDiffMode value is 0 (no diffential) or 1 (differential)
     159            if (imageEntry.getGpsDiffMode().equals(0) || imageEntry.getGpsDiffMode().equals(1)) {
     160                gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_DIFFERENTIAL, imageEntry.getGpsDiffMode().shortValue());
     161            }
     162        }
     163       
     164        if (imageEntry.getGps2d3dMode() != null) {
     165            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_MEASURE_MODE);
     166            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_MEASURE_MODE, imageEntry.getGps2d3dMode().toString());
     167        }
     168
     169        if (imageEntry.getExifGpsProcMethod() != null) {
     170            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_PROCESSING_METHOD);
     171            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_PROCESSING_METHOD, imageEntry.getExifGpsProcMethod());
     172        }
     173
     174        if (imageEntry.getExifGpsDatum() != null) {
     175            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_MAP_DATUM);
     176            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_MAP_DATUM, imageEntry.getExifGpsDatum().toString());
     177        }
     178       
     179        if (imageEntry.getExifHPosErr() != null) {
     180            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_HOR_POSITIONING_ERROR);
     181            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_HOR_POSITIONING_ERROR, RationalNumber.valueOf(imageEntry.getExifHPosErr()));
     182        }
     183
     184        if (imageEntry.getExifGpsDop() != null) {
     185            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_DOP);
     186            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_DOP, RationalNumber.valueOf(imageEntry.getExifGpsDop()));
    154187        }
    155188
     
    166199        }
    167200    }
     201
     202    /**
     203     * Normalizes an angle to the range [0.0, 360.0[ degrees.
     204     * This will fix any angle value <0 and >= 360
     205     * @param angle the angle to normalize (in degrees)
     206     * @return the equivalent angle value in the range [0.0, 360.0[
     207     */
     208    private static Double checkAngle(Double angle) {
     209        if (angle < 0.0) {
     210            angle %= 360.0; // >-360.0...-0.0
     211            angle += 360.0; // >0.0...360.0
     212        }
     213        if (angle >= 360.0) {
     214            angle %= 360.0;
     215        }
     216        return angle;
     217    }
    168218}
  • applications/editors/josm/plugins/photo_geotagging/src/org/openstreetmap/josm/plugins/photo_geotagging/GeotaggingAction.java

    r36275 r36436  
    6565    static final int MTIME_MODE_PREVIOUS_VALUE = 2;
    6666
    67     public GeotaggingAction() {
     67    GeotaggingAction() {
    6868        super(tr("Write coordinates to image header"), ImageProvider.get("geotagging"));
    6969    }
     
    8686
    8787        for (ImageEntry e : layer.getImages()) {
    88              /* Only write lat/lon to the file, if the position is known and
     88            /* Only write lat/lon to the file, if the position is known and
    8989                the GPS data changed. */
    9090            if (e.getPos() != null && e.hasNewGpsData()) {
     
    135135        final JPanel settingsPanel = new JPanel(new GridBagLayout());
    136136        settingsPanel.setBorder(BorderFactory.createTitledBorder(tr("settings")));
    137         cont.add(settingsPanel, GBC.eol().insets(3,10,3,0));
     137        cont.add(settingsPanel, GBC.eol().insets(3, 10, 3, 0));
    138138
    139139        final JCheckBox backups = new JCheckBox(tr("keep backup files"), Config.getPref().getBoolean(KEEP_BACKUP, true));
    140         settingsPanel.add(backups, GBC.eol().insets(3,3,0,0));
     140        settingsPanel.add(backups, GBC.eol().insets(3, 3, 0, 0));
    141141
    142142        final JCheckBox setMTime = new JCheckBox(tr("change file modification time:"), Config.getPref().getBoolean(CHANGE_MTIME, false));
    143         settingsPanel.add(setMTime, GBC.std().insets(3,3,5,3));
     143        settingsPanel.add(setMTime, GBC.std().insets(3, 3, 5, 3));
    144144
    145145        final String[] mTimeModeArray = {"----", tr("to gps time"), tr("to previous value (unchanged mtime)")};
    146146        final JComboBox<String> mTimeMode = new JComboBox<>(mTimeModeArray);
    147         {
    148             String mTimeModePref = Config.getPref().get(MTIME_MODE, null);
    149             int mTimeIdx = 0;
    150             if ("gps".equals(mTimeModePref)) {
    151                 mTimeIdx = 1;
    152             } else if ("previous".equals(mTimeModePref)) {
    153                 mTimeIdx = 2;
    154             }
    155             mTimeMode.setSelectedIndex(setMTime.isSelected() ? mTimeIdx : 0);
    156         }
    157         settingsPanel.add(mTimeMode, GBC.eol().insets(3,3,3,3));
     147
     148        String mTimeModePrefName = Config.getPref().get(MTIME_MODE, null);
     149        int mTimeIdx = 0;
     150        if ("gps".equals(mTimeModePrefName)) {
     151            mTimeIdx = 1;
     152        } else if ("previous".equals(mTimeModePrefName)) {
     153            mTimeIdx = 2;
     154        }
     155        mTimeMode.setSelectedIndex(setMTime.isSelected() ? mTimeIdx : 0);
     156
     157        settingsPanel.add(mTimeMode, GBC.eol().insets(3, 3, 3, 3));
    158158
    159159        setMTime.addActionListener(e -> {
     
    219219        private int currentIndex;
    220220
    221         public GeoTaggingRunnable(List<ImageEntry> images, boolean keep_backup, int mTimeMode) {
     221        GeoTaggingRunnable(List<ImageEntry> images, boolean keep_backup, int mTimeMode) {
    222222            super(tr("Photo Geotagging Plugin"));
    223223            this.images = images;
     
    225225            this.mTimeMode = mTimeMode;
    226226        }
    227 
    228227
    229228        @Override
     
    258257                    }
    259258                    sb.append("</ul><br>")
    260                       .append(tr("This can likely be fixed by rewriting the entire EXIF section, however some (rare) unknown tags may get lost in the process.<br>"
     259                      .append(tr("This can likely be fixed by rewriting the entire EXIF section, however some"
     260                              + " (rare) unknown tags may get lost in the process.<br>"
    261261                              + "Would you like to proceed anyway?"));
    262262
     
    360360                }
    361361            }
    362             if ( mTimeMode == MTIME_MODE_PREVIOUS_VALUE
     362            if (mTimeMode == MTIME_MODE_PREVIOUS_VALUE
    363363                 // this is also the fallback if one of the other
    364364                 // modes failed to determine the modification time
     
    371371            chooseFiles(e.getFile());
    372372            if (canceled) return;
    373             ExifGPSTagger.setExifGPSTag(fileFrom, fileTo, e.getPos().lat(), e.getPos().lon(),
    374                     e.getGpsInstant(), e.getSpeed(), e.getElevation(), e.getExifImgDir(), lossy);
     373            ExifGPSTagger.setExifGPSTag(fileFrom, fileTo, e, lossy);
    375374
    376375            if (mTime != null) {
     
    391390            }
    392391
    393             File fileBackup = new File(file.getParentFile(),file.getName()+"_");
     392            File fileBackup = new File(file.getParentFile(), file.getName()+"_");
    394393            if (fileBackup.exists()) {
    395394                confirm_override();
     
    511510
    512511    private GeoImageLayer getLayer() {
    513         return (GeoImageLayer)LayerListDialog.getInstance().getModel().getSelectedLayers().get(0);
     512        return (GeoImageLayer) LayerListDialog.getInstance().getModel().getSelectedLayers().get(0);
    514513    }
    515514
     
    539538    }
    540539}
     540
  • applications/editors/josm/plugins/photo_geotagging/test/unit/org/openstreetmap/josm/plugins/photo_geotagging/ExifGPSTaggerTest.java

    r35933 r36436  
     1// License: GPL. For details, see LICENSE file.
     2// SPDX-License-Identifier: GPL-2.0-or-later
    13package org.openstreetmap.josm.plugins.photo_geotagging;
    24
     
    1921import org.junit.jupiter.api.io.TempDir;
    2022import org.openstreetmap.josm.TestUtils;
     23import org.openstreetmap.josm.data.coor.LatLon;
     24import org.openstreetmap.josm.gui.layer.geoimage.ImageEntry;
    2125
    2226class ExifGPSTaggerTest {
     
    2529    File tempFolder;
    2630
     31    private static ImageEntry newImageEntry(String file, Double lat, Double lon, Instant exifTime,
     32                                            Double speed, Double elevation, Double imgDir) {
     33        ImageEntry entry = new ImageEntry(new File(file));
     34        entry.setPos(new LatLon(lat, lon));
     35        entry.setExifTime(exifTime);
     36        entry.setSpeed(speed);
     37        entry.setElevation(elevation);
     38        entry.setExifImgDir(imgDir);
     39        return entry;
     40    }
     41
    2742    @Test
    2843    void testTicket11757() {
    2944        final File in = new File(TestUtils.getTestDataRoot(), "_DSC1234.jpg");
    3045        final File out = new File(tempFolder, in.getName());
    31         assertDoesNotThrow(() -> ExifGPSTagger.setExifGPSTag(in, out, 12, 34, Instant.now(), 12.34, Math.E, Math.PI, true));
     46        final ImageEntry image = newImageEntry("test", 12d, 34d, Instant.now(), 12.34d, Math.E, Math.PI);
     47        assertDoesNotThrow(() -> ExifGPSTagger.setExifGPSTag(in, out, image, true));
    3248    }
    3349
     
    4763        final File in = new File(TestUtils.getTestDataRoot(), "IMG_7250_small.JPG");
    4864        final File out = new File(tempFolder, in.getName());
    49         ExifGPSTagger.setExifGPSTag(in, out, 12, 34, Instant.now(), 12.34, Math.E, Math.PI, false);
     65        final ImageEntry image = newImageEntry("test", 12d, 34d, Instant.now(), 12.34d, Math.E, Math.PI);
     66        ExifGPSTagger.setExifGPSTag(in, out, image, true);
    5067        final Process jhead = Runtime.getRuntime().exec(new String[]{"jhead", out.getAbsolutePath()});
    5168        final String stdout = new Scanner(jhead.getErrorStream()).useDelimiter("\\A").next();
     
    5370        assertFalse(stdout.contains("Suspicious offset of first Exif IFD value"));
    5471    }
     72
     73    @Test
     74    public void testTicket24278() {
     75        final File in = new File(TestUtils.getTestDataRoot(), "_DSC1234.jpg");
     76        final File out = new File(tempFolder, in.getName());
     77        final ImageEntry image = newImageEntry("test", 12d, 34d, Instant.now(), 12.34d, Math.E, Math.PI);
     78        image.setExifGpsTrack(Math.PI);
     79        image.setGpsDiffMode(2);
     80        image.setGps2d3dMode(3);
     81        image.setExifGpsProcMethod("GPS");
     82        image.setExifHPosErr(1.2d);
     83        image.setExifGpsDop(2.5d);
     84        image.setExifGpsDatum("WGS84");
     85        assertDoesNotThrow(() -> ExifGPSTagger.setExifGPSTag(in, out, image, true));
     86        /* TODO read temp file and assertEquals EXIF metadata values */
     87    }
    5588}
  • applications/editors/josm/plugins/photo_geotagging/test/unit/org/openstreetmap/josm/plugins/photo_geotagging/GeotaggingActionTest.java

    r35933 r36436  
     1// License: GPL. For details, see LICENSE file.
     2// SPDX-License-Identifier: GPL-2.0-or-later
    13package org.openstreetmap.josm.plugins.photo_geotagging;
    2 
    34
    45import java.io.File;
Note: See TracChangeset for help on using the changeset viewer.