|
19 | 19 |
|
20 | 20 | import java.util.ArrayList;
|
21 | 21 | import java.util.List;
|
| 22 | +import org.locationtech.jts.geom.Coordinate; |
| 23 | +import org.locationtech.jts.geom.GeometryFactory; |
| 24 | +import org.locationtech.jts.geom.LineString; |
| 25 | +import org.locationtech.jts.operation.linemerge.LineMerger; |
22 | 26 |
|
| 27 | +/** |
| 28 | + * Provides methods for generating isoline contours from digital elevation models (DEMs). |
| 29 | + */ |
23 | 30 | public class IsoLines {
|
24 | 31 |
|
25 |
| - public record Point(double x, double y) { |
26 |
| - } |
| 32 | + private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); |
| 33 | + |
| 34 | + private static final double EPSILON = 1e-10; |
27 | 35 |
|
28 |
| - public record IsoLine(List<Point> points) { |
| 36 | + private IsoLines() { |
| 37 | + // Prevent instantiation |
29 | 38 | }
|
30 | 39 |
|
31 |
| - public static List<IsoLine> isoLines(double[] grid, int gridSize, double level) { |
32 |
| - List<IsoLine> isoLines = new ArrayList<>(); |
33 |
| - for (int y = 0; y < gridSize - 1; y++) { |
34 |
| - for (int x = 0; x < gridSize - 1; x++) { |
35 |
| - int index = (grid[y * (gridSize + 1) + x] > level ? 1 : 0) | |
36 |
| - (grid[y * (gridSize + 1) + (x + 1)] > level ? 2 : 0) | |
37 |
| - (grid[(y + 1) * (gridSize + 1) + (x + 1)] > level ? 4 : 0) | |
38 |
| - (grid[(y + 1) * (gridSize + 1) + x] > level ? 8 : 0); |
39 |
| - List<Point> points = new ArrayList<>(); |
40 |
| - switch (index) { |
41 |
| - case 1: |
42 |
| - case 14: |
43 |
| - points.add(interpolate(x, y, x, y + 1, grid, gridSize + 1, level)); |
44 |
| - points.add(interpolate(x, y, x + 1, y, grid, gridSize + 1, level)); |
45 |
| - break; |
46 |
| - case 2: |
47 |
| - case 13: |
48 |
| - points.add(interpolate(x + 1, y, x, y, grid, gridSize + 1, level)); |
49 |
| - points.add(interpolate(x + 1, y, x + 1, y + 1, grid, gridSize + 1, level)); |
50 |
| - break; |
51 |
| - case 3: |
52 |
| - case 12: |
53 |
| - points.add(interpolate(x, y, x, y + 1, grid, gridSize + 1, level)); |
54 |
| - points.add(interpolate(x + 1, y, x + 1, y + 1, grid, gridSize + 1, level)); |
55 |
| - break; |
56 |
| - case 4: |
57 |
| - case 11: |
58 |
| - points.add(interpolate(x + 1, y + 1, x + 1, y, grid, gridSize + 1, level)); |
59 |
| - points.add(interpolate(x + 1, y + 1, x, y + 1, grid, gridSize + 1, level)); |
60 |
| - break; |
61 |
| - case 5: |
62 |
| - points.add(interpolate(x, y, x, y + 1, grid, gridSize + 1, level)); |
63 |
| - points.add(interpolate(x, y + 1, x + 1, y + 1, grid, gridSize + 1, level)); |
64 |
| - points.add(interpolate(x + 1, y, x + 1, y + 1, grid, gridSize + 1, level)); |
65 |
| - points.add(interpolate(x + 1, y, x, y, grid, gridSize + 1, level)); |
66 |
| - break; |
67 |
| - case 6: |
68 |
| - case 9: |
69 |
| - points.add(interpolate(x, y, x + 1, y, grid, gridSize + 1, level)); |
70 |
| - points.add(interpolate(x, y + 1, x + 1, y + 1, grid, gridSize + 1, level)); |
71 |
| - break; |
72 |
| - case 7: |
73 |
| - case 8: |
74 |
| - points.add(interpolate(x, y, x, y + 1, grid, gridSize + 1, level)); |
75 |
| - points.add(interpolate(x, y + 1, x + 1, y + 1, grid, gridSize + 1, level)); |
76 |
| - break; |
77 |
| - case 10: |
78 |
| - points.add(interpolate(x, y, x + 1, y, grid, gridSize + 1, level)); |
79 |
| - points.add(interpolate(x + 1, y, x + 1, y + 1, grid, gridSize + 1, level)); |
80 |
| - points.add(interpolate(x + 1, y + 1, x, y + 1, grid, gridSize + 1, level)); |
81 |
| - points.add(interpolate(x, y + 1, x, y, grid, gridSize + 1, level)); |
82 |
| - break; |
83 |
| - } |
84 |
| - if (!points.isEmpty()) { |
85 |
| - isoLines.add(new IsoLine(points)); |
86 |
| - } |
| 40 | + /** |
| 41 | + * Generates isolines for a given grid at a specific level. |
| 42 | + * |
| 43 | + * @param grid The elevation data |
| 44 | + * @param width The width of the grid |
| 45 | + * @param height The height of the grid |
| 46 | + * @param level The elevation level for which to generate isolines |
| 47 | + * @param normalize Whether to normalize the coordinates |
| 48 | + * @return A list of LineString objects representing the isolines |
| 49 | + */ |
| 50 | + public static List<LineString> generateIsoLines( |
| 51 | + double[] grid, int width, int height, |
| 52 | + double level, boolean normalize) { |
| 53 | + validateInput(grid, width, height); |
| 54 | + List<LineString> lineStrings = new ArrayList<>(); |
| 55 | + for (int y = 0; y < height - 1; y++) { |
| 56 | + for (int x = 0; x < width - 1; x++) { |
| 57 | + processCell(grid, width, height, level, normalize, lineStrings, y, x); |
87 | 58 | }
|
88 | 59 | }
|
89 |
| - return isoLines; |
| 60 | + return mergeLineStrings(lineStrings); |
90 | 61 | }
|
91 | 62 |
|
92 |
| - public static List<IsoLine> isoLines(double[] grid, int gridSize, int start, int end, |
93 |
| - int interval) { |
94 |
| - List<IsoLine> isoLines = new ArrayList<>(); |
95 |
| - for (int level = start; level < end; level++) { |
96 |
| - isoLines.addAll(isoLines(grid, gridSize, level)); |
| 63 | + /** |
| 64 | + * Generates isolines for a given grid at multiple levels within a specified range. |
| 65 | + * |
| 66 | + * @param grid The elevation data |
| 67 | + * @param width The width of the grid |
| 68 | + * @param height The height of the grid |
| 69 | + * @param start The starting elevation level |
| 70 | + * @param end The ending elevation level |
| 71 | + * @param interval The interval between elevation levels |
| 72 | + * @param normalize Whether to normalize the coordinates |
| 73 | + * @return A list of LineString objects representing the isolines |
| 74 | + */ |
| 75 | + public static List<LineString> generateIsoLines( |
| 76 | + double[] grid, int width, int height, |
| 77 | + int start, int end, int interval, |
| 78 | + boolean normalize) { |
| 79 | + validateInput(grid, width, height); |
| 80 | + List<LineString> isoLines = new ArrayList<>(); |
| 81 | + for (int level = start; level < end; level += interval) { |
| 82 | + isoLines.addAll(generateIsoLines(grid, width, height, level, normalize)); |
97 | 83 | }
|
98 | 84 | return isoLines;
|
99 | 85 | }
|
100 | 86 |
|
101 |
| - private static Point interpolate( |
102 |
| - int x1, |
103 |
| - int y1, |
104 |
| - int x2, |
105 |
| - int y2, |
106 |
| - double[] grid, |
107 |
| - int width, |
108 |
| - double level) { |
| 87 | + private static List<LineString> mergeLineStrings(List<LineString> lineStrings) { |
| 88 | + LineMerger lineMerger = new LineMerger(); |
| 89 | + lineMerger.add(lineStrings); |
| 90 | + return new ArrayList<>(lineMerger.getMergedLineStrings()); |
| 91 | + } |
| 92 | + |
| 93 | + private static void validateInput(double[] grid, int width, int height) { |
| 94 | + if (grid == null || grid.length == 0) { |
| 95 | + throw new IllegalArgumentException("Grid array cannot be null or empty"); |
| 96 | + } |
| 97 | + if (width <= 0 || height <= 0) { |
| 98 | + throw new IllegalArgumentException("Width and height must be positive"); |
| 99 | + } |
| 100 | + if (grid.length != width * height) { |
| 101 | + throw new IllegalArgumentException("Grid array length does not match width * height"); |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + private static void processCell( |
| 106 | + double[] grid, int width, int height, |
| 107 | + double level, boolean normalize, List<LineString> lineStrings, |
| 108 | + int y, int x) { |
| 109 | + double tl = grid[y * width + x]; |
| 110 | + double tr = grid[y * width + (x + 1)]; |
| 111 | + double br = grid[(y + 1) * width + (x + 1)]; |
| 112 | + double bl = grid[(y + 1) * width + x]; |
| 113 | + |
| 114 | + int index = |
| 115 | + (tl > level ? 1 : 0) | |
| 116 | + (tr > level ? 2 : 0) | |
| 117 | + (br > level ? 4 : 0) | |
| 118 | + (bl > level ? 8 : 0); |
| 119 | + |
| 120 | + switch (index) { |
| 121 | + case 1: |
| 122 | + case 14: |
| 123 | + createLineString( |
| 124 | + grid, width, height, level, normalize, lineStrings, |
| 125 | + x, y, x + 1, y, |
| 126 | + x, y + 1, x, y); |
| 127 | + break; |
| 128 | + case 2: |
| 129 | + case 13: |
| 130 | + createLineString( |
| 131 | + grid, width, height, level, normalize, lineStrings, |
| 132 | + x + 1, y, x, y, |
| 133 | + x + 1, y, x + 1, y + 1); |
| 134 | + break; |
| 135 | + case 3: |
| 136 | + case 12: |
| 137 | + createLineString( |
| 138 | + grid, width, height, level, normalize, lineStrings, |
| 139 | + x, y, x, y + 1, |
| 140 | + x + 1, y, x + 1, y + 1); |
| 141 | + break; |
| 142 | + case 4: |
| 143 | + case 11: |
| 144 | + createLineString( |
| 145 | + grid, width, height, level, normalize, lineStrings, |
| 146 | + x + 1, y + 1, x + 1, y, |
| 147 | + x + 1, y + 1, x, y + 1); |
| 148 | + break; |
| 149 | + case 5: |
| 150 | + createLineString( |
| 151 | + grid, width, height, level, normalize, lineStrings, |
| 152 | + x, y, x, y + 1, |
| 153 | + x, y + 1, x + 1, y + 1); |
| 154 | + createLineString( |
| 155 | + grid, width, height, level, normalize, lineStrings, |
| 156 | + x + 1, y, x + 1, y + 1, |
| 157 | + x + 1, y, x, y); |
| 158 | + break; |
| 159 | + case 6: |
| 160 | + case 9: |
| 161 | + createLineString( |
| 162 | + grid, width, height, level, normalize, lineStrings, |
| 163 | + x, y, x + 1, y, |
| 164 | + x, y + 1, x + 1, y + 1); |
| 165 | + break; |
| 166 | + case 7: |
| 167 | + case 8: |
| 168 | + createLineString( |
| 169 | + grid, width, height, level, normalize, lineStrings, |
| 170 | + x, y, x, y + 1, |
| 171 | + x, y + 1, x + 1, y + 1); |
| 172 | + break; |
| 173 | + case 10: |
| 174 | + createLineString( |
| 175 | + grid, width, height, level, normalize, lineStrings, |
| 176 | + x, y, x + 1, y, |
| 177 | + x + 1, y, x + 1, y + 1); |
| 178 | + createLineString( |
| 179 | + grid, width, height, level, normalize, lineStrings, |
| 180 | + x + 1, y + 1, x, y + 1, |
| 181 | + x, y + 1, x, y); |
| 182 | + break; |
| 183 | + } |
| 184 | + } |
| 185 | + |
| 186 | + private static void createLineString( |
| 187 | + double[] grid, int width, int height, |
| 188 | + double level, boolean normalize, List<LineString> lineStrings, |
| 189 | + int x1, int y1, int x2, int y2, |
| 190 | + int x3, int y3, int x4, int y4) { |
| 191 | + Coordinate c1 = interpolate(grid, width, height, level, normalize, x1, y1, x2, y2); |
| 192 | + Coordinate c2 = interpolate(grid, width, height, level, normalize, x3, y3, x4, y4); |
| 193 | + lineStrings.add(GEOMETRY_FACTORY.createLineString(new Coordinate[] {c1, c2})); |
| 194 | + } |
| 195 | + |
| 196 | + private static Coordinate interpolate( |
| 197 | + double[] grid, int width, int height, |
| 198 | + double level, boolean normalize, |
| 199 | + int x1, int y1, int x2, int y2) { |
109 | 200 | double v1 = grid[y1 * width + x1];
|
110 | 201 | double v2 = grid[y2 * width + x2];
|
111 |
| - double t = (level - v1) / (v2 - v1); |
112 |
| - return new Point(x1 + t * (x2 - x1), y1 + t * (y2 - y1)); |
| 202 | + double t = (Math.abs(v2 - v1) < EPSILON) ? 0.5 : (level - v1) / (v2 - v1); |
| 203 | + double x = x1 + t * (x2 - x1); |
| 204 | + double y = y1 + t * (y2 - y1); |
| 205 | + if (normalize) { |
| 206 | + x = x / (width - 1) * width; |
| 207 | + y = y / (height - 1) * height; |
| 208 | + } |
| 209 | + return new Coordinate(x, y); |
113 | 210 | }
|
114 |
| - |
115 | 211 | }
|
0 commit comments