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 != null ? 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}