package tim.prune.function.srtm; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.IOException; import java.net.URL; import java.net.HttpURLConnection; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import tim.prune.GpsPrune; /** * Abstract class implemented by each download source */ public abstract class SrtmSource { /** * Name of the source * A string used to build function.downloadsrtm.getName() for getting * the string shown to the user in menus and messages, also used to * build cache directory */ public abstract String getName(); /** * Whether the source is ready to use * @return true, or false if configuration is necessary */ public abstract boolean isReadyToUse(); /** * Download one tile of SRTM data to cache * @param inTile the tile to get */ public abstract boolean downloadTile(SrtmTile inTile) throws SrtmSourceException; /** * Returns the number of rows of the tile * * Tiles from viewfinderpanoramas have different resolutions depending * on the location. * @param inTile the tile whose size we want * @return number of rows; 1201 or 3601 in current implementation */ public abstract int getRowSize(SrtmTile inTile); /** * Whether the tile is cached * @return true if the tile is in the cache and ready to be used */ public boolean isCached(SrtmTile inTile) { File cachedFileName = getCacheFileName(inTile); return cachedFileName != null && getCacheFileName(inTile).exists(); } /** * Get the heigths for that tile * * @return an one-dimentional array of size {@link getRowSize ** 2} * @throws SrtmSourceException notably if the tile is not cached yet */ public int[] getTileHeights(SrtmTile inTile) throws SrtmSourceException { File cacheFileName = getCacheFileName(inTile); if (cacheFileName == null) { throw new SrtmSourceException("Tile "+inTile.getTileName()+" not in cache"); } try { ZipInputStream inStream = new ZipInputStream(new FileInputStream(cacheFileName)); ZipEntry entry = inStream.getNextEntry(); int rowSize = getRowSize(inTile); int tileSize = rowSize * rowSize; if (entry.getSize() != 2 * tileSize) { throw new SrtmSourceException("Tile file "+cacheFileName+" does not have the expected size"); } return slurpTileHeigths(inStream, tileSize); } catch (IOException e) { throw new SrtmSourceException("Failure opening "+cacheFileName+" for reading:"+e.getMessage()); } } // helpers used internally by the client classes protected abstract String getSourceExtension(); /** * Returns the cache directory for tiles, a sub-directory of the map * cache */ protected File getCacheDir() { return SrtmDiskCache.getCacheDir(getName()); } /** * Return the name of the filename for that tile * For viewfinderpanoramas.org tiles, that might be a ZIP file * containing several tiles */ protected File getCacheFileName(SrtmTile inTile) { String fileName = inTile.getTileName() + getSourceExtension(); return new File(getCacheDir(), fileName); } protected InputStream getStreamToUrl(URL inTileUrl) throws SrtmSourceException { try { HttpURLConnection conn = (HttpURLConnection) inTileUrl.openConnection(); conn.setRequestProperty("User-Agent", "GpsPrune v" + GpsPrune.VERSION_NUMBER); int status = conn.getResponseCode(); if (status == 200) { return conn.getInputStream(); } else if (status == 404) { throw new SrtmSourceException("Tile not found: "+conn.getURL()); } else { throw new SrtmSourceException("Invalid response from server: " +status+conn.getContent()); } } catch (IOException e) { throw new SrtmSourceException("Error while downloading tile from "+inTileUrl+": "+e.getMessage()); } } /** * Helper for downloadTile() * @param inStream Stream to read from, typically from HTTP connection * @param outputFile Where to save the data being read */ protected boolean readToFile(InputStream inStream, File outputFile) throws SrtmSourceException { try { FileOutputStream outStream = new FileOutputStream(outputFile); byte[] buffer = new byte[4096]; int read = 0; while ((read = inStream.read(buffer)) != -1) { outStream.write(buffer, 0, read); } // Make sure streams are closed try {inStream.close();} catch (Exception e) {} try {outStream.close();} catch (Exception e) {} return true; } catch (IOException e) { throw new SrtmSourceException("Error downloading tile:" + e.getMessage()); } } /** * Helper for getTileHeights * @param inStream Stream to read from, assumed to have been advanced * to the appropriate tile * @param tileSize the expected tileSize (square of getRowSize; assumed * to have been checked by the caller) * @return the tile heights */ protected int[] slurpTileHeigths(ZipInputStream inStream, int tileSize) throws IOException { int[] heights = new int[tileSize]; int dataSize = 2 * tileSize; byte[] buffer = new byte[dataSize]; // Read entire file contents into one byte array int alreadyRead = 0; while (alreadyRead < dataSize) { alreadyRead += inStream.read(buffer, alreadyRead, dataSize - alreadyRead); } for (int i = 0; i < tileSize; i++) { // Bytes are signed. Cast high-order to int with sign // extension, and clamp low-order to its unsigned range heights[i] = buffer[2 * i] * 256 + (0xff & buffer[2 * i + 1]); } // Close stream inStream.close(); return heights; } }