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 /** 047 * The place to remember 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.runInEDT(this::addWay); 079 } 080 081 @Override 082 public String getPermissionMessage() { 083 return tr("Remote Control has been asked to create a new way."); 084 } 085 086 @Override 087 public PermissionPrefWithDefault getPermissionPref() { 088 return PermissionPrefWithDefault.CREATE_OBJECTS; 089 } 090 091 @Override 092 protected void validateRequest() throws RequestHandlerBadRequestException { 093 allCoordinates.clear(); 094 for (String coordinatesString : splitArg("way", SPLITTER_SEMIC)) { 095 String[] coordinates = coordinatesString.split(",\\s*", 2); 096 if (coordinates.length < 2) { 097 throw new RequestHandlerBadRequestException( 098 tr("Invalid coordinates: {0}", Arrays.toString(coordinates))); 099 } 100 try { 101 double lat = Double.parseDouble(coordinates[0]); 102 double lon = Double.parseDouble(coordinates[1]); 103 allCoordinates.add(new LatLon(lat, lon)); 104 } catch (NumberFormatException e) { 105 throw new RequestHandlerBadRequestException("NumberFormatException ("+e.getMessage()+')', e); 106 } 107 } 108 if (allCoordinates.isEmpty()) { 109 throw new RequestHandlerBadRequestException(tr("Empty ways")); 110 } else if (allCoordinates.size() == 1) { 111 throw new RequestHandlerBadRequestException(tr("One node ways")); 112 } 113 if (MainApplication.getLayerManager().getEditLayer() == null) { 114 throw new RequestHandlerBadRequestException(tr("There is no layer opened to add way")); 115 } 116 } 117 118 /** 119 * Find the node with almost the same coords in dataset or in already added nodes 120 * @param ll coordinates 121 * @param commands list of commands that will be modified if needed 122 * @return node with almost the same coords 123 * @since 5845 124 */ 125 Node findOrCreateNode(LatLon ll, List<Command> commands) { 126 Node nd = null; 127 128 if (MainApplication.isDisplayingMapView()) { 129 MapView mapView = MainApplication.getMap().mapView; 130 nd = mapView.getNearestNode(mapView.getPoint(ll), OsmPrimitive::isUsable); 131 if (nd != null && nd.getCoor().greatCircleDistance(ll) > Config.getPref().getDouble("remote.tolerance", 0.1)) { 132 nd = null; // node is too far 133 } 134 } 135 136 Node prev = null; 137 for (Entry<LatLon, Node> entry : addedNodes.entrySet()) { 138 LatLon lOld = entry.getKey(); 139 if (lOld.greatCircleDistance(ll) < Config.getPref().getDouble("remotecontrol.tolerance", 0.1)) { 140 prev = entry.getValue(); 141 break; 142 } 143 } 144 145 if (prev != null) { 146 nd = prev; 147 } else if (nd == null) { 148 nd = new Node(ll); 149 // Now execute the commands to add this node. 150 commands.add(new AddCommand(OsmDataManager.getInstance().getEditDataSet(), nd)); 151 addedNodes.put(ll, nd); 152 } 153 return nd; 154 } 155 156 /** 157 * This function creates the way with given coordinates of nodes 158 */ 159 private void addWay() { 160 addedNodes = new HashMap<>(); 161 Way way = new Way(); 162 List<Command> commands = new LinkedList<>(); 163 for (LatLon ll : allCoordinates) { 164 Node node = findOrCreateNode(ll, commands); 165 way.addNode(node); 166 } 167 allCoordinates.clear(); 168 DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 169 commands.add(new AddCommand(ds, way)); 170 UndoRedoHandler.getInstance().add(new SequenceCommand(tr("Add way"), commands)); 171 ds.setSelected(way); 172 if (PermissionPrefWithDefault.CHANGE_VIEWPORT.isAllowed()) { 173 AutoScaleAction.autoScale(AutoScaleMode.SELECTION); 174 } else { 175 MainApplication.getMap().mapView.repaint(); 176 } 177 // parse parameter addtags=tag1=value1|tag2=value2 178 AddTagsDialog.addTags(args, sender, Collections.singleton(way)); 179 } 180}