Skip to content

Commit e7a8a02

Browse files
authored
Merge pull request #321 from ie3-institute/ms/#320-adding-new-feature-to-GeoUtils
Adding new feature to geo utils
2 parents 0e4e5d4 + 25992c1 commit e7a8a02

File tree

3 files changed

+85
-18
lines changed

3 files changed

+85
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- `OsmEntity` and `OsmContainer` to provide a simple, lightweight representation of openstreetmap data
1212
- QuantityUtils previously implemented in SIMONA [#288](https://github.com/ie3-institute/PowerSystemUtils/issues/288)
1313
- Enhanced `RichQuantityDouble` with new units and generic method [#312](https://github.com/ie3-institute/PowerSystemUtils/issues/312), [#322](https://github.com/ie3-institute/PowerSystemUtils/issues/322)
14+
- Calculation of bounding box to `GeoUtils` [#320](https://github.com/ie3-institute/PowerSystemUtils/issues/320)
1415

1516
### Changed
1617
- Refactored `GeoUtils`, moved them to the scala package and tailored them toward the `loactiontec.jts` Geometries used in the `OsmContainer` [#163](https://github.com/ie3-institute/PowerSystemUtils/issues/163)

src/main/java/edu/ie3/util/geo/GeoUtils.java

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
*/
66
package edu.ie3.util.geo;
77

8-
import static edu.ie3.util.quantities.PowerSystemUnits.*;
8+
import static edu.ie3.util.quantities.PowerSystemUnits.METRE;
9+
import static edu.ie3.util.quantities.PowerSystemUnits.RADIAN;
910
import static java.lang.Math.*;
1011

1112
import edu.ie3.util.exceptions.GeoException;
@@ -16,15 +17,7 @@
1617
import javax.measure.quantity.Angle;
1718
import javax.measure.quantity.Length;
1819
import org.locationtech.jts.algorithm.ConvexHull;
19-
import org.locationtech.jts.geom.Coordinate;
20-
import org.locationtech.jts.geom.Geometry;
21-
import org.locationtech.jts.geom.GeometryCollection;
22-
import org.locationtech.jts.geom.GeometryFactory;
23-
import org.locationtech.jts.geom.LineString;
24-
import org.locationtech.jts.geom.LinearRing;
25-
import org.locationtech.jts.geom.Point;
26-
import org.locationtech.jts.geom.Polygon;
27-
import org.locationtech.jts.geom.PrecisionModel;
20+
import org.locationtech.jts.geom.*;
2821
import org.locationtech.jts.geom.impl.CoordinateArraySequence;
2922
import org.locationtech.jts.math.Vector2D;
3023
import tech.units.indriya.ComparableQuantity;
@@ -34,7 +27,7 @@ public class GeoUtils {
3427
public static final GeometryFactory DEFAULT_GEOMETRY_FACTORY =
3528
new GeometryFactory(new PrecisionModel(), 4326);
3629

37-
private static final ComparableQuantity<Length> EARTH_RADIUS =
30+
public static final ComparableQuantity<Length> EARTH_RADIUS =
3831
Quantities.getQuantity(6378137.0, METRE);
3932

4033
protected GeoUtils() {
@@ -211,6 +204,45 @@ public static ComparableQuantity<Length> calcHaversine(LineString lineString) {
211204
return y;
212205
}
213206

207+
/**
208+
* Method for calculating a bounding box around a point. This method uses an inverse haversine to
209+
* turn a distance into x- and y-deltas. These deltas are used to create an envelope which
210+
* represents the bounding box. The methode can be found here: <a
211+
* href="https://math.stackexchange.com/questions/474602/reverse-use-of-haversine-formula">source</a>
212+
*
213+
* @param coordinate the starting point for the calculation
214+
* @param distance maximal distance in x- and y-direction
215+
* @return envelope
216+
*/
217+
public static Envelope calculateBoundingBox(
218+
Point coordinate, ComparableQuantity<Length> distance) {
219+
// y-degrees are evenly spaced, so we can just divide a distance
220+
// by the earth's radius to get a y-delta in radians
221+
double deltaY = distance.divide(EARTH_RADIUS).getValue().doubleValue();
222+
223+
// because the spacing between x-degrees change between the equator
224+
// and the poles, we need to calculate the x-delta using the inverse
225+
// haversine formula
226+
double sinus = sin(deltaY / 2);
227+
double squaredSinus = pow(sinus, 2);
228+
double cosine = cos(toRadians(coordinate.getY()));
229+
double squaredCosine = pow(cosine, 2);
230+
231+
double deltaX = 2 * asin(sqrt(squaredSinus / squaredCosine));
232+
233+
// turning the deltas to degree
234+
double deltaXDegree = toDegrees(deltaX);
235+
double deltaYDegree = toDegrees(deltaY);
236+
237+
// calculating minimums and maximums for longitude and latitude and returning them as an
238+
// envelope
239+
return new Envelope(
240+
coordinate.getX() - deltaXDegree,
241+
coordinate.getX() + deltaXDegree,
242+
coordinate.getY() - deltaYDegree,
243+
coordinate.getY() + deltaYDegree);
244+
}
245+
214246
/**
215247
* Builds a convex hull from a set of latitude/longitude coordinates.
216248
*

src/test/groovy/edu/ie3/util/geo/GeoUtilsTest.groovy

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,8 @@
55
*/
66
package edu.ie3.util.geo
77

8-
import static edu.ie3.util.quantities.PowerSystemUnits.METRE
9-
10-
import edu.ie3.util.quantities.PowerSystemUnits
118
import edu.ie3.util.quantities.QuantityUtil
12-
import org.locationtech.jts.geom.Coordinate
13-
import org.locationtech.jts.geom.GeometryFactory
14-
import org.locationtech.jts.geom.LineString
15-
import org.locationtech.jts.geom.PrecisionModel
9+
import org.locationtech.jts.geom.*
1610
import org.locationtech.jts.io.geojson.GeoJsonReader
1711
import spock.lang.Shared
1812
import spock.lang.Specification
@@ -21,6 +15,8 @@ import tech.units.indriya.quantity.Quantities
2115

2216
import javax.measure.quantity.Length
2317

18+
import static edu.ie3.util.quantities.PowerSystemUnits.METRE
19+
2420
class GeoUtilsTest extends Specification {
2521

2622
@Shared
@@ -294,4 +290,42 @@ class GeoUtilsTest extends Specification {
294290
Math.abs(actual.x - 7.5) < 1e-9
295291
Math.abs(actual.y - 50d) < 1e-9
296292
}
293+
294+
def "GeoUtils calculates x-delta (longitude) correctly"() {
295+
given:
296+
Point coordinate = GeoUtils.buildPoint(50, 7)
297+
ComparableQuantity<Length> distanceX = GeoUtils.calcHaversine(50, 7, 50, 6)
298+
299+
when:
300+
Envelope envelope = GeoUtils.calculateBoundingBox(coordinate, distanceX)
301+
302+
then:
303+
envelope.minX == 6
304+
envelope.maxX == 8
305+
306+
double yMin = 49.35721717797476
307+
double yMax = 50.64278282202524
308+
309+
envelope.minY == yMin
310+
envelope.maxY == yMax
311+
}
312+
313+
def "GeoUtils calculates y-delta (latitude) correctly"() {
314+
given:
315+
Point coordinate = GeoUtils.buildPoint(50, 7)
316+
ComparableQuantity<Length> distanceX = GeoUtils.calcHaversine(51, 7, 52, 7)
317+
318+
when:
319+
Envelope envelope = GeoUtils.calculateBoundingBox(coordinate, distanceX)
320+
321+
then:
322+
double xMin = 5.444248126340358
323+
double xMax = 8.555751873659641
324+
325+
envelope.minX == xMin
326+
envelope.maxX == xMax
327+
328+
envelope.minY == 49
329+
envelope.maxY == 51
330+
}
297331
}

0 commit comments

Comments
 (0)