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