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
.*;
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
.*;
17 import java
.awt
.image
.*;
22 import java
.util
.concurrent
.*;
26 * @version $Id: TiledImageLayer.java 2128 2007-06-22 08:18:32Z garakl $
28 public class TiledImageLayer
extends AbstractLayer
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;
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
)
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.
74 if (!this.retainLevelZeroTiles
)
77 for (TextureTile tile
: this.topLevels
)
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()
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
++)
190 Angle t1
= Tile
.computeColumnLongitude(firstCol
, dLon
);
191 for (int col
= firstCol
; col
<= lastCol
; col
++)
196 this.topLevels
.add(new TextureTile(new Sector(p1
, p2
, t1
, t2
), level
, row
, col
));
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
);
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
);
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
);
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
);
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
)
353 if (d3
< minDistance
)
355 if (d4
< minDistance
)
357 if (d5
< minDistance
)
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 ======================= //
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
);
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
);
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
);
413 if (this.drawTileIDs
)
414 this.drawTileIDs(dc
, this.currentTiles
);
416 if (this.drawBoundingVolumes
)
417 this.drawBoundingVolumes(dc
, this.currentTiles
);
419 this.currentTiles
.clear();
423 this.requestQ
.clear();
426 private void sendRequests()
428 RequestTask task
= this.requestQ
.poll();
431 if (!WorldWind
.threadedTaskService().isFull())
433 WorldWind
.threadedTaskService().addTask(task
);
435 task
= this.requestQ
.poll();
439 public boolean isLayerInView(DrawContext dc
)
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
);
468 return dc
.getGlobe().computePointFromPosition(pos
.getLatitude(), pos
.getLongitude(), 0d
);
474 private static class LevelComparer
implements Comparator
<TextureTile
>
476 public int compare(TextureTile ta
, TextureTile tb
)
481 if (ta
.holdsTexture())
482 la
= ta
.getLevelNumber();
484 la
= ta
.getFallbackTile().getLevelNumber();
486 if (tb
.holdsTexture())
487 lb
= tb
.getLevelNumber();
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);
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)
585 tile
.setTextureData(textureData
);
586 if (tile
.getLevelNumber() != 0 || !this.retainLevelZeroTiles
)
587 this.addTileToCache(tile
);
592 private static TextureData
readTexture(java
.net
.URL url
)
596 return TextureIO
.newTextureData(url
, false, null);
600 String message
= WorldWind
.retrieveErrMsg("layers.TextureLayer.ExceptionAttemptingToReadTextureFile");
601 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, message
+ url
, e
);
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())
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
);
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()))
642 // if(this instanceof JPIPLayer)
644 // JPIPLayer jpipLayer = (JPIPLayer)this;
645 // retriever = new JpipRetriever(url, tile, jpipLayer.getJ2KImage());
649 // String message = WorldWind.retrieveErrMsg("layers.JPIPLayer.UnexpectedLayerType");
650 // WorldWind.logger().log(java.util.logging.Level.FINE, message);
656 String message
= WorldWind
.retrieveErrMsg("layers.TextureLayer.ExceptionCreatingTextureUrl");
657 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, message
+ "unknown protocol " + url
);
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
)
700 // check to ensure load is still needed
701 if (this.layer
.isTextureInMemory(this.tile
))
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);
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
)
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
)
754 if (o
== null || getClass() != o
.getClass())
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
)
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
))
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
);
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
);
820 final File outFile
= WorldWind
.dataFileCache().newFile(this.tile
.getPath());
823 String msg
= WorldWind
.retrieveErrMsg("generic.CantCreateCacheFile") + this.tile
.getPath();
824 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, msg
);
828 if (outFile
.exists())
831 // TODO: Better, more generic and flexible handling of file-format type
834 String contentType
= r
.getContentType();
835 if (contentType
== 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());
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
);
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
);
879 this.layer
.firePropertyChange(AVKey
.LAYER
, null, this);
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
);
894 public Color
getColor(Angle latitude
, Angle longitude
, int levelNumber
)
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)
906 if (containingTile
== 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
);
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
);
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
);
928 return this.resolveColor(containingTile
, image
, latitude
, longitude
);
930 // All attempts to find the image have failed.
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
)
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);
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
);
963 BufferedImage image
= ImageIO
.read(new File(url
.toURI()));
966 return null; // TODO: warn
969 WorldWind
.memoryCache().add(cacheKey
, image
, image
.getRaster().getDataBuffer().getSize());
970 this.levels
.unmarkResourceAbsent(tile
);
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
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))
1006 // if(this instanceof JPIPLayer)
1008 // JPIPLayer jpipLayer = (JPIPLayer)this;
1009 // retriever = new JpipRetriever(resourceURL, tile, jpipLayer.getJ2KImage());
1013 // String message = WorldWind.retrieveErrMsg("layers.JPIPLayer.UnexpectedLayerType");
1014 // WorldWind.logger().log(java.util.logging.Level.FINE, message);
1022 e
.printStackTrace(); // TODO
1026 private TextureTile
getContainingTile(TextureTile tile
, Angle latitude
, Angle longitude
, int levelNumber
)
1028 if (!tile
.getSector().contains(latitude
, longitude
))
1031 if (tile
.getLevelNumber() == levelNumber
|| this.levels
.isFinalLevel(tile
.getLevelNumber()))
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
;
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
)
1099 public ByteBuffer
run(Retriever retriever
)
1101 if (!retriever
.getState().equals(Retriever
.RETRIEVER_STATE_SUCCESSFUL
))
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
);
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
];
1126 return null; // TODO: log error
1129 String path
= tile
.getPath().substring(0, tile
.getPath().lastIndexOf("."));
1132 final File outFile
= WorldWind
.dataFileCache().newFile(path
);
1133 if (outFile
== null)
1135 String msg
= WorldWind
.retrieveErrMsg("generic.CantCreateCacheFile")
1137 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, msg
);
1143 WWIO
.saveBuffer(buffer
, outFile
);
1146 catch (IOException e
)
1148 e
.printStackTrace(); // TODO: log error
1156 // private void renderTiles2(DrawContext dc)
1158 // // Render all the tiles collected during assembleTiles()
1159 // GL gl = dc.getGL();
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);
1172 // gl.glEnable(GL.GL_DEPTH_TEST);
1173 // gl.glDepthFunc(GL.GL_LEQUAL);
1175 // if (this.useTransparentTextures)
1177 // gl.glEnable(GL.GL_BLEND);
1178 // gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
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())
1196 // TextureTile[] tilesToRender = this.getIntersectingTiles(sg);
1197 // if (tilesToRender == null)
1199 //// System.out.printf("%d, ", tilesToRender.length);
1201 // int numTilesRendered = 0;
1202 // while (numTilesRendered < tilesToRender.length)
1204 // int numTexUnitsUsed = 0;
1205 // while (numTexUnitsUsed < dc.getNumTextureUnits() && numTilesRendered < tilesToRender.length)
1207 // if (this.setupTileTexture(dc, tilesToRender[numTilesRendered++],
1208 // GL.GL_TEXTURE0 + numTexUnitsUsed))
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);
1226 // sg.renderMultiTexture(dc, numTexUnitsUsed);
1228 // // Turn all the multi-texture units off
1229 // for (int i = 0; i < dc.getNumTextureUnits(); i++)
1231 // gl.glActiveTexture(GL.GL_TEXTURE0 + i);
1232 // gl.glDisable(GL.GL_TEXTURE_2D);
1234 // gl.glActiveTexture(GL.GL_TEXTURE0);
1237 //// System.out.println();
1239 // gl.glMatrixMode(javax.media.opengl.GL.GL_TEXTURE);
1240 // gl.glPopMatrix();
1242 // catch (Exception e)
1244 // String message = WorldWind.retrieveErrMsg("generic.ExceptionWhileRenderingLayer");
1245 // message += this.getClass().getName();
1246 // WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
1250 // gl.glPopAttrib();
1254 // private static String[] buildFragmentShader(int numTexUnits)
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()");
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++)
1269 // lines.add("if (" + i + " <= maxTexUnit)");
1271 // lines.add("if (all(greaterThanEqual(gl_TexCoord[" + i + "].st, zero))"
1272 // + " && all(lessThanEqual(gl_TexCoord[" + i + "].st, one)))");
1274 // lines.add(" color = texture2D(tex[" + i + "], gl_TexCoord[" + i + "].st);");
1275 // lines.add(" hasColor = true;");
1279 // for (int i = 0; i < numTexUnits; i++)
1284 // lines.add(" if(hasColor)");
1285 // lines.add(" gl_FragColor = color;");
1286 // lines.add(" else");
1287 // lines.add(" discard;");
1290 // return lines.toArray(new String[lines.size()]);
1293 // private int fProgram = -1;
1295 // private void initFragmentProgram(int numTextureUnits)
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++)
1305 // lineCounts[i] = lines[i].length();
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)
1317 // gl.glGetShaderInfoLog(fShader, log.length, logLength, 0, log, 0);
1318 // String sLog = new String(log);
1319 // System.out.println("Compile : " + sLog);
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)
1329 // gl.glGetShaderInfoLog(fShader, log.length, logLength, 0, log, 0);
1330 // String sLog = new String(log);
1331 // System.out.println("Link : " + sLog);
1335 // private String[] texUnitPositions;
1336 // private int[] texSamplers;
1338 // private void renderTiles3(DrawContext dc)
1340 // // Render all the tiles collected during assembleTiles()
1341 // GL gl = dc.getGL();
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);
1354 // dc.setNumTextureUnits(1);
1355 // if (this.fProgram == -1)
1356 // this.initFragmentProgram(dc.getNumTextureUnits());
1358 // if (this.texUnitPositions == null)
1360 // this.texUnitPositions = new String[dc.getNumTextureUnits()];
1361 // for (int i = 0; i < dc.getNumTextureUnits(); i++)
1363 // this.texUnitPositions[i] = "tex[" + i + "]";
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++)
1374 // this.texSamplers[i] = gl.glGetUniformLocation(fProgram, "tex[" + i + "]");
1375 // gl.glUniform1i(this.texSamplers[i], i);
1378 // int maxTexUnit = gl.glGetUniformLocation(fProgram, "maxTexUnit");
1380 // gl.glEnable(GL.GL_DEPTH_TEST);
1381 // gl.glDepthFunc(GL.GL_LEQUAL);
1383 // if (this.useTransparentTextures)
1385 // gl.glEnable(GL.GL_BLEND);
1386 // gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
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())
1400 // TextureTile[] tilesToRender = this.getIntersectingTiles(sg);
1401 // if (tilesToRender == null)
1403 //// System.out.printf("%d, ", tilesToRender.length);
1405 // int numTilesRendered = 0;
1406 // while (numTilesRendered < tilesToRender.length)
1408 // int numTexUnitsUsed = 0;
1409 // while (numTexUnitsUsed < dc.getNumTextureUnits() && numTilesRendered < tilesToRender.length)
1411 // if (this.setupTileTexture(dc, tilesToRender[numTilesRendered++],
1412 // GL.GL_TEXTURE0 + numTexUnitsUsed))
1414 // ++numTexUnitsUsed;
1418 // gl.glUniform1i(maxTexUnit, numTexUnitsUsed - 1);
1419 // sg.renderMultiTexture(dc, numTexUnitsUsed);
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();
1429 // catch (Exception e)
1431 // String message = WorldWind.retrieveErrMsg("generic.ExceptionWhileRenderingLayer");
1432 // message += this.getClass().getName();
1433 // WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
1437 // gl.glPopAttrib();