Updated to worldwind release 20070817
[worldwind-tracker.git] / gov / nasa / worldwind / layers / TiledImageLayer.java
blob3c192295b1382944a9ad8610a7c3fc246a89ec08
1 /*
2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
5 All Rights Reserved.
6 */
7 package gov.nasa.worldwind.layers;
9 import com.sun.opengl.util.j2d.TextRenderer;
10 import gov.nasa.worldwind.WorldWind;
11 import gov.nasa.worldwind.geom.*;
12 import gov.nasa.worldwind.render.DrawContext;
13 import gov.nasa.worldwind.retrieve.*;
14 import gov.nasa.worldwind.util.*;
15 import gov.nasa.worldwind.view.View;
17 import javax.imageio.ImageIO;
18 import javax.media.opengl.GL;
19 import java.awt.*;
20 import java.awt.image.*;
21 import java.io.*;
22 import java.net.*;
23 import java.nio.ByteBuffer;
24 import java.util.*;
25 import java.util.concurrent.PriorityBlockingQueue;
27 /**
28 * @author tag
29 * @version $Id: TiledImageLayer.java 2533 2007-08-13 05:59:08Z tgaskins $
31 public abstract class TiledImageLayer extends AbstractLayer
33 // Infrastructure
34 private static final LevelComparer levelComparer = new LevelComparer();
35 private final LevelSet levels;
36 private ArrayList<TextureTile> topLevels;
37 private boolean forceLevelZeroLoads = false;
38 private boolean levelZeroLoaded = false;
39 private boolean retainLevelZeroTiles = false;
40 private String tileCountName;
42 // Diagnostic flags
43 private boolean showImageTileOutlines = false;
44 private boolean drawTileBoundaries = false;
45 private boolean useTransparentTextures = false;
46 private boolean drawTileIDs = false;
47 private boolean drawBoundingVolumes = false;
48 private TextRenderer textRenderer = null;
50 // Stuff computed each frame
51 private ArrayList<TextureTile> currentTiles = new ArrayList<TextureTile>();
52 private TextureTile currentResourceTile;
53 private Vec4 referencePoint;
54 private PriorityBlockingQueue<Runnable> requestQ = new PriorityBlockingQueue<Runnable>(200);
56 abstract protected void requestTexture(DrawContext dc, TextureTile tile);
58 abstract protected void forceTextureLoad(TextureTile tile);
60 public TiledImageLayer(LevelSet levelSet)
62 if (levelSet == null)
64 String message = Logging.getMessage("nullValue.LevelSetIsNull");
65 Logging.logger().severe(message);
66 throw new IllegalArgumentException(message);
69 this.levels = new LevelSet(levelSet); // the caller's levelSet may change internally, so we copy it.
71 this.createTopLevelTiles();
73 this.setPickEnabled(false); // textures are assumed to be terrain unless specifically indicated otherwise.
74 this.tileCountName = this.getName() + " Tiles";
77 @Override
78 public void setName(String name)
80 super.setName(name);
81 this.tileCountName = this.getName() + " Tiles";
84 public boolean isUseTransparentTextures()
86 return this.useTransparentTextures;
89 public void setUseTransparentTextures(boolean useTransparentTextures)
91 this.useTransparentTextures = useTransparentTextures;
94 public boolean isForceLevelZeroLoads()
96 return this.forceLevelZeroLoads;
99 public void setForceLevelZeroLoads(boolean forceLevelZeroLoads)
101 this.forceLevelZeroLoads = forceLevelZeroLoads;
104 public boolean isRetainLevelZeroTiles()
106 return retainLevelZeroTiles;
109 public void setRetainLevelZeroTiles(boolean retainLevelZeroTiles)
111 this.retainLevelZeroTiles = retainLevelZeroTiles;
114 public boolean isDrawTileIDs()
116 return drawTileIDs;
119 public void setDrawTileIDs(boolean drawTileIDs)
121 this.drawTileIDs = drawTileIDs;
124 public boolean isDrawTileBoundaries()
126 return drawTileBoundaries;
129 public void setDrawTileBoundaries(boolean drawTileBoundaries)
131 this.drawTileBoundaries = drawTileBoundaries;
134 public boolean isShowImageTileOutlines()
136 return showImageTileOutlines;
139 public void setShowImageTileOutlines(boolean showImageTileOutlines)
141 this.showImageTileOutlines = showImageTileOutlines;
144 public boolean isDrawBoundingVolumes()
146 return drawBoundingVolumes;
149 public void setDrawBoundingVolumes(boolean drawBoundingVolumes)
151 this.drawBoundingVolumes = drawBoundingVolumes;
154 protected LevelSet getLevels()
156 return levels;
159 protected PriorityBlockingQueue<Runnable> getRequestQ()
161 return requestQ;
164 private void createTopLevelTiles()
166 Sector sector = this.levels.getSector();
168 Angle dLat = this.levels.getLevelZeroTileDelta().getLatitude();
169 Angle dLon = this.levels.getLevelZeroTileDelta().getLongitude();
171 // Determine the row and column offset from the common World Wind global tiling origin.
172 Level level = levels.getFirstLevel();
173 int firstRow = Tile.computeRow(level.getTileDelta().getLatitude(), sector.getMinLatitude());
174 int firstCol = Tile.computeColumn(level.getTileDelta().getLongitude(), sector.getMinLongitude());
175 int lastRow = Tile.computeRow(level.getTileDelta().getLatitude(), sector.getMaxLatitude());
176 int lastCol = Tile.computeColumn(level.getTileDelta().getLongitude(), sector.getMaxLongitude());
178 int nLatTiles = lastRow - firstRow + 1;
179 int nLonTiles = lastCol - firstCol + 1;
181 this.topLevels = new ArrayList<TextureTile>(nLatTiles * nLonTiles);
183 Angle p1 = Tile.computeRowLatitude(firstRow, dLat);
184 for (int row = firstRow; row <= lastRow; row++)
186 Angle p2;
187 p2 = p1.add(dLat);
189 Angle t1 = Tile.computeColumnLongitude(firstCol, dLon);
190 for (int col = firstCol; col <= lastCol; col++)
192 Angle t2;
193 t2 = t1.add(dLon);
195 this.topLevels.add(new TextureTile(new Sector(p1, p2, t1, t2), level, row, col));
196 t1 = t2;
198 p1 = p2;
202 private void loadAllTopLevelTextures(DrawContext dc)
204 for (TextureTile tile : this.topLevels)
206 if (!tile.isTextureInMemory(dc.getTextureCache()))
207 this.forceTextureLoad(tile);
210 this.levelZeroLoaded = true;
213 // ============== Tile Assembly ======================= //
214 // ============== Tile Assembly ======================= //
215 // ============== Tile Assembly ======================= //
217 private void assembleTiles(DrawContext dc)
219 this.currentTiles.clear();
221 for (TextureTile tile : this.topLevels)
223 if (this.isTileVisible(dc, tile))
225 this.currentResourceTile = null;
226 this.addTileOrDescendants(dc, tile);
231 private void addTileOrDescendants(DrawContext dc, TextureTile tile)
233 if (this.meetsRenderCriteria(dc, tile))
235 this.addTile(dc, tile);
236 return;
239 // The incoming tile does not meet the rendering criteria, so it must be subdivided and those
240 // subdivisions tested against the criteria.
242 // All tiles that meet the selection criteria are drawn, but some of those tiles will not have
243 // textures associated with them either because their texture isn't loaded yet or because they
244 // are finer grain than the layer has textures for. In these cases the tiles use the texture of
245 // the closest ancestor that has a texture loaded. This ancestor is called the currentResourceTile.
246 // A texture transform is applied during rendering to align the sector's texture coordinates with the
247 // appropriate region of the ancestor's texture.
249 TextureTile ancestorResource = null;
253 if (tile.isTextureInMemory(dc.getTextureCache()) || tile.getLevelNumber() == 0)
255 ancestorResource = this.currentResourceTile;
256 this.currentResourceTile = tile;
259 // Ensure that levels finer than the finest image have the finest image around
260 // TODO: find finest level with a non-missing tile
261 if (this.levels.isFinalLevel(tile.getLevelNumber()) && !tile.isTextureInMemory(dc.getTextureCache()))
262 this.requestTexture(dc, tile);
264 TextureTile[] subTiles = tile.createSubTiles(this.levels.getLevel(tile.getLevelNumber() + 1));
265 for (TextureTile child : subTiles)
267 if (this.isTileVisible(dc, child))
268 this.addTileOrDescendants(dc, child);
271 finally
273 if (ancestorResource != null) // Pop this tile as the currentResource ancestor
274 this.currentResourceTile = ancestorResource;
278 private void addTile(DrawContext dc, TextureTile tile)
280 tile.setFallbackTile(null);
282 if (tile.isTextureInMemory(dc.getTextureCache()))
284 this.addTileToCurrent(tile);
285 return;
288 // Level 0 loads may be forced
289 if (tile.getLevelNumber() == 0 && this.forceLevelZeroLoads && !tile.isTextureInMemory(dc.getTextureCache()))
291 this.forceTextureLoad(tile);
292 if (tile.isTextureInMemory(dc.getTextureCache()))
294 this.addTileToCurrent(tile);
295 return;
299 // Tile's texture isn't available, so request it
300 if (tile.getLevelNumber() < this.levels.getNumLevels())
302 // Request only tiles with data associated at this level
303 if (!this.levels.isResourceAbsent(tile))
304 this.requestTexture(dc, tile);
307 // Set up to use the currentResource tile's texture
308 if (this.currentResourceTile != null)
310 if (this.currentResourceTile.getLevelNumber() == 0 && this.forceLevelZeroLoads &&
311 !this.currentResourceTile.isTextureInMemory(dc.getTextureCache()) &&
312 !this.currentResourceTile.isTextureInMemory(dc.getTextureCache()))
313 this.forceTextureLoad(this.currentResourceTile);
315 if (this.currentResourceTile.isTextureInMemory(dc.getTextureCache()))
317 tile.setFallbackTile(currentResourceTile);
318 this.addTileToCurrent(tile);
323 private void addTileToCurrent(TextureTile tile)
325 this.currentTiles.add(tile);
328 private boolean isTileVisible(DrawContext dc, TextureTile tile)
330 return tile.getExtent(dc).intersects(dc.getView().getFrustumInModelCoordinates()) &&
331 (dc.getVisibleSector() == null || dc.getVisibleSector().intersects(tile.getSector()));
334 private boolean meetsRenderCriteria(DrawContext dc, TextureTile tile)
336 return this.levels.isFinalLevel(tile.getLevelNumber()) || !needToSplit(dc, tile.getSector(), 20);
339 private static boolean needToSplit(DrawContext dc, Sector sector, int density)
341 Vec4[] corners = sector.computeCornerPoints(dc.getGlobe());
342 Vec4 centerPoint = sector.computeCenterPoint(dc.getGlobe());
344 View view = dc.getView();
345 double d1 = view.getEyePoint().distanceTo3(corners[0]);
346 double d2 = view.getEyePoint().distanceTo3(corners[1]);
347 double d3 = view.getEyePoint().distanceTo3(corners[2]);
348 double d4 = view.getEyePoint().distanceTo3(corners[3]);
349 double d5 = view.getEyePoint().distanceTo3(centerPoint);
351 double minDistance = d1;
352 if (d2 < minDistance)
353 minDistance = d2;
354 if (d3 < minDistance)
355 minDistance = d3;
356 if (d4 < minDistance)
357 minDistance = d4;
358 if (d5 < minDistance)
359 minDistance = d5;
361 double cellSize = (Math.PI * sector.getDeltaLatRadians() * dc.getGlobe().getRadius()) / density;
363 return !(Math.log10(cellSize) <= (Math.log10(minDistance) - 1));
366 // ============== Rendering ======================= //
367 // ============== Rendering ======================= //
368 // ============== Rendering ======================= //
370 @Override
371 protected final void doRender(DrawContext dc)
373 if (this.forceLevelZeroLoads && !this.levelZeroLoaded)
374 this.loadAllTopLevelTextures(dc);
375 if (dc.getSurfaceGeometry() == null || dc.getSurfaceGeometry().size() < 1)
376 return; // TODO: throw an illegal state exception?
378 dc.getGeographicSurfaceTileRenderer().setShowImageTileOutlines(this.showImageTileOutlines);
380 draw(dc);
383 private void draw(DrawContext dc)
385 this.referencePoint = this.computeReferencePoint(dc);
387 this.assembleTiles(dc); // Determine the tiles to draw.
389 if (this.currentTiles.size() >= 1)
391 TextureTile[] sortedTiles = new TextureTile[this.currentTiles.size()];
392 sortedTiles = this.currentTiles.toArray(sortedTiles);
393 Arrays.sort(sortedTiles, levelComparer);
395 GL gl = dc.getGL();
397 gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_POLYGON_BIT);
399 if (this.isUseTransparentTextures())
401 gl.glEnable(GL.GL_BLEND);
402 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
405 gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
406 gl.glEnable(GL.GL_CULL_FACE);
407 gl.glCullFace(GL.GL_BACK);
409 dc.setPerFrameStatistic(PerformanceStatistic.IMAGE_TILE_COUNT, this.tileCountName, this.currentTiles.size());
410 dc.getGeographicSurfaceTileRenderer().renderTiles(dc, this.currentTiles);
412 gl.glPopAttrib();
414 if (this.drawTileIDs)
415 this.drawTileIDs(dc, this.currentTiles);
417 if (this.drawBoundingVolumes)
418 this.drawBoundingVolumes(dc, this.currentTiles);
420 this.currentTiles.clear();
423 this.sendRequests();
424 this.requestQ.clear();
427 private void sendRequests()
429 Runnable task = this.requestQ.poll();
430 while (task != null)
432 if (!WorldWind.getTaskService().isFull())
434 WorldWind.getTaskService().addTask(task);
436 task = this.requestQ.poll();
440 public boolean isLayerInView(DrawContext dc)
442 if (dc == null)
444 String message = Logging.getMessage("nullValue.DrawContextIsNull");
445 Logging.logger().severe(message);
446 throw new IllegalStateException(message);
449 if (dc.getView() == null)
451 String message = Logging.getMessage("layers.AbstractLayer.NoViewSpecifiedInDrawingContext");
452 Logging.logger().severe(message);
453 throw new IllegalStateException(message);
456 return !(dc.getVisibleSector() != null && !this.levels.getSector().intersects(dc.getVisibleSector()));
459 private Vec4 computeReferencePoint(DrawContext dc)
461 java.awt.geom.Rectangle2D viewport = dc.getView().getViewport();
462 int x = (int) viewport.getWidth() / 2;
463 for (int y = (int) (0.75 * viewport.getHeight()); y >= 0; y--)
465 Position pos = dc.getView().computePositionFromScreenPoint(x, y);
466 if (pos == null)
467 continue;
469 return dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), 0d);
472 return null;
475 protected Vec4 getReferencePoint()
477 return this.referencePoint;
480 private static class LevelComparer implements Comparator<TextureTile>
482 public int compare(TextureTile ta, TextureTile tb)
484 int la = ta.getFallbackTile() == null ? ta.getLevelNumber() : ta.getFallbackTile().getLevelNumber();
485 int lb = tb.getFallbackTile() == null ? tb.getLevelNumber() : tb.getFallbackTile().getLevelNumber();
487 return la < lb ? -1 : la == lb ? 0 : 1;
491 private void drawTileIDs(DrawContext dc, ArrayList<TextureTile> tiles)
493 java.awt.Rectangle viewport = dc.getView().getViewport();
494 if (this.textRenderer == null)
495 this.textRenderer = new TextRenderer(java.awt.Font.decode("Arial-Plain-13"), true, true);
497 dc.getGL().glDisable(GL.GL_DEPTH_TEST);
498 dc.getGL().glDisable(GL.GL_BLEND);
499 dc.getGL().glDisable(GL.GL_TEXTURE_2D);
501 this.textRenderer.setColor(java.awt.Color.YELLOW);
502 this.textRenderer.beginRendering(viewport.width, viewport.height);
503 for (TextureTile tile : tiles)
505 String tileLabel = tile.getLabel();
507 if (tile.getFallbackTile() != null)
508 tileLabel += "/" + tile.getFallbackTile().getLabel();
510 LatLon ll = tile.getSector().getCentroid();
511 Vec4 pt = dc.getGlobe().computePointFromPosition(ll.getLatitude(), ll.getLongitude(),
512 dc.getGlobe().getElevation(ll.getLatitude(), ll.getLongitude()));
513 pt = dc.getView().project(pt);
514 this.textRenderer.draw(tileLabel, (int) pt.x, (int) pt.y);
516 this.textRenderer.endRendering();
519 private void drawBoundingVolumes(DrawContext dc, ArrayList<TextureTile> tiles)
521 float[] previousColor = new float[4];
522 dc.getGL().glGetFloatv(GL.GL_CURRENT_COLOR, previousColor, 0);
523 dc.getGL().glColor3d(0, 1, 0);
525 for (TextureTile tile : tiles)
527 ((Cylinder) tile.getExtent(dc)).render(dc);
530 Cylinder c =
531 Sector.computeBoundingCylinder(dc.getGlobe(), dc.getVerticalExaggeration(), this.levels.getSector());
532 dc.getGL().glColor3d(1, 1, 0);
533 c.render(dc);
535 dc.getGL().glColor4fv(previousColor, 0);
538 // ============== Color Lookup ======================= //
539 // ============== Color Lookup ======================= //
540 // ============== Color Lookup ======================= //
542 public Color getColor(Angle latitude, Angle longitude, int levelNumber)
544 // TODO: check args
546 // Find the tile containing the position in the specified level.
547 TextureTile containingTile = null;
548 for (TextureTile tile : this.topLevels)
550 containingTile = this.getContainingTile(tile, latitude, longitude, levelNumber);
551 if (containingTile != null)
552 break;
554 if (containingTile == null)
555 return null;
557 String pathBase = containingTile.getPath().substring(0, containingTile.getPath().lastIndexOf("."));
558 String cacheKey = pathBase + ".BufferedImage";
560 // Look up the color if the image is in memory.
561 BufferedImage image = (BufferedImage) WorldWind.getMemoryCache("BatchTiles").getObject(cacheKey);
562 if (image != null)
563 return this.resolveColor(containingTile, image, latitude, longitude);
565 // Read the image from disk since it's not in memory.
566 image = this.requestImage(containingTile, cacheKey);
567 if (image != null)
568 return this.resolveColor(containingTile, image, latitude, longitude);
570 // Retrieve it from the net since it's not on disk.
571 this.downloadImage(containingTile);
573 // Try to read from disk again after retrieving it from the net.
574 image = this.requestImage(containingTile, cacheKey);
575 if (image != null)
576 return this.resolveColor(containingTile, image, latitude, longitude);
578 // All attempts to find the image have failed.
579 return null;
582 private final static String[] formats = new String[]{"jpg", "jpeg", "png", "tiff"};
583 private final static String[] suffixes = new String[]{".jpg", ".jpg", ".png", ".tiff"};
585 private BufferedImage requestImage(TextureTile tile, String cacheKey)
587 URL url = null;
588 String pathBase = tile.getPath().substring(0, tile.getPath().lastIndexOf("."));
589 for (String suffix : suffixes)
591 String path = pathBase + suffix;
592 url = WorldWind.getDataFileCache().findFile(path, false);
593 if (url != null)
594 break;
597 if (url == null)
598 return null;
600 if (WWIO.isFileOutOfDate(url, tile.getLevel().getExpiryTime()))
602 // The file has expired. Delete it then request download of newer.
603 gov.nasa.worldwind.WorldWind.getDataFileCache().removeFile(url);
604 String message = Logging.getMessage("generic.DataFileExpired", url);
605 Logging.logger().fine(message);
607 else
611 BufferedImage image = ImageIO.read(new File(url.toURI()));
612 if (image == null)
614 return null; // TODO: warn
617 WorldWind.getMemoryCache("BatchTiles").add(cacheKey, image, image.getRaster().getDataBuffer().getSize());
618 this.levels.unmarkResourceAbsent(tile);
619 return image;
621 catch (IOException e)
623 // Assume that something's wrong with the file and delete it.
624 gov.nasa.worldwind.WorldWind.getDataFileCache().removeFile(url);
625 this.levels.markResourceAbsent(tile);
626 String message = Logging.getMessage("generic.DeletedCorruptDataFile", url);
627 Logging.logger().info(message);
629 catch (URISyntaxException e)
631 e.printStackTrace(); // TODO
635 return null;
638 private void downloadImage(final TextureTile tile)
642 String urlString = tile.getResourceURL().toExternalForm().replace("dds", "");
643 final URL resourceURL = new URL(urlString);
644 Retriever retriever;
646 String protocol = resourceURL.getProtocol();
648 if ("http".equalsIgnoreCase(protocol))
650 retriever = new HTTPRetriever(resourceURL, new HttpRetrievalPostProcessor(tile));
652 else
654 // TODO:
655 Logging.logger().severe(
656 Logging.getMessage("layers.TextureLayer.UnknownRetrievalProtocol", resourceURL));
657 return;
660 retriever.call();
662 catch (Exception e)
664 e.printStackTrace(); // TODO
668 private TextureTile getContainingTile(TextureTile tile, Angle latitude, Angle longitude, int levelNumber)
670 if (!tile.getSector().contains(latitude, longitude))
671 return null;
673 if (tile.getLevelNumber() == levelNumber || this.levels.isFinalLevel(tile.getLevelNumber()))
674 return tile;
676 TextureTile containingTile;
677 TextureTile[] subTiles = tile.createSubTiles(this.levels.getLevel(tile.getLevelNumber() + 1));
678 for (TextureTile child : subTiles)
680 containingTile = this.getContainingTile(child, latitude, longitude, levelNumber);
681 if (containingTile != null)
682 return containingTile;
685 return null;
688 private Color resolveColor(TextureTile tile, BufferedImage image, Angle latitude, Angle longitude)
690 Sector sector = tile.getSector();
692 final double dLat = sector.getMaxLatitude().degrees - latitude.degrees;
693 final double dLon = longitude.degrees - sector.getMinLongitude().degrees;
694 final double sLat = dLat / sector.getDeltaLat().degrees;
695 final double sLon = dLon / sector.getDeltaLon().degrees;
697 final int tileHeight = tile.getLevel().getTileHeight();
698 final int tileWidth = tile.getLevel().getTileWidth();
699 int x = (int) ((tileWidth - 1) * sLon);
700 int y = (int) ((tileHeight - 1) * sLat);
701 int w = x < (tileWidth - 1) ? 1 : 0;
702 int h = y < (tileHeight - 1) ? 1 : 0;
704 double dh = sector.getDeltaLat().degrees / (tileHeight - 1);
705 double dw = sector.getDeltaLon().degrees / (tileWidth - 1);
706 double ssLat = (dLat - y * dh) / dh;
707 double ssLon = (dLon - x * dw) / dw;
709 int sw = image.getRGB(x, y);
710 int se = image.getRGB(x + w, y);
711 int ne = image.getRGB(x + w, y + h);
712 int nw = image.getRGB(x, y + h);
714 Color csw = new Color(sw);
715 Color cse = new Color(se);
716 Color cne = new Color(ne);
717 Color cnw = new Color(nw);
719 Color ctop = interpolateColors(cnw, cne, ssLon);
720 Color cbot = interpolateColors(csw, cse, ssLon);
722 return interpolateColors(cbot, ctop, ssLat);
725 private Color interpolateColors(Color ca, Color cb, double s)
727 int r = (int) (s * ca.getRed() + (1 - s) * cb.getRed());
728 int g = (int) (s * ca.getGreen() + (1 - s) * cb.getGreen());
729 int b = (int) (s * ca.getBlue() + (1 - s) * cb.getBlue());
731 return new Color(r, g, b);
734 private class HttpRetrievalPostProcessor implements RetrievalPostProcessor
736 private TextureTile tile;
738 public HttpRetrievalPostProcessor(TextureTile tile)
740 this.tile = tile;
743 public ByteBuffer run(Retriever retriever)
745 if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL))
746 return null;
748 HTTPRetriever htr = (HTTPRetriever) retriever;
749 if (htr.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT)
751 // Mark tile as missing to avoid excessive attempts
752 TiledImageLayer.this.levels.markResourceAbsent(tile);
753 return null;
756 URLRetriever r = (URLRetriever) retriever;
757 ByteBuffer buffer = r.getBuffer();
759 String suffix = null;
760 for (int i = 0; i < formats.length; i++)
762 if (htr.getContentType().toLowerCase().contains(formats[i]))
764 suffix = suffixes[i];
765 break;
768 if (suffix == null)
770 return null; // TODO: logger error
773 String path = tile.getPath().substring(0, tile.getPath().lastIndexOf("."));
774 path += suffix;
776 final File outFile = WorldWind.getDataFileCache().newFile(path);
777 if (outFile == null)
779 String msg = Logging.getMessage("generic.CantCreateCacheFile", tile.getPath());
780 Logging.logger().severe(msg);
781 return null;
786 WWIO.saveBuffer(buffer, outFile);
787 return buffer;
789 catch (IOException e)
791 e.printStackTrace(); // TODO: logger error
792 return null;