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}