From b2df09740d4af2853bd3f697ae1846ccf8e09757 Mon Sep 17 00:00:00 2001 From: Peng Liu Date: Thu, 19 Sep 2019 10:36:26 +0300 Subject: [PATCH 1/5] [turf] Add turf area measurement method and test. --- .../java/com/mapbox/turf/TurfMeasurement.java | 99 +++++++++++++++++++ .../com/mapbox/turf/TurfMeasurementTest.java | 8 ++ .../test/resources/turf-area/polygon.geojson | 19 ++++ .../src/test/resources/turf-area/polygon.json | 1 + 4 files changed, 127 insertions(+) create mode 100644 services-turf/src/test/resources/turf-area/polygon.geojson create mode 100644 services-turf/src/test/resources/turf-area/polygon.json diff --git a/services-turf/src/main/java/com/mapbox/turf/TurfMeasurement.java b/services-turf/src/main/java/com/mapbox/turf/TurfMeasurement.java index d47b52762..4f89e6fe9 100644 --- a/services-turf/src/main/java/com/mapbox/turf/TurfMeasurement.java +++ b/services-turf/src/main/java/com/mapbox/turf/TurfMeasurement.java @@ -39,6 +39,8 @@ private TurfMeasurement() { throw new AssertionError("No Instances."); } + public static double EARTH_RADIUS = 6378137; + /** * Takes two {@link Point}s and finds the geographic bearing between them. * @@ -572,4 +574,101 @@ public static BoundingBox square(@NonNull BoundingBox boundingBox) { ); } } + + public static double area(@NonNull Feature feature) { + return feature.geometry() != null ? area(feature.geometry()) : 0.0f; + } + + public static double area(@NonNull FeatureCollection featureCollection) { + List features = featureCollection.features(); + double total = 0.0f; + if (features != null) { + for (Feature feature : features) { + total += area(feature); + } + } + return total; + } + + public static double area(@NonNull Geometry geometry) { + return calculateArea(geometry); + } + + private static double calculateArea(@NonNull Geometry geometry) { + double total = 0.0f; + if (geometry instanceof Polygon) { + return polygonArea(((Polygon) geometry).coordinates()); + } else if (geometry instanceof MultiPolygon) { + List>> coordinates = ((MultiPolygon) geometry).coordinates(); + for (int i = 0; i < coordinates.size(); i++) { + total += polygonArea(coordinates.get(i)); + } + return total; + } else { + // Area should be 0 for case Point, MultiPoint, LineString and MultiLineString + return 0.0f; + } + } + + private static double polygonArea(@NonNull List> coordinates) { + double total = 0.0f; + if (coordinates.size() > 0) { + total += Math.abs(ringArea(coordinates.get(0))); + for (int i = 1; i < coordinates.size(); i ++) { + total -= Math.abs(ringArea(coordinates.get(i))); + } + } + return total; + } + + /** + * Calculate the approximate area of the polygon were it projected onto the earth. + * Note that this area will be positive if ring is oriented clockwise, otherwise it will be negative. + * + * Reference: + * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for Polygons on a Sphere", + * JPL Publication 07-03, Jet Propulsion + * Laboratory, Pasadena, CA, June 2007 https://trs.jpl.nasa.gov/handle/2014/41271 + * + * @param coordinates A list of {@link Point} of Ring Coordinates + * @return The approximate signed geodesic area of the polygon in square meters. + */ + private static double ringArea(@NonNull List coordinates) { + Point p1; + Point p2; + Point p3; + int lowerIndex; + int middleIndex; + int upperIndex; + double total = 0.0f; + final int coordsLength = coordinates.size(); + + if (coordsLength > 2) { + for (int i = 0; i < coordsLength; i++) { + if (i == coordsLength -2) { // i = N-2 + lowerIndex = coordsLength - 2; + middleIndex = coordsLength - 1; + upperIndex = 0; + } else if (i == coordsLength - 1) { // i = N-1 + lowerIndex = coordsLength - 1; + middleIndex = 0; + upperIndex = 1; + } else { // i = 0 to N-3 + lowerIndex = i; + middleIndex = i + 1; + upperIndex = i + 2; + } + p1 = coordinates.get(lowerIndex); + p2 = coordinates.get(middleIndex); + p3 = coordinates.get(upperIndex); + total += (rad(p3.longitude()) - rad(p1.longitude())) * Math.sin(rad(p2.latitude())); + } + total = total * EARTH_RADIUS * EARTH_RADIUS / 2; + } + return total; + } + + private static double rad(double num) { + return num * Math.PI /180; + } } diff --git a/services-turf/src/test/java/com/mapbox/turf/TurfMeasurementTest.java b/services-turf/src/test/java/com/mapbox/turf/TurfMeasurementTest.java index 691a416b9..e222cada0 100644 --- a/services-turf/src/test/java/com/mapbox/turf/TurfMeasurementTest.java +++ b/services-turf/src/test/java/com/mapbox/turf/TurfMeasurementTest.java @@ -44,6 +44,8 @@ public class TurfMeasurementTest extends TestUtils { private static final String TURF_ENVELOPE_FEATURE_COLLECTION = "turf-envelope/feature-collection.geojson"; private static final String LINE_DISTANCE_MULTILINESTRING = "turf-line-distance/multilinestring.geojson"; + private static final String TURF_AREA_GEOJSON = "turf-area/polygon.geojson"; + private static final String TURF_AREA_RESULT = "turf-area/polygon.json"; @Rule public ExpectedException thrown = ExpectedException.none(); @@ -474,4 +476,10 @@ public void square(){ assertEquals(BoundingBox.fromCoordinates(-2.5, 0, 7.5, 10), sq1); assertEquals(BoundingBox.fromCoordinates(0, -2.5, 10, 7.5), sq2); } + + @Test + public void area() { + double expected = Double.valueOf(loadJsonFixture(TURF_AREA_RESULT)); + assertEquals(expected, TurfMeasurement.area(Feature.fromJson(loadJsonFixture(TURF_AREA_GEOJSON))), 1); + } } diff --git a/services-turf/src/test/resources/turf-area/polygon.geojson b/services-turf/src/test/resources/turf-area/polygon.geojson new file mode 100644 index 000000000..e9b1c8ce7 --- /dev/null +++ b/services-turf/src/test/resources/turf-area/polygon.geojson @@ -0,0 +1,19 @@ +{ + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [125, -15], + [113, -22], + [117, -37], + [130, -33], + [148, -39], + [154, -27], + [144, -15], + [125, -15] + ] + ] + } +} \ No newline at end of file diff --git a/services-turf/src/test/resources/turf-area/polygon.json b/services-turf/src/test/resources/turf-area/polygon.json new file mode 100644 index 000000000..9a8d74ff6 --- /dev/null +++ b/services-turf/src/test/resources/turf-area/polygon.json @@ -0,0 +1 @@ +7766240997209 From 085bf7188f748e004d826666b34cfaaea8b14359 Mon Sep 17 00:00:00 2001 From: Peng Liu Date: Thu, 19 Sep 2019 10:54:00 +0300 Subject: [PATCH 2/5] [turf] Add javadoc. --- .../java/com/mapbox/turf/TurfMeasurement.java | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/services-turf/src/main/java/com/mapbox/turf/TurfMeasurement.java b/services-turf/src/main/java/com/mapbox/turf/TurfMeasurement.java index 4f89e6fe9..7dc7a658b 100644 --- a/services-turf/src/main/java/com/mapbox/turf/TurfMeasurement.java +++ b/services-turf/src/main/java/com/mapbox/turf/TurfMeasurement.java @@ -39,6 +39,9 @@ private TurfMeasurement() { throw new AssertionError("No Instances."); } + /** + * Earth's radius in meters. + */ public static double EARTH_RADIUS = 6378137; /** @@ -575,10 +578,24 @@ public static BoundingBox square(@NonNull BoundingBox boundingBox) { } } + /** + * Takes one {@link Feature} and returns it's area in square meters. + * + * @param feature input {@link Feature} + * @return area in square meters + * @since 4.9.0 + */ public static double area(@NonNull Feature feature) { return feature.geometry() != null ? area(feature.geometry()) : 0.0f; } + /** + * Takes one {@link FeatureCollection} and returns it's area in square meters. + * + * @param featureCollection input {@link FeatureCollection} + * @return area in square meters + * @since 4.9.0 + */ public static double area(@NonNull FeatureCollection featureCollection) { List features = featureCollection.features(); double total = 0.0f; @@ -590,6 +607,13 @@ public static double area(@NonNull FeatureCollection featureCollection) { return total; } + /** + * Takes one {@link Geometry} and returns it's area in square meters. + * + * @param geometry input {@link Geometry} + * @return area in square meters + * @since 4.9.0 + */ public static double area(@NonNull Geometry geometry) { return calculateArea(geometry); } @@ -614,7 +638,7 @@ private static double polygonArea(@NonNull List> coordinates) { double total = 0.0f; if (coordinates.size() > 0) { total += Math.abs(ringArea(coordinates.get(0))); - for (int i = 1; i < coordinates.size(); i ++) { + for (int i = 1; i < coordinates.size(); i++) { total -= Math.abs(ringArea(coordinates.get(i))); } } @@ -623,7 +647,8 @@ private static double polygonArea(@NonNull List> coordinates) { /** * Calculate the approximate area of the polygon were it projected onto the earth. - * Note that this area will be positive if ring is oriented clockwise, otherwise it will be negative. + * Note that this area will be positive if ring is oriented clockwise, otherwise + * it will be negative. * * Reference: * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for Polygons on a Sphere", @@ -645,7 +670,7 @@ private static double ringArea(@NonNull List coordinates) { if (coordsLength > 2) { for (int i = 0; i < coordsLength; i++) { - if (i == coordsLength -2) { // i = N-2 + if (i == coordsLength - 2) { // i = N-2 lowerIndex = coordsLength - 2; middleIndex = coordsLength - 1; upperIndex = 0; @@ -669,6 +694,6 @@ private static double ringArea(@NonNull List coordinates) { } private static double rad(double num) { - return num * Math.PI /180; + return num * Math.PI / 180; } } From 6b33b4f595443f966e639070314d6d215925a9da Mon Sep 17 00:00:00 2001 From: Peng Liu Date: Thu, 19 Sep 2019 11:16:52 +0300 Subject: [PATCH 3/5] [turf] Add more test cases. --- .../com/mapbox/turf/TurfMeasurementTest.java | 36 +++++++-- .../featurecollection-polygon.geojson | 75 +++++++++++++++++++ .../turf-area/featurecollection-polygon.json | 1 + .../turf-area/geometry-polygon.geojson | 10 +++ .../resources/turf-area/geometry-polygon.json | 1 + .../resources/turf-area/multi-polygon.geojson | 60 +++++++++++++++ .../resources/turf-area/multi-polygon.json | 1 + 7 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 services-turf/src/test/resources/turf-area/featurecollection-polygon.geojson create mode 100644 services-turf/src/test/resources/turf-area/featurecollection-polygon.json create mode 100644 services-turf/src/test/resources/turf-area/geometry-polygon.geojson create mode 100644 services-turf/src/test/resources/turf-area/geometry-polygon.json create mode 100644 services-turf/src/test/resources/turf-area/multi-polygon.geojson create mode 100644 services-turf/src/test/resources/turf-area/multi-polygon.json diff --git a/services-turf/src/test/java/com/mapbox/turf/TurfMeasurementTest.java b/services-turf/src/test/java/com/mapbox/turf/TurfMeasurementTest.java index e222cada0..4b0c5c381 100644 --- a/services-turf/src/test/java/com/mapbox/turf/TurfMeasurementTest.java +++ b/services-turf/src/test/java/com/mapbox/turf/TurfMeasurementTest.java @@ -44,8 +44,14 @@ public class TurfMeasurementTest extends TestUtils { private static final String TURF_ENVELOPE_FEATURE_COLLECTION = "turf-envelope/feature-collection.geojson"; private static final String LINE_DISTANCE_MULTILINESTRING = "turf-line-distance/multilinestring.geojson"; - private static final String TURF_AREA_GEOJSON = "turf-area/polygon.geojson"; - private static final String TURF_AREA_RESULT = "turf-area/polygon.json"; + private static final String TURF_AREA_POLYGON_GEOJSON = "turf-area/polygon.geojson"; + private static final String TURF_AREA_POLYGON_RESULT = "turf-area/polygon.json"; + private static final String TURF_AREA_MULTIPOLYGON_GEOJSON = "turf-area/multi-polygon.geojson"; + private static final String TURF_AREA_MULTIPOLYGON_RESULT = "turf-area/multi-polygon.json"; + private static final String TURF_AREA_GEOM_POLYGON_GEOJSON = "turf-area/geometry-polygon.geojson"; + private static final String TURF_AREA_GEOM_POLYGON_RESULT = "turf-area/geometry-polygon.json"; + private static final String TURF_AREA_FEATURECOLLECTION_POLYGON_GEOJSON = "turf-area/featurecollection-polygon.geojson"; + private static final String TURF_AREA_FEATURECOLLECTION_POLYGON_RESULT = "turf-area/featurecollection-polygon.json"; @Rule public ExpectedException thrown = ExpectedException.none(); @@ -478,8 +484,28 @@ public void square(){ } @Test - public void area() { - double expected = Double.valueOf(loadJsonFixture(TURF_AREA_RESULT)); - assertEquals(expected, TurfMeasurement.area(Feature.fromJson(loadJsonFixture(TURF_AREA_GEOJSON))), 1); + public void areaPolygon() { + double expected = Double.valueOf(loadJsonFixture(TURF_AREA_POLYGON_RESULT)); + assertEquals(expected, TurfMeasurement.area(Feature.fromJson(loadJsonFixture(TURF_AREA_POLYGON_GEOJSON))), 1); } + + @Test + public void areaMultiPolygon() { + double expected = Double.valueOf(loadJsonFixture(TURF_AREA_MULTIPOLYGON_RESULT)); + assertEquals(expected, TurfMeasurement.area(Feature.fromJson(loadJsonFixture(TURF_AREA_MULTIPOLYGON_GEOJSON))), 1); + } + + @Test + public void areaGeometry() { + double expected = Double.valueOf(loadJsonFixture(TURF_AREA_GEOM_POLYGON_RESULT)); + assertEquals(expected, TurfMeasurement.area(Polygon.fromJson(loadJsonFixture(TURF_AREA_GEOM_POLYGON_GEOJSON))), 1); + } + + @Test + public void areaFeatureCollection() { + double expected = Double.valueOf(loadJsonFixture(TURF_AREA_FEATURECOLLECTION_POLYGON_RESULT)); + assertEquals(expected, TurfMeasurement.area(FeatureCollection.fromJson(loadJsonFixture(TURF_AREA_FEATURECOLLECTION_POLYGON_GEOJSON))), 1); + } + + } diff --git a/services-turf/src/test/resources/turf-area/featurecollection-polygon.geojson b/services-turf/src/test/resources/turf-area/featurecollection-polygon.geojson new file mode 100644 index 000000000..e5bde0805 --- /dev/null +++ b/services-turf/src/test/resources/turf-area/featurecollection-polygon.geojson @@ -0,0 +1,75 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.109375, + 47.040182144806664 + ], + [ + 4.5703125, + 44.59046718130883 + ], + [ + 7.03125, + 49.15296965617042 + ], + [ + -3.515625, + 49.83798245308484 + ], + [ + -2.109375, + 47.040182144806664 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 9.64599609375, + 47.70976154266637 + ], + [ + 9.4482421875, + 47.73932336136857 + ], + [ + 8.8330078125, + 47.47266286861342 + ], + [ + 10.21728515625, + 46.604167162931844 + ], + [ + 11.755371093749998, + 46.81509864599243 + ], + [ + 11.865234375, + 47.90161354142077 + ], + [ + 9.64599609375, + 47.70976154266637 + ] + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/services-turf/src/test/resources/turf-area/featurecollection-polygon.json b/services-turf/src/test/resources/turf-area/featurecollection-polygon.json new file mode 100644 index 000000000..cf516b0df --- /dev/null +++ b/services-turf/src/test/resources/turf-area/featurecollection-polygon.json @@ -0,0 +1 @@ +294852371360 \ No newline at end of file diff --git a/services-turf/src/test/resources/turf-area/geometry-polygon.geojson b/services-turf/src/test/resources/turf-area/geometry-polygon.geojson new file mode 100644 index 000000000..b4a5e3606 --- /dev/null +++ b/services-turf/src/test/resources/turf-area/geometry-polygon.geojson @@ -0,0 +1,10 @@ +{ + "type": "Polygon", + "coordinates": [[ + [-2.275543, 53.464547 ], + [-2.275543, 53.489271 ], + [-2.215118, 53.489271 ], + [-2.215118, 53.464547 ], + [-2.275543, 53.464547 ] + ]] +} diff --git a/services-turf/src/test/resources/turf-area/geometry-polygon.json b/services-turf/src/test/resources/turf-area/geometry-polygon.json new file mode 100644 index 000000000..abd6005e0 --- /dev/null +++ b/services-turf/src/test/resources/turf-area/geometry-polygon.json @@ -0,0 +1 @@ +11017976 \ No newline at end of file diff --git a/services-turf/src/test/resources/turf-area/multi-polygon.geojson b/services-turf/src/test/resources/turf-area/multi-polygon.geojson new file mode 100644 index 000000000..0fc8fc5c1 --- /dev/null +++ b/services-turf/src/test/resources/turf-area/multi-polygon.geojson @@ -0,0 +1,60 @@ +{ + "type": "Feature", + "properties": { + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + 102, + 2 + ], + [ + 103, + 2 + ], + [ + 103, + 3 + ], + [ + 102, + 3 + ], + [ + 102, + 2 + ] + ] + ], + [ + [ + [ + 100, + 0 + ], + [ + 101, + 0 + ], + [ + 101, + 1 + ], + [ + 100, + 1 + ], + [ + 100, + 0 + ] + ] + ] + ] + } +} \ No newline at end of file diff --git a/services-turf/src/test/resources/turf-area/multi-polygon.json b/services-turf/src/test/resources/turf-area/multi-polygon.json new file mode 100644 index 000000000..59e3bae95 --- /dev/null +++ b/services-turf/src/test/resources/turf-area/multi-polygon.json @@ -0,0 +1 @@ +24771477332 \ No newline at end of file From 0e5e56082e5bc5227e28f0e45830ed5d15895a10 Mon Sep 17 00:00:00 2001 From: Peng Liu Date: Thu, 19 Sep 2019 11:46:26 +0300 Subject: [PATCH 4/5] [turf] Update change log. --- CHANGELOG.md | 1 + docs/turf-port.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 348bd8227..13fb78f68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Mapbox welcomes participation and contributions from everyone. ### 4.9.0 - September 23, 2019 - Added intersection search support to MapboxGeocoding [#1074](https://github.com/mapbox/mapbox-java/pull/1074) - Added support for Turf polygonToLine method [#1075](https://github.com/mapbox/mapbox-java/pull/1075) +- Added support for Turf area measurement method [#1079](https://github.com/mapbox/mapbox-java/pull/1079) ### v4.9.0-alpha.1 - September 4, 2019 diff --git a/docs/turf-port.md b/docs/turf-port.md index c0d2a16bb..9621e9909 100644 --- a/docs/turf-port.md +++ b/docs/turf-port.md @@ -8,7 +8,7 @@ Below's an on going list of the Turf functions which currently exist inside the ## Measurement - [x] turf-along -- [ ] turf-area +- [x] turf-area - [x] turf-bbox - [x] turf-bbox-polygon - [x] turf-bearing From 3f85b230d87546d896ccc253a2f7e2d8d37ae9b1 Mon Sep 17 00:00:00 2001 From: Peng Liu Date: Tue, 24 Sep 2019 10:55:52 +0300 Subject: [PATCH 5/5] [turf] Change turf area since to 4.10.0 --- CHANGELOG.md | 3 ++- .../src/main/java/com/mapbox/turf/TurfMeasurement.java | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13fb78f68..12dc4af43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,11 @@ Mapbox welcomes participation and contributions from everyone. ### master +- Added support for Turf area measurement method [#1079](https://github.com/mapbox/mapbox-java/pull/1079) + ### 4.9.0 - September 23, 2019 - Added intersection search support to MapboxGeocoding [#1074](https://github.com/mapbox/mapbox-java/pull/1074) - Added support for Turf polygonToLine method [#1075](https://github.com/mapbox/mapbox-java/pull/1075) -- Added support for Turf area measurement method [#1079](https://github.com/mapbox/mapbox-java/pull/1079) ### v4.9.0-alpha.1 - September 4, 2019 diff --git a/services-turf/src/main/java/com/mapbox/turf/TurfMeasurement.java b/services-turf/src/main/java/com/mapbox/turf/TurfMeasurement.java index 7dc7a658b..2f40680bb 100644 --- a/services-turf/src/main/java/com/mapbox/turf/TurfMeasurement.java +++ b/services-turf/src/main/java/com/mapbox/turf/TurfMeasurement.java @@ -583,7 +583,7 @@ public static BoundingBox square(@NonNull BoundingBox boundingBox) { * * @param feature input {@link Feature} * @return area in square meters - * @since 4.9.0 + * @since 4.10.0 */ public static double area(@NonNull Feature feature) { return feature.geometry() != null ? area(feature.geometry()) : 0.0f; @@ -594,7 +594,7 @@ public static double area(@NonNull Feature feature) { * * @param featureCollection input {@link FeatureCollection} * @return area in square meters - * @since 4.9.0 + * @since 4.10.0 */ public static double area(@NonNull FeatureCollection featureCollection) { List features = featureCollection.features(); @@ -612,7 +612,7 @@ public static double area(@NonNull FeatureCollection featureCollection) { * * @param geometry input {@link Geometry} * @return area in square meters - * @since 4.9.0 + * @since 4.10.0 */ public static double area(@NonNull Geometry geometry) { return calculateArea(geometry);