package org.osm2world.core.map_elevation.creation; import static java.lang.Double.isNaN; import static java.lang.Math.*; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import org.osm2world.core.map_data.creation.MapProjection; import org.osm2world.core.map_data.data.MapData; import org.osm2world.core.map_data.data.MapNode; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; /** * SRTM data for a part of the planet */ public class SRTMData implements TerrainElevationData { private final File tileDirectory; private final MapProjection projection; private final SRTMTile[][] tiles; public SRTMData(File tileDirectory, MapProjection projection) { this.tileDirectory = tileDirectory; this.projection = projection; this.tiles = new SRTMTile[360][180]; } @Override public Collection<VectorXYZ> getSites(double minLon, double minLat, double maxLon, double maxLat) throws IOException { Collection<VectorXYZ> result = new ArrayList<VectorXYZ>(); int minLonInt = (int)floor(minLon); int minLatInt = (int)floor(minLat); int maxLonInt = (int)ceil(maxLon); int maxLatInt = (int)ceil(maxLat); for (int lon = minLonInt; lon < maxLonInt; lon++) { for (int lat = minLatInt; lat < maxLatInt; lat++) { loadTileIfNecessary(lon, lat); addTileSites(result, lon, lat, minLon, minLat, maxLon, maxLat); } } return result; } /** * variant of getSites which calculates minimum and maximum lat/lon * from the bounds of a {@link MapData} instance * * TODO: make projection reversible, then replace both getSites methods * with a single getSite(AxisAlignedBoundingBox dataBounds) method */ @Override public Collection<VectorXYZ> getSites(MapData mapData) throws IOException { double minLon = Double.POSITIVE_INFINITY; double minLat = Double.POSITIVE_INFINITY; double maxLon = Double.NEGATIVE_INFINITY; double maxLat = Double.NEGATIVE_INFINITY; /* find the minimum and maximum lat/lon in the data */ for (MapNode mapNode : mapData.getMapNodes()) { double lon = mapNode.getOsmNode().lon; double lat = mapNode.getOsmNode().lat; if (!isNaN(lat) && !isNaN(lon)) { minLon = min(minLon, lon); minLat = min(minLat, lat); maxLon = max(maxLon, lon); maxLat = max(maxLat, lat); } } /* add a small seam for robustness */ minLon -= 0.02; minLat -= 0.02; maxLon += 0.02; maxLat += 0.02; /* TODO: the seam could be smaller - such as this - if empty terrain nodes did have lat/lon minLon -= 0.005; minLat -= 0.005; maxLon += 0.005; maxLat += 0.005; */ /* retrieve the sites for the query */ return getSites(minLon, minLat, maxLon, maxLat); } private void loadTileIfNecessary(int lon, int lat) throws IOException { if (getTile(lon, lat) == null) { String fileName = tileDirectory.getPath() + File.separator; if (lat >= 0) { fileName += String.format("N%02d", lat); } else { fileName += String.format("S%02d", -lat); } if (lon >= 0) { fileName += String.format("E%03d", lon); } else { fileName += String.format("W%03d", -lon); } fileName += ".hgt"; File file = new File(fileName); if (file.exists()) { setTile(lon, lat, new SRTMTile(file)); } else { System.err.println("warning: missing SRTM tile " + file.getName()); } } } private void addTileSites(Collection<VectorXYZ> result, int tileLon, int tileLat, double minLon, double minLat, double maxLon, double maxLat) { SRTMTile tile = getTile(tileLon, tileLat); if (tile == null) return; /* add a site for each SRTM pixel (except last line and column, * which is duplicated in adjacent tiles) */ int minX = max(0, (int)ceil(SRTMTile.PIXELS * (minLon - tileLon))); int maxX = min(SRTMTile.PIXELS - 1, (int)floor(SRTMTile.PIXELS * (maxLon - tileLon))); int minY = max(0, (int)ceil(SRTMTile.PIXELS * (minLat - tileLat))); int maxY = min(SRTMTile.PIXELS - 1, (int)floor(SRTMTile.PIXELS * (maxLat - tileLat))); for (int x = minX; x < maxX; x++) { for (int y = minY; y < maxY; y++) { short value = tile.getData(x, y); double lat = tileLat + 1.0 / SRTMTile.PIXELS * (y + 0.5); double lon = tileLon + 1.0 / SRTMTile.PIXELS * (x + 0.5); VectorXZ pos = projection.calcPos(lat, lon); if (value != SRTMTile.BLANK_VALUE && !Double.isNaN(pos.x) && !Double.isNaN(pos.z)) { result.add(pos.xyz(value)); } } } } private SRTMTile getTile(int tileLon, int tileLat) { return tiles[tileLon+180][tileLat+90]; } private void setTile(int tileLon, int tileLat, SRTMTile tile) { tiles[tileLon+180][tileLat+90] = tile; } }