2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
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
;
20 import java
.awt
.image
.*;
23 import java
.nio
.ByteBuffer
;
25 import java
.util
.concurrent
.PriorityBlockingQueue
;
29 * @version $Id: TiledImageLayer.java 2533 2007-08-13 05:59:08Z tgaskins $
31 public abstract class TiledImageLayer
extends AbstractLayer
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
;
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
)
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";
78 public void setName(String 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()
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()
159 protected PriorityBlockingQueue
<Runnable
> getRequestQ()
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
++)
189 Angle t1
= Tile
.computeColumnLongitude(firstCol
, dLon
);
190 for (int col
= firstCol
; col
<= lastCol
; col
++)
195 this.topLevels
.add(new TextureTile(new Sector(p1
, p2
, t1
, t2
), level
, row
, col
));
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
);
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
);
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
);
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
);
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
)
354 if (d3
< minDistance
)
356 if (d4
< minDistance
)
358 if (d5
< minDistance
)
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 ======================= //
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
);
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
);
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
);
414 if (this.drawTileIDs
)
415 this.drawTileIDs(dc
, this.currentTiles
);
417 if (this.drawBoundingVolumes
)
418 this.drawBoundingVolumes(dc
, this.currentTiles
);
420 this.currentTiles
.clear();
424 this.requestQ
.clear();
427 private void sendRequests()
429 Runnable task
= this.requestQ
.poll();
432 if (!WorldWind
.getTaskService().isFull())
434 WorldWind
.getTaskService().addTask(task
);
436 task
= this.requestQ
.poll();
440 public boolean isLayerInView(DrawContext dc
)
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
);
469 return dc
.getGlobe().computePointFromPosition(pos
.getLatitude(), pos
.getLongitude(), 0d
);
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
);
531 Sector
.computeBoundingCylinder(dc
.getGlobe(), dc
.getVerticalExaggeration(), this.levels
.getSector());
532 dc
.getGL().glColor3d(1, 1, 0);
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
)
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)
554 if (containingTile
== 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
);
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
);
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
);
576 return this.resolveColor(containingTile
, image
, latitude
, longitude
);
578 // All attempts to find the image have failed.
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
)
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);
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
);
611 BufferedImage image
= ImageIO
.read(new File(url
.toURI()));
614 return null; // TODO: warn
617 WorldWind
.getMemoryCache("BatchTiles").add(cacheKey
, image
, image
.getRaster().getDataBuffer().getSize());
618 this.levels
.unmarkResourceAbsent(tile
);
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
638 private void downloadImage(final TextureTile tile
)
642 String urlString
= tile
.getResourceURL().toExternalForm().replace("dds", "");
643 final URL resourceURL
= new URL(urlString
);
646 String protocol
= resourceURL
.getProtocol();
648 if ("http".equalsIgnoreCase(protocol
))
650 retriever
= new HTTPRetriever(resourceURL
, new HttpRetrievalPostProcessor(tile
));
655 Logging
.logger().severe(
656 Logging
.getMessage("layers.TextureLayer.UnknownRetrievalProtocol", resourceURL
));
664 e
.printStackTrace(); // TODO
668 private TextureTile
getContainingTile(TextureTile tile
, Angle latitude
, Angle longitude
, int levelNumber
)
670 if (!tile
.getSector().contains(latitude
, longitude
))
673 if (tile
.getLevelNumber() == levelNumber
|| this.levels
.isFinalLevel(tile
.getLevelNumber()))
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
;
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
)
743 public ByteBuffer
run(Retriever retriever
)
745 if (!retriever
.getState().equals(Retriever
.RETRIEVER_STATE_SUCCESSFUL
))
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
);
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
];
770 return null; // TODO: logger error
773 String path
= tile
.getPath().substring(0, tile
.getPath().lastIndexOf("."));
776 final File outFile
= WorldWind
.getDataFileCache().newFile(path
);
779 String msg
= Logging
.getMessage("generic.CantCreateCacheFile", tile
.getPath());
780 Logging
.logger().severe(msg
);
786 WWIO
.saveBuffer(buffer
, outFile
);
789 catch (IOException e
)
791 e
.printStackTrace(); // TODO: logger error