source: osm/applications/editors/josm/plugins/cadastre-fr/src/cadastre_fr/Address.java@ 22186

Last change on this file since 22186 was 22186, checked in by pieren, 15 years ago

First working version for help tool addr.

File size: 20.3 KB
Line 
1package cadastre_fr;
2
3import static org.openstreetmap.josm.tools.I18n.tr;
4
5import java.awt.Cursor;
6import java.awt.GridBagLayout;
7import java.awt.Point;
8import java.awt.Rectangle;
9import java.awt.Toolkit;
10import java.awt.event.ActionEvent;
11import java.awt.event.ActionListener;
12import java.awt.event.ComponentAdapter;
13import java.awt.event.ComponentEvent;
14import java.awt.event.KeyEvent;
15import java.awt.event.MouseEvent;
16import java.awt.event.MouseListener;
17import java.awt.event.MouseMotionListener;
18import java.awt.event.WindowEvent;
19import java.awt.event.WindowListener;
20import java.util.ArrayList;
21import java.util.Collection;
22import java.util.Collections;
23import java.util.HashMap;
24import java.util.HashSet;
25import java.util.Iterator;
26import java.util.LinkedList;
27import java.util.List;
28import java.util.Map;
29import java.util.Set;
30
31import javax.swing.ButtonGroup;
32import javax.swing.ImageIcon;
33import javax.swing.JButton;
34import javax.swing.JCheckBox;
35import javax.swing.JDialog;
36import javax.swing.JLabel;
37import javax.swing.JOptionPane;
38import javax.swing.JPanel;
39import javax.swing.JRadioButton;
40import javax.swing.JTextField;
41import javax.swing.event.ChangeEvent;
42import javax.swing.event.ChangeListener;
43
44import org.openstreetmap.josm.Main;
45import org.openstreetmap.josm.command.AddCommand;
46import org.openstreetmap.josm.command.ChangeCommand;
47import org.openstreetmap.josm.command.ChangePropertyCommand;
48import org.openstreetmap.josm.command.Command;
49import org.openstreetmap.josm.command.SequenceCommand;
50import org.openstreetmap.josm.actions.mapmode.MapMode;
51import org.openstreetmap.josm.data.coor.EastNorth;
52import org.openstreetmap.josm.data.osm.Node;
53import org.openstreetmap.josm.data.osm.OsmPrimitive;
54import org.openstreetmap.josm.data.osm.Relation;
55import org.openstreetmap.josm.data.osm.RelationMember;
56import org.openstreetmap.josm.data.osm.Way;
57import org.openstreetmap.josm.data.osm.WaySegment;
58import org.openstreetmap.josm.gui.MapFrame;
59import org.openstreetmap.josm.gui.MapView;
60import org.openstreetmap.josm.tools.GBC;
61import org.openstreetmap.josm.tools.ImageProvider;
62import org.openstreetmap.josm.tools.Pair;
63import org.openstreetmap.josm.tools.Shortcut;
64
65public class Address extends MapMode implements MouseListener, MouseMotionListener, ActionListener {
66 private static final long serialVersionUID = 1L;
67
68 // perhaps make all these tags configurable in the future
69 private String tagHighway = "highway";
70 private String tagHighwayName = "name";
71 private String tagHouseNumber = "addr:housenumber";
72 private String tagHouseStreet = "addr:street";
73 private String tagBuilding = "building";
74 private String relationAddrType = "associatedStreet";
75 private String relationAddrName = "name";
76 private String relationAddrStreetRole = "street";
77 private String relationMemberHouse = "house";
78
79 private JRadioButton plus_one = new JRadioButton("+1", false);
80 private JRadioButton plus_two = new JRadioButton("+2", true); // enable this by default
81 private JRadioButton minus_one = new JRadioButton("-1", false);
82 private JRadioButton minus_two = new JRadioButton("-2", false);
83 final JCheckBox tagPolygon = new JCheckBox(tr("on polygon"));
84
85 JDialog dialog = null;
86 JButton clearButton = null;
87 final JTextField inputNumber = new JTextField();
88 final JTextField inputStreet = new JTextField();
89 JLabel link = new JLabel();
90 private Way selectedWay;
91 private Relation selectedRelation;
92
93 MapFrame mapFrame;
94
95 public Address(MapFrame mapFrame) {
96 super(tr("Add address"), "buildings",
97 tr("Helping tool for tag address"),
98 Shortcut.registerShortcut("mapmode:buildings", tr("Mode: {0}", tr("Buildings")), KeyEvent.VK_E, Shortcut.GROUP_EDIT),
99 mapFrame, getCursor());
100 this.mapFrame = mapFrame;
101 }
102
103 @Override public void enterMode() {
104 super.enterMode();
105 if (dialog == null) {
106 createDialog();
107 }
108 dialog.setVisible(true);
109 Main.map.mapView.addMouseListener(this);
110 }
111
112 @Override public void exitMode() {
113 super.exitMode();
114 Main.map.mapView.removeMouseListener(this);
115 dialog.setVisible(false);
116 }
117
118 @Override
119 public void mousePressed(MouseEvent e) {
120 if (e.getButton() != MouseEvent.BUTTON1)
121 return;
122
123 MapView mv = Main.map.mapView;
124 Point mousePos = e.getPoint();
125 List<Way> mouseOnExistingWays = new ArrayList<Way>();
126 List<Way> mouseOnExistingBuildingWays = new ArrayList<Way>();
127 mouseOnExistingWays = new ArrayList<Way>();
128 Node currentMouseNode = mv.getNearestNode(mousePos, OsmPrimitive.isSelectablePredicate);
129 if (currentMouseNode != null) {
130 // click on existing node
131 setNewSelection(currentMouseNode);
132 String num = currentMouseNode.get(tagHouseNumber);
133 if (num != null) {
134 try {
135 // add new address
136 Integer.parseInt(num);
137 inputNumber.setText(num);
138 applyInputNumberChange();
139 } catch (NumberFormatException en) {
140 System.out.println("Unable to parse house number \"" + num + "\"");
141 }
142 }
143 if (currentMouseNode.get(tagHouseStreet) != null) {
144 inputStreet.setText(currentMouseNode.get(tagHouseNumber));
145 setSelectedWay((Way)null);
146 } else {
147 // check if the node belongs to an associatedStreet relation
148 List<OsmPrimitive> l = currentMouseNode.getReferrers();
149 boolean nodeBelongsToRelation = false;
150 for (OsmPrimitive osm : l) {
151 if (osm instanceof Relation && osm.hasKey("type") && osm.get("type").equals(relationAddrType)) {
152 for (RelationMember rm : ((Relation)osm).getMembers()) {
153 if (rm.getRole().equals(relationAddrStreetRole)) {
154 OsmPrimitive osp = rm.getMember();
155 if (osp instanceof Way && osp.hasKey(tagHighwayName)) {
156 inputStreet.setText(osp.get(tagHighwayName));
157 setSelectedWay((Way)osp, (Relation)osm);
158 nodeBelongsToRelation = true;
159 break;
160 }
161 }
162 }
163 }
164 }
165 if (!nodeBelongsToRelation) {
166 // node exists but doesn't carry address information : add tags like a new node
167 Collection<Command> cmds = new LinkedList<Command>();
168 addAddrToPrimitive(currentMouseNode, cmds);
169 }
170 }
171 } else {
172 List<WaySegment> wss = mv.getNearestWaySegments(mousePos, OsmPrimitive.isSelectablePredicate);
173 for(WaySegment ws : wss) {
174 if (ws.way.get(tagHighway) != null && ws.way.get(tagHighwayName) != null)
175 mouseOnExistingWays.add(ws.way);
176 else if (ws.way.get(tagBuilding) != null && ws.way.get(tagHouseNumber) == null)
177 mouseOnExistingBuildingWays.add(ws.way);
178 }
179 if (mouseOnExistingWays.size() == 1) {
180 // clicked on existing highway => set new street name
181 inputStreet.setText(mouseOnExistingWays.get(0).get(tagHighwayName));
182 setSelectedWay(mouseOnExistingWays.get(0));
183 inputNumber.setText("");
184 setNewSelection(mouseOnExistingWays.get(0));
185 } else if (mouseOnExistingWays.size() == 0) {
186 // clicked a non highway and not a node => add the new address
187 if (inputStreet.getText().equals("") || inputNumber.getText().equals("")) {
188 Toolkit.getDefaultToolkit().beep();
189 } else {
190 Collection<Command> cmds = new LinkedList<Command>();
191 if (tagPolygon.isSelected()) {
192 addAddrToPolygon(mouseOnExistingBuildingWays, cmds);
193 } else {
194 Node n = createNewNode(e, cmds);
195 addAddrToPrimitive(n, cmds);
196 }
197 }
198 }
199 }
200
201 }
202
203 private void addAddrToPolygon(List<Way> mouseOnExistingBuildingWays, Collection<Command> cmds) {
204 for (Way w:mouseOnExistingBuildingWays) {
205 cmds.add(new ChangePropertyCommand(w, tagHouseNumber, inputNumber.getText()));
206 addAddrToPrimitive(w, cmds);
207 }
208 }
209
210 private void addAddrToPrimitive(OsmPrimitive osm, Collection<Command> cmds) {
211 // add the current tag addr:housenumber in node and member in relation
212 cmds.add(new ChangePropertyCommand(osm, tagHouseNumber, inputNumber.getText()));
213 if (Main.pref.getBoolean("cadastrewms.addr.dontUseRelation", false)) {
214 cmds.add(new ChangePropertyCommand(osm, tagHouseStreet, inputStreet.getText()));
215 } else if (selectedWay != null) {
216 // add the node to its relation
217 if (selectedRelation != null) {
218 RelationMember rm = new RelationMember(relationMemberHouse, osm);
219 Relation newRel = new Relation(selectedRelation);
220 newRel.addMember(rm);
221 cmds.add(new ChangeCommand(selectedRelation, newRel));
222 } else {
223 // create new relation
224 Relation newRel = new Relation();
225 newRel.put("type", relationAddrType);
226 newRel.put(relationAddrName, selectedWay.get(tagHighwayName));
227 newRel.addMember(new RelationMember(relationAddrStreetRole, selectedWay));
228 newRel.addMember(new RelationMember(relationMemberHouse, osm));
229 cmds.add(new AddCommand(newRel));
230 }
231 }
232 try {
233 applyInputNumberChange();
234 Command c = new SequenceCommand("Add node address", cmds);
235 Main.main.undoRedo.add(c);
236 setNewSelection(osm);
237 } catch (NumberFormatException en) {
238 System.out.println("Unable to parse house number \"" + inputNumber.getText() + "\"");
239 }
240 }
241
242 private Node createNewNode(MouseEvent e, Collection<Command> cmds) {
243 // DrawAction.mouseReleased() but without key modifiers
244 Node n = new Node(Main.map.mapView.getLatLon(e.getX(), e.getY()));
245 cmds.add(new AddCommand(n));
246 List<WaySegment> wss = Main.map.mapView.getNearestWaySegments(e.getPoint(), OsmPrimitive.isSelectablePredicate);
247 Map<Way, List<Integer>> insertPoints = new HashMap<Way, List<Integer>>();
248 for (WaySegment ws : wss) {
249 List<Integer> is;
250 if (insertPoints.containsKey(ws.way)) {
251 is = insertPoints.get(ws.way);
252 } else {
253 is = new ArrayList<Integer>();
254 insertPoints.put(ws.way, is);
255 }
256
257 is.add(ws.lowerIndex);
258 }
259 Set<Pair<Node,Node>> segSet = new HashSet<Pair<Node,Node>>();
260 ArrayList<Way> replacedWays = new ArrayList<Way>();
261 ArrayList<Way> reuseWays = new ArrayList<Way>();
262 for (Map.Entry<Way, List<Integer>> insertPoint : insertPoints.entrySet()) {
263 Way w = insertPoint.getKey();
264 List<Integer> is = insertPoint.getValue();
265 Way wnew = new Way(w);
266 pruneSuccsAndReverse(is);
267 for (int i : is) {
268 segSet.add(Pair.sort(new Pair<Node,Node>(w.getNode(i), w.getNode(i+1))));
269 }
270 for (int i : is) {
271 wnew.addNode(i + 1, n);
272 }
273 cmds.add(new ChangeCommand(insertPoint.getKey(), wnew));
274 replacedWays.add(insertPoint.getKey());
275 reuseWays.add(wnew);
276 }
277 adjustNode(segSet, n);
278
279 return n;
280 }
281
282 private static void adjustNode(Collection<Pair<Node,Node>> segs, Node n) {
283
284 switch (segs.size()) {
285 case 0:
286 return;
287 case 2:
288 // This computes the intersection between
289 // the two segments and adjusts the node position.
290 Iterator<Pair<Node,Node>> i = segs.iterator();
291 Pair<Node,Node> seg = i.next();
292 EastNorth A = seg.a.getEastNorth();
293 EastNorth B = seg.b.getEastNorth();
294 seg = i.next();
295 EastNorth C = seg.a.getEastNorth();
296 EastNorth D = seg.b.getEastNorth();
297
298 double u=det(B.east() - A.east(), B.north() - A.north(), C.east() - D.east(), C.north() - D.north());
299
300 // Check for parallel segments and do nothing if they are
301 // In practice this will probably only happen when a way has been duplicated
302
303 if (u == 0) return;
304
305 // q is a number between 0 and 1
306 // It is the point in the segment where the intersection occurs
307 // if the segment is scaled to lenght 1
308
309 double q = det(B.north() - C.north(), B.east() - C.east(), D.north() - C.north(), D.east() - C.east()) / u;
310 EastNorth intersection = new EastNorth(
311 B.east() + q * (A.east() - B.east()),
312 B.north() + q * (A.north() - B.north()));
313
314 int snapToIntersectionThreshold
315 = Main.pref.getInteger("edit.snap-intersection-threshold",10);
316
317 // only adjust to intersection if within snapToIntersectionThreshold pixel of mouse click; otherwise
318 // fall through to default action.
319 // (for semi-parallel lines, intersection might be miles away!)
320 if (Main.map.mapView.getPoint(n).distance(Main.map.mapView.getPoint(intersection)) < snapToIntersectionThreshold) {
321 n.setEastNorth(intersection);
322 return;
323 }
324
325 default:
326 EastNorth P = n.getEastNorth();
327 seg = segs.iterator().next();
328 A = seg.a.getEastNorth();
329 B = seg.b.getEastNorth();
330 double a = P.distanceSq(B);
331 double b = P.distanceSq(A);
332 double c = A.distanceSq(B);
333 q = (a - b + c) / (2*c);
334 n.setEastNorth(new EastNorth(B.east() + q * (A.east() - B.east()), B.north() + q * (A.north() - B.north())));
335 }
336 }
337
338 static double det(double a, double b, double c, double d) {
339 return a * d - b * c;
340 }
341
342 private static void pruneSuccsAndReverse(List<Integer> is) {
343 //if (is.size() < 2) return;
344
345 HashSet<Integer> is2 = new HashSet<Integer>();
346 for (int i : is) {
347 if (!is2.contains(i - 1) && !is2.contains(i + 1)) {
348 is2.add(i);
349 }
350 }
351 is.clear();
352 is.addAll(is2);
353 Collections.sort(is);
354 Collections.reverse(is);
355 }
356
357 private static Cursor getCursor() {
358 try {
359 return ImageProvider.getCursor("crosshair", null);
360 } catch (Exception e) {
361 }
362 return Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
363 }
364
365 private void applyInputNumberChange() {
366 Integer num = Integer.parseInt(inputNumber.getText());
367 if (plus_one.isSelected())
368 num = num + 1;
369 if (plus_two.isSelected())
370 num = num + 2;
371 if (minus_one.isSelected() && num > 1)
372 num = num - 1;
373 if (minus_two.isSelected() && num > 2)
374 num = num - 2;
375 inputNumber.setText(num.toString());
376 }
377
378 private void createDialog() {
379 ImageIcon iconLink = ImageProvider.get(null, "Mf_relation.png");
380 link.setIcon(iconLink);
381 link.setEnabled(false);
382 JPanel p = new JPanel(new GridBagLayout());
383 JLabel number = new JLabel(tr("Next no"));
384 JLabel street = new JLabel(tr("Street"));
385 p.add(number, GBC.std().insets(0, 0, 0, 0));
386 p.add(inputNumber, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 0, 5));
387 p.add(street, GBC.std().insets(0, 0, 0, 0));
388 JPanel p2 = new JPanel(new GridBagLayout());
389 inputStreet.setEditable(false);
390 p2.add(inputStreet, GBC.std().fill(GBC.HORIZONTAL).insets(5, 0, 0, 0));
391 p2.add(link, GBC.eol().insets(10, 0, 0, 0));
392 p.add(p2, GBC.eol().fill(GBC.HORIZONTAL));
393 clearButton = new JButton("Clear");
394 clearButton.addActionListener(new ActionListener() {
395 public void actionPerformed(ActionEvent e) {
396 inputNumber.setText("");
397 inputStreet.setText("");
398 setSelectedWay((Way)null);
399 }
400 });
401 ButtonGroup bgIncremental = new ButtonGroup();
402 bgIncremental.add(plus_one);
403 bgIncremental.add(plus_two);
404 bgIncremental.add(minus_one);
405 bgIncremental.add(minus_two);
406 p.add(minus_one, GBC.std().insets(10, 0, 10, 0));
407// p.add(plus_one, GBC.eol().fill(GBC.HORIZONTAL).insets(10, 0, 0, 0));
408 p.add(plus_one, GBC.std().insets(0, 0, 10, 0));
409 tagPolygon.setSelected(Main.pref.getBoolean("cadastrewms.addr.onBuilding", false));
410 tagPolygon.addChangeListener(new ChangeListener() {
411 @Override
412 public void stateChanged(ChangeEvent arg0) {
413 Main.pref.put("cadastrewms.addr.onBuilding", tagPolygon.isSelected());
414 }
415 });
416 p.add(tagPolygon, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 0, 0));
417 p.add(minus_two, GBC.std().insets(10, 0, 10, 0));
418 p.add(plus_two, GBC.std().insets(0, 0, 10, 0));
419 p.add(clearButton, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 0, 0));
420
421 final Object[] options = {};
422 final JOptionPane pane = new JOptionPane(p,
423 JOptionPane.PLAIN_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION,
424 null, options, null);
425 dialog = pane.createDialog(Main.parent, tr("Enter addresses"));
426 dialog.setModal(false);
427 dialog.setAlwaysOnTop(true);
428 dialog.addComponentListener(new ComponentAdapter() {
429 protected void rememberGeometry() {
430 Main.pref.put("cadastrewms.addr.bounds", dialog.getX()+","+dialog.getY()+","+dialog.getWidth()+","+dialog.getHeight());
431 }
432 @Override public void componentMoved(ComponentEvent e) {
433 rememberGeometry();
434 }
435 @Override public void componentResized(ComponentEvent e) {
436 rememberGeometry();
437 }
438 });
439 dialog.addWindowListener(new WindowListener() {
440 @Override
441 public void windowClosing(WindowEvent arg0) {
442 exitMode();
443 Main.map.selectMapMode((MapMode)Main.map.getDefaultButtonAction());
444 }
445 public void windowClosed(WindowEvent e) {}
446 public void windowActivated(WindowEvent arg0) {}
447 public void windowDeactivated(WindowEvent arg0) {}
448 public void windowDeiconified(WindowEvent arg0) {}
449 public void windowIconified(WindowEvent arg0) {}
450 public void windowOpened(WindowEvent arg0) {}
451 });
452 String bounds = Main.pref.get("cadastrewms.addr.bounds",null);
453 if (bounds != null) {
454 String[] b = bounds.split(",");
455 dialog.setBounds(new Rectangle(
456 Integer.parseInt(b[0]),Integer.parseInt(b[1]),Integer.parseInt(b[2]),Integer.parseInt(b[3])));
457 }
458}
459
460 private void setSelectedWay(Way w) {
461 this.selectedWay = w;
462 this.selectedRelation = null;
463 if (w == null) {
464 link.setEnabled(false);
465 } else
466 link.setEnabled(true);
467 }
468
469 private void setSelectedWay(Way w, Relation r) {
470 setSelectedWay(w);
471 this.selectedRelation = r;
472 }
473
474 private void setNewSelection(OsmPrimitive osm) {
475 Collection<OsmPrimitive> newSelection = new LinkedList<OsmPrimitive>(Main.main.getCurrentDataSet().getSelected());
476 newSelection.clear();
477 newSelection.add(osm);
478 getCurrentDataSet().setSelected(osm);
479 }
480
481}
Note: See TracBrowser for help on using the repository browser.