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.Point;
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.Collections;
010import java.util.HashMap;
011import java.util.LinkedList;
012import java.util.List;
013import java.util.Map;
014
015import org.openstreetmap.josm.Main;
016import org.openstreetmap.josm.actions.AutoScaleAction;
017import org.openstreetmap.josm.command.AddCommand;
018import org.openstreetmap.josm.command.Command;
019import org.openstreetmap.josm.command.SequenceCommand;
020import org.openstreetmap.josm.data.coor.LatLon;
021import org.openstreetmap.josm.data.osm.Node;
022import org.openstreetmap.josm.data.osm.OsmPrimitive;
023import org.openstreetmap.josm.data.osm.Way;
024import org.openstreetmap.josm.gui.util.GuiHelper;
025import org.openstreetmap.josm.io.remotecontrol.AddTagsDialog;
026import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
027
028/**
029 * Adds a way to the current dataset. For instance, {@code /add_way?way=lat1,lon2;lat2,lon2}.
030 */
031public class AddWayHandler extends RequestHandler {
032
033    /**
034     * The remote control command name used to add a way.
035     */
036    public static final String command = "add_way";
037
038    private final List<LatLon> allCoordinates = new ArrayList<>();
039    
040    private Way way;
041
042    /**
043     * The place to remeber already added nodes (they are reused if needed @since 5845
044     */
045    Map<LatLon, Node> addedNodes;
046
047    @Override
048    public String[] getMandatoryParams() {
049        return new String[]{"way"};
050    }
051
052    @Override
053    public String[] getOptionalParams() {
054        return new String[] { "addtags" };
055    }
056
057    @Override
058    public String getUsage() {
059        return "adds a way (given by a semicolon separated sequence of lat,lon pairs) to the current dataset";
060    }
061
062    @Override
063    public String[] getUsageExamples() {
064        return new String[] {
065            "/add_way?way=53.2,13.3;53.3,13.3;53.3,13.2",
066            "/add_way?&addtags=building=yes&way=45.437213,-2.810792;45.437988,-2.455983;45.224080,-2.455036;45.223302,-2.809845;45.437213,-2.810792"
067        };
068    }
069
070    @Override
071    protected void handleRequest() throws RequestHandlerErrorException, RequestHandlerBadRequestException {
072        GuiHelper.runInEDTAndWait(new Runnable() {
073            @Override public void run() {
074                way = addWay();
075            }
076        });
077        // parse parameter addtags=tag1=value1|tag2=value2
078        AddTagsDialog.addTags(args, sender, Collections.singleton(way));
079    }
080
081    @Override
082    public String getPermissionMessage() {
083        return tr("Remote Control has been asked to create a new way.");
084    }
085
086    @Override
087    public PermissionPrefWithDefault getPermissionPref() {
088        return PermissionPrefWithDefault.CREATE_OBJECTS;
089    }
090
091    @Override
092    protected void validateRequest() throws RequestHandlerBadRequestException {
093        allCoordinates.clear();
094        for (String coordinatesString : args.get("way").split(";\\s*")) {
095            String[] coordinates = coordinatesString.split(",\\s*", 2);
096            if (coordinates.length < 2) {
097                throw new RequestHandlerBadRequestException(
098                        tr("Invalid coordinates: {0}", Arrays.toString(coordinates)));
099            }
100            try {
101                double lat = Double.parseDouble(coordinates[0]);
102                double lon = Double.parseDouble(coordinates[1]);
103                allCoordinates.add(new LatLon(lat, lon));
104            } catch (NumberFormatException e) {
105                throw new RequestHandlerBadRequestException("NumberFormatException ("+e.getMessage()+")");
106            }
107        }
108        if (allCoordinates.isEmpty()) {
109            throw new RequestHandlerBadRequestException(tr("Empty ways"));
110        } else if (allCoordinates.size() == 1) {
111            throw new RequestHandlerBadRequestException(tr("One node ways"));
112        }
113        if (!Main.main.hasEditLayer()) {
114             throw new RequestHandlerBadRequestException(tr("There is no layer opened to add way"));
115        }
116    }
117
118    /**
119     * Find the node with almost the same ccords in dataset or in already added nodes
120     * @since 5845
121     **/
122    Node findOrCreateNode(LatLon ll,  List<Command> commands) {
123        Node nd = null;
124
125        if (Main.isDisplayingMapView()) {
126            Point p = Main.map.mapView.getPoint(ll);
127            nd = Main.map.mapView.getNearestNode(p, OsmPrimitive.isUsablePredicate);
128            if (nd!=null && nd.getCoor().greatCircleDistance(ll) > Main.pref.getDouble("remote.tolerance", 0.1)) {
129                nd = null; // node is too far
130            }
131        }
132
133        Node prev = null;
134        for (LatLon lOld: addedNodes.keySet()) {
135            if (lOld.greatCircleDistance(ll) < Main.pref.getDouble("remotecontrol.tolerance", 0.1)) {
136                prev = addedNodes.get(lOld);
137                break;
138            }
139        }
140
141        if (prev!=null) {
142            nd = prev;
143        } else if (nd==null) {
144            nd = new Node(ll);
145            // Now execute the commands to add this node.
146            commands.add(new AddCommand(nd));
147            addedNodes.put(ll, nd);
148        }
149        return nd;
150    }
151
152    /*
153     * This function creates the way with given coordinates of nodes
154     */
155    private Way addWay() {
156        addedNodes = new HashMap<>();
157        Way way = new Way();
158        List<Command> commands = new LinkedList<>();
159        for (LatLon ll : allCoordinates) {
160            Node node = findOrCreateNode(ll, commands);
161            way.addNode(node);
162        }
163        allCoordinates.clear();
164        commands.add(new AddCommand(way));
165        Main.main.undoRedo.add(new SequenceCommand(tr("Add way"), commands));
166        Main.main.getCurrentDataSet().setSelected(way);
167        if (PermissionPrefWithDefault.CHANGE_VIEWPORT.isAllowed()) {
168            AutoScaleAction.autoScale("selection");
169        } else {
170            Main.map.mapView.repaint();
171        }
172        return way;
173    }
174}