Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryAbstractImage.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryAbstractImage.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryAbstractImage.java	(revision 31515)
@@ -9,5 +9,5 @@
 
 /**
- * Abstract superclass for all image objects. At the moment there is just 2,
+ * Abstract superclass for all image objects. At the moment there are just 2,
  * {@link MapillaryImportedImage} and {@link MapillaryImage}.
  *
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryLayer.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryLayer.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryLayer.java	(revision 31515)
@@ -52,9 +52,9 @@
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.plugins.mapillary.cache.CacheUtils;
-import org.openstreetmap.josm.plugins.mapillary.downloads.MapillaryDownloader;
 import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryFilterDialog;
 import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryMainDialog;
 import org.openstreetmap.josm.plugins.mapillary.history.MapillaryRecord;
 import org.openstreetmap.josm.plugins.mapillary.history.commands.CommandDelete;
+import org.openstreetmap.josm.plugins.mapillary.io.download.MapillaryDownloader;
 import org.openstreetmap.josm.plugins.mapillary.mode.AbstractMode;
 import org.openstreetmap.josm.plugins.mapillary.mode.JoinMode;
@@ -312,5 +312,4 @@
         continue;
       Point p = mv.getPoint(imageAbs.getLatLon());
-
       Point nextp = null;
       // Draw sequence line
@@ -327,5 +326,5 @@
           g.drawLine(p.x, p.y, nextp.x, nextp.y);
       }
-
+      // Draws icons
       if (imageAbs instanceof MapillaryImage) {
         MapillaryImage image = (MapillaryImage) imageAbs;
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryPlugin.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryPlugin.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryPlugin.java	(revision 31515)
@@ -25,9 +25,9 @@
 import org.openstreetmap.josm.plugins.mapillary.actions.MapillaryWalkAction;
 import org.openstreetmap.josm.plugins.mapillary.actions.MapillaryZoomAction;
-import org.openstreetmap.josm.plugins.mapillary.downloads.MapillaryDownloader;
 import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryFilterDialog;
 import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryHistoryDialog;
 import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryMainDialog;
 import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryPreferenceSetting;
+import org.openstreetmap.josm.plugins.mapillary.io.download.MapillaryDownloader;
 import org.openstreetmap.josm.plugins.mapillary.oauth.MapillaryUser;
 import org.openstreetmap.josm.tools.ImageProvider;
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/actions/MapillaryDownloadViewAction.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/actions/MapillaryDownloadViewAction.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/actions/MapillaryDownloadViewAction.java	(revision 31515)
@@ -7,5 +7,5 @@
 
 import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.plugins.mapillary.downloads.MapillaryDownloader;
+import org.openstreetmap.josm.plugins.mapillary.io.download.MapillaryDownloader;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Shortcut;
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/actions/MapillaryExportAction.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/actions/MapillaryExportAction.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/actions/MapillaryExportAction.java	(revision 31515)
@@ -22,6 +22,6 @@
 import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
 import org.openstreetmap.josm.plugins.mapillary.MapillaryPlugin;
-import org.openstreetmap.josm.plugins.mapillary.downloads.MapillaryExportManager;
 import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryExportDialog;
+import org.openstreetmap.josm.plugins.mapillary.io.export.MapillaryExportManager;
 import org.openstreetmap.josm.tools.Shortcut;
 
Index: plications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillaryDownloader.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillaryDownloader.java	(revision 31514)
+++ 	(revision )
@@ -1,237 +1,0 @@
-package org.openstreetmap.josm.plugins.mapillary.downloads;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.util.ArrayList;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import javax.swing.JOptionPane;
-import javax.swing.SwingUtilities;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryPlugin;
-
-/**
- * Class that concentrates all the ways of downloading of the plugin. All the
- * download petitions will be managed one by one.
- *
- * @author nokutu
- *
- */
-public class MapillaryDownloader {
-
-  /** Possible download modes. */
-  public static final String[] MODES = new String[] { "Automatic",
-      "Semiautomatic", "Manual" };
-  /** Automatic mode. */
-  public static final int AUTOMATIC = 0;
-  /** Semiautomatic mode. */
-  public static final int SEMIAUTOMATIC = 1;
-  /** Manual mode. */
-  public static final int MANUAL = 2;
-
-  /** All the Threads that have been run. Used to interrupt them properly. */
-  private static ArrayList<Thread> threads = new ArrayList<>();
-
-  /** Max area to be downloaded */
-  public static final double MAX_AREA = Main.pref.getDouble(
-      "mapillary.max-download-area", 0.015);
-
-  /** Base URL of the Mapillary API. */
-  public final static String BASE_URL = "https://a.mapillary.com/v2/";
-  /** Client ID for the app */
-  public final static String CLIENT_ID = "T1Fzd20xZjdtR0s1VDk5OFNIOXpYdzoxNDYyOGRkYzUyYTFiMzgz";
-  /** Executor that will run the petitions. */
-  private static ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(3, 5,
-      100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));;
-
-  /**
-   * Gets all the images in a square. It downloads all the images of all the
-   * sequences that pass through the given rectangle.
-   *
-   * @param minLatLon
-   *          The minimum latitude and longitude of the rectangle.
-   * @param maxLatLon
-   *          The maximum latitude and longitude of the rectangle
-   */
-  public static void getImages(LatLon minLatLon, LatLon maxLatLon) {
-    ConcurrentHashMap<String, Double> queryStringParts = new ConcurrentHashMap<>();
-    queryStringParts.put("min_lat", minLatLon.lat());
-    queryStringParts.put("min_lon", minLatLon.lon());
-    queryStringParts.put("max_lat", maxLatLon.lat());
-    queryStringParts.put("max_lon", maxLatLon.lon());
-    run(new MapillarySquareDownloadManagerThread(queryStringParts));
-  }
-
-  private static void run(Thread t) {
-    threads.add(t);
-    EXECUTOR.execute(t);
-  }
-
-  /**
-   * If some part of the current view has not been downloaded, it is downloaded.
-   *
-   */
-  public static void completeView() {
-    if (getMode() != SEMIAUTOMATIC && getMode() != MANUAL)
-      throw new IllegalStateException("Must be in semiautomatic or manual mode");
-    Bounds view = Main.map.mapView.getRealBounds();
-    if (view.getArea() > MAX_AREA)
-      return;
-    if (isViewDownloaded(view))
-      return;
-    MapillaryLayer.getInstance().getData().bounds.add(view);
-    getImages(view);
-  }
-
-  private static boolean isViewDownloaded(Bounds view) {
-    int n = 15;
-    boolean[][] inside = new boolean[n][n];
-    for (int i = 0; i < n; i++) {
-      for (int j = 0; j < n; j++) {
-        if (isInBounds(new LatLon(view.getMinLat()
-            + (view.getMaxLat() - view.getMinLat()) * ((double) i / n),
-            view.getMinLon() + (view.getMaxLon() - view.getMinLon())
-                * ((double) j / n)))) {
-          inside[i][j] = true;
-        }
-      }
-    }
-    for (int i = 0; i < n; i++) {
-      for (int j = 0; j < n; j++) {
-        if (!inside[i][j])
-          return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Checks if the given {@link LatLon} object lies inside the bounds of the
-   * image.
-   *
-   * @param latlon
-   *          The coordinates to check.
-   * @return true if it lies inside the bounds; false otherwise;
-   */
-  private static boolean isInBounds(LatLon latlon) {
-    for (Bounds bounds : MapillaryLayer.getInstance().getData().bounds) {
-      if (bounds.contains(latlon))
-        return true;
-    }
-    return false;
-  }
-
-  /**
-   * Gets the images within the given bounds.
-   *
-   * @param bounds
-   *          A {@link Bounds} object containing the area to be downloaded.
-   */
-  public static void getImages(Bounds bounds) {
-    getImages(bounds.getMin(), bounds.getMax());
-  }
-
-  /**
-   * Downloads all images of the area covered by the OSM data. This is only just
-   * for automatic download.
-   */
-  public static void automaticDownload() {
-    MapillaryLayer layer = MapillaryLayer.getInstance();
-    if (isAreaTooBig()) {
-      tooBigErrorDialog();
-      return;
-    }
-
-    if (getMode() != AUTOMATIC)
-      throw new IllegalStateException("Must be in automatic mode.");
-    for (Bounds bounds : Main.map.mapView.getEditLayer().data
-        .getDataSourceBounds()) {
-      if (!layer.getData().bounds.contains(bounds)) {
-        layer.getData().bounds.add(bounds);
-        MapillaryDownloader.getImages(bounds.getMin(), bounds.getMax());
-      }
-    }
-  }
-
-  /**
-   * Checks if the area of the OSM data is too big. This means that probably
-   * lots of Mapillary images are going to be downloaded, slowing down the
-   * program too much. To solve this the automatic is stopped, an alert is shown
-   * and you will have to download areas manually.
-   */
-  private static boolean isAreaTooBig() {
-    double area = 0;
-    for (Bounds bounds : Main.map.mapView.getEditLayer().data
-        .getDataSourceBounds()) {
-      area += bounds.getArea();
-    }
-    if (area > MAX_AREA)
-      return true;
-    return false;
-  }
-
-  /**
-   * Returns the current download mode.
-   *
-   * @return 0 - automatic; 1 - semiautomatic; 2 - manual.
-   */
-  public static int getMode() {
-    if (Main.pref.get("mapillary.download-mode").equals(MODES[0])
-        && (MapillaryLayer.INSTANCE == null || !MapillaryLayer.INSTANCE.TEMP_SEMIAUTOMATIC))
-      return 0;
-    else if (Main.pref.get("mapillary.download-mode").equals(MODES[1])
-        || (MapillaryLayer.INSTANCE != null && MapillaryLayer.getInstance().TEMP_SEMIAUTOMATIC))
-      return 1;
-    else if (Main.pref.get("mapillary.download-mode").equals(MODES[2]))
-      return 2;
-    else if (Main.pref.get("mapillary.download-mode").equals(""))
-      return 0;
-    else
-      throw new IllegalStateException();
-  }
-
-  private static void tooBigErrorDialog() {
-    if (!SwingUtilities.isEventDispatchThread()) {
-      SwingUtilities.invokeLater(new Runnable() {
-        @Override
-        public void run() {
-          tooBigErrorDialog();
-        }
-      });
-    } else {
-      MapillaryLayer.getInstance().TEMP_SEMIAUTOMATIC = true;
-      MapillaryPlugin.setMenuEnabled(MapillaryPlugin.DOWNLOAD_VIEW_MENU, true);
-      JOptionPane
-          .showMessageDialog(
-              Main.parent,
-              tr("The downloaded OSM area is too big. Download mode has been changed to semiautomatic until the layer is restarted."));
-    }
-  }
-
-  /**
-   * Stops all running threads.
-   */
-  public static void stopAll() {
-    for (Thread t : threads) {
-      if (t.isAlive())
-        System.out.println(t);
-      t.interrupt();
-    }
-    threads.clear();
-    EXECUTOR.shutdownNow();
-    try {
-      EXECUTOR.awaitTermination(30, TimeUnit.SECONDS);
-    } catch (InterruptedException e) {
-      Main.error(e);
-    }
-    EXECUTOR = new ThreadPoolExecutor(3, 5, 100, TimeUnit.SECONDS,
-        new ArrayBlockingQueue<Runnable>(100));
-  }
-}
Index: plications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillaryExportDownloadThread.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillaryExportDownloadThread.java	(revision 31514)
+++ 	(revision )
@@ -1,75 +1,0 @@
-package org.openstreetmap.josm.plugins.mapillary.downloads;
-
-import java.awt.image.BufferedImage;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.concurrent.ArrayBlockingQueue;
-
-import javax.imageio.ImageIO;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.cache.CacheEntry;
-import org.openstreetmap.josm.data.cache.CacheEntryAttributes;
-import org.openstreetmap.josm.data.cache.ICachedLoaderListener;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
-import org.openstreetmap.josm.plugins.mapillary.cache.MapillaryCache;
-
-/**
- * This is the thread that downloads one of the images that are going to be
- * exported and writes them in a {@link ArrayBlockingQueue}.
- *
- * @author nokutu
- * @see MapillaryExportManager
- * @see MapillaryExportWriterThread
- */
-public class MapillaryExportDownloadThread extends Thread implements
-    ICachedLoaderListener {
-
-  private ArrayBlockingQueue<BufferedImage> queue;
-  private ArrayBlockingQueue<MapillaryAbstractImage> queueImages;
-
-  private MapillaryImage image;
-
-  /**
-   * Main constructor.
-   *
-   * @param image
-   *          Image to be downloaded.
-   * @param queue
-   *          Queue of {@link BufferedImage} objects for the
-   *          {@link MapillaryExportWriterThread}.
-   * @param queueImages
-   *          Queue of {@link MapillaryAbstractImage} objects for the
-   *          {@link MapillaryExportWriterThread}.
-   */
-  public MapillaryExportDownloadThread(MapillaryImage image,
-      ArrayBlockingQueue<BufferedImage> queue,
-      ArrayBlockingQueue<MapillaryAbstractImage> queueImages) {
-    this.queue = queue;
-    this.image = image;
-    this.queueImages = queueImages;
-  }
-
-  @Override
-  public void run() {
-    new MapillaryCache(this.image.getKey(), MapillaryCache.Type.FULL_IMAGE)
-        .submit(this, false);
-  }
-
-  @Override
-  public synchronized void loadingFinished(CacheEntry data,
-      CacheEntryAttributes attributes, LoadResult result) {
-    try {
-      synchronized (this.getClass()) {
-        this.queue
-            .put(ImageIO.read(new ByteArrayInputStream(data.getContent())));
-        this.queueImages.put(this.image);
-      }
-    } catch (InterruptedException e) {
-      Main.error(e);
-    } catch (IOException e) {
-      Main.error(e);
-    }
-  }
-}
Index: plications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillaryExportManager.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillaryExportManager.java	(revision 31514)
+++ 	(revision )
@@ -1,145 +1,0 @@
-package org.openstreetmap.josm.plugins.mapillary.downloads;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.image.BufferedImage;
-import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.gui.PleaseWaitRunnable;
-import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
-import org.openstreetmap.josm.io.OsmTransferException;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryImportedImage;
-import org.xml.sax.SAXException;
-
-/**
- * Export main thread. Exportation works by creating a
- * {@link MapillaryExportWriterThread} and several
- * {@link MapillaryExportDownloadThread}. The second ones download every single
- * image that is going to be exported and stores them in an
- * {@link ArrayBlockingQueue}. Then it is picked by the first one and written on
- * the selected folder. Each image will be named by its key.
- *
- * @author nokutu
- * @see MapillaryExportWriterThread
- * @see MapillaryExportDownloadThread
- */
-public class MapillaryExportManager extends PleaseWaitRunnable {
-
-  private ArrayBlockingQueue<BufferedImage> queue;
-  private ArrayBlockingQueue<MapillaryAbstractImage> queueImages;
-
-  private final int amount;
-  private List<MapillaryAbstractImage> images;
-  private String path;
-
-  private Thread writer;
-  private ThreadPoolExecutor ex;
-
-  /**
-   * Main constructor.
-   *
-   * @param images
-   *          Set of {@link MapillaryAbstractImage} objects to be exported.
-   * @param path
-   *          Export path.
-   */
-  public MapillaryExportManager(List<MapillaryAbstractImage> images, String path) {
-    super(tr("Downloading") + "...", new PleaseWaitProgressMonitor(
-        "Exporting Mapillary Images"), true);
-    this.queue = new ArrayBlockingQueue<>(10);
-    this.queueImages = new ArrayBlockingQueue<>(10);
-
-    this.images = images;
-    this.amount = images.size();
-    this.path = path;
-  }
-
-  /**
-   * Constructor used to rewrite imported images.
-   *
-   * @param images
-   *          The set of {@link MapillaryImportedImage} object that is going to
-   *          be rewritten.
-   * @throws IOException
-   *           If the file of one of the {@link MapillaryImportedImage} objects
-   *           doesn't contain a picture.
-   */
-  public MapillaryExportManager(List<MapillaryImportedImage> images)
-      throws IOException {
-    super(tr("Downloading") + "...", new PleaseWaitProgressMonitor(
-        "Exporting Mapillary Images"), true);
-    this.queue = new ArrayBlockingQueue<>(10);
-    this.queueImages = new ArrayBlockingQueue<>(10);
-    for (MapillaryImportedImage image : images) {
-      this.queue.add(image.getImage());
-      this.queueImages.add(image);
-    }
-    this.amount = images.size();
-  }
-
-  @Override
-  protected void cancel() {
-    this.writer.interrupt();
-    this.ex.shutdown();
-  }
-
-  @Override
-  protected void realRun() throws SAXException, IOException,
-      OsmTransferException {
-    // Starts a writer thread in order to write the pictures on the disk.
-    this.writer = new MapillaryExportWriterThread(this.path, this.queue,
-        this.queueImages, this.amount, this.getProgressMonitor());
-    this.writer.start();
-    if (this.path == null) {
-      try {
-        this.writer.join();
-      } catch (InterruptedException e) {
-        Main.error(e);
-      }
-      return;
-    }
-    this.ex = new ThreadPoolExecutor(20, 35, 25, TimeUnit.SECONDS,
-        new ArrayBlockingQueue<Runnable>(10));
-    for (MapillaryAbstractImage image : this.images) {
-      if (image instanceof MapillaryImage) {
-        try {
-          this.ex.execute(new MapillaryExportDownloadThread(
-              (MapillaryImage) image, this.queue, this.queueImages));
-        } catch (Exception e) {
-          Main.error(e);
-        }
-      } else if (image instanceof MapillaryImportedImage) {
-        try {
-          this.queue.put(((MapillaryImportedImage) image).getImage());
-          this.queueImages.put(image);
-        } catch (InterruptedException e) {
-          Main.error(e);
-        }
-      }
-      try {
-        // If the queue is full, waits for it to have more space
-        // available before executing anything else.
-        while (this.ex.getQueue().remainingCapacity() == 0)
-          Thread.sleep(100);
-      } catch (Exception e) {
-        Main.error(e);
-      }
-    }
-    try {
-      this.writer.join();
-    } catch (InterruptedException e) {
-      Main.error(e);
-    }
-  }
-
-  @Override
-  protected void finish() {
-  }
-}
Index: plications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillaryExportWriterThread.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillaryExportWriterThread.java	(revision 31514)
+++ 	(revision )
@@ -1,161 +1,0 @@
-package org.openstreetmap.josm.plugins.mapillary.downloads;
-
-import java.awt.image.BufferedImage;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.concurrent.ArrayBlockingQueue;
-
-import javax.imageio.ImageIO;
-
-import org.apache.commons.imaging.ImageReadException;
-import org.apache.commons.imaging.ImageWriteException;
-import org.apache.commons.imaging.Imaging;
-import org.apache.commons.imaging.common.ImageMetadata;
-import org.apache.commons.imaging.common.RationalNumber;
-import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
-import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter;
-import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
-import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants;
-import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants;
-import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
-import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
-import org.openstreetmap.josm.gui.progress.ProgressMonitor;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryImportedImage;
-
-/**
- * Writes the images from the queue in the file system.
- *
- * @author nokutu
- * @see MapillaryExportManager
- */
-public class MapillaryExportWriterThread extends Thread {
-
-  private final String path;
-  private final ArrayBlockingQueue<BufferedImage> queue;
-  private final ArrayBlockingQueue<MapillaryAbstractImage> queueImages;
-  private final int amount;
-  private final ProgressMonitor monitor;
-
-  /**
-   * Main constructor.
-   *
-   * @param path
-   *          Path to write the pictures.
-   * @param queue
-   *          Queue of {@link MapillaryAbstractImage} objects.
-   * @param queueImages
-   *          Queue of {@link BufferedImage} objects.
-   * @param amount
-   *          Amount of images that are going to be exported.
-   * @param monitor
-   *          Progress monitor.
-   */
-  public MapillaryExportWriterThread(String path,
-      ArrayBlockingQueue<BufferedImage> queue,
-      ArrayBlockingQueue<MapillaryAbstractImage> queueImages, int amount,
-      ProgressMonitor monitor) {
-    this.path = path;
-    this.queue = queue;
-    this.queueImages = queueImages;
-    this.amount = amount;
-    this.monitor = monitor;
-  }
-
-  @Override
-  public void run() {
-    this.monitor.setCustomText("Downloaded 0/" + this.amount);
-    // File tempFile = null;
-    BufferedImage img;
-    MapillaryAbstractImage mimg = null;
-    String finalPath = "";
-    for (int i = 0; i < this.amount; i++) {
-      try {
-        img = this.queue.take();
-        mimg = this.queueImages.take();
-        if (img == null || mimg == null)
-          throw new IllegalStateException("Null image");
-        if (this.path == null && mimg instanceof MapillaryImportedImage) {
-          String path = ((MapillaryImportedImage) mimg).getFile().getPath();
-          finalPath = path.substring(0, path.lastIndexOf('.'));
-        } else if (mimg instanceof MapillaryImage)
-          finalPath = this.path + "/" + ((MapillaryImage) mimg).getKey();
-        else if (mimg instanceof MapillaryImportedImage)
-          finalPath = this.path + "/"
-              + ((MapillaryImportedImage) mimg).getFile().getName();
-        ;
-
-        // Transforms the image into a byte array.
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        ImageIO.write(img, "jpg", outputStream);
-        byte[] imageBytes = outputStream.toByteArray();
-
-        // Write EXIF tags
-        TiffOutputSet outputSet = null;
-        TiffOutputDirectory exifDirectory = null;
-        TiffOutputDirectory gpsDirectory = null;
-        // If the image is imported, loads the rest of the EXIF data.
-        if (mimg instanceof MapillaryImportedImage) {
-          final ImageMetadata metadata = Imaging
-              .getMetadata(((MapillaryImportedImage) mimg).getFile());
-          final JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
-          if (null != jpegMetadata) {
-            final TiffImageMetadata exif = jpegMetadata.getExif();
-            if (null != exif) {
-              outputSet = exif.getOutputSet();
-            }
-          }
-        }
-        if (null == outputSet) {
-          outputSet = new TiffOutputSet();
-        }
-        exifDirectory = outputSet.getOrCreateExifDirectory();
-        gpsDirectory = outputSet.getOrCreateGPSDirectory();
-
-        gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF);
-        gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF,
-            GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF_VALUE_TRUE_NORTH);
-
-        gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION);
-        gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION,
-            RationalNumber.valueOf(mimg.getCa()));
-
-        exifDirectory.removeField(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL);
-        if (mimg instanceof MapillaryImportedImage)
-          exifDirectory.add(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL,
-              ((MapillaryImportedImage) mimg).getDate("yyyy/MM/dd hh:mm:ss"));
-        else if (mimg instanceof MapillaryImage)
-          exifDirectory.add(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL,
-              ((MapillaryImage) mimg).getDate("yyyy/MM/dd hh/mm/ss"));
-        outputSet.setGPSInDegrees(mimg.getLatLon().lon(), mimg.getLatLon()
-            .lat());
-        OutputStream os = new BufferedOutputStream(new FileOutputStream(
-            finalPath + ".jpg"));
-        new ExifRewriter()
-            .updateExifMetadataLossless(imageBytes, os, outputSet);
-
-        os.close();
-      } catch (InterruptedException e) {
-        Main.info("Mapillary export cancelled");
-        return;
-      } catch (IOException e) {
-        Main.error(e);
-      } catch (ImageWriteException e) {
-        Main.error(e);
-      } catch (ImageReadException e) {
-        Main.error(e);
-      }
-
-      // Increases the progress bar.
-      this.monitor.worked(PleaseWaitProgressMonitor.PROGRESS_BAR_MAX
-          / this.amount);
-      this.monitor.setCustomText("Downloaded " + (i + 1) + "/" + this.amount);
-    }
-  }
-}
Index: plications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillaryImageInfoDownloadThread.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillaryImageInfoDownloadThread.java	(revision 31514)
+++ 	(revision )
@@ -1,75 +1,0 @@
-package org.openstreetmap.josm.plugins.mapillary.downloads;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.concurrent.ExecutorService;
-
-import javax.json.Json;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
-
-/**
- * This thread downloads one of the images in a given area.
- *
- * @author nokutu
- * @see MapillarySquareDownloadManagerThread
- */
-public class MapillaryImageInfoDownloadThread extends Thread {
-  private static final String URL = MapillaryDownloader.BASE_URL + "search/im/";
-  private final String queryString;
-  private final ExecutorService ex;
-
-  /**
-   * Main constructor.
-   *
-   * @param ex
-   *          {@link ExecutorService} object that is executing this thread.
-   * @param queryString
-   *          A String containing the parameters for the download.
-   */
-  public MapillaryImageInfoDownloadThread(ExecutorService ex,
-      String queryString) {
-    this.ex = ex;
-    this.queryString = queryString;
-  }
-
-  @Override
-  public void run() {
-    try {
-      BufferedReader br = new BufferedReader(new InputStreamReader(new URL(URL
-          + this.queryString).openStream(), "UTF-8"));
-      JsonObject jsonobj = Json.createReader(br).readObject();
-      if (!jsonobj.getBoolean("more"))
-        this.ex.shutdown();
-      JsonArray jsonarr = jsonobj.getJsonArray("ims");
-      JsonObject data;
-      for (int i = 0; i < jsonarr.size(); i++) {
-        data = jsonarr.getJsonObject(i);
-        String key = data.getString("key");
-        for (MapillaryAbstractImage image : MapillaryLayer.getInstance().getData()
-            .getImages()) {
-          if (image instanceof MapillaryImage) {
-            if (((MapillaryImage) image).getKey().equals(key)
-                && ((MapillaryImage) image).getUser() == null) {
-              ((MapillaryImage) image).setUser(data.getString("user"));
-              ((MapillaryImage) image).setCapturedAt(data.getJsonNumber(
-                  "captured_at").longValue());
-            }
-          }
-        }
-      }
-    } catch (MalformedURLException e) {
-      Main.error(e);
-    } catch (IOException e) {
-      Main.error(e);
-    }
-  }
-}
Index: plications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillarySequenceDownloadThread.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillarySequenceDownloadThread.java	(revision 31514)
+++ 	(revision )
@@ -1,129 +1,0 @@
-package org.openstreetmap.josm.plugins.mapillary.downloads;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-
-import javax.json.Json;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryData;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
-import org.openstreetmap.josm.plugins.mapillary.MapillarySequence;
-
-/**
- * This thread downloads all the pictures in a given sequence and creates the
- * needed MapillaryImage and MapillarySequence objects. It just stores the ones
- * in the given area.
- *
- * @author nokutu
- * @see MapillarySquareDownloadManagerThread
- */
-public class MapillarySequenceDownloadThread extends Thread {
-  private static final String URL = MapillaryDownloader.BASE_URL + "search/s/";
-
-  private final String queryString;
-  private final ExecutorService ex;
-  private final MapillaryLayer layer;
-
-  /**
-   * Main constructor.
-   *
-   * @param ex
-   *          {@link ExecutorService} executing this thread.
-   * @param queryString
-   *          String containing the parameters for the download.
-   */
-  public MapillarySequenceDownloadThread(ExecutorService ex, String queryString) {
-    this.queryString = queryString;
-    this.ex = ex;
-    this.layer = MapillaryLayer.getInstance();
-  }
-
-  @Override
-  public void run() {
-    try {
-      BufferedReader br;
-      br = new BufferedReader(new InputStreamReader(new URL(URL
-          + this.queryString).openStream(), "UTF-8"));
-      JsonObject jsonall = Json.createReader(br).readObject();
-
-      if (!jsonall.getBoolean("more") && !this.ex.isShutdown())
-        this.ex.shutdown();
-      JsonArray jsonseq = jsonall.getJsonArray("ss");
-      boolean isSequenceWrong = false;
-      for (int i = 0; i < jsonseq.size(); i++) {
-        JsonObject jsonobj = jsonseq.getJsonObject(i);
-        JsonArray cas = jsonobj.getJsonArray("cas");
-        JsonArray coords = jsonobj.getJsonArray("coords");
-        JsonArray keys = jsonobj.getJsonArray("keys");
-        ArrayList<MapillaryImage> images = new ArrayList<>();
-        for (int j = 0; j < cas.size(); j++) {
-          try {
-            images.add(new MapillaryImage(keys.getString(j), coords
-                .getJsonArray(j).getJsonNumber(1).doubleValue(), coords
-                .getJsonArray(j).getJsonNumber(0).doubleValue(), cas
-                .getJsonNumber(j).doubleValue()));
-          } catch (IndexOutOfBoundsException e) {
-            Main.warn("Mapillary bug at " + URL + this.queryString);
-            isSequenceWrong = true;
-          }
-        }
-        if (isSequenceWrong)
-          break;
-        MapillarySequence sequence = new MapillarySequence(
-            jsonobj.getString("key"), jsonobj.getJsonNumber("captured_at")
-                .longValue());
-        List<MapillaryImage> finalImages = new ArrayList<>(images);
-        // Here it gets only those images which are in the downloaded
-        // area.
-        for (MapillaryAbstractImage img : images) {
-          if (!isInside(img))
-            finalImages.remove(img);
-        }
-        synchronized (this.getClass()) {
-          synchronized (MapillaryAbstractImage.class) {
-            for (MapillaryImage img : finalImages) {
-              if (this.layer.getData().getImages().contains(img)) {
-                // The image in finalImages is substituted by the one in the
-                // database, as they represent the same picture.
-                img = (MapillaryImage) this.layer.getData().getImages()
-                    .get(this.layer.getData().getImages().indexOf(img));
-                sequence.add(img);
-                ((MapillaryImage) this.layer.getData().getImages()
-                    .get(this.layer.getData().getImages().indexOf(img)))
-                    .setSequence(sequence);
-                finalImages.set(finalImages.indexOf(img), img);
-              } else {
-                img.setSequence(sequence);
-                sequence.add(img);
-              }
-            }
-          }
-        }
-
-        this.layer.getData().add(
-            new ArrayList<MapillaryAbstractImage>(finalImages), false);
-      }
-    } catch (IOException e) {
-      Main.error("Error reading the url " + URL + this.queryString
-          + " might be a Mapillary problem.");
-    }
-    MapillaryData.dataUpdated();
-  }
-
-  private boolean isInside(MapillaryAbstractImage image) {
-    for (int i = 0; i < this.layer.getData().bounds.size(); i++)
-      if (this.layer.getData().bounds.get(i).contains(image.getLatLon()))
-        return true;
-    return false;
-  }
-}
Index: plications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillarySquareDownloadManagerThread.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillarySquareDownloadManagerThread.java	(revision 31514)
+++ 	(revision )
@@ -1,169 +1,0 @@
-package org.openstreetmap.josm.plugins.mapillary.downloads;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.Locale;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryData;
-import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryFilterDialog;
-import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryMainDialog;
-import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryUtils;
-import org.openstreetmap.josm.plugins.mapillary.utils.PluginState;
-
-/**
- * This Class is needed to create an indeterminate amount of downloads, because
- * the Mapillary API has a parameter called page which is needed when the amount
- * of requested images is quite big.
- *
- * @author nokutu
- *
- * @see MapillaryDownloader
- * @see MapillarySequenceDownloadThread
- * @see MapillaryImageInfoDownloadThread
- * @see MapillaryTrafficSignDownloadThread
- */
-public class MapillarySquareDownloadManagerThread extends Thread {
-
-  private final String imageQueryString;
-  private final String sequenceQueryString;
-  private final String signQueryString;
-
-  private ThreadPoolExecutor downloadExecutor = new ThreadPoolExecutor(3, 5,
-      25, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5));
-  private ThreadPoolExecutor completeExecutor = new ThreadPoolExecutor(3, 5,
-      25, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5));
-  private ThreadPoolExecutor signsExecutor = new ThreadPoolExecutor(3, 5, 25,
-      TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5));
-
-  /**
-   * Main constructor.
-   *
-   * @param queryStringParts
-   *          The query data.
-   *
-   */
-  public MapillarySquareDownloadManagerThread(
-      ConcurrentHashMap<String, Double> queryStringParts) {
-    this.imageQueryString = buildQueryString(queryStringParts);
-    this.sequenceQueryString = buildQueryString(queryStringParts);
-    this.signQueryString = buildQueryString(queryStringParts);
-
-    // TODO: Move this line to the appropriate place, here's no GET-request
-    Main.info("GET " + this.sequenceQueryString + " (Mapillary plugin)");
-  }
-
-  // TODO: Maybe move into a separate utility class?
-  private static String buildQueryString(ConcurrentHashMap<String, Double> hash) {
-    StringBuilder ret = new StringBuilder("?client_id="
-        + MapillaryDownloader.CLIENT_ID);
-    for (String key : hash.keySet())
-      if (key != null)
-        try {
-          ret.append("&" + URLEncoder.encode(key, "UTF-8")).append(
-              "="
-                  + URLEncoder.encode(
-                      String.format(Locale.UK, "%f", hash.get(key)), "UTF-8"));
-        } catch (UnsupportedEncodingException e) {
-          // This should not happen, as the encoding is hard-coded
-        }
-    return ret.toString();
-  }
-
-  @Override
-  public void run() {
-    try {
-      PluginState.startDownload();
-      MapillaryUtils.updateHelpText();
-      downloadSequences();
-      completeImages();
-      MapillaryMainDialog.getInstance().updateTitle();
-      downloadSigns();
-    } catch (InterruptedException e) {
-      Main.error("Mapillary download interrupted (probably because of closing the layer).");
-    } finally {
-      PluginState.finishDownload();
-    }
-    MapillaryUtils.updateHelpText();
-    MapillaryData.dataUpdated();
-    MapillaryFilterDialog.getInstance().refresh();
-    MapillaryMainDialog.getInstance().updateImage();
-  }
-
-  /**
-   * Downloads the sequence positions, directions and keys.
-   *
-   * @throws InterruptedException
-   *           if the thread is interrupted while running this method.
-   */
-  private void downloadSequences() throws InterruptedException {
-    int page = 0;
-    while (!this.downloadExecutor.isShutdown()) {
-      this.downloadExecutor.execute(new MapillarySequenceDownloadThread(
-          this.downloadExecutor, this.sequenceQueryString + "&page=" + page
-              + "&limit=10"));
-      while (this.downloadExecutor.getQueue().remainingCapacity() == 0)
-        Thread.sleep(500);
-      page++;
-    }
-    this.downloadExecutor.awaitTermination(15, TimeUnit.SECONDS);
-    MapillaryData.dataUpdated();
-  }
-
-  /**
-   * Downloads the image's author's username and the image's location.
-   *
-   * @throws InterruptedException
-   *           if the thread is interrupted while running this method.
-   */
-  private void completeImages() throws InterruptedException {
-    int page = 0;
-    while (!this.completeExecutor.isShutdown()) {
-      this.completeExecutor.execute(new MapillaryImageInfoDownloadThread(
-          this.completeExecutor, this.imageQueryString + "&page=" + page
-              + "&limit=20"));
-      while (this.completeExecutor.getQueue().remainingCapacity() == 0)
-        Thread.sleep(100);
-      page++;
-    }
-    this.completeExecutor.awaitTermination(15, TimeUnit.SECONDS);
-  }
-
-  /**
-   * Downloads the traffic signs in the images.
-   *
-   * @throws InterruptedException
-   *           if the thread is interrupted while running this method.
-   */
-  private void downloadSigns() throws InterruptedException {
-    int page = 0;
-    while (!this.signsExecutor.isShutdown()) {
-      this.signsExecutor.execute(new MapillaryTrafficSignDownloadThread(
-          this.signsExecutor, this.signQueryString + "&page=" + page
-              + "&limit=20"));
-      while (this.signsExecutor.getQueue().remainingCapacity() == 0)
-        Thread.sleep(100);
-      page++;
-    }
-    this.signsExecutor.awaitTermination(15, TimeUnit.SECONDS);
-  }
-
-  @Override
-  public void interrupt() {
-    super.interrupt();
-    this.downloadExecutor.shutdownNow();
-    this.completeExecutor.shutdownNow();
-    this.signsExecutor.shutdownNow();
-    try {
-      this.downloadExecutor.awaitTermination(15, TimeUnit.SECONDS);
-      this.completeExecutor.awaitTermination(15, TimeUnit.SECONDS);
-      this.signsExecutor.awaitTermination(15, TimeUnit.SECONDS);
-    } catch (InterruptedException e) {
-      Main.error(e);
-    }
-  }
-}
Index: plications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillaryTrafficSignDownloadThread.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/downloads/MapillaryTrafficSignDownloadThread.java	(revision 31514)
+++ 	(revision )
@@ -1,97 +1,0 @@
-package org.openstreetmap.josm.plugins.mapillary.downloads;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.concurrent.ExecutorService;
-
-import javax.json.Json;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
-
-/**
- * Downloads the signs information in a given area.
- *
- * @author nokutu
- *
- */
-public class MapillaryTrafficSignDownloadThread extends Thread {
-  private static final String URL = MapillaryDownloader.BASE_URL
-      + "search/im/or/";
-  private final String queryString;
-  private final ExecutorService ex;
-
-  /**
-   * Main constructor.
-   *
-   * @param ex
-   *          {@link ExecutorService} object that is executing this thread.
-   * @param queryString
-   *          A String containing the parameter for the download.
-   */
-  public MapillaryTrafficSignDownloadThread(ExecutorService ex,
-      String queryString) {
-    this.ex = ex;
-    this.queryString = queryString;
-  }
-
-  @Override
-  public void run() {
-    BufferedReader br;
-    try {
-      br = new BufferedReader(new InputStreamReader(new URL(URL
-          + this.queryString).openStream(), "UTF-8"));
-      JsonObject jsonobj = Json.createReader(br).readObject();
-      if (!jsonobj.getBoolean("more")) {
-        this.ex.shutdown();
-      }
-      JsonArray jsonarr = jsonobj.getJsonArray("ims");
-      for (int i = 0; i < jsonarr.size(); i++) {
-        JsonArray rects = jsonarr.getJsonObject(i).getJsonArray("rects");
-        JsonArray rectversions = jsonarr.getJsonObject(i).getJsonArray(
-            "rectversions");
-        String key = jsonarr.getJsonObject(i).getString("key");
-        if (rectversions != null) {
-          for (int j = 0; j < rectversions.size(); j++) {
-            rects = rectversions.getJsonObject(j).getJsonArray("rects");
-            for (int k = 0; k < rects.size(); k++) {
-              JsonObject data = rects.getJsonObject(k);
-              for (MapillaryAbstractImage image : MapillaryLayer.getInstance()
-                  .getData().getImages())
-                if (image instanceof MapillaryImage
-                    && ((MapillaryImage) image).getKey().equals(key))
-                  try {
-                    ((MapillaryImage) image).addSign(data.getString("type"));
-                  } catch (Exception e) {
-                    Main.error("Error when downloading sign.");
-                  }
-            }
-          }
-        }
-
-        // Just one sign on the picture
-        else if (rects != null) {
-          for (int j = 0; j < rects.size(); j++) {
-            JsonObject data = rects.getJsonObject(j);
-            for (MapillaryAbstractImage image : MapillaryLayer.getInstance()
-                .getData().getImages())
-              if (image instanceof MapillaryImage
-                  && ((MapillaryImage) image).getKey().equals(key))
-                ((MapillaryImage) image).addSign(data.getString("type"));
-          }
-        }
-      }
-    } catch (MalformedURLException e) {
-      Main.error(e);
-    } catch (IOException e) {
-      Main.error(e);
-    }
-  }
-}
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/FinishedUploadDialog.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/FinishedUploadDialog.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/FinishedUploadDialog.java	(revision 31515)
@@ -52,6 +52,4 @@
       }
     }
-
   }
-
 }
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryImageDisplay.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryImageDisplay.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryImageDisplay.java	(revision 31515)
@@ -92,5 +92,5 @@
         this.mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
       }
-      // Applicate the zoom to the visible rectangle in image coordinates
+      // Set the zoom to the visible rectangle in image coordinates
       if (e.getWheelRotation() > 0) {
         visibleRect.width = visibleRect.width * 3 / 2;
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryPreferenceSetting.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryPreferenceSetting.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryPreferenceSetting.java	(revision 31515)
@@ -20,5 +20,5 @@
 import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting;
 import org.openstreetmap.josm.plugins.mapillary.MapillaryPlugin;
-import org.openstreetmap.josm.plugins.mapillary.downloads.MapillaryDownloader;
+import org.openstreetmap.josm.plugins.mapillary.io.download.MapillaryDownloader;
 import org.openstreetmap.josm.plugins.mapillary.oauth.MapillaryUser;
 import org.openstreetmap.josm.plugins.mapillary.oauth.OAuthPortListener;
@@ -52,5 +52,4 @@
   public void addGui(PreferenceTabbedPane gui) {
     JPanel panel = new JPanel();
-
     this.reverseButtons.setSelected(Main.pref
         .getBoolean("mapillary.reverse-buttons"));
@@ -60,8 +59,6 @@
     this.moveTo.setSelected(Main.pref.getBoolean("mapillary.move-to-picture",
         true));
-
     panel.setLayout(new FlowLayout(FlowLayout.LEFT));
     panel.add(this.reverseButtons);
-
     // Sets the value of the ComboBox.
     if (Main.pref.get("mapillary.download-mode").equals(
@@ -78,5 +75,4 @@
     downloadModePanel.add(this.downloadMode);
     panel.add(downloadModePanel);
-
     panel.add(this.displayHour);
     panel.add(this.format24);
@@ -88,10 +84,8 @@
       this.login.setText("Logged as: " + MapillaryUser.getUsername()
           + ". Click to relogin.");
-
     panel.add(this.login);
     if (MapillaryUser.getUsername() != null) {
       JButton logout = new JButton(new LogoutAction());
       logout.setText("Logout");
-
       panel.add(logout);
     }
@@ -117,5 +111,4 @@
       MapillaryPlugin.setMenuEnabled(MapillaryPlugin.DOWNLOAD_VIEW_MENU, true);
     }
-
     Main.pref.put("mapillary.display-hour", this.displayHour.isSelected());
     Main.pref.put("mapillary.format-24", this.format24.isSelected());
@@ -143,5 +136,4 @@
       OAuthPortListener portListener = new OAuthPortListener();
       portListener.start();
-
       String url = "http://www.mapillary.com/connect?redirect_uri=http:%2F%2Flocalhost:8763%2F&client_id=T1Fzd20xZjdtR0s1VDk5OFNIOXpYdzoxNDYyOGRkYzUyYTFiMzgz&response_type=token&scope=user:read%20public:upload%20public:write";
       try {
@@ -170,4 +162,3 @@
     }
   }
-
 }
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryUploadDialog.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryUploadDialog.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryUploadDialog.java	(revision 31515)
@@ -16,4 +16,6 @@
 
 /**
+ * JPanel used when uploading pictures.
+ *
  * @author nokutu
  *
@@ -31,5 +33,5 @@
 
   /**
-   * Main constructor.
+   * Creates the JPanel and adds the needed elements.
    */
   public MapillaryUploadDialog() {
@@ -37,5 +39,4 @@
     if (MapillaryUser.getUsername() != null) {
       this.group = new ButtonGroup();
-
       this.sequence = new JRadioButton(tr("Upload selected sequence"));
       if (MapillaryLayer.getInstance().getData().getSelectedImage() == null
@@ -45,12 +46,11 @@
       add(this.sequence);
       this.group.setSelected(this.sequence.getModel(), true);
-
       this.delete = new JCheckBox(tr("Delete after upload"));
       this.delete.setSelected(Main.pref.getBoolean(
           "mapillary.delete-after-upload", true));
       add(this.delete);
-    } else {
-      this.add(new JLabel("Go to setting and log in to Mapillary before uploading."));
-    }
+    } else
+      this.add(new JLabel(
+          "Go to setting and log in to Mapillary before uploading."));
   }
 }
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryDownloader.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryDownloader.java	(revision 31515)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryDownloader.java	(revision 31515)
@@ -0,0 +1,237 @@
+package org.openstreetmap.josm.plugins.mapillary.io.download;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryPlugin;
+
+/**
+ * Class that concentrates all the ways of downloading of the plugin. All the
+ * download petitions will be managed one by one.
+ *
+ * @author nokutu
+ *
+ */
+public class MapillaryDownloader {
+
+  /** Possible download modes. */
+  public static final String[] MODES = new String[] { "Automatic",
+      "Semiautomatic", "Manual" };
+  /** Automatic mode. */
+  public static final int AUTOMATIC = 0;
+  /** Semiautomatic mode. */
+  public static final int SEMIAUTOMATIC = 1;
+  /** Manual mode. */
+  public static final int MANUAL = 2;
+
+  /** All the Threads that have been run. Used to interrupt them properly. */
+  private static ArrayList<Thread> threads = new ArrayList<>();
+
+  /** Max area to be downloaded */
+  public static final double MAX_AREA = Main.pref.getDouble(
+      "mapillary.max-download-area", 0.015);
+
+  /** Base URL of the Mapillary API. */
+  public final static String BASE_URL = "https://a.mapillary.com/v2/";
+  /** Client ID for the app */
+  public final static String CLIENT_ID = "T1Fzd20xZjdtR0s1VDk5OFNIOXpYdzoxNDYyOGRkYzUyYTFiMzgz";
+  /** Executor that will run the petitions. */
+  private static ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(3, 5,
+      100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));;
+
+  /**
+   * Gets all the images in a square. It downloads all the images of all the
+   * sequences that pass through the given rectangle.
+   *
+   * @param minLatLon
+   *          The minimum latitude and longitude of the rectangle.
+   * @param maxLatLon
+   *          The maximum latitude and longitude of the rectangle
+   */
+  public static void getImages(LatLon minLatLon, LatLon maxLatLon) {
+    ConcurrentHashMap<String, Double> queryStringParts = new ConcurrentHashMap<>();
+    queryStringParts.put("min_lat", minLatLon.lat());
+    queryStringParts.put("min_lon", minLatLon.lon());
+    queryStringParts.put("max_lat", maxLatLon.lat());
+    queryStringParts.put("max_lon", maxLatLon.lon());
+    run(new MapillarySquareDownloadManagerThread(queryStringParts));
+  }
+
+  private static void run(Thread t) {
+    threads.add(t);
+    EXECUTOR.execute(t);
+  }
+
+  /**
+   * If some part of the current view has not been downloaded, it is downloaded.
+   *
+   */
+  public static void completeView() {
+    if (getMode() != SEMIAUTOMATIC && getMode() != MANUAL)
+      throw new IllegalStateException("Must be in semiautomatic or manual mode");
+    Bounds view = Main.map.mapView.getRealBounds();
+    if (view.getArea() > MAX_AREA)
+      return;
+    if (isViewDownloaded(view))
+      return;
+    MapillaryLayer.getInstance().getData().bounds.add(view);
+    getImages(view);
+  }
+
+  private static boolean isViewDownloaded(Bounds view) {
+    int n = 15;
+    boolean[][] inside = new boolean[n][n];
+    for (int i = 0; i < n; i++) {
+      for (int j = 0; j < n; j++) {
+        if (isInBounds(new LatLon(view.getMinLat()
+            + (view.getMaxLat() - view.getMinLat()) * ((double) i / n),
+            view.getMinLon() + (view.getMaxLon() - view.getMinLon())
+                * ((double) j / n)))) {
+          inside[i][j] = true;
+        }
+      }
+    }
+    for (int i = 0; i < n; i++) {
+      for (int j = 0; j < n; j++) {
+        if (!inside[i][j])
+          return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Checks if the given {@link LatLon} object lies inside the bounds of the
+   * image.
+   *
+   * @param latlon
+   *          The coordinates to check.
+   * @return true if it lies inside the bounds; false otherwise;
+   */
+  private static boolean isInBounds(LatLon latlon) {
+    for (Bounds bounds : MapillaryLayer.getInstance().getData().bounds) {
+      if (bounds.contains(latlon))
+        return true;
+    }
+    return false;
+  }
+
+  /**
+   * Gets the images within the given bounds.
+   *
+   * @param bounds
+   *          A {@link Bounds} object containing the area to be downloaded.
+   */
+  public static void getImages(Bounds bounds) {
+    getImages(bounds.getMin(), bounds.getMax());
+  }
+
+  /**
+   * Downloads all images of the area covered by the OSM data. This is only just
+   * for automatic download.
+   */
+  public static void automaticDownload() {
+    MapillaryLayer layer = MapillaryLayer.getInstance();
+    if (isAreaTooBig()) {
+      tooBigErrorDialog();
+      return;
+    }
+
+    if (getMode() != AUTOMATIC)
+      throw new IllegalStateException("Must be in automatic mode.");
+    for (Bounds bounds : Main.map.mapView.getEditLayer().data
+        .getDataSourceBounds()) {
+      if (!layer.getData().bounds.contains(bounds)) {
+        layer.getData().bounds.add(bounds);
+        MapillaryDownloader.getImages(bounds.getMin(), bounds.getMax());
+      }
+    }
+  }
+
+  /**
+   * Checks if the area of the OSM data is too big. This means that probably
+   * lots of Mapillary images are going to be downloaded, slowing down the
+   * program too much. To solve this the automatic is stopped, an alert is shown
+   * and you will have to download areas manually.
+   */
+  private static boolean isAreaTooBig() {
+    double area = 0;
+    for (Bounds bounds : Main.map.mapView.getEditLayer().data
+        .getDataSourceBounds()) {
+      area += bounds.getArea();
+    }
+    if (area > MAX_AREA)
+      return true;
+    return false;
+  }
+
+  /**
+   * Returns the current download mode.
+   *
+   * @return 0 - automatic; 1 - semiautomatic; 2 - manual.
+   */
+  public static int getMode() {
+    if (Main.pref.get("mapillary.download-mode").equals(MODES[0])
+        && (MapillaryLayer.INSTANCE == null || !MapillaryLayer.INSTANCE.TEMP_SEMIAUTOMATIC))
+      return 0;
+    else if (Main.pref.get("mapillary.download-mode").equals(MODES[1])
+        || (MapillaryLayer.INSTANCE != null && MapillaryLayer.getInstance().TEMP_SEMIAUTOMATIC))
+      return 1;
+    else if (Main.pref.get("mapillary.download-mode").equals(MODES[2]))
+      return 2;
+    else if (Main.pref.get("mapillary.download-mode").equals(""))
+      return 0;
+    else
+      throw new IllegalStateException();
+  }
+
+  private static void tooBigErrorDialog() {
+    if (!SwingUtilities.isEventDispatchThread()) {
+      SwingUtilities.invokeLater(new Runnable() {
+        @Override
+        public void run() {
+          tooBigErrorDialog();
+        }
+      });
+    } else {
+      MapillaryLayer.getInstance().TEMP_SEMIAUTOMATIC = true;
+      MapillaryPlugin.setMenuEnabled(MapillaryPlugin.DOWNLOAD_VIEW_MENU, true);
+      JOptionPane
+          .showMessageDialog(
+              Main.parent,
+              tr("The downloaded OSM area is too big. Download mode has been changed to semiautomatic until the layer is restarted."));
+    }
+  }
+
+  /**
+   * Stops all running threads.
+   */
+  public static void stopAll() {
+    for (Thread t : threads) {
+      if (t.isAlive())
+        System.out.println(t);
+      t.interrupt();
+    }
+    threads.clear();
+    EXECUTOR.shutdownNow();
+    try {
+      EXECUTOR.awaitTermination(30, TimeUnit.SECONDS);
+    } catch (InterruptedException e) {
+      Main.error(e);
+    }
+    EXECUTOR = new ThreadPoolExecutor(3, 5, 100, TimeUnit.SECONDS,
+        new ArrayBlockingQueue<Runnable>(100));
+  }
+}
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryImageInfoDownloadThread.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryImageInfoDownloadThread.java	(revision 31515)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryImageInfoDownloadThread.java	(revision 31515)
@@ -0,0 +1,75 @@
+package org.openstreetmap.josm.plugins.mapillary.io.download;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.ExecutorService;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
+
+/**
+ * This thread downloads one of the images in a given area.
+ *
+ * @author nokutu
+ * @see MapillarySquareDownloadManagerThread
+ */
+public class MapillaryImageInfoDownloadThread extends Thread {
+  private static final String URL = MapillaryDownloader.BASE_URL + "search/im/";
+  private final String queryString;
+  private final ExecutorService ex;
+
+  /**
+   * Main constructor.
+   *
+   * @param ex
+   *          {@link ExecutorService} object that is executing this thread.
+   * @param queryString
+   *          A String containing the parameters for the download.
+   */
+  public MapillaryImageInfoDownloadThread(ExecutorService ex,
+      String queryString) {
+    this.ex = ex;
+    this.queryString = queryString;
+  }
+
+  @Override
+  public void run() {
+    try {
+      BufferedReader br = new BufferedReader(new InputStreamReader(new URL(URL
+          + this.queryString).openStream(), "UTF-8"));
+      JsonObject jsonobj = Json.createReader(br).readObject();
+      if (!jsonobj.getBoolean("more"))
+        this.ex.shutdown();
+      JsonArray jsonarr = jsonobj.getJsonArray("ims");
+      JsonObject data;
+      for (int i = 0; i < jsonarr.size(); i++) {
+        data = jsonarr.getJsonObject(i);
+        String key = data.getString("key");
+        for (MapillaryAbstractImage image : MapillaryLayer.getInstance().getData()
+            .getImages()) {
+          if (image instanceof MapillaryImage) {
+            if (((MapillaryImage) image).getKey().equals(key)
+                && ((MapillaryImage) image).getUser() == null) {
+              ((MapillaryImage) image).setUser(data.getString("user"));
+              ((MapillaryImage) image).setCapturedAt(data.getJsonNumber(
+                  "captured_at").longValue());
+            }
+          }
+        }
+      }
+    } catch (MalformedURLException e) {
+      Main.error(e);
+    } catch (IOException e) {
+      Main.error(e);
+    }
+  }
+}
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySequenceDownloadThread.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySequenceDownloadThread.java	(revision 31515)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySequenceDownloadThread.java	(revision 31515)
@@ -0,0 +1,129 @@
+package org.openstreetmap.josm.plugins.mapillary.io.download;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryData;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
+import org.openstreetmap.josm.plugins.mapillary.MapillarySequence;
+
+/**
+ * This thread downloads all the pictures in a given sequence and creates the
+ * needed MapillaryImage and MapillarySequence objects. It just stores the ones
+ * in the given area.
+ *
+ * @author nokutu
+ * @see MapillarySquareDownloadManagerThread
+ */
+public class MapillarySequenceDownloadThread extends Thread {
+  private static final String URL = MapillaryDownloader.BASE_URL + "search/s/";
+
+  private final String queryString;
+  private final ExecutorService ex;
+  private final MapillaryLayer layer;
+
+  /**
+   * Main constructor.
+   *
+   * @param ex
+   *          {@link ExecutorService} executing this thread.
+   * @param queryString
+   *          String containing the parameters for the download.
+   */
+  public MapillarySequenceDownloadThread(ExecutorService ex, String queryString) {
+    this.queryString = queryString;
+    this.ex = ex;
+    this.layer = MapillaryLayer.getInstance();
+  }
+
+  @Override
+  public void run() {
+    try {
+      BufferedReader br;
+      br = new BufferedReader(new InputStreamReader(new URL(URL
+          + this.queryString).openStream(), "UTF-8"));
+      JsonObject jsonall = Json.createReader(br).readObject();
+
+      if (!jsonall.getBoolean("more") && !this.ex.isShutdown())
+        this.ex.shutdown();
+      JsonArray jsonseq = jsonall.getJsonArray("ss");
+      boolean isSequenceWrong = false;
+      for (int i = 0; i < jsonseq.size(); i++) {
+        JsonObject jsonobj = jsonseq.getJsonObject(i);
+        JsonArray cas = jsonobj.getJsonArray("cas");
+        JsonArray coords = jsonobj.getJsonArray("coords");
+        JsonArray keys = jsonobj.getJsonArray("keys");
+        ArrayList<MapillaryImage> images = new ArrayList<>();
+        for (int j = 0; j < cas.size(); j++) {
+          try {
+            images.add(new MapillaryImage(keys.getString(j), coords
+                .getJsonArray(j).getJsonNumber(1).doubleValue(), coords
+                .getJsonArray(j).getJsonNumber(0).doubleValue(), cas
+                .getJsonNumber(j).doubleValue()));
+          } catch (IndexOutOfBoundsException e) {
+            Main.warn("Mapillary bug at " + URL + this.queryString);
+            isSequenceWrong = true;
+          }
+        }
+        if (isSequenceWrong)
+          break;
+        MapillarySequence sequence = new MapillarySequence(
+            jsonobj.getString("key"), jsonobj.getJsonNumber("captured_at")
+                .longValue());
+        List<MapillaryImage> finalImages = new ArrayList<>(images);
+        // Here it gets only those images which are in the downloaded
+        // area.
+        for (MapillaryAbstractImage img : images) {
+          if (!isInside(img))
+            finalImages.remove(img);
+        }
+        synchronized (this.getClass()) {
+          synchronized (MapillaryAbstractImage.class) {
+            for (MapillaryImage img : finalImages) {
+              if (this.layer.getData().getImages().contains(img)) {
+                // The image in finalImages is substituted by the one in the
+                // database, as they represent the same picture.
+                img = (MapillaryImage) this.layer.getData().getImages()
+                    .get(this.layer.getData().getImages().indexOf(img));
+                sequence.add(img);
+                ((MapillaryImage) this.layer.getData().getImages()
+                    .get(this.layer.getData().getImages().indexOf(img)))
+                    .setSequence(sequence);
+                finalImages.set(finalImages.indexOf(img), img);
+              } else {
+                img.setSequence(sequence);
+                sequence.add(img);
+              }
+            }
+          }
+        }
+
+        this.layer.getData().add(
+            new ArrayList<MapillaryAbstractImage>(finalImages), false);
+      }
+    } catch (IOException e) {
+      Main.error("Error reading the url " + URL + this.queryString
+          + " might be a Mapillary problem.");
+    }
+    MapillaryData.dataUpdated();
+  }
+
+  private boolean isInside(MapillaryAbstractImage image) {
+    for (int i = 0; i < this.layer.getData().bounds.size(); i++)
+      if (this.layer.getData().bounds.get(i).contains(image.getLatLon()))
+        return true;
+    return false;
+  }
+}
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySquareDownloadManagerThread.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySquareDownloadManagerThread.java	(revision 31515)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySquareDownloadManagerThread.java	(revision 31515)
@@ -0,0 +1,169 @@
+package org.openstreetmap.josm.plugins.mapillary.io.download;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Locale;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryData;
+import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryFilterDialog;
+import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryMainDialog;
+import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryUtils;
+import org.openstreetmap.josm.plugins.mapillary.utils.PluginState;
+
+/**
+ * This Class is needed to create an indeterminate amount of downloads, because
+ * the Mapillary API has a parameter called page which is needed when the amount
+ * of requested images is quite big.
+ *
+ * @author nokutu
+ *
+ * @see MapillaryDownloader
+ * @see MapillarySequenceDownloadThread
+ * @see MapillaryImageInfoDownloadThread
+ * @see MapillaryTrafficSignDownloadThread
+ */
+public class MapillarySquareDownloadManagerThread extends Thread {
+
+  private final String imageQueryString;
+  private final String sequenceQueryString;
+  private final String signQueryString;
+
+  private ThreadPoolExecutor downloadExecutor = new ThreadPoolExecutor(3, 5,
+      25, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5));
+  private ThreadPoolExecutor completeExecutor = new ThreadPoolExecutor(3, 5,
+      25, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5));
+  private ThreadPoolExecutor signsExecutor = new ThreadPoolExecutor(3, 5, 25,
+      TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5));
+
+  /**
+   * Main constructor.
+   *
+   * @param queryStringParts
+   *          The query data.
+   *
+   */
+  public MapillarySquareDownloadManagerThread(
+      ConcurrentHashMap<String, Double> queryStringParts) {
+    this.imageQueryString = buildQueryString(queryStringParts);
+    this.sequenceQueryString = buildQueryString(queryStringParts);
+    this.signQueryString = buildQueryString(queryStringParts);
+
+    // TODO: Move this line to the appropriate place, here's no GET-request
+    Main.info("GET " + this.sequenceQueryString + " (Mapillary plugin)");
+  }
+
+  // TODO: Maybe move into a separate utility class?
+  private static String buildQueryString(ConcurrentHashMap<String, Double> hash) {
+    StringBuilder ret = new StringBuilder("?client_id="
+        + MapillaryDownloader.CLIENT_ID);
+    for (String key : hash.keySet())
+      if (key != null)
+        try {
+          ret.append("&" + URLEncoder.encode(key, "UTF-8")).append(
+              "="
+                  + URLEncoder.encode(
+                      String.format(Locale.UK, "%f", hash.get(key)), "UTF-8"));
+        } catch (UnsupportedEncodingException e) {
+          // This should not happen, as the encoding is hard-coded
+        }
+    return ret.toString();
+  }
+
+  @Override
+  public void run() {
+    try {
+      PluginState.startDownload();
+      MapillaryUtils.updateHelpText();
+      downloadSequences();
+      completeImages();
+      MapillaryMainDialog.getInstance().updateTitle();
+      downloadSigns();
+    } catch (InterruptedException e) {
+      Main.error("Mapillary download interrupted (probably because of closing the layer).");
+    } finally {
+      PluginState.finishDownload();
+    }
+    MapillaryUtils.updateHelpText();
+    MapillaryData.dataUpdated();
+    MapillaryFilterDialog.getInstance().refresh();
+    MapillaryMainDialog.getInstance().updateImage();
+  }
+
+  /**
+   * Downloads the sequence positions, directions and keys.
+   *
+   * @throws InterruptedException
+   *           if the thread is interrupted while running this method.
+   */
+  private void downloadSequences() throws InterruptedException {
+    int page = 0;
+    while (!this.downloadExecutor.isShutdown()) {
+      this.downloadExecutor.execute(new MapillarySequenceDownloadThread(
+          this.downloadExecutor, this.sequenceQueryString + "&page=" + page
+              + "&limit=10"));
+      while (this.downloadExecutor.getQueue().remainingCapacity() == 0)
+        Thread.sleep(500);
+      page++;
+    }
+    this.downloadExecutor.awaitTermination(15, TimeUnit.SECONDS);
+    MapillaryData.dataUpdated();
+  }
+
+  /**
+   * Downloads the image's author's username and the image's location.
+   *
+   * @throws InterruptedException
+   *           if the thread is interrupted while running this method.
+   */
+  private void completeImages() throws InterruptedException {
+    int page = 0;
+    while (!this.completeExecutor.isShutdown()) {
+      this.completeExecutor.execute(new MapillaryImageInfoDownloadThread(
+          this.completeExecutor, this.imageQueryString + "&page=" + page
+              + "&limit=20"));
+      while (this.completeExecutor.getQueue().remainingCapacity() == 0)
+        Thread.sleep(100);
+      page++;
+    }
+    this.completeExecutor.awaitTermination(15, TimeUnit.SECONDS);
+  }
+
+  /**
+   * Downloads the traffic signs in the images.
+   *
+   * @throws InterruptedException
+   *           if the thread is interrupted while running this method.
+   */
+  private void downloadSigns() throws InterruptedException {
+    int page = 0;
+    while (!this.signsExecutor.isShutdown()) {
+      this.signsExecutor.execute(new MapillaryTrafficSignDownloadThread(
+          this.signsExecutor, this.signQueryString + "&page=" + page
+              + "&limit=20"));
+      while (this.signsExecutor.getQueue().remainingCapacity() == 0)
+        Thread.sleep(100);
+      page++;
+    }
+    this.signsExecutor.awaitTermination(15, TimeUnit.SECONDS);
+  }
+
+  @Override
+  public void interrupt() {
+    super.interrupt();
+    this.downloadExecutor.shutdownNow();
+    this.completeExecutor.shutdownNow();
+    this.signsExecutor.shutdownNow();
+    try {
+      this.downloadExecutor.awaitTermination(15, TimeUnit.SECONDS);
+      this.completeExecutor.awaitTermination(15, TimeUnit.SECONDS);
+      this.signsExecutor.awaitTermination(15, TimeUnit.SECONDS);
+    } catch (InterruptedException e) {
+      Main.error(e);
+    }
+  }
+}
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryTrafficSignDownloadThread.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryTrafficSignDownloadThread.java	(revision 31515)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryTrafficSignDownloadThread.java	(revision 31515)
@@ -0,0 +1,97 @@
+package org.openstreetmap.josm.plugins.mapillary.io.download;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.ExecutorService;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
+
+/**
+ * Downloads the signs information in a given area.
+ *
+ * @author nokutu
+ *
+ */
+public class MapillaryTrafficSignDownloadThread extends Thread {
+  private static final String URL = MapillaryDownloader.BASE_URL
+      + "search/im/or/";
+  private final String queryString;
+  private final ExecutorService ex;
+
+  /**
+   * Main constructor.
+   *
+   * @param ex
+   *          {@link ExecutorService} object that is executing this thread.
+   * @param queryString
+   *          A String containing the parameter for the download.
+   */
+  public MapillaryTrafficSignDownloadThread(ExecutorService ex,
+      String queryString) {
+    this.ex = ex;
+    this.queryString = queryString;
+  }
+
+  @Override
+  public void run() {
+    BufferedReader br;
+    try {
+      br = new BufferedReader(new InputStreamReader(new URL(URL
+          + this.queryString).openStream(), "UTF-8"));
+      JsonObject jsonobj = Json.createReader(br).readObject();
+      if (!jsonobj.getBoolean("more")) {
+        this.ex.shutdown();
+      }
+      JsonArray jsonarr = jsonobj.getJsonArray("ims");
+      for (int i = 0; i < jsonarr.size(); i++) {
+        JsonArray rects = jsonarr.getJsonObject(i).getJsonArray("rects");
+        JsonArray rectversions = jsonarr.getJsonObject(i).getJsonArray(
+            "rectversions");
+        String key = jsonarr.getJsonObject(i).getString("key");
+        if (rectversions != null) {
+          for (int j = 0; j < rectversions.size(); j++) {
+            rects = rectversions.getJsonObject(j).getJsonArray("rects");
+            for (int k = 0; k < rects.size(); k++) {
+              JsonObject data = rects.getJsonObject(k);
+              for (MapillaryAbstractImage image : MapillaryLayer.getInstance()
+                  .getData().getImages())
+                if (image instanceof MapillaryImage
+                    && ((MapillaryImage) image).getKey().equals(key))
+                  try {
+                    ((MapillaryImage) image).addSign(data.getString("type"));
+                  } catch (Exception e) {
+                    Main.error("Error when downloading sign.");
+                  }
+            }
+          }
+        }
+
+        // Just one sign on the picture
+        else if (rects != null) {
+          for (int j = 0; j < rects.size(); j++) {
+            JsonObject data = rects.getJsonObject(j);
+            for (MapillaryAbstractImage image : MapillaryLayer.getInstance()
+                .getData().getImages())
+              if (image instanceof MapillaryImage
+                  && ((MapillaryImage) image).getKey().equals(key))
+                ((MapillaryImage) image).addSign(data.getString("type"));
+          }
+        }
+      }
+    } catch (MalformedURLException e) {
+      Main.error(e);
+    } catch (IOException e) {
+      Main.error(e);
+    }
+  }
+}
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/export/MapillaryExportDownloadThread.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/export/MapillaryExportDownloadThread.java	(revision 31515)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/export/MapillaryExportDownloadThread.java	(revision 31515)
@@ -0,0 +1,75 @@
+package org.openstreetmap.josm.plugins.mapillary.io.export;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import javax.imageio.ImageIO;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.cache.CacheEntry;
+import org.openstreetmap.josm.data.cache.CacheEntryAttributes;
+import org.openstreetmap.josm.data.cache.ICachedLoaderListener;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
+import org.openstreetmap.josm.plugins.mapillary.cache.MapillaryCache;
+
+/**
+ * This is the thread that downloads one of the images that are going to be
+ * exported and writes them in a {@link ArrayBlockingQueue}.
+ *
+ * @author nokutu
+ * @see MapillaryExportManager
+ * @see MapillaryExportWriterThread
+ */
+public class MapillaryExportDownloadThread extends Thread implements
+    ICachedLoaderListener {
+
+  private ArrayBlockingQueue<BufferedImage> queue;
+  private ArrayBlockingQueue<MapillaryAbstractImage> queueImages;
+
+  private MapillaryImage image;
+
+  /**
+   * Main constructor.
+   *
+   * @param image
+   *          Image to be downloaded.
+   * @param queue
+   *          Queue of {@link BufferedImage} objects for the
+   *          {@link MapillaryExportWriterThread}.
+   * @param queueImages
+   *          Queue of {@link MapillaryAbstractImage} objects for the
+   *          {@link MapillaryExportWriterThread}.
+   */
+  public MapillaryExportDownloadThread(MapillaryImage image,
+      ArrayBlockingQueue<BufferedImage> queue,
+      ArrayBlockingQueue<MapillaryAbstractImage> queueImages) {
+    this.queue = queue;
+    this.image = image;
+    this.queueImages = queueImages;
+  }
+
+  @Override
+  public void run() {
+    new MapillaryCache(this.image.getKey(), MapillaryCache.Type.FULL_IMAGE)
+        .submit(this, false);
+  }
+
+  @Override
+  public synchronized void loadingFinished(CacheEntry data,
+      CacheEntryAttributes attributes, LoadResult result) {
+    try {
+      synchronized (this.getClass()) {
+        this.queue
+            .put(ImageIO.read(new ByteArrayInputStream(data.getContent())));
+        this.queueImages.put(this.image);
+      }
+    } catch (InterruptedException e) {
+      Main.error(e);
+    } catch (IOException e) {
+      Main.error(e);
+    }
+  }
+}
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/export/MapillaryExportManager.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/export/MapillaryExportManager.java	(revision 31515)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/export/MapillaryExportManager.java	(revision 31515)
@@ -0,0 +1,145 @@
+package org.openstreetmap.josm.plugins.mapillary.io.export;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryImportedImage;
+import org.xml.sax.SAXException;
+
+/**
+ * Export main thread. Exportation works by creating a
+ * {@link MapillaryExportWriterThread} and several
+ * {@link MapillaryExportDownloadThread}. The second ones download every single
+ * image that is going to be exported and stores them in an
+ * {@link ArrayBlockingQueue}. Then it is picked by the first one and written on
+ * the selected folder. Each image will be named by its key.
+ *
+ * @author nokutu
+ * @see MapillaryExportWriterThread
+ * @see MapillaryExportDownloadThread
+ */
+public class MapillaryExportManager extends PleaseWaitRunnable {
+
+  private ArrayBlockingQueue<BufferedImage> queue;
+  private ArrayBlockingQueue<MapillaryAbstractImage> queueImages;
+
+  private final int amount;
+  private List<MapillaryAbstractImage> images;
+  private String path;
+
+  private Thread writer;
+  private ThreadPoolExecutor ex;
+
+  /**
+   * Main constructor.
+   *
+   * @param images
+   *          Set of {@link MapillaryAbstractImage} objects to be exported.
+   * @param path
+   *          Export path.
+   */
+  public MapillaryExportManager(List<MapillaryAbstractImage> images, String path) {
+    super(tr("Downloading") + "...", new PleaseWaitProgressMonitor(
+        "Exporting Mapillary Images"), true);
+    this.queue = new ArrayBlockingQueue<>(10);
+    this.queueImages = new ArrayBlockingQueue<>(10);
+
+    this.images = images;
+    this.amount = images.size();
+    this.path = path;
+  }
+
+  /**
+   * Constructor used to rewrite imported images.
+   *
+   * @param images
+   *          The set of {@link MapillaryImportedImage} object that is going to
+   *          be rewritten.
+   * @throws IOException
+   *           If the file of one of the {@link MapillaryImportedImage} objects
+   *           doesn't contain a picture.
+   */
+  public MapillaryExportManager(List<MapillaryImportedImage> images)
+      throws IOException {
+    super(tr("Downloading") + "...", new PleaseWaitProgressMonitor(
+        "Exporting Mapillary Images"), true);
+    this.queue = new ArrayBlockingQueue<>(10);
+    this.queueImages = new ArrayBlockingQueue<>(10);
+    for (MapillaryImportedImage image : images) {
+      this.queue.add(image.getImage());
+      this.queueImages.add(image);
+    }
+    this.amount = images.size();
+  }
+
+  @Override
+  protected void cancel() {
+    this.writer.interrupt();
+    this.ex.shutdown();
+  }
+
+  @Override
+  protected void realRun() throws SAXException, IOException,
+      OsmTransferException {
+    // Starts a writer thread in order to write the pictures on the disk.
+    this.writer = new MapillaryExportWriterThread(this.path, this.queue,
+        this.queueImages, this.amount, this.getProgressMonitor());
+    this.writer.start();
+    if (this.path == null) {
+      try {
+        this.writer.join();
+      } catch (InterruptedException e) {
+        Main.error(e);
+      }
+      return;
+    }
+    this.ex = new ThreadPoolExecutor(20, 35, 25, TimeUnit.SECONDS,
+        new ArrayBlockingQueue<Runnable>(10));
+    for (MapillaryAbstractImage image : this.images) {
+      if (image instanceof MapillaryImage) {
+        try {
+          this.ex.execute(new MapillaryExportDownloadThread(
+              (MapillaryImage) image, this.queue, this.queueImages));
+        } catch (Exception e) {
+          Main.error(e);
+        }
+      } else if (image instanceof MapillaryImportedImage) {
+        try {
+          this.queue.put(((MapillaryImportedImage) image).getImage());
+          this.queueImages.put(image);
+        } catch (InterruptedException e) {
+          Main.error(e);
+        }
+      }
+      try {
+        // If the queue is full, waits for it to have more space
+        // available before executing anything else.
+        while (this.ex.getQueue().remainingCapacity() == 0)
+          Thread.sleep(100);
+      } catch (Exception e) {
+        Main.error(e);
+      }
+    }
+    try {
+      this.writer.join();
+    } catch (InterruptedException e) {
+      Main.error(e);
+    }
+  }
+
+  @Override
+  protected void finish() {
+  }
+}
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/export/MapillaryExportWriterThread.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/export/MapillaryExportWriterThread.java	(revision 31515)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/export/MapillaryExportWriterThread.java	(revision 31515)
@@ -0,0 +1,161 @@
+package org.openstreetmap.josm.plugins.mapillary.io.export;
+
+import java.awt.image.BufferedImage;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import javax.imageio.ImageIO;
+
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.ImageWriteException;
+import org.apache.commons.imaging.Imaging;
+import org.apache.commons.imaging.common.ImageMetadata;
+import org.apache.commons.imaging.common.RationalNumber;
+import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
+import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter;
+import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
+import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants;
+import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants;
+import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
+import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
+import org.openstreetmap.josm.plugins.mapillary.MapillaryImportedImage;
+
+/**
+ * Writes the images from the queue in the file system.
+ *
+ * @author nokutu
+ * @see MapillaryExportManager
+ */
+public class MapillaryExportWriterThread extends Thread {
+
+  private final String path;
+  private final ArrayBlockingQueue<BufferedImage> queue;
+  private final ArrayBlockingQueue<MapillaryAbstractImage> queueImages;
+  private final int amount;
+  private final ProgressMonitor monitor;
+
+  /**
+   * Main constructor.
+   *
+   * @param path
+   *          Path to write the pictures.
+   * @param queue
+   *          Queue of {@link MapillaryAbstractImage} objects.
+   * @param queueImages
+   *          Queue of {@link BufferedImage} objects.
+   * @param amount
+   *          Amount of images that are going to be exported.
+   * @param monitor
+   *          Progress monitor.
+   */
+  public MapillaryExportWriterThread(String path,
+      ArrayBlockingQueue<BufferedImage> queue,
+      ArrayBlockingQueue<MapillaryAbstractImage> queueImages, int amount,
+      ProgressMonitor monitor) {
+    this.path = path;
+    this.queue = queue;
+    this.queueImages = queueImages;
+    this.amount = amount;
+    this.monitor = monitor;
+  }
+
+  @Override
+  public void run() {
+    this.monitor.setCustomText("Downloaded 0/" + this.amount);
+    // File tempFile = null;
+    BufferedImage img;
+    MapillaryAbstractImage mimg = null;
+    String finalPath = "";
+    for (int i = 0; i < this.amount; i++) {
+      try {
+        img = this.queue.take();
+        mimg = this.queueImages.take();
+        if (img == null || mimg == null)
+          throw new IllegalStateException("Null image");
+        if (this.path == null && mimg instanceof MapillaryImportedImage) {
+          String path = ((MapillaryImportedImage) mimg).getFile().getPath();
+          finalPath = path.substring(0, path.lastIndexOf('.'));
+        } else if (mimg instanceof MapillaryImage)
+          finalPath = this.path + "/" + ((MapillaryImage) mimg).getKey();
+        else if (mimg instanceof MapillaryImportedImage)
+          finalPath = this.path + "/"
+              + ((MapillaryImportedImage) mimg).getFile().getName();
+        ;
+
+        // Transforms the image into a byte array.
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ImageIO.write(img, "jpg", outputStream);
+        byte[] imageBytes = outputStream.toByteArray();
+
+        // Write EXIF tags
+        TiffOutputSet outputSet = null;
+        TiffOutputDirectory exifDirectory = null;
+        TiffOutputDirectory gpsDirectory = null;
+        // If the image is imported, loads the rest of the EXIF data.
+        if (mimg instanceof MapillaryImportedImage) {
+          final ImageMetadata metadata = Imaging
+              .getMetadata(((MapillaryImportedImage) mimg).getFile());
+          final JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
+          if (null != jpegMetadata) {
+            final TiffImageMetadata exif = jpegMetadata.getExif();
+            if (null != exif) {
+              outputSet = exif.getOutputSet();
+            }
+          }
+        }
+        if (null == outputSet) {
+          outputSet = new TiffOutputSet();
+        }
+        exifDirectory = outputSet.getOrCreateExifDirectory();
+        gpsDirectory = outputSet.getOrCreateGPSDirectory();
+
+        gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF);
+        gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF,
+            GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF_VALUE_TRUE_NORTH);
+
+        gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION);
+        gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION,
+            RationalNumber.valueOf(mimg.getCa()));
+
+        exifDirectory.removeField(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL);
+        if (mimg instanceof MapillaryImportedImage)
+          exifDirectory.add(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL,
+              ((MapillaryImportedImage) mimg).getDate("yyyy/MM/dd hh:mm:ss"));
+        else if (mimg instanceof MapillaryImage)
+          exifDirectory.add(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL,
+              ((MapillaryImage) mimg).getDate("yyyy/MM/dd hh/mm/ss"));
+        outputSet.setGPSInDegrees(mimg.getLatLon().lon(), mimg.getLatLon()
+            .lat());
+        OutputStream os = new BufferedOutputStream(new FileOutputStream(
+            finalPath + ".jpg"));
+        new ExifRewriter()
+            .updateExifMetadataLossless(imageBytes, os, outputSet);
+
+        os.close();
+      } catch (InterruptedException e) {
+        Main.info("Mapillary export cancelled");
+        return;
+      } catch (IOException e) {
+        Main.error(e);
+      } catch (ImageWriteException e) {
+        Main.error(e);
+      } catch (ImageReadException e) {
+        Main.error(e);
+      }
+
+      // Increases the progress bar.
+      this.monitor.worked(PleaseWaitProgressMonitor.PROGRESS_BAR_MAX
+          / this.amount);
+      this.monitor.setCustomText("Downloaded " + (i + 1) + "/" + this.amount);
+    }
+  }
+}
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/mode/AbstractMode.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/mode/AbstractMode.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/mode/AbstractMode.java	(revision 31515)
@@ -14,5 +14,5 @@
 import org.openstreetmap.josm.plugins.mapillary.MapillaryData;
 import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
-import org.openstreetmap.josm.plugins.mapillary.downloads.MapillaryDownloader;
+import org.openstreetmap.josm.plugins.mapillary.io.download.MapillaryDownloader;
 
 /**
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/mode/SelectMode.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/mode/SelectMode.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/mode/SelectMode.java	(revision 31515)
@@ -36,5 +36,4 @@
   private MapillaryAbstractImage lastClicked;
   private MapillaryRecord record;
-
   private boolean nothingHighlighted;
   private boolean imageHighlighted = false;
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/oauth/MapillaryUser.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/oauth/MapillaryUser.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/oauth/MapillaryUser.java	(revision 31515)
@@ -51,6 +51,6 @@
       return null;
     HashMap<String, String> hash = new HashMap<>();
-    if (images_hash == null)
-      try {
+    try {
+      if (images_hash == null)
         images_hash = OAuthUtils
             .getWithHeader(
@@ -58,11 +58,6 @@
                     "https://a.mapillary.com/v2/me/uploads/secrets?client_id=T1Fzd20xZjdtR0s1VDk5OFNIOXpYdzoxNDYyOGRkYzUyYTFiMzgz"))
             .getString("images_hash");
-      } catch (IOException e) {
-        Main.error(e);
-        isTokenValid = false;
-      }
-    hash.put("images_hash", images_hash);
-    if (images_policy == null)
-      try {
+      hash.put("images_hash", images_hash);
+      if (images_policy == null)
         images_policy = OAuthUtils
             .getWithHeader(
@@ -70,8 +65,8 @@
                     "https://a.mapillary.com/v2/me/uploads/secrets?client_id=T1Fzd20xZjdtR0s1VDk5OFNIOXpYdzoxNDYyOGRkYzUyYTFiMzgz"))
             .getString("images_policy");
-      } catch (IOException e) {
-        Main.error(e);
-        isTokenValid = false;
-      }
+    } catch (IOException e) {
+      Main.error(e);
+      isTokenValid = false;
+    }
     hash.put("images_policy", images_policy);
     return hash;
@@ -85,5 +80,6 @@
     images_policy = null;
     images_hash = null;
-    isTokenValid = true;
+    isTokenValid = false;
+    Main.pref.put("mapillary.access-token", null);
   }
 }
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/oauth/OAuthPortListener.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/oauth/OAuthPortListener.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/oauth/OAuthPortListener.java	(revision 31515)
@@ -63,4 +63,5 @@
           + accessToken);
       // Saves the access token in preferences.
+      MapillaryUser.isTokenValid = true;
       if (Main.main != null) {
         Main.pref.put("mapillary.access-token", accessToken);
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/oauth/UploadUtils.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/oauth/UploadUtils.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/oauth/UploadUtils.java	(revision 31515)
@@ -189,7 +189,5 @@
     ImageIO.write(image.getImage(), "jpg", outputStream);
     byte[] imageBytes = outputStream.toByteArray();
-
     new ExifRewriter().updateExifMetadataLossless(imageBytes, os, outputSet);
-
     return tempFile;
   }
@@ -203,5 +201,4 @@
   public static void upload(MapillaryImportedImage image) {
     upload(image, UUID.randomUUID());
-
   }
 
@@ -228,5 +225,4 @@
     hash.put("signature", signature);
     hash.put("Content-Type", "image/jpeg");
-
     try {
       uploadFile(updateFile(image), hash);
@@ -257,5 +253,4 @@
     }
     entityBuilder.addPart("file", new FileBody(file));
-
     HttpEntity entity = entityBuilder.build();
     httpPost.setEntity(entity);
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/utils/PluginState.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/utils/PluginState.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/utils/PluginState.java	(revision 31515)
@@ -99,7 +99,7 @@
 
   /**
-   * Returns the string to be written in the status bar.
+   * Returns the text to be written in the status bar.
    *
-   * @return The String that is going to be written in the status bar.
+   * @return The {@code String} that is going to be written in the status bar.
    */
   public static String getUploadString() {
Index: /applications/editors/josm/plugins/mapillary/test/unit/org/openstreetmap/josm/plugins/mapillary/downloads/MapillarySequenceDownloadThreadTest.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/test/unit/org/openstreetmap/josm/plugins/mapillary/downloads/MapillarySequenceDownloadThreadTest.java	(revision 31514)
+++ /applications/editors/josm/plugins/mapillary/test/unit/org/openstreetmap/josm/plugins/mapillary/downloads/MapillarySequenceDownloadThreadTest.java	(revision 31515)
@@ -15,6 +15,6 @@
 import org.openstreetmap.josm.plugins.mapillary.AbstractTest;
 import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
-import org.openstreetmap.josm.plugins.mapillary.downloads.MapillaryDownloader;
-import org.openstreetmap.josm.plugins.mapillary.downloads.MapillarySequenceDownloadThread;
+import org.openstreetmap.josm.plugins.mapillary.io.download.MapillaryDownloader;
+import org.openstreetmap.josm.plugins.mapillary.io.download.MapillarySequenceDownloadThread;
 
 /**
@@ -25,5 +25,5 @@
   /**
    * Test method for
-   * {@link org.openstreetmap.josm.plugins.mapillary.downloads.MapillarySequenceDownloadThread#run()}
+   * {@link org.openstreetmap.josm.plugins.mapillary.io.download.MapillarySequenceDownloadThread#run()}
    * .
    *
