/* Copyright (C) 2001, 2006 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. */ package gov.nasa.worldwind.layers; import com.sun.opengl.util.j2d.*; import gov.nasa.worldwind.*; import gov.nasa.worldwind.geom.*; import gov.nasa.worldwind.globes.*; import gov.nasa.worldwind.render.*; import gov.nasa.worldwind.retrieve.*; import gov.nasa.worldwind.util.*; import javax.imageio.*; import javax.media.opengl.*; import java.awt.*; import java.awt.geom.*; import java.awt.image.*; import java.io.*; import java.net.*; import java.nio.*; import java.util.*; import java.util.concurrent.*; /** * @author tag * @version $Id: TiledImageLayer.java 5102 2008-04-21 05:41:05Z tgaskins $ */ public abstract class TiledImageLayer extends AbstractLayer { // Infrastructure private static final LevelComparer levelComparer = new LevelComparer(); private final LevelSet levels; private ArrayList<TextureTile> topLevels; private boolean forceLevelZeroLoads = false; private boolean levelZeroLoaded = false; private boolean retainLevelZeroTiles = false; private String tileCountName; private double splitScale = 0.9; // TODO: Make configurable private boolean useMipMaps = false; private ArrayList<String> supportedImageFormats = new ArrayList<String>(); // Diagnostic flags private boolean showImageTileOutlines = false; private boolean drawTileBoundaries = false; private boolean useTransparentTextures = false; private boolean drawTileIDs = false; private boolean drawBoundingVolumes = false; private TextRenderer textRenderer = null; // Stuff computed each frame private ArrayList<TextureTile> currentTiles = new ArrayList<TextureTile>(); private TextureTile currentResourceTile; private Vec4 referencePoint; private boolean atMaxResolution = false; private PriorityBlockingQueue<Runnable> requestQ = new PriorityBlockingQueue<Runnable>(200); abstract protected void requestTexture(DrawContext dc, TextureTile tile); abstract protected void forceTextureLoad(TextureTile tile); public TiledImageLayer(LevelSet levelSet) { if (levelSet == null) { String message = Logging.getMessage("nullValue.LevelSetIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.levels = new LevelSet(levelSet); // the caller's levelSet may change internally, so we copy it. this.createTopLevelTiles(); this.setPickEnabled(false); // textures are assumed to be terrain unless specifically indicated otherwise. this.tileCountName = this.getName() + " Tiles"; } @Override public void setName(String name) { super.setName(name); this.tileCountName = this.getName() + " Tiles"; } public boolean isUseTransparentTextures() { return this.useTransparentTextures; } public void setUseTransparentTextures(boolean useTransparentTextures) { this.useTransparentTextures = useTransparentTextures; } public boolean isForceLevelZeroLoads() { return this.forceLevelZeroLoads; } public void setForceLevelZeroLoads(boolean forceLevelZeroLoads) { this.forceLevelZeroLoads = forceLevelZeroLoads; } public boolean isRetainLevelZeroTiles() { return retainLevelZeroTiles; } public void setRetainLevelZeroTiles(boolean retainLevelZeroTiles) { this.retainLevelZeroTiles = retainLevelZeroTiles; } public boolean isDrawTileIDs() { return drawTileIDs; } public void setDrawTileIDs(boolean drawTileIDs) { this.drawTileIDs = drawTileIDs; } public boolean isDrawTileBoundaries() { return drawTileBoundaries; } public void setDrawTileBoundaries(boolean drawTileBoundaries) { this.drawTileBoundaries = drawTileBoundaries; } public boolean isShowImageTileOutlines() { return showImageTileOutlines; } public void setShowImageTileOutlines(boolean showImageTileOutlines) { this.showImageTileOutlines = showImageTileOutlines; } public boolean isDrawBoundingVolumes() { return drawBoundingVolumes; } public void setDrawBoundingVolumes(boolean drawBoundingVolumes) { this.drawBoundingVolumes = drawBoundingVolumes; } protected LevelSet getLevels() { return levels; } protected void setSplitScale(double splitScale) { this.splitScale = splitScale; } protected PriorityBlockingQueue<Runnable> getRequestQ() { return requestQ; } public boolean isMultiResolution() { return this.getLevels() != null && this.getLevels().getNumLevels() > 1; } public boolean isAtMaxResolution() { return this.atMaxResolution; } public boolean isUseMipMaps() { return useMipMaps; } public void setUseMipMaps(boolean useMipMaps) { this.useMipMaps = useMipMaps; } private void createTopLevelTiles() { Sector sector = this.levels.getSector(); Angle dLat = this.levels.getLevelZeroTileDelta().getLatitude(); Angle dLon = this.levels.getLevelZeroTileDelta().getLongitude(); // Determine the row and column offset from the common World Wind global tiling origin. Level level = levels.getFirstLevel(); int firstRow = Tile.computeRow(level.getTileDelta().getLatitude(), sector.getMinLatitude()); int firstCol = Tile.computeColumn(level.getTileDelta().getLongitude(), sector.getMinLongitude()); int lastRow = Tile.computeRow(level.getTileDelta().getLatitude(), sector.getMaxLatitude()); int lastCol = Tile.computeColumn(level.getTileDelta().getLongitude(), sector.getMaxLongitude()); int nLatTiles = lastRow - firstRow + 1; int nLonTiles = lastCol - firstCol + 1; this.topLevels = new ArrayList<TextureTile>(nLatTiles * nLonTiles); Angle p1 = Tile.computeRowLatitude(firstRow, dLat); for (int row = firstRow; row <= lastRow; row++) { Angle p2; p2 = p1.add(dLat); Angle t1 = Tile.computeColumnLongitude(firstCol, dLon); for (int col = firstCol; col <= lastCol; col++) { Angle t2; t2 = t1.add(dLon); this.topLevels.add(new TextureTile(new Sector(p1, p2, t1, t2), level, row, col)); t1 = t2; } p1 = p2; } } private void loadAllTopLevelTextures(DrawContext dc) { for (TextureTile tile : this.topLevels) { if (!tile.isTextureInMemory(dc.getTextureCache())) this.forceTextureLoad(tile); } this.levelZeroLoaded = true; } // ============== Tile Assembly ======================= // // ============== Tile Assembly ======================= // // ============== Tile Assembly ======================= // private void assembleTiles(DrawContext dc) { this.currentTiles.clear(); for (TextureTile tile : this.topLevels) { if (this.isTileVisible(dc, tile)) { this.currentResourceTile = null; this.addTileOrDescendants(dc, tile); } } } private void addTileOrDescendants(DrawContext dc, TextureTile tile) { if (this.meetsRenderCriteria(dc, tile)) { this.addTile(dc, tile); return; } // The incoming tile does not meet the rendering criteria, so it must be subdivided and those // subdivisions tested against the criteria. // All tiles that meet the selection criteria are drawn, but some of those tiles will not have // textures associated with them either because their texture isn't loaded yet or because they // are finer grain than the layer has textures for. In these cases the tiles use the texture of // the closest ancestor that has a texture loaded. This ancestor is called the currentResourceTile. // A texture transform is applied during rendering to align the sector's texture coordinates with the // appropriate region of the ancestor's texture. TextureTile ancestorResource = null; try { // TODO: Revise this to reflect that the parent layer is only requested while the algorithm continues // to search for the layer matching the criteria. // At this point the tile does not meet the render criteria but it may have its texture in memory. // If so, register this tile as the resource tile. If not, then this tile will be the next level // below a tile with texture in memory. So to provide progressive resolution increase, add this tile // to the draw list. That will cause the tile to be drawn using its parent tile's texture, and it will // cause it's texture to be requested. At some future call to this method the tile's texture will be in // memory, it will not meet the render criteria, but will serve as the parent to a tile that goes // through this same process as this method recurses. The result of all this is that a tile isn't rendered // with its own texture unless all its parents have their textures loaded. In addition to causing // progressive resolution increase, this ensures that the parents are available as the user zooms out, and // therefore the layer remains visible until the user is zoomed out to the point the layer is no longer // active. if (tile.isTextureInMemory(dc.getTextureCache()) || tile.getLevelNumber() == 0) { ancestorResource = this.currentResourceTile; this.currentResourceTile = tile; } else if (!tile.getLevel().isEmpty()) { // this.addTile(dc, tile); // return; // Issue a request for the parent before descending to the children. if (tile.getLevelNumber() < this.levels.getNumLevels()) { // Request only tiles with data associated at this level if (!this.levels.isResourceAbsent(tile)) this.requestTexture(dc, tile); } } TextureTile[] subTiles = tile.createSubTiles(this.levels.getLevel(tile.getLevelNumber() + 1)); for (TextureTile child : subTiles) { if (this.isTileVisible(dc, child)) this.addTileOrDescendants(dc, child); } } finally { if (ancestorResource != null) // Pop this tile as the currentResource ancestor this.currentResourceTile = ancestorResource; } } private void addTile(DrawContext dc, TextureTile tile) { tile.setFallbackTile(null); if (tile.isTextureInMemory(dc.getTextureCache())) { // System.out.printf("Sector %s, min = %f, max = %f\n", tile.getSector(), // dc.getGlobe().getMinElevation(tile.getSector()), dc.getGlobe().getMaxElevation(tile.getSector())); this.addTileToCurrent(tile); return; } // Level 0 loads may be forced if (tile.getLevelNumber() == 0 && this.forceLevelZeroLoads && !tile.isTextureInMemory(dc.getTextureCache())) { this.forceTextureLoad(tile); if (tile.isTextureInMemory(dc.getTextureCache())) { this.addTileToCurrent(tile); return; } } // Tile's texture isn't available, so request it if (tile.getLevelNumber() < this.levels.getNumLevels()) { // Request only tiles with data associated at this level if (!this.levels.isResourceAbsent(tile)) this.requestTexture(dc, tile); } // Set up to use the currentResource tile's texture if (this.currentResourceTile != null) { if (this.currentResourceTile.getLevelNumber() == 0 && this.forceLevelZeroLoads && !this.currentResourceTile.isTextureInMemory(dc.getTextureCache()) && !this.currentResourceTile.isTextureInMemory(dc.getTextureCache())) this.forceTextureLoad(this.currentResourceTile); if (this.currentResourceTile.isTextureInMemory(dc.getTextureCache())) { tile.setFallbackTile(currentResourceTile); this.addTileToCurrent(tile); } } } private void addTileToCurrent(TextureTile tile) { this.currentTiles.add(tile); } private boolean isTileVisible(DrawContext dc, TextureTile tile) { // if (!(tile.getExtent(dc).intersects(dc.getView().getFrustumInModelCoordinates()) // && (dc.getVisibleSector() == null || dc.getVisibleSector().intersects(tile.getSector())))) // return false; // // Position eyePos = dc.getView().getEyePosition(); // LatLon centroid = tile.getSector().getCentroid(); // Angle d = LatLon.greatCircleDistance(eyePos.getLatLon(), centroid); // if ((!tile.getLevelName().equals("0")) && d.compareTo(tile.getSector().getDeltaLat().multiply(2.5)) == 1) // return false; // // return true; // return tile.getExtent(dc).intersects(dc.getView().getFrustumInModelCoordinates()) && (dc.getVisibleSector() == null || dc.getVisibleSector().intersects(tile.getSector())); } // // private boolean meetsRenderCriteria2(DrawContext dc, TextureTile tile) // { // if (this.levels.isFinalLevel(tile.getLevelNumber())) // return true; // // Sector sector = tile.getSector(); // Vec4[] corners = sector.computeCornerPoints(dc.getGlobe()); // Vec4 centerPoint = sector.computeCenterPoint(dc.getGlobe()); // // View view = dc.getView(); // double d1 = view.getEyePoint().distanceTo3(corners[0]); // double d2 = view.getEyePoint().distanceTo3(corners[1]); // double d3 = view.getEyePoint().distanceTo3(corners[2]); // double d4 = view.getEyePoint().distanceTo3(corners[3]); // double d5 = view.getEyePoint().distanceTo3(centerPoint); // // double minDistance = d1; // if (d2 < minDistance) // minDistance = d2; // if (d3 < minDistance) // minDistance = d3; // if (d4 < minDistance) // minDistance = d4; // if (d5 < minDistance) // minDistance = d5; // // double r = 0; // if (minDistance == d1) // r = corners[0].getLength3(); // if (minDistance == d2) // r = corners[1].getLength3(); // if (minDistance == d3) // r = corners[2].getLength3(); // if (minDistance == d4) // r = corners[3].getLength3(); // if (minDistance == d5) // r = centerPoint.getLength3(); // // double texelSize = tile.getLevel().getTexelSize(r); // double pixelSize = dc.getView().computePixelSizeAtDistance(minDistance); // // return 2 * pixelSize >= texelSize; // } private boolean meetsRenderCriteria(DrawContext dc, TextureTile tile) { return this.levels.isFinalLevel(tile.getLevelNumber()) || !needToSplit(dc, tile.getSector()); } private boolean needToSplit(DrawContext dc, Sector sector) { Vec4[] corners = sector.computeCornerPoints(dc.getGlobe()); Vec4 centerPoint = sector.computeCenterPoint(dc.getGlobe()); View view = dc.getView(); double d1 = view.getEyePoint().distanceTo3(corners[0]); double d2 = view.getEyePoint().distanceTo3(corners[1]); double d3 = view.getEyePoint().distanceTo3(corners[2]); double d4 = view.getEyePoint().distanceTo3(corners[3]); double d5 = view.getEyePoint().distanceTo3(centerPoint); double minDistance = d1; if (d2 < minDistance) minDistance = d2; if (d3 < minDistance) minDistance = d3; if (d4 < minDistance) minDistance = d4; if (d5 < minDistance) minDistance = d5; double cellSize = (Math.PI * sector.getDeltaLatRadians() * dc.getGlobe().getRadius()) / 20; // TODO return !(Math.log10(cellSize) <= (Math.log10(minDistance) - this.splitScale)); } private boolean atMaxLevel(DrawContext dc) { Position vpc = dc.getViewportCenterPosition(); if (dc.getView() == null || this.getLevels() == null || vpc == null) return false; if (!this.getLevels().getSector().contains(vpc.getLatitude(), vpc.getLongitude())) return true; Level nextToLast = this.getLevels().getNextToLastLevel(); if (nextToLast == null) return true; Sector centerSector = nextToLast.computeSectorForPosition(vpc.getLatitude(), vpc.getLongitude()); return this.needToSplit(dc, centerSector); } // ============== Rendering ======================= // // ============== Rendering ======================= // // ============== Rendering ======================= // @Override public void render(DrawContext dc) { this.atMaxResolution = this.atMaxLevel(dc); super.render(dc); } @Override protected final void doRender(DrawContext dc) { if (this.forceLevelZeroLoads && !this.levelZeroLoaded) this.loadAllTopLevelTextures(dc); if (dc.getSurfaceGeometry() == null || dc.getSurfaceGeometry().size() < 1) return; dc.getGeographicSurfaceTileRenderer().setShowImageTileOutlines(this.showImageTileOutlines); draw(dc); } private void draw(DrawContext dc) { this.referencePoint = this.computeReferencePoint(dc); this.assembleTiles(dc); // Determine the tiles to draw. if (this.currentTiles.size() >= 1) { TextureTile[] sortedTiles = new TextureTile[this.currentTiles.size()]; sortedTiles = this.currentTiles.toArray(sortedTiles); Arrays.sort(sortedTiles, levelComparer); GL gl = dc.getGL(); if (this.isUseTransparentTextures() || this.getOpacity() < 1) { gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_POLYGON_BIT | GL.GL_CURRENT_BIT); gl.glColor4d(1d, 1d, 1d, this.getOpacity()); gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); } else { gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_POLYGON_BIT); } gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL); gl.glEnable(GL.GL_CULL_FACE); gl.glCullFace(GL.GL_BACK); dc.setPerFrameStatistic(PerformanceStatistic.IMAGE_TILE_COUNT, this.tileCountName, this.currentTiles.size()); dc.getGeographicSurfaceTileRenderer().renderTiles(dc, this.currentTiles); gl.glPopAttrib(); if (this.drawTileIDs) this.drawTileIDs(dc, this.currentTiles); if (this.drawBoundingVolumes) this.drawBoundingVolumes(dc, this.currentTiles); this.currentTiles.clear(); } this.sendRequests(); this.requestQ.clear(); } private void sendRequests() { Runnable task = this.requestQ.poll(); while (task != null) { if (!WorldWind.getTaskService().isFull()) { WorldWind.getTaskService().addTask(task); } task = this.requestQ.poll(); } } public boolean isLayerInView(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } if (dc.getView() == null) { String message = Logging.getMessage("layers.AbstractLayer.NoViewSpecifiedInDrawingContext"); Logging.logger().severe(message); throw new IllegalStateException(message); } return !(dc.getVisibleSector() != null && !this.levels.getSector().intersects(dc.getVisibleSector())); } private Vec4 computeReferencePoint(DrawContext dc) { if (dc.getViewportCenterPosition() != null) return dc.getGlobe().computePointFromPosition(dc.getViewportCenterPosition()); java.awt.geom.Rectangle2D viewport = dc.getView().getViewport(); int x = (int) viewport.getWidth() / 2; for (int y = (int) (0.5 * viewport.getHeight()); y >= 0; y--) { Position pos = dc.getView().computePositionFromScreenPoint(x, y); if (pos == null) continue; return dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), 0d); } return null; } protected Vec4 getReferencePoint() { return this.referencePoint; } private static class LevelComparer implements Comparator<TextureTile> { public int compare(TextureTile ta, TextureTile tb) { int la = ta.getFallbackTile() == null ? ta.getLevelNumber() : ta.getFallbackTile().getLevelNumber(); int lb = tb.getFallbackTile() == null ? tb.getLevelNumber() : tb.getFallbackTile().getLevelNumber(); return la < lb ? -1 : la == lb ? 0 : 1; } } private void drawTileIDs(DrawContext dc, ArrayList<TextureTile> tiles) { java.awt.Rectangle viewport = dc.getView().getViewport(); if (this.textRenderer == null) { this.textRenderer = new TextRenderer(java.awt.Font.decode("Arial-Plain-13"), true, true); this.textRenderer.setUseVertexArrays(false); } dc.getGL().glDisable(GL.GL_DEPTH_TEST); dc.getGL().glDisable(GL.GL_BLEND); dc.getGL().glDisable(GL.GL_TEXTURE_2D); this.textRenderer.setColor(java.awt.Color.YELLOW); this.textRenderer.beginRendering(viewport.width, viewport.height); for (TextureTile tile : tiles) { String tileLabel = tile.getLabel(); if (tile.getFallbackTile() != null) tileLabel += "/" + tile.getFallbackTile().getLabel(); LatLon ll = tile.getSector().getCentroid(); Vec4 pt = dc.getGlobe().computePointFromPosition(ll.getLatitude(), ll.getLongitude(), dc.getGlobe().getElevation(ll.getLatitude(), ll.getLongitude())); pt = dc.getView().project(pt); this.textRenderer.draw(tileLabel, (int) pt.x, (int) pt.y); } this.textRenderer.endRendering(); } private void drawBoundingVolumes(DrawContext dc, ArrayList<TextureTile> tiles) { float[] previousColor = new float[4]; dc.getGL().glGetFloatv(GL.GL_CURRENT_COLOR, previousColor, 0); dc.getGL().glColor3d(0, 1, 0); for (TextureTile tile : tiles) { ((Cylinder) tile.getExtent(dc)).render(dc); } Cylinder c = dc.getGlobe().computeBoundingCylinder(dc.getVerticalExaggeration(), this.levels.getSector()); dc.getGL().glColor3d(1, 1, 0); c.render(dc); dc.getGL().glColor4fv(previousColor, 0); } // ============== Image Composition ======================= // // ============== Image Composition ======================= // // ============== Image Composition ======================= // public ArrayList<String> getAvailableImageFormats() { return new ArrayList<String>(this.supportedImageFormats); } public boolean isImageFormatAvailable(String imageFormat) { return imageFormat != null && this.supportedImageFormats.contains(imageFormat); } public String getDefaultImageFormat() { return this.supportedImageFormats.size() > 0 ? this.supportedImageFormats.get(0) : null; } protected void setAvailableImageFormats(String[] formats) { this.supportedImageFormats.clear(); if (formats != null) { for (String format : formats) this.supportedImageFormats.add(format); } } private BufferedImage requestImage(TextureTile tile, String mimeType) throws URISyntaxException { String pathBase = tile.getPath().substring(0, tile.getPath().lastIndexOf(".")); String suffix = WWIO.makeSuffixForMimeType(mimeType); String path = pathBase + suffix; URL url = WorldWind.getDataFileCache().findFile(path, false); if (url == null) // image is not local return null; if (WWIO.isFileOutOfDate(url, tile.getLevel().getExpiryTime())) { // The file has expired. Delete it. WorldWind.getDataFileCache().removeFile(url); String message = Logging.getMessage("generic.DataFileExpired", url); Logging.logger().fine(message); } else { try { File imageFile = new File(url.toURI()); BufferedImage image = ImageIO.read(imageFile); if (image == null) { String message = Logging.getMessage("generic.ImageReadFailed", imageFile); throw new RuntimeException(message); } this.levels.unmarkResourceAbsent(tile); return image; } catch (IOException e) { // Assume that something's wrong with the file and delete it. gov.nasa.worldwind.WorldWind.getDataFileCache().removeFile(url); this.levels.markResourceAbsent(tile); String message = Logging.getMessage("generic.DeletedCorruptDataFile", url); Logging.logger().info(message); } } return null; } private void downloadImage(final TextureTile tile, String mimeType) throws Exception { // System.out.println(tile.getPath()); final URL resourceURL = tile.getResourceURL(mimeType); Retriever retriever; String protocol = resourceURL.getProtocol(); if ("http".equalsIgnoreCase(protocol)) { retriever = new HTTPRetriever(resourceURL, new HttpRetrievalPostProcessor(tile)); } else { String message = Logging.getMessage("layers.TextureLayer.UnknownRetrievalProtocol", resourceURL); throw new RuntimeException(message); } retriever.setConnectTimeout(10000); retriever.setReadTimeout(20000); retriever.call(); } public int computeLevelForResolution(Sector sector, Globe globe, double resolution) { if (sector == null) { String message = Logging.getMessage("nullValue.SectorIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } if (globe == null) { String message = Logging.getMessage("nullValue.GlobeIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } double texelSize = 0; Level targetLevel = this.levels.getLastLevel(); for (int i = 0; i < this.getLevels().getLastLevel().getLevelNumber(); i++) { if (this.levels.isLevelEmpty(i)) continue; texelSize = this.levels.getLevel(i).getTexelSize(globe.getRadius()); if (texelSize > resolution) continue; targetLevel = this.levels.getLevel(i); break; } Logging.logger() .info(Logging.getMessage("layers.TiledImageLayer.LevelSelection", targetLevel.getLevelNumber(), texelSize)); return targetLevel.getLevelNumber(); } public BufferedImage composeImageForSector(Sector sector, int imageWidth, int imageHeight, int levelNumber, String mimeType, boolean abortOnError, BufferedImage image) { if (sector == null) { String message = Logging.getMessage("nullValue.SectorIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } if (levelNumber < 0) { levelNumber = this.levels.getLastLevel().getLevelNumber(); } else if (levelNumber > this.levels.getLastLevel().getLevelNumber()) { Logging.logger().warning(Logging.getMessage("generic.LevelRequestedGreaterThanMaxLevel", levelNumber, this.levels.getLastLevel().getLevelNumber())); levelNumber = this.levels.getLastLevel().getLevelNumber(); } TextureTile[][] tiles = this.getTilesInSector(sector, levelNumber); if (tiles.length == 0 || tiles[0].length == 0) { Logging.logger().severe(Logging.getMessage("layers.TiledImageLayer.NoImagesAvailable")); return null; } if (image == null) image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); for (TextureTile[] row : tiles) { for (TextureTile tile : row) { if (tile == null) continue; BufferedImage tileImage; try { tileImage = this.getImage(tile, mimeType); double sh = ((double) imageHeight / (double) tileImage.getHeight()) * (tile.getSector().getDeltaLat().divide(sector.getDeltaLat())); double sw = ((double) imageWidth / (double) tileImage.getWidth()) * (tile.getSector().getDeltaLon().divide(sector.getDeltaLon())); double dh = imageHeight * (-tile.getSector().getMaxLatitude().subtract(sector.getMaxLatitude()).degrees / sector.getDeltaLat().degrees); double dw = imageWidth * (tile.getSector().getMinLongitude().subtract(sector.getMinLongitude()).degrees / sector.getDeltaLon().degrees); AffineTransform txf = g.getTransform(); g.translate(dw, dh); g.scale(sw, sh); g.drawImage(tileImage, 0, 0, null); g.setTransform(txf); } catch (Exception e) { if (abortOnError) throw new RuntimeException(e); String message = Logging.getMessage("generic.ExceptionWhileRequestingImage", tile.getPath()); Logging.logger().log(java.util.logging.Level.WARNING, message, e); } } } return image; } public int countImagesInSector(Sector sector, int levelNumber) { if (sector == null) { String msg = Logging.getMessage("nullValue.SectorIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } Level targetLevel = this.levels.getLastLevel(); if (levelNumber >= 0) { for (int i = levelNumber; i < this.getLevels().getLastLevel().getLevelNumber(); i++) { if (this.levels.isLevelEmpty(i)) continue; targetLevel = this.levels.getLevel(i); break; } } // Collect all the tiles intersecting the input sector. LatLon delta = targetLevel.getTileDelta(); final int nwRow = Tile.computeRow(delta.getLatitude(), sector.getMaxLatitude()); final int nwCol = Tile.computeColumn(delta.getLongitude(), sector.getMinLongitude()); final int seRow = Tile.computeRow(delta.getLatitude(), sector.getMinLatitude()); final int seCol = Tile.computeColumn(delta.getLongitude(), sector.getMaxLongitude()); int numRows = nwRow - seRow + 1; int numCols = seCol - nwCol + 1; return numRows * numCols; } private TextureTile[][] getTilesInSector(Sector sector, int levelNumber) { if (sector == null) { String msg = Logging.getMessage("nullValue.SectorIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } Level targetLevel = this.levels.getLastLevel(); if (levelNumber >= 0) { for (int i = levelNumber; i < this.getLevels().getLastLevel().getLevelNumber(); i++) { if (this.levels.isLevelEmpty(i)) continue; targetLevel = this.levels.getLevel(i); break; } } // Collect all the tiles intersecting the input sector. LatLon delta = targetLevel.getTileDelta(); final int nwRow = Tile.computeRow(delta.getLatitude(), sector.getMaxLatitude()); final int nwCol = Tile.computeColumn(delta.getLongitude(), sector.getMinLongitude()); final int seRow = Tile.computeRow(delta.getLatitude(), sector.getMinLatitude()); final int seCol = Tile.computeColumn(delta.getLongitude(), sector.getMaxLongitude()); int numRows = nwRow - seRow + 1; int numCols = seCol - nwCol + 1; TextureTile[][] sectorTiles = new TextureTile[numRows][numCols]; for (int row = nwRow; row >= seRow; row--) { for (int col = nwCol; col <= seCol; col++) { TileKey key = new TileKey(targetLevel.getLevelNumber(), row, col, targetLevel.getCacheName()); Sector tileSector = this.levels.computeSectorForKey(key); sectorTiles[nwRow - row][col - nwCol] = new TextureTile(tileSector, targetLevel, row, col); } } return sectorTiles; } private BufferedImage getImage(TextureTile tile, String mimeType) throws Exception { // Read the image from disk. BufferedImage image = this.requestImage(tile, mimeType); if (image != null) return image; // Retrieve it from the net since it's not on disk. this.downloadImage(tile, mimeType); // Try to read from disk again after retrieving it from the net. image = this.requestImage(tile, mimeType); if (image == null) { String message = Logging.getMessage("layers.TiledImageLayer.ImageUnavailable", tile.getPath()); throw new RuntimeException(message); } return image; } private class HttpRetrievalPostProcessor implements RetrievalPostProcessor { private TextureTile tile; public HttpRetrievalPostProcessor(TextureTile tile) { this.tile = tile; } public ByteBuffer run(Retriever retriever) { if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL)) return null; HTTPRetriever htr = (HTTPRetriever) retriever; if (htr.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT) { // Mark tile as missing to avoid excessive attempts TiledImageLayer.this.levels.markResourceAbsent(tile); return null; } if (htr.getResponseCode() != HttpURLConnection.HTTP_OK) return null; URLRetriever r = (URLRetriever) retriever; ByteBuffer buffer = r.getBuffer(); String suffix = WWIO.makeSuffixForMimeType(htr.getContentType()); if (suffix == null) { return null; // TODO: log error } String path = tile.getPath().substring(0, tile.getPath().lastIndexOf(".")); path += suffix; final File outFile = WorldWind.getDataFileCache().newFile(path); if (outFile == null) return null; try { WWIO.saveBuffer(buffer, outFile); return buffer; } catch (IOException e) { e.printStackTrace(); // TODO: log error return null; } } } }