Update to Worldwind release 0.4.0
[worldwind-tracker.git] / gov / nasa / worldwind / layers / TiledImageLayer.java
blob7986787890381b3f2ce9d8f076628f2cf02beeb4
1 /*
2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
5 All Rights Reserved.
6 */
7 package gov.nasa.worldwind.layers;
9 import com.sun.opengl.util.j2d.TextRenderer;
10 import gov.nasa.worldwind.WorldWind;
11 import gov.nasa.worldwind.geom.*;
12 import gov.nasa.worldwind.render.DrawContext;
13 import gov.nasa.worldwind.retrieve.*;
14 import gov.nasa.worldwind.util.*;
15 import gov.nasa.worldwind.View;
17 import javax.imageio.ImageIO;
18 import javax.media.opengl.GL;
19 import java.awt.*;
20 import java.awt.image.*;
21 import java.io.*;
22 import java.net.*;
23 import java.nio.ByteBuffer;
24 import java.util.*;
25 import java.util.concurrent.PriorityBlockingQueue;
27 /**
28 * @author tag
29 * @version $Id: TiledImageLayer.java 3685 2007-12-03 19:51:05Z tgaskins $
31 public abstract class TiledImageLayer extends AbstractLayer
33 // Infrastructure
34 private static final LevelComparer levelComparer = new LevelComparer();
35 private final LevelSet levels;
36 private ArrayList<TextureTile> topLevels;
37 private boolean forceLevelZeroLoads = false;
38 private boolean levelZeroLoaded = false;
39 private boolean retainLevelZeroTiles = false;
40 private String tileCountName;
41 private double splitScale = 0.9; // TODO: Make configurable
43 // Diagnostic flags
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)
64 if (levelSet == null)
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";
79 @Override
80 public void setName(String name)
82 super.setName(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()
118 return drawTileIDs;
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()
158 return levels;
161 protected void setSplitScale(double splitScale)
163 this.splitScale = splitScale;
166 protected PriorityBlockingQueue<Runnable> getRequestQ()
168 return requestQ;
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++)
203 Angle p2;
204 p2 = p1.add(dLat);
206 Angle t1 = Tile.computeColumnLongitude(firstCol, dLon);
207 for (int col = firstCol; col <= lastCol; col++)
209 Angle t2;
210 t2 = t1.add(dLon);
212 this.topLevels.add(new TextureTile(new Sector(p1, p2, t1, t2), level, row, col));
213 t1 = t2;
215 p1 = p2;
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);
253 return;
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
280 // active.
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);
289 return;
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);
299 finally
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);
315 return;
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);
325 return;
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()))))
362 // return false;
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)
368 // return false;
370 // return true;
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)
377 // {
378 // if (this.levels.isFinalLevel(tile.getLevelNumber()))
379 // return true;
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)
394 // minDistance = d2;
395 // if (d3 < minDistance)
396 // minDistance = d3;
397 // if (d4 < minDistance)
398 // minDistance = d4;
399 // if (d5 < minDistance)
400 // minDistance = d5;
402 // double r = 0;
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;
418 // }
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)
439 minDistance = d2;
440 if (d3 < minDistance)
441 minDistance = d3;
442 if (d4 < minDistance)
443 minDistance = d4;
444 if (d5 < minDistance)
445 minDistance = d5;
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)
456 return false;
458 if (!this.getLevels().getSector().contains(vpc.getLatitude(), vpc.getLongitude()))
459 return true;
461 Level nextToLast = this.getLevels().getNextToLastLevel();
462 if (nextToLast == null)
463 return true;
465 Sector centerSector = nextToLast.computeSectorForPosition(vpc.getLatitude(), vpc.getLongitude());
466 return this.needToSplit(dc, centerSector);
469 // ============== Rendering ======================= //
470 // ============== Rendering ======================= //
471 // ============== Rendering ======================= //
473 @Override
474 public void render(DrawContext dc)
476 this.atMaxResolution = this.atMaxLevel(dc);
477 super.render(dc);
480 @Override
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);
490 draw(dc);
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);
505 GL gl = dc.getGL();
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);
514 else
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);
527 gl.glPopAttrib();
529 if (this.drawTileIDs)
530 this.drawTileIDs(dc, this.currentTiles);
532 if (this.drawBoundingVolumes)
533 this.drawBoundingVolumes(dc, this.currentTiles);
535 this.currentTiles.clear();
538 this.sendRequests();
539 this.requestQ.clear();
542 private void sendRequests()
544 Runnable task = this.requestQ.poll();
545 while (task != null)
547 if (!WorldWind.getTaskService().isFull())
549 WorldWind.getTaskService().addTask(task);
551 task = this.requestQ.poll();
555 public boolean isLayerInView(DrawContext dc)
557 if (dc == null)
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);
584 if (pos == null)
585 continue;
587 return dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), 0d);
590 return null;
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);
648 Cylinder c =
649 dc.getGlobe().computeBoundingCylinder(dc.getVerticalExaggeration(), this.levels.getSector());
650 dc.getGL().glColor3d(1, 1, 0);
651 c.render(dc);
653 dc.getGL().glColor4fv(previousColor, 0);
656 // private TextureTile getContainingTile(TextureTile tile, Angle latitude, Angle longitude, int levelNumber)
657 // {
658 // if (!tile.getSector().contains(latitude, longitude))
659 // return null;
661 // if (tile.getLevelNumber() == levelNumber || this.levels.isFinalLevel(tile.getLevelNumber()))
662 // return tile;
664 // TextureTile containingTile;
665 // TextureTile[] subTiles = tile.createSubTiles(this.levels.getLevel(tile.getLevelNumber() + 1));
666 // for (TextureTile child : subTiles)
667 // {
668 // containingTile = this.getContainingTile(child, latitude, longitude, levelNumber);
669 // if (containingTile != null)
670 // return containingTile;
671 // }
673 // return null;
674 // }
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)
685 URL url = null;
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);
691 if (url != null)
692 break;
695 if (url == null)
696 return null;
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);
705 else
709 BufferedImage image = ImageIO.read(new File(url.toURI()));
710 if (image == null)
712 return null; // TODO: warn
715 this.levels.unmarkResourceAbsent(tile);
716 return image;
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
732 return null;
735 private void downloadImage(final TextureTile tile)
739 String urlString = tile.getResourceURL().toExternalForm().replace("dds", "");
740 final URL resourceURL = new URL(urlString);
741 Retriever retriever;
743 String protocol = resourceURL.getProtocol();
745 if ("http".equalsIgnoreCase(protocol))
747 retriever = new HTTPRetriever(resourceURL, new HttpRetrievalPostProcessor(tile));
749 else
751 // TODO:
752 Logging.logger().severe(
753 Logging.getMessage("layers.TextureLayer.UnknownRetrievalProtocol", resourceURL));
754 return;
757 retriever.call();
759 catch (Exception e)
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)
772 this.image = image;
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
789 return null;
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();
807 int tileWidth;
808 int tileHeight;
809 if (overallHeight >= overallWidth)
811 tileHeight = imageSize / ny;
812 double aspect = (double) overallWidth / (double) overallHeight;
813 tileWidth = (int) (aspect * imageSize / nx);
815 else
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())
829 * imageHeight);
830 double dw = -(
831 sector.getMinLongitude().subtract(actualSector.getMinLongitude()).divide(actualSector.getDeltaLon())
832 * imageWidth);
834 BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
835 Graphics2D g = image.createGraphics();
836 g.scale(sw, sh);
837 g.translate(dw, dh);
839 int y = 0;
840 for (TiledImageLayer.ImageSector[] row : imageSectors)
842 int x = 0;
843 for (TiledImageLayer.ImageSector is : row)
845 Image img = is.image.getScaledInstance(tileWidth, tileHeight, Image.SCALE_SMOOTH);
846 g.drawImage(img, x, y, null);
847 x += tileWidth;
849 y += tileHeight;
852 return image;
855 private ImageSector[][] getImagesInSector(Sector sector, int levelNumber)
857 if (sector == null)
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))
872 continue;
874 targetLevel = this.levels.getLevel(i);
875 break;
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);
898 if (image != null)
899 imageSectors[nwRow - row][col - nwCol] = new ImageSector(image, textureTile.getSector());
903 return imageSectors;
906 private BufferedImage getImage(TextureTile tile)
908 // TODO: check args
910 // Read the image from disk.
911 BufferedImage image = this.requestImage(tile);
912 if (image != null)
913 return image;
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);
922 if (image != null)
923 return image;
926 // All attempts to find the image have failed.
927 return null;
930 private class HttpRetrievalPostProcessor implements RetrievalPostProcessor
932 private TextureTile tile;
934 public HttpRetrievalPostProcessor(TextureTile tile)
936 this.tile = tile;
939 public ByteBuffer run(Retriever retriever)
941 if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL))
942 return null;
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);
949 return null;
952 if (htr.getResponseCode() != HttpURLConnection.HTTP_OK)
953 return null;
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];
964 break;
967 if (suffix == null)
969 return null; // TODO: logger error
972 String path = tile.getPath().substring(0, tile.getPath().lastIndexOf("."));
973 path += suffix;
975 final File outFile = WorldWind.getDataFileCache().newFile(path);
976 if (outFile == null)
977 return null;
981 WWIO.saveBuffer(buffer, outFile);
982 return buffer;
984 catch (IOException e)
986 e.printStackTrace(); // TODO: logger error
987 return null;