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(new Runnable() {
076            @Override public void run() {
077                way = addWay();
078            }
079        });
080        // parse parameter addtags=tag1=value1|tag2=value2
081        AddTagsDialog.addTags(args, sender, Collections.singleton(way));
082    }
083
084    @Override
085    public String getPermissionMessage() {
086        return tr("Remote Control has been asked to create a new way.");
087    }
088
089    @Override
090    public PermissionPrefWithDefault getPermissionPref() {
091        return PermissionPrefWithDefault.CREATE_OBJECTS;
092    }
093
094    @Override
095    protected void validateRequest() throws RequestHandlerBadRequestException {
096        allCoordinates.clear();
097        for (String coordinatesString : args.get("way").split(";\\s*")) {
098            String[] coordinates = coordinatesString.split(",\\s*", 2);
099            if (coordinates.length < 2) {
100                throw new RequestHandlerBadRequestException(
101                        tr("Invalid coordinates: {0}", Arrays.toString(coordinates)));
102            }
103            try {
104                double lat = Double.parseDouble(coordinates[0]);
105                double lon = Double.parseDouble(coordinates[1]);
106                allCoordinates.add(new LatLon(lat, lon));
107            } catch (NumberFormatException e) {
108                throw new RequestHandlerBadRequestException("NumberFormatException ("+e.getMessage()+')', e);
109            }
110        }
111        if (allCoordinates.isEmpty()) {
112            throw new RequestHandlerBadRequestException(tr("Empty ways"));
113        } else if (allCoordinates.size() == 1) {
114            throw new RequestHandlerBadRequestException(tr("One node ways"));
115        }
116        if (!Main.main.hasEditLayer()) {
117             throw new RequestHandlerBadRequestException(tr("There is no layer opened to add way"));
118        }
119    }
120
121    /**
122     * Find the node with almost the same coords in dataset or in already added nodes
123     * @param ll coordinates
124     * @param commands list of commands that will be modified if needed
125     * @return node with almost the same coords
126     * @since 5845
127     */
128    Node findOrCreateNode(LatLon ll, List<Command> commands) {
129        Node nd = null;
130
131        if (Main.isDisplayingMapView()) {
132            Point p = Main.map.mapView.getPoint(ll);
133            nd = Main.map.mapView.getNearestNode(p, OsmPrimitive.isUsablePredicate);
134            if (nd != null && nd.getCoor().greatCircleDistance(ll) > Main.pref.getDouble("remote.tolerance", 0.1)) {
135                nd = null; // node is too far
136            }
137        }
138
139        Node prev = null;
140        for (Entry<LatLon, Node> entry : addedNodes.entrySet()) {
141            LatLon lOld = entry.getKey();
142            if (lOld.greatCircleDistance(ll) < Main.pref.getDouble("remotecontrol.tolerance", 0.1)) {
143                prev = entry.getValue();
144                break;
145            }
146        }
147
148        if (prev != null) {
149            nd = prev;
150        } else if (nd == null) {
151            nd = new Node(ll);
152            // Now execute the commands to add this node.
153            commands.add(new AddCommand(nd));
154            addedNodes.put(ll, nd);
155        }
156        return nd;
157    }
158
159    /*
160     * This function creates the way with given coordinates of nodes
161     */
162    private Way addWay() {
163        addedNodes = new HashMap<>();
164        Way way = new Way();
165        List<Command> commands = new LinkedList<>();
166        for (LatLon ll : allCoordinates) {
167            Node node = findOrCreateNode(ll, commands);
168            way.addNode(node);
169        }
170        allCoordinates.clear();
171        commands.add(new AddCommand(way));
172        Main.main.undoRedo.add(new SequenceCommand(tr("Add way"), commands));
173        Main.main.getCurrentDataSet().setSelected(way);
174        if (PermissionPrefWithDefault.CHANGE_VIEWPORT.isAllowed()) {
175            AutoScaleAction.autoScale("selection");
176        } else {
177            Main.map.mapView.repaint();
178        }
179        return way;
180    }
181}