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 31827)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryPlugin.java	(revision 31828)
@@ -41,5 +41,4 @@
  */
 public class MapillaryPlugin extends Plugin {
-  public static final String CLIENT_ID = "T1Fzd20xZjdtR0s1VDk5OFNIOXpYdzoxNDYyOGRkYzUyYTFiMzgz";
 
   /** OS route separator */
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 31827)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/FinishedUploadDialog.java	(revision 31828)
@@ -16,4 +16,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL;
 import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryUtils;
 import org.openstreetmap.josm.plugins.mapillary.utils.PluginState;
@@ -44,9 +45,8 @@
 
   private class WebAction implements ActionListener {
-
     @Override
     public void actionPerformed(ActionEvent e) {
       try {
-        MapillaryUtils.browse(new URL("http://www.mapillary.com/map/upload/im"));
+        MapillaryUtils.browse(MapillaryURL.browseUploadImageURL());
       } catch (IOException e1) {
         Main.error(e1);
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/HyperlinkLabel.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/HyperlinkLabel.java	(revision 31827)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/HyperlinkLabel.java	(revision 31828)
@@ -12,5 +12,4 @@
 import java.awt.event.MouseEvent;
 import java.io.IOException;
-import java.net.MalformedURLException;
 import java.net.URL;
 
@@ -21,4 +20,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL;
 import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryUtils;
 
@@ -60,13 +60,12 @@
    *
    * @param key The key of the image that the hyperlink will point to.
-   * @throws MalformedURLException when the key appended to the base URL forms a malformed URL
    */
-  public void setURL(String key) throws MalformedURLException {
+  public void setURL(String key) {
     this.key = key;
     if (key == null) {
       this.url = null;
-      return;
+    } else {
+      this.url = MapillaryURL.browseImageURL(key);
     }
-    this.url = new URL("http://www.mapillary.com/map/im/" + key);
   }
 
@@ -135,5 +134,5 @@
         public void actionPerformed(ActionEvent paramActionEvent) {
           try {
-            MapillaryUtils.browse(new URL("http://www.mapillary.com/map/e/" + key));
+            MapillaryUtils.browse(MapillaryURL.browseEditURL(key));
           } catch (IOException e) {
             Main.error(e);
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryMainDialog.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryMainDialog.java	(revision 31827)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryMainDialog.java	(revision 31828)
@@ -238,9 +238,5 @@
         this.mapillaryImageDisplay.hyperlink.setVisible(true);
         MapillaryImage mapillaryImage = (MapillaryImage) this.image;
-        try {
-          this.mapillaryImageDisplay.hyperlink.setURL(mapillaryImage.getKey());
-        } catch (MalformedURLException e1) {
-          Main.error(e1);
-        }
+        this.mapillaryImageDisplay.hyperlink.setURL(mapillaryImage.getKey());
         // Downloads the thumbnail.
         this.mapillaryImageDisplay.setImage(null);
@@ -269,9 +265,5 @@
       } else if (this.image instanceof MapillaryImportedImage) {
         this.mapillaryImageDisplay.hyperlink.setVisible(false);
-        try {
-          this.mapillaryImageDisplay.hyperlink.setURL(null);
-        } catch (MalformedURLException e1) {
-          Main.error(e1);
-        }
+        this.mapillaryImageDisplay.hyperlink.setURL(null);
         MapillaryImportedImage mapillaryImage = (MapillaryImportedImage) this.image;
         try {
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 31827)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryPreferenceSetting.java	(revision 31828)
@@ -28,4 +28,5 @@
 import org.openstreetmap.josm.plugins.mapillary.oauth.MapillaryUser;
 import org.openstreetmap.josm.plugins.mapillary.oauth.OAuthPortListener;
+import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL;
 import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryUtils;
 import org.openstreetmap.josm.tools.GBC;
@@ -169,7 +170,6 @@
       OAuthPortListener portListener = new OAuthPortListener(callback);
       portListener.start();
-      String url = "http://www.mapillary.com/connect?redirect_uri=http:%2F%2Flocalhost:"+OAuthPortListener.PORT+"%2F&client_id="+MapillaryPlugin.CLIENT_ID+"&response_type=token&scope=user:read%20public:upload%20public:write";
       try {
-        MapillaryUtils.browse(new URL(url));
+        MapillaryUtils.browse(MapillaryURL.connectURL("http://localhost:"+OAuthPortListener.PORT));
       } catch (IOException e) {
         Main.error(e);
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 31827)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryDownloader.java	(revision 31828)
@@ -38,6 +38,4 @@
       "mapillary.max-download-area", 0.015);
 
-  /** Base URL of the Mapillary API. */
-  public static final String BASE_URL = "https://a.mapillary.com/v2/";
   /** Executor that will run the petitions. */
   private static ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(3, 5,
@@ -54,10 +52,8 @@
    */
   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));
+    if (maxLatLon == null || maxLatLon == null) {
+      throw new IllegalArgumentException();
+    }
+    getImages(new Bounds(minLatLon, maxLatLon));
   }
 
@@ -69,5 +65,5 @@
    */
   public static void getImages(Bounds bounds) {
-    getImages(bounds.getMin(), bounds.getMax());
+    run(new MapillarySquareDownloadManagerThread(bounds));
   }
 
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 31827)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryImageInfoDownloadThread.java	(revision 31828)
@@ -6,5 +6,4 @@
 import java.io.InputStreamReader;
 import java.net.MalformedURLException;
-import java.net.URL;
 import java.util.concurrent.ExecutorService;
 
@@ -14,7 +13,9 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
 import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
 import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
+import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL;
 
 /**
@@ -25,6 +26,6 @@
  */
 public class MapillaryImageInfoDownloadThread extends Thread {
-  private static final String URL = MapillaryDownloader.BASE_URL + "search/im/";
-  private final String queryString;
+  private final Bounds bounds;
+  private final int page;
   private final ExecutorService ex;
 
@@ -37,8 +38,8 @@
    *          A String containing the parameters for the download.
    */
-  public MapillaryImageInfoDownloadThread(ExecutorService ex,
-      String queryString) {
+  public MapillaryImageInfoDownloadThread(ExecutorService ex, Bounds bounds, int page) {
+    this.bounds = bounds;
+    this.page = page;
     this.ex = ex;
-    this.queryString = queryString;
   }
 
@@ -46,7 +47,5 @@
   public void run() {
     try (
-      BufferedReader br = new BufferedReader(new InputStreamReader(
-        new URL(URL + this.queryString).openStream(), "UTF-8")
-      );
+      BufferedReader br = new BufferedReader(new InputStreamReader(MapillaryURL.searchImageURL(bounds, page).openStream(), "UTF-8"));
     ) {
       JsonObject jsonobj = Json.createReader(br).readObject();
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 31827)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySequenceDownloadThread.java	(revision 31828)
@@ -5,5 +5,4 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
@@ -15,4 +14,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
 import org.openstreetmap.josm.plugins.mapillary.MapillaryData;
@@ -20,4 +20,5 @@
 import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
 import org.openstreetmap.josm.plugins.mapillary.MapillarySequence;
+import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL;
 
 /**
@@ -30,9 +31,7 @@
  */
 public class MapillarySequenceDownloadThread extends Thread {
-  private static final String URL = MapillaryDownloader.BASE_URL + "search/s/";
-
-  private final String queryString;
+  private final Bounds bounds;
+  private final int page;
   private final ExecutorService ex;
-  private final MapillaryLayer layer;
 
   /**
@@ -44,8 +43,8 @@
    *          String containing the parameters for the download.
    */
-  public MapillarySequenceDownloadThread(ExecutorService ex, String queryString) {
-    this.queryString = queryString;
+  public MapillarySequenceDownloadThread(ExecutorService ex, Bounds bounds, int page) {
+    this.bounds = bounds;
+    this.page = page;
     this.ex = ex;
-    this.layer = MapillaryLayer.getInstance();
   }
 
@@ -54,5 +53,6 @@
     try (
       BufferedReader br = new BufferedReader(new InputStreamReader(
-        new URL(URL + this.queryString).openStream(), "UTF-8"
+          MapillaryURL.searchSequenceURL(bounds, page).openStream(),
+          "UTF-8"
       ));
     ) {
@@ -76,5 +76,5 @@
                 .getJsonNumber(j).doubleValue()));
           } catch (IndexOutOfBoundsException e) {
-            Main.warn("Mapillary bug at " + URL + this.queryString);
+            Main.warn("Mapillary bug at " + MapillaryURL.searchSequenceURL(bounds, page));
             isSequenceWrong = true;
           }
@@ -95,12 +95,12 @@
           synchronized (MapillaryAbstractImage.class) {
             for (MapillaryImage img : finalImages) {
-              if (this.layer.getData().getImages().contains(img)) {
+              if (MapillaryLayer.getInstance().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));
+                img = (MapillaryImage) MapillaryLayer.getInstance().getData().getImages()
+                    .get(MapillaryLayer.getInstance().getData().getImages().indexOf(img));
                 sequence.add(img);
-                ((MapillaryImage) this.layer.getData().getImages()
-                    .get(this.layer.getData().getImages().indexOf(img)))
+                ((MapillaryImage) MapillaryLayer.getInstance().getData().getImages()
+                    .get(MapillaryLayer.getInstance().getData().getImages().indexOf(img)))
                     .setSequence(sequence);
                 finalImages.set(finalImages.indexOf(img), img);
@@ -113,10 +113,8 @@
         }
 
-        this.layer.getData().add(
-            new ArrayList<MapillaryAbstractImage>(finalImages), false);
+        MapillaryLayer.getInstance().getData().add(new ArrayList<MapillaryAbstractImage>(finalImages), false);
       }
     } catch (IOException e) {
-      Main.error("Error reading the url " + URL + this.queryString
-          + " might be a Mapillary problem.");
+      Main.error("Error reading the url " + MapillaryURL.searchSequenceURL(bounds, page) + " might be a Mapillary problem.", e);
     }
     MapillaryData.dataUpdated();
@@ -124,6 +122,6 @@
 
   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()))
+    for (int i = 0; i < MapillaryLayer.getInstance().getData().bounds.size(); i++)
+      if (MapillaryLayer.getInstance().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 31827)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySquareDownloadManagerThread.java	(revision 31828)
@@ -2,16 +2,11 @@
 package org.openstreetmap.josm.plugins.mapillary.io.download;
 
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.Locale;
-import java.util.Map.Entry;
 import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.plugins.mapillary.MapillaryData;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryPlugin;
 import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryFilterDialog;
 import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryMainDialog;
@@ -33,7 +28,5 @@
 public class MapillarySquareDownloadManagerThread extends Thread {
 
-  private final String imageQueryString;
-  private final String sequenceQueryString;
-  private final String signQueryString;
+  private final Bounds bounds;
 
   private final ThreadPoolExecutor downloadExecutor = new ThreadPoolExecutor(3, 5,
@@ -51,27 +44,6 @@
    *
    */
-  public MapillarySquareDownloadManagerThread(ConcurrentMap<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(ConcurrentMap<String, Double> hash) {
-    StringBuilder ret = new StringBuilder().append("?client_id=").append(MapillaryPlugin.CLIENT_ID);
-    for (Entry<String, Double> entry : hash.entrySet())
-      if (entry.getKey() != null)
-        try {
-          ret.append('&')
-            .append(URLEncoder.encode(entry.getKey(), "UTF-8"))
-            .append('=')
-            .append(URLEncoder.encode(String.format(Locale.UK, "%f", entry.getValue()), "UTF-8"));
-        } catch (UnsupportedEncodingException e) {
-          // This should not happen, as the encoding is hard-coded
-        }
-    return ret.toString();
+  public MapillarySquareDownloadManagerThread(Bounds bounds) {
+    this.bounds = bounds;
   }
 
@@ -105,6 +77,5 @@
     int page = 0;
     while (!this.downloadExecutor.isShutdown()) {
-      this.downloadExecutor.execute(new MapillarySequenceDownloadThread(
-          this.downloadExecutor, this.sequenceQueryString + "&page=" + page + "&limit=10"));
+      this.downloadExecutor.execute(new MapillarySequenceDownloadThread(this.downloadExecutor, bounds, page));
       while (this.downloadExecutor.getQueue().remainingCapacity() == 0)
         Thread.sleep(500);
@@ -124,7 +95,5 @@
     int page = 0;
     while (!this.completeExecutor.isShutdown()) {
-      this.completeExecutor.execute(new MapillaryImageInfoDownloadThread(
-          this.completeExecutor, this.imageQueryString + "&page=" + page
-              + "&limit=20"));
+      this.completeExecutor.execute(new MapillaryImageInfoDownloadThread(completeExecutor, bounds, page));
       while (this.completeExecutor.getQueue().remainingCapacity() == 0)
         Thread.sleep(100);
@@ -143,7 +112,5 @@
     int page = 0;
     while (!this.signsExecutor.isShutdown()) {
-      this.signsExecutor.execute(new MapillaryTrafficSignDownloadThread(
-          this.signsExecutor, this.signQueryString + "&page=" + page
-              + "&limit=20"));
+      this.signsExecutor.execute(new MapillaryTrafficSignDownloadThread(this.signsExecutor, bounds, page));
       while (this.signsExecutor.getQueue().remainingCapacity() == 0)
         Thread.sleep(100);
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 31827)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryTrafficSignDownloadThread.java	(revision 31828)
@@ -6,5 +6,4 @@
 import java.io.InputStreamReader;
 import java.net.MalformedURLException;
-import java.net.URL;
 import java.util.concurrent.ExecutorService;
 
@@ -14,7 +13,9 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage;
 import org.openstreetmap.josm.plugins.mapillary.MapillaryImage;
 import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer;
+import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL;
 
 /**
@@ -25,7 +26,6 @@
  */
 public class MapillaryTrafficSignDownloadThread extends Thread {
-  private static final String URL = MapillaryDownloader.BASE_URL
-      + "search/im/or/";
-  private final String queryString;
+  private final Bounds bounds;
+  private final int page;
   private final ExecutorService ex;
 
@@ -33,13 +33,12 @@
    * Main constructor.
    *
-   * @param ex
-   *          {@link ExecutorService} object that is executing this thread.
-   * @param queryString
-   *          A String containing the parameter for the download.
+   * @param ex {@link ExecutorService} object that is executing this thread.
+   * @param bounds the bounds in which the traffic signs should be downloaded
+   * @page page the pagenumber of the results page that should be retrieved
    */
-  public MapillaryTrafficSignDownloadThread(ExecutorService ex,
-      String queryString) {
+  public MapillaryTrafficSignDownloadThread(ExecutorService ex, Bounds bounds, int page) {
+    this.bounds = bounds;
+    this.page = page;
     this.ex = ex;
-    this.queryString = queryString;
   }
 
@@ -49,5 +48,5 @@
     try (
       BufferedReader br = new BufferedReader(new InputStreamReader(
-        new URL(URL + this.queryString).openStream(), "UTF-8"
+        MapillaryURL.searchTrafficSignURL(bounds, page).openStream(), "UTF-8"
       ));
     ) {
@@ -67,13 +66,7 @@
             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.");
-                  }
+              for (MapillaryAbstractImage image : MapillaryLayer.getInstance().getData().getImages())
+                if (image instanceof MapillaryImage && ((MapillaryImage) image).getKey().equals(key))
+                  ((MapillaryImage) image).addSign(data.getString("type"));
             }
           }
@@ -84,8 +77,6 @@
           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))
+            for (MapillaryAbstractImage image : MapillaryLayer.getInstance().getData().getImages())
+              if (image instanceof MapillaryImage && ((MapillaryImage) image).getKey().equals(key))
                 ((MapillaryImage) image).addSign(data.getString("type"));
           }
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 31827)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/oauth/MapillaryUser.java	(revision 31828)
@@ -3,11 +3,9 @@
 
 import java.io.IOException;
-import java.net.URL;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.plugins.mapillary.MapillaryPlugin;
-import org.openstreetmap.josm.plugins.mapillary.io.download.MapillaryDownloader;
+import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL;
 
 /**
@@ -39,5 +37,5 @@
       try {
         username = OAuthUtils
-            .getWithHeader(new URL(MapillaryDownloader.BASE_URL+"me?client_id="+MapillaryPlugin.CLIENT_ID))
+            .getWithHeader(MapillaryURL.userURL())
             .getString("username");
       } catch (IOException e) {
@@ -60,17 +58,13 @@
       if (imagesHash == null)
         imagesHash = OAuthUtils
-            .getWithHeader(
-                new URL(
-                    "https://a.mapillary.com/v2/me/uploads/secrets?client_id="+MapillaryPlugin.CLIENT_ID))
+            .getWithHeader(MapillaryURL.uploadSecretsURL())
             .getString("images_hash");
       hash.put("images_hash", imagesHash);
       if (imagesPolicy == null)
         imagesPolicy = OAuthUtils
-            .getWithHeader(
-                new URL(
-                    "https://a.mapillary.com/v2/me/uploads/secrets?client_id="+MapillaryPlugin.CLIENT_ID))
+            .getWithHeader(MapillaryURL.uploadSecretsURL())
             .getString("images_policy");
     } catch (IOException e) {
-      Main.info("Invalid Mapillary token, reseting field");
+      Main.info("Invalid Mapillary token, resetting field");
       reset();
     }
Index: /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/utils/MapillaryURL.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/utils/MapillaryURL.java	(revision 31828)
+++ /applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/utils/MapillaryURL.java	(revision 31828)
@@ -0,0 +1,115 @@
+package org.openstreetmap.josm.plugins.mapillary.utils;
+
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Bounds;
+
+public final class MapillaryURL {
+  private static final String CLIENT_ID = "T1Fzd20xZjdtR0s1VDk5OFNIOXpYdzoxNDYyOGRkYzUyYTFiMzgz";
+  /** Base URL of the Mapillary API. */
+  private static final String BASE_API_URL = "https://a.mapillary.com/v2/";
+  private static final String BASE_WEBSITE_URL = "https://www.mapillary.com/";
+
+  private MapillaryURL() {
+    // Private constructor to avoid instantiation
+  }
+
+  public static URL browseEditURL(String key) {
+    if (key == null || !key.matches("[a-zA-Z0-9\\-_]{22}")) {
+      throw new IllegalArgumentException("Invalid image key");
+    }
+    return string2URL(BASE_WEBSITE_URL + "map/e/" + key);
+  }
+
+  public static URL browseImageURL(String key) {
+    if (key == null || !key.matches("[a-zA-Z0-9\\-_]{22}")) {
+      throw new IllegalArgumentException("Invalid image key");
+    }
+    return string2URL(BASE_WEBSITE_URL + "map/im/" + key);
+  }
+
+  public static URL browseUploadImageURL() {
+    return string2URL(BASE_WEBSITE_URL + "map/upload/im");
+  }
+
+  public static URL connectURL(String redirectURI) {
+    HashMap<String, String> parts = new HashMap<>();
+    parts.put("redirect_uri", redirectURI);
+    parts.put("response_type", "token");
+    parts.put("scope", "user:read public:upload public:write");
+    return string2URL(BASE_WEBSITE_URL + "connect/"+queryString(parts));
+  }
+
+  public static URL searchImageURL(Bounds bounds, int page) {
+    HashMap<String, String> parts = new HashMap<>();
+    putBoundsInQueryStringParts(parts, bounds);
+    parts.put("page", Integer.toString(page));
+    parts.put("limit", "20");
+    return string2URL(BASE_API_URL + "search/im/" + queryString(parts));
+  }
+
+  public static URL searchSequenceURL(Bounds bounds, int page) {
+    HashMap<String, String> parts = new HashMap<>();
+    putBoundsInQueryStringParts(parts, bounds);
+    parts.put("page", Integer.toString(page));
+    parts.put("limit", "10");
+    return string2URL(BASE_API_URL + "search/s/" + queryString(parts));
+  }
+
+  public static URL searchTrafficSignURL(Bounds bounds, int page) {
+    HashMap<String, String> parts = new HashMap<>();
+    putBoundsInQueryStringParts(parts, bounds);
+    parts.put("page", Integer.toString(page));
+    parts.put("limit", "20");
+    return string2URL(BASE_API_URL + "search/im/or/" + queryString(parts));
+  }
+
+  public static URL uploadSecretsURL() {
+    return string2URL(BASE_API_URL + "me/uploads/secrets/" + queryString(null));
+  }
+
+  public static URL userURL() {
+    return string2URL(BASE_API_URL + "me/" + queryString(null));
+  }
+
+  private static void putBoundsInQueryStringParts(Map<String, String> parts, Bounds bounds) {
+    parts.put("min_lat", String.format(Locale.UK, "%f", bounds.getMin().lat()));
+    parts.put("max_lat", String.format(Locale.UK, "%f", bounds.getMax().lat()));
+    parts.put("min_lon", String.format(Locale.UK, "%f", bounds.getMin().lon()));
+    parts.put("max_lon", String.format(Locale.UK, "%f", bounds.getMax().lon()));
+  }
+
+  private static String queryString(Map<String, String> parts) {
+    StringBuilder ret = new StringBuilder().append("?client_id=").append(CLIENT_ID);
+    if (parts != null) {
+      for (Entry<String, String> entry : parts.entrySet()) {
+        try {
+          ret.append('&')
+            .append(URLEncoder.encode(entry.getKey(), "UTF-8"))
+            .append('=')
+            .append(URLEncoder.encode(entry.getValue(), "UTF-8"));
+        } catch (UnsupportedEncodingException e) {
+          Main.error(e); // This should not happen, as the encoding is hard-coded
+        }
+      }
+    }
+    return ret.toString();
+  }
+
+  private static URL string2URL(String string) {
+    try {
+      return new URL(string);
+    } catch (MalformedURLException e) {
+      Main.error("The MapillaryAPI class produces malformed URLs!", e);
+      return null;
+    }
+  }
+}
Index: /applications/editors/josm/plugins/mapillary/test/unit/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySequenceDownloadThreadTest.java
===================================================================
--- /applications/editors/josm/plugins/mapillary/test/unit/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySequenceDownloadThreadTest.java	(revision 31827)
+++ /applications/editors/josm/plugins/mapillary/test/unit/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySequenceDownloadThreadTest.java	(revision 31828)
@@ -43,10 +43,5 @@
 
     ExecutorService ex = Executors.newSingleThreadExecutor();
-    String queryString = String
-        .format(
-            Locale.UK,
-            "?max_lat=%.8f&max_lon=%.8f&min_lat=%.8f&min_lon=%.8f&limit=10&client_id=%s",
-            maxLatLon.lat(), maxLatLon.lon(), minLatLon.lat(), minLatLon.lon(),
-            MapillaryPlugin.CLIENT_ID);
+    Bounds bounds = new Bounds(minLatLon, maxLatLon);
     MapillaryLayer.getInstance().getData().bounds.add(new Bounds(minLatLon,
         maxLatLon));
@@ -58,6 +53,5 @@
       System.out.println("Sending sequence-request " + page
           + " to Mapillary-servers…");
-      Thread downloadThread = new MapillarySequenceDownloadThread(ex,
-          queryString + "&page=" + page);
+      Thread downloadThread = new MapillarySequenceDownloadThread(ex, bounds, page);
       downloadThread.start();
       downloadThread.join();
@@ -66,6 +60,5 @@
     }
     assertTrue(MapillaryLayer.getInstance().getData().getImages().size() >= 1);
-    System.out
-        .println("One or more images were added to the MapillaryLayer within the given bounds.");
+    System.out.println("One or more images were added to the MapillaryLayer within the given bounds.");
   }
 
