Worldwind public release 0.2.1
[worldwind-tracker.git] / gov / nasa / worldwind / layers / TiledImageLayer.java
blobf6913eac76a8d31e67f58e62cd9808de69581f9c
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.*;
10 import com.sun.opengl.util.texture.*;
11 import gov.nasa.worldwind.*;
12 import gov.nasa.worldwind.geom.*;
14 import javax.imageio.*;
15 import javax.media.opengl.*;
16 import java.awt.*;
17 import java.awt.image.*;
18 import java.io.*;
19 import java.net.*;
20 import java.nio.*;
21 import java.util.*;
22 import java.util.concurrent.*;
24 /**
25 * @author tag
26 * @version $Id: TiledImageLayer.java 2128 2007-06-22 08:18:32Z garakl $
28 public class TiledImageLayer extends AbstractLayer
30 // Infrastructure
31 private static final LevelComparer levelComparer = new LevelComparer();
32 private final LevelSet levels;
33 private ArrayList<TextureTile> topLevels;
34 private final Object fileLock = new Object();
35 private boolean forceLevelZeroLoads = false;
36 private boolean retainLevelZeroTiles = false;
38 // Diagnostic flags
39 private boolean showImageTileOutlines = false;
40 private boolean drawTileBoundaries = false;
41 private boolean drawWireframe = false;
42 private boolean useTransparentTextures = false;
43 private boolean drawTileIDs = false;
44 private boolean drawBoundingVolumes = false;
45 private TextRenderer textRenderer = null;
47 // Stuff computed each frame
48 private ArrayList<TextureTile> currentTiles = new ArrayList<TextureTile>();
49 private TextureTile currentResourceTile;
50 private Vec4 referencePoint;
51 private PriorityBlockingQueue<RequestTask> requestQ = new PriorityBlockingQueue<RequestTask>(200);
53 public TiledImageLayer(LevelSet levelSet)
55 if (levelSet == null)
57 String message = WorldWind.retrieveErrMsg("nullValue.LevelSetIsNull");
58 WorldWind.logger().log(java.util.logging.Level.FINE, message);
59 throw new IllegalArgumentException(message);
62 this.levels = new LevelSet(levelSet); // the caller's levelSet may change internally, so we copy it.
64 this.createTopLevelTiles();
65 if (this.forceLevelZeroLoads)
66 this.loadAllTopLevelTextures();
68 this.setPickEnabled(false); // textures are assumed to be terrain unless specifically indicated otherwise.
71 @Override
72 public void dispose()
74 if (!this.retainLevelZeroTiles)
75 return;
77 for (TextureTile tile : this.topLevels)
79 tile.dispose();
83 public boolean isUseTransparentTextures()
85 return this.useTransparentTextures;
88 public void setUseTransparentTextures(boolean useTransparentTextures)
90 this.useTransparentTextures = useTransparentTextures;
93 public boolean isForceLevelZeroLoads()
95 return this.forceLevelZeroLoads;
98 public void setForceLevelZeroLoads(boolean forceLevelZeroLoads)
100 this.forceLevelZeroLoads = forceLevelZeroLoads;
101 if (this.forceLevelZeroLoads)
102 this.loadAllTopLevelTextures();
105 public boolean isRetainLevelZeroTiles()
107 return retainLevelZeroTiles;
110 public void setRetainLevelZeroTiles(boolean retainLevelZeroTiles)
112 this.retainLevelZeroTiles = retainLevelZeroTiles;
115 public boolean isDrawTileIDs()
117 return drawTileIDs;
120 public void setDrawTileIDs(boolean drawTileIDs)
122 this.drawTileIDs = drawTileIDs;
125 public boolean isDrawTileBoundaries()
127 return drawTileBoundaries;
130 public void setDrawTileBoundaries(boolean drawTileBoundaries)
132 this.drawTileBoundaries = drawTileBoundaries;
135 public boolean isDrawWireframe()
137 return drawWireframe;
140 public void setDrawWireframe(boolean drawWireframe)
142 this.drawWireframe = drawWireframe;
145 public boolean isShowImageTileOutlines()
147 return showImageTileOutlines;
150 public void setShowImageTileOutlines(boolean showImageTileOutlines)
152 this.showImageTileOutlines = showImageTileOutlines;
155 public boolean isDrawBoundingVolumes()
157 return drawBoundingVolumes;
160 public void setDrawBoundingVolumes(boolean drawBoundingVolumes)
162 this.drawBoundingVolumes = drawBoundingVolumes;
165 private void createTopLevelTiles()
167 Sector sector = this.levels.getSector();
169 Angle dLat = this.levels.getLevelZeroTileDelta().getLatitude();
170 Angle dLon = this.levels.getLevelZeroTileDelta().getLongitude();
172 // Determine the row and column offset from the common World Wind global tiling origin.
173 Level level = levels.getFirstLevel();
174 int firstRow = Tile.computeRow(level.getTileDelta().getLatitude(), sector.getMinLatitude());
175 int firstCol = Tile.computeColumn(level.getTileDelta().getLongitude(), sector.getMinLongitude());
176 int lastRow = Tile.computeRow(level.getTileDelta().getLatitude(), sector.getMaxLatitude());
177 int lastCol = Tile.computeColumn(level.getTileDelta().getLongitude(), sector.getMaxLongitude());
179 int nLatTiles = lastRow - firstRow + 1;
180 int nLonTiles = lastCol - firstCol + 1;
182 this.topLevels = new ArrayList<TextureTile>(nLatTiles * nLonTiles);
184 Angle p1 = Tile.computeRowLatitude(firstRow, dLat);
185 for (int row = firstRow; row <= lastRow; row++)
187 Angle p2;
188 p2 = p1.add(dLat);
190 Angle t1 = Tile.computeColumnLongitude(firstCol, dLon);
191 for (int col = firstCol; col <= lastCol; col++)
193 Angle t2;
194 t2 = t1.add(dLon);
196 this.topLevels.add(new TextureTile(new Sector(p1, p2, t1, t2), level, row, col));
197 t1 = t2;
199 p1 = p2;
203 private void loadAllTopLevelTextures()
205 for (TextureTile tile : this.topLevels)
207 if (!tile.holdsTexture())
208 this.forceTextureLoad(tile);
212 // ============== Tile Assembly ======================= //
213 // ============== Tile Assembly ======================= //
214 // ============== Tile Assembly ======================= //
216 private void assembleTiles(DrawContext dc)
218 this.currentTiles.clear();
219 // this.currentSpan = null;
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.holdsTexture() || 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()) && !this.isTextureInMemory(tile))
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.holdsTexture())
284 this.addTileToCurrent(tile);
285 return;
288 // Level 0 loads may be forced
289 if (tile.getLevelNumber() == 0 && this.forceLevelZeroLoads)
291 this.forceTextureLoad(tile);
292 if (tile.holdsTexture())
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.holdsTexture())
312 this.forceTextureLoad(this.currentResourceTile);
314 if (this.currentResourceTile.holdsTexture())
316 tile.setFallbackTile(currentResourceTile);
317 this.addTileToCurrent(tile);
322 private void addTileToCurrent(TextureTile tile)
324 this.currentTiles.add(tile);
327 private boolean isTileVisible(DrawContext dc, TextureTile tile)
329 return tile.getExtent(dc).intersects(dc.getView().getFrustumInModelCoordinates())
330 && (dc.getVisibleSector() == null || dc.getVisibleSector().intersects(tile.getSector()));
333 private boolean meetsRenderCriteria(DrawContext dc, TextureTile tile)
335 return this.levels.isFinalLevel(tile.getLevelNumber()) || !needToSplit(dc, tile.getSector(), 20);
338 private static boolean needToSplit(DrawContext dc, Sector sector, int density)
340 Vec4[] corners = sector.computeCornerPoints(dc.getGlobe());
341 Vec4 centerPoint = sector.computeCenterPoint(dc.getGlobe());
343 View view = dc.getView();
344 double d1 = view.getEyePoint().distanceTo3(corners[0]);
345 double d2 = view.getEyePoint().distanceTo3(corners[1]);
346 double d3 = view.getEyePoint().distanceTo3(corners[2]);
347 double d4 = view.getEyePoint().distanceTo3(corners[3]);
348 double d5 = view.getEyePoint().distanceTo3(centerPoint);
350 double minDistance = d1;
351 if (d2 < minDistance)
352 minDistance = d2;
353 if (d3 < minDistance)
354 minDistance = d3;
355 if (d4 < minDistance)
356 minDistance = d4;
357 if (d5 < minDistance)
358 minDistance = d5;
360 double cellSize = (Math.PI * sector.getDeltaLatRadians() * dc.getGlobe().getRadius()) / density;
362 return !(Math.log10(cellSize) <= (Math.log10(minDistance) - 1));
365 // ============== Rendering ======================= //
366 // ============== Rendering ======================= //
367 // ============== Rendering ======================= //
369 @Override
370 protected final void doRender(DrawContext dc)
372 if (dc.getSurfaceGeometry() == null || dc.getSurfaceGeometry().size() < 1)
373 return; // TODO: throw an illegal state exception?
375 dc.getSurfaceTileRenderer().setShowImageTileOutlines(this.showImageTileOutlines);
377 draw(dc);
380 private void draw(DrawContext dc)
382 TextureTile.disposeTextures(); // Clean up any unused textures while within a OpenGl context thread.
384 this.referencePoint = this.computeReferencePoint(dc);
386 this.assembleTiles(dc); // Determine the tiles to draw.
388 if (this.currentTiles.size() >= 1)
390 TextureTile[] sortedTiles = new TextureTile[this.currentTiles.size()];
391 sortedTiles = this.currentTiles.toArray(sortedTiles);
392 Arrays.sort(sortedTiles, levelComparer);
394 GL gl = dc.getGL();
396 gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_POLYGON_BIT);
398 if (this.isUseTransparentTextures())
400 gl.glEnable(GL.GL_BLEND);
401 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
404 gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
405 gl.glEnable(GL.GL_CULL_FACE);
406 gl.glCullFace(GL.GL_BACK);
408 // System.out.println(this.getName() + " " + this.currentTiles.size()); // **************
409 dc.getSurfaceTileRenderer().renderTiles(dc, this.currentTiles);
411 gl.glPopAttrib();
413 if (this.drawTileIDs)
414 this.drawTileIDs(dc, this.currentTiles);
416 if (this.drawBoundingVolumes)
417 this.drawBoundingVolumes(dc, this.currentTiles);
419 this.currentTiles.clear();
422 this.sendRequests();
423 this.requestQ.clear();
426 private void sendRequests()
428 RequestTask task = this.requestQ.poll();
429 while (task != null)
431 if (!WorldWind.threadedTaskService().isFull())
433 WorldWind.threadedTaskService().addTask(task);
435 task = this.requestQ.poll();
439 public boolean isLayerInView(DrawContext dc)
441 if (dc == null)
443 String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
444 WorldWind.logger().log(java.util.logging.Level.FINE, message);
445 throw new IllegalStateException(message);
448 if (dc.getView() == null)
450 String message = WorldWind.retrieveErrMsg("layers.AbstractLayer.NoViewSpecifiedInDrawingContext");
451 WorldWind.logger().log(java.util.logging.Level.FINE, message);
452 throw new IllegalStateException(message);
455 return !(dc.getVisibleSector() != null && !this.levels.getSector().intersects(dc.getVisibleSector()));
458 private Vec4 computeReferencePoint(DrawContext dc)
460 java.awt.geom.Rectangle2D viewport = dc.getView().getViewport();
461 int x = (int) viewport.getWidth() / 2;
462 for (int y = (int) (0.75 * viewport.getHeight()); y >= 0; y--)
464 Position pos = dc.getView().computePositionFromScreenPoint(x, y);
465 if (pos == null)
466 continue;
468 return dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), 0d);
471 return null;
474 private static class LevelComparer implements Comparator<TextureTile>
476 public int compare(TextureTile ta, TextureTile tb)
478 int la;
479 int lb;
481 if (ta.holdsTexture())
482 la = ta.getLevelNumber();
483 else
484 la = ta.getFallbackTile().getLevelNumber();
486 if (tb.holdsTexture())
487 lb = tb.getLevelNumber();
488 else
489 lb = tb.getFallbackTile().getLevelNumber();
491 return la < lb ? -1 : la == lb ? 0 : 1;
495 private void drawTileIDs(DrawContext dc, ArrayList<TextureTile> tiles)
497 java.awt.Rectangle viewport = dc.getView().getViewport();
498 if (this.textRenderer == null)
499 this.textRenderer = new TextRenderer(java.awt.Font.decode("Arial-Plain-13"), true, true);
501 dc.getGL().glDisable(GL.GL_DEPTH_TEST);
502 dc.getGL().glDisable(GL.GL_BLEND);
503 dc.getGL().glDisable(GL.GL_TEXTURE_2D);
505 this.textRenderer.setColor(java.awt.Color.YELLOW);
506 this.textRenderer.beginRendering(viewport.width, viewport.height);
507 for (TextureTile tile : tiles)
509 String tileLabel = tile.getLabel();
511 if (tile.getFallbackTile() != null)
512 tileLabel += "/" + tile.getFallbackTile().getLabel();
514 LatLon ll = tile.getSector().getCentroid();
515 Vec4 pt = dc.getGlobe().computePointFromPosition(ll.getLatitude(), ll.getLongitude(),
516 dc.getGlobe().getElevation(ll.getLatitude(), ll.getLongitude()));
517 pt = dc.getView().project(pt);
518 this.textRenderer.draw(tileLabel, (int) pt.x, (int) pt.y);
520 this.textRenderer.endRendering();
523 private void drawBoundingVolumes(DrawContext dc, ArrayList<TextureTile> tiles)
525 float[] previousColor = new float[4];
526 dc.getGL().glGetFloatv(GL.GL_CURRENT_COLOR, previousColor, 0);
527 dc.getGL().glColor3d(0, 1, 0);
529 for (TextureTile tile : tiles)
531 ((Cylinder) tile.getExtent(dc)).render(dc);
534 Cylinder c = Sector.computeBoundingCylinder(dc.getGlobe(), dc.getVerticalExaggeration(),
535 this.levels.getSector());
536 dc.getGL().glColor3d(1, 1, 0);
537 c.render(dc);
539 dc.getGL().glColor4fv(previousColor, 0);
542 // ============== Image Reading and Downloading ======================= //
543 // ============== Image Reading and Downloading ======================= //
544 // ============== Image Reading and Downloading ======================= //
546 private void requestTexture(DrawContext dc, TextureTile tile)
548 Vec4 centroid = tile.getCentroidPoint(dc.getGlobe());
549 if (this.referencePoint != null)
550 tile.setPriority(centroid.distanceTo3(this.referencePoint));
552 RequestTask task = new RequestTask(tile, this);
553 this.requestQ.add(task);
556 private void forceTextureLoad(TextureTile tile)
558 final java.net.URL textureURL = WorldWind.dataFileCache().findFile(tile.getPath(), true);
560 if (textureURL != null && WWIO.isFileOutOfDate(textureURL, tile.getLevel().getExpiryTime()))
562 // The file has expired. Delete it.
563 gov.nasa.worldwind.WorldWind.dataFileCache().removeFile(textureURL);
564 String message = WorldWind.retrieveErrMsg("generic.DataFileExpired") + textureURL;
565 WorldWind.logger().log(java.util.logging.Level.FINER, message);
567 else if (textureURL != null)
569 this.loadTexture(tile, textureURL);
573 private boolean loadTexture(TextureTile tile, java.net.URL textureURL)
575 TextureData textureData;
577 synchronized (this.fileLock)
579 textureData = readTexture(textureURL);
582 if (textureData == null)
583 return false;
585 tile.setTextureData(textureData);
586 if (tile.getLevelNumber() != 0 || !this.retainLevelZeroTiles)
587 this.addTileToCache(tile);
589 return true;
592 private static TextureData readTexture(java.net.URL url)
596 return TextureIO.newTextureData(url, false, null);
598 catch (Exception e)
600 String message = WorldWind.retrieveErrMsg("layers.TextureLayer.ExceptionAttemptingToReadTextureFile");
601 WorldWind.logger().log(java.util.logging.Level.FINE, message + url, e);
602 return null;
606 private void addTileToCache(TextureTile tile)
608 WorldWind.memoryCache().add(tile.getTileKey(), tile);
611 private boolean isTextureInMemory(TextureTile tile)
613 return ((tile.getLevelNumber() == 0 && tile.holdsTexture())
614 || WorldWind.memoryCache().getObject(tile.getTileKey()) != null);
617 private void downloadTexture(final TextureTile tile)
619 if (WorldWind.retrievalService().isFull())
620 return;
622 java.net.URL url;
625 url = tile.getResourceURL();
627 catch (java.net.MalformedURLException e)
629 String message = WorldWind.retrieveErrMsg("layers.TextureLayer.ExceptionCreatingTextureUrl");
630 WorldWind.logger().log(java.util.logging.Level.FINE, message + tile, e);
631 return;
634 Retriever retriever = null;
636 if("http".equalsIgnoreCase(url.getProtocol()))
638 retriever = new HTTPRetriever(url, new DownloadPostProcessor(tile, this));
640 // else if("jpip".equalsIgnoreCase(url.getProtocol()))
641 // {
642 // if(this instanceof JPIPLayer)
643 // {
644 // JPIPLayer jpipLayer = (JPIPLayer)this;
645 // retriever = new JpipRetriever(url, tile, jpipLayer.getJ2KImage());
646 // }
647 // else
648 // {
649 // String message = WorldWind.retrieveErrMsg("layers.JPIPLayer.UnexpectedLayerType");
650 // WorldWind.logger().log(java.util.logging.Level.FINE, message);
651 // return;
652 // }
653 // }
654 else
656 String message = WorldWind.retrieveErrMsg("layers.TextureLayer.ExceptionCreatingTextureUrl");
657 WorldWind.logger().log(java.util.logging.Level.FINE, message + "unknown protocol " + url);
658 return;
661 // Apply any overridden timeouts.
662 Integer cto = AVListImpl.getIntegerValue(this, AVKey.URL_CONNECT_TIMEOUT);
663 if (cto != null && cto > 0)
664 retriever.setConnectTimeout(cto);
665 Integer cro = AVListImpl.getIntegerValue(this, AVKey.URL_READ_TIMEOUT);
666 if (cro != null && cro > 0)
667 retriever.setReadTimeout(cro);
668 Integer srl = AVListImpl.getIntegerValue(this, AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT);
669 if (srl != null && srl > 0)
670 retriever.setStaleRequestLimit(srl);
672 WorldWind.retrievalService().runRetriever(retriever, tile.getPriority());
675 private void saveBuffer(java.nio.ByteBuffer buffer, java.io.File outFile) throws java.io.IOException
677 synchronized (this.fileLock) // sychronized with read of file in RequestTask.run()
679 gov.nasa.worldwind.WWIO.saveBuffer(buffer, outFile);
683 // ============== Inner classes ======================= //
684 // ============== Inner classes ======================= //
685 // ============== Inner classes ======================= //
687 private static class RequestTask implements Runnable, Comparable<RequestTask>
689 private final TiledImageLayer layer;
690 private final TextureTile tile;
692 private RequestTask(TextureTile tile, TiledImageLayer layer)
694 this.layer = layer;
695 this.tile = tile;
698 public void run()
700 // check to ensure load is still needed
701 if (this.layer.isTextureInMemory(this.tile))
702 return;
704 final java.net.URL textureURL = WorldWind.dataFileCache().findFile(tile.getPath(), false);
705 if (textureURL != null)
707 if (WWIO.isFileOutOfDate(textureURL, this.tile.getLevel().getExpiryTime()))
709 // The file has expired. Delete it then request download of newer.
710 gov.nasa.worldwind.WorldWind.dataFileCache().removeFile(textureURL);
711 String message = WorldWind.retrieveErrMsg("generic.DataFileExpired") + textureURL;
712 WorldWind.logger().log(java.util.logging.Level.FINER, message);
714 else if (this.layer.loadTexture(tile, textureURL))
716 layer.levels.unmarkResourceAbsent(tile);
717 this.layer.firePropertyChange(gov.nasa.worldwind.AVKey.LAYER, null, this);
718 return;
720 else
722 // Assume that something's wrong with the file and delete it.
723 gov.nasa.worldwind.WorldWind.dataFileCache().removeFile(textureURL);
724 layer.levels.markResourceAbsent(tile);
725 String message = WorldWind.retrieveErrMsg("generic.DeletedCorruptDataFile") + textureURL;
726 WorldWind.logger().log(java.util.logging.Level.FINE, message);
730 this.layer.downloadTexture(this.tile);
734 * @param that the task to compare
735 * @return -1 if <code>this</code> less than <code>that</code>, 1 if greater than, 0 if equal
736 * @throws IllegalArgumentException if <code>that</code> is null
738 public int compareTo(RequestTask that)
740 if (that == null)
742 String msg = WorldWind.retrieveErrMsg("nullValue.RequestTaskIsNull");
743 WorldWind.logger().log(java.util.logging.Level.FINE, msg);
744 throw new IllegalArgumentException(msg);
746 return this.tile.getPriority() == that.tile.getPriority() ? 0 :
747 this.tile.getPriority() < that.tile.getPriority() ? -1 : 1;
750 public boolean equals(Object o)
752 if (this == o)
753 return true;
754 if (o == null || getClass() != o.getClass())
755 return false;
757 final RequestTask that = (RequestTask) o;
759 // Don't include layer in comparison so that requests are shared among layers
760 return !(tile != null ? !tile.equals(that.tile) : that.tile != null);
763 public int hashCode()
765 return (tile != null ? tile.hashCode() : 0);
768 public String toString()
770 return this.tile.toString();
774 private static class DownloadPostProcessor implements RetrievalPostProcessor
776 // TODO: Rewrite this inner class, factoring out the generic parts.
777 private final TextureTile tile;
778 private final TiledImageLayer layer;
780 public DownloadPostProcessor(TextureTile tile, TiledImageLayer layer)
782 this.tile = tile;
783 this.layer = layer;
786 public ByteBuffer run(Retriever retriever)
788 if (retriever == null)
790 String msg = WorldWind.retrieveErrMsg("nullValue.RetrieverIsNull");
791 WorldWind.logger().log(java.util.logging.Level.FINE, msg);
792 throw new IllegalArgumentException(msg);
797 if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL))
798 return null;
800 URLRetriever r = (URLRetriever) retriever;
801 ByteBuffer buffer = r.getBuffer();
803 if (retriever instanceof HTTPRetriever)
805 HTTPRetriever htr = (HTTPRetriever) retriever;
806 if (htr.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT)
808 // Mark tile as missing to avoid excessive attempts
809 this.layer.levels.markResourceAbsent(this.tile);
810 return null;
812 else if (htr.getResponseCode() != HttpURLConnection.HTTP_OK)
814 // Also mark tile as missing, but for an unknown reason.
815 this.layer.levels.markResourceAbsent(this.tile);
816 return null;
820 final File outFile = WorldWind.dataFileCache().newFile(this.tile.getPath());
821 if (outFile == null)
823 String msg = WorldWind.retrieveErrMsg("generic.CantCreateCacheFile") + this.tile.getPath();
824 WorldWind.logger().log(java.util.logging.Level.FINE, msg);
825 return null;
828 if (outFile.exists())
829 return buffer;
831 // TODO: Better, more generic and flexible handling of file-format type
832 if (buffer != null)
834 String contentType = r.getContentType();
835 if (contentType == null)
837 // TODO: log message
838 return null;
841 if (contentType.contains("xml") || contentType.contains("html") || contentType.contains("text"))
843 this.layer.levels.markResourceAbsent(this.tile);
845 StringBuffer sb = new StringBuffer();
846 while (buffer.hasRemaining())
848 sb.append((char) buffer.get());
850 // TODO: parse out the message if the content is xml or html.
851 WorldWind.logger().log(java.util.logging.Level.FINE, sb.toString());
853 return null;
855 else if (contentType.contains("dds"))
857 this.layer.saveBuffer(buffer, outFile);
859 else if (contentType.contains("zip"))
861 // Assume it's zipped DDS, which the retriever would have unzipped into the buffer.
862 this.layer.saveBuffer(buffer, outFile);
864 else if (outFile.getName().endsWith(".dds"))
866 // Convert to DDS and save the result.
867 buffer = DDSConverter.convertToDDS(buffer, contentType);
868 if (buffer != null)
869 this.layer.saveBuffer(buffer, outFile);
871 else if (contentType.contains("image"))
873 // Just save whatever it is to the cache.
874 this.layer.saveBuffer(buffer, outFile);
877 if (buffer != null)
879 this.layer.firePropertyChange(AVKey.LAYER, null, this);
881 return buffer;
884 catch (java.io.IOException e)
886 this.layer.levels.markResourceAbsent(this.tile);
887 String message = WorldWind.retrieveErrMsg("layers.TextureLayer.ExceptionSavingRetrievedTextureFile");
888 WorldWind.logger().log(java.util.logging.Level.FINE, message + tile.getPath(), e);
890 return null;
894 public Color getColor(Angle latitude, Angle longitude, int levelNumber)
896 // TODO: check args
898 // Find the tile containing the position in the specified level.
899 TextureTile containingTile = null;
900 for (TextureTile tile : this.topLevels)
902 containingTile = this.getContainingTile(tile, latitude, longitude, levelNumber);
903 if (containingTile != null)
904 break;
906 if (containingTile == null)
907 return null;
909 String pathBase = containingTile.getPath().substring(0, containingTile.getPath().lastIndexOf("."));
910 String cacheKey = pathBase + ".BufferedImage";
912 // Look up the color if the image is in memory.
913 BufferedImage image = (BufferedImage) WorldWind.memoryCache().getObject(cacheKey);
914 if (image != null)
915 return this.resolveColor(containingTile, image, latitude, longitude);
917 // Read the image from disk since it's not in memory.
918 image = this.requestImage(containingTile, cacheKey);
919 if (image != null)
920 return this.resolveColor(containingTile, image, latitude, longitude);
922 // Retrieve it from the net since it's not on disk.
923 this.downloadImage(containingTile);
925 // Try to read from disk again after retrieving it from the net.
926 image = this.requestImage(containingTile, cacheKey);
927 if (image != null)
928 return this.resolveColor(containingTile, image, latitude, longitude);
930 // All attempts to find the image have failed.
931 return null;
934 private final static String[] formats = new String[] {"jpg", "jpeg", "png", "tiff"};
935 private final static String[] suffixes = new String[] {".jpg", ".jpg", ".png", ".tiff"};
937 private BufferedImage requestImage(TextureTile tile, String cacheKey)
939 URL url = null;
940 String pathBase = tile.getPath().substring(0, tile.getPath().lastIndexOf("."));
941 for (String suffix : suffixes)
943 String path = pathBase + suffix;
944 url = WorldWind.dataFileCache().findFile(path, false);
945 if (url != null)
946 break;
949 if (url == null)
950 return null;
952 if (WWIO.isFileOutOfDate(url, tile.getLevel().getExpiryTime()))
954 // The file has expired. Delete it then request download of newer.
955 gov.nasa.worldwind.WorldWind.dataFileCache().removeFile(url);
956 String message = WorldWind.retrieveErrMsg("generic.DataFileExpired") + url;
957 WorldWind.logger().log(java.util.logging.Level.FINER, message);
959 else
963 BufferedImage image = ImageIO.read(new File(url.toURI()));
964 if (image == null)
966 return null; // TODO: warn
969 WorldWind.memoryCache().add(cacheKey, image, image.getRaster().getDataBuffer().getSize());
970 this.levels.unmarkResourceAbsent(tile);
971 return image;
973 catch (IOException e)
975 // Assume that something's wrong with the file and delete it.
976 gov.nasa.worldwind.WorldWind.dataFileCache().removeFile(url);
977 this.levels.markResourceAbsent(tile);
978 String message = WorldWind.retrieveErrMsg("generic.DeletedCorruptDataFile") + url;
979 WorldWind.logger().log(java.util.logging.Level.FINE, message);
981 catch (URISyntaxException e)
983 e.printStackTrace(); // TODO
987 return null;
990 private void downloadImage(final TextureTile tile)
994 String urlString = tile.getResourceURL().toExternalForm().replace("dds", "");
995 final URL resourceURL = new URL(urlString);
996 Retriever retriever = null;
998 String protocol = resourceURL.getProtocol();
1000 if("http".equalsIgnoreCase(protocol))
1002 retriever = new HTTPRetriever(resourceURL, new HttpRetrievalPostProcessor(tile));
1004 // else if("http".equalsIgnoreCase(protocol))
1005 // {
1006 // if(this instanceof JPIPLayer)
1007 // {
1008 // JPIPLayer jpipLayer = (JPIPLayer)this;
1009 // retriever = new JpipRetriever(resourceURL, tile, jpipLayer.getJ2KImage());
1010 // }
1011 // else
1012 // {
1013 // String message = WorldWind.retrieveErrMsg("layers.JPIPLayer.UnexpectedLayerType");
1014 // WorldWind.logger().log(java.util.logging.Level.FINE, message);
1015 // return;
1016 // }
1017 // }
1018 retriever.call();
1020 catch (Exception e)
1022 e.printStackTrace(); // TODO
1026 private TextureTile getContainingTile(TextureTile tile, Angle latitude, Angle longitude, int levelNumber)
1028 if (!tile.getSector().contains(latitude, longitude))
1029 return null;
1031 if (tile.getLevelNumber() == levelNumber || this.levels.isFinalLevel(tile.getLevelNumber()))
1032 return tile;
1034 TextureTile containingTile;
1035 TextureTile[] subTiles = tile.createSubTiles(this.levels.getLevel(tile.getLevelNumber() + 1));
1036 for (TextureTile child : subTiles)
1038 containingTile = this.getContainingTile(child, latitude, longitude, levelNumber);
1039 if (containingTile != null)
1040 return containingTile;
1043 return null;
1046 private Color resolveColor(TextureTile tile, BufferedImage image, Angle latitude, Angle longitude)
1048 Sector sector = tile.getSector();
1050 final double dLat = sector.getMaxLatitude().degrees - latitude.degrees;
1051 final double dLon = longitude.degrees - sector.getMinLongitude().degrees;
1052 final double sLat = dLat / sector.getDeltaLat().degrees;
1053 final double sLon = dLon / sector.getDeltaLon().degrees;
1055 final int tileHeight = tile.getLevel().getTileHeight();
1056 final int tileWidth = tile.getLevel().getTileWidth();
1057 int x = (int) ((tileWidth - 1) * sLon);
1058 int y = (int) ((tileHeight - 1) * sLat);
1059 int w = x < (tileWidth - 1) ? 1 : 0;
1060 int h = y < (tileHeight - 1) ? 1 : 0;
1062 double dh = sector.getDeltaLat().degrees / (tileHeight - 1);
1063 double dw = sector.getDeltaLon().degrees / (tileWidth - 1);
1064 double ssLat = (dLat - y * dh) / dh;
1065 double ssLon = (dLon - x * dw) / dw;
1067 int sw = image.getRGB(x, y);
1068 int se = image.getRGB(x + w, y);
1069 int ne = image.getRGB(x + w, y + h);
1070 int nw = image.getRGB(x, y + h);
1072 Color csw = new Color(sw);
1073 Color cse = new Color(se);
1074 Color cne = new Color(ne);
1075 Color cnw = new Color(nw);
1077 Color ctop = interpolateColors(cnw, cne, ssLon);
1078 Color cbot = interpolateColors(csw, cse, ssLon);
1080 return interpolateColors(cbot, ctop, ssLat);
1083 private Color interpolateColors(Color ca, Color cb, double s)
1085 int r = (int) (s * ca.getRed() + (1 - s) * cb.getRed());
1086 int g = (int) (s * ca.getGreen() + (1 - s) * cb.getGreen());
1087 int b = (int) (s * ca.getBlue() + (1 - s) * cb.getBlue());
1089 return new Color(r, g, b);
1092 private class HttpRetrievalPostProcessor implements RetrievalPostProcessor
1094 private TextureTile tile;
1095 public HttpRetrievalPostProcessor(TextureTile tile)
1097 this.tile = tile;
1099 public ByteBuffer run(Retriever retriever)
1101 if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL))
1102 return null;
1104 HTTPRetriever htr = (HTTPRetriever) retriever;
1105 if (htr.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT)
1107 // Mark tile as missing to avoid excessive attempts
1108 TiledImageLayer.this.levels.markResourceAbsent(tile);
1109 return null;
1112 URLRetriever r = (URLRetriever) retriever;
1113 ByteBuffer buffer = r.getBuffer();
1115 String suffix = null;
1116 for (int i = 0; i < formats.length; i++)
1118 if (htr.getContentType().toLowerCase().contains(formats[i]))
1120 suffix = suffixes[i];
1121 break;
1124 if (suffix == null)
1126 return null; // TODO: log error
1129 String path = tile.getPath().substring(0, tile.getPath().lastIndexOf("."));
1130 path += suffix;
1132 final File outFile = WorldWind.dataFileCache().newFile(path);
1133 if (outFile == null)
1135 String msg = WorldWind.retrieveErrMsg("generic.CantCreateCacheFile")
1136 + tile.getPath();
1137 WorldWind.logger().log(java.util.logging.Level.FINE, msg);
1138 return null;
1143 WWIO.saveBuffer(buffer, outFile);
1144 return buffer;
1146 catch (IOException e)
1148 e.printStackTrace(); // TODO: log error
1149 return null;
1156 // private void renderTiles2(DrawContext dc)
1157 // {
1158 // // Render all the tiles collected during assembleTiles()
1159 // GL gl = dc.getGL();
1161 // gl.glPushAttrib(
1162 // GL.GL_COLOR_BUFFER_BIT // for blend func, current color, alpha func, color mask
1163 // | GL.GL_POLYGON_BIT // for face culling, polygon mode
1164 // | GL.GL_ENABLE_BIT
1165 // | GL.GL_CURRENT_BIT
1166 // | GL.GL_DEPTH_BUFFER_BIT // for depth mask
1167 // | GL.GL_TEXTURE_BIT // for texture env
1168 // | GL.GL_TRANSFORM_BIT);
1170 // try
1171 // {
1172 // gl.glEnable(GL.GL_DEPTH_TEST);
1173 // gl.glDepthFunc(GL.GL_LEQUAL);
1175 // if (this.useTransparentTextures)
1176 // {
1177 // gl.glEnable(GL.GL_BLEND);
1178 // gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
1179 // }
1181 // gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
1182 // gl.glEnable(GL.GL_CULL_FACE);
1183 // gl.glCullFace(GL.GL_BACK);
1184 // gl.glColor4d(0, 0, 0, 0);
1186 // gl.glEnable(GL.GL_ALPHA_TEST);
1187 // gl.glAlphaFunc(GL.GL_GREATER, 0.01f);
1189 // gl.glMatrixMode(javax.media.opengl.GL.GL_TEXTURE);
1190 // gl.glPushMatrix();
1191 // gl.glLoadIdentity();
1193 //// System.out.printf("%d geo tiles, image tiles: ", dc.getSurfaceGeometry().size());
1194 // for (SectorGeometry sg : dc.getSurfaceGeometry())
1195 // {
1196 // TextureTile[] tilesToRender = this.getIntersectingTiles(sg);
1197 // if (tilesToRender == null)
1198 // continue;
1199 //// System.out.printf("%d, ", tilesToRender.length);
1201 // int numTilesRendered = 0;
1202 // while (numTilesRendered < tilesToRender.length)
1203 // {
1204 // int numTexUnitsUsed = 0;
1205 // while (numTexUnitsUsed < dc.getNumTextureUnits() && numTilesRendered < tilesToRender.length)
1206 // {
1207 // if (this.setupTileTexture(dc, tilesToRender[numTilesRendered++],
1208 // GL.GL_TEXTURE0 + numTexUnitsUsed))
1209 // {
1210 // ++numTexUnitsUsed;
1211 // gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_COMBINE);
1212 // gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC0_RGB, GL.GL_TEXTURE);
1213 // gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC1_RGB, GL.GL_PREVIOUS);
1214 // gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC2_RGB, GL.GL_TEXTURE);
1215 // gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_OPERAND2_RGB, GL.GL_SRC_ALPHA);
1216 // gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_COMBINE_RGB, GL.GL_INTERPOLATE);
1218 // gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC0_ALPHA, GL.GL_TEXTURE);
1219 // gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC1_ALPHA, GL.GL_PREVIOUS);
1220 // gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC2_ALPHA, GL.GL_TEXTURE);
1221 // gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_OPERAND2_ALPHA, GL.GL_SRC_ALPHA);
1222 // gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_COMBINE_ALPHA, GL.GL_INTERPOLATE);
1223 // }
1224 // }
1226 // sg.renderMultiTexture(dc, numTexUnitsUsed);
1228 // // Turn all the multi-texture units off
1229 // for (int i = 0; i < dc.getNumTextureUnits(); i++)
1230 // {
1231 // gl.glActiveTexture(GL.GL_TEXTURE0 + i);
1232 // gl.glDisable(GL.GL_TEXTURE_2D);
1233 // }
1234 // gl.glActiveTexture(GL.GL_TEXTURE0);
1235 // }
1236 // }
1237 //// System.out.println();
1239 // gl.glMatrixMode(javax.media.opengl.GL.GL_TEXTURE);
1240 // gl.glPopMatrix();
1241 // }
1242 // catch (Exception e)
1243 // {
1244 // String message = WorldWind.retrieveErrMsg("generic.ExceptionWhileRenderingLayer");
1245 // message += this.getClass().getName();
1246 // WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
1247 // }
1248 // finally
1249 // {
1250 // gl.glPopAttrib();
1251 // }
1252 // }
1254 // private static String[] buildFragmentShader(int numTexUnits)
1255 // {
1256 // ArrayList<String> lines = new ArrayList<String>();
1258 // lines.add("uniform sampler2D tex[" + numTexUnits + "];");
1259 // lines.add("uniform int maxTexUnit;");
1260 // lines.add("void main()");
1261 // lines.add("{");
1262 // lines.add(" vec2 zero = vec2(0.0);");
1263 // lines.add(" vec2 one = vec2(1.0);");
1264 // lines.add(" vec4 color = vec4(0.0);");
1265 // lines.add(" bool hasColor = false;");
1267 // for (int i = 0; i < numTexUnits; i++)
1268 // {
1269 // lines.add("if (" + i + " <= maxTexUnit)");
1270 // lines.add("{");
1271 // lines.add("if (all(greaterThanEqual(gl_TexCoord[" + i + "].st, zero))"
1272 // + " && all(lessThanEqual(gl_TexCoord[" + i + "].st, one)))");
1273 // lines.add("{");
1274 // lines.add(" color = texture2D(tex[" + i + "], gl_TexCoord[" + i + "].st);");
1275 // lines.add(" hasColor = true;");
1276 // lines.add("}");
1277 // }
1279 // for (int i = 0; i < numTexUnits; i++)
1280 // {
1281 // lines.add("}");
1282 // }
1284 // lines.add(" if(hasColor)");
1285 // lines.add(" gl_FragColor = color;");
1286 // lines.add(" else");
1287 // lines.add(" discard;");
1288 // lines.add("}");
1290 // return lines.toArray(new String[lines.size()]);
1291 // }
1293 // private int fProgram = -1;
1295 // private void initFragmentProgram(int numTextureUnits)
1296 // {
1297 // String[] lines = buildFragmentShader(numTextureUnits);
1299 // GL gl = GLContext.getCurrent().getGL();
1301 // int fShader = gl.glCreateShader(GL.GL_FRAGMENT_SHADER);
1302 // int[] lineCounts = new int[lines.length];
1303 // for (int i = 0; i < lines.length; i++)
1304 // {
1305 // lineCounts[i] = lines[i].length();
1306 // }
1307 // gl.glShaderSource(fShader, lines.length, lines, lineCounts, 0);
1308 // gl.glCompileShader(fShader);
1310 // int[] status = new int[1];
1311 // byte[] log = new byte[4096];
1312 // int[] logLength = new int[1];
1313 // gl.glGetShaderiv(fShader, GL.GL_COMPILE_STATUS, status, 0);
1314 // gl.glGetShaderInfoLog(fShader, log.length, logLength, 0, log, 0);
1315 // if (status[0] != 1)
1316 // {
1317 // gl.glGetShaderInfoLog(fShader, log.length, logLength, 0, log, 0);
1318 // String sLog = new String(log);
1319 // System.out.println("Compile : " + sLog);
1320 // }
1322 // this.fProgram = gl.glCreateProgram();
1323 // gl.glAttachShader(this.fProgram, fShader);
1324 // gl.glLinkProgram(this.fProgram);
1326 // gl.glGetProgramiv(this.fProgram, GL.GL_LINK_STATUS, status, 0);
1327 // if (status[0] != 1)
1328 // {
1329 // gl.glGetShaderInfoLog(fShader, log.length, logLength, 0, log, 0);
1330 // String sLog = new String(log);
1331 // System.out.println("Link : " + sLog);
1332 // }
1333 // }
1335 // private String[] texUnitPositions;
1336 // private int[] texSamplers;
1338 // private void renderTiles3(DrawContext dc)
1339 // {
1340 // // Render all the tiles collected during assembleTiles()
1341 // GL gl = dc.getGL();
1343 // gl.glPushAttrib(
1344 // GL.GL_COLOR_BUFFER_BIT // for blend func, current color, alpha func, color mask
1345 // | GL.GL_POLYGON_BIT // for face culling, polygon mode
1346 // | GL.GL_ENABLE_BIT
1347 // | GL.GL_CURRENT_BIT
1348 // | GL.GL_DEPTH_BUFFER_BIT // for depth mask
1349 // | GL.GL_TEXTURE_BIT // for texture env
1350 // | GL.GL_TRANSFORM_BIT);
1352 // try
1353 // {
1354 // dc.setNumTextureUnits(1);
1355 // if (this.fProgram == -1)
1356 // this.initFragmentProgram(dc.getNumTextureUnits());
1358 // if (this.texUnitPositions == null)
1359 // {
1360 // this.texUnitPositions = new String[dc.getNumTextureUnits()];
1361 // for (int i = 0; i < dc.getNumTextureUnits(); i++)
1362 // {
1363 // this.texUnitPositions[i] = "tex[" + i + "]";
1364 // }
1365 // }
1367 // if (this.texSamplers == null)
1368 // this.texSamplers = new int[dc.getNumTextureUnits()];
1370 // gl.glUseProgram(this.fProgram);
1372 // for (int i = 0; i < dc.getNumTextureUnits(); i++)
1373 // {
1374 // this.texSamplers[i] = gl.glGetUniformLocation(fProgram, "tex[" + i + "]");
1375 // gl.glUniform1i(this.texSamplers[i], i);
1376 // }
1378 // int maxTexUnit = gl.glGetUniformLocation(fProgram, "maxTexUnit");
1380 // gl.glEnable(GL.GL_DEPTH_TEST);
1381 // gl.glDepthFunc(GL.GL_LEQUAL);
1383 // if (this.useTransparentTextures)
1384 // {
1385 // gl.glEnable(GL.GL_BLEND);
1386 // gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
1387 // }
1389 // gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
1390 // gl.glEnable(GL.GL_CULL_FACE);
1391 // gl.glCullFace(GL.GL_BACK);
1392 // gl.glColor4d(0, 0, 0, 0);
1394 // gl.glMatrixMode(GL.GL_TEXTURE);
1395 // gl.glPushMatrix();
1397 //// System.out.printf("%d geo tiles, image tiles: ", dc.getSurfaceGeometry().size());
1398 // for (SectorGeometry sg : dc.getSurfaceGeometry())
1399 // {
1400 // TextureTile[] tilesToRender = this.getIntersectingTiles(sg);
1401 // if (tilesToRender == null)
1402 // continue;
1403 //// System.out.printf("%d, ", tilesToRender.length);
1405 // int numTilesRendered = 0;
1406 // while (numTilesRendered < tilesToRender.length)
1407 // {
1408 // int numTexUnitsUsed = 0;
1409 // while (numTexUnitsUsed < dc.getNumTextureUnits() && numTilesRendered < tilesToRender.length)
1410 // {
1411 // if (this.setupTileTexture(dc, tilesToRender[numTilesRendered++],
1412 // GL.GL_TEXTURE0 + numTexUnitsUsed))
1413 // {
1414 // ++numTexUnitsUsed;
1415 // }
1416 // }
1418 // gl.glUniform1i(maxTexUnit, numTexUnitsUsed - 1);
1419 // sg.renderMultiTexture(dc, numTexUnitsUsed);
1420 // }
1421 // }
1422 //// System.out.println();
1424 // gl.glActiveTexture(GL.GL_TEXTURE0);
1425 // gl.glUseProgram(0);
1426 // gl.glMatrixMode(javax.media.opengl.GL.GL_TEXTURE);
1427 // gl.glPopMatrix();
1428 // }
1429 // catch (Exception e)
1430 // {
1431 // String message = WorldWind.retrieveErrMsg("generic.ExceptionWhileRenderingLayer");
1432 // message += this.getClass().getName();
1433 // WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
1434 // }
1435 // finally
1436 // {
1437 // gl.glPopAttrib();
1438 // }
1439 // }