1 package tim.prune.threedee;
3 import java.awt.image.BufferedImage;
5 import java.io.IOException;
7 import javax.imageio.ImageIO;
8 import javax.vecmath.Point3d;
9 import javax.vecmath.TexCoord2f;
11 import tim.prune.data.Altitude;
12 import tim.prune.data.Coordinate;
13 import tim.prune.data.DataPoint;
14 import tim.prune.data.DoubleRange;
15 import tim.prune.data.Field;
16 import tim.prune.data.FieldList;
17 import tim.prune.data.Latitude;
18 import tim.prune.data.Longitude;
19 import tim.prune.data.Track;
20 import tim.prune.data.TrackExtents;
21 import tim.prune.data.UnitSetLibrary;
22 import tim.prune.gui.map.MapUtils;
25 * Helper for generating the arrays needed for the 3d terrain
27 public class TerrainHelper
29 /** Number of nodes on each side of the square grid */
30 private int _gridSize = 0;
34 * @param inGridSize grid size
36 public TerrainHelper(int inGridSize) {
37 _gridSize = inGridSize;
43 public int getGridSize() {
49 * Convert the terrain coordinates from raw form to TriangleStripArray form
50 * (with repeated nodes)
51 * @param inRawPoints array of raw points as formed from the track
52 * @return point coordinates as array
54 public Point3d[] getTerrainCoordinates(Point3d[] inRawPoints)
56 final int numNodes = _gridSize * _gridSize;
57 if (_gridSize <= 1 || inRawPoints == null || inRawPoints.length != numNodes) {return null;}
58 // Put these nodes into a new result array (repeating nodes as necessary)
59 final int resultSize = _gridSize * (_gridSize * 2 - 2);
60 Point3d[] result = new Point3d[resultSize];
61 final int numStrips = _gridSize - 1;
63 for (int strip=0; strip<numStrips; strip++)
65 for (int col=0; col<_gridSize; col++)
67 int bottomNodeIndex = strip * _gridSize + col;
68 int topNodeIndex = bottomNodeIndex + _gridSize;
69 result[resultIndex++] = inRawPoints[bottomNodeIndex];
70 result[resultIndex++] = inRawPoints[topNodeIndex];
78 * Get the texture coordinates as an array
79 * @return texture coordinates as array
81 public TexCoord2f[] getTextureCoordinates()
83 if (_gridSize <= 1) {return null;}
84 final int numNodes = _gridSize * _gridSize;
85 final float gridStep = 1.0f / (_gridSize - 1);
86 // Build all the required nodes
87 TexCoord2f[] nodes = new TexCoord2f[numNodes];
88 for (int i=0; i<_gridSize; i++)
90 for (int j=0; j<_gridSize; j++)
92 nodes[j * _gridSize + i] = new TexCoord2f(gridStep * i, 1.0f - gridStep * j);
95 // Now put these nodes into a new result array (repeating nodes as necessary)
96 final int resultSize = _gridSize * (_gridSize * 2 - 2);
97 TexCoord2f[] result = new TexCoord2f[resultSize];
98 final int numStrips = _gridSize - 1;
100 for (int strip=0; strip<numStrips; strip++)
102 for (int col=0; col<_gridSize; col++)
104 int bottomNodeIndex = strip * _gridSize + col;
105 int topNodeIndex = bottomNodeIndex + _gridSize;
106 result[resultIndex++] = nodes[bottomNodeIndex];
107 result[resultIndex++] = nodes[topNodeIndex];
114 * @return strip lengths as array
116 public int[] getStripLengths()
118 final int numStrips = _gridSize - 1;
119 final int nodesPerStrip = _gridSize * 2;
120 int[] result = new int[numStrips];
121 for (int i=0; i<numStrips; i++) {
122 result[i] = nodesPerStrip;
128 * Create a grid of points in a new Track
129 * @param inDataTrack track from which the extents should be obtained
130 * @return Track containing all the points in the grid
132 public Track createGridTrack(Track inDataTrack)
134 // Work out the size of the current track
135 TrackExtents extents = new TrackExtents(inDataTrack);
136 extents.applySquareBorder();
137 DoubleRange xRange = extents.getXRange();
138 DoubleRange yRange = extents.getYRange();
139 // Create the array of points
140 final int numPoints = _gridSize * _gridSize;
141 final double xStep = xRange.getRange() / (_gridSize - 1);
142 final double yStep = yRange.getRange() / (_gridSize - 1);
143 DataPoint[] points = new DataPoint[numPoints];
144 for (int i=0; i<_gridSize; i++)
146 double pY = yRange.getMinimum() + i * yStep;
147 for (int j=0; j<_gridSize; j++)
149 // Create a new point with the appropriate lat and long, with no altitude
150 double pX = xRange.getMinimum() + j * xStep;
151 DataPoint point = new DataPoint(
152 new Latitude(MapUtils.getLatitudeFromY(pY), Coordinate.FORMAT_NONE),
153 new Longitude(MapUtils.getLongitudeFromX(pX), Coordinate.FORMAT_NONE),
155 //System.out.println("Created point at " + point.getLatitude().output(Coordinate.FORMAT_DEG_MIN_SEC)
156 // + ", " + point.getLongitude().output(Coordinate.FORMAT_DEG_MIN_SEC));
157 points[i * _gridSize + j] = point;
160 // Put these into a new track
161 Field[] fields = {Field.LATITUDE, Field.LONGITUDE, Field.ALTITUDE};
162 Track grid = new Track(new FieldList(fields), points);
167 * Write the given terrain track out to an indexed png file
168 * @param inModel three-d data model with terrain
169 * @param inPngFile file to write to
171 public void writeHeightMap(ThreeDModel inModel, File inPngFile)
173 BufferedImage image = new BufferedImage(_gridSize, _gridSize, BufferedImage.TYPE_BYTE_INDEXED);
174 for (int y=0; y<_gridSize; y++)
176 for (int x=0; x<_gridSize; x++)
178 double heightValue = inModel.getScaledTerrainValue(y * _gridSize + x) * 256;
179 // Need to ask colour model what rgb to use for this index (a little round-the-houses)
180 image.setRGB(x, y, image.getColorModel().getRGB((int) heightValue));
185 ImageIO.write(image, "PNG", inPngFile);
187 catch (IOException ioe) {System.err.println(ioe.getClass().getName() + " - " + ioe.getMessage());}
192 * Try to fix the voids in the given terrain track by averaging neighbour values where possible
193 * @param inTerrainTrack terrain track to fix
195 public void fixVoids(Track inTerrainTrack)
197 int numVoids = countVoids(inTerrainTrack);
198 if (numVoids == 0) {return;}
199 // System.out.println("Starting to fix, num voids = " + numVoids);
200 // Fix the holes which are surrounded on all four sides by non-holes
201 fixSingleHoles(inTerrainTrack);
202 // System.out.println("Fixed single holes, now num voids = " + countVoids(inTerrainTrack));
203 // Maybe there is something to do in the corners?
204 fixCorners(inTerrainTrack);
205 // Now fix the bigger holes, which should fix everything left
206 fixBiggerHoles(inTerrainTrack);
207 final int numHolesLeft = countVoids(inTerrainTrack);
208 if (numHolesLeft > 0) {
209 System.out.println("Fixed bigger holes, now num voids = " + countVoids(inTerrainTrack));
214 * @param inTerrainTrack terrain track
215 * @return number of voids (points without altitudes)
217 private static int countVoids(Track inTerrainTrack)
220 if (inTerrainTrack != null)
222 for (int i=0; i<inTerrainTrack.getNumPoints(); i++) {
223 if (!inTerrainTrack.getPoint(i).hasAltitude()) {
232 * Just deal with single holes surrounded by at least four direct neighbours
233 * @param inTerrainTrack terrain track to fix
235 private void fixSingleHoles(Track inTerrainTrack)
237 // Holes with neighbours in all directions
238 final int startIndex = 1, endIndex = _gridSize - 2;
239 for (int x = startIndex; x <= endIndex; x++)
241 for (int y = startIndex; y <= endIndex; y++)
243 int pIndex = x * _gridSize + y;
244 // Get the point and its neighbours
245 final DataPoint p = inTerrainTrack.getPoint(pIndex);
246 if (!p.hasAltitude())
248 final DataPoint pl = inTerrainTrack.getPoint(pIndex - 1);
249 final DataPoint pr = inTerrainTrack.getPoint(pIndex + 1);
250 final DataPoint pu = inTerrainTrack.getPoint(pIndex + _gridSize);
251 final DataPoint pd = inTerrainTrack.getPoint(pIndex - _gridSize);
252 // Check if the points are null??
253 if (pl == null || pr == null || pu == null || pd == null)
255 System.err.println("Woah. Got a null point in fixSingleHoles. x=" + x + ", y=" + y + ", grid=" + _gridSize);
256 System.err.println("index=" + pIndex);
257 if (pl == null) System.err.println("pl is null");
258 if (pr == null) System.err.println("pr is null");
259 if (pu == null) System.err.println("pu is null");
260 if (pd == null) System.err.println("pd is null");
263 // Check that all the neighbours have altitudes
264 if (pl.hasAltitude() && pr.hasAltitude() && pu.hasAltitude() && pd.hasAltitude())
266 // Now check the double-neighbours
267 final DataPoint pll = inTerrainTrack.getPoint(pIndex - 2);
268 final DataPoint prr = inTerrainTrack.getPoint(pIndex + 2);
269 final DataPoint puu = inTerrainTrack.getPoint(pIndex + 2 * _gridSize);
270 final DataPoint pdd = inTerrainTrack.getPoint(pIndex - 2 * _gridSize);
272 double altitude = 0.0;
273 if (pll != null && pll.hasAltitude() && prr != null && prr.hasAltitude()
274 && puu != null && puu.hasAltitude() && pdd != null & pdd.hasAltitude())
276 // Use the double-neighbours too to take into account the gradients
278 pl.getAltitude().getMetricValue() * 1.5
279 - pll.getAltitude().getMetricValue() * 0.5
280 + pr.getAltitude().getMetricValue() * 1.5
281 - prr.getAltitude().getMetricValue() * 0.5
282 + pd.getAltitude().getMetricValue() * 1.5
283 - pdd.getAltitude().getMetricValue() * 0.5
284 + pu.getAltitude().getMetricValue() * 1.5
285 - puu.getAltitude().getMetricValue() * 0.5) / 4.0;
289 // no double-neighbours, just use neighbours
291 pl.getAltitude().getMetricValue()
292 + pr.getAltitude().getMetricValue()
293 + pd.getAltitude().getMetricValue()
294 + pu.getAltitude().getMetricValue()) / 4.0;
296 // Set this altitude in the point
297 p.setFieldValue(Field.ALTITUDE, "" + altitude, false);
298 // force value to metres
299 p.getAltitude().reset(new Altitude((int) altitude, UnitSetLibrary.UNITS_METRES));
307 * Try to fix the corners, if they're blank
308 * @param inTerrainTrack terrain track
310 private void fixCorners(Track inTerrainTrack)
312 fixCorner(inTerrainTrack, 0, 1, 1);
313 fixCorner(inTerrainTrack, _gridSize-1, -1, 1);
314 fixCorner(inTerrainTrack, (_gridSize-1)*_gridSize, 1, -1);
315 fixCorner(inTerrainTrack, _gridSize*_gridSize-1, -1, -1);
319 * Fix a single corner by searching along adjacent edges and averaging the nearest neighbours
320 * @param inTerrainTrack terrain track
321 * @param inCornerIndex index of corner to fill
322 * @param inXinc increment in x direction (+1 or -1)
323 * @param inYinc increment in y direction (+1 or -1)
325 private void fixCorner(Track inTerrainTrack, int inCornerIndex, int inXinc, int inYinc)
327 DataPoint corner = inTerrainTrack.getPoint(inCornerIndex);
328 if (corner == null || corner.hasAltitude()) {return;}
329 // Corner hasn't got an altitude, we'll have to look for it
330 int sIndex1 = inCornerIndex, sIndex2 = inCornerIndex;
331 Altitude alt1 = null, alt2 = null;
333 for (int i=1; i<_gridSize && !corner.hasAltitude(); i++)
336 sIndex2 += (inYinc * _gridSize);
337 // System.out.println("To fill corner " + inCornerIndex + ", looking at indexes " + sIndex1 + " and " + sIndex2);
340 DataPoint source1 = inTerrainTrack.getPoint(sIndex1);
341 if (source1 != null && source1.hasAltitude()) {alt1 = source1.getAltitude();}
345 DataPoint source2 = inTerrainTrack.getPoint(sIndex2);
346 if (source2 != null && source2.hasAltitude()) {alt2 = source2.getAltitude();}
348 // Can we average these?
349 if (alt1 != null && alt2 != null)
351 // System.out.println("Averaging values " + alt1.getMetricValue() + " and " + alt2.getMetricValue());
352 int newAltitude = (int) ((alt1.getMetricValue() + alt2.getMetricValue()) / 2.0);
353 corner.setFieldValue(Field.ALTITUDE, "" + newAltitude, false);
359 * Try to fix bigger holes by interpolating between neighbours
360 * @param inTerrainTrack terrain track
362 private void fixBiggerHoles(Track inTerrainTrack)
364 double[] altitudes = new double[inTerrainTrack.getNumPoints()];
365 for (int i=0; i<_gridSize; i++)
367 int prevHoriz = -1, prevVert = -1;
368 for (int j=0; j<_gridSize; j++)
370 if (inTerrainTrack.getPoint(i * _gridSize + j).hasAltitude())
372 if (prevHoriz > -1 && prevHoriz != (j-1))
374 // System.out.println("Found a gap for y=" + i +" between x=" + prevHoriz + " and " + j + " (" + (j-prevHoriz-1) + ")");
375 double startVal = inTerrainTrack.getPoint(i * _gridSize + prevHoriz).getAltitude().getMetricValue();
376 double endVal = inTerrainTrack.getPoint(i * _gridSize + j).getAltitude().getMetricValue();
377 for (int k=prevHoriz + 1; k< j; k++)
379 double val = startVal + (k-prevHoriz) * (endVal-startVal) / (j-prevHoriz);
380 if (altitudes[i * _gridSize + k] > 0.0) {
381 altitudes[i * _gridSize + k] = (altitudes[i * _gridSize + k] + val) / 2.0;
384 altitudes[i * _gridSize + k] = val;
390 if (inTerrainTrack.getPoint(j * _gridSize + i).hasAltitude())
392 if (prevVert > -1 && prevVert != (j-1))
394 // System.out.println("Found a gap for x=" + i +" between y=" + prevVert + " and " + j + " (" + (j-prevVert-1) + ")");
395 double startVal = inTerrainTrack.getPoint(prevVert * _gridSize + i).getAltitude().getMetricValue();
396 double endVal = inTerrainTrack.getPoint(j * _gridSize + i).getAltitude().getMetricValue();
397 for (int k=prevVert + 1; k< j; k++)
399 double val = startVal + (k-prevVert) * (endVal-startVal) / (j-prevVert);
400 if (altitudes[k * _gridSize + i] > 0.0) {
401 altitudes[k * _gridSize + i] = (altitudes[k * _gridSize + i] + val) / 2.0;
404 altitudes[k * _gridSize + i] = val;
412 // Now the doubles have been set and/or averaged, we can set the values in the points
413 for (int i=0; i<inTerrainTrack.getNumPoints(); i++)
415 DataPoint p = inTerrainTrack.getPoint(i);
416 if (!p.hasAltitude() && altitudes[i] > 0.0)
418 p.setFieldValue(Field.ALTITUDE, "" + altitudes[i], false);
419 p.getAltitude().reset(new Altitude((int) altitudes[i], UnitSetLibrary.UNITS_METRES));