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; 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.Node; 022import org.openstreetmap.josm.data.osm.OsmPrimitive; 023import org.openstreetmap.josm.data.osm.Way; 024import org.openstreetmap.josm.gui.util.GuiHelper; 025import org.openstreetmap.josm.io.remotecontrol.AddTagsDialog; 026import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault; 027 028/** 029 * Adds a way to the current dataset. For instance, {@code /add_way?way=lat1,lon2;lat2,lon2}. 030 */ 031public class AddWayHandler extends RequestHandler { 032 033 /** 034 * The remote control command name used to add a way. 035 */ 036 public static final String command = "add_way"; 037 038 private final List<LatLon> allCoordinates = new ArrayList<>(); 039 040 private Way way; 041 042 /** 043 * The place to remeber already added nodes (they are reused if needed @since 5845 044 */ 045 Map<LatLon, Node> addedNodes; 046 047 @Override 048 public String[] getMandatoryParams() { 049 return new String[]{"way"}; 050 } 051 052 @Override 053 public String[] getOptionalParams() { 054 return new String[] { "addtags" }; 055 } 056 057 @Override 058 public String getUsage() { 059 return "adds a way (given by a semicolon separated sequence of lat,lon pairs) to the current dataset"; 060 } 061 062 @Override 063 public String[] getUsageExamples() { 064 return new String[] { 065 "/add_way?way=53.2,13.3;53.3,13.3;53.3,13.2", 066 "/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" 067 }; 068 } 069 070 @Override 071 protected void handleRequest() throws RequestHandlerErrorException, RequestHandlerBadRequestException { 072 GuiHelper.runInEDTAndWait(new Runnable() { 073 @Override public void run() { 074 way = addWay(); 075 } 076 }); 077 // parse parameter addtags=tag1=value1|tag2=value2 078 AddTagsDialog.addTags(args, sender, Collections.singleton(way)); 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 : args.get("way").split(";\\s*")) { 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()+")"); 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 (!Main.main.hasEditLayer()) { 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 ccords in dataset or in already added nodes 120 * @since 5845 121 **/ 122 Node findOrCreateNode(LatLon ll, List<Command> commands) { 123 Node nd = null; 124 125 if (Main.isDisplayingMapView()) { 126 Point p = Main.map.mapView.getPoint(ll); 127 nd = Main.map.mapView.getNearestNode(p, OsmPrimitive.isUsablePredicate); 128 if (nd!=null && nd.getCoor().greatCircleDistance(ll) > Main.pref.getDouble("remote.tolerance", 0.1)) { 129 nd = null; // node is too far 130 } 131 } 132 133 Node prev = null; 134 for (LatLon lOld: addedNodes.keySet()) { 135 if (lOld.greatCircleDistance(ll) < Main.pref.getDouble("remotecontrol.tolerance", 0.1)) { 136 prev = addedNodes.get(lOld); 137 break; 138 } 139 } 140 141 if (prev!=null) { 142 nd = prev; 143 } else if (nd==null) { 144 nd = new Node(ll); 145 // Now execute the commands to add this node. 146 commands.add(new AddCommand(nd)); 147 addedNodes.put(ll, nd); 148 } 149 return nd; 150 } 151 152 /* 153 * This function creates the way with given coordinates of nodes 154 */ 155 private Way addWay() { 156 addedNodes = new HashMap<>(); 157 Way way = new Way(); 158 List<Command> commands = new LinkedList<>(); 159 for (LatLon ll : allCoordinates) { 160 Node node = findOrCreateNode(ll, commands); 161 way.addNode(node); 162 } 163 allCoordinates.clear(); 164 commands.add(new AddCommand(way)); 165 Main.main.undoRedo.add(new SequenceCommand(tr("Add way"), commands)); 166 Main.main.getCurrentDataSet().setSelected(way); 167 if (PermissionPrefWithDefault.CHANGE_VIEWPORT.isAllowed()) { 168 AutoScaleAction.autoScale("selection"); 169 } else { 170 Main.map.mapView.repaint(); 171 } 172 return way; 173 } 174}