    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}