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.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 * @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 (Main.isDisplayingMapView()) { 131 Point p = Main.map.mapView.getPoint(ll); 132 nd = Main.map.mapView.getNearestNode(p, OsmPrimitive.isUsablePredicate); 133 if (nd != null && nd.getCoor().greatCircleDistance(ll) > Main.pref.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) < Main.pref.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(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 commands.add(new AddCommand(way)); 171 Main.main.undoRedo.add(new SequenceCommand(tr("Add way"), commands)); 172 Main.main.getCurrentDataSet().setSelected(way); 173 if (PermissionPrefWithDefault.CHANGE_VIEWPORT.isAllowed()) { 174 AutoScaleAction.autoScale("selection"); 175 } else { 176 Main.map.mapView.repaint(); 177 } 178 return way; 179 } 180}