package cadastre_fr; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Color; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.swing.JOptionPane; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.gui.MapFrame; import org.openstreetmap.josm.actions.mapmode.MapMode; import org.openstreetmap.josm.command.AddCommand; import org.openstreetmap.josm.command.ChangeCommand; import org.openstreetmap.josm.command.ChangePropertyCommand; import org.openstreetmap.josm.command.Command; import org.openstreetmap.josm.command.MoveCommand; import org.openstreetmap.josm.command.SequenceCommand; import org.openstreetmap.josm.data.coor.EastNorth; import org.openstreetmap.josm.data.osm.BBox; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.osm.WaySegment; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Shortcut; import org.openstreetmap.josm.gui.layer.Layer; /** * Trace a way around buildings from cadastre images. * Inspired by Lakewalker plugin. * @author Pieren */ public class Buildings extends MapMode implements MouseListener, MouseMotionListener { private static final long serialVersionUID = 1L; GeorefImage selectedImage; WMSLayer selectedLayer; private EastNorth clickedEastNorth; private class Pixel { public Point p; public int dir; public Pixel(int x, int y, int dir) { this.p = new Point(x,y); this.dir = dir; } @Override public int hashCode() { return p.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof Pixel) return p.equals(new Point(((Pixel)obj).p.x, ((Pixel)obj).p.y)); return p.equals(obj); } } private ArrayList listPixels = new ArrayList(); private static final int cMaxnode = 10000; private int[] dirsX = new int[] {1,1,0,-1,-1,-1,0,1}; private int[] dirsY = new int[] {0,1,1,1,0,-1,-1,-1}; private int orange = Color.ORANGE.getRGB(); // new color of pixels ending nowhere (cul-de-sac) BuildingsImageModifier bim = new BuildingsImageModifier(); private double snapDistance = Main.pref.getDouble("cadastrewms.snap-distance", 60); // in centimeters private double snapDistanceSq = snapDistance*snapDistance; private double dx, dy; public Buildings(MapFrame mapFrame) { super(tr("Grab buildings"), "buildings", tr("Extract building on click (vector images only)"), Shortcut.registerShortcut("mapmode:buildings", tr("Mode: {0}", tr("Buildings")), KeyEvent.VK_E, Shortcut.GROUP_EDIT), mapFrame, ImageProvider.getCursor("normal", "move")); } @Override public void enterMode() { super.enterMode(); boolean atLeastOneBuildingLayer = false; for (Layer layer : Main.map.mapView.getAllLayers()) { if (layer instanceof WMSLayer && ((WMSLayer)layer).isBuildingsOnly()) { atLeastOneBuildingLayer = true; break; } } if (atLeastOneBuildingLayer && Main.main.getCurrentDataSet() != null) { Main.map.mapView.addMouseListener(this); Main.map.mapView.addMouseMotionListener(this); } else { JOptionPane.showMessageDialog(Main.parent,tr("This feature requires (at least) one special cadastre\nBuildings layer and an OSM data layer.")); exitMode(); Main.map.selectMapMode((MapMode)Main.map.getDefaultButtonAction()); } } @Override public void exitMode() { super.exitMode(); Main.map.mapView.removeMouseListener(this); Main.map.mapView.removeMouseMotionListener(this); } @Override public void mousePressed(MouseEvent e) { if (e.getButton() != MouseEvent.BUTTON1) return; selectedImage = null; // boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0; // boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0; boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0; for (Layer layer : Main.map.mapView.getAllLayers()) { if (layer.isVisible() && layer instanceof WMSLayer && ((WMSLayer)layer).isBuildingsOnly() ) { clickedEastNorth = Main.map.mapView.getEastNorth(e.getX(), e.getY()); selectedLayer = ((WMSLayer) layer); selectedImage = selectedLayer.findImage(clickedEastNorth); } } if (selectedImage != null) { int x = (int)((clickedEastNorth.east() - selectedImage.min.east())*selectedImage.getPixelPerEast()); int y = selectedImage.image.getHeight() - (int)((clickedEastNorth.north() - selectedImage.min.north())*selectedImage.getPixelPerNorth()); int rgb = selectedImage.image.getRGB(x, y); System.out.println("image found"+", x="+x+", y="+y+", RGB="+rgb); boolean clickOnRoof = bim.isRoofColor(rgb, shift); boolean clickOnBuilding = bim.isBuildingColor(rgb, shift); if (clickOnRoof || clickOnBuilding) { if (traceBuilding(x, y, clickOnBuilding, shift) && listPixels.size() > 3) { Way wayToCreate = new Way(); Way way2 = new Way(); double pPE = selectedImage.getPixelPerEast(); double pPN = selectedImage.getPixelPerNorth(); for (int i=0; i < listPixels.size(); i++) { EastNorth en = new EastNorth(selectedImage.min.east() + ((listPixels.get(i).p.x + 0.5)/ pPE), selectedImage.max.north() - ((listPixels.get(i).p.y + 0.5)/ pPN)); Node nodeToAdd = new Node(Main.proj.eastNorth2latlon(en)); wayToCreate.addNode(nodeToAdd); } wayToCreate.addNode(wayToCreate.getNode(0)); // close the way new SimplifyWay().simplifyWay(wayToCreate, 0.2); // move the node closing the loop and simplify again for (int i = 1; i < wayToCreate.getNodesCount(); i++) { way2.addNode(wayToCreate.getNode(i)); } way2.addNode(way2.getNode(0)); new SimplifyWay().simplifyWay(way2, 0.2); Way wayToAdd = new Way(); Collection cmds = new LinkedList(); for (int i = 0; i < way2.getNodesCount()-1; i++) { Node nearestNode = getNearestNode(way2.getNode(i)); if (nearestNode == null) { List wss = getNearestWaySegments(way2.getNode(i)); wayToAdd.addNode(way2.getNode(i)); cmds.add(new AddCommand(way2.getNode(i))); if (wss.size() > 0) cmds.add(new MoveCommand(way2.getNode(i), dx, dy)); joinNodeToExistingWays(wss, way2.getNode(i), cmds); } else {// reuse the existing node wayToAdd.addNode(nearestNode); cmds.add(new MoveCommand(nearestNode, dx, dy)); } } wayToAdd.addNode(wayToAdd.getNode(0)); cmds.add(new AddCommand(wayToAdd)); if (clickOnBuilding) addBuildingTags(cmds, wayToAdd); if (clickOnRoof) { addRoofTags(cmds, wayToAdd); } Main.main.undoRedo.add(new SequenceCommand(tr("Create building"), cmds)); getCurrentDataSet().setSelected(wayToAdd); Main.map.repaint(); } } } } @Override public void mouseDragged(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mouseMoved(MouseEvent e) { } @Override public void mouseClicked(MouseEvent e) { } private void addBuildingTags(Collection cmds, Way wayToAdd) { cmds.add(new ChangePropertyCommand(wayToAdd, "building", "yes")); } private void addRoofTags(Collection cmds, Way wayToAdd) { cmds.add(new ChangePropertyCommand(wayToAdd, "building", "yes")); cmds.add(new ChangePropertyCommand(wayToAdd, "wall", "no")); } private boolean traceBuilding (int x, int y, boolean buildingColors, boolean ignoreParcels) { // search start point at same x but smallest y (upper border on the screen) int startY = 0; int startX = x; while (y > 0) { y--; if (!bim.isBuildingOrRoofColor(selectedImage.image, x, y, buildingColors, ignoreParcels)) { System.out.println("at "+x+","+y+" color was "+selectedImage.image.getRGB(x,y)); y++; startY = y; break; } } if (startY == 0) { System.out.println("border not found"); return false; } else System.out.println("start at x="+startX+", y="+startY); listPixels.clear(); int test_x = 0; int test_y = 0; int new_dir = 0; addPixeltoList(x, y, new_dir); int last_dir = 1; for(int i = 0; i < cMaxnode; i++){ //System.out.println("node "+i); for(int d = 1; d <= this.dirsY.length; d++){ new_dir = (last_dir + d + 4) % 8; test_x = x + this.dirsX[new_dir]; test_y = y + this.dirsY[new_dir]; if(test_x < 0 || test_x >= selectedImage.image.getWidth() || test_y < 0 || test_y >= selectedImage.image.getHeight()){ System.out.println("Outside image"); return false; } if(bim.isBuildingOrRoofColor(selectedImage.image, test_x, test_y, buildingColors, ignoreParcels)){ System.out.println("building color at "+test_x+","+test_y+" new_dir="+new_dir); break; } if(d == this.dirsY.length-1){ System.out.println("Got stuck at "+x+","+y); // cul-de-sac : disable current pixel and move two steps back selectedImage.image.setRGB(x, y, orange); if (removeTwoLastPixelsFromList()) { x = listPixels.get(listPixels.size()-1).p.x; y = listPixels.get(listPixels.size()-1).p.y; last_dir = listPixels.get(listPixels.size()-1).dir; System.out.println("return at "+x+","+y+" and try again"); d = 1; continue; } else { System.out.println("cannot try another way"); return false; } } } // if (last_dir == new_dir) // // Same direction. First simplification by removing previous pixel. // listPixels.remove(listPixels.size()-1); last_dir = new_dir; // Set the pixel we found as current x = test_x; y = test_y; // Break the loop if we managed to get back to our starting point if (listPixels.contains(new Pixel(x, y, 0))){ System.out.println("loop closed at "+x+","+y+", exit"); break; } addPixeltoList(x, y, new_dir); } System.out.println("list size="+listPixels.size()); return true; } private void addPixeltoList(int x, int y, int dir) { listPixels.add( new Pixel(x, y, dir)); System.out.println("added pixel at "+x+","+y); } private boolean removeTwoLastPixelsFromList() { if (listPixels.size() > 2) { System.out.println("remove "+listPixels.get(listPixels.size()-1).p.x + ","+listPixels.get(listPixels.size()-1).p.y); listPixels.remove(listPixels.size()-1); System.out.println("remove "+listPixels.get(listPixels.size()-1).p.x + ","+listPixels.get(listPixels.size()-1).p.y); listPixels.remove(listPixels.size()-1); return true; } return false; } private BBox getSnapDistanceBBox(Node n) { return new BBox(Main.proj.eastNorth2latlon(new EastNorth(n.getEastNorth().east() - snapDistance, n.getEastNorth().north() - snapDistance)), Main.proj.eastNorth2latlon(new EastNorth(n.getEastNorth().east() + snapDistance, n.getEastNorth().north() + snapDistance))); } private Point getPointInCentimeters(Node n) { return new Point(new Double(n.getEastNorth().getX()*100).intValue(), new Double(n.getEastNorth().getY()*100).intValue()); } public Node getNearestNode(Node newNode) { Point newP = getPointInCentimeters(newNode); DataSet ds = getCurrentDataSet(); if (ds == null) return null; double minDistanceSq = snapDistanceSq; Node minNode = null; for (Node n : ds.searchNodes(getSnapDistanceBBox(newNode))) { if (!n.isUsable()) { continue; } Point sp = new Point(new Double(n.getEastNorth().getX()*100).intValue(), new Double(n.getEastNorth().getY()*100).intValue()); double dist = newP.distanceSq(sp); // in centimeter ! if (dist < minDistanceSq) { minDistanceSq = dist; minNode = n; } // when multiple nodes on one point, prefer new or selected nodes else if (dist == minDistanceSq && minNode != null && ((n.isNew() && ds.isSelected(n)) || (!ds.isSelected(minNode) && (ds.isSelected(n) || n.isNew())))) { minNode = n; } } if (minNode != null) { dx = (newNode.getEastNorth().getX() - minNode.getEastNorth().getX())/2; dy = (newNode.getEastNorth().getY() - minNode.getEastNorth().getY())/2; } return minNode; } private List getNearestWaySegments(Node newNode) { Point newP = new Point(new Double(newNode.getEastNorth().getX()*100).intValue(), new Double(newNode.getEastNorth().getY()*100).intValue()); TreeMap> nearest = new TreeMap>(); DataSet ds = getCurrentDataSet(); if (ds == null) return null; for (Way w : ds.searchWays(getSnapDistanceBBox(newNode))) { if (!w.isUsable()) { continue; } Node lastN = null; int i = -2; for (Node n : w.getNodes()) { i++; if (n.isDeleted() || n.isIncomplete()) { continue; } if (lastN == null) { lastN = n; continue; } Point A = getPointInCentimeters(lastN); Point B = getPointInCentimeters(n); double c = A.distanceSq(B); double a = newP.distanceSq(B); double b = newP.distanceSq(A); double perDist = a - (a - b + c) * (a - b + c) / 4 / c; // perpendicular distance squared if (perDist < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) { if (ds.isSelected(w)) { perDist -= 0.00001; } List l; if (nearest.containsKey(perDist)) { l = nearest.get(perDist); } else { l = new LinkedList(); nearest.put(perDist, l); } Point pivot = A; double startAngle = Math.atan2(B.x-pivot.x, B.y-pivot.y); double endAngle = Math.atan2(newP.x-pivot.x, newP.y-pivot.y); double angle = endAngle - startAngle; double ratio = A.distance(newP)/A.distance(B);//).intValue(); Point perP = new Point(A.x+new Double((B.x-A.x)*ratio).intValue(), A.y+new Double((B.y-A.y)*ratio).intValue()); dx = (perP.x-newP.x)/200.0; // back to meters this time and whole distance by two dy = (perP.y-newP.y)/200.0; System.out.println(angle+","+ ratio+","+perP ); l.add(new WaySegment(w, i)); } lastN = n; } } ArrayList nearestList = new ArrayList(); for (List wss : nearest.values()) { nearestList.addAll(wss); } return nearestList; } private void joinNodeToExistingWays(List wss, Node newNode, Collection cmds) { HashMap> insertPoints = new HashMap>(); for (WaySegment ws : wss) { List is; if (insertPoints.containsKey(ws.way)) { is = insertPoints.get(ws.way); } else { is = new ArrayList(); insertPoints.put(ws.way, is); } if (ws.way.getNode(ws.lowerIndex) != newNode && ws.way.getNode(ws.lowerIndex+1) != newNode) { is.add(ws.lowerIndex); } } for (Map.Entry> insertPoint : insertPoints.entrySet()) { List is = insertPoint.getValue(); if (is.size() == 0) continue; Way w = insertPoint.getKey(); List nodesToAdd = w.getNodes(); pruneSuccsAndReverse(is); for (int i : is) { nodesToAdd.add(i+1, newNode); } Way wnew = new Way(w); wnew.setNodes(nodesToAdd); cmds.add(new ChangeCommand(w, wnew)); } } private static void pruneSuccsAndReverse(List is) { HashSet is2 = new HashSet(); for (int i : is) { if (!is2.contains(i - 1) && !is2.contains(i + 1)) { is2.add(i); } } is.clear(); is.addAll(is2); Collections.sort(is); Collections.reverse(is); } }