- Timestamp:
- 2008-11-20T19:33:47+01:00 (16 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/OrthogonalizeAction.java
r1085 r1089 47 47 } 48 48 49 50 51 52 49 public void actionPerformed(ActionEvent e) { 50 51 Collection<OsmPrimitive> sel = Main.ds.getSelected(); 52 53 53 ArrayList<Node> dirnodes = new ArrayList<Node>(); 54 54 55 55 // Check the selection if it is suitible for the orthogonalization 56 56 for (OsmPrimitive osm : sel) { 57 57 // Check if not more than two nodes in the selection 58 58 if(osm instanceof Node) { … … 65 65 } 66 66 // Check if selection consists now only of ways 67 67 if (!(osm instanceof Way)) { 68 68 JOptionPane.showMessageDialog(Main.parent, tr("Selection must consist only of ways.")); 69 69 return; 70 70 } 71 71 72 72 // Check if every way is made of at least four segments and closed 73 73 Way way = (Way)osm; … … 76 76 return; 77 77 } 78 78 79 79 // Check if every edge in the way is a definite edge of at least 45 degrees of direction change 80 80 // Otherwise, two segments could be turned into same direction and intersection would fail. 81 81 // Or changes of shape would be too serious. 82 for (int i1=0; i1 < way.nodes.size()-1; i1++) { 83 int i2 = (i1+1) % (way.nodes.size()-1);84 int i3 = (i1+2) % (way.nodes.size()-1);85 double angle1 =Math.abs(way.nodes.get(i1).eastNorth.heading(way.nodes.get(i2).eastNorth));86 double angle2 = Math.abs(way.nodes.get(i2).eastNorth.heading(way.nodes.get(i3).eastNorth));87 double delta = Math.abs(angle2 - angle1);88 while(delta > Math.PI) delta -= Math.PI;89 if(delta < Math.PI/4) {90 JOptionPane.showMessageDialog(Main.parent, tr("Please select ways with almost right angles to orthogonalize."));91 return;92 }82 for (int i1=0; i1 < way.nodes.size()-1; i1++) { 83 int i2 = (i1+1) % (way.nodes.size()-1); 84 int i3 = (i1+2) % (way.nodes.size()-1); 85 double angle1 =Math.abs(way.nodes.get(i1).eastNorth.heading(way.nodes.get(i2).eastNorth)); 86 double angle2 = Math.abs(way.nodes.get(i2).eastNorth.heading(way.nodes.get(i3).eastNorth)); 87 double delta = Math.abs(angle2 - angle1); 88 while(delta > Math.PI) delta -= Math.PI; 89 if(delta < Math.PI/4) { 90 JOptionPane.showMessageDialog(Main.parent, tr("Please select ways with almost right angles to orthogonalize.")); 91 return; 92 } 93 93 } 94 94 } … … 96 96 if ("EPSG:4326".equals(Main.proj.toString())) { 97 97 String msg = tr("<html>You are using the EPSG:4326 projection which might lead<br>" + 98 "to undesirable results when doing rectangular alignments.<br>" +99 "Change your projection to get rid of this warning.<br>" +100 "Do you want to continue?");98 "to undesirable results when doing rectangular alignments.<br>" + 99 "Change your projection to get rid of this warning.<br>" + 100 "Do you want to continue?"); 101 101 102 102 if (!DontShowAgainInfo.show("align_rectangular_4326", msg, false)) { … … 108 108 JOptionPane.showMessageDialog(Main.parent, tr("Only one node selected")); 109 109 return; 110 } 111 110 } 111 112 112 // Now all checks are done and we can now do the neccessary computations 113 113 // From here it is assumed that the above checks hold 114 114 Collection<Command> cmds = new LinkedList<Command>(); 115 double align_to_heading; 116 117 118 if(dirnodes.size() == 2) { // When selection contained two nodes, use the nodes to compute a direction to align to 119 double heading; 120 heading = dirnodes.get(0).eastNorth.heading(dirnodes.get(1).eastNorth); 121 while(heading > Math.PI/4) heading -= Math.PI/2; 122 align_to_heading=heading; 123 } else { // Otherwise compute the alignment direction from the ways in the collection 124 // First, compute the weighted average of the headings of all segments 125 double sum_weighted_headings = 0.0; 126 double sum_weights = 0.0; 115 double align_to_heading = 0.0; 116 boolean use_dirnodes = false; 117 118 if(dirnodes.size() == 2) { 119 // When selection contains two nodes, use the nodes to compute a direction 120 // to align all ways to 121 align_to_heading = normalize_angle(dirnodes.get(0).eastNorth.heading(dirnodes.get(1).eastNorth)); 122 use_dirnodes = true; 123 } 124 127 125 for (OsmPrimitive osm : sel) { 128 if(!(osm instanceof Way)) 129 continue; 126 if(!(osm instanceof Way)) 127 continue; 128 130 129 Way way = (Way)osm; 131 130 int nodes = way.nodes.size(); 132 int sides = nodes - 1;133 // To find orientation of all segments, compute weighted average of all segment's headings134 // all headings are mapped into [0, 3*4*PI) by PI/2 rotations so both main orientations are mapped into one135 // the headings are weighted by the length of the segment establishing it, so a longer segment, that is more136 // likely to have the correct orientation, has more influence in the computing than a short segment, that is easier to misalign.137 for (int i=0; i < sides; i++) {138 double heading;139 double weight;140 heading = way.nodes.get(i).eastNorth.heading(way.nodes.get(i+1).eastNorth);141 //Put into [0, PI/4) to find main direction142 while(heading > Math.PI/4) heading -= Math.PI/2;143 weight = way.nodes.get(i).eastNorth.distance(way.nodes.get(i+1).eastNorth);144 sum_weighted_headings += heading*weight;145 sum_weights += weight;146 }147 }148 align_to_heading = sum_weighted_headings/sum_weights;149 }150 151 for (OsmPrimitive osm : sel) {152 if(!(osm instanceof Way))153 continue;154 Way myWay = (Way)osm;155 int nodes = myWay.nodes.size();156 131 int sides = nodes - 1; 157 158 132 // Copy necessary data into a more suitable data structure 159 133 EastNorth en[] = new EastNorth[sides]; 160 for (int i=0; i < sides; i++) { 161 en[i] = new EastNorth(myWay.nodes.get(i).eastNorth.east(), myWay.nodes.get(i).eastNorth.north()); 162 } 163 134 for (int i=0; i < sides; i++) { 135 en[i] = new EastNorth(way.nodes.get(i).eastNorth.east(), way.nodes.get(i).eastNorth.north()); 136 } 137 138 if (! use_dirnodes) { 139 // To find orientation of all segments, compute weighted average of all segment's headings 140 // all headings are mapped into [-PI/4, PI/4] by PI/2 rotations so both main orientations are mapped into one 141 // the headings are weighted by the length of the segment establishing it, so a longer segment, that is more 142 // likely to have the correct orientation, has more influence in the computing than a short segment, that is easier to misalign. 143 double headings[] = new double[sides]; 144 double weights[] = new double[sides]; 145 for (int i=0; i < sides; i++) { 146 headings[i] = normalize_angle(way.nodes.get(i).eastNorth.heading(way.nodes.get(i+1).eastNorth)); 147 weights[i] = way.nodes.get(i).eastNorth.distance(way.nodes.get(i+1).eastNorth); 148 } 149 150 // CAVEAT: for orientations near -PI/4 or PI/4 the mapping into ONE orientation fails 151 // resulting in a heading-difference between adjacent sides of almost PI/2 152 // and a totally wrong average 153 // check for this (use PI/3 as arbitray limit) and rotate into ONE orientation 154 double angle_diff_max = 0.0; 155 for (int i=0; i < sides; i++) { 156 double diff = 0.0; 157 if (i == 0) { 158 diff = heading_diff(headings[i], headings[sides - 1]); 159 } else { 160 diff = heading_diff(headings[i], headings[i - 1]); 161 } 162 if (diff > angle_diff_max) angle_diff_max = diff; 163 } 164 165 if (angle_diff_max > Math.PI/3) { 166 // rearrange headings: everything < 0 gets PI/2-rotated 167 for (int i=0; i < sides; i++) { 168 if (headings[i] < 0) 169 headings[i] += Math.PI/2; 170 } 171 } 172 173 // TODO: 174 // use angle_diff_max as an indicator that the way is already orthogonal 175 // e.g. if angle_diff_max is less then Math.toRadians(0.5) 176 // and do nothing in that case (?) 177 178 // Compute the weighted average of the headings of all segments 179 double sum_weighted_headings = 0.0; 180 double sum_weights = 0.0; 181 for (int i=0; i < sides; i++) { 182 sum_weighted_headings += headings[i] * weights[i]; 183 sum_weights += weights[i]; 184 } 185 align_to_heading = normalize_angle(sum_weighted_headings/sum_weights); 186 } 187 188 164 189 for (int i=0; i < sides; i++) { 165 190 // Compute handy indices of three nodes to be used in one loop iteration. … … 173 198 double delta1, delta2; 174 199 // Compute neccessary rotation of first segment to align it with main orientation 175 heading1 = en[i1].heading(en[i2]); 176 //Put into [-PI/4, PI/4) because we want a minimum of rotation so we don't swap node positions 177 while(heading1 - align_to_heading > Math.PI/4) heading1 -= Math.PI/2; 178 while(heading1 - align_to_heading < -Math.PI/4) heading1 += Math.PI/2; 200 heading1 = normalize_angle(en[i1].heading(en[i2]), align_to_heading); 179 201 delta1 = align_to_heading - heading1; 180 202 // Compute neccessary rotation of second segment to align it with main orientation 181 heading2 = en[i2].heading(en[i3]); 182 //Put into [-PI/4, PI/4) because we want a minimum of rotation so we don't swap node positions 183 while(heading2 - align_to_heading > Math.PI/4) heading2 -= Math.PI/2; 184 while(heading2 - align_to_heading < -Math.PI/4) heading2 += Math.PI/2; 203 heading2 = normalize_angle(en[i2].heading(en[i3]), align_to_heading); 185 204 delta2 = align_to_heading - heading2; 186 205 // To align a segment, rotate around its center … … 194 213 // compute intersection of segments 195 214 double u=det(B.east() - A.east(), B.north() - A.north(), 196 C.east() - D.east(), C.north() - D.north());197 215 C.east() - D.east(), C.north() - D.north()); 216 198 217 // Check for parallel segments and do nothing if they are 199 218 // In practice this will probably only happen when a way has 200 219 // been duplicated 201 220 202 221 if (u == 0) continue; 203 222 204 223 // q is a number between 0 and 1 205 224 // It is the point in the segment where the intersection occurs 206 225 // if the segment is scaled to length 1 207 226 208 227 double q = det(B.north() - C.north(), B.east() - C.east(), 209 D.north() - C.north(), D.east() - C.east()) / u;228 D.north() - C.north(), D.east() - C.east()) / u; 210 229 EastNorth intersection = new EastNorth( 211 230 B.east() + q * (A.east() - B.east()), 212 231 B.north() + q * (A.north() - B.north())); 213 214 Node n = myWay.nodes.get(i2);232 233 Node n = way.nodes.get(i2); 215 234 216 235 LatLon ill = Main.proj.eastNorth2latlon(intersection); … … 218 237 double dx = intersection.east()-n.eastNorth.east(); 219 238 double dy = intersection.north()-n.eastNorth.north(); 220 cmds.add(new MoveCommand(n, dx, dy)); 221 } 222 } 223 } 224 239 cmds.add(new MoveCommand(n, dx, dy)); 240 } 241 } 242 } 243 225 244 if (cmds.size() > 0) { 226 245 Main.main.undoRedo.add(new SequenceCommand(tr("Orthogonalize"), cmds)); 227 246 Main.map.repaint(); 228 247 } 229 230 248 } 249 231 250 static double det(double a, double b, double c, double d) 232 251 { … … 234 253 } 235 254 255 static double normalize_angle(double h) { 256 return normalize_angle(h, 0.0); 257 } 258 static double normalize_angle(double h, double align_to) { 259 double llimit = -Math.PI/4; 260 double ulimit = Math.PI/4; 261 while (h - align_to > ulimit) h -= Math.PI/2; 262 while (h - align_to < llimit) h += Math.PI/2; 263 264 return h; 265 } 266 267 static double heading_diff(double h1, double h2) { 268 double heading_delta = h1 > h2 ? h1 - h2 : h2 - h1; 269 return heading_delta; 270 } 236 271 }
Note:
See TracChangeset
for help on using the changeset viewer.