MATSIM
CoordUtils.java
Go to the documentation of this file.
1 /* *********************************************************************** *
2  * project: org.matsim.*
3  * CoordUtils.java
4  * *
5  * *********************************************************************** *
6  * *
7  * copyright : (C) 2009 by the members listed in the COPYING, *
8  * LICENSE and WARRANTY file. *
9  * email : info at matsim dot org *
10  * *
11  * *********************************************************************** *
12  * *
13  * This program is free software; you can redistribute it and/or modify *
14  * it under the terms of the GNU General Public License as published by *
15  * the Free Software Foundation; either version 2 of the License, or *
16  * (at your option) any later version. *
17  * See also COPYING, LICENSE and WARRANTY file *
18  * *
19  * *********************************************************************** */
20 
21 package org.matsim.core.utils.geometry;
22 
23 import org.apache.logging.log4j.LogManager;
24 import org.apache.logging.log4j.Logger;
25 import org.locationtech.jts.geom.Coordinate;
26 import org.matsim.api.core.v01.Coord;
27 import org.matsim.core.gbl.Gbl;
28 
29 import java.math.BigDecimal;
30 import java.math.RoundingMode;
31 import java.util.concurrent.atomic.AtomicLong;
32 
33 public abstract class CoordUtils {
34  final private static Logger LOG = LogManager.getLogger(CoordUtils.class);
36 
37  public static Coordinate createGeotoolsCoordinate( final Coord coord ) {
38  return new Coordinate( coord.getX(), coord.getY() ) ;
39  }
40 
41  public static Coord createCoord( final Coordinate coordinate ) {
42  return new Coord( coordinate.x, coordinate.y ) ;
43  }
44 
45  public static Coord createCoord( final double xx, final double yy ) {
46  return new Coord(xx, yy);
47  }
48 
49  public static Coord createCoord( final double xx, final double yy, final double zz){
50  return new Coord(xx, yy, zz);
51  }
52 
53  public static Coord plus ( Coord coord1, Coord coord2 ) {
54  if( !coord1.hasZ() && !coord2.hasZ() ){
55  /* Both are 2D coordinates. */
56  double xx = coord1.getX() + coord2.getX();
57  double yy = coord1.getY() + coord2.getY();
58  return new Coord(xx, yy);
59  } else if( coord1.hasZ() && coord2.hasZ() ){
60  /* Both are 3D coordinates. */
61  double xx = coord1.getX() + coord2.getX();
62  double yy = coord1.getY() + coord2.getY();
63  double zz = coord1.getZ() + coord2.getZ();
64  return new Coord(xx, yy, zz);
65  } else{
66  throw new RuntimeException("Cannot 'plus' coordinates if one has elevation (z) and the other not; coord1.hasZ=" + coord1.hasZ()
67  + "; coord2.hasZ=" + coord2.hasZ() );
68  }
69  }
70 
71  public static Coord minus ( Coord coord1, Coord coord2 ) {
72  if( !coord1.hasZ() && !coord2.hasZ() ){
73  /* Both are 2D coordinates. */
74  double xx = coord1.getX() - coord2.getX();
75  double yy = coord1.getY() - coord2.getY();
76  return new Coord(xx, yy);
77  } else if( coord1.hasZ() && coord2.hasZ() ){
78  /* Both are 3D coordinates. */
79  double xx = coord1.getX() - coord2.getX();
80  double yy = coord1.getY() - coord2.getY();
81  double zz = coord1.getZ() - coord2.getZ();
82  return new Coord(xx, yy, zz);
83  } else{
84  throw new RuntimeException("Cannot 'minus' coordinates if one has elevation (z) and the other not.");
85  }
86  }
87 
88  public static Coord scalarMult( double alpha, Coord coord ) {
89  if(!coord.hasZ()){
90  /* 2D coordinate. */
91  double xx = alpha * coord.getX();
92  double yy = alpha * coord.getY();
93  return new Coord(xx, yy);
94  } else {
95  /* 3D coordinate. */
96  double xx = alpha * coord.getX();
97  double yy = alpha * coord.getY();
98  double zz = alpha * coord.getZ();
99  return new Coord(xx, yy, zz);
100  }
101  }
102 
103 
104  public static Coord getCenter( Coord coord1, Coord coord2 ) {
105  if( !coord1.hasZ() && !coord2.hasZ() ){
106  /* Both are 2D coordinates. */
107  double xx = 0.5*( coord1.getX() + coord2.getX() ) ;
108  double yy = 0.5*( coord1.getY() + coord2.getY() ) ;
109  return new Coord(xx, yy);
110  } else if( coord1.hasZ() && coord2.hasZ() ){
111  /* Both are 3D coordinates. */
112  double xx = 0.5*( coord1.getX() + coord2.getX() ) ;
113  double yy = 0.5*( coord1.getY() + coord2.getY() ) ;
114  double zz = 0.5*( coord1.getZ() + coord2.getZ() ) ;
115  return new Coord(xx, yy, zz);
116  } else{
117  throw new RuntimeException("Cannot get the center for coordinates if one has elevation (z) and the other not.");
118  }
119  }
120 
121  public static double length( Coord coord ) {
122  if(!coord.hasZ()){
123  return Math.sqrt(
124  coord.getX()*coord.getX() +
125  coord.getY()*coord.getY() ) ;
126  } else{
127  return Math.sqrt(
128  coord.getX()*coord.getX() +
129  coord.getY()*coord.getY() +
130  coord.getZ()*coord.getZ()) ;
131  }
132  }
133 
140  public static Coord rotateToRight( Coord coord ) {
141  if( !coord.hasZ() ){
142  /* 2D coordinate */
143  final double y = -coord.getX();
144  return new Coord(coord.getY(), y);
145  } else{
146  /* 3D coordinate */
147  final double y = -coord.getX();
148  return new Coord(coord.getY(), y, coord.getZ());
149  }
150  }
151 
152 
153  public static Coord getCenterWOffset( Coord coord1, Coord coord2 ) {
154  if( !coord1.hasZ() && !coord2.hasZ() ){
155  /* Both are 2D coordinates. */
156  Coord fromTo = minus( coord2, coord1 ) ;
157  Coord offset = scalarMult( 0.1 , rotateToRight( fromTo ) ) ;
158  Coord centerWOffset = plus( getCenter( coord1, coord2 ) , offset ) ;
159  return centerWOffset ;
160  } else if( coord1.hasZ() && coord2.hasZ() ){
161  /* TODO Both are 3D coordinates. */
162  throw new RuntimeException("3D version not implemented.");
163  } else{
164  throw new RuntimeException("Cannot get the center for coordinates if one has elevation (z) and the other not.");
165  }
166  }
167 
173  public static Coord round(Coord coord) {
174  int scale = Math.abs(coord.getX()) > 180 || Math.abs(coord.getY()) > 180 ? 2 : 6;
175  return round(coord, scale);
176  }
177 
181  public static Coord round(Coord coord, int scale) {
182  if (coord.hasZ()) {
183  return new Coord(roundNumber(coord.getX(), scale), roundNumber(coord.getY(), scale), coord.getZ());
184  }
185 
186  return new Coord(roundNumber(coord.getX(), scale), roundNumber(coord.getY(), scale));
187  }
188 
189  private static double roundNumber(double x, int scale) {
190  return BigDecimal.valueOf(x).setScale(scale, RoundingMode.HALF_EVEN).doubleValue();
191  }
192 
193  public static double calcEuclideanDistance(Coord coord, Coord other) {
194  return eucledianDistanceCalculator.calculateDistance(coord, other);
195  }
196 
206  public static double calcProjectedEuclideanDistance(Coord coord, Coord other) {
207  double xDiff = other.getX()-coord.getX();
208  double yDiff = other.getY()-coord.getY();
209  return Math.sqrt((xDiff*xDiff) + (yDiff*yDiff));
210  }
211 
212 
220  private static double dotProduct( Coord coord1, Coord coord2 ) {
221  if( !coord1.hasZ() && !coord2.hasZ() ){
222  /* Both are 2D coordinates. */
223  return coord1.getX()*coord2.getX() +
224  coord1.getY()*coord2.getY();
225  } else if( coord1.hasZ() && coord2.hasZ() ){
226  /* Both are 3D coordinates. */
227  return coord1.getX()*coord2.getX() +
228  coord1.getY()*coord2.getY() +
229  coord1.getZ()*coord2.getZ();
230  } else{
231  throw new RuntimeException("Cannot get the dot-product of coordinates if one has elevation (z) and the other not.");
232  }
233  }
234 
235 
254  private static boolean onlyOnceWarnGiven = false;
255 
256  public static double distancePointLinesegment(final Coord lineFrom, final Coord lineTo, final Coord point) {
257  if( !lineFrom.hasZ() && !lineTo.hasZ() && !point.hasZ() ){
258  /* All coordinates are 2D and in the XY plane. */
259 
260  /* The shortest distance is where the tangent of the line goes
261  * through "point". The dot product (point - P) dot (lineTo - lineFrom)
262  * must be 0, when P is a point on the line. P can be substituted
263  * with lineFrom + u*(lineTo - lineFrom). Thus it must be:
264  * (point - lineFrom - u*(lineTo - lineFrom)) dot (lineTo - lineFrom) == 0
265  * From this follows:
266  * (point.x - lineFrom.x)(lineTo.x - lineFrom.x) + (point.y - lineFrom.y)(lineTo.y - lineFrom.y)
267  * u = ---------------------------------------------------------------------------------------------
268  * (lineTo.x - lineFrom.x)(lineTo.x - lineFrom.x) + (lineTo.y - lineFrom.y)(lineTo.y - lineFrom.y)
269  *
270  * Substituting this gives:
271  * x = lineFrom.x + u*(lineFrom.x - lineTo.x) , y = lineFrom.y + u*(lineFrom.y - lineTo.y)
272  *
273  * The shortest distance is now the distance between "point" and
274  * (x | y)
275  */
276  double lineDX = lineTo.getX() - lineFrom.getX();
277  double lineDY = lineTo.getY() - lineFrom.getY();
278 
279  if ((lineDX == 0.0) && (lineDY == 0.0)) {
280  // the line segment is a point without dimension
281  return calcEuclideanDistance(lineFrom, point);
282  }
283 
284  double u = ((point.getX() - lineFrom.getX())*lineDX + (point.getY() - lineFrom.getY())*lineDY) /
285  (lineDX*lineDX + lineDY*lineDY);
286 
287  if (u <= 0) {
288  // (x | y) is not on the line segment, but before lineFrom
289  return calcEuclideanDistance(lineFrom, point);
290  }
291  if (u >= 1) {
292  // (x | y) is not on the line segment, but after lineTo
293  return calcEuclideanDistance(lineTo, point);
294  }
295 
296  return calcEuclideanDistance(new Coord(lineFrom.getX() + u * lineDX, lineFrom.getY() + u * lineDY), point);
297  } else if( lineFrom.hasZ() && lineTo.hasZ() && point.hasZ() ){
298  /* All coordinates are 3D. */
299  double lineDX = lineTo.getX() - lineFrom.getX();
300  double lineDY = lineTo.getY() - lineFrom.getY();
301  double lineDZ = lineTo.getZ() - lineFrom.getZ();
302 
303  if((lineDX == 0.0) && (lineDY == 0.0) && (lineDZ == 0.0)){
304  return calcEuclideanDistance(lineFrom, point);
305  }
306 
307  Coord v = minus(lineTo, lineFrom);
308  Coord w = minus(point, lineFrom);
309 
310  double c1 = dotProduct(w, v);
311  if(c1 <= 0.0){
312  Coord m = minus(point, lineFrom);
313  return Math.sqrt(dotProduct(m, m));
314  }
315 
316  double c2 = dotProduct(v, v);
317  if(c2 <= c1){
318  Coord m = minus(point, lineTo);
319  return Math.sqrt(dotProduct(m, m));
320  }
321 
322  double b = c1 / c2;
323  Coord p = plus(lineFrom, scalarMult(b, v));
324  Coord m = minus(point, p);
325  return Math.sqrt(dotProduct(m, m));
326  } else{
327  if (!onlyOnceWarnGiven) {
328  LogManager.getLogger(CoordUtils.class).warn("Mix of 2D / 3D coordinates. Assuming 2D only.\n" + Gbl.ONLYONCE);
329  onlyOnceWarnGiven = true;
330  }
331  return distancePointLinesegment(new Coord(lineFrom.getX(), lineFrom.getY()), new Coord(lineTo.getX(), lineTo.getY()), new Coord(point.getX(), point.getY()));
332  }
333  }
334 
335 
357  public static Coord orthogonalProjectionOnLineSegment(final Coord lineFrom, final Coord lineTo, final Coord point) {
358  if( !lineFrom.hasZ() && !lineTo.hasZ() && !point.hasZ() ){
359  /* All coordinates are 2D. */
360 
361  /* Concerning explanation and usage of the dot product for these calculation, please
362  * read comments of "distancePointLinesegment".
363  */
364  double lineDX = lineTo.getX() - lineFrom.getX();
365  double lineDY = lineTo.getY() - lineFrom.getY();
366 
367  if ((lineDX == 0.0) && (lineDY == 0.0)) {
368  // the line segment is a point without dimension
369  return lineFrom;
370  }
371 
372  double u = ((point.getX() - lineFrom.getX())*lineDX + (point.getY() - lineFrom.getY())*lineDY) /
373  (lineDX*lineDX + lineDY*lineDY);
374 
375  if (u <= 0) {
376  // (x | y) is not on the line segment, but before lineFrom
377  return lineFrom;
378  }
379  if (u >= 1) {
380  // (x | y) is not on the line segment, but after lineTo
381  return lineTo;
382  }
383  return new Coord(lineFrom.getX() + u * lineDX, lineFrom.getY() + u * lineDY);
384  } else if(lineFrom.hasZ() && lineTo.hasZ() && point.hasZ() ){
385  /* All coordinates are 3D. */
386  Coord direction = minus(lineTo, lineFrom);
387 
388  double t0 = dotProduct(direction, minus(point, lineFrom)) / dotProduct(direction, direction);
389  Coord q = plus(lineFrom, scalarMult(t0, direction));
390  return q;
391  } else{
392  if (!onlyOnceWarnGiven) {
393  LogManager.getLogger(CoordUtils.class).warn("Mix of 2D / 3D coordinates. Assuming 2D only.\n" + Gbl.ONLYONCE);
394  onlyOnceWarnGiven = true;
395  }
396  return orthogonalProjectionOnLineSegment(new Coord(lineFrom.getX(), lineFrom.getY()), new Coord(lineTo.getX(), lineTo.getY()), new Coord(point.getX(), point.getY()));
397  //throw new RuntimeException("All given coordinates must either be 2D, or 3D. A mix is not allowed.");
398  }
399  }
400 
401  private static class EucledianDistanceCalculator {
402 
403 
404  private static final int maxWarnCount = 10;
405  private final AtomicLong warnCounter = new AtomicLong(0);
406 
407  private double calculateDistance(Coord coord, Coord other) {
408  /* Depending on the coordinate system that is used, determining the
409  * distance based on the euclidean distance will lead to wrong results.
410  * However, if the distance is not to large (<1km) this will be a usable
411  * distance estimation. Another comfortable way to calculate correct
412  * distances would be, to use the distance functions provided by
413  * geotools lib. May be we need to discuss what part of GIS functionality
414  * we should implement by our own and for what part we could use an
415  * existing GIS like geotools. We need to discuss this in terms of code
416  * robustness, performance and so on ... [gl] */
417  if( !coord.hasZ() && !other.hasZ() ){
418  /* Both are 2D coordinates. */
419  double xDiff = other.getX()-coord.getX();
420  double yDiff = other.getY()-coord.getY();
421  return Math.sqrt((xDiff*xDiff) + (yDiff*yDiff));
422  } else if( coord.hasZ() && other.hasZ() ){
423  /* Both are 3D coordinates. */
424  double xDiff = other.getX()-coord.getX();
425  double yDiff = other.getY()-coord.getY();
426  double zDiff = other.getZ()-coord.getZ();
427  return Math.sqrt((xDiff*xDiff) + (yDiff*yDiff) + (zDiff*zDiff));
428  } else{
429  // there used to be a warning here, but it would clutter our log file
430  // hence we silently calculate the distance on a 2D pane now. janek mai'21
431  if (warnCounter.incrementAndGet() <= maxWarnCount) {
432  LOG.warn("Mixed use of elevation in coordinates: " + coord +
433  "; " + other);
434  LOG.warn("Returning projected coordinate distance (using x and y components only)");
435 
436  if (warnCounter.get() == maxWarnCount) {
437  LOG.warn("Future occurences of this logging statement are suppressed.");
438  }
439  }
440 
441  return calcProjectedEuclideanDistance(coord, other);
442  }
443  }
444 
445  }
446 }
static final String ONLYONCE
Definition: Gbl.java:42
static double length(Coord coord)
static double roundNumber(double x, int scale)
static double calcEuclideanDistance(Coord coord, Coord other)
static double dotProduct(Coord coord1, Coord coord2)
static Coord orthogonalProjectionOnLineSegment(final Coord lineFrom, final Coord lineTo, final Coord point)
static Coord rotateToRight(Coord coord)
static Coord minus(Coord coord1, Coord coord2)
Definition: CoordUtils.java:71
static double distancePointLinesegment(final Coord lineFrom, final Coord lineTo, final Coord point)
static Coordinate createGeotoolsCoordinate(final Coord coord)
Definition: CoordUtils.java:37
static Coord getCenter(Coord coord1, Coord coord2)
static Coord createCoord(final Coordinate coordinate)
Definition: CoordUtils.java:41
final static EucledianDistanceCalculator eucledianDistanceCalculator
Definition: CoordUtils.java:35
static Coord plus(Coord coord1, Coord coord2)
Definition: CoordUtils.java:53
static Coord round(Coord coord, int scale)
static Coord getCenterWOffset(Coord coord1, Coord coord2)
static Coord scalarMult(double alpha, Coord coord)
Definition: CoordUtils.java:88
static double calcProjectedEuclideanDistance(Coord coord, Coord other)
static Coord createCoord(final double xx, final double yy, final double zz)
Definition: CoordUtils.java:49
static Coord createCoord(final double xx, final double yy)
Definition: CoordUtils.java:45