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

Last change on this file since 32556 was 32556, checked in by donvip, 9 years ago

checkstyle

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