Updated to worldwind release 20070817
[worldwind-tracker.git] / gov / nasa / worldwind / layers / RpfLayer.java
blob28389bbbacf34d5bec60bcb918e572210fb173ba
1 /*
2 Copyright (C) 2001, 2006 United States Government as represented by
3 the Administrator of the National Aeronautics and Space Administration.
4 All Rights Reserved.
5 */
6 package gov.nasa.worldwind.layers;
8 import com.sun.opengl.util.*;
9 import com.sun.opengl.util.texture.*;
10 import gov.nasa.worldwind.*;
11 import gov.nasa.worldwind.formats.rpf.*;
12 import gov.nasa.worldwind.geom.*;
14 import javax.media.opengl.*;
15 import java.awt.*;
16 import java.awt.geom.*;
17 import java.awt.image.*;
18 import java.io.*;
19 import java.net.*;
20 import java.nio.*;
21 import java.util.*;
22 import java.util.Queue;
23 import java.util.concurrent.*;
24 import java.util.concurrent.locks.*;
25 import java.util.logging.Level;
27 /**
28 * @author dcollins
29 * @version $Id: RpfLayer.java 2233 2007-07-06 23:56:34Z tgaskins $
31 public class RpfLayer extends AbstractLayer
33 public static final int DefaultWindow = 8;
34 public static final double DefaultDrawFrameThreshold = 0.75;
35 public static final double DefaultDrawIconThreshold = 0.25;
36 public static final long DefaultCacheLoWater = 192L * 1024L * 1024L;
37 public static final long DefaultCacheHiWater = 256L * 1024L * 1024L;
38 private static final long FrameExpiryTime = new GregorianCalendar(2007, 5, 31).getTimeInMillis();
39 private final RpfDataSeries dataSeries;
40 private int latitudeWindow;
41 private int longitudeWindow;
42 private double drawFrameThreshold;
43 private double drawIconThreshold;
45 private final MemoryCache memoryCache;
46 private final String cachePathBase = "Earth/RPF/";
48 public RpfLayer(RpfDataSeries dataSeries)
50 if (dataSeries == null)
52 String message = WorldWind.retrieveErrMsg("nullValue.RpfDataSeriesIsNull");
53 WorldWind.logger().log(Level.FINE, message);
54 throw new IllegalArgumentException(message);
56 this.dataSeries = dataSeries;
57 this.latitudeWindow = DefaultWindow;
58 this.longitudeWindow = DefaultWindow;
59 this.drawFrameThreshold = DefaultDrawFrameThreshold;
60 this.drawIconThreshold = DefaultDrawIconThreshold;
61 // Initialize the MemoryCache.
62 this.memoryCache = new BasicMemoryCache(DefaultCacheLoWater, DefaultCacheHiWater);
63 this.memoryCache.addCacheListener(new MemoryCache.CacheListener()
65 public void entryRemoved(Object key, Object clientObject)
67 if (clientObject == null || !(clientObject instanceof RpfTextureTile))
68 return;
69 RpfTextureTile rpfTextureTile = (RpfTextureTile) clientObject;
70 disposalQueue.offer(rpfTextureTile);
72 });
75 public int getLatitudeWindow()
77 return latitudeWindow;
80 public void setLatitudeWindow(int latitudeWindow)
82 this.latitudeWindow = latitudeWindow;
85 public int getLongitudeWindow()
87 return longitudeWindow;
90 public void setLongitudeWindow(int longitudeWindow)
92 this.longitudeWindow = longitudeWindow;
95 public double getDrawFrameThreshold()
97 return drawFrameThreshold;
100 public void setDrawFrameThreshold(double drawFrameThreshold)
102 this.drawFrameThreshold = drawFrameThreshold;
105 public double getDrawIconThreshold()
107 return drawIconThreshold;
110 public void setDrawIconThreshold(double drawIconThreshold)
112 this.drawIconThreshold = drawIconThreshold;
115 public String toString()
117 StringBuilder sb = new StringBuilder();
118 sb.append(this.dataSeries.seriesCode);
119 sb.append(": ");
120 sb.append(this.dataSeries.dataSeries);
121 return sb.toString();
124 // ============== Frame Directory ======================= //
125 // ============== Frame Directory ======================= //
126 // ============== Frame Directory ======================= //
128 private final Map<FrameKey, FrameRecord> frameDirectory = new HashMap<FrameKey, FrameRecord>();
129 private Sector sector = Sector.EMPTY_SECTOR;
130 private int modCount = 0;
131 private int lastModCount = 0;
133 private static class FrameKey
135 public final RpfZone zone;
136 public final int frameNumber;
137 private final int hashCode;
139 public FrameKey(RpfZone zone, int frameNumber)
141 this.zone = zone;
142 this.frameNumber = frameNumber;
143 this.hashCode = this.computeHash();
146 private int computeHash()
148 int hash = 0;
149 if (this.zone != null)
150 hash = 29 * hash + this.zone.ordinal();
151 hash = 29 * hash + this.frameNumber;
152 return hash;
155 public boolean equals(Object o)
157 if (this == o)
158 return true;
159 if (o == null || !o.getClass().equals(this.getClass()))
160 return false;
161 final FrameKey that = (FrameKey) o;
162 return (this.zone == that.zone) && (this.frameNumber == that.frameNumber);
165 public int hashCode()
167 return this.hashCode;
171 private static class FrameRecord
173 private final String filePath;
174 private RpfFrameProperties properties;
175 private Sector sector;
176 private String cacheFilePath;
177 final Lock fileLock = new ReentrantLock();
179 public FrameRecord(File file)
181 String fileName = file.getName().toUpperCase();
182 this.filePath = file.getAbsolutePath();
183 this.properties = RpfFrameFilenameUtil.parseFilename(fileName);
186 public boolean equals(Object o)
188 if (this == o)
189 return true;
190 if (o == null || !o.getClass().equals(this.getClass()))
191 return false;
192 final FrameRecord that = (FrameRecord) o;
193 return this.filePath.equals(that.filePath);
196 public String getCacheFilePath(RpfLayer layer)
198 if (this.cacheFilePath == null)
200 String fileName = RpfFrameFilenameUtil.filenameFor(this.properties);
201 this.cacheFilePath = cachePathFor(layer.cachePathBase, fileName, this.properties);
203 return this.cacheFilePath;
206 public long getExpiryTime()
208 return FrameExpiryTime;
211 public String getFilePath()
213 return this.filePath;
216 public RpfFrameProperties getRpfFrameProperties()
218 return this.properties;
221 public Sector getSector()
223 if (this.sector == null)
225 RpfFrameProperties rpfFrameProperties = this.getRpfFrameProperties();
226 this.sector = sectorFor(rpfFrameProperties);
228 return this.sector;
232 public int addFrame(File file)
234 if (file == null)
236 String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull");
237 WorldWind.logger().log(Level.FINE, message);
238 throw new IllegalArgumentException(message);
241 FrameRecord record = null;
244 record = new FrameRecord(file);
246 catch (Exception e)
248 String message = WorldWind.retrieveErrMsg("layers.RpfLayer.ExceptionParsingFileName") + file;
249 WorldWind.logger().log(Level.FINE, message, e);
252 if (record != null && (this.dataSeries == record.getRpfFrameProperties().dataSeries))
254 this.addRecord(record);
255 return 1;
257 return 0;
260 private void addRecord(FrameRecord record)
262 FrameKey key = keyFor(record);
263 this.frameDirectory.put(key, record);
264 ++this.modCount;
267 private static String cachePathFor(String cachePathBase, String filename, RpfFrameProperties properties)
269 StringBuilder sb = new StringBuilder(cachePathBase);
270 sb.append(properties.dataSeries.seriesCode).append(File.separatorChar);
271 sb.append(properties.zone.zoneCode).append(File.separatorChar);
272 sb.append(filename);
273 sb.append(".").append(TextureIO.DDS);
274 return sb.toString();
277 private static FrameKey keyFor(FrameRecord record)
279 return new FrameKey(record.getRpfFrameProperties().zone, record.getRpfFrameProperties().frameNumber);
282 public int removeFrameFile(File file)
284 if (file == null)
285 return 0;
286 RpfFrameProperties properties = null;
289 properties = RpfFrameFilenameUtil.parseFilename(file.getName().toUpperCase());
291 catch (IllegalArgumentException e)
293 String message = WorldWind.retrieveErrMsg("layers.RpfLayer.ExceptionParsingFileName") + file;
294 WorldWind.logger().log(Level.FINE, message, e);
296 return removeFrame(properties);
299 private int removeFrame(RpfFrameProperties properties)
301 if (properties == null)
302 return 0;
303 if (this.dataSeries == properties.dataSeries)
305 this.removeKey(new FrameKey(properties.zone, properties.frameNumber));
306 return 1;
308 return 0;
311 private boolean removeKey(FrameKey key)
313 FrameRecord value = this.frameDirectory.remove(key);
314 --this.modCount;
315 return value != null;
318 private static Sector sectorFor(RpfFrameProperties properties)
320 if (properties == null
321 || properties.zone == null
322 || properties.dataSeries == null)
323 return null;
324 RpfZone.ZoneValues zoneValues = properties.zone.zoneValues(properties.dataSeries);
325 if (properties.frameNumber < 0 || properties.frameNumber > (zoneValues.maximumFrameNumber - 1))
326 return null;
327 return zoneValues.frameExtent(properties.frameNumber);
330 private void updateSector()
332 Sector newSector = null;
333 for (FrameRecord record : this.frameDirectory.values())
335 if (record.getSector() != null)
336 newSector = (newSector != null) ? newSector.union(record.getSector()) : record.getSector();
338 this.sector = newSector;
341 // ============== Tile Assembly ======================= //
342 // ============== Tile Assembly ======================= //
343 // ============== Tile Assembly ======================= //
345 private void assembleTiles(DrawContext dc, Map<FrameKey, RpfTextureTile> cachedTiles,
346 Queue<RpfTextureTile> tilesToRender)
348 synchronized (cachedTiles)
350 for (RpfTextureTile rpfTextureTile : cachedTiles.values())
352 if (this.isSectorVisible(dc, rpfTextureTile.getSector()))
353 tilesToRender.offer(rpfTextureTile);
358 private void assembleRequests(DrawContext dc, RpfDataSeries dataSeries, Sector viewingSector,
359 Queue<FrameRecord> framesToRequest)
361 for (RpfZone zone : RpfZone.values())
363 RpfZone.ZoneValues zoneValues = zone.zoneValues(dataSeries);
364 Sector sector = zoneValues.extent.intersection(viewingSector);
365 if (sector != null && this.isSectorVisible(dc, sector))
366 this.assembleRequestsForZone(dc, zoneValues, sector, framesToRequest);
370 private void assembleRequestsForZone(DrawContext dc, RpfZone.ZoneValues zoneValues, Sector sector,
371 Queue<FrameRecord> framesToRequest)
373 int startRow = zoneValues.frameRowFromLatitude(sector.getMinLatitude());
374 int endRow = zoneValues.frameRowFromLatitude(sector.getMaxLatitude());
375 int startCol = zoneValues.frameColumnFromLongitude(sector.getMinLongitude());
376 int endCol = zoneValues.frameColumnFromLongitude(sector.getMaxLongitude());
378 for (int row = startRow; row <= endRow; row++)
380 for (int col = startCol; col <= endCol; col++)
382 int frameNum = zoneValues.frameNumber(row, col);
383 FrameKey key = new FrameKey(zoneValues.zone, frameNum);
384 RpfTextureTile tile = this.getTile(key);
385 if (tile == null)
387 FrameRecord record = this.frameDirectory.get(key);
388 if (record != null && this.isSectorVisible(dc, record.getSector()))
389 framesToRequest.offer(record);
395 private static Sector[] normalizeSector(Sector sector)
397 Angle minLat = clampAngle(sector.getMinLatitude(), Angle.NEG90, Angle.POS90);
398 Angle maxLat = clampAngle(sector.getMaxLatitude(), Angle.NEG90, Angle.POS90);
399 if (maxLat.degrees < minLat.degrees)
401 Angle tmp = minLat;
402 minLat = maxLat;
403 maxLat = tmp;
406 Angle minLon = normalizeAngle(sector.getMinLongitude(), Angle.NEG180, Angle.POS180);
407 Angle maxLon = normalizeAngle(sector.getMaxLongitude(), Angle.NEG180, Angle.POS180);
408 if (maxLon.degrees < minLon.degrees)
410 return new Sector[] {
411 new Sector(minLat, maxLat, minLon, Angle.POS180),
412 new Sector(minLat, maxLat, Angle.NEG180, maxLon),
416 return new Sector[] {new Sector(minLat, maxLat, minLon, maxLon)};
419 private static Sector createViewSector(RpfDataSeries dataSeries,
420 Angle centerLat, Angle centerLon, int latWindowFrames, int lonWindowFrames)
422 RpfZone.ZoneValues zoneValues = RpfZone.Zone1.zoneValues(dataSeries);
423 if (zoneValues == null)
424 return null;
425 Angle latWindow = zoneValues.latitudinalFrameExtent.multiply(latWindowFrames);
426 Angle lonWindow = zoneValues.longitudinalFrameExtent.multiply(lonWindowFrames);
427 Angle lat_over_2 = latWindow.divide(2.0);
428 Angle lon_over_2 = lonWindow.divide(2.0);
429 return new Sector(
430 centerLat.subtract(lat_over_2), centerLat.add(lat_over_2),
431 centerLon.subtract(lon_over_2), centerLon.add(lon_over_2));
434 private static Angle clampAngle(Angle angle, Angle min, Angle max)
436 return (angle.degrees < min.degrees) ? min : ((angle.degrees > max.degrees) ? max : angle);
439 private static Angle normalizeAngle(Angle angle, Angle min, Angle max)
441 Angle range = max.subtract(min);
442 return (angle.degrees < min.degrees) ?
443 angle.add(range) : ((angle.degrees > max.degrees) ? angle.subtract(range) : angle);
446 // ============== Rendering ======================= //
447 // ============== Rendering ======================= //
448 // ============== Rendering ======================= //
450 // TODO: compute overview image, simple using j2D
451 // TODO: extrapolate based on small sample
453 private static class RpfTextureTile extends TextureTile
455 final FrameKey frameKey;
456 final FrameRecord frameRecord;
458 RpfTextureTile(FrameKey frameKey, FrameRecord frameRecord)
460 super(frameRecord.getSector());
461 this.frameKey = frameKey;
462 this.frameRecord = frameRecord;
466 private final BlockingQueue<Disposable> disposalQueue = new LinkedBlockingQueue<Disposable>();
467 private final IconRenderer iconRenderer = new IconRenderer();
468 private TextureTile coverageTile;
469 private WWIcon icon;
470 private boolean drawCoverage = true;
471 private boolean drawIcon = true;
472 private static Boolean haveARBNonPowerOfTwo = null;
474 private static TextureData createCoverageTextureData(int width, int height,
475 Sector extent, Collection<FrameRecord> coverage)
477 IntBuffer buffer = BufferUtil.newIntBuffer(width * height);
478 Angle latWidth = extent.getMaxLatitude().subtract(extent.getMinLatitude());
479 Angle lonWidth = extent.getMaxLongitude().subtract(extent.getMinLongitude());
480 for (FrameRecord record : coverage)
482 int x0 = (int) Math.round((width - 1)
483 * record.sector.getMinLongitude().subtract(extent.getMinLongitude()).divide(lonWidth));
484 int x1 = (int) Math.round((width - 1)
485 * record.sector.getMaxLongitude().subtract(extent.getMinLongitude()).divide(lonWidth));
487 int y0 = (int) Math.round((height - 1)
488 * record.sector.getMinLatitude().subtract(extent.getMinLatitude()).divide(latWidth));
489 int y1 = (int) Math.round((height - 1)
490 * record.sector.getMaxLatitude().subtract(extent.getMinLatitude()).divide(latWidth));
492 for (int y = y0; y <= y1; y++)
494 for (int x = x0; x <= x1; x++)
496 buffer.put(x + y * width, 0x4040407F);
500 buffer.rewind();
501 return new TextureData(GL.GL_RGBA, width, height, 0, GL.GL_RGBA, GL.GL_UNSIGNED_INT_8_8_8_8,
502 false, false, false, buffer, null);
505 // private static TextureData createCoverageTextureData(int width, int height,
506 // Sector extent, Collection<FrameRecord> coverage)
507 // {
508 // BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
509 // Graphics2D g2d = image.createGraphics();
510 // Color fillColor = new Color(0.5f, 0.5f, 0.5f, 0.5f);
511 // for (FrameRecord record : coverage)
512 // drawFrame(g2d, width, height, fillColor, extent, record);
514 // TextureData textureData = new TextureData(GL.GL_RGBA, GL.GL_RGBA, false, image);
515 // textureData.setMustFlipVertically(false);
516 // return textureData;
517 // }
519 // private static void drawFrame(Graphics2D g2d, int gWidth, int gHeight, Color fillColor,
520 // Sector graphicsExtent, FrameRecord record)
521 // {
522 // Angle xRange = graphicsExtent.getMaxLongitude().subtract(graphicsExtent.getMinLongitude());
523 // int x0 = (int) Math.round((gWidth - 1)
524 // * record.getSector().getMinLongitude().subtract(graphicsExtent.getMinLongitude()).divide(xRange));
525 // int x1 = (int) Math.round((gWidth - 1)
526 // * record.getSector().getMaxLongitude().subtract(graphicsExtent.getMinLongitude()).divide(xRange));
528 // Angle yRange = graphicsExtent.getMaxLatitude().subtract(graphicsExtent.getMinLatitude());
529 // int y0 = (int) Math.round((gHeight - 1)
530 // * record.getSector().getMinLatitude().subtract(graphicsExtent.getMinLatitude()).divide(yRange));
531 // int y1 = (int) Math.round((gHeight - 1)
532 // * record.getSector().getMaxLatitude().subtract(graphicsExtent.getMinLatitude()).divide(yRange));
534 // g2d.setColor(fillColor);
535 // g2d.fillRect(x0, y0, x1 - x0, y1 - y0);
537 //// BufferedImage img = null;
538 //// try
539 //// {
540 //// RpfImageFile rpfImageFile = RpfImageFile.load(new File(record.filePath));
541 //// img = rpfImageFile.getBufferedImage();
542 //// }
543 //// catch (Exception e)
544 //// {
545 //// WorldWind.logger().log(Level.FINE, "", e);
546 //// }
547 //// if (img == null)
548 //// return;
549 ////
550 ////
551 //// double sx = (x1 - x0) / (double) img.getWidth();
552 //// double sy = (y1 - y0) / (double) img.getHeight();
553 //// BufferedImageOp op = new AffineTransformOp(
554 //// AffineTransform.getScaleInstance(sx, sy), AffineTransformOp.TYPE_BICUBIC);
555 ////
556 //// g2d.drawImage(img, op, x0, y0);
557 // }
559 public void dispose()
561 // Clear queues.
562 this.cachedTiles.clear();
563 this.readQueue.clear();
564 this.downloadQueue.clear();
565 // Dispose objects that allocated GL memory.
566 if (this.iconRenderer != null)
567 disposalQueue.offer(this.iconRenderer);
568 if (this.coverageTile != null)
570 disposalQueue.offer(this.coverageTile);
571 this.coverageTile = null;
573 processDisposables();
576 protected void doRender(DrawContext dc)
578 // Gather GL extension info.
579 if (haveARBNonPowerOfTwo == null)
580 haveARBNonPowerOfTwo = dc.getGL().isExtensionAvailable("GL_ARB_texture_non_power_of_two");
582 // Process disposable queue.
583 this.processDisposables();
585 // Update sector and coverage renderables when frame contents change.
586 if (this.modCount != this.lastModCount)
588 this.updateSector();
589 this.updateCoverage();
590 this.lastModCount = this.modCount;
593 if (!this.isSectorVisible(dc, this.sector))
594 return;
596 // Create viewing window.
597 View view = dc.getView();
598 Sector viewingSector = createViewSector(
599 this.dataSeries,
600 view.getLatitude(), view.getLongitude(),
601 this.latitudeWindow, this.longitudeWindow);
603 // Assemble frame requests.
604 Queue<FrameRecord> requestQueue = new LinkedList<FrameRecord>();
605 for (Sector sector : normalizeSector(viewingSector))
607 this.assembleRequests(dc, this.dataSeries, sector, requestQueue);
610 // Assemble visible, in-memory tiles.
611 Queue<RpfTextureTile> renderQueue = new LinkedList<RpfTextureTile>();
612 this.assembleTiles(dc, this.cachedTiles, renderQueue);
613 // Compute the union of rendred tile sectors.
614 Sector drawSector = null;
615 for (RpfTextureTile tile : renderQueue)
617 drawSector = (drawSector != null) ? drawSector.union(tile.getSector()) : tile.getSector();
620 // Render overview.
621 this.renderCoverage(dc);
623 // Render frame tiles.
624 if (percentViewportOfSector(dc, viewingSector) > this.drawFrameThreshold)
626 this.renderFrames(dc, renderQueue);
627 this.requestAllFrames(requestQueue);
630 // Render icon.
631 if (percentViewportOfSector(dc, this.sector) < this.drawIconThreshold)
633 this.renderIcon(dc, drawSector);
636 this.sendRequests();
639 public WWIcon getIcon()
641 return this.icon;
644 private static void initializeOverviewTexture(DrawContext dc, Texture texture)
646 texture.setTexParameteri(GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
647 texture.setTexParameteri(GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
648 texture.setTexParameteri(GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
649 texture.setTexParameteri(GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);
650 int[] maxAnisotropy = new int[1];
651 dc.getGL().glGetIntegerv(GL.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy, 0);
652 texture.setTexParameteri(GL.GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy[0]);
655 public boolean isDrawCoverage()
657 return this.drawCoverage;
660 public boolean isDrawIcon()
662 return this.drawIcon;
665 private boolean isSectorVisible(DrawContext dc, Sector sector)
667 if (dc.getVisibleSector() != null && !sector.intersects(dc.getVisibleSector()))
668 return false;
670 Extent e = Sector.computeBoundingCylinder(dc.getGlobe(), dc.getVerticalExaggeration(), sector);
671 return e.intersects(dc.getView().getFrustumInModelCoordinates());
674 private static int pixelSizeOfSector(DrawContext dc, Sector sector)
676 LatLon centroid = sector.getCentroid();
677 Globe globe = dc.getGlobe();
678 Vec4 centroidPoint = globe.computePointFromPosition(centroid.getLatitude(), centroid.getLongitude(), 0);
679 Vec4 minPoint = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMinLongitude(), 0);
680 Vec4 maxPoint = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMaxLongitude(), 0);
681 double distanceToEye = centroidPoint.distanceTo3(dc.getView().getEyePoint());
682 double sectorSize = minPoint.distanceTo3(maxPoint);
683 double pixelSize = dc.getView().computePixelSizeAtDistance(distanceToEye);
684 return (int) Math.round(sectorSize / pixelSize);
687 private static double percentViewportOfSector(DrawContext dc, Sector sector)
689 Rectangle viewport = dc.getView().getViewport();
690 double pixelSize = pixelSizeOfSector(dc, sector);
691 double maxDimension = Math.min(viewport.width, viewport.height);
692 return pixelSize / maxDimension;
695 private void processDisposables()
697 Disposable disposable;
698 while ((disposable = this.disposalQueue.poll()) != null)
700 if (disposable instanceof RpfTextureTile)
701 this.evictTile(((RpfTextureTile) disposable).frameKey);
702 disposable.dispose();
706 private void renderCoverage(DrawContext dc)
708 // Render coverage tile.
709 if (this.drawCoverage && this.coverageTile != null)
711 GL gl = dc.getGL();
712 int attribBits = GL.GL_ENABLE_BIT | GL.GL_COLOR_BUFFER_BIT | GL.GL_POLYGON_BIT;
713 gl.glPushAttrib(attribBits);
716 gl.glEnable(GL.GL_BLEND);
717 gl.glEnable(GL.GL_CULL_FACE);
718 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
719 gl.glCullFace(GL.GL_BACK);
720 gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
721 dc.getSurfaceTileRenderer().renderTile(dc, this.coverageTile);
723 finally
725 gl.glPopAttrib();
730 private void renderIcon(DrawContext dc, Sector drawSector)
732 // Render coverage icon.
733 if (this.drawIcon && this.icon != null)
735 LatLon centroid = (drawSector != null) ? drawSector.getCentroid() : this.sector.getCentroid();
736 this.icon.setPosition(new Position(centroid, 0));
737 this.iconRenderer.render(dc, this.icon, null);
741 private void renderFrames(DrawContext dc, Queue<RpfTextureTile> rpfTextureTiles)
743 GL gl = dc.getGL();
744 int attribBits = GL.GL_ENABLE_BIT | GL.GL_POLYGON_BIT;
745 gl.glPushAttrib(attribBits);
748 gl.glEnable(GL.GL_CULL_FACE);
749 gl.glCullFace(GL.GL_BACK);
750 gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
751 dc.getSurfaceTileRenderer().renderTiles(dc, rpfTextureTiles);
753 finally
755 gl.glPopAttrib();
759 public void setIcon(WWIcon icon)
761 if (icon == null)
763 String message = WorldWind.retrieveErrMsg("nullValue.Icon");
764 WorldWind.logger().log(Level.FINE, message);
765 throw new IllegalArgumentException(message);
767 this.icon = icon;
770 public void setDrawCoverage(boolean drawCoverage)
772 this.drawCoverage = drawCoverage;
775 public void setDrawIcon(boolean drawIcon)
777 this.drawIcon = drawIcon;
780 private void updateCoverage()
782 if (this.coverageTile != null)
783 disposalQueue.offer(this.coverageTile);
784 TextureData textureData = createCoverageTextureData(1024, 1024, this.sector, this.frameDirectory.values());
785 this.coverageTile = new TextureTile(this.sector)
787 public void initializeTexture(DrawContext dc)
789 if (this.getTexture() == null)
791 Texture tex = TextureIO.newTexture(this.getTextureData());
792 initializeOverviewTexture(dc, tex);
793 this.setTexture(tex);
797 this.coverageTile.setTextureData(textureData);
800 // ============== Image Reading and Conversion ======================= //
801 // ============== Image Reading and Conversion ======================= //
802 // ============== Image Reading and Conversion ======================= //
804 private final Map<FrameKey, RpfTextureTile> cachedTiles = new HashMap<FrameKey, RpfTextureTile>();
805 private final LinkedList<FrameRequest> downloadQueue = new LinkedList<FrameRequest>();
806 private final LinkedList<FrameRequest> readQueue = new LinkedList<FrameRequest>();
807 // private final AbsentResourceList absentResourceList = new AbsentResourceList();
809 private static class FrameRequest
811 static final long STALE_REQUEST_LIMIT = 10000L;
812 final FrameRecord frameRecord;
813 long timestamp;
815 public FrameRequest(FrameRecord frameRecord)
817 this.frameRecord = frameRecord;
818 this.timestamp = System.currentTimeMillis();
821 public void touch()
823 this.timestamp = System.currentTimeMillis();
826 public boolean isStale()
828 return (System.currentTimeMillis() - this.timestamp) > STALE_REQUEST_LIMIT;
832 private static class RpfRetriever extends WWObjectImpl implements Retriever
834 private final RpfLayer layer;
835 private final FrameRecord record;
836 private volatile RpfImageFile rpfImageFile;
837 private volatile ByteBuffer buffer;
838 private volatile String state = RETRIEVER_STATE_NOT_STARTED;
839 private long submitTime;
840 private long beginTime;
841 private long endTime;
842 private int connectTimeout = -1;
843 private int readTimeout = -1;
844 private int staleRequestLimit = -1;
846 public RpfRetriever(RpfLayer layer, FrameRecord record)
848 this.layer = layer;
849 this.record = record;
852 public boolean equals(Object o)
854 if (this == o)
855 return true;
856 if (o == null || !o.getClass().equals(this.getClass()))
857 return false;
858 final RpfRetriever that = (RpfRetriever) o;
859 return this.record.equals(that.record);
862 public long getBeginTime()
864 return this.beginTime;
867 public ByteBuffer getBuffer()
869 return this.buffer;
872 public int getContentLength()
874 return 0;
877 public int getContentLengthRead()
879 return 0;
882 public String getContentType()
884 return null;
887 public long getEndTime()
889 return this.endTime;
892 public String getName()
894 return this.record.getFilePath();
897 public String getState()
899 return this.state;
902 public long getSubmitTime()
904 return this.submitTime;
907 public int getConnectTimeout()
909 return connectTimeout;
912 public void setConnectTimeout(int connectTimeout)
914 this.connectTimeout = connectTimeout;
917 public int getReadTimeout()
919 return readTimeout;
922 public void setReadTimeout(int readTimeout)
924 this.readTimeout = readTimeout;
927 public int getStaleRequestLimit()
929 return this.staleRequestLimit;
932 public void setStaleRequestLimit(int staleRequestLimit)
934 this.staleRequestLimit = staleRequestLimit;
937 private boolean interrupted()
939 if (Thread.currentThread().isInterrupted())
941 this.setState(RETRIEVER_STATE_INTERRUPTED);
942 String message = WorldWind.retrieveErrMsg("layers.RpfLayer.DownloadInterrupted")
943 + this.record.getFilePath();
944 WorldWind.logger().log(Level.FINER, message);
945 return true;
947 return false;
950 public void setBeginTime(long beginTime)
952 this.beginTime = beginTime;
955 public void setEndTime(long endTime)
957 this.endTime = endTime;
960 private void setState(String state)
962 String oldState = this.state;
963 this.state = state;
964 this.firePropertyChange(AVKey.RETRIEVER_STATE, oldState, this.state);
967 public void setSubmitTime(long submitTime)
969 this.submitTime = submitTime;
972 public Retriever call() throws Exception
974 if (this.interrupted())
975 return this;
977 String cacheFilePath = this.record.getCacheFilePath(this.layer);
979 if (!this.record.fileLock.tryLock())
981 this.setState(RETRIEVER_STATE_SUCCESSFUL);
982 return this;
986 this.setState(RETRIEVER_STATE_STARTED);
988 if (!this.interrupted())
990 if (isFileResident(cacheFilePath))
992 this.setState(RETRIEVER_STATE_SUCCESSFUL);
993 return this;
997 if (!this.interrupted())
999 this.setState(RETRIEVER_STATE_CONNECTING);
1000 File file = new File(this.record.getFilePath());
1001 if (!file.exists())
1003 String message = WorldWind.retrieveErrMsg("generic.fileNotFound") + this.record.getFilePath();
1004 throw new IOException(message);
1006 this.rpfImageFile = RpfImageFile.load(file);
1009 if (!this.interrupted())
1011 this.setState(RETRIEVER_STATE_READING);
1012 File file = WorldWind.dataFileCache().newFile(cacheFilePath);
1013 if (file == null)
1015 String message = WorldWind.retrieveErrMsg("generic.CantCreateCacheFile")
1016 + cacheFilePath;
1017 throw new IOException(message);
1019 if (haveARBNonPowerOfTwo)
1021 this.buffer = this.rpfImageFile.getImageAsDdsTexture();
1023 else
1025 BufferedImage src = this.rpfImageFile.getBufferedImage();
1026 int dstWidth = previousPowerOfTwo(src.getWidth());
1027 int dstHeight = previousPowerOfTwo(src.getHeight());
1028 double sx = dstWidth / (double) src.getWidth();
1029 double sy = dstHeight / (double) src.getHeight();
1030 AffineTransformOp op = new AffineTransformOp(
1031 AffineTransform.getScaleInstance(sx, sy), AffineTransformOp.TYPE_BICUBIC);
1032 BufferedImage dst = new BufferedImage(dstWidth, dstHeight, src.getType());
1033 op.filter(src, dst);
1034 this.buffer = DDSConverter.convertToDxt1NoTransparency(dst);
1036 WWIO.saveBuffer(this.buffer, file);
1039 if (!this.interrupted())
1041 this.setState(RETRIEVER_STATE_SUCCESSFUL);
1042 // this.layer.absentResourceList.unmarkResourceAbsent(absentIdFor(this.record));
1043 this.layer.firePropertyChange(AVKey.LAYER, null, this.layer);
1046 catch (Exception e)
1048 this.setState(RETRIEVER_STATE_ERROR);
1049 // this.layer.absentResourceList.markResourceAbsent(absentIdFor(this.record));
1050 throw e;
1052 finally
1054 this.record.fileLock.unlock();
1057 return this;
1061 private static class ReadTask implements Runnable
1063 public final RpfLayer layer;
1064 public final FrameRecord record;
1066 public ReadTask(RpfLayer layer, FrameRecord record)
1068 this.layer = layer;
1069 this.record = record;
1072 private void deleteCorruptFile(URL fileURL)
1074 WorldWind.dataFileCache().removeFile(fileURL);
1075 String message = WorldWind.retrieveErrMsg("generic.DeletedCorruptDataFile") + fileURL;
1076 WorldWind.logger().log(Level.FINER, message);
1079 // private void deleteOutOfDateFile(URL fileURL)
1080 // {
1081 // WorldWind.dataFileCache().removeFile(fileURL);
1082 // String message = WorldWind.retrieveErrMsg("generic.DataFileExpired") + fileURL;
1083 // WorldWind.logger().log(Level.FINER, message);
1084 // }
1086 public boolean equals(Object o)
1088 if (this == o)
1089 return true;
1090 if (o == null || !o.getClass().equals(this.getClass()))
1091 return false;
1092 final ReadTask that = (ReadTask) o;
1093 return (this.record != null) ? this.record.equals(that.record) : (that.record == null);
1096 public void run()
1098 FrameKey key = keyFor(this.record);
1099 if (!this.layer.isTileResident(key))
1101 this.readFrame(key, this.record);
1102 this.layer.firePropertyChange(AVKey.LAYER, null, this.layer);
1106 public void readFrame(FrameKey key, FrameRecord record)
1108 String cacheFilePath = record.getCacheFilePath(this.layer);
1109 URL dataFileURL = WorldWind.dataFileCache().findFile(cacheFilePath, false);
1110 // The cached file does not exist. Issue a download and return.
1111 if (dataFileURL == null)
1113 this.layer.downloadFrame(this.record);
1114 return;
1117 if (!record.fileLock.tryLock())
1118 return;
1121 // // The file has expired. Delete it.
1122 // if (WWIO.isFileOutOfDate(dataFileURL, record.getExpiryTime()))
1123 // {
1124 // this.deleteOutOfDateFile(dataFileURL);
1125 // this.layer.firePropertyChange(AVKey.LAYER, null, this.layer);
1126 // return;
1127 // }
1129 if (this.layer.isTileResident(key))
1130 return;
1132 TextureData textureData = null;
1135 textureData = TextureIO.newTextureData(dataFileURL, false, TextureIO.DDS);
1137 catch (IOException e)
1139 String message = WorldWind.retrieveErrMsg("generic.TextureIOException") + cacheFilePath;
1140 WorldWind.logger().log(Level.FINE, message, e);
1143 // The file has expired. Delete it.
1144 if (textureData == null)
1146 this.deleteCorruptFile(dataFileURL);
1147 // this.layer.absentResourceList.markResourceAbsent(absentIdFor(record));
1148 return;
1150 else
1152 // this.layer.absentResourceList.unmarkResourceAbsent(absentIdFor(record));
1155 RpfTextureTile rpfTextureTile = new RpfTextureTile(key, record);
1156 rpfTextureTile.setTextureData(textureData);
1157 this.layer.makeTileResident(key, rpfTextureTile);
1158 this.layer.firePropertyChange(AVKey.LAYER, null, this.layer);
1160 finally
1162 record.fileLock.unlock();
1167 private void downloadFrame(FrameRecord record)
1169 // if (this.absentResourceList.isResourceAbsent(absentIdFor(record)))
1170 // return;
1172 synchronized (this.downloadQueue)
1174 FrameRequest frameRequest = new FrameRequest(record);
1175 int index = this.downloadQueue.indexOf(frameRequest);
1176 // Frame has not been requested, add it.
1177 if (index < 0)
1178 this.downloadQueue.addFirst(frameRequest);
1179 // Frame is already requested, update it's timestamp.
1180 else
1181 this.downloadQueue.get(index).touch();
1185 private void evictTile(FrameKey key)
1187 synchronized (this.cachedTiles)
1189 // Value is assumed to be evicted from memory cache.
1190 this.cachedTiles.remove(key);
1194 private RpfTextureTile getTile(FrameKey key)
1196 synchronized (this.cachedTiles)
1198 // Touch the value in memory cache.
1199 this.memoryCache.getObject(key);
1200 // Return the value in our local cache directory.
1201 return this.cachedTiles.get(key);
1205 // private static long absentIdFor(FrameRecord record)
1206 // {
1207 // RpfFrameProperties properties = record.getRpfFrameProperties();
1208 // return properties.zone.ordinal() + (100 * properties.frameNumber);
1209 // }
1211 private static boolean isFileResident(String fileName)
1213 return WorldWind.dataFileCache().findFile(fileName, false) != null;
1216 private boolean isTileResident(FrameKey key)
1218 synchronized (this.cachedTiles)
1220 // Value may be evicted from memory cache, but still in local cache directory
1221 // (not yet processsed for disposal).
1222 return this.cachedTiles.get(key) != null;
1226 private void makeTileResident(FrameKey key, RpfTextureTile tile)
1228 synchronized (this.memoryCache)
1230 this.memoryCache.add(key, tile);
1233 synchronized (this.cachedTiles)
1235 this.cachedTiles.put(key, tile);
1239 private static int previousPowerOfTwo(int value)
1241 int result = 1;
1242 while (result < value)
1244 result <<= 1;
1246 return (result >> 1);
1249 private void requestFrame(FrameRecord record)
1251 // if (this.absentResourceList.isResourceAbsent(absentIdFor(record)))
1252 // return;
1254 synchronized (this.readQueue)
1256 FrameRequest frameRequest = new FrameRequest(record);
1257 int index = this.readQueue.indexOf(frameRequest);
1258 // Frame has not been requested, add it.
1259 if (index < 0)
1260 this.readQueue.addFirst(frameRequest);
1261 // Frame is already requested, update it's timestamp.
1262 else
1263 this.readQueue.get(index).touch();
1267 private void requestAllFrames(Collection<FrameRecord> frameRecords)
1269 for (FrameRecord record : frameRecords)
1271 this.requestFrame(record);
1275 private void sendRequests()
1277 synchronized (this.readQueue)
1279 FrameRequest request;
1280 while (!WorldWind.threadedTaskService().isFull() && (request = this.readQueue.poll()) != null)
1282 if (!request.isStale())
1283 WorldWind.threadedTaskService().addTask(new ReadTask(this, request.frameRecord));
1287 synchronized (this.downloadQueue)
1289 FrameRequest request;
1290 while (!WorldWind.retrievalService().isFull() && (request = this.downloadQueue.poll()) != null)
1292 if (!request.isStale())
1293 WorldWind.retrievalService().runRetriever(new RpfRetriever(this, request.frameRecord));