001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io.remotecontrol.handler; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.geom.Area; 007import java.awt.geom.Rectangle2D; 008import java.util.HashSet; 009import java.util.Set; 010import java.util.concurrent.Future; 011 012import org.openstreetmap.josm.Main; 013import org.openstreetmap.josm.actions.AutoScaleAction; 014import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask; 015import org.openstreetmap.josm.actions.downloadtasks.DownloadTask; 016import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler; 017import org.openstreetmap.josm.data.Bounds; 018import org.openstreetmap.josm.data.coor.LatLon; 019import org.openstreetmap.josm.data.osm.BBox; 020import org.openstreetmap.josm.data.osm.DataSet; 021import org.openstreetmap.josm.data.osm.Node; 022import org.openstreetmap.josm.data.osm.OsmPrimitive; 023import org.openstreetmap.josm.data.osm.Relation; 024import org.openstreetmap.josm.data.osm.Way; 025import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 026import org.openstreetmap.josm.gui.util.GuiHelper; 027import org.openstreetmap.josm.io.remotecontrol.AddTagsDialog; 028import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault; 029import org.openstreetmap.josm.tools.Utils; 030 031/** 032 * Handler for {@code load_and_zoom} and {@code zoom} requests. 033 * @since 3707 034 */ 035public class LoadAndZoomHandler extends RequestHandler { 036 037 /** 038 * The remote control command name used to load data and zoom. 039 */ 040 public static final String command = "load_and_zoom"; 041 042 /** 043 * The remote control command name used to zoom. 044 */ 045 public static final String command2 = "zoom"; 046 047 // Mandatory arguments 048 private double minlat; 049 private double maxlat; 050 private double minlon; 051 private double maxlon; 052 053 // Optional argument 'select' 054 private final Set<Long> ways = new HashSet<Long>(); 055 private final Set<Long> nodes = new HashSet<Long>(); 056 private final Set<Long> relations = new HashSet<Long>(); 057 058 @Override 059 public String getPermissionMessage() { 060 String msg = tr("Remote Control has been asked to load data from the API.") + 061 "<br>" + tr("Bounding box: ") + new BBox(minlon, minlat, maxlon, maxlat).toStringCSV(", "); 062 if (args.containsKey("select") && ways.size()+nodes.size()+relations.size() > 0) { 063 msg += "<br>" + tr("Sel.: Rel.:{0} / Ways:{1} / Nodes:{2}", relations.size(), ways.size(), nodes.size()); 064 } 065 return msg; 066 } 067 068 @Override 069 public String[] getMandatoryParams() { 070 return new String[] { "bottom", "top", "left", "right" }; 071 } 072 073 @Override 074 public String[] getOptionalParams() { 075 return new String[] {"new_layer", "addtags", "select", "zoom_mode"}; 076 } 077 078 @Override 079 public String[] getUsageExamples() { 080 return getUsageExamples(myCommand); 081 } 082 083 @Override 084 public String[] getUsageExamples(String cmd) { 085 if (command.equals(cmd)) { 086 return new String[] { 087 "/load_and_zoom?addtags=wikipedia:de=Wei%C3%9Fe_Gasse|maxspeed=5&select=way23071688,way23076176,way23076177,&left=13.740&right=13.741&top=51.05&bottom=51.049", 088 "/load_and_zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&select=node413602999&new_layer=true"}; 089 } else { 090 return new String[] { 091 "/zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&select=node413602999"}; 092 } 093 } 094 095 @Override 096 protected void handleRequest() throws RequestHandlerErrorException { 097 DownloadTask osmTask = new DownloadOsmTask(); 098 try { 099 boolean newLayer = isLoadInNewLayer(); 100 101 if (command.equals(myCommand)) { 102 if (!PermissionPrefWithDefault.LOAD_DATA.isAllowed()) { 103 Main.info("RemoteControl: download forbidden by preferences"); 104 } else { 105 Area toDownload = null; 106 if (!newLayer) { 107 // find out whether some data has already been downloaded 108 Area present = null; 109 DataSet ds = Main.main.getCurrentDataSet(); 110 if (ds != null) { 111 present = ds.getDataSourceArea(); 112 } 113 if (present != null && !present.isEmpty()) { 114 toDownload = new Area(new Rectangle2D.Double(minlon,minlat,maxlon-minlon,maxlat-minlat)); 115 toDownload.subtract(present); 116 if (!toDownload.isEmpty()) { 117 // the result might not be a rectangle (L shaped etc) 118 Rectangle2D downloadBounds = toDownload.getBounds2D(); 119 minlat = downloadBounds.getMinY(); 120 minlon = downloadBounds.getMinX(); 121 maxlat = downloadBounds.getMaxY(); 122 maxlon = downloadBounds.getMaxX(); 123 } 124 } 125 } 126 if (toDownload != null && toDownload.isEmpty()) { 127 Main.info("RemoteControl: no download necessary"); 128 } else { 129 Future<?> future = osmTask.download(newLayer, new Bounds(minlat,minlon,maxlat,maxlon), null /* let the task manage the progress monitor */); 130 Main.worker.submit(new PostDownloadHandler(osmTask, future)); 131 } 132 } 133 } 134 } catch (Exception ex) { 135 Main.warn("RemoteControl: Error parsing load_and_zoom remote control request:"); 136 ex.printStackTrace(); 137 throw new RequestHandlerErrorException(); 138 } 139 140 /** 141 * deselect objects if parameter addtags given 142 */ 143 if (args.containsKey("addtags")) { 144 GuiHelper.executeByMainWorkerInEDT(new Runnable() { 145 @Override 146 public void run() { 147 DataSet ds = Main.main.getCurrentDataSet(); 148 if(ds == null) // e.g. download failed 149 return; 150 ds.clearSelection(); 151 } 152 }); 153 } 154 155 final Bounds bbox = new Bounds(minlat, minlon, maxlat, maxlon); 156 if (args.containsKey("select") && PermissionPrefWithDefault.CHANGE_SELECTION.isAllowed()) { 157 // select objects after downloading, zoom to selection. 158 GuiHelper.executeByMainWorkerInEDT(new Runnable() { 159 @Override 160 public void run() { 161 HashSet<OsmPrimitive> newSel = new HashSet<OsmPrimitive>(); 162 DataSet ds = Main.main.getCurrentDataSet(); 163 if(ds == null) // e.g. download failed 164 return; 165 for (Way w : ds.getWays()) { 166 if (ways.contains(w.getId())) { 167 newSel.add(w); 168 } 169 } 170 ways.clear(); 171 for (Node n : ds.getNodes()) { 172 if (nodes.contains(n.getId())) { 173 newSel.add(n); 174 } 175 } 176 nodes.clear(); 177 for (Relation r : ds.getRelations()) { 178 if (relations.contains(r.getId())) { 179 newSel.add(r); 180 } 181 } 182 relations.clear(); 183 ds.setSelected(newSel); 184 if (PermissionPrefWithDefault.CHANGE_VIEWPORT.isAllowed()) { 185 // zoom_mode=(download|selection), defaults to selection 186 if (!"download".equals(args.get("zoom_mode")) && !newSel.isEmpty()) { 187 AutoScaleAction.autoScale("selection"); 188 } else { 189 zoom(bbox); 190 } 191 } 192 if (Main.isDisplayingMapView() && Main.map.relationListDialog != null) { 193 Main.map.relationListDialog.selectRelations(null); // unselect all relations to fix #7342 194 Main.map.relationListDialog.dataChanged(null); 195 Main.map.relationListDialog.selectRelations(Utils.filteredCollection(newSel, Relation.class)); 196 } 197 } 198 }); 199 } else if (PermissionPrefWithDefault.CHANGE_VIEWPORT.isAllowed()) { 200 // after downloading, zoom to downloaded area. 201 zoom(bbox); 202 } 203 204 AddTagsDialog.addTags(args, sender); 205 } 206 207 protected void zoom(final Bounds bounds) { 208 // make sure this isn't called unless there *is* a MapView 209 if (Main.isDisplayingMapView()) { 210 GuiHelper.executeByMainWorkerInEDT(new Runnable() { 211 @Override 212 public void run() { 213 BoundingXYVisitor bbox = new BoundingXYVisitor(); 214 bbox.visit(bounds); 215 Main.map.mapView.recalculateCenterScale(bbox); 216 } 217 }); 218 } 219 } 220 221 @Override 222 public PermissionPrefWithDefault getPermissionPref() { 223 return null; 224 } 225 226 @Override 227 protected void validateRequest() throws RequestHandlerBadRequestException { 228 // Process mandatory arguments 229 minlat = 0; 230 maxlat = 0; 231 minlon = 0; 232 maxlon = 0; 233 try { 234 minlat = LatLon.roundToOsmPrecision(Double.parseDouble(args.get("bottom"))); 235 maxlat = LatLon.roundToOsmPrecision(Double.parseDouble(args.get("top"))); 236 minlon = LatLon.roundToOsmPrecision(Double.parseDouble(args.get("left"))); 237 maxlon = LatLon.roundToOsmPrecision(Double.parseDouble(args.get("right"))); 238 } catch (NumberFormatException e) { 239 throw new RequestHandlerBadRequestException("NumberFormatException ("+e.getMessage()+")"); 240 } 241 242 // Current API 0.6 check: "The latitudes must be between -90 and 90" 243 if (!LatLon.isValidLat(minlat) || !LatLon.isValidLat(maxlat)) { 244 throw new RequestHandlerBadRequestException(tr("The latitudes must be between {0} and {1}", -90d, 90d)); 245 } 246 // Current API 0.6 check: "longitudes between -180 and 180" 247 if (!LatLon.isValidLon(minlon) || !LatLon.isValidLon(maxlon)) { 248 throw new RequestHandlerBadRequestException(tr("The longitudes must be between {0} and {1}", -180d, 180d)); 249 } 250 // Current API 0.6 check: "the minima must be less than the maxima" 251 if (minlat > maxlat || minlon > maxlon) { 252 throw new RequestHandlerBadRequestException(tr("The minima must be less than the maxima")); 253 } 254 255 // Process optional argument 'select' 256 if (args.containsKey("select")) { 257 ways.clear(); 258 nodes.clear(); 259 relations.clear(); 260 for (String item : args.get("select").split(",")) { 261 try { 262 if (item.startsWith("way")) { 263 ways.add(Long.parseLong(item.substring(3))); 264 } else if (item.startsWith("node")) { 265 nodes.add(Long.parseLong(item.substring(4))); 266 } else if (item.startsWith("relation")) { 267 relations.add(Long.parseLong(item.substring(8))); 268 } else if (item.startsWith("rel")) { 269 relations.add(Long.parseLong(item.substring(3))); 270 } else { 271 Main.warn("RemoteControl: invalid selection '"+item+"' ignored"); 272 } 273 } catch (NumberFormatException e) { 274 Main.warn("RemoteControl: invalid selection '"+item+"' ignored"); 275 } 276 } 277 } 278 } 279}