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