diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Geometry.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Geometry.java
index 199277dafa..5186c624a2 100644
|
a
|
b
|
public class Geometry {
|
| 44 | 44 | Path2D.Float line = null; |
| 45 | 45 | Area area = null; |
| 46 | 46 | // MVT uses delta encoding. Each feature starts at (0, 0). |
| 47 | | double x = 0; |
| 48 | | double y = 0; |
| | 47 | int x = 0; |
| | 48 | int y = 0; |
| 49 | 49 | // Area is used to determine the inner/outer of a polygon |
| 50 | | double areaAreaSq = 0; |
| | 50 | final int maxArraySize = commands.stream().filter(command -> command.getType() != Command.ClosePath).mapToInt(command -> command.getOperations().length).sum(); |
| | 51 | final List<Integer> xArray = new ArrayList<>(maxArraySize); |
| | 52 | final List<Integer> yArray = new ArrayList<>(maxArraySize); |
| 51 | 53 | for (CommandInteger command : commands) { |
| 52 | 54 | final short[] operations = command.getOperations(); |
| 53 | 55 | // Technically, there is no reason why there can be multiple MoveTo operations in one command, but that is undefined behavior |
| 54 | 56 | if (command.getType() == Command.MoveTo && operations.length == 2) { |
| 55 | | areaAreaSq = 0; |
| 56 | 57 | x += operations[0]; |
| 57 | 58 | y += operations[1]; |
| 58 | 59 | line = new Path2D.Float(); |
| 59 | 60 | line.moveTo(x, y); |
| | 61 | xArray.add(x); |
| | 62 | yArray.add(y); |
| 60 | 63 | shapes.add(line); |
| 61 | 64 | } else if (command.getType() == Command.LineTo && operations.length % 2 == 0 && line != null) { |
| 62 | 65 | for (int i = 0; i < operations.length / 2; i++) { |
| 63 | | final double lx = x; |
| 64 | | final double ly = y; |
| 65 | 66 | x += operations[2 * i]; |
| 66 | 67 | y += operations[2 * i + 1]; |
| 67 | | areaAreaSq += lx * y - x * ly; |
| | 68 | xArray.add(x); |
| | 69 | yArray.add(y); |
| 68 | 70 | line.lineTo(x, y); |
| 69 | 71 | } |
| 70 | 72 | // ClosePath should only be used with Polygon geometry |
| … |
… |
public class Geometry {
|
| 76 | 78 | shapes.add(area); |
| 77 | 79 | } |
| 78 | 80 | |
| | 81 | final double areaAreaSq = calculateSurveyorsArea(xArray.stream().mapToInt(i -> i).toArray(), yArray.stream().mapToInt(i -> i).toArray()); |
| 79 | 82 | Area nArea = new Area(line); |
| 80 | 83 | // SonarLint thinks that this is never > 0. It can be. |
| 81 | 84 | if (areaAreaSq > 0) { |
| … |
… |
public class Geometry {
|
| 92 | 95 | } |
| 93 | 96 | } |
| 94 | 97 | |
| | 98 | /** |
| | 99 | * This is also known as the "shoelace formula". |
| | 100 | * @param xArray The array of x coordinates |
| | 101 | * @param yArray The array of y coordinates |
| | 102 | * @return The area of the object |
| | 103 | * @throws IllegalArgumentException if the array lengths are not equal |
| | 104 | */ |
| | 105 | static double calculateSurveyorsArea(int[] xArray, int[] yArray) { |
| | 106 | if (xArray.length != yArray.length) { |
| | 107 | throw new IllegalArgumentException("Cannot calculate areas when arrays are uneven"); |
| | 108 | } |
| | 109 | // Lines have no area |
| | 110 | if (xArray.length < 3) { |
| | 111 | return 0; |
| | 112 | } |
| | 113 | int area = 0; |
| | 114 | // Do the non-special stuff first (x0 * y1 - x1 * y0) |
| | 115 | for (int i = 0; i < xArray.length - 1; i++) { |
| | 116 | area += xArray[i] * yArray[i + 1] - xArray[i + 1] * yArray[i]; |
| | 117 | } |
| | 118 | // Now calculate the edges (xn * y0 - x0 * yn) |
| | 119 | area += xArray[xArray.length - 1] * yArray[0] - xArray[0] * yArray[yArray.length - 1]; |
| | 120 | return area / 2d; |
| | 121 | } |
| | 122 | |
| 95 | 123 | /** |
| 96 | 124 | * Get the shapes to draw this geometry with |
| 97 | 125 | * @return A collection of shapes |
diff --git a/test/unit/org/openstreetmap/josm/data/imagery/vectortile/mapbox/GeometryTest.java b/test/unit/org/openstreetmap/josm/data/imagery/vectortile/mapbox/GeometryTest.java
index 14731168f7..0c6e2c1ae9 100644
|
a
|
b
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
| 6 | 6 | import static org.junit.jupiter.api.Assertions.assertThrows; |
| 7 | 7 | import static org.junit.jupiter.api.Assertions.assertTrue; |
| 8 | 8 | |
| 9 | | |
| 10 | 9 | import java.awt.geom.Area; |
| 11 | 10 | import java.awt.geom.Ellipse2D; |
| 12 | 11 | import java.awt.geom.Path2D; |
| … |
… |
class GeometryTest {
|
| 138 | 137 | assertEquals("POLYGON cannot have zero area", exception.getMessage()); |
| 139 | 138 | } |
| 140 | 139 | |
| | 140 | /** |
| | 141 | * This checks that the area is properly calculated |
| | 142 | */ |
| | 143 | @Test |
| | 144 | void testNonRegression20971And21254() { |
| | 145 | assertEquals(15.0, Geometry.calculateSurveyorsArea(new int[]{1507, 1509, 1509}, new int[]{3029, 3018, 3033})); |
| | 146 | assertEquals(0.0, Geometry.calculateSurveyorsArea(new int[]{0, 0, 0}, new int[]{0, 0, 0})); |
| | 147 | assertEquals(0.0, Geometry.calculateSurveyorsArea(new int[2], new int[2])); |
| | 148 | assertThrows(IllegalArgumentException.class, () -> Geometry.calculateSurveyorsArea(new int[3], new int[4])); |
| | 149 | } |
| | 150 | |
| 141 | 151 | @Test |
| 142 | 152 | void testMultiPolygon() { |
| 143 | 153 | List<CommandInteger> commands = new ArrayList<>(10); |