001 //$HeadURL$ 002 /*---------------------------------------------------------------------------- 003 This file is part of deegree, http://deegree.org/ 004 Copyright (C) 2001-2009 by: 005 Department of Geography, University of Bonn 006 and 007 lat/lon GmbH 008 009 This library is free software; you can redistribute it and/or modify it under 010 the terms of the GNU Lesser General Public License as published by the Free 011 Software Foundation; either version 2.1 of the License, or (at your option) 012 any later version. 013 This library is distributed in the hope that it will be useful, but WITHOUT 014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 016 details. 017 You should have received a copy of the GNU Lesser General Public License 018 along with this library; if not, write to the Free Software Foundation, Inc., 019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 020 021 Contact information: 022 023 lat/lon GmbH 024 Aennchenstr. 19, 53177 Bonn 025 Germany 026 http://lat-lon.de/ 027 028 Department of Geography, University of Bonn 029 Prof. Dr. Klaus Greve 030 Postfach 1147, 53001 Bonn 031 Germany 032 http://www.geographie.uni-bonn.de/deegree/ 033 034 e-mail: info@deegree.org 035 ----------------------------------------------------------------------------*/ 036 037 package org.deegree.model.spatialschema; 038 039 import javax.vecmath.Point3d; 040 import javax.vecmath.Vector2d; 041 import javax.vecmath.Vector3d; 042 043 import org.deegree.framework.log.ILogger; 044 import org.deegree.framework.log.LoggerFactory; 045 046 /** 047 * Utility class for the linearization of arcs and circles. 048 * 049 * @author <a href="mailto:elmasry@lat-lon.de">Moataz Elmasry</a> 050 * @author last edited by: $Author: elmasri$ 051 * 052 * @version $Revision: $, $Date: 9 May 2008 13:09:29$ 053 */ 054 public class LinearizationUtil { 055 056 private static ILogger LOG = LoggerFactory.getLogger( LinearizationUtil.class ); 057 058 private static double PI2 = Math.PI * 2; 059 060 private static double EPSILON = 1E-11; 061 062 /** 063 * Takes in a three Position of an arc and computes a linearized Arc (linestring) using 150 positions/vertices 064 * 065 * @param p0 066 * @param p1 067 * @param p2 068 * @return An array of linearized Positions 069 */ 070 public static Position[] linearizeArc( Position p0, Position p1, Position p2 ) { 071 return linearizeArc( p0, p1, p2, 150 ); 072 } 073 074 /** 075 * Takes in a three Positions of an Arc and computes a linearized Arc (linestring). 076 * 077 * @param p0 078 * @param p1 079 * @param p2 080 * 081 * @param numberOfOpPositions 082 * the number of linearized Positions to produce 083 * @return An array of linearized Positions 084 */ 085 public static Position[] linearizeArc( Position p0, Position p1, Position p2, int numberOfOpPositions ) { 086 087 // shift the points down (to reduce the occurrence of floating point errors), independently on the x and y axes 088 double shiftx = findShiftX( p0, p1, p2 ); 089 double shifty = findShiftY( p0, p1, p2 ); 090 // if the points are already shifted, this does no harm! 091 Position p0Shifted = new PositionImpl( p0.getX() - shiftx, p0.getY() - shifty ); 092 Position p1Shifted = new PositionImpl( p1.getX() - shiftx, p1.getY() - shifty ); 093 Position p2Shifted = new PositionImpl( p2.getX() - shiftx, p2.getY() - shifty ); 094 095 if ( areCollinear( p0Shifted, p1Shifted, p2Shifted ) ) { 096 Position[] Positions = new Position[3]; 097 Positions[0] = p0; 098 Positions[1] = p1; 099 Positions[2] = p2; 100 return Positions; 101 } 102 103 int j = numberOfOpPositions - 1; 104 if ( j < 1 ) { 105 return null; 106 } 107 108 double[] positionsDouble = computeArc( p0Shifted.getX(), p0Shifted.getY(), p1Shifted.getX(), p1Shifted.getY(), 109 p2Shifted.getX(), p2Shifted.getY() ); 110 double d11 = computeArcOrientation( p0Shifted.getX(), p0Shifted.getY(), p1Shifted.getX(), p1Shifted.getY(), 111 p2Shifted.getX(), p2Shifted.getY() ); 112 double d12 = positionsDouble[3]; 113 double d13 = positionsDouble[5]; 114 if ( d11 > 0 && d13 < d12 ) { 115 d13 += PI2; 116 } else if ( d11 < 0 && d13 > d12 ) { 117 d13 -= PI2; 118 } 119 Position outPositions[] = new Position[numberOfOpPositions]; 120 for ( int k = 0; k <= j; k++ ) { 121 double d15 = d12 + ( ( d13 - d12 ) * k ) / j; 122 double xCoord = positionsDouble[0] + positionsDouble[2] * Math.cos( d15 ); 123 double yCoord = positionsDouble[1] + positionsDouble[2] * Math.sin( d15 ); 124 outPositions[k] = GeometryFactory.createPosition( xCoord, yCoord ); 125 } 126 127 // ensure numerical stability for start and end points 128 outPositions[0] = GeometryFactory.createPosition( p0Shifted.getX(), p0Shifted.getY() ); 129 outPositions[outPositions.length - 1] = GeometryFactory.createPosition( p2Shifted.getX(), p2Shifted.getY() ); 130 131 // shift the points back up 132 for ( int i = 0; i < outPositions.length; i++ ) { 133 outPositions[i] = new PositionImpl( outPositions[i].getX() + shiftx, outPositions[i].getY() + shifty ); 134 } 135 136 return outPositions; 137 } 138 139 private static double findShiftY( Position p0, Position p1, Position p2 ) { 140 double miny = p0.getY(); 141 if ( p1.getY() < miny ) { 142 miny = p1.getY(); 143 } 144 if ( p2.getY() < miny ) { 145 miny = p2.getY(); 146 } 147 148 double maxy = p0.getY(); 149 if ( p1.getX() > maxy ) { 150 maxy = p1.getY(); 151 } 152 if ( p2.getY() > maxy ) { 153 maxy = p2.getY(); 154 } 155 return ( miny + maxy ) / 2; 156 } 157 158 private static double findShiftX( Position p0, Position p1, Position p2 ) { 159 double minx = p0.getX(); 160 if ( p1.getX() < minx ) { 161 minx = p1.getX(); 162 } 163 if ( p2.getX() < minx ) { 164 minx = p2.getX(); 165 } 166 167 double maxx = p0.getX(); 168 if ( p1.getX() > maxx ) { 169 maxx = p1.getX(); 170 } 171 if ( p2.getX() > maxx ) { 172 maxx = p2.getX(); 173 } 174 return ( minx + maxx ) / 2; 175 } 176 177 /** 178 * Calculates a sequence of {@link Position}s on the arc of a circle. 179 * <p> 180 * The circle is constructed from the input points: All three points belong to the arc of the circle. They must be 181 * distinct and non-colinear. To form a complete circle, the arc is extended past the third control point until the 182 * first control point is encountered. 183 * 184 * @param p0 185 * start point on the arc of the circle 186 * @param p1 187 * second point on the arc of the circle 188 * @param p2 189 * third point on the arc of the circle 190 * @param numberOfPositions 191 * number of interpolation points, the returned array contains numberOfPositions+1 entries, to ensure 192 * that the first point is identical to the last 193 * @return Position array with numberOfPositions+1 entries, first entry and last entry are identical to p0 194 * @throws IllegalArgumentException 195 * if no order can be determined, because the points are identical or co-linear 196 */ 197 public static Position[] linearizeCircle( Position p0, Position p1, Position p2, int numberOfPositions ) { 198 199 // shift the points down (to reduce the occurrence of floating point errors), independently on the x and y axes 200 double shiftx = findShiftX( p0, p1, p2 ); 201 double shifty = findShiftY( p0, p1, p2 ); 202 // if the points are already shifted, this does no harm! 203 Position p0Shifted = new PositionImpl( p0.getX() - shiftx, p0.getY() - shifty ); 204 Position p1Shifted = new PositionImpl( p1.getX() - shiftx, p1.getY() - shifty ); 205 Position p2Shifted = new PositionImpl( p2.getX() - shiftx, p2.getY() - shifty ); 206 207 Position[] positions = new Position[numberOfPositions + 1]; 208 209 Position center = findCircleCenter( p0Shifted, p1Shifted, p2Shifted ); 210 211 double centerX = center.getX(); 212 double centerY = center.getY(); 213 double dx = p0Shifted.getX() - centerX; 214 double dy = p0Shifted.getY() - centerY; 215 double radius = Math.sqrt( dx * dx + dy * dy ); 216 217 double angleStep = 0.0; 218 if ( isClockwise( p0Shifted, p1Shifted, p2Shifted ) ) { 219 angleStep = -Math.PI * 2 / numberOfPositions; 220 } else { 221 angleStep = Math.PI * 2 / numberOfPositions; 222 } 223 double angle = Math.atan2( dy, dx ); 224 225 for ( int i = 0; i < numberOfPositions; i++ ) { 226 double x = centerX + Math.cos( angle ) * radius; 227 double y = centerY + Math.sin( angle ) * radius; 228 positions[i] = GeometryFactory.createPosition( x, y ); 229 angle += angleStep; 230 } 231 positions[numberOfPositions] = positions[0]; 232 233 // shift the points back up 234 for ( int i = 0; i <= numberOfPositions; i++ ) { 235 positions[i] = new PositionImpl( positions[i].getX() + shiftx, positions[i].getY() + shifty ); 236 } 237 238 return positions; 239 } 240 241 /** 242 * Returns whether the order of the given three points is clockwise or counterclockwise. Uses the (signed) area of a 243 * planar triangle to get to know about the order of the points. 244 * 245 * @param p0 246 * first point 247 * @param p1 248 * second point 249 * @param p2 250 * third point 251 * @return true, if order is clockwise, otherwise false 252 * @throws IllegalArgumentException 253 * if no order can be determined, because the points are identical or co-linear 254 */ 255 static final boolean isClockwise( Position p0, Position p1, Position p2 ) 256 throws IllegalArgumentException { 257 258 if ( areCollinear( p0, p1, p2 ) ) { 259 throw new IllegalArgumentException( "Cannot evaluate isClockwise(). The three points are colinear." ); 260 } 261 double res = ( p2.getX() - p0.getX() ) * ( ( p2.getY() + p0.getY() ) / 2 ) + ( p1.getX() - p2.getX() ) 262 * ( ( p1.getY() + p2.getY() ) / 2 ) + ( p0.getX() - p1.getX() ) * ( ( p0.getY() + p1.getY() ) / 2 ); 263 return res < 0.0 ? true : false; 264 } 265 266 /** 267 * Tests if the given three points are collinear. 268 * <p> 269 * NOTE: Only this method should be used throughout the whole linearization process for testing collinearity to 270 * avoid inconsistent results (the necessary EPSILON would differ). 271 * 272 * @param p0 273 * @param p1 274 * @param p2 275 * @return true if the points are collinear, false otherwise 276 */ 277 public static boolean areCollinear( Position p0, Position p1, Position p2 ) { 278 double res = ( p2.getX() - p0.getX() ) * ( ( p2.getY() + p0.getY() ) / 2 ) + ( p1.getX() - p2.getX() ) 279 * ( ( p1.getY() + p2.getY() ) / 2 ) + ( p0.getX() - p1.getX() ) * ( ( p0.getY() + p1.getY() ) / 2 ); 280 return Math.abs( res ) < EPSILON; 281 } 282 283 /** 284 * Computes an Arc from of three points (wich must not be collinear). 285 * 286 * @return array of points representing the computed arc 287 */ 288 private static final double[] computeArc( double d1, double d2, double d3, double d4, double d5, double d6 ) { 289 290 double d13 = d1 - d3; 291 double d14 = d3 - d5; 292 double d15 = d4 - d6; 293 double d16 = d2 - d4; 294 double d17 = d1 + d3; 295 double d18 = d3 + d5; 296 double d19 = d2 + d4; 297 double d20 = d4 + d6; 298 double d21 = d13 * d15 - d14 * d16; 299 300 double d7 = ( ( d15 * d17 * d13 - d16 * d18 * d14 ) + d16 * d15 * ( d16 + d15 ) ) / d21; 301 double d8; 302 if ( Math.abs( d4 - d6 ) < 5E-008D ) { 303 d8 = ( d19 * d16 + ( d17 - d7 ) * d13 ) / d16; 304 } else { 305 d8 = ( d20 * d15 + ( d18 - d7 ) * d14 ) / d15; 306 } 307 d7 *= 0.5; 308 d8 *= 0.5; 309 double d9 = Math.sqrt( ( d7 - d1 ) * ( d7 - d1 ) + ( d8 - d2 ) * ( d8 - d2 ) ); 310 double d10 = Math.atan2( d2 - d8, d1 - d7 ); 311 if ( d10 < 0 ) { 312 d10 += PI2; 313 } 314 double d11 = Math.atan2( d4 - d8, d3 - d7 ); 315 if ( d11 < 0 ) { 316 d11 += PI2; 317 } 318 double d12 = Math.atan2( d6 - d8, d5 - d7 ); 319 if ( d12 < 0 ) { 320 d12 += PI2; 321 } 322 323 double ad[] = new double[6]; 324 ad[0] = d7; 325 ad[1] = d8; 326 ad[2] = d9; 327 ad[3] = d10; 328 ad[4] = d11; 329 ad[5] = d12; 330 return ad; 331 332 } 333 334 private static final double computeArcOrientation( double d, double d1, double d2, double d3, double d4, double d5 ) { 335 return ( d * d3 + d2 * d5 + d4 * d1 ) - ( d4 * d3 + d2 * d1 + d * d5 ); 336 } 337 338 /** 339 * Transforms an array of Position to an array of double 340 * 341 * @param positions 342 * @return array of double 343 */ 344 protected static double[] positionToDouble( Position[] positions ) { 345 double[] ad = new double[positions.length * 2]; 346 for ( int i = 0; i < positions.length; i++ ) { 347 ad[i] = positions[i].getX(); 348 ad[i + 1] = positions[i].getY(); 349 } 350 return ad; 351 } 352 353 /** 354 * Transforms an array of double to an array of Position 355 * 356 * @param doubles 357 * @return generated array of Position or null if doubles%2 != 0 358 */ 359 protected static Position[] doubleToPosition( double[] doubles ) { 360 // The number of doubles has to be even 361 if ( doubles.length % 2 != 0 ) { 362 return null; 363 } 364 Position[] positions = new Position[doubles.length / 2]; 365 for ( int i = 0; i < positions.length; i++ ) { 366 positions[i] = GeometryFactory.createPosition( doubles[i], doubles[i + 1] ); 367 } 368 return positions; 369 } 370 371 /** 372 * Finds the center of a cirlce/arc using three points that lie on the circle. Thanks to wikipedia: 373 * http://en.wikipedia.org/wiki/Circumradius#Coordinates_of_circumcenter. 374 * 375 * @param p0 376 * @param p1 377 * @param p2 378 * @return center of the circle 379 * @throws IllegalArgumentException 380 * if the points are co linear, e.g. on a line. 381 */ 382 static Position findCircleCenter( Position p0, Position p1, Position p2 ) 383 throws IllegalArgumentException { 384 385 // shift the points down (to reduce the occurrence of floating point errors), independently on the x and y axes 386 double shiftx = findShiftX( p0, p1, p2 ); 387 double shifty = findShiftY( p0, p1, p2 ); 388 // if the points are already shifted, this does no harm! 389 Position p0Shifted = new PositionImpl( p0.getX() - shiftx, p0.getY() - shifty ); 390 Position p1Shifted = new PositionImpl( p1.getX() - shiftx, p1.getY() - shifty ); 391 Position p2Shifted = new PositionImpl( p2.getX() - shiftx, p2.getY() - shifty ); 392 393 if ( areCollinear( p0Shifted, p1Shifted, p2Shifted ) ) { 394 throw new IllegalArgumentException( "The given points are co linear, no circum center can be calculated." ); 395 } 396 397 Vector3d a = new Vector3d( p0Shifted.getAsPoint3d() ); 398 Vector3d b = new Vector3d( p1Shifted.getAsPoint3d() ); 399 Vector3d c = new Vector3d( p2Shifted.getAsPoint3d() ); 400 401 if ( Double.isNaN( a.z ) ) { 402 a.z = 0; 403 } 404 if ( Double.isNaN( b.z ) ) { 405 b.z = 0; 406 } 407 if ( Double.isNaN( c.z ) ) { 408 c.z = 0; 409 } 410 411 Vector3d ab = new Vector3d( a ); 412 Vector3d ac = new Vector3d( a ); 413 Vector3d bc = new Vector3d( b ); 414 Vector3d ba = new Vector3d( b ); 415 Vector3d ca = new Vector3d( c ); 416 Vector3d cb = new Vector3d( c ); 417 418 ab.sub( b ); 419 ac.sub( c ); 420 bc.sub( c ); 421 ba.sub( a ); 422 ca.sub( a ); 423 cb.sub( b ); 424 425 Vector3d cros = new Vector3d(); 426 cros.cross( ab, bc ); 427 double crosSquare = 2 * cros.length() * cros.length(); 428 429 if ( LOG.isDebug() ) { 430 double radius = ( ab.length() * bc.length() * ca.length() ) / ( 2 * cros.length() ); 431 LOG.logDebug( "Radius: " + radius ); 432 } 433 434 a.scale( ( ( bc.length() * bc.length() ) * ab.dot( ac ) ) / crosSquare ); 435 b.scale( ( ( ac.length() * ac.length() ) * ba.dot( bc ) ) / crosSquare ); 436 c.scale( ( ( ab.length() * ab.length() ) * ca.dot( cb ) ) / crosSquare ); 437 438 Point3d circle = new Point3d( a ); 439 circle.add( b ); 440 circle.add( c ); 441 442 // shift the center circle back up 443 circle.x += shiftx; 444 circle.y += shifty; 445 446 return GeometryFactory.createPosition( circle ); 447 } 448 449 /** 450 * Finds the angle between three points, p0,p1 and a center, where the angle to be measured. In other words, its the 451 * angle between the segments p0-center and p1-center. 452 * 453 * @param p0 454 * @param p1 455 * @param centre 456 * @return calculated angle in degree 457 */ 458 private static double findAngle( Position p0, Position p1, Position centre ) { 459 Vector2d vec1 = calcVector( centre, p0 ); 460 Vector2d vec2 = calcVector( centre, p1 ); 461 double cosTheta = ( vec1.dot( vec2 ) ) / ( vec1.length() * vec2.length() ); 462 int round = 1000; 463 // Using the 1000th decimal to round the cosTheta. 464 cosTheta = Math.ceil( cosTheta * round ) / round; 465 // convert from radian to degree 466 return Math.acos( cosTheta ) / Math.PI * 180; 467 468 } 469 470 /** 471 * Finds the vector between two points. which is the difference between p1 and p0 and the direction is from p0 to 472 * p1. 473 * 474 * @param p0 475 * @param p1 476 * @return calculated vector 477 */ 478 protected static Vector2d calcVector( Position p0, Position p1 ) { 479 return new Vector2d( p1.getX() - p0.getX(), p1.getY() - p0.getY() ); 480 } 481 }