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
;
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 3685 2007-12-03 19:51:05Z 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
;
41 private double splitScale
= 0.9; // TODO: Make configurable
44 private boolean showImageTileOutlines
= false;
45 private boolean drawTileBoundaries
= false;
46 private boolean useTransparentTextures
= false;
47 private boolean drawTileIDs
= false;
48 private boolean drawBoundingVolumes
= false;
49 private TextRenderer textRenderer
= null;
51 // Stuff computed each frame
52 private ArrayList
<TextureTile
> currentTiles
= new ArrayList
<TextureTile
>();
53 private TextureTile currentResourceTile
;
54 private Vec4 referencePoint
;
55 private boolean atMaxResolution
= false;
56 private PriorityBlockingQueue
<Runnable
> requestQ
= new PriorityBlockingQueue
<Runnable
>(200);
58 abstract protected void requestTexture(DrawContext dc
, TextureTile tile
);
60 abstract protected void forceTextureLoad(TextureTile tile
);
62 public TiledImageLayer(LevelSet levelSet
)
66 String message
= Logging
.getMessage("nullValue.LevelSetIsNull");
67 Logging
.logger().severe(message
);
68 throw new IllegalArgumentException(message
);
71 this.levels
= new LevelSet(levelSet
); // the caller's levelSet may change internally, so we copy it.
73 this.createTopLevelTiles();
75 this.setPickEnabled(false); // textures are assumed to be terrain unless specifically indicated otherwise.
76 this.tileCountName
= this.getName() + " Tiles";
80 public void setName(String name
)
83 this.tileCountName
= this.getName() + " Tiles";
86 public boolean isUseTransparentTextures()
88 return this.useTransparentTextures
;
91 public void setUseTransparentTextures(boolean useTransparentTextures
)
93 this.useTransparentTextures
= useTransparentTextures
;
96 public boolean isForceLevelZeroLoads()
98 return this.forceLevelZeroLoads
;
101 public void setForceLevelZeroLoads(boolean forceLevelZeroLoads
)
103 this.forceLevelZeroLoads
= forceLevelZeroLoads
;
106 public boolean isRetainLevelZeroTiles()
108 return retainLevelZeroTiles
;
111 public void setRetainLevelZeroTiles(boolean retainLevelZeroTiles
)
113 this.retainLevelZeroTiles
= retainLevelZeroTiles
;
116 public boolean isDrawTileIDs()
121 public void setDrawTileIDs(boolean drawTileIDs
)
123 this.drawTileIDs
= drawTileIDs
;
126 public boolean isDrawTileBoundaries()
128 return drawTileBoundaries
;
131 public void setDrawTileBoundaries(boolean drawTileBoundaries
)
133 this.drawTileBoundaries
= drawTileBoundaries
;
136 public boolean isShowImageTileOutlines()
138 return showImageTileOutlines
;
141 public void setShowImageTileOutlines(boolean showImageTileOutlines
)
143 this.showImageTileOutlines
= showImageTileOutlines
;
146 public boolean isDrawBoundingVolumes()
148 return drawBoundingVolumes
;
151 public void setDrawBoundingVolumes(boolean drawBoundingVolumes
)
153 this.drawBoundingVolumes
= drawBoundingVolumes
;
156 protected LevelSet
getLevels()
161 protected void setSplitScale(double splitScale
)
163 this.splitScale
= splitScale
;
166 protected PriorityBlockingQueue
<Runnable
> getRequestQ()
171 public boolean isMultiResolution()
173 return this.getLevels() != null && this.getLevels().getNumLevels() > 1;
176 public boolean isAtMaxResolution()
178 return this.atMaxResolution
;
181 private void createTopLevelTiles()
183 Sector sector
= this.levels
.getSector();
185 Angle dLat
= this.levels
.getLevelZeroTileDelta().getLatitude();
186 Angle dLon
= this.levels
.getLevelZeroTileDelta().getLongitude();
188 // Determine the row and column offset from the common World Wind global tiling origin.
189 Level level
= levels
.getFirstLevel();
190 int firstRow
= Tile
.computeRow(level
.getTileDelta().getLatitude(), sector
.getMinLatitude());
191 int firstCol
= Tile
.computeColumn(level
.getTileDelta().getLongitude(), sector
.getMinLongitude());
192 int lastRow
= Tile
.computeRow(level
.getTileDelta().getLatitude(), sector
.getMaxLatitude());
193 int lastCol
= Tile
.computeColumn(level
.getTileDelta().getLongitude(), sector
.getMaxLongitude());
195 int nLatTiles
= lastRow
- firstRow
+ 1;
196 int nLonTiles
= lastCol
- firstCol
+ 1;
198 this.topLevels
= new ArrayList
<TextureTile
>(nLatTiles
* nLonTiles
);
200 Angle p1
= Tile
.computeRowLatitude(firstRow
, dLat
);
201 for (int row
= firstRow
; row
<= lastRow
; row
++)
206 Angle t1
= Tile
.computeColumnLongitude(firstCol
, dLon
);
207 for (int col
= firstCol
; col
<= lastCol
; col
++)
212 this.topLevels
.add(new TextureTile(new Sector(p1
, p2
, t1
, t2
), level
, row
, col
));
219 private void loadAllTopLevelTextures(DrawContext dc
)
221 for (TextureTile tile
: this.topLevels
)
223 if (!tile
.isTextureInMemory(dc
.getTextureCache()))
224 this.forceTextureLoad(tile
);
227 this.levelZeroLoaded
= true;
230 // ============== Tile Assembly ======================= //
231 // ============== Tile Assembly ======================= //
232 // ============== Tile Assembly ======================= //
234 private void assembleTiles(DrawContext dc
)
236 this.currentTiles
.clear();
238 for (TextureTile tile
: this.topLevels
)
240 if (this.isTileVisible(dc
, tile
))
242 this.currentResourceTile
= null;
243 this.addTileOrDescendants(dc
, tile
);
248 private void addTileOrDescendants(DrawContext dc
, TextureTile tile
)
250 if (this.meetsRenderCriteria(dc
, tile
))
252 this.addTile(dc
, tile
);
256 // The incoming tile does not meet the rendering criteria, so it must be subdivided and those
257 // subdivisions tested against the criteria.
259 // All tiles that meet the selection criteria are drawn, but some of those tiles will not have
260 // textures associated with them either because their texture isn't loaded yet or because they
261 // are finer grain than the layer has textures for. In these cases the tiles use the texture of
262 // the closest ancestor that has a texture loaded. This ancestor is called the currentResourceTile.
263 // A texture transform is applied during rendering to align the sector's texture coordinates with the
264 // appropriate region of the ancestor's texture.
266 TextureTile ancestorResource
= null;
270 // At this point the tile does not meet the render criteria but it may have its texture in memory.
271 // If so, register this tile as the resource tile. If not, then this tile will be the next level
272 // below a tile with texture in memory. So to provide progressive resolution increase, add this tile
273 // to the draw list. That will cause the tile to be drawn using its parent tile's texture, and it will
274 // cause it's texture to be requested. At some future call to this method the tile's texture will be in
275 // memory, it will not meet the render criteria, but will serve as the parent to a tile that goes
276 // through this same process as this method recurses. The result of all this is that a tile isn't rendered
277 // with its own texture unless all its parents have their textures loaded. In addition to causing
278 // progressive resolution increase, this ensures that the parents are available as the user zooms out, and
279 // therefore the layer remains visible until the user is zoomed out to the point the layer is no longer
281 if (tile
.isTextureInMemory(dc
.getTextureCache()) || tile
.getLevelNumber() == 0)
283 ancestorResource
= this.currentResourceTile
;
284 this.currentResourceTile
= tile
;
286 else if (!tile
.getLevel().isEmpty())
288 this.addTile(dc
, tile
);
292 TextureTile
[] subTiles
= tile
.createSubTiles(this.levels
.getLevel(tile
.getLevelNumber() + 1));
293 for (TextureTile child
: subTiles
)
295 if (this.isTileVisible(dc
, child
))
296 this.addTileOrDescendants(dc
, child
);
301 if (ancestorResource
!= null) // Pop this tile as the currentResource ancestor
302 this.currentResourceTile
= ancestorResource
;
306 private void addTile(DrawContext dc
, TextureTile tile
)
308 tile
.setFallbackTile(null);
310 if (tile
.isTextureInMemory(dc
.getTextureCache()))
312 // System.out.printf("Sector %s, min = %f, max = %f\n", tile.getSector(),
313 // dc.getGlobe().getMinElevation(tile.getSector()), dc.getGlobe().getMaxElevation(tile.getSector()));
314 this.addTileToCurrent(tile
);
318 // Level 0 loads may be forced
319 if (tile
.getLevelNumber() == 0 && this.forceLevelZeroLoads
&& !tile
.isTextureInMemory(dc
.getTextureCache()))
321 this.forceTextureLoad(tile
);
322 if (tile
.isTextureInMemory(dc
.getTextureCache()))
324 this.addTileToCurrent(tile
);
329 // Tile's texture isn't available, so request it
330 if (tile
.getLevelNumber() < this.levels
.getNumLevels())
332 // Request only tiles with data associated at this level
333 if (!this.levels
.isResourceAbsent(tile
))
334 this.requestTexture(dc
, tile
);
337 // Set up to use the currentResource tile's texture
338 if (this.currentResourceTile
!= null)
340 if (this.currentResourceTile
.getLevelNumber() == 0 && this.forceLevelZeroLoads
&&
341 !this.currentResourceTile
.isTextureInMemory(dc
.getTextureCache()) &&
342 !this.currentResourceTile
.isTextureInMemory(dc
.getTextureCache()))
343 this.forceTextureLoad(this.currentResourceTile
);
345 if (this.currentResourceTile
.isTextureInMemory(dc
.getTextureCache()))
347 tile
.setFallbackTile(currentResourceTile
);
348 this.addTileToCurrent(tile
);
353 private void addTileToCurrent(TextureTile tile
)
355 this.currentTiles
.add(tile
);
358 private boolean isTileVisible(DrawContext dc
, TextureTile tile
)
360 // if (!(tile.getExtent(dc).intersects(dc.getView().getFrustumInModelCoordinates())
361 // && (dc.getVisibleSector() == null || dc.getVisibleSector().intersects(tile.getSector()))))
364 // Position eyePos = dc.getView().getEyePosition();
365 // LatLon centroid = tile.getSector().getCentroid();
366 // Angle d = LatLon.sphericalDistance(eyePos.getLatLon(), centroid);
367 // if ((!tile.getLevelName().equals("0")) && d.compareTo(tile.getSector().getDeltaLat().multiply(2.5)) == 1)
372 return tile
.getExtent(dc
).intersects(dc
.getView().getFrustumInModelCoordinates()) &&
373 (dc
.getVisibleSector() == null || dc
.getVisibleSector().intersects(tile
.getSector()));
376 // private boolean meetsRenderCriteria2(DrawContext dc, TextureTile tile)
378 // if (this.levels.isFinalLevel(tile.getLevelNumber()))
381 // Sector sector = tile.getSector();
382 // Vec4[] corners = sector.computeCornerPoints(dc.getGlobe());
383 // Vec4 centerPoint = sector.computeCenterPoint(dc.getGlobe());
385 // View view = dc.getView();
386 // double d1 = view.getEyePoint().distanceTo3(corners[0]);
387 // double d2 = view.getEyePoint().distanceTo3(corners[1]);
388 // double d3 = view.getEyePoint().distanceTo3(corners[2]);
389 // double d4 = view.getEyePoint().distanceTo3(corners[3]);
390 // double d5 = view.getEyePoint().distanceTo3(centerPoint);
392 // double minDistance = d1;
393 // if (d2 < minDistance)
395 // if (d3 < minDistance)
397 // if (d4 < minDistance)
399 // if (d5 < minDistance)
403 // if (minDistance == d1)
404 // r = corners[0].getLength3();
405 // if (minDistance == d2)
406 // r = corners[1].getLength3();
407 // if (minDistance == d3)
408 // r = corners[2].getLength3();
409 // if (minDistance == d4)
410 // r = corners[3].getLength3();
411 // if (minDistance == d5)
412 // r = centerPoint.getLength3();
414 // double texelSize = tile.getLevel().getTexelSize(r);
415 // double pixelSize = dc.getView().computePixelSizeAtDistance(minDistance);
417 // return 2 * pixelSize >= texelSize;
420 private boolean meetsRenderCriteria(DrawContext dc
, TextureTile tile
)
422 return this.levels
.isFinalLevel(tile
.getLevelNumber()) || !needToSplit(dc
, tile
.getSector());
425 private boolean needToSplit(DrawContext dc
, Sector sector
)
427 Vec4
[] corners
= sector
.computeCornerPoints(dc
.getGlobe());
428 Vec4 centerPoint
= sector
.computeCenterPoint(dc
.getGlobe());
430 View view
= dc
.getView();
431 double d1
= view
.getEyePoint().distanceTo3(corners
[0]);
432 double d2
= view
.getEyePoint().distanceTo3(corners
[1]);
433 double d3
= view
.getEyePoint().distanceTo3(corners
[2]);
434 double d4
= view
.getEyePoint().distanceTo3(corners
[3]);
435 double d5
= view
.getEyePoint().distanceTo3(centerPoint
);
437 double minDistance
= d1
;
438 if (d2
< minDistance
)
440 if (d3
< minDistance
)
442 if (d4
< minDistance
)
444 if (d5
< minDistance
)
447 double cellSize
= (Math
.PI
* sector
.getDeltaLatRadians() * dc
.getGlobe().getRadius()) / 20; // TODO
449 return !(Math
.log10(cellSize
) <= (Math
.log10(minDistance
) - this.splitScale
));
452 private boolean atMaxLevel(DrawContext dc
)
454 Position vpc
= dc
.getViewportCenterPosition();
455 if (dc
.getView() == null || this.getLevels() == null || vpc
== null)
458 if (!this.getLevels().getSector().contains(vpc
.getLatitude(), vpc
.getLongitude()))
461 Level nextToLast
= this.getLevels().getNextToLastLevel();
462 if (nextToLast
== null)
465 Sector centerSector
= nextToLast
.computeSectorForPosition(vpc
.getLatitude(), vpc
.getLongitude());
466 return this.needToSplit(dc
, centerSector
);
469 // ============== Rendering ======================= //
470 // ============== Rendering ======================= //
471 // ============== Rendering ======================= //
474 public void render(DrawContext dc
)
476 this.atMaxResolution
= this.atMaxLevel(dc
);
481 protected final void doRender(DrawContext dc
)
483 if (this.forceLevelZeroLoads
&& !this.levelZeroLoaded
)
484 this.loadAllTopLevelTextures(dc
);
485 if (dc
.getSurfaceGeometry() == null || dc
.getSurfaceGeometry().size() < 1)
486 return; // TODO: throw an illegal state exception?
488 dc
.getGeographicSurfaceTileRenderer().setShowImageTileOutlines(this.showImageTileOutlines
);
493 private void draw(DrawContext dc
)
495 this.referencePoint
= this.computeReferencePoint(dc
);
497 this.assembleTiles(dc
); // Determine the tiles to draw.
499 if (this.currentTiles
.size() >= 1)
501 TextureTile
[] sortedTiles
= new TextureTile
[this.currentTiles
.size()];
502 sortedTiles
= this.currentTiles
.toArray(sortedTiles
);
503 Arrays
.sort(sortedTiles
, levelComparer
);
507 if (this.isUseTransparentTextures() || this.getOpacity() < 1)
509 gl
.glPushAttrib(GL
.GL_COLOR_BUFFER_BIT
| GL
.GL_POLYGON_BIT
| GL
.GL_CURRENT_BIT
);
510 gl
.glColor4d(1d
, 1d
, 1d
, this.getOpacity());
511 gl
.glEnable(GL
.GL_BLEND
);
512 gl
.glBlendFunc(GL
.GL_SRC_ALPHA
, GL
.GL_ONE_MINUS_SRC_ALPHA
);
516 gl
.glPushAttrib(GL
.GL_COLOR_BUFFER_BIT
| GL
.GL_POLYGON_BIT
);
519 gl
.glPolygonMode(GL
.GL_FRONT
, GL
.GL_FILL
);
520 gl
.glEnable(GL
.GL_CULL_FACE
);
521 gl
.glCullFace(GL
.GL_BACK
);
523 dc
.setPerFrameStatistic(PerformanceStatistic
.IMAGE_TILE_COUNT
, this.tileCountName
,
524 this.currentTiles
.size());
525 dc
.getGeographicSurfaceTileRenderer().renderTiles(dc
, this.currentTiles
);
529 if (this.drawTileIDs
)
530 this.drawTileIDs(dc
, this.currentTiles
);
532 if (this.drawBoundingVolumes
)
533 this.drawBoundingVolumes(dc
, this.currentTiles
);
535 this.currentTiles
.clear();
539 this.requestQ
.clear();
542 private void sendRequests()
544 Runnable task
= this.requestQ
.poll();
547 if (!WorldWind
.getTaskService().isFull())
549 WorldWind
.getTaskService().addTask(task
);
551 task
= this.requestQ
.poll();
555 public boolean isLayerInView(DrawContext dc
)
559 String message
= Logging
.getMessage("nullValue.DrawContextIsNull");
560 Logging
.logger().severe(message
);
561 throw new IllegalStateException(message
);
564 if (dc
.getView() == null)
566 String message
= Logging
.getMessage("layers.AbstractLayer.NoViewSpecifiedInDrawingContext");
567 Logging
.logger().severe(message
);
568 throw new IllegalStateException(message
);
571 return !(dc
.getVisibleSector() != null && !this.levels
.getSector().intersects(dc
.getVisibleSector()));
574 private Vec4
computeReferencePoint(DrawContext dc
)
576 if (dc
.getViewportCenterPosition() != null)
577 return dc
.getGlobe().computePointFromPosition(dc
.getViewportCenterPosition());
579 java
.awt
.geom
.Rectangle2D viewport
= dc
.getView().getViewport();
580 int x
= (int) viewport
.getWidth() / 2;
581 for (int y
= (int) (0.5 * viewport
.getHeight()); y
>= 0; y
--)
583 Position pos
= dc
.getView().computePositionFromScreenPoint(x
, y
);
587 return dc
.getGlobe().computePointFromPosition(pos
.getLatitude(), pos
.getLongitude(), 0d
);
593 protected Vec4
getReferencePoint()
595 return this.referencePoint
;
598 private static class LevelComparer
implements Comparator
<TextureTile
>
600 public int compare(TextureTile ta
, TextureTile tb
)
602 int la
= ta
.getFallbackTile() == null ? ta
.getLevelNumber() : ta
.getFallbackTile().getLevelNumber();
603 int lb
= tb
.getFallbackTile() == null ? tb
.getLevelNumber() : tb
.getFallbackTile().getLevelNumber();
605 return la
< lb ?
-1 : la
== lb ?
0 : 1;
609 private void drawTileIDs(DrawContext dc
, ArrayList
<TextureTile
> tiles
)
611 java
.awt
.Rectangle viewport
= dc
.getView().getViewport();
612 if (this.textRenderer
== null)
613 this.textRenderer
= new TextRenderer(java
.awt
.Font
.decode("Arial-Plain-13"), true, true);
615 dc
.getGL().glDisable(GL
.GL_DEPTH_TEST
);
616 dc
.getGL().glDisable(GL
.GL_BLEND
);
617 dc
.getGL().glDisable(GL
.GL_TEXTURE_2D
);
619 this.textRenderer
.setColor(java
.awt
.Color
.YELLOW
);
620 this.textRenderer
.beginRendering(viewport
.width
, viewport
.height
);
621 for (TextureTile tile
: tiles
)
623 String tileLabel
= tile
.getLabel();
625 if (tile
.getFallbackTile() != null)
626 tileLabel
+= "/" + tile
.getFallbackTile().getLabel();
628 LatLon ll
= tile
.getSector().getCentroid();
629 Vec4 pt
= dc
.getGlobe().computePointFromPosition(ll
.getLatitude(), ll
.getLongitude(),
630 dc
.getGlobe().getElevation(ll
.getLatitude(), ll
.getLongitude()));
631 pt
= dc
.getView().project(pt
);
632 this.textRenderer
.draw(tileLabel
, (int) pt
.x
, (int) pt
.y
);
634 this.textRenderer
.endRendering();
637 private void drawBoundingVolumes(DrawContext dc
, ArrayList
<TextureTile
> tiles
)
639 float[] previousColor
= new float[4];
640 dc
.getGL().glGetFloatv(GL
.GL_CURRENT_COLOR
, previousColor
, 0);
641 dc
.getGL().glColor3d(0, 1, 0);
643 for (TextureTile tile
: tiles
)
645 ((Cylinder
) tile
.getExtent(dc
)).render(dc
);
649 dc
.getGlobe().computeBoundingCylinder(dc
.getVerticalExaggeration(), this.levels
.getSector());
650 dc
.getGL().glColor3d(1, 1, 0);
653 dc
.getGL().glColor4fv(previousColor
, 0);
656 // private TextureTile getContainingTile(TextureTile tile, Angle latitude, Angle longitude, int levelNumber)
658 // if (!tile.getSector().contains(latitude, longitude))
661 // if (tile.getLevelNumber() == levelNumber || this.levels.isFinalLevel(tile.getLevelNumber()))
664 // TextureTile containingTile;
665 // TextureTile[] subTiles = tile.createSubTiles(this.levels.getLevel(tile.getLevelNumber() + 1));
666 // for (TextureTile child : subTiles)
668 // containingTile = this.getContainingTile(child, latitude, longitude, levelNumber);
669 // if (containingTile != null)
670 // return containingTile;
676 // ============== Image Composition ======================= //
677 // ============== Image Composition ======================= //
678 // ============== Image Composition ======================= //
680 private final static String
[] formats
= new String
[] {"jpg", "jpeg", "png", "tiff"};
681 private final static String
[] suffixes
= new String
[] {".jpg", ".jpg", ".png", ".tiff"};
683 private BufferedImage
requestImage(TextureTile tile
)
686 String pathBase
= tile
.getPath().substring(0, tile
.getPath().lastIndexOf("."));
687 for (String suffix
: suffixes
)
689 String path
= pathBase
+ suffix
;
690 url
= WorldWind
.getDataFileCache().findFile(path
, false);
698 if (WWIO
.isFileOutOfDate(url
, tile
.getLevel().getExpiryTime()))
700 // The file has expired. Delete it then request download of newer.
701 WorldWind
.getDataFileCache().removeFile(url
);
702 String message
= Logging
.getMessage("generic.DataFileExpired", url
);
703 Logging
.logger().fine(message
);
709 BufferedImage image
= ImageIO
.read(new File(url
.toURI()));
712 return null; // TODO: warn
715 this.levels
.unmarkResourceAbsent(tile
);
718 catch (IOException e
)
720 // Assume that something's wrong with the file and delete it.
721 gov
.nasa
.worldwind
.WorldWind
.getDataFileCache().removeFile(url
);
722 this.levels
.markResourceAbsent(tile
);
723 String message
= Logging
.getMessage("generic.DeletedCorruptDataFile", url
);
724 Logging
.logger().info(message
);
726 catch (URISyntaxException e
)
728 e
.printStackTrace(); // TODO
735 private void downloadImage(final TextureTile tile
)
739 String urlString
= tile
.getResourceURL().toExternalForm().replace("dds", "");
740 final URL resourceURL
= new URL(urlString
);
743 String protocol
= resourceURL
.getProtocol();
745 if ("http".equalsIgnoreCase(protocol
))
747 retriever
= new HTTPRetriever(resourceURL
, new HttpRetrievalPostProcessor(tile
));
752 Logging
.logger().severe(
753 Logging
.getMessage("layers.TextureLayer.UnknownRetrievalProtocol", resourceURL
));
761 e
.printStackTrace(); // TODO
765 private static class ImageSector
767 private final BufferedImage image
;
768 private final Sector sector
;
770 public ImageSector(BufferedImage image
, Sector sector
)
773 this.sector
= sector
;
777 public BufferedImage
composeImageForSector(Sector sector
, int imageSize
)
779 return this.composeImageForSector(sector
, imageSize
, -1);
782 public BufferedImage
composeImageForSector(Sector sector
, int imageSize
, int levelNumber
)
784 TiledImageLayer
.ImageSector
[][] imageSectors
= this.getImagesInSector(sector
, levelNumber
);
786 if (imageSectors
== null)
788 System
.out
.println("No images available."); // TODO
792 Sector actualSector
= null;
793 for (TiledImageLayer
.ImageSector
[] isa
: imageSectors
)
795 for (TiledImageLayer
.ImageSector is
: isa
)
797 actualSector
= Sector
.union(actualSector
, is
.sector
);
801 int ny
= imageSectors
.length
;
802 int nx
= imageSectors
[0].length
;
804 int overallHeight
= ny
* imageSectors
[0][0].image
.getHeight();
805 int overallWidth
= nx
* imageSectors
[0][0].image
.getWidth();
809 if (overallHeight
>= overallWidth
)
811 tileHeight
= imageSize
/ ny
;
812 double aspect
= (double) overallWidth
/ (double) overallHeight
;
813 tileWidth
= (int) (aspect
* imageSize
/ nx
);
817 tileWidth
= imageSize
/ nx
;
818 double aspect
= (double) overallHeight
/ (double) overallWidth
;
819 tileHeight
= (int) (aspect
* imageSize
/ ny
);
822 int imageHeight
= tileHeight
* imageSectors
.length
;
823 int imageWidth
= tileWidth
* imageSectors
[0].length
;
825 //noinspection ConstantConditions
826 double sh
= 1 / sector
.getDeltaLat().divide(actualSector
.getDeltaLat());
827 double sw
= 1 / sector
.getDeltaLon().divide(actualSector
.getDeltaLon());
828 double dh
= -(actualSector
.getMaxLatitude().subtract(sector
.getMaxLatitude()).divide(actualSector
.getDeltaLat())
831 sector
.getMinLongitude().subtract(actualSector
.getMinLongitude()).divide(actualSector
.getDeltaLon())
834 BufferedImage image
= new BufferedImage(imageWidth
, imageHeight
, BufferedImage
.TYPE_INT_RGB
);
835 Graphics2D g
= image
.createGraphics();
840 for (TiledImageLayer
.ImageSector
[] row
: imageSectors
)
843 for (TiledImageLayer
.ImageSector is
: row
)
845 Image img
= is
.image
.getScaledInstance(tileWidth
, tileHeight
, Image
.SCALE_SMOOTH
);
846 g
.drawImage(img
, x
, y
, null);
855 private ImageSector
[][] getImagesInSector(Sector sector
, int levelNumber
)
859 String msg
= Logging
.getMessage("nullValue.SectorIsNull");
860 Logging
.logger().severe(msg
);
861 throw new IllegalArgumentException(msg
);
864 // TODO: check level number arg
866 Level targetLevel
= this.levels
.getLastLevel();
867 if (levelNumber
>= 0)
869 for (int i
= levelNumber
; i
< this.getLevels().getLastLevel().getLevelNumber(); i
++)
871 if (this.levels
.isLevelEmpty(i
))
874 targetLevel
= this.levels
.getLevel(i
);
879 // Collect all the tiles intersecting the input sector.
880 LatLon delta
= targetLevel
.getTileDelta();
881 final int nwRow
= Tile
.computeRow(delta
.getLatitude(), sector
.getMaxLatitude());
882 final int nwCol
= Tile
.computeColumn(delta
.getLongitude(), sector
.getMinLongitude());
883 final int seRow
= Tile
.computeRow(delta
.getLatitude(), sector
.getMinLatitude());
884 final int seCol
= Tile
.computeColumn(delta
.getLongitude(), sector
.getMaxLongitude());
886 int numRows
= nwRow
- seRow
+ 1;
887 int numCols
= seCol
- nwCol
+ 1;
888 ImageSector
[][] imageSectors
= new ImageSector
[numRows
][numCols
];
890 for (int row
= nwRow
; row
>= seRow
; row
--)
892 for (int col
= nwCol
; col
<= seCol
; col
++)
894 TileKey key
= new TileKey(targetLevel
.getLevelNumber(), row
, col
, targetLevel
.getCacheName());
895 Sector tileSector
= this.levels
.computeSectorForKey(key
);
896 TextureTile textureTile
= new TextureTile(tileSector
, targetLevel
, row
, col
);
897 BufferedImage image
= this.getImage(textureTile
);
899 imageSectors
[nwRow
- row
][col
- nwCol
] = new ImageSector(image
, textureTile
.getSector());
906 private BufferedImage
getImage(TextureTile tile
)
910 // Read the image from disk.
911 BufferedImage image
= this.requestImage(tile
);
915 // Retrieve it from the net since it's not on disk.
916 for (int i
= 0; i
< 10; i
++)
918 this.downloadImage(tile
);
920 // Try to read from disk again after retrieving it from the net.
921 image
= this.requestImage(tile
);
926 // All attempts to find the image have failed.
930 private class HttpRetrievalPostProcessor
implements RetrievalPostProcessor
932 private TextureTile tile
;
934 public HttpRetrievalPostProcessor(TextureTile tile
)
939 public ByteBuffer
run(Retriever retriever
)
941 if (!retriever
.getState().equals(Retriever
.RETRIEVER_STATE_SUCCESSFUL
))
944 HTTPRetriever htr
= (HTTPRetriever
) retriever
;
945 if (htr
.getResponseCode() == HttpURLConnection
.HTTP_NO_CONTENT
)
947 // Mark tile as missing to avoid excessive attempts
948 TiledImageLayer
.this.levels
.markResourceAbsent(tile
);
952 if (htr
.getResponseCode() != HttpURLConnection
.HTTP_OK
)
955 URLRetriever r
= (URLRetriever
) retriever
;
956 ByteBuffer buffer
= r
.getBuffer();
958 String suffix
= null;
959 for (int i
= 0; i
< formats
.length
; i
++)
961 if (htr
.getContentType().toLowerCase().contains(formats
[i
]))
963 suffix
= suffixes
[i
];
969 return null; // TODO: logger error
972 String path
= tile
.getPath().substring(0, tile
.getPath().lastIndexOf("."));
975 final File outFile
= WorldWind
.getDataFileCache().newFile(path
);
981 WWIO
.saveBuffer(buffer
, outFile
);
984 catch (IOException e
)
986 e
.printStackTrace(); // TODO: logger error