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