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

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

Many small fixes and improvements

File size: 19.6 KB
Line 
1package cadastre_fr;
2
3import static org.openstreetmap.josm.tools.I18n.tr;
4
5import java.awt.Color;
6import java.awt.Point;
7import java.awt.event.ActionEvent;
8import java.awt.event.KeyEvent;
9import java.awt.event.MouseEvent;
10import java.awt.event.MouseListener;
11import java.awt.event.MouseMotionListener;
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.HashMap;
16import java.util.HashSet;
17import java.util.LinkedList;
18import java.util.List;
19import java.util.Map;
20import java.util.TreeMap;
21
22import javax.swing.JOptionPane;
23
24import org.openstreetmap.josm.Main;
25import org.openstreetmap.josm.gui.MapFrame;
26import org.openstreetmap.josm.actions.mapmode.MapMode;
27import org.openstreetmap.josm.command.AddCommand;
28import org.openstreetmap.josm.command.ChangeCommand;
29import org.openstreetmap.josm.command.ChangePropertyCommand;
30import org.openstreetmap.josm.command.Command;
31import org.openstreetmap.josm.command.MoveCommand;
32import org.openstreetmap.josm.command.SequenceCommand;
33import org.openstreetmap.josm.data.coor.EastNorth;
34import org.openstreetmap.josm.data.osm.BBox;
35import org.openstreetmap.josm.data.osm.DataSet;
36import org.openstreetmap.josm.data.osm.Node;
37import org.openstreetmap.josm.data.osm.Way;
38import org.openstreetmap.josm.data.osm.WaySegment;
39import org.openstreetmap.josm.tools.ImageProvider;
40import org.openstreetmap.josm.tools.Shortcut;
41import org.openstreetmap.josm.gui.layer.Layer;
42
43/**
44 * Trace a way around buildings from cadastre images.
45 * Inspired by Lakewalker plugin.
46 * @author Pieren
47 */
48public class Buildings extends MapMode implements MouseListener, MouseMotionListener {
49
50 private static final long serialVersionUID = 1L;
51 GeorefImage selectedImage;
52 WMSLayer selectedLayer;
53 private EastNorth clickedEastNorth;
54 private class Pixel {
55 public Point p;
56 public int dir;
57 public Pixel(int x, int y, int dir) {
58 this.p = new Point(x,y);
59 this.dir = dir;
60 }
61 @Override
62 public int hashCode() {
63 return p.hashCode();
64 }
65 @Override
66 public boolean equals(Object obj) {
67 if (obj instanceof Pixel)
68 return p.equals(new Point(((Pixel)obj).p.x, ((Pixel)obj).p.y));
69 return p.equals(obj);
70 }
71 }
72 private ArrayList<Pixel> listPixels = new ArrayList<Pixel>();
73
74 private static final int cMaxnode = 10000;
75 private int[] dirsX = new int[] {1,1,0,-1,-1,-1,0,1};
76 private int[] dirsY = new int[] {0,1,1,1,0,-1,-1,-1};
77
78 private int orange = Color.ORANGE.getRGB(); // new color of pixels ending nowhere (cul-de-sac)
79 BuildingsImageModifier bim = new BuildingsImageModifier();
80
81 private double snapDistance = Main.pref.getDouble("cadastrewms.snap-distance", 60); // in centimeters
82 private double snapDistanceSq = snapDistance*snapDistance;
83 private double dx, dy;
84
85
86 public Buildings(MapFrame mapFrame) {
87 super(tr("Grab buildings"), "buildings",
88 tr("Extract building on click (vector images only)"),
89 Shortcut.registerShortcut("mapmode:buildings", tr("Mode: {0}", tr("Buildings")), KeyEvent.VK_E, Shortcut.GROUP_EDIT),
90 mapFrame, ImageProvider.getCursor("normal", "move"));
91 }
92
93 @Override public void enterMode() {
94 super.enterMode();
95 boolean atLeastOneBuildingLayer = false;
96 for (Layer layer : Main.map.mapView.getAllLayers()) {
97 if (layer instanceof WMSLayer && ((WMSLayer)layer).isBuildingsOnly()) {
98 atLeastOneBuildingLayer = true;
99 break;
100 }
101 }
102 if (atLeastOneBuildingLayer && Main.main.getCurrentDataSet() != null) {
103 Main.map.mapView.addMouseListener(this);
104 Main.map.mapView.addMouseMotionListener(this);
105 } else {
106 JOptionPane.showMessageDialog(Main.parent,tr("This feature requires (at least) one special cadastre\nBuildings layer and an OSM data layer."));
107 exitMode();
108 Main.map.selectMapMode((MapMode)Main.map.getDefaultButtonAction());
109 }
110 }
111
112 @Override public void exitMode() {
113 super.exitMode();
114 Main.map.mapView.removeMouseListener(this);
115 Main.map.mapView.removeMouseMotionListener(this);
116 }
117
118 @Override
119 public void mousePressed(MouseEvent e) {
120 if (e.getButton() != MouseEvent.BUTTON1)
121 return;
122 selectedImage = null;
123 // boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
124 // boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
125 boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
126 for (Layer layer : Main.map.mapView.getAllLayers()) {
127 if (layer.isVisible() && layer instanceof WMSLayer && ((WMSLayer)layer).isBuildingsOnly() ) {
128 clickedEastNorth = Main.map.mapView.getEastNorth(e.getX(), e.getY());
129 selectedLayer = ((WMSLayer) layer);
130 selectedImage = selectedLayer.findImage(clickedEastNorth);
131 }
132 }
133 if (selectedImage != null) {
134 int x = (int)((clickedEastNorth.east() - selectedImage.min.east())*selectedImage.getPixelPerEast());
135 int y = selectedImage.image.getHeight() - (int)((clickedEastNorth.north() - selectedImage.min.north())*selectedImage.getPixelPerNorth());
136 int rgb = selectedImage.image.getRGB(x, y);
137 System.out.println("image found"+", x="+x+", y="+y+", RGB="+rgb);
138 boolean clickOnRoof = bim.isRoofColor(rgb, shift);
139 boolean clickOnBuilding = bim.isBuildingColor(rgb, shift);
140 if (clickOnRoof || clickOnBuilding) {
141 if (traceBuilding(x, y, clickOnBuilding, shift) && listPixels.size() > 3) {
142 Way wayToCreate = new Way();
143 Way way2 = new Way();
144 double pPE = selectedImage.getPixelPerEast();
145 double pPN = selectedImage.getPixelPerNorth();
146 for (int i=0; i < listPixels.size(); i++) {
147 EastNorth en = new EastNorth(selectedImage.min.east() + ((listPixels.get(i).p.x + 0.5)/ pPE),
148 selectedImage.max.north() - ((listPixels.get(i).p.y + 0.5)/ pPN));
149 Node nodeToAdd = new Node(Main.proj.eastNorth2latlon(en));
150 wayToCreate.addNode(nodeToAdd);
151 }
152 wayToCreate.addNode(wayToCreate.getNode(0)); // close the way
153 new SimplifyWay().simplifyWay(wayToCreate, 0.2);
154 // move the node closing the loop and simplify again
155 for (int i = 1; i < wayToCreate.getNodesCount(); i++) {
156 way2.addNode(wayToCreate.getNode(i));
157 }
158 way2.addNode(way2.getNode(0));
159 new SimplifyWay().simplifyWay(way2, 0.2);
160 Way wayToAdd = new Way();
161 Collection<Command> cmds = new LinkedList<Command>();
162 for (int i = 0; i < way2.getNodesCount()-1; i++) {
163 Node nearestNode = getNearestNode(way2.getNode(i));
164 if (nearestNode == null) {
165 List<WaySegment> wss = getNearestWaySegments(way2.getNode(i));
166 wayToAdd.addNode(way2.getNode(i));
167 cmds.add(new AddCommand(way2.getNode(i)));
168 if (wss.size() > 0)
169 cmds.add(new MoveCommand(way2.getNode(i), dx, dy));
170 joinNodeToExistingWays(wss, way2.getNode(i), cmds);
171 } else {// reuse the existing node
172 wayToAdd.addNode(nearestNode);
173 cmds.add(new MoveCommand(nearestNode, dx, dy));
174 }
175 }
176 wayToAdd.addNode(wayToAdd.getNode(0));
177 cmds.add(new AddCommand(wayToAdd));
178 if (clickOnBuilding)
179 addBuildingTags(cmds, wayToAdd);
180 if (clickOnRoof) {
181 addRoofTags(cmds, wayToAdd);
182 }
183 Main.main.undoRedo.add(new SequenceCommand(tr("Create building"), cmds));
184 getCurrentDataSet().setSelected(wayToAdd);
185 Main.map.repaint();
186 }
187 }
188 }
189 }
190
191 @Override public void mouseDragged(MouseEvent e) {
192 }
193
194 @Override public void mouseReleased(MouseEvent e) {
195 }
196
197 public void mouseEntered(MouseEvent e) {
198 }
199 public void mouseExited(MouseEvent e) {
200 }
201 public void mouseMoved(MouseEvent e) {
202 }
203
204 @Override public void mouseClicked(MouseEvent e) {
205 }
206
207 private void addBuildingTags(Collection<Command> cmds, Way wayToAdd) {
208 cmds.add(new ChangePropertyCommand(wayToAdd, "building", "yes"));
209 }
210
211 private void addRoofTags(Collection<Command> cmds, Way wayToAdd) {
212 cmds.add(new ChangePropertyCommand(wayToAdd, "building", "yes"));
213 cmds.add(new ChangePropertyCommand(wayToAdd, "wall", "no"));
214 }
215
216 private boolean traceBuilding (int x, int y, boolean buildingColors, boolean ignoreParcels) {
217 // search start point at same x but smallest y (upper border on the screen)
218 int startY = 0; int startX = x;
219 while (y > 0) {
220 y--;
221 if (!bim.isBuildingOrRoofColor(selectedImage.image, x, y, buildingColors, ignoreParcels)) {
222 System.out.println("at "+x+","+y+" color was "+selectedImage.image.getRGB(x,y));
223 y++;
224 startY = y;
225 break;
226 }
227 }
228 if (startY == 0) {
229 System.out.println("border not found");
230 return false;
231 } else
232 System.out.println("start at x="+startX+", y="+startY);
233 listPixels.clear();
234 int test_x = 0;
235 int test_y = 0;
236 int new_dir = 0;
237 addPixeltoList(x, y, new_dir);
238 int last_dir = 1;
239 for(int i = 0; i < cMaxnode; i++){
240 //System.out.println("node "+i);
241 for(int d = 1; d <= this.dirsY.length; d++){
242 new_dir = (last_dir + d + 4) % 8;
243 test_x = x + this.dirsX[new_dir];
244 test_y = y + this.dirsY[new_dir];
245 if(test_x < 0 || test_x >= selectedImage.image.getWidth() ||
246 test_y < 0 || test_y >= selectedImage.image.getHeight()){
247 System.out.println("Outside image");
248 return false;
249 }
250 if(bim.isBuildingOrRoofColor(selectedImage.image, test_x, test_y, buildingColors, ignoreParcels)){
251 System.out.println("building color at "+test_x+","+test_y+" new_dir="+new_dir);
252 break;
253 }
254
255 if(d == this.dirsY.length-1){
256 System.out.println("Got stuck at "+x+","+y);
257 // cul-de-sac : disable current pixel and move two steps back
258 selectedImage.image.setRGB(x, y, orange);
259 if (removeTwoLastPixelsFromList()) {
260 x = listPixels.get(listPixels.size()-1).p.x;
261 y = listPixels.get(listPixels.size()-1).p.y;
262 last_dir = listPixels.get(listPixels.size()-1).dir;
263 System.out.println("return at "+x+","+y+" and try again");
264 d = 1;
265 continue;
266 } else {
267 System.out.println("cannot try another way");
268 return false;
269 }
270 }
271 }
272// if (last_dir == new_dir)
273// // Same direction. First simplification by removing previous pixel.
274// listPixels.remove(listPixels.size()-1);
275 last_dir = new_dir;
276 // Set the pixel we found as current
277 x = test_x;
278 y = test_y;
279 // Break the loop if we managed to get back to our starting point
280 if (listPixels.contains(new Pixel(x, y, 0))){
281 System.out.println("loop closed at "+x+","+y+", exit");
282 break;
283 }
284 addPixeltoList(x, y, new_dir);
285 }
286 System.out.println("list size="+listPixels.size());
287 return true;
288 }
289
290 private void addPixeltoList(int x, int y, int dir) {
291 listPixels.add( new Pixel(x, y, dir));
292 System.out.println("added pixel at "+x+","+y);
293 }
294
295 private boolean removeTwoLastPixelsFromList() {
296 if (listPixels.size() > 2) {
297 System.out.println("remove "+listPixels.get(listPixels.size()-1).p.x + ","+listPixels.get(listPixels.size()-1).p.y);
298 listPixels.remove(listPixels.size()-1);
299 System.out.println("remove "+listPixels.get(listPixels.size()-1).p.x + ","+listPixels.get(listPixels.size()-1).p.y);
300 listPixels.remove(listPixels.size()-1);
301 return true;
302 }
303 return false;
304 }
305
306 private BBox getSnapDistanceBBox(Node n) {
307 return new BBox(Main.proj.eastNorth2latlon(new EastNorth(n.getEastNorth().east() - snapDistance, n.getEastNorth().north() - snapDistance)),
308 Main.proj.eastNorth2latlon(new EastNorth(n.getEastNorth().east() + snapDistance, n.getEastNorth().north() + snapDistance)));
309 }
310
311 private Point getPointInCentimeters(Node n) {
312 return new Point(new Double(n.getEastNorth().getX()*100).intValue(),
313 new Double(n.getEastNorth().getY()*100).intValue());
314 }
315
316 public Node getNearestNode(Node newNode) {
317 Point newP = getPointInCentimeters(newNode);
318 DataSet ds = getCurrentDataSet();
319 if (ds == null)
320 return null;
321
322 double minDistanceSq = snapDistanceSq;
323 Node minNode = null;
324 for (Node n : ds.searchNodes(getSnapDistanceBBox(newNode))) {
325 if (!n.isUsable()) {
326 continue;
327 }
328 Point sp = new Point(new Double(n.getEastNorth().getX()*100).intValue(),
329 new Double(n.getEastNorth().getY()*100).intValue());
330 double dist = newP.distanceSq(sp); // in centimeter !
331 if (dist < minDistanceSq) {
332 minDistanceSq = dist;
333 minNode = n;
334 }
335 // when multiple nodes on one point, prefer new or selected nodes
336 else if (dist == minDistanceSq && minNode != null
337 && ((n.isNew() && ds.isSelected(n))
338 || (!ds.isSelected(minNode) && (ds.isSelected(n) || n.isNew())))) {
339 minNode = n;
340 }
341 }
342 if (minNode != null) {
343 dx = (newNode.getEastNorth().getX() - minNode.getEastNorth().getX())/2;
344 dy = (newNode.getEastNorth().getY() - minNode.getEastNorth().getY())/2;
345 }
346 return minNode;
347 }
348
349 private List<WaySegment> getNearestWaySegments(Node newNode) {
350 Point newP = new Point(new Double(newNode.getEastNorth().getX()*100).intValue(),
351 new Double(newNode.getEastNorth().getY()*100).intValue());
352 TreeMap<Double, List<WaySegment>> nearest = new TreeMap<Double, List<WaySegment>>();
353 DataSet ds = getCurrentDataSet();
354 if (ds == null)
355 return null;
356
357 for (Way w : ds.searchWays(getSnapDistanceBBox(newNode))) {
358 if (!w.isUsable()) {
359 continue;
360 }
361 Node lastN = null;
362 int i = -2;
363 for (Node n : w.getNodes()) {
364 i++;
365 if (n.isDeleted() || n.isIncomplete()) {
366 continue;
367 }
368 if (lastN == null) {
369 lastN = n;
370 continue;
371 }
372
373 Point A = getPointInCentimeters(lastN);
374 Point B = getPointInCentimeters(n);
375 double c = A.distanceSq(B);
376 double a = newP.distanceSq(B);
377 double b = newP.distanceSq(A);
378 double perDist = a - (a - b + c) * (a - b + c) / 4 / c; // perpendicular distance squared
379 if (perDist < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
380 if (ds.isSelected(w)) {
381 perDist -= 0.00001;
382 }
383 List<WaySegment> l;
384 if (nearest.containsKey(perDist)) {
385 l = nearest.get(perDist);
386 } else {
387 l = new LinkedList<WaySegment>();
388 nearest.put(perDist, l);
389 }
390 Point pivot = A;
391 double startAngle = Math.atan2(B.x-pivot.x, B.y-pivot.y);
392 double endAngle = Math.atan2(newP.x-pivot.x, newP.y-pivot.y);
393 double angle = endAngle - startAngle;
394 double ratio = A.distance(newP)/A.distance(B);//).intValue();
395 Point perP = new Point(A.x+new Double((B.x-A.x)*ratio).intValue(),
396 A.y+new Double((B.y-A.y)*ratio).intValue());
397 dx = (perP.x-newP.x)/200.0; // back to meters this time and whole distance by two
398 dy = (perP.y-newP.y)/200.0;
399 System.out.println(angle+","+ ratio+","+perP );
400 l.add(new WaySegment(w, i));
401 }
402
403 lastN = n;
404 }
405 }
406 ArrayList<WaySegment> nearestList = new ArrayList<WaySegment>();
407 for (List<WaySegment> wss : nearest.values()) {
408 nearestList.addAll(wss);
409 }
410 return nearestList;
411 }
412
413 private void joinNodeToExistingWays(List<WaySegment> wss, Node newNode, Collection<Command> cmds) {
414 HashMap<Way, List<Integer>> insertPoints = new HashMap<Way, List<Integer>>();
415 for (WaySegment ws : wss) {
416 List<Integer> is;
417 if (insertPoints.containsKey(ws.way)) {
418 is = insertPoints.get(ws.way);
419 } else {
420 is = new ArrayList<Integer>();
421 insertPoints.put(ws.way, is);
422 }
423
424 if (ws.way.getNode(ws.lowerIndex) != newNode
425 && ws.way.getNode(ws.lowerIndex+1) != newNode) {
426 is.add(ws.lowerIndex);
427 }
428 }
429
430 for (Map.Entry<Way, List<Integer>> insertPoint : insertPoints.entrySet()) {
431 List<Integer> is = insertPoint.getValue();
432 if (is.size() == 0)
433 continue;
434
435 Way w = insertPoint.getKey();
436 List<Node> nodesToAdd = w.getNodes();
437 pruneSuccsAndReverse(is);
438 for (int i : is) {
439 nodesToAdd.add(i+1, newNode);
440 }
441 Way wnew = new Way(w);
442 wnew.setNodes(nodesToAdd);
443 cmds.add(new ChangeCommand(w, wnew));
444 }
445 }
446
447 private static void pruneSuccsAndReverse(List<Integer> is) {
448 HashSet<Integer> is2 = new HashSet<Integer>();
449 for (int i : is) {
450 if (!is2.contains(i - 1) && !is2.contains(i + 1)) {
451 is2.add(i);
452 }
453 }
454 is.clear();
455 is.addAll(is2);
456 Collections.sort(is);
457 Collections.reverse(is);
458 }
459
460}
Note: See TracBrowser for help on using the repository browser.