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