2 Copyright (C) 2001, 2006 United States Government as represented by
3 the Administrator of the National Aeronautics and Space Administration.
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
.*;
16 import java
.awt
.geom
.*;
17 import java
.awt
.image
.*;
22 import java
.util
.Queue
;
23 import java
.util
.concurrent
.*;
24 import java
.util
.concurrent
.locks
.*;
25 import java
.util
.logging
.Level
;
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
))
69 RpfTextureTile rpfTextureTile
= (RpfTextureTile
) clientObject
;
70 disposalQueue
.offer(rpfTextureTile
);
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
);
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
)
142 this.frameNumber
= frameNumber
;
143 this.hashCode
= this.computeHash();
146 private int computeHash()
149 if (this.zone
!= null)
150 hash
= 29 * hash
+ this.zone
.ordinal();
151 hash
= 29 * hash
+ this.frameNumber
;
155 public boolean equals(Object o
)
159 if (o
== null || !o
.getClass().equals(this.getClass()))
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
)
190 if (o
== null || !o
.getClass().equals(this.getClass()))
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
);
232 public int addFrame(File file
)
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
);
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
);
260 private void addRecord(FrameRecord record
)
262 FrameKey key
= keyFor(record
);
263 this.frameDirectory
.put(key
, record
);
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
);
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
)
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)
303 if (this.dataSeries
== properties
.dataSeries
)
305 this.removeKey(new FrameKey(properties
.zone
, properties
.frameNumber
));
311 private boolean removeKey(FrameKey key
)
313 FrameRecord value
= this.frameDirectory
.remove(key
);
315 return value
!= null;
318 private static Sector
sectorFor(RpfFrameProperties properties
)
320 if (properties
== null
321 || properties
.zone
== null
322 || properties
.dataSeries
== null)
324 RpfZone
.ZoneValues zoneValues
= properties
.zone
.zoneValues(properties
.dataSeries
);
325 if (properties
.frameNumber
< 0 || properties
.frameNumber
> (zoneValues
.maximumFrameNumber
- 1))
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
);
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
)
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)
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);
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
;
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);
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)
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;
519 // private static void drawFrame(Graphics2D g2d, int gWidth, int gHeight, Color fillColor,
520 // Sector graphicsExtent, FrameRecord record)
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;
540 //// RpfImageFile rpfImageFile = RpfImageFile.load(new File(record.filePath));
541 //// img = rpfImageFile.getBufferedImage();
543 //// catch (Exception e)
545 //// WorldWind.logger().log(Level.FINE, "", e);
547 //// if (img == null)
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);
556 //// g2d.drawImage(img, op, x0, y0);
559 public void dispose()
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
)
589 this.updateCoverage();
590 this.lastModCount
= this.modCount
;
593 if (!this.isSectorVisible(dc
, this.sector
))
596 // Create viewing window.
597 View view
= dc
.getView();
598 Sector viewingSector
= createViewSector(
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();
621 this.renderCoverage(dc
);
623 // Render frame tiles.
624 if (percentViewportOfSector(dc
, viewingSector
) > this.drawFrameThreshold
)
626 this.renderFrames(dc
, renderQueue
);
627 this.requestAllFrames(requestQueue
);
631 if (percentViewportOfSector(dc
, this.sector
) < this.drawIconThreshold
)
633 this.renderIcon(dc
, drawSector
);
639 public WWIcon
getIcon()
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()))
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)
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
);
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
)
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
);
759 public void setIcon(WWIcon icon
)
763 String message
= WorldWind
.retrieveErrMsg("nullValue.Icon");
764 WorldWind
.logger().log(Level
.FINE
, message
);
765 throw new IllegalArgumentException(message
);
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
;
815 public FrameRequest(FrameRecord frameRecord
)
817 this.frameRecord
= frameRecord
;
818 this.timestamp
= System
.currentTimeMillis();
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
)
849 this.record
= record
;
852 public boolean equals(Object o
)
856 if (o
== null || !o
.getClass().equals(this.getClass()))
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()
872 public int getContentLength()
877 public int getContentLengthRead()
882 public String
getContentType()
887 public long getEndTime()
892 public String
getName()
894 return this.record
.getFilePath();
897 public String
getState()
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()
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
);
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
;
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())
977 String cacheFilePath
= this.record
.getCacheFilePath(this.layer
);
979 if (!this.record
.fileLock
.tryLock())
981 this.setState(RETRIEVER_STATE_SUCCESSFUL
);
986 this.setState(RETRIEVER_STATE_STARTED
);
988 if (!this.interrupted())
990 if (isFileResident(cacheFilePath
))
992 this.setState(RETRIEVER_STATE_SUCCESSFUL
);
997 if (!this.interrupted())
999 this.setState(RETRIEVER_STATE_CONNECTING
);
1000 File file
= new File(this.record
.getFilePath());
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
);
1015 String message
= WorldWind
.retrieveErrMsg("generic.CantCreateCacheFile")
1017 throw new IOException(message
);
1019 if (haveARBNonPowerOfTwo
)
1021 this.buffer
= this.rpfImageFile
.getImageAsDdsTexture();
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
);
1048 this.setState(RETRIEVER_STATE_ERROR
);
1049 // this.layer.absentResourceList.markResourceAbsent(absentIdFor(this.record));
1054 this.record
.fileLock
.unlock();
1061 private static class ReadTask
implements Runnable
1063 public final RpfLayer layer
;
1064 public final FrameRecord record
;
1066 public ReadTask(RpfLayer layer
, FrameRecord record
)
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)
1081 // WorldWind.dataFileCache().removeFile(fileURL);
1082 // String message = WorldWind.retrieveErrMsg("generic.DataFileExpired") + fileURL;
1083 // WorldWind.logger().log(Level.FINER, message);
1086 public boolean equals(Object o
)
1090 if (o
== null || !o
.getClass().equals(this.getClass()))
1092 final ReadTask that
= (ReadTask
) o
;
1093 return (this.record
!= null) ?
this.record
.equals(that
.record
) : (that
.record
== null);
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
);
1117 if (!record
.fileLock
.tryLock())
1121 // // The file has expired. Delete it.
1122 // if (WWIO.isFileOutOfDate(dataFileURL, record.getExpiryTime()))
1124 // this.deleteOutOfDateFile(dataFileURL);
1125 // this.layer.firePropertyChange(AVKey.LAYER, null, this.layer);
1129 if (this.layer
.isTileResident(key
))
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));
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
);
1162 record
.fileLock
.unlock();
1167 private void downloadFrame(FrameRecord record
)
1169 // if (this.absentResourceList.isResourceAbsent(absentIdFor(record)))
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.
1178 this.downloadQueue
.addFirst(frameRequest
);
1179 // Frame is already requested, update it's timestamp.
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)
1207 // RpfFrameProperties properties = record.getRpfFrameProperties();
1208 // return properties.zone.ordinal() + (100 * properties.frameNumber);
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
)
1242 while (result
< value
)
1246 return (result
>> 1);
1249 private void requestFrame(FrameRecord record
)
1251 // if (this.absentResourceList.isResourceAbsent(absentIdFor(record)))
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.
1260 this.readQueue
.addFirst(frameRequest
);
1261 // Frame is already requested, update it's timestamp.
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
));