source: josm/trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/LoadAndZoomHandler.java @ 13109

Last change on this file since 13109 was 13109, checked in by Don-vip, 16 months ago

fix #15537 - Support changeset hashtags (hashtags changeset tag, extracted from comment at upload, or set by remote control through new changeset_hashtags parameter)

  • Property svn:eol-style set to native
File size: 13.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io.remotecontrol.handler;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.geom.Area;
7import java.awt.geom.Rectangle2D;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.HashSet;
12import java.util.Set;
13import java.util.concurrent.Future;
14
15import org.openstreetmap.josm.actions.AutoScaleAction;
16import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
17import org.openstreetmap.josm.actions.downloadtasks.DownloadTask;
18import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
19import org.openstreetmap.josm.data.Bounds;
20import org.openstreetmap.josm.data.coor.LatLon;
21import org.openstreetmap.josm.data.osm.BBox;
22import org.openstreetmap.josm.data.osm.DataSet;
23import org.openstreetmap.josm.data.osm.OsmPrimitive;
24import org.openstreetmap.josm.data.osm.Relation;
25import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
26import org.openstreetmap.josm.data.osm.search.SearchCompiler;
27import org.openstreetmap.josm.data.osm.search.SearchParseError;
28import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
29import org.openstreetmap.josm.gui.MainApplication;
30import org.openstreetmap.josm.gui.MapFrame;
31import org.openstreetmap.josm.gui.util.GuiHelper;
32import org.openstreetmap.josm.io.remotecontrol.AddTagsDialog;
33import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
34import org.openstreetmap.josm.tools.Logging;
35import org.openstreetmap.josm.tools.SubclassFilteredCollection;
36import org.openstreetmap.josm.tools.Utils;
37
38/**
39 * Handler for {@code load_and_zoom} and {@code zoom} requests.
40 * @since 3707
41 */
42public class LoadAndZoomHandler extends RequestHandler {
43
44    /**
45     * The remote control command name used to load data and zoom.
46     */
47    public static final String command = "load_and_zoom";
48
49    /**
50     * The remote control command name used to zoom.
51     */
52    public static final String command2 = "zoom";
53
54    // Mandatory arguments
55    private double minlat;
56    private double maxlat;
57    private double minlon;
58    private double maxlon;
59
60    // Optional argument 'select'
61    private final Set<SimplePrimitiveId> toSelect = new HashSet<>();
62
63    @Override
64    public String getPermissionMessage() {
65        String msg = tr("Remote Control has been asked to load data from the API.") +
66                "<br>" + tr("Bounding box: ") + new BBox(minlon, minlat, maxlon, maxlat).toStringCSV(", ");
67        if (args.containsKey("select") && !toSelect.isEmpty()) {
68            msg += "<br>" + tr("Selection: {0}", toSelect.size());
69        }
70        return msg;
71    }
72
73    @Override
74    public String[] getMandatoryParams() {
75        return new String[] {"bottom", "top", "left", "right"};
76    }
77
78    @Override
79    public String[] getOptionalParams() {
80        return new String[] {"new_layer", "layer_name", "addtags", "select", "zoom_mode",
81                "changeset_comment", "changeset_source", "changeset_hashtags", "search"};
82    }
83
84    @Override
85    public String getUsage() {
86        return "download a bounding box from the API, zoom to the downloaded area and optionally select one or more objects";
87    }
88
89    @Override
90    public String[] getUsageExamples() {
91        return getUsageExamples(myCommand);
92    }
93
94    @Override
95    public String[] getUsageExamples(String cmd) {
96        if (command.equals(cmd)) {
97            return new String[] {
98                    "/load_and_zoom?addtags=wikipedia:de=Wei%C3%9Fe_Gasse|maxspeed=5&select=way23071688,way23076176,way23076177," +
99                            "&left=13.740&right=13.741&top=51.05&bottom=51.049",
100                    "/load_and_zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&select=node413602999&new_layer=true"};
101        } else {
102            return new String[] {
103            "/zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&select=node413602999",
104            "/zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&search=highway+OR+railway",
105            };
106        }
107    }
108
109    @Override
110    protected void handleRequest() throws RequestHandlerErrorException {
111        DownloadTask osmTask = new DownloadOsmTask() {
112            {
113                newLayerName = args.get("layer_name");
114            }
115        };
116        try {
117            boolean newLayer = isLoadInNewLayer();
118
119            if (command.equals(myCommand)) {
120                if (!PermissionPrefWithDefault.LOAD_DATA.isAllowed()) {
121                    Logging.info("RemoteControl: download forbidden by preferences");
122                } else {
123                    Area toDownload = null;
124                    if (!newLayer) {
125                        // find out whether some data has already been downloaded
126                        Area present = null;
127                        DataSet ds = MainApplication.getLayerManager().getEditDataSet();
128                        if (ds != null) {
129                            present = ds.getDataSourceArea();
130                        }
131                        if (present != null && !present.isEmpty()) {
132                            toDownload = new Area(new Rectangle2D.Double(minlon, minlat, maxlon-minlon, maxlat-minlat));
133                            toDownload.subtract(present);
134                            if (!toDownload.isEmpty()) {
135                                // the result might not be a rectangle (L shaped etc)
136                                Rectangle2D downloadBounds = toDownload.getBounds2D();
137                                minlat = downloadBounds.getMinY();
138                                minlon = downloadBounds.getMinX();
139                                maxlat = downloadBounds.getMaxY();
140                                maxlon = downloadBounds.getMaxX();
141                            }
142                        }
143                    }
144                    if (toDownload != null && toDownload.isEmpty()) {
145                        Logging.info("RemoteControl: no download necessary");
146                    } else {
147                        Future<?> future = osmTask.download(newLayer, new Bounds(minlat, minlon, maxlat, maxlon),
148                                null /* let the task manage the progress monitor */);
149                        MainApplication.worker.submit(new PostDownloadHandler(osmTask, future));
150                    }
151                }
152            }
153        } catch (RuntimeException ex) { // NOPMD
154            Logging.warn("RemoteControl: Error parsing load_and_zoom remote control request:");
155            Logging.error(ex);
156            throw new RequestHandlerErrorException(ex);
157        }
158
159        /**
160         * deselect objects if parameter addtags given
161         */
162        if (args.containsKey("addtags")) {
163            GuiHelper.executeByMainWorkerInEDT(() -> {
164                DataSet ds = MainApplication.getLayerManager().getEditDataSet();
165                if (ds == null) // e.g. download failed
166                    return;
167                ds.clearSelection();
168            });
169        }
170
171        final Collection<OsmPrimitive> forTagAdd = new HashSet<>();
172        final Bounds bbox = new Bounds(minlat, minlon, maxlat, maxlon);
173        if (args.containsKey("select") && PermissionPrefWithDefault.CHANGE_SELECTION.isAllowed()) {
174            // select objects after downloading, zoom to selection.
175            GuiHelper.executeByMainWorkerInEDT(() -> {
176                Set<OsmPrimitive> newSel = new HashSet<>();
177                DataSet ds = MainApplication.getLayerManager().getEditDataSet();
178                if (ds == null) // e.g. download failed
179                    return;
180                for (SimplePrimitiveId id : toSelect) {
181                    final OsmPrimitive p = ds.getPrimitiveById(id);
182                    if (p != null) {
183                        newSel.add(p);
184                        forTagAdd.add(p);
185                    }
186                }
187                toSelect.clear();
188                ds.setSelected(newSel);
189                zoom(newSel, bbox);
190                MapFrame map = MainApplication.getMap();
191                if (MainApplication.isDisplayingMapView() && map.relationListDialog != null) {
192                    map.relationListDialog.selectRelations(null); // unselect all relations to fix #7342
193                    map.relationListDialog.dataChanged(null);
194                    map.relationListDialog.selectRelations(Utils.filteredCollection(newSel, Relation.class));
195                }
196            });
197        } else if (args.containsKey("search") && PermissionPrefWithDefault.CHANGE_SELECTION.isAllowed()) {
198            try {
199                final SearchCompiler.Match search = SearchCompiler.compile(args.get("search"));
200                MainApplication.worker.submit(() -> {
201                    final DataSet ds = MainApplication.getLayerManager().getEditDataSet();
202                    final Collection<OsmPrimitive> filteredPrimitives = SubclassFilteredCollection.filter(ds.allPrimitives(), search);
203                    ds.setSelected(filteredPrimitives);
204                    forTagAdd.addAll(filteredPrimitives);
205                    zoom(filteredPrimitives, bbox);
206                });
207            } catch (SearchParseError ex) {
208                Logging.error(ex);
209                throw new RequestHandlerErrorException(ex);
210            }
211        } else {
212            // after downloading, zoom to downloaded area.
213            zoom(Collections.<OsmPrimitive>emptySet(), bbox);
214        }
215
216        // add changeset tags after download if necessary
217        if (args.containsKey("changeset_comment") || args.containsKey("changeset_source") || args.containsKey("changeset_hashtags")) {
218            MainApplication.worker.submit(() -> {
219                DataSet ds = MainApplication.getLayerManager().getEditDataSet();
220                if (ds != null) {
221                    for (String tag : Arrays.asList("changeset_comment", "changeset_source", "changeset_hashtags")) {
222                        if (args.containsKey(tag)) {
223                            ds.addChangeSetTag(tag.substring("changeset_".length()), args.get(tag));
224                        }
225                    }
226                }
227            });
228        }
229
230        AddTagsDialog.addTags(args, sender, forTagAdd);
231    }
232
233    protected void zoom(Collection<OsmPrimitive> primitives, final Bounds bbox) {
234        if (!PermissionPrefWithDefault.CHANGE_VIEWPORT.isAllowed()) {
235            return;
236        }
237        // zoom_mode=(download|selection), defaults to selection
238        if (!"download".equals(args.get("zoom_mode")) && !primitives.isEmpty()) {
239            AutoScaleAction.autoScale("selection");
240        } else if (MainApplication.isDisplayingMapView()) {
241            // make sure this isn't called unless there *is* a MapView
242            GuiHelper.executeByMainWorkerInEDT(() -> {
243                BoundingXYVisitor bbox1 = new BoundingXYVisitor();
244                bbox1.visit(bbox);
245                MainApplication.getMap().mapView.zoomTo(bbox1);
246            });
247        }
248    }
249
250    @Override
251    public PermissionPrefWithDefault getPermissionPref() {
252        return null;
253    }
254
255    @Override
256    protected void validateRequest() throws RequestHandlerBadRequestException {
257        // Process mandatory arguments
258        minlat = 0;
259        maxlat = 0;
260        minlon = 0;
261        maxlon = 0;
262        try {
263            minlat = LatLon.roundToOsmPrecision(Double.parseDouble(args != null ? args.get("bottom") : ""));
264            maxlat = LatLon.roundToOsmPrecision(Double.parseDouble(args != null ? args.get("top") : ""));
265            minlon = LatLon.roundToOsmPrecision(Double.parseDouble(args != null ? args.get("left") : ""));
266            maxlon = LatLon.roundToOsmPrecision(Double.parseDouble(args != null ? args.get("right") : ""));
267        } catch (NumberFormatException e) {
268            throw new RequestHandlerBadRequestException("NumberFormatException ("+e.getMessage()+')', e);
269        }
270
271        // Current API 0.6 check: "The latitudes must be between -90 and 90"
272        if (!LatLon.isValidLat(minlat) || !LatLon.isValidLat(maxlat)) {
273            throw new RequestHandlerBadRequestException(tr("The latitudes must be between {0} and {1}", -90d, 90d));
274        }
275        // Current API 0.6 check: "longitudes between -180 and 180"
276        if (!LatLon.isValidLon(minlon) || !LatLon.isValidLon(maxlon)) {
277            throw new RequestHandlerBadRequestException(tr("The longitudes must be between {0} and {1}", -180d, 180d));
278        }
279        // Current API 0.6 check: "the minima must be less than the maxima"
280        if (minlat > maxlat || minlon > maxlon) {
281            throw new RequestHandlerBadRequestException(tr("The minima must be less than the maxima"));
282        }
283
284        // Process optional argument 'select'
285        if (args != null && args.containsKey("select")) {
286            toSelect.clear();
287            for (String item : args.get("select").split(",")) {
288                if (!item.isEmpty()) {
289                    try {
290                        toSelect.add(SimplePrimitiveId.fromString(item));
291                    } catch (IllegalArgumentException ex) {
292                        Logging.log(Logging.LEVEL_WARN, "RemoteControl: invalid selection '" + item + "' ignored", ex);
293                    }
294                }
295            }
296        }
297    }
298}
Note: See TracBrowser for help on using the repository browser.