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