001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.plugins.streetside.io.export;
003
004import java.awt.image.BufferedImage;
005import java.io.BufferedOutputStream;
006import java.io.ByteArrayOutputStream;
007import java.io.FileOutputStream;
008import java.io.IOException;
009import java.io.OutputStream;
010import java.util.concurrent.ArrayBlockingQueue;
011
012import javax.imageio.ImageIO;
013
014import org.apache.commons.imaging.ImageReadException;
015import org.apache.commons.imaging.ImageWriteException;
016import org.apache.commons.imaging.Imaging;
017import org.apache.commons.imaging.common.ImageMetadata;
018import org.apache.commons.imaging.common.RationalNumber;
019import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
020import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter;
021import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
022import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants;
023import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants;
024import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
025import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
026import org.apache.log4j.Logger;
027import org.openstreetmap.josm.gui.progress.ProgressMonitor;
028import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
029import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
030import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
031import org.openstreetmap.josm.plugins.streetside.StreetsideImportedImage;
032
033/**
034 * Writes the images from the queue in the file system.
035 *
036 * @author nokutu
037 * @see StreetsideExportManager
038 */
039public class StreetsideExportWriterThread extends Thread {
040
041  final static Logger logger = Logger.getLogger(StreetsideExportWriterThread.class);
042
043  private final String path;
044  private final ArrayBlockingQueue<BufferedImage> queue;
045  private final ArrayBlockingQueue<StreetsideAbstractImage> queueImages;
046  private final int amount;
047  private final ProgressMonitor monitor;
048
049  /**
050   * Main constructor.
051   *
052   * @param path
053   *          Path to write the pictures.
054   * @param queue
055   *          Queue of {@link StreetsideAbstractImage} objects.
056   * @param queueImages
057   *          Queue of {@link BufferedImage} objects.
058   * @param amount
059   *          Amount of images that are going to be exported.
060   * @param monitor
061   *          Progress monitor.
062   */
063  public StreetsideExportWriterThread(String path,
064      ArrayBlockingQueue<BufferedImage> queue,
065      ArrayBlockingQueue<StreetsideAbstractImage> queueImages, int amount,
066      ProgressMonitor monitor) {
067    this.path = path;
068    this.queue = queue;
069    this.queueImages = queueImages;
070    this.amount = amount;
071    this.monitor = monitor;
072  }
073
074  @Override
075  public void run() {
076    monitor.setCustomText("Downloaded 0/" + amount);
077    BufferedImage img;
078    StreetsideAbstractImage mimg;
079    String finalPath = "";
080    for (int i = 0; i < amount; i++) {
081      try {
082        img = queue.take();
083        mimg = queueImages.take();
084        if (path == null && mimg instanceof StreetsideImportedImage) {
085          String path = ((StreetsideImportedImage) mimg).getFile().getPath();
086          finalPath = path.substring(0, path.lastIndexOf('.'));
087        } else if (mimg instanceof StreetsideImage) {
088          finalPath = path + '/' + ((StreetsideImage) mimg).getId();
089        } else if (mimg instanceof StreetsideImportedImage) {
090          finalPath = path + '/' + ((StreetsideImportedImage) mimg).getFile().getName();
091        }
092
093        // Transforms the image into a byte array.
094        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
095        ImageIO.write(img, "jpg", outputStream);
096        byte[] imageBytes = outputStream.toByteArray();
097
098        // Write EXIF tags
099        TiffOutputSet outputSet = null;
100        TiffOutputDirectory exifDirectory;
101        TiffOutputDirectory gpsDirectory;
102        // If the image is imported, loads the rest of the EXIF data.
103        if (mimg instanceof StreetsideImportedImage) {
104          final ImageMetadata metadata = Imaging
105              .getMetadata(((StreetsideImportedImage) mimg).getFile());
106          final JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
107          if (null != jpegMetadata) {
108            final TiffImageMetadata exif = jpegMetadata.getExif();
109            if (null != exif) {
110              outputSet = exif.getOutputSet();
111            }
112          }
113        }
114        if (null == outputSet) {
115          outputSet = new TiffOutputSet();
116        }
117        exifDirectory = outputSet.getOrCreateExifDirectory();
118        gpsDirectory = outputSet.getOrCreateGPSDirectory();
119
120        gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF);
121        gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF,
122            GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF_VALUE_TRUE_NORTH);
123
124        gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION);
125        gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION,
126            RationalNumber.valueOf(mimg.getMovingHe()));
127
128        exifDirectory.removeField(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL);
129        if (mimg instanceof StreetsideImportedImage) {
130          exifDirectory.add(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL,
131              mimg.getDate("yyyy/MM/dd HH:mm:ss"));
132        } else if (mimg instanceof StreetsideImage) {
133          exifDirectory.add(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL,
134              mimg.getDate("yyyy/MM/dd HH/mm/ss"));
135        }
136        outputSet.setGPSInDegrees(mimg.getMovingLatLon().lon(), mimg.getMovingLatLon().lat());
137        OutputStream os = new BufferedOutputStream(new FileOutputStream(finalPath + ".jpg"));
138        new ExifRewriter().updateExifMetadataLossless(imageBytes, os, outputSet);
139
140        os.close();
141      } catch (InterruptedException e) {
142        logger.info("Streetside export cancelled");
143        return;
144      } catch (IOException | ImageReadException | ImageWriteException e) {
145        logger.error(e);
146      }
147
148      // Increases the progress bar.
149      monitor.worked(PleaseWaitProgressMonitor.PROGRESS_BAR_MAX / amount);
150      monitor.setCustomText("Downloaded " + (i + 1) + "/" + amount);
151    }
152  }
153}