Changeset 31828 in osm for applications
- Timestamp:
- 2015-12-15T16:32:30+01:00 (9 years ago)
- Location:
- applications/editors/josm/plugins/mapillary
- Files:
-
- 1 added
- 12 edited
Legend:
- Unmodified
- Added
- Removed
-
applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/MapillaryPlugin.java
r31827 r31828 41 41 */ 42 42 public class MapillaryPlugin extends Plugin { 43 public static final String CLIENT_ID = "T1Fzd20xZjdtR0s1VDk5OFNIOXpYdzoxNDYyOGRkYzUyYTFiMzgz";44 43 45 44 /** OS route separator */ -
applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/FinishedUploadDialog.java
r31799 r31828 16 16 17 17 import org.openstreetmap.josm.Main; 18 import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL; 18 19 import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryUtils; 19 20 import org.openstreetmap.josm.plugins.mapillary.utils.PluginState; … … 44 45 45 46 private class WebAction implements ActionListener { 46 47 47 @Override 48 48 public void actionPerformed(ActionEvent e) { 49 49 try { 50 MapillaryUtils.browse( new URL("http://www.mapillary.com/map/upload/im"));50 MapillaryUtils.browse(MapillaryURL.browseUploadImageURL()); 51 51 } catch (IOException e1) { 52 52 Main.error(e1); -
applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/HyperlinkLabel.java
r31816 r31828 12 12 import java.awt.event.MouseEvent; 13 13 import java.io.IOException; 14 import java.net.MalformedURLException;15 14 import java.net.URL; 16 15 … … 21 20 22 21 import org.openstreetmap.josm.Main; 22 import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL; 23 23 import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryUtils; 24 24 … … 60 60 * 61 61 * @param key The key of the image that the hyperlink will point to. 62 * @throws MalformedURLException when the key appended to the base URL forms a malformed URL63 62 */ 64 public void setURL(String key) throws MalformedURLException{63 public void setURL(String key) { 65 64 this.key = key; 66 65 if (key == null) { 67 66 this.url = null; 68 return; 67 } else { 68 this.url = MapillaryURL.browseImageURL(key); 69 69 } 70 this.url = new URL("http://www.mapillary.com/map/im/" + key);71 70 } 72 71 … … 135 134 public void actionPerformed(ActionEvent paramActionEvent) { 136 135 try { 137 MapillaryUtils.browse( new URL("http://www.mapillary.com/map/e/" +key));136 MapillaryUtils.browse(MapillaryURL.browseEditURL(key)); 138 137 } catch (IOException e) { 139 138 Main.error(e); -
applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryMainDialog.java
r31815 r31828 238 238 this.mapillaryImageDisplay.hyperlink.setVisible(true); 239 239 MapillaryImage mapillaryImage = (MapillaryImage) this.image; 240 try { 241 this.mapillaryImageDisplay.hyperlink.setURL(mapillaryImage.getKey()); 242 } catch (MalformedURLException e1) { 243 Main.error(e1); 244 } 240 this.mapillaryImageDisplay.hyperlink.setURL(mapillaryImage.getKey()); 245 241 // Downloads the thumbnail. 246 242 this.mapillaryImageDisplay.setImage(null); … … 269 265 } else if (this.image instanceof MapillaryImportedImage) { 270 266 this.mapillaryImageDisplay.hyperlink.setVisible(false); 271 try { 272 this.mapillaryImageDisplay.hyperlink.setURL(null); 273 } catch (MalformedURLException e1) { 274 Main.error(e1); 275 } 267 this.mapillaryImageDisplay.hyperlink.setURL(null); 276 268 MapillaryImportedImage mapillaryImage = (MapillaryImportedImage) this.image; 277 269 try { -
applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/gui/MapillaryPreferenceSetting.java
r31810 r31828 28 28 import org.openstreetmap.josm.plugins.mapillary.oauth.MapillaryUser; 29 29 import org.openstreetmap.josm.plugins.mapillary.oauth.OAuthPortListener; 30 import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL; 30 31 import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryUtils; 31 32 import org.openstreetmap.josm.tools.GBC; … … 169 170 OAuthPortListener portListener = new OAuthPortListener(callback); 170 171 portListener.start(); 171 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";172 172 try { 173 MapillaryUtils.browse( new URL(url));173 MapillaryUtils.browse(MapillaryURL.connectURL("http://localhost:"+OAuthPortListener.PORT)); 174 174 } catch (IOException e) { 175 175 Main.error(e); -
applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryDownloader.java
r31826 r31828 38 38 "mapillary.max-download-area", 0.015); 39 39 40 /** Base URL of the Mapillary API. */41 public static final String BASE_URL = "https://a.mapillary.com/v2/";42 40 /** Executor that will run the petitions. */ 43 41 private static ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(3, 5, … … 54 52 */ 55 53 public static void getImages(LatLon minLatLon, LatLon maxLatLon) { 56 ConcurrentHashMap<String, Double> queryStringParts = new ConcurrentHashMap<>(); 57 queryStringParts.put("min_lat", minLatLon.lat()); 58 queryStringParts.put("min_lon", minLatLon.lon()); 59 queryStringParts.put("max_lat", maxLatLon.lat()); 60 queryStringParts.put("max_lon", maxLatLon.lon()); 61 run(new MapillarySquareDownloadManagerThread(queryStringParts)); 54 if (maxLatLon == null || maxLatLon == null) { 55 throw new IllegalArgumentException(); 56 } 57 getImages(new Bounds(minLatLon, maxLatLon)); 62 58 } 63 59 … … 69 65 */ 70 66 public static void getImages(Bounds bounds) { 71 getImages(bounds.getMin(), bounds.getMax());67 run(new MapillarySquareDownloadManagerThread(bounds)); 72 68 } 73 69 -
applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryImageInfoDownloadThread.java
r31788 r31828 6 6 import java.io.InputStreamReader; 7 7 import java.net.MalformedURLException; 8 import java.net.URL;9 8 import java.util.concurrent.ExecutorService; 10 9 … … 14 13 15 14 import org.openstreetmap.josm.Main; 15 import org.openstreetmap.josm.data.Bounds; 16 16 import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage; 17 17 import org.openstreetmap.josm.plugins.mapillary.MapillaryImage; 18 18 import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer; 19 import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL; 19 20 20 21 /** … … 25 26 */ 26 27 public class MapillaryImageInfoDownloadThread extends Thread { 27 private static final String URL = MapillaryDownloader.BASE_URL + "search/im/";28 private final String queryString;28 private final Bounds bounds; 29 private final int page; 29 30 private final ExecutorService ex; 30 31 … … 37 38 * A String containing the parameters for the download. 38 39 */ 39 public MapillaryImageInfoDownloadThread(ExecutorService ex, 40 String queryString) { 40 public MapillaryImageInfoDownloadThread(ExecutorService ex, Bounds bounds, int page) { 41 this.bounds = bounds; 42 this.page = page; 41 43 this.ex = ex; 42 this.queryString = queryString;43 44 } 44 45 … … 46 47 public void run() { 47 48 try ( 48 BufferedReader br = new BufferedReader(new InputStreamReader( 49 new URL(URL + this.queryString).openStream(), "UTF-8") 50 ); 49 BufferedReader br = new BufferedReader(new InputStreamReader(MapillaryURL.searchImageURL(bounds, page).openStream(), "UTF-8")); 51 50 ) { 52 51 JsonObject jsonobj = Json.createReader(br).readObject(); -
applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySequenceDownloadThread.java
r31788 r31828 5 5 import java.io.IOException; 6 6 import java.io.InputStreamReader; 7 import java.net.URL;8 7 import java.util.ArrayList; 9 8 import java.util.List; … … 15 14 16 15 import org.openstreetmap.josm.Main; 16 import org.openstreetmap.josm.data.Bounds; 17 17 import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage; 18 18 import org.openstreetmap.josm.plugins.mapillary.MapillaryData; … … 20 20 import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer; 21 21 import org.openstreetmap.josm.plugins.mapillary.MapillarySequence; 22 import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL; 22 23 23 24 /** … … 30 31 */ 31 32 public class MapillarySequenceDownloadThread extends Thread { 32 private static final String URL = MapillaryDownloader.BASE_URL + "search/s/"; 33 34 private final String queryString; 33 private final Bounds bounds; 34 private final int page; 35 35 private final ExecutorService ex; 36 private final MapillaryLayer layer;37 36 38 37 /** … … 44 43 * String containing the parameters for the download. 45 44 */ 46 public MapillarySequenceDownloadThread(ExecutorService ex, String queryString) { 47 this.queryString = queryString; 45 public MapillarySequenceDownloadThread(ExecutorService ex, Bounds bounds, int page) { 46 this.bounds = bounds; 47 this.page = page; 48 48 this.ex = ex; 49 this.layer = MapillaryLayer.getInstance();50 49 } 51 50 … … 54 53 try ( 55 54 BufferedReader br = new BufferedReader(new InputStreamReader( 56 new URL(URL + this.queryString).openStream(), "UTF-8" 55 MapillaryURL.searchSequenceURL(bounds, page).openStream(), 56 "UTF-8" 57 57 )); 58 58 ) { … … 76 76 .getJsonNumber(j).doubleValue())); 77 77 } catch (IndexOutOfBoundsException e) { 78 Main.warn("Mapillary bug at " + URL + this.queryString);78 Main.warn("Mapillary bug at " + MapillaryURL.searchSequenceURL(bounds, page)); 79 79 isSequenceWrong = true; 80 80 } … … 95 95 synchronized (MapillaryAbstractImage.class) { 96 96 for (MapillaryImage img : finalImages) { 97 if ( this.layer.getData().getImages().contains(img)) {97 if (MapillaryLayer.getInstance().getData().getImages().contains(img)) { 98 98 // The image in finalImages is substituted by the one in the 99 99 // database, as they represent the same picture. 100 img = (MapillaryImage) this.layer.getData().getImages()101 .get( this.layer.getData().getImages().indexOf(img));100 img = (MapillaryImage) MapillaryLayer.getInstance().getData().getImages() 101 .get(MapillaryLayer.getInstance().getData().getImages().indexOf(img)); 102 102 sequence.add(img); 103 ((MapillaryImage) this.layer.getData().getImages()104 .get( this.layer.getData().getImages().indexOf(img)))103 ((MapillaryImage) MapillaryLayer.getInstance().getData().getImages() 104 .get(MapillaryLayer.getInstance().getData().getImages().indexOf(img))) 105 105 .setSequence(sequence); 106 106 finalImages.set(finalImages.indexOf(img), img); … … 113 113 } 114 114 115 this.layer.getData().add( 116 new ArrayList<MapillaryAbstractImage>(finalImages), false); 115 MapillaryLayer.getInstance().getData().add(new ArrayList<MapillaryAbstractImage>(finalImages), false); 117 116 } 118 117 } catch (IOException e) { 119 Main.error("Error reading the url " + URL + this.queryString 120 + " might be a Mapillary problem."); 118 Main.error("Error reading the url " + MapillaryURL.searchSequenceURL(bounds, page) + " might be a Mapillary problem.", e); 121 119 } 122 120 MapillaryData.dataUpdated(); … … 124 122 125 123 private boolean isInside(MapillaryAbstractImage image) { 126 for (int i = 0; i < this.layer.getData().bounds.size(); i++)127 if ( this.layer.getData().bounds.get(i).contains(image.getLatLon()))124 for (int i = 0; i < MapillaryLayer.getInstance().getData().bounds.size(); i++) 125 if (MapillaryLayer.getInstance().getData().bounds.get(i).contains(image.getLatLon())) 128 126 return true; 129 127 return false; -
applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySquareDownloadManagerThread.java
r31811 r31828 2 2 package org.openstreetmap.josm.plugins.mapillary.io.download; 3 3 4 import java.io.UnsupportedEncodingException;5 import java.net.URLEncoder;6 import java.util.Locale;7 import java.util.Map.Entry;8 4 import java.util.concurrent.ArrayBlockingQueue; 9 import java.util.concurrent.ConcurrentMap;10 5 import java.util.concurrent.ThreadPoolExecutor; 11 6 import java.util.concurrent.TimeUnit; 12 7 13 8 import org.openstreetmap.josm.Main; 9 import org.openstreetmap.josm.data.Bounds; 14 10 import org.openstreetmap.josm.plugins.mapillary.MapillaryData; 15 import org.openstreetmap.josm.plugins.mapillary.MapillaryPlugin;16 11 import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryFilterDialog; 17 12 import org.openstreetmap.josm.plugins.mapillary.gui.MapillaryMainDialog; … … 33 28 public class MapillarySquareDownloadManagerThread extends Thread { 34 29 35 private final String imageQueryString; 36 private final String sequenceQueryString; 37 private final String signQueryString; 30 private final Bounds bounds; 38 31 39 32 private final ThreadPoolExecutor downloadExecutor = new ThreadPoolExecutor(3, 5, … … 51 44 * 52 45 */ 53 public MapillarySquareDownloadManagerThread(ConcurrentMap<String, Double> queryStringParts) { 54 this.imageQueryString = buildQueryString(queryStringParts); 55 this.sequenceQueryString = buildQueryString(queryStringParts); 56 this.signQueryString = buildQueryString(queryStringParts); 57 58 // TODO: Move this line to the appropriate place, here's no GET-request 59 Main.info("GET " + this.sequenceQueryString + " (Mapillary plugin)"); 60 } 61 62 // TODO: Maybe move into a separate utility class? 63 private static String buildQueryString(ConcurrentMap<String, Double> hash) { 64 StringBuilder ret = new StringBuilder().append("?client_id=").append(MapillaryPlugin.CLIENT_ID); 65 for (Entry<String, Double> entry : hash.entrySet()) 66 if (entry.getKey() != null) 67 try { 68 ret.append('&') 69 .append(URLEncoder.encode(entry.getKey(), "UTF-8")) 70 .append('=') 71 .append(URLEncoder.encode(String.format(Locale.UK, "%f", entry.getValue()), "UTF-8")); 72 } catch (UnsupportedEncodingException e) { 73 // This should not happen, as the encoding is hard-coded 74 } 75 return ret.toString(); 46 public MapillarySquareDownloadManagerThread(Bounds bounds) { 47 this.bounds = bounds; 76 48 } 77 49 … … 105 77 int page = 0; 106 78 while (!this.downloadExecutor.isShutdown()) { 107 this.downloadExecutor.execute(new MapillarySequenceDownloadThread( 108 this.downloadExecutor, this.sequenceQueryString + "&page=" + page + "&limit=10")); 79 this.downloadExecutor.execute(new MapillarySequenceDownloadThread(this.downloadExecutor, bounds, page)); 109 80 while (this.downloadExecutor.getQueue().remainingCapacity() == 0) 110 81 Thread.sleep(500); … … 124 95 int page = 0; 125 96 while (!this.completeExecutor.isShutdown()) { 126 this.completeExecutor.execute(new MapillaryImageInfoDownloadThread( 127 this.completeExecutor, this.imageQueryString + "&page=" + page 128 + "&limit=20")); 97 this.completeExecutor.execute(new MapillaryImageInfoDownloadThread(completeExecutor, bounds, page)); 129 98 while (this.completeExecutor.getQueue().remainingCapacity() == 0) 130 99 Thread.sleep(100); … … 143 112 int page = 0; 144 113 while (!this.signsExecutor.isShutdown()) { 145 this.signsExecutor.execute(new MapillaryTrafficSignDownloadThread( 146 this.signsExecutor, this.signQueryString + "&page=" + page 147 + "&limit=20")); 114 this.signsExecutor.execute(new MapillaryTrafficSignDownloadThread(this.signsExecutor, bounds, page)); 148 115 while (this.signsExecutor.getQueue().remainingCapacity() == 0) 149 116 Thread.sleep(100); -
applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/io/download/MapillaryTrafficSignDownloadThread.java
r31788 r31828 6 6 import java.io.InputStreamReader; 7 7 import java.net.MalformedURLException; 8 import java.net.URL;9 8 import java.util.concurrent.ExecutorService; 10 9 … … 14 13 15 14 import org.openstreetmap.josm.Main; 15 import org.openstreetmap.josm.data.Bounds; 16 16 import org.openstreetmap.josm.plugins.mapillary.MapillaryAbstractImage; 17 17 import org.openstreetmap.josm.plugins.mapillary.MapillaryImage; 18 18 import org.openstreetmap.josm.plugins.mapillary.MapillaryLayer; 19 import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL; 19 20 20 21 /** … … 25 26 */ 26 27 public class MapillaryTrafficSignDownloadThread extends Thread { 27 private static final String URL = MapillaryDownloader.BASE_URL 28 + "search/im/or/"; 29 private final String queryString; 28 private final Bounds bounds; 29 private final int page; 30 30 private final ExecutorService ex; 31 31 … … 33 33 * Main constructor. 34 34 * 35 * @param ex 36 * {@link ExecutorService} object that is executing this thread. 37 * @param queryString 38 * A String containing the parameter for the download. 35 * @param ex {@link ExecutorService} object that is executing this thread. 36 * @param bounds the bounds in which the traffic signs should be downloaded 37 * @page page the pagenumber of the results page that should be retrieved 39 38 */ 40 public MapillaryTrafficSignDownloadThread(ExecutorService ex, 41 String queryString) { 39 public MapillaryTrafficSignDownloadThread(ExecutorService ex, Bounds bounds, int page) { 40 this.bounds = bounds; 41 this.page = page; 42 42 this.ex = ex; 43 this.queryString = queryString;44 43 } 45 44 … … 49 48 try ( 50 49 BufferedReader br = new BufferedReader(new InputStreamReader( 51 new URL(URL + this.queryString).openStream(), "UTF-8"50 MapillaryURL.searchTrafficSignURL(bounds, page).openStream(), "UTF-8" 52 51 )); 53 52 ) { … … 67 66 for (int k = 0; k < rects.size(); k++) { 68 67 JsonObject data = rects.getJsonObject(k); 69 for (MapillaryAbstractImage image : MapillaryLayer.getInstance() 70 .getData().getImages()) 71 if (image instanceof MapillaryImage 72 && ((MapillaryImage) image).getKey().equals(key)) 73 try { 74 ((MapillaryImage) image).addSign(data.getString("type")); 75 } catch (Exception e) { 76 Main.error("Error when downloading sign."); 77 } 68 for (MapillaryAbstractImage image : MapillaryLayer.getInstance().getData().getImages()) 69 if (image instanceof MapillaryImage && ((MapillaryImage) image).getKey().equals(key)) 70 ((MapillaryImage) image).addSign(data.getString("type")); 78 71 } 79 72 } … … 84 77 for (int j = 0; j < rects.size(); j++) { 85 78 JsonObject data = rects.getJsonObject(j); 86 for (MapillaryAbstractImage image : MapillaryLayer.getInstance() 87 .getData().getImages()) 88 if (image instanceof MapillaryImage 89 && ((MapillaryImage) image).getKey().equals(key)) 79 for (MapillaryAbstractImage image : MapillaryLayer.getInstance().getData().getImages()) 80 if (image instanceof MapillaryImage && ((MapillaryImage) image).getKey().equals(key)) 90 81 ((MapillaryImage) image).addSign(data.getString("type")); 91 82 } -
applications/editors/josm/plugins/mapillary/src/org/openstreetmap/josm/plugins/mapillary/oauth/MapillaryUser.java
r31827 r31828 3 3 4 4 import java.io.IOException; 5 import java.net.URL;6 5 import java.util.HashMap; 7 6 import java.util.Map; 8 7 9 8 import org.openstreetmap.josm.Main; 10 import org.openstreetmap.josm.plugins.mapillary.MapillaryPlugin; 11 import org.openstreetmap.josm.plugins.mapillary.io.download.MapillaryDownloader; 9 import org.openstreetmap.josm.plugins.mapillary.utils.MapillaryURL; 12 10 13 11 /** … … 39 37 try { 40 38 username = OAuthUtils 41 .getWithHeader( new URL(MapillaryDownloader.BASE_URL+"me?client_id="+MapillaryPlugin.CLIENT_ID))39 .getWithHeader(MapillaryURL.userURL()) 42 40 .getString("username"); 43 41 } catch (IOException e) { … … 60 58 if (imagesHash == null) 61 59 imagesHash = OAuthUtils 62 .getWithHeader( 63 new URL( 64 "https://a.mapillary.com/v2/me/uploads/secrets?client_id="+MapillaryPlugin.CLIENT_ID)) 60 .getWithHeader(MapillaryURL.uploadSecretsURL()) 65 61 .getString("images_hash"); 66 62 hash.put("images_hash", imagesHash); 67 63 if (imagesPolicy == null) 68 64 imagesPolicy = OAuthUtils 69 .getWithHeader( 70 new URL( 71 "https://a.mapillary.com/v2/me/uploads/secrets?client_id="+MapillaryPlugin.CLIENT_ID)) 65 .getWithHeader(MapillaryURL.uploadSecretsURL()) 72 66 .getString("images_policy"); 73 67 } catch (IOException e) { 74 Main.info("Invalid Mapillary token, reset ing field");68 Main.info("Invalid Mapillary token, resetting field"); 75 69 reset(); 76 70 } -
applications/editors/josm/plugins/mapillary/test/unit/org/openstreetmap/josm/plugins/mapillary/io/download/MapillarySequenceDownloadThreadTest.java
r31799 r31828 43 43 44 44 ExecutorService ex = Executors.newSingleThreadExecutor(); 45 String queryString = String 46 .format( 47 Locale.UK, 48 "?max_lat=%.8f&max_lon=%.8f&min_lat=%.8f&min_lon=%.8f&limit=10&client_id=%s", 49 maxLatLon.lat(), maxLatLon.lon(), minLatLon.lat(), minLatLon.lon(), 50 MapillaryPlugin.CLIENT_ID); 45 Bounds bounds = new Bounds(minLatLon, maxLatLon); 51 46 MapillaryLayer.getInstance().getData().bounds.add(new Bounds(minLatLon, 52 47 maxLatLon)); … … 58 53 System.out.println("Sending sequence-request " + page 59 54 + " to Mapillary-servers…"); 60 Thread downloadThread = new MapillarySequenceDownloadThread(ex, 61 queryString + "&page=" + page); 55 Thread downloadThread = new MapillarySequenceDownloadThread(ex, bounds, page); 62 56 downloadThread.start(); 63 57 downloadThread.join(); … … 66 60 } 67 61 assertTrue(MapillaryLayer.getInstance().getData().getImages().size() >= 1); 68 System.out 69 .println("One or more images were added to the MapillaryLayer within the given bounds."); 62 System.out.println("One or more images were added to the MapillaryLayer within the given bounds."); 70 63 } 71 64
Note:
See TracChangeset
for help on using the changeset viewer.