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