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

Last change on this file since 32425 was 32425, checked in by donvip, 10 years ago

remove calls to deprecated methods

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