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

Last change on this file since 22504 was 22504, checked in by pieren, 14 years ago

Fix minor issues about the new ctrl modifier in addr helping tool.

File size: 21.8 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 boolean shift;
92 private boolean ctrl;
93
94 public Address(MapFrame mapFrame) {
95 super(tr("Add address"), "buildings",
96 tr("Helping tool for tag address"),
97 Shortcut.registerShortcut("mapmode:buildings", tr("Mode: {0}", tr("Buildings")), KeyEvent.VK_E, Shortcut.GROUP_EDIT),
98 mapFrame, getCursor());
99 }
100
101 @Override public void enterMode() {
102 super.enterMode();
103 if (dialog == null) {
104 createDialog();
105 }
106 dialog.setVisible(true);
107 Main.map.mapView.addMouseListener(this);
108 }
109
110 @Override public void exitMode() {
111 if (Main.map.mapView != null) {
112 super.exitMode();
113 Main.map.mapView.removeMouseListener(this);
114 }
115 dialog.setVisible(false);
116 }
117
118 @Override
119 public void mousePressed(MouseEvent e) {
120 if (e.getButton() != MouseEvent.BUTTON1)
121 return;
122 shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
123 ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
124 MapView mv = Main.map.mapView;
125 Point mousePos = e.getPoint();
126 List<Way> mouseOnExistingWays = new ArrayList<Way>();
127 List<Way> mouseOnExistingBuildingWays = new ArrayList<Way>();
128 mouseOnExistingWays = new ArrayList<Way>();
129 Node currentMouseNode = mv.getNearestNode(mousePos, OsmPrimitive.isSelectablePredicate);
130 if (currentMouseNode != null) {
131 // click on existing node
132 setNewSelection(currentMouseNode);
133 String num = currentMouseNode.get(tagHouseNumber);
134 if (num != null) {
135 try {
136 // add new address
137 Integer.parseInt(num);
138 inputNumber.setText(num);
139 applyInputNumberChange();
140 } catch (NumberFormatException en) {
141 System.out.println("Unable to parse house number \"" + num + "\"");
142 }
143 }
144 if (currentMouseNode.get(tagHouseStreet) != null) {
145 inputStreet.setText(currentMouseNode.get(tagHouseStreet));
146 if (ctrl) {
147 Collection<Command> cmds = new LinkedList<Command>();
148 addAddrToPrimitive(currentMouseNode, cmds);
149 if (num == null)
150 applyInputNumberChange();
151 }
152 setSelectedWay((Way)null);
153 } else {
154 // check if the node belongs to an associatedStreet relation
155 Way wayInRelationAddr = findWayInRelationAddr(currentMouseNode);
156 if (wayInRelationAddr == null) {
157 // node exists but doesn't carry address information : add tags like a new node
158 if (ctrl) {
159 applyInputNumberChange();
160 }
161 Collection<Command> cmds = new LinkedList<Command>();
162 addAddrToPrimitive(currentMouseNode, cmds);
163 } else {
164 inputStreet.setText(wayInRelationAddr.get(tagHighwayName));
165 setSelectedWay(wayInRelationAddr);
166 }
167 }
168 } else {
169 List<WaySegment> wss = mv.getNearestWaySegments(mousePos, OsmPrimitive.isSelectablePredicate);
170 for(WaySegment ws : wss) {
171 if (ws.way.get(tagHighway) != null && ws.way.get(tagHighwayName) != null)
172 mouseOnExistingWays.add(ws.way);
173 else if (ws.way.get(tagBuilding) != null && ws.way.get(tagHouseNumber) == null)
174 mouseOnExistingBuildingWays.add(ws.way);
175 }
176 if (mouseOnExistingWays.size() == 1) {
177 // clicked on existing highway => set new street name
178 inputStreet.setText(mouseOnExistingWays.get(0).get(tagHighwayName));
179 setSelectedWay(mouseOnExistingWays.get(0));
180 inputNumber.setText("");
181 setNewSelection(mouseOnExistingWays.get(0));
182 } else if (mouseOnExistingWays.size() == 0) {
183 // clicked a non highway and not a node => add the new address
184 if (inputStreet.getText().equals("") || inputNumber.getText().equals("")) {
185 Toolkit.getDefaultToolkit().beep();
186 } else {
187 Collection<Command> cmds = new LinkedList<Command>();
188 if (ctrl) {
189 applyInputNumberChange();
190 }
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 Way findWayInRelationAddr(Node n) {
204 List<OsmPrimitive> l = n.getReferrers();
205 for (OsmPrimitive osm : l) {
206 if (osm instanceof Relation && osm.hasKey("type") && osm.get("type").equals(relationAddrType)) {
207 for (RelationMember rm : ((Relation)osm).getMembers()) {
208 if (rm.getRole().equals(relationAddrStreetRole)) {
209 OsmPrimitive osp = rm.getMember();
210 if (osp instanceof Way && osp.hasKey(tagHighwayName)) {
211 return (Way)osp;
212 }
213 }
214 }
215 }
216 }
217 return null;
218 }
219
220 private void addAddrToPolygon(List<Way> mouseOnExistingBuildingWays, Collection<Command> cmds) {
221 for (Way w:mouseOnExistingBuildingWays) {
222 addAddrToPrimitive(w, cmds);
223 }
224 }
225
226 private void addAddrToPrimitive(OsmPrimitive osm, Collection<Command> cmds) {
227 // add the current tag addr:housenumber in node and member in relation (if so configured)
228 if (shift) {
229 try {
230 revertInputNumberChange();
231 } catch (NumberFormatException en) {
232 System.out.println("Unable to parse house number \"" + inputNumber.getText() + "\"");
233 }
234
235 }
236 cmds.add(new ChangePropertyCommand(osm, tagHouseNumber, inputNumber.getText()));
237 if (Main.pref.getBoolean("cadastrewms.addr.dontUseRelation", false)) {
238 cmds.add(new ChangePropertyCommand(osm, tagHouseStreet, inputStreet.getText()));
239 } else if (selectedWay != null) {
240 Relation selectedRelation = findRelationAddr(selectedWay);
241 // add the node to its relation
242 if (selectedRelation != null) {
243 RelationMember rm = new RelationMember(relationMemberHouse, osm);
244 Relation newRel = new Relation(selectedRelation);
245 newRel.addMember(rm);
246 cmds.add(new ChangeCommand(selectedRelation, newRel));
247 } else {
248 // create new relation
249 Relation newRel = new Relation();
250 newRel.put("type", relationAddrType);
251 newRel.put(relationAddrName, selectedWay.get(tagHighwayName));
252 newRel.addMember(new RelationMember(relationAddrStreetRole, selectedWay));
253 newRel.addMember(new RelationMember(relationMemberHouse, osm));
254 cmds.add(new AddCommand(newRel));
255 }
256 }
257 try {
258 applyInputNumberChange();
259 Command c = new SequenceCommand("Add node address", cmds);
260 Main.main.undoRedo.add(c);
261 setNewSelection(osm);
262 } catch (NumberFormatException en) {
263 System.out.println("Unable to parse house number \"" + inputNumber.getText() + "\"");
264 }
265 }
266
267 private Relation findRelationAddr(Way w) {
268 List<OsmPrimitive> l = w.getReferrers();
269 for (OsmPrimitive osm : l) {
270 if (osm instanceof Relation && osm.hasKey("type") && osm.get("type").equals(relationAddrType)) {
271 return (Relation)osm;
272 }
273 }
274 return null;
275 }
276
277 private Node createNewNode(MouseEvent e, Collection<Command> cmds) {
278 // DrawAction.mouseReleased() but without key modifiers
279 Node n = new Node(Main.map.mapView.getLatLon(e.getX(), e.getY()));
280 cmds.add(new AddCommand(n));
281 List<WaySegment> wss = Main.map.mapView.getNearestWaySegments(e.getPoint(), OsmPrimitive.isSelectablePredicate);
282 Map<Way, List<Integer>> insertPoints = new HashMap<Way, List<Integer>>();
283 for (WaySegment ws : wss) {
284 List<Integer> is;
285 if (insertPoints.containsKey(ws.way)) {
286 is = insertPoints.get(ws.way);
287 } else {
288 is = new ArrayList<Integer>();
289 insertPoints.put(ws.way, is);
290 }
291
292 is.add(ws.lowerIndex);
293 }
294 Set<Pair<Node,Node>> segSet = new HashSet<Pair<Node,Node>>();
295 ArrayList<Way> replacedWays = new ArrayList<Way>();
296 ArrayList<Way> reuseWays = new ArrayList<Way>();
297 for (Map.Entry<Way, List<Integer>> insertPoint : insertPoints.entrySet()) {
298 Way w = insertPoint.getKey();
299 List<Integer> is = insertPoint.getValue();
300 Way wnew = new Way(w);
301 pruneSuccsAndReverse(is);
302 for (int i : is) {
303 segSet.add(Pair.sort(new Pair<Node,Node>(w.getNode(i), w.getNode(i+1))));
304 }
305 for (int i : is) {
306 wnew.addNode(i + 1, n);
307 }
308 cmds.add(new ChangeCommand(insertPoint.getKey(), wnew));
309 replacedWays.add(insertPoint.getKey());
310 reuseWays.add(wnew);
311 }
312 adjustNode(segSet, n);
313
314 return n;
315 }
316
317 private static void adjustNode(Collection<Pair<Node,Node>> segs, Node n) {
318
319 switch (segs.size()) {
320 case 0:
321 return;
322 case 2:
323 // This computes the intersection between
324 // the two segments and adjusts the node position.
325 Iterator<Pair<Node,Node>> i = segs.iterator();
326 Pair<Node,Node> seg = i.next();
327 EastNorth A = seg.a.getEastNorth();
328 EastNorth B = seg.b.getEastNorth();
329 seg = i.next();
330 EastNorth C = seg.a.getEastNorth();
331 EastNorth D = seg.b.getEastNorth();
332
333 double u=det(B.east() - A.east(), B.north() - A.north(), C.east() - D.east(), C.north() - D.north());
334
335 // Check for parallel segments and do nothing if they are
336 // In practice this will probably only happen when a way has been duplicated
337
338 if (u == 0) return;
339
340 // q is a number between 0 and 1
341 // It is the point in the segment where the intersection occurs
342 // if the segment is scaled to lenght 1
343
344 double q = det(B.north() - C.north(), B.east() - C.east(), D.north() - C.north(), D.east() - C.east()) / u;
345 EastNorth intersection = new EastNorth(
346 B.east() + q * (A.east() - B.east()),
347 B.north() + q * (A.north() - B.north()));
348
349 int snapToIntersectionThreshold
350 = Main.pref.getInteger("edit.snap-intersection-threshold",10);
351
352 // only adjust to intersection if within snapToIntersectionThreshold pixel of mouse click; otherwise
353 // fall through to default action.
354 // (for semi-parallel lines, intersection might be miles away!)
355 if (Main.map.mapView.getPoint(n).distance(Main.map.mapView.getPoint(intersection)) < snapToIntersectionThreshold) {
356 n.setEastNorth(intersection);
357 return;
358 }
359
360 default:
361 EastNorth P = n.getEastNorth();
362 seg = segs.iterator().next();
363 A = seg.a.getEastNorth();
364 B = seg.b.getEastNorth();
365 double a = P.distanceSq(B);
366 double b = P.distanceSq(A);
367 double c = A.distanceSq(B);
368 q = (a - b + c) / (2*c);
369 n.setEastNorth(new EastNorth(B.east() + q * (A.east() - B.east()), B.north() + q * (A.north() - B.north())));
370 }
371 }
372
373 static double det(double a, double b, double c, double d) {
374 return a * d - b * c;
375 }
376
377 private static void pruneSuccsAndReverse(List<Integer> is) {
378 //if (is.size() < 2) return;
379
380 HashSet<Integer> is2 = new HashSet<Integer>();
381 for (int i : is) {
382 if (!is2.contains(i - 1) && !is2.contains(i + 1)) {
383 is2.add(i);
384 }
385 }
386 is.clear();
387 is.addAll(is2);
388 Collections.sort(is);
389 Collections.reverse(is);
390 }
391
392 private static Cursor getCursor() {
393 try {
394 return ImageProvider.getCursor("crosshair", null);
395 } catch (Exception e) {
396 }
397 return Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
398 }
399
400 private void applyInputNumberChange() {
401 Integer num = Integer.parseInt(inputNumber.getText());
402 if (plus_one.isSelected())
403 num = num + 1;
404 if (plus_two.isSelected())
405 num = num + 2;
406 if (minus_one.isSelected() && num > 1)
407 num = num - 1;
408 if (minus_two.isSelected() && num > 2)
409 num = num - 2;
410 inputNumber.setText(num.toString());
411 }
412
413 private void revertInputNumberChange() {
414 Integer num = Integer.parseInt(inputNumber.getText());
415 if (plus_one.isSelected())
416 num = num - 1;
417 if (plus_two.isSelected())
418 num = num - 2;
419 if (minus_one.isSelected() && num > 1)
420 num = num + 1;
421 if (minus_two.isSelected() && num > 2)
422 num = num + 2;
423 inputNumber.setText(num.toString());
424 }
425
426 private void createDialog() {
427 ImageIcon iconLink = ImageProvider.get(null, "Mf_relation.png");
428 link.setIcon(iconLink);
429 link.setEnabled(false);
430 JPanel p = new JPanel(new GridBagLayout());
431 JLabel number = new JLabel(tr("Next no"));
432 JLabel street = new JLabel(tr("Street"));
433 p.add(number, GBC.std().insets(0, 0, 0, 0));
434 p.add(inputNumber, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 0, 5));
435 p.add(street, GBC.std().insets(0, 0, 0, 0));
436 JPanel p2 = new JPanel(new GridBagLayout());
437 inputStreet.setEditable(false);
438 p2.add(inputStreet, GBC.std().fill(GBC.HORIZONTAL).insets(5, 0, 0, 0));
439 p2.add(link, GBC.eol().insets(10, 0, 0, 0));
440 p.add(p2, GBC.eol().fill(GBC.HORIZONTAL));
441 clearButton = new JButton("Clear");
442 clearButton.addActionListener(new ActionListener() {
443 public void actionPerformed(ActionEvent e) {
444 inputNumber.setText("");
445 inputStreet.setText("");
446 setSelectedWay((Way)null);
447 }
448 });
449 ButtonGroup bgIncremental = new ButtonGroup();
450 bgIncremental.add(plus_one);
451 bgIncremental.add(plus_two);
452 bgIncremental.add(minus_one);
453 bgIncremental.add(minus_two);
454 p.add(minus_one, GBC.std().insets(10, 0, 10, 0));
455// p.add(plus_one, GBC.eol().fill(GBC.HORIZONTAL).insets(10, 0, 0, 0));
456 p.add(plus_one, GBC.std().insets(0, 0, 10, 0));
457 tagPolygon.setSelected(Main.pref.getBoolean("cadastrewms.addr.onBuilding", false));
458 tagPolygon.addChangeListener(new ChangeListener() {
459 @Override
460 public void stateChanged(ChangeEvent arg0) {
461 Main.pref.put("cadastrewms.addr.onBuilding", tagPolygon.isSelected());
462 }
463 });
464 p.add(tagPolygon, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 0, 0));
465 p.add(minus_two, GBC.std().insets(10, 0, 10, 0));
466 p.add(plus_two, GBC.std().insets(0, 0, 10, 0));
467 p.add(clearButton, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 0, 0));
468
469 final Object[] options = {};
470 final JOptionPane pane = new JOptionPane(p,
471 JOptionPane.PLAIN_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION,
472 null, options, null);
473 dialog = pane.createDialog(Main.parent, tr("Enter addresses"));
474 dialog.setModal(false);
475 dialog.setAlwaysOnTop(true);
476 dialog.addComponentListener(new ComponentAdapter() {
477 protected void rememberGeometry() {
478 Main.pref.put("cadastrewms.addr.bounds", dialog.getX()+","+dialog.getY()+","+dialog.getWidth()+","+dialog.getHeight());
479 }
480 @Override public void componentMoved(ComponentEvent e) {
481 rememberGeometry();
482 }
483 @Override public void componentResized(ComponentEvent e) {
484 rememberGeometry();
485 }
486 });
487 dialog.addWindowListener(new WindowListener() {
488 @Override
489 public void windowClosing(WindowEvent arg0) {
490 exitMode();
491 Main.map.selectMapMode((MapMode)Main.map.getDefaultButtonAction());
492 }
493 public void windowClosed(WindowEvent e) {}
494 public void windowActivated(WindowEvent arg0) {}
495 public void windowDeactivated(WindowEvent arg0) {}
496 public void windowDeiconified(WindowEvent arg0) {}
497 public void windowIconified(WindowEvent arg0) {}
498 public void windowOpened(WindowEvent arg0) {}
499 });
500 String bounds = Main.pref.get("cadastrewms.addr.bounds",null);
501 if (bounds != null) {
502 String[] b = bounds.split(",");
503 dialog.setBounds(new Rectangle(
504 Integer.parseInt(b[0]),Integer.parseInt(b[1]),Integer.parseInt(b[2]),Integer.parseInt(b[3])));
505 }
506}
507
508 private void setSelectedWay(Way w) {
509 this.selectedWay = w;
510 if (w == null) {
511 link.setEnabled(false);
512 } else
513 link.setEnabled(true);
514 }
515
516 private void setNewSelection(OsmPrimitive osm) {
517 Collection<OsmPrimitive> newSelection = new LinkedList<OsmPrimitive>(Main.main.getCurrentDataSet().getSelected());
518 newSelection.clear();
519 newSelection.add(osm);
520 getCurrentDataSet().setSelected(osm);
521 }
522
523}
Note: See TracBrowser for help on using the repository browser.