Skip to content

Commit 9739241

Browse files
authored
[turf] Add turf area measurement method. (#1079)
* [turf] Add turf area measurement method and test. * [turf] Add javadoc. * [turf] Add more test cases. * [turf] Update change log. * [turf] Change turf area since to 4.10.0
1 parent f590c74 commit 9739241

File tree

12 files changed

+329
-1
lines changed

12 files changed

+329
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Mapbox welcomes participation and contributions from everyone.
44

55
### master
66

7+
- Added support for Turf area measurement method [#1079](https://github.com/mapbox/mapbox-java/pull/1079)
8+
79
### 4.9.0 - September 23, 2019
810
- Added intersection search support to MapboxGeocoding [#1074](https://github.com/mapbox/mapbox-java/pull/1074)
911
- Added support for Turf polygonToLine method [#1075](https://github.com/mapbox/mapbox-java/pull/1075)

docs/turf-port.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Below's an on going list of the Turf functions which currently exist inside the
88

99
## Measurement
1010
- [x] turf-along
11-
- [ ] turf-area
11+
- [x] turf-area
1212
- [x] turf-bbox
1313
- [x] turf-bbox-polygon
1414
- [x] turf-bearing

services-turf/src/main/java/com/mapbox/turf/TurfMeasurement.java

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ private TurfMeasurement() {
3939
throw new AssertionError("No Instances.");
4040
}
4141

42+
/**
43+
* Earth's radius in meters.
44+
*/
45+
public static double EARTH_RADIUS = 6378137;
46+
4247
/**
4348
* Takes two {@link Point}s and finds the geographic bearing between them.
4449
*
@@ -572,4 +577,123 @@ public static BoundingBox square(@NonNull BoundingBox boundingBox) {
572577
);
573578
}
574579
}
580+
581+
/**
582+
* Takes one {@link Feature} and returns it's area in square meters.
583+
*
584+
* @param feature input {@link Feature}
585+
* @return area in square meters
586+
* @since 4.10.0
587+
*/
588+
public static double area(@NonNull Feature feature) {
589+
return feature.geometry() != null ? area(feature.geometry()) : 0.0f;
590+
}
591+
592+
/**
593+
* Takes one {@link FeatureCollection} and returns it's area in square meters.
594+
*
595+
* @param featureCollection input {@link FeatureCollection}
596+
* @return area in square meters
597+
* @since 4.10.0
598+
*/
599+
public static double area(@NonNull FeatureCollection featureCollection) {
600+
List<Feature> features = featureCollection.features();
601+
double total = 0.0f;
602+
if (features != null) {
603+
for (Feature feature : features) {
604+
total += area(feature);
605+
}
606+
}
607+
return total;
608+
}
609+
610+
/**
611+
* Takes one {@link Geometry} and returns it's area in square meters.
612+
*
613+
* @param geometry input {@link Geometry}
614+
* @return area in square meters
615+
* @since 4.10.0
616+
*/
617+
public static double area(@NonNull Geometry geometry) {
618+
return calculateArea(geometry);
619+
}
620+
621+
private static double calculateArea(@NonNull Geometry geometry) {
622+
double total = 0.0f;
623+
if (geometry instanceof Polygon) {
624+
return polygonArea(((Polygon) geometry).coordinates());
625+
} else if (geometry instanceof MultiPolygon) {
626+
List<List<List<Point>>> coordinates = ((MultiPolygon) geometry).coordinates();
627+
for (int i = 0; i < coordinates.size(); i++) {
628+
total += polygonArea(coordinates.get(i));
629+
}
630+
return total;
631+
} else {
632+
// Area should be 0 for case Point, MultiPoint, LineString and MultiLineString
633+
return 0.0f;
634+
}
635+
}
636+
637+
private static double polygonArea(@NonNull List<List<Point>> coordinates) {
638+
double total = 0.0f;
639+
if (coordinates.size() > 0) {
640+
total += Math.abs(ringArea(coordinates.get(0)));
641+
for (int i = 1; i < coordinates.size(); i++) {
642+
total -= Math.abs(ringArea(coordinates.get(i)));
643+
}
644+
}
645+
return total;
646+
}
647+
648+
/**
649+
* Calculate the approximate area of the polygon were it projected onto the earth.
650+
* Note that this area will be positive if ring is oriented clockwise, otherwise
651+
* it will be negative.
652+
*
653+
* Reference:
654+
* Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for Polygons on a Sphere",
655+
* JPL Publication 07-03, Jet Propulsion
656+
* Laboratory, Pasadena, CA, June 2007 https://trs.jpl.nasa.gov/handle/2014/41271
657+
*
658+
* @param coordinates A list of {@link Point} of Ring Coordinates
659+
* @return The approximate signed geodesic area of the polygon in square meters.
660+
*/
661+
private static double ringArea(@NonNull List<Point> coordinates) {
662+
Point p1;
663+
Point p2;
664+
Point p3;
665+
int lowerIndex;
666+
int middleIndex;
667+
int upperIndex;
668+
double total = 0.0f;
669+
final int coordsLength = coordinates.size();
670+
671+
if (coordsLength > 2) {
672+
for (int i = 0; i < coordsLength; i++) {
673+
if (i == coordsLength - 2) { // i = N-2
674+
lowerIndex = coordsLength - 2;
675+
middleIndex = coordsLength - 1;
676+
upperIndex = 0;
677+
} else if (i == coordsLength - 1) { // i = N-1
678+
lowerIndex = coordsLength - 1;
679+
middleIndex = 0;
680+
upperIndex = 1;
681+
} else { // i = 0 to N-3
682+
lowerIndex = i;
683+
middleIndex = i + 1;
684+
upperIndex = i + 2;
685+
}
686+
p1 = coordinates.get(lowerIndex);
687+
p2 = coordinates.get(middleIndex);
688+
p3 = coordinates.get(upperIndex);
689+
total += (rad(p3.longitude()) - rad(p1.longitude())) * Math.sin(rad(p2.latitude()));
690+
}
691+
total = total * EARTH_RADIUS * EARTH_RADIUS / 2;
692+
}
693+
return total;
694+
}
695+
696+
private static double rad(double num) {
697+
return num * Math.PI / 180;
698+
}
575699
}

services-turf/src/test/java/com/mapbox/turf/TurfMeasurementTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ public class TurfMeasurementTest extends TestUtils {
4444
private static final String TURF_ENVELOPE_FEATURE_COLLECTION = "turf-envelope/feature-collection.geojson";
4545
private static final String LINE_DISTANCE_MULTILINESTRING
4646
= "turf-line-distance/multilinestring.geojson";
47+
private static final String TURF_AREA_POLYGON_GEOJSON = "turf-area/polygon.geojson";
48+
private static final String TURF_AREA_POLYGON_RESULT = "turf-area/polygon.json";
49+
private static final String TURF_AREA_MULTIPOLYGON_GEOJSON = "turf-area/multi-polygon.geojson";
50+
private static final String TURF_AREA_MULTIPOLYGON_RESULT = "turf-area/multi-polygon.json";
51+
private static final String TURF_AREA_GEOM_POLYGON_GEOJSON = "turf-area/geometry-polygon.geojson";
52+
private static final String TURF_AREA_GEOM_POLYGON_RESULT = "turf-area/geometry-polygon.json";
53+
private static final String TURF_AREA_FEATURECOLLECTION_POLYGON_GEOJSON = "turf-area/featurecollection-polygon.geojson";
54+
private static final String TURF_AREA_FEATURECOLLECTION_POLYGON_RESULT = "turf-area/featurecollection-polygon.json";
4755

4856
@Rule
4957
public ExpectedException thrown = ExpectedException.none();
@@ -474,4 +482,30 @@ public void square(){
474482
assertEquals(BoundingBox.fromCoordinates(-2.5, 0, 7.5, 10), sq1);
475483
assertEquals(BoundingBox.fromCoordinates(0, -2.5, 10, 7.5), sq2);
476484
}
485+
486+
@Test
487+
public void areaPolygon() {
488+
double expected = Double.valueOf(loadJsonFixture(TURF_AREA_POLYGON_RESULT));
489+
assertEquals(expected, TurfMeasurement.area(Feature.fromJson(loadJsonFixture(TURF_AREA_POLYGON_GEOJSON))), 1);
490+
}
491+
492+
@Test
493+
public void areaMultiPolygon() {
494+
double expected = Double.valueOf(loadJsonFixture(TURF_AREA_MULTIPOLYGON_RESULT));
495+
assertEquals(expected, TurfMeasurement.area(Feature.fromJson(loadJsonFixture(TURF_AREA_MULTIPOLYGON_GEOJSON))), 1);
496+
}
497+
498+
@Test
499+
public void areaGeometry() {
500+
double expected = Double.valueOf(loadJsonFixture(TURF_AREA_GEOM_POLYGON_RESULT));
501+
assertEquals(expected, TurfMeasurement.area(Polygon.fromJson(loadJsonFixture(TURF_AREA_GEOM_POLYGON_GEOJSON))), 1);
502+
}
503+
504+
@Test
505+
public void areaFeatureCollection() {
506+
double expected = Double.valueOf(loadJsonFixture(TURF_AREA_FEATURECOLLECTION_POLYGON_RESULT));
507+
assertEquals(expected, TurfMeasurement.area(FeatureCollection.fromJson(loadJsonFixture(TURF_AREA_FEATURECOLLECTION_POLYGON_GEOJSON))), 1);
508+
}
509+
510+
477511
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{
2+
"type": "FeatureCollection",
3+
"features": [
4+
{
5+
"type": "Feature",
6+
"properties": {},
7+
"geometry": {
8+
"type": "Polygon",
9+
"coordinates": [
10+
[
11+
[
12+
-2.109375,
13+
47.040182144806664
14+
],
15+
[
16+
4.5703125,
17+
44.59046718130883
18+
],
19+
[
20+
7.03125,
21+
49.15296965617042
22+
],
23+
[
24+
-3.515625,
25+
49.83798245308484
26+
],
27+
[
28+
-2.109375,
29+
47.040182144806664
30+
]
31+
]
32+
]
33+
}
34+
},
35+
{
36+
"type": "Feature",
37+
"properties": {},
38+
"geometry": {
39+
"type": "Polygon",
40+
"coordinates": [
41+
[
42+
[
43+
9.64599609375,
44+
47.70976154266637
45+
],
46+
[
47+
9.4482421875,
48+
47.73932336136857
49+
],
50+
[
51+
8.8330078125,
52+
47.47266286861342
53+
],
54+
[
55+
10.21728515625,
56+
46.604167162931844
57+
],
58+
[
59+
11.755371093749998,
60+
46.81509864599243
61+
],
62+
[
63+
11.865234375,
64+
47.90161354142077
65+
],
66+
[
67+
9.64599609375,
68+
47.70976154266637
69+
]
70+
]
71+
]
72+
}
73+
}
74+
]
75+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
294852371360
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"type": "Polygon",
3+
"coordinates": [[
4+
[-2.275543, 53.464547 ],
5+
[-2.275543, 53.489271 ],
6+
[-2.215118, 53.489271 ],
7+
[-2.215118, 53.464547 ],
8+
[-2.275543, 53.464547 ]
9+
]]
10+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
11017976
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"type": "Feature",
3+
"properties": {
4+
"stroke": "#F00",
5+
"stroke-width": 6
6+
},
7+
"geometry": {
8+
"type": "MultiPolygon",
9+
"coordinates": [
10+
[
11+
[
12+
[
13+
102,
14+
2
15+
],
16+
[
17+
103,
18+
2
19+
],
20+
[
21+
103,
22+
3
23+
],
24+
[
25+
102,
26+
3
27+
],
28+
[
29+
102,
30+
2
31+
]
32+
]
33+
],
34+
[
35+
[
36+
[
37+
100,
38+
0
39+
],
40+
[
41+
101,
42+
0
43+
],
44+
[
45+
101,
46+
1
47+
],
48+
[
49+
100,
50+
1
51+
],
52+
[
53+
100,
54+
0
55+
]
56+
]
57+
]
58+
]
59+
}
60+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
24771477332

0 commit comments

Comments
 (0)