From b669116dc9aa961b743e71f470cdf46a4f16ac03 Mon Sep 17 00:00:00 2001
From: Joel Aaron Cohen
Date: Tue, 27 Nov 2007 12:18:47 -0500
Subject: [PATCH] Worldwind public release 0.2
---
ErrorStrings.properties | 443 +++++++
ErrorStrings_de_DE.properties | 6 +
ErrorStrings_ja.properties | 2 +
ErrorStrings_zh_CN.properties | 8 +
ThreadStrings.properties | 6 +
config/worldwind.properties | 35 +
gov/nasa/worldwind/AVKey.java | 80 ++
gov/nasa/worldwind/AVList.java | 122 ++
gov/nasa/worldwind/AVListImpl.java | 239 ++++
gov/nasa/worldwind/AbsentResourceList.java | 84 ++
gov/nasa/worldwind/AbstractFileCache.java | 500 +++++++
gov/nasa/worldwind/AbstractView.java | 391 ++++++
gov/nasa/worldwind/BasicDataFileCache.java | 35 +
gov/nasa/worldwind/BasicElevationModel.java | 676 ++++++++++
gov/nasa/worldwind/BasicFrameController.java | 324 +++++
gov/nasa/worldwind/BasicMemoryCache.java | 411 ++++++
gov/nasa/worldwind/BasicModel.java | 172 +++
gov/nasa/worldwind/BasicOrbitView.java | 442 +++++++
gov/nasa/worldwind/BasicRetrievalService.java | 505 +++++++
gov/nasa/worldwind/BasicSceneController.java | 228 ++++
gov/nasa/worldwind/Cacheable.java | 26 +
gov/nasa/worldwind/Configuration.java | 268 ++++
gov/nasa/worldwind/DDSConverter.java | 844 ++++++++++++
gov/nasa/worldwind/DrawContext.java | 269 ++++
gov/nasa/worldwind/DrawContextImpl.java | 384 ++++++
gov/nasa/worldwind/ElevationModel.java | 126 ++
gov/nasa/worldwind/FileCache.java | 34 +
gov/nasa/worldwind/FrameController.java | 26 +
gov/nasa/worldwind/GeoRSSParser.java | 569 ++++++++
gov/nasa/worldwind/Globe.java | 46 +
gov/nasa/worldwind/HTTPRetriever.java | 61 +
gov/nasa/worldwind/IconRenderer.java | 582 ++++++++
gov/nasa/worldwind/InputHandler.java | 30 +
gov/nasa/worldwind/Layer.java | 34 +
gov/nasa/worldwind/LayerList.java | 90 ++
gov/nasa/worldwind/Level.java | 345 +++++
gov/nasa/worldwind/LevelSet.java | 234 ++++
gov/nasa/worldwind/Locatable.java | 18 +
gov/nasa/worldwind/Material.java | 103 ++
gov/nasa/worldwind/MemoryCache.java | 174 +++
gov/nasa/worldwind/Model.java | 40 +
gov/nasa/worldwind/OrderedRenderable.java | 16 +
gov/nasa/worldwind/Pedestal.java | 44 +
gov/nasa/worldwind/PickSupport.java | 114 ++
gov/nasa/worldwind/Pickable.java | 16 +
gov/nasa/worldwind/PickedObject.java | 131 ++
gov/nasa/worldwind/PickedObjectList.java | 55 +
gov/nasa/worldwind/PlaceName.java | 41 +
gov/nasa/worldwind/PlaceNameRenderer.java | 333 +++++
gov/nasa/worldwind/PlaceNameService.java | 391 ++++++
gov/nasa/worldwind/PlaceNameServiceSet.java | 81 ++
gov/nasa/worldwind/PositionEvent.java | 55 +
gov/nasa/worldwind/PositionListener.java | 18 +
gov/nasa/worldwind/Renderable.java | 25 +
gov/nasa/worldwind/RenderingEvent.java | 39 +
gov/nasa/worldwind/RenderingListener.java | 18 +
gov/nasa/worldwind/RetrievalFuture.java | 16 +
gov/nasa/worldwind/RetrievalPostProcessor.java | 16 +
gov/nasa/worldwind/RetrievalService.java | 30 +
.../worldwind/RetrieveToFilePostProcessor.java | 82 ++
gov/nasa/worldwind/Retriever.java | 46 +
gov/nasa/worldwind/SceneController.java | 40 +
gov/nasa/worldwind/SectorGeometry.java | 30 +
gov/nasa/worldwind/SectorGeometryList.java | 125 ++
gov/nasa/worldwind/SelectEvent.java | 74 ++
gov/nasa/worldwind/SelectListener.java | 18 +
gov/nasa/worldwind/StringUtil.java | 23 +
gov/nasa/worldwind/SurfaceTileRenderer.java | 309 +++++
gov/nasa/worldwind/Tessellator.java | 16 +
gov/nasa/worldwind/ThreadedTaskService.java | 172 +++
gov/nasa/worldwind/Tile.java | 407 ++++++
gov/nasa/worldwind/TileKey.java | 205 +++
gov/nasa/worldwind/ToolTipRenderer.java | 275 ++++
gov/nasa/worldwind/Track.java | 20 +
gov/nasa/worldwind/TrackPoint.java | 30 +
gov/nasa/worldwind/TrackPointIterator.java | 22 +
gov/nasa/worldwind/TrackPointIteratorImpl.java | 99 ++
gov/nasa/worldwind/TrackRenderer.java | 409 ++++++
gov/nasa/worldwind/TrackSegment.java | 16 +
gov/nasa/worldwind/URLRetriever.java | 475 +++++++
gov/nasa/worldwind/UserFacingIcon.java | 143 ++
gov/nasa/worldwind/Version.java | 51 +
gov/nasa/worldwind/View.java | 381 ++++++
.../worldwind/WWDuplicateRequestException.java | 19 +
gov/nasa/worldwind/WWIO.java | 354 +++++
gov/nasa/worldwind/WWIcon.java | 59 +
gov/nasa/worldwind/WWObject.java | 19 +
gov/nasa/worldwind/WWObjectImpl.java | 24 +
gov/nasa/worldwind/WWRuntimeException.java | 33 +
gov/nasa/worldwind/WorldWind.java | 180 +++
gov/nasa/worldwind/WorldWindow.java | 81 ++
gov/nasa/worldwind/WorldWindowGLAutoDrawable.java | 187 +++
gov/nasa/worldwind/WorldWindowImpl.java | 62 +
gov/nasa/worldwind/awt/AWTInputHandler.java | 852 ++++++++++++
gov/nasa/worldwind/awt/InterpolatorTimer.java | 303 +++++
gov/nasa/worldwind/awt/KeyPollTimer.java | 129 ++
gov/nasa/worldwind/awt/WorldWindowGLCanvas.java | 216 +++
gov/nasa/worldwind/awt/WorldWindowGLJPanel.java | 191 +++
gov/nasa/worldwind/formats/gpx/ElementParser.java | 178 +++
gov/nasa/worldwind/formats/gpx/GpxReader.java | 194 +++
gov/nasa/worldwind/formats/gpx/GpxTrack.java | 127 ++
gov/nasa/worldwind/formats/gpx/GpxTrackPoint.java | 173 +++
.../worldwind/formats/gpx/GpxTrackSegment.java | 77 ++
.../formats/nitfs/AbstractRpf2DdsCompress.java | 28 +
.../worldwind/formats/nitfs/Cadrg2DdsCompress.java | 44 +
.../worldwind/formats/nitfs/Cib2DdsCompress.java | 59 +
.../formats/nitfs/CompressionLookupRecord.java | 89 ++
gov/nasa/worldwind/formats/nitfs/DDSBlock4x4.java | 36 +
.../formats/nitfs/NitfsDataExtensionSegment.java | 21 +
.../formats/nitfs/NitfsExtendedHeaderSegment.java | 21 +
.../worldwind/formats/nitfs/NitfsFileHeader.java | 275 ++++
.../worldwind/formats/nitfs/NitfsImageBand.java | 126 ++
.../worldwind/formats/nitfs/NitfsImageSegment.java | 654 +++++++++
.../worldwind/formats/nitfs/NitfsLabelSegment.java | 21 +
gov/nasa/worldwind/formats/nitfs/NitfsMessage.java | 208 +++
.../nitfs/NitfsReservedExtensionSegment.java | 21 +
.../formats/nitfs/NitfsRuntimeException.java | 51 +
gov/nasa/worldwind/formats/nitfs/NitfsSegment.java | 41 +
.../worldwind/formats/nitfs/NitfsSegmentType.java | 35 +
.../formats/nitfs/NitfsSymbolSegment.java | 21 +
.../worldwind/formats/nitfs/NitfsTextSegment.java | 21 +
.../nitfs/NitfsUserDefinedHeaderSegment.java | 30 +
gov/nasa/worldwind/formats/nitfs/NitfsUtil.java | 133 ++
.../worldwind/formats/nitfs/Rpf2DdsCompress.java | 18 +
.../formats/nitfs/UserDefinedImageSubheader.java | 58 +
gov/nasa/worldwind/formats/nmea/NmeaReader.java | 255 ++++
.../worldwind/formats/nmea/NmeaTrackPoint.java | 194 +++
gov/nasa/worldwind/formats/rpf/RpfColorMap.java | 111 ++
gov/nasa/worldwind/formats/rpf/RpfDataSeries.java | 190 +++
gov/nasa/worldwind/formats/rpf/RpfFile.java | 42 +
.../worldwind/formats/rpf/RpfFileComponents.java | 44 +
.../formats/rpf/RpfFrameFileComponents.java | 166 +++
.../formats/rpf/RpfFrameFileIndexSection.java | 193 +++
.../rpf/RpfFrameFilenameFormatException.java | 32 +
.../formats/rpf/RpfFrameFilenameUtil.java | 261 ++++
.../worldwind/formats/rpf/RpfFrameProperties.java | 85 ++
.../formats/rpf/RpfFramePropertyType.java | 68 +
.../worldwind/formats/rpf/RpfHeaderSection.java | 43 +
gov/nasa/worldwind/formats/rpf/RpfImageFile.java | 188 +++
gov/nasa/worldwind/formats/rpf/RpfImageType.java | 20 +
.../worldwind/formats/rpf/RpfLocationSection.java | 303 +++++
gov/nasa/worldwind/formats/rpf/RpfProducer.java | 94 ++
gov/nasa/worldwind/formats/rpf/RpfTocCrawler.java | 294 +++++
gov/nasa/worldwind/formats/rpf/RpfTocFile.java | 75 ++
.../formats/rpf/RpfUserDefinedHeaderSegment.java | 37 +
gov/nasa/worldwind/formats/rpf/RpfZone.java | 526 ++++++++
gov/nasa/worldwind/geom/Angle.java | 463 +++++++
gov/nasa/worldwind/geom/Cylinder.java | 308 +++++
gov/nasa/worldwind/geom/Extent.java | 61 +
gov/nasa/worldwind/geom/Frustum.java | 268 ++++
gov/nasa/worldwind/geom/Intersection.java | 82 ++
gov/nasa/worldwind/geom/LatLon.java | 152 +++
gov/nasa/worldwind/geom/Line.java | 136 ++
gov/nasa/worldwind/geom/Matrix.java | 32 +
gov/nasa/worldwind/geom/Matrix4.java | 857 ++++++++++++
gov/nasa/worldwind/geom/Plane.java | 164 +++
gov/nasa/worldwind/geom/Point.java | 556 ++++++++
gov/nasa/worldwind/geom/PolarPoint.java | 214 +++
gov/nasa/worldwind/geom/Polyline.java | 350 +++++
gov/nasa/worldwind/geom/Position.java | 76 ++
gov/nasa/worldwind/geom/Quadrilateral.java | 190 +++
gov/nasa/worldwind/geom/Quaternion.java | 577 ++++++++
gov/nasa/worldwind/geom/Sector.java | 725 ++++++++++
gov/nasa/worldwind/geom/Sphere.java | 323 +++++
gov/nasa/worldwind/geom/SurfacePolygon.java | 92 ++
gov/nasa/worldwind/geom/SurfacePolyline.java | 31 +
gov/nasa/worldwind/geom/SurfaceQuadrilateral.java | 51 +
gov/nasa/worldwind/geom/SurfaceShape.java | 218 +++
gov/nasa/worldwind/geom/Triangle.java | 121 ++
gov/nasa/worldwind/geom/ViewFrustum.java | 182 +++
gov/nasa/worldwind/globes/Earth.java | 25 +
gov/nasa/worldwind/globes/EarthElevationModel.java | 44 +
.../globes/EllipsoidIcosahedralTessellator.java | 900 +++++++++++++
.../globes/EllipsoidRectangularTessellator.java | 944 +++++++++++++
gov/nasa/worldwind/globes/EllipsoidalGlobe.java | 380 ++++++
gov/nasa/worldwind/layers/AbstractLayer.java | 236 ++++
gov/nasa/worldwind/layers/CompassLayer.java | 436 ++++++
.../worldwind/layers/Earth/BMNGSurfaceLayer.java | 49 +
.../layers/Earth/EarthNASAPlaceNameLayer.java | 245 ++++
gov/nasa/worldwind/layers/Earth/LandsatI3.java | 51 +
.../layers/Earth/PoliticalBoundariesLayer.java | 86 ++
.../worldwind/layers/Earth/USGSDigitalOrtho.java | 54 +
.../worldwind/layers/Earth/USGSUrbanAreaOrtho.java | 52 +
gov/nasa/worldwind/layers/IconLayer.java | 105 ++
gov/nasa/worldwind/layers/PlaceNameLayer.java | 872 ++++++++++++
gov/nasa/worldwind/layers/RenderableLayer.java | 137 ++
gov/nasa/worldwind/layers/RpfLayer.java | 1166 ++++++++++++++++
gov/nasa/worldwind/layers/TextureTile.java | 408 ++++++
gov/nasa/worldwind/layers/TiledImageLayer.java | 1394 ++++++++++++++++++++
gov/nasa/worldwind/layers/TrackLayer.java | 165 +++
worldwinddemo/AWT1Up.java | 317 +++++
worldwinddemo/BasicDemo.java | 163 +++
worldwinddemo/StatusBar.java | 113 ++
193 files changed, 37180 insertions(+)
create mode 100644 ErrorStrings.properties
create mode 100644 ErrorStrings_de_DE.properties
create mode 100644 ErrorStrings_ja.properties
create mode 100644 ErrorStrings_zh_CN.properties
create mode 100644 ThreadStrings.properties
create mode 100644 config/worldwind.properties
create mode 100644 gov/nasa/worldwind/AVKey.java
create mode 100644 gov/nasa/worldwind/AVList.java
create mode 100644 gov/nasa/worldwind/AVListImpl.java
create mode 100644 gov/nasa/worldwind/AbsentResourceList.java
create mode 100644 gov/nasa/worldwind/AbstractFileCache.java
create mode 100644 gov/nasa/worldwind/AbstractView.java
create mode 100644 gov/nasa/worldwind/BasicDataFileCache.java
create mode 100644 gov/nasa/worldwind/BasicElevationModel.java
create mode 100644 gov/nasa/worldwind/BasicFrameController.java
create mode 100644 gov/nasa/worldwind/BasicMemoryCache.java
create mode 100644 gov/nasa/worldwind/BasicModel.java
create mode 100644 gov/nasa/worldwind/BasicOrbitView.java
create mode 100644 gov/nasa/worldwind/BasicRetrievalService.java
create mode 100644 gov/nasa/worldwind/BasicSceneController.java
create mode 100644 gov/nasa/worldwind/Cacheable.java
create mode 100644 gov/nasa/worldwind/Configuration.java
create mode 100644 gov/nasa/worldwind/DDSConverter.java
create mode 100644 gov/nasa/worldwind/DrawContext.java
create mode 100644 gov/nasa/worldwind/DrawContextImpl.java
create mode 100644 gov/nasa/worldwind/ElevationModel.java
create mode 100644 gov/nasa/worldwind/FileCache.java
create mode 100644 gov/nasa/worldwind/FrameController.java
create mode 100644 gov/nasa/worldwind/GeoRSSParser.java
create mode 100644 gov/nasa/worldwind/Globe.java
create mode 100644 gov/nasa/worldwind/HTTPRetriever.java
create mode 100644 gov/nasa/worldwind/IconRenderer.java
create mode 100644 gov/nasa/worldwind/InputHandler.java
create mode 100644 gov/nasa/worldwind/Layer.java
create mode 100644 gov/nasa/worldwind/LayerList.java
create mode 100644 gov/nasa/worldwind/Level.java
create mode 100644 gov/nasa/worldwind/LevelSet.java
create mode 100644 gov/nasa/worldwind/Locatable.java
create mode 100644 gov/nasa/worldwind/Material.java
create mode 100644 gov/nasa/worldwind/MemoryCache.java
create mode 100644 gov/nasa/worldwind/Model.java
create mode 100644 gov/nasa/worldwind/OrderedRenderable.java
create mode 100644 gov/nasa/worldwind/Pedestal.java
create mode 100644 gov/nasa/worldwind/PickSupport.java
create mode 100644 gov/nasa/worldwind/Pickable.java
create mode 100644 gov/nasa/worldwind/PickedObject.java
create mode 100644 gov/nasa/worldwind/PickedObjectList.java
create mode 100644 gov/nasa/worldwind/PlaceName.java
create mode 100644 gov/nasa/worldwind/PlaceNameRenderer.java
create mode 100644 gov/nasa/worldwind/PlaceNameService.java
create mode 100644 gov/nasa/worldwind/PlaceNameServiceSet.java
create mode 100644 gov/nasa/worldwind/PositionEvent.java
create mode 100644 gov/nasa/worldwind/PositionListener.java
create mode 100644 gov/nasa/worldwind/Renderable.java
create mode 100644 gov/nasa/worldwind/RenderingEvent.java
create mode 100644 gov/nasa/worldwind/RenderingListener.java
create mode 100644 gov/nasa/worldwind/RetrievalFuture.java
create mode 100644 gov/nasa/worldwind/RetrievalPostProcessor.java
create mode 100644 gov/nasa/worldwind/RetrievalService.java
create mode 100644 gov/nasa/worldwind/RetrieveToFilePostProcessor.java
create mode 100644 gov/nasa/worldwind/Retriever.java
create mode 100644 gov/nasa/worldwind/SceneController.java
create mode 100644 gov/nasa/worldwind/SectorGeometry.java
create mode 100644 gov/nasa/worldwind/SectorGeometryList.java
create mode 100644 gov/nasa/worldwind/SelectEvent.java
create mode 100644 gov/nasa/worldwind/SelectListener.java
create mode 100644 gov/nasa/worldwind/StringUtil.java
create mode 100644 gov/nasa/worldwind/SurfaceTileRenderer.java
create mode 100644 gov/nasa/worldwind/Tessellator.java
create mode 100644 gov/nasa/worldwind/ThreadedTaskService.java
create mode 100644 gov/nasa/worldwind/Tile.java
create mode 100644 gov/nasa/worldwind/TileKey.java
create mode 100644 gov/nasa/worldwind/ToolTipRenderer.java
create mode 100644 gov/nasa/worldwind/Track.java
create mode 100644 gov/nasa/worldwind/TrackPoint.java
create mode 100644 gov/nasa/worldwind/TrackPointIterator.java
create mode 100644 gov/nasa/worldwind/TrackPointIteratorImpl.java
create mode 100644 gov/nasa/worldwind/TrackRenderer.java
create mode 100644 gov/nasa/worldwind/TrackSegment.java
create mode 100644 gov/nasa/worldwind/URLRetriever.java
create mode 100644 gov/nasa/worldwind/UserFacingIcon.java
create mode 100644 gov/nasa/worldwind/Version.java
create mode 100644 gov/nasa/worldwind/View.java
create mode 100644 gov/nasa/worldwind/WWDuplicateRequestException.java
create mode 100644 gov/nasa/worldwind/WWIO.java
create mode 100644 gov/nasa/worldwind/WWIcon.java
create mode 100644 gov/nasa/worldwind/WWObject.java
create mode 100644 gov/nasa/worldwind/WWObjectImpl.java
create mode 100644 gov/nasa/worldwind/WWRuntimeException.java
create mode 100644 gov/nasa/worldwind/WorldWind.java
create mode 100644 gov/nasa/worldwind/WorldWindow.java
create mode 100644 gov/nasa/worldwind/WorldWindowGLAutoDrawable.java
create mode 100644 gov/nasa/worldwind/WorldWindowImpl.java
create mode 100644 gov/nasa/worldwind/awt/AWTInputHandler.java
create mode 100644 gov/nasa/worldwind/awt/InterpolatorTimer.java
create mode 100644 gov/nasa/worldwind/awt/KeyPollTimer.java
create mode 100644 gov/nasa/worldwind/awt/WorldWindowGLCanvas.java
create mode 100644 gov/nasa/worldwind/awt/WorldWindowGLJPanel.java
create mode 100644 gov/nasa/worldwind/formats/gpx/ElementParser.java
create mode 100644 gov/nasa/worldwind/formats/gpx/GpxReader.java
create mode 100644 gov/nasa/worldwind/formats/gpx/GpxTrack.java
create mode 100644 gov/nasa/worldwind/formats/gpx/GpxTrackPoint.java
create mode 100644 gov/nasa/worldwind/formats/gpx/GpxTrackSegment.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/AbstractRpf2DdsCompress.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/Cadrg2DdsCompress.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/Cib2DdsCompress.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/CompressionLookupRecord.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/DDSBlock4x4.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsDataExtensionSegment.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsExtendedHeaderSegment.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsFileHeader.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsImageBand.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsImageSegment.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsLabelSegment.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsMessage.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsReservedExtensionSegment.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsRuntimeException.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsSegment.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsSegmentType.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsSymbolSegment.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsTextSegment.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsUserDefinedHeaderSegment.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/NitfsUtil.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/Rpf2DdsCompress.java
create mode 100644 gov/nasa/worldwind/formats/nitfs/UserDefinedImageSubheader.java
create mode 100644 gov/nasa/worldwind/formats/nmea/NmeaReader.java
create mode 100644 gov/nasa/worldwind/formats/nmea/NmeaTrackPoint.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfColorMap.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfDataSeries.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfFile.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfFileComponents.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfFrameFileComponents.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfFrameFileIndexSection.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfFrameFilenameFormatException.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfFrameFilenameUtil.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfFrameProperties.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfFramePropertyType.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfHeaderSection.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfImageFile.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfImageType.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfLocationSection.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfProducer.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfTocCrawler.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfTocFile.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfUserDefinedHeaderSegment.java
create mode 100644 gov/nasa/worldwind/formats/rpf/RpfZone.java
create mode 100644 gov/nasa/worldwind/geom/Angle.java
create mode 100644 gov/nasa/worldwind/geom/Cylinder.java
create mode 100644 gov/nasa/worldwind/geom/Extent.java
create mode 100644 gov/nasa/worldwind/geom/Frustum.java
create mode 100644 gov/nasa/worldwind/geom/Intersection.java
create mode 100644 gov/nasa/worldwind/geom/LatLon.java
create mode 100644 gov/nasa/worldwind/geom/Line.java
create mode 100644 gov/nasa/worldwind/geom/Matrix.java
create mode 100644 gov/nasa/worldwind/geom/Matrix4.java
create mode 100644 gov/nasa/worldwind/geom/Plane.java
create mode 100644 gov/nasa/worldwind/geom/Point.java
create mode 100644 gov/nasa/worldwind/geom/PolarPoint.java
create mode 100644 gov/nasa/worldwind/geom/Polyline.java
create mode 100644 gov/nasa/worldwind/geom/Position.java
create mode 100644 gov/nasa/worldwind/geom/Quadrilateral.java
create mode 100644 gov/nasa/worldwind/geom/Quaternion.java
create mode 100644 gov/nasa/worldwind/geom/Sector.java
create mode 100644 gov/nasa/worldwind/geom/Sphere.java
create mode 100644 gov/nasa/worldwind/geom/SurfacePolygon.java
create mode 100644 gov/nasa/worldwind/geom/SurfacePolyline.java
create mode 100644 gov/nasa/worldwind/geom/SurfaceQuadrilateral.java
create mode 100644 gov/nasa/worldwind/geom/SurfaceShape.java
create mode 100644 gov/nasa/worldwind/geom/Triangle.java
create mode 100644 gov/nasa/worldwind/geom/ViewFrustum.java
create mode 100644 gov/nasa/worldwind/globes/Earth.java
create mode 100644 gov/nasa/worldwind/globes/EarthElevationModel.java
create mode 100644 gov/nasa/worldwind/globes/EllipsoidIcosahedralTessellator.java
create mode 100644 gov/nasa/worldwind/globes/EllipsoidRectangularTessellator.java
create mode 100644 gov/nasa/worldwind/globes/EllipsoidalGlobe.java
create mode 100644 gov/nasa/worldwind/layers/AbstractLayer.java
create mode 100644 gov/nasa/worldwind/layers/CompassLayer.java
create mode 100644 gov/nasa/worldwind/layers/Earth/BMNGSurfaceLayer.java
create mode 100644 gov/nasa/worldwind/layers/Earth/EarthNASAPlaceNameLayer.java
create mode 100644 gov/nasa/worldwind/layers/Earth/LandsatI3.java
create mode 100644 gov/nasa/worldwind/layers/Earth/PoliticalBoundariesLayer.java
create mode 100644 gov/nasa/worldwind/layers/Earth/USGSDigitalOrtho.java
create mode 100644 gov/nasa/worldwind/layers/Earth/USGSUrbanAreaOrtho.java
create mode 100644 gov/nasa/worldwind/layers/IconLayer.java
create mode 100644 gov/nasa/worldwind/layers/PlaceNameLayer.java
create mode 100644 gov/nasa/worldwind/layers/RenderableLayer.java
create mode 100644 gov/nasa/worldwind/layers/RpfLayer.java
create mode 100644 gov/nasa/worldwind/layers/TextureTile.java
create mode 100644 gov/nasa/worldwind/layers/TiledImageLayer.java
create mode 100644 gov/nasa/worldwind/layers/TrackLayer.java
create mode 100644 worldwinddemo/AWT1Up.java
create mode 100644 worldwinddemo/BasicDemo.java
create mode 100644 worldwinddemo/StatusBar.java
diff --git a/ErrorStrings.properties b/ErrorStrings.properties
new file mode 100644
index 0000000..86b68fb
--- /dev/null
+++ b/ErrorStrings.properties
@@ -0,0 +1,443 @@
+#README: This file has several sections:
+# o Generic punctuation characters that can be used in messages
+# o Generic terms that can be used in messages
+# o Generic error/warning/log messages
+# o Class-specific error/warning/log messages
+
+#***********************************************************************************************
+# PLEASE KEEP THESE IN ALPHABETICAL ORDER * (except for the term, generic and nullValue blocks)
+#***********************************************************************************************
+
+# Punctuation characters
+punctuation.space=\u0020
+
+# Generic terms
+term.cacheFolder=cache folder
+term.datasetName=dataset name
+term.expiryTime=expiryTime
+term.formatSuffix=format suffix
+term.levelNumber=level number
+term.levelName=level name
+term.numLevels=number of levels
+term.numEMptyLevels=number of empty levels
+term.sector=sector
+term.service=service
+term.tileDelta=tile delta
+term.tileHeight=tile height
+term.tileURLBuilder=tile URL builder
+term.tileWidth=tile width
+term.unknown=unknown
+
+# Generic errors
+generic.AllUsersWindowsProfileNotKnown=The user-shared Windows profile cannot be determined.
+generic.angleOutOfRange=Angle out of range
+generic.arrayInvalidLength=Array invalid length
+generic.CantCreateCacheFile=Unable to create cache file for \u0020
+generic.columnIndexOutOfRange=Column index out of range
+generic.CorruptFile=File is corrupt\u0020
+generic.DataFileExpired=Deleting out of date data file\u0020
+generic.DeletedCorruptDataFile=Deleted corrupted data file\u0020
+generic.deltaAngleOutOfRange=Delta angle out of range
+generic.IOExceptionDuringTextureInitialization=IOException during texture initialization
+generic.ExceptionWhilePickingIcon=Exception while picking icon\u0020
+generic.ExceptionWhileRenderingIcon=Exception while rendering icon\u0020
+generic.ExceptionWhileRenderingLayer=Exception while rendering layer\u0020
+generic.EnumNotFound=Cannot find enumeration:\u0020
+generic.fileNotFound=File not found
+generic.InsufficientPositions=Insufficient number of positions
+generic.InvalidHint=Hint value is not recognized
+generic.indexOutOfRange=Index out of range
+generic.invalidIndex=Invalid index
+generic.NoSurfaceGeometry=No surface geometry to render
+generic.NumTextureUnitsLessThanOne=The number of texture units specified is less than 1
+generic.rowIndexOutOfRange=Row index out of range
+generic.TextureIOException=TextureIO exception while reading file\u0020
+generic.unknown=Unknown
+generic.UnknownOperatingSystem=The operating system this program is running on is not recognized.
+generic.UnsupportedOperation=The operation is not supported:\u0020
+generic.UsersHomeDirectoryNotKnown=This user's home director cannot be determined.
+generic.UsersWindowsProfileNotKnown=The user's Windows profile cannot be determined.
+generic.ValueOutOfRange=Value is out of range:\u0020
+
+# Null argument/value messages
+nullValue.ActionEventIsNull=Action event is null
+nullValue.AngleIsNull=Angle is null
+nullValue.ArrayIsNull=char array is null
+nullValue.AttributeKeyIsNull=Attribute key is null
+nullValue.AttributesIsNull=Attributes is null
+nullValue.BufferNull=Buffer is null
+nullValue.ByteBufferIsNull=ByteBuffer is null
+nullValue.CacheEntryIsNull=Cache Entry is null
+nullValue.CenterIsNull=Center is null
+nullValue.CenterPointIsNull=Center point is null
+nullValue.CharacterIsNull=Character is null
+nullValue.CollectionIsNull=Collection is null
+nullValue.ColorIsNull=Color is null
+nullValue.CompassPositionIsNull=Compass position is null
+nullValue.ConnectionIsNull=Connection is null
+nullValue.DataSetIsNull=Dataset is null
+nullValue.DestNull=Destination is null
+nullValue.DeviceIsNull=Device is null
+nullValue.DirectionIsNull=Direction is null
+nullValue.DisplayStyleIsNull=Display style is null
+nullValue.DocumentIsNull=Document is null
+nullValue.DrawContextIsNull=Drawing context is null
+nullValue.ElementNameIsNull=Element name is null
+nullValue.ElevationsIsNull=Elevations is null
+nullValue.ElevationModelIsNull=Elevation Model is null
+nullValue.EndPointIsNull=End point is null
+nullValue.EntriesIsNull=Entries is null
+nullValue.EventIsNull=Event is null
+nullValue.ExtentIsNull=Extent is null
+nullValue.FileCachePathIsNull=File cache path is null of zero length
+nullValue.FileIsNull=File is null
+nullValue.FilePathIsNull=File path is null
+nullValue.FontIsNull=Font is null
+nullValue.FontRenderContextIsNull=Font Render Context is null
+nullValue.FOVIsNull=Field of view is null
+nullValue.FrameControllerIsNull=FrameController is null
+nullValue.FrustumIsNull=Frustum is null
+nullValue.GeometryIsNull=Geometry is null
+nullValue.GetMethodIsNull=Attribute query listener is null
+nullValue.GetMethodReferenceIsNull=Get method reference is null
+nullValue.GLIsNull=GL is null
+nullValue.GLContextIsNull=GLContext is null
+nullValue.GLContextNotCurrent=GLContext is not current
+nullValue.GlobeIsNull=Globe is null
+nullValue.Icon=Icon is null
+nullValue.IconIterator=Icon iterator is null
+nullValue.IconFilePath=Icon file path is null
+nullValue.InputAnglesNull=One or more input angles are null
+nullValue.InputFileNameIsNull=Input file name is null
+nullValue.InputStreamIsNull=InputStream is null
+nullValue.IntersectionPointIsNull=Intersection point is null
+nullValue.Iterator=Iterator is null
+nullValue.KeyIsNull=Key is null
+nullValue.LatLonIsNull=LatLon is null
+nullValue.LatitudeOrLongitudeIsNull=Latitude or longitude is null
+nullValue.LayerIsNull=Layer is null
+nullValue.LayersIsNull=Layers is null
+nullValue.LayerParams=Layer parameters
+nullValue.LevelIsNull=Level is null
+nullValue.LevelSetIsNull=Level set is null
+nullValue.LevelZeroTileDeltaIsNull=Level zero tile delta is null
+nullValue.LineIsNull=Line is null
+nullValue.ListenerIsNull=Listener is null
+nullValue.LNameIsNull=lname is null
+nullValue.MaterialIsNull=Material is null
+nullValue.MatrixIsNull=Matrix is null
+nullValue.MimeTypeIsNull=MimeType is null
+nullValue.ModelIsNull=Model is null
+nullValue.ObjectIsNull=Object is null
+nullValue.OrderedRenderable=Ordered renderable is null
+nullValue.OriginIsNull=Origin is null
+nullValue.org.xml.sax.AttributesIsNull=org.xml.sax.Attributes is null
+nullValue.PathIsNull=Path is null
+nullValue.PickedObjectList=Picked objec list is null
+nullValue.PickedObject=Picked object is null
+nullValue.PickPoint=Pick point is null
+nullValue.PlaceNameServiceIsNull=PlaceNameService is null
+nullValue.PlaceNameServiceSetIsNull=PlaceNameServiceSet set is null
+nullValue.PlaneIsNull=Plane is null
+nullValue.PointIsNull=Point is null
+nullValue.PointsArrayIsNull=Points array is null
+nullValue.PostProcessorIsNull=PostProcessor is null
+nullValue.PolarPointArrayIsNull=PolarPoint array is null
+nullValue.PositionsListIsNull=Positions list is null
+nullValue.PositionIsNull=Position is null
+nullValue.PropertyNameIsNull=Property name is null
+nullValue.PropertyChangeEventIsNull=Property change event is null
+nullValue.PropertyChangeListenerIsNull=Property change listener is null
+nullValue.QNameIsNull=qname is null
+nullValue.QuaternionIsNull=Quaternion is null
+nullValue.RequestTaskIsNull=Request task is null
+nullValue.RetrieverIsNull=Retriever is null
+nullValue.RenderInfoIsNull=Geometry rendering info is null
+nullValue.RetrieverNameIsNull=Retriever name is null
+nullValue.RotationAngleIsNull=Rotation angle is null
+nullValue.RpfFrameExtendedPropertiesIsNull=RpfFrameExtendedProperties is null
+nullValue.RpfFramePropertiesIsNull=RpfFramePropreties is null
+nullValue.RpfDataSeriesIsNull=RpfDataSeries is null
+nullValue.RpfFramePropertyTypeIsNull=RpfFramePropertyType is null
+nullValue.RpfProducerIsNull=RpfProducer is null
+nullValue.RpfTocFileIsNull=RpfTocFile is null
+nullValue.RpfZoneIsNull=RpfZone is null
+nullValue.RunnableIsNull=Runnable is null
+nullValue.SectorIsNull=Sector is null
+nullValue.SectorGeometryListIsNull=SectorGeometryList is null
+nullValue.ServiceIsNull=Service is null
+nullValue.SelectionListener=SelectionListener is null
+nullValue.SetMethodIsNull=Attribute change listener is null
+nullValue.SetMethodReferenceIsNull=Set method reference is null
+nullValue.Shape=Shape is null
+nullValue.StringIsNull=String is null
+nullValue.TextureDataIsNull=TextureData is null
+nullValue.TextureIsNull=Texture is null
+nullValue.ThreadIsNull=Thread is null
+nullValue.TileIsNull=Tile is null
+nullValue.TileIterableIsNull=Tile iterable is null
+nullValue.TileDeltaIsNull=Tile Delta is null
+nullValue.TimeIsNull=Time is null
+nullValue.TracksIsNull=Track list is null
+nullValue.TracksPointsIteratorNull=Track points iterator is null
+nullValue.URIIsNull=URI is null
+nullValue.URLIsNull=URL is null
+nullValue.VectorIsNull=Vector is null
+nullValue.VertexBufferNull=Vertex buffer is null
+nullValue.ViewIsNull=View is null
+nullValue.visibleSectorNull=Visible sector is null
+nullValue.ShellCommandIsNullOrEmpty=Shell command is null or empty
+
+
+iterator.NoMoreTrackPoints=No more track points
+iterator.RemoveNotSupported=Remove operation not supported
+
+AbstractView.FieldOfViewIsNull=Field-of-view is null
+AbstractView.FrustumIsNull=Frustum is null
+AbstractView.ModelViewIsNull=Model-view matrix is null
+AbstractView.NoGlobeSpecifiedInDrawingContext=No globe specified in drawing context
+AbstractView.ProjectionIsNull=Projection-matrix is null
+AbstractView.ReferenceMatrixStackIsEmpty=Reference matrix stack is empty
+AbstractView.DrawingContextGLIsNull=Drawing context GL instance is null
+AbstractView.ViewportIsNull=Viewport is null
+AbstractView.ViewportWidthLessThanOrEqualZero=Viewport width is less than or equal to zero
+
+AVAAccessibleImpl.AttributeValueForKeyIsNotAString=Attribute value for key is not a String. Key:\u0020
+
+awt.AWTInputHandler.EventSourceNotAComponent=Event source is not an instance of java.awt.Component
+awt.InterpolatorTimer.DelayLessThanZero=Initial delay or delay is less than zero
+awt.InterpolatorTimer.PeriodLessThanZero=period is less than zero
+awt.InterpolatorTimer.DifferentMixTypes=Begin and end are different types
+awt.InterpolatorTimer.ErrorThresholdLessThanZero=Error threshold is less than zero
+awt.InterpolatorTimer.StepCoefficientLessThanZero=Step coefficient is less than zero
+awt.InterpolatorTimer.ViewPropertiesIsNull=ViewProperties is null
+awt.KeyPollTimer.PeriodLessThanZero=Period is less than zero
+awt.WorldWindowGLSurface.UnabletoCreateWindow=Unable to create WorldWindow
+
+BasicMemoryCache.nullListenerAdded=attemped to add null listener to BasicCache
+BasicMemoryCache.nullListenerRemoved=attempted to remove null listener from BasicCache
+
+BasicOrbitView.NullSurfacePoint=Point on surface is null
+BasicOrbitView.NoGlobeSpecified=No globe specified in AbstractView
+BasicOrbitView.InvalidConstraints=Invalid constraint argument(s)
+
+BasicElevationModel.AttemptToSetGlobeTwice=Attemped to set globe. Globe may only be set once
+BasicElevationModel.DensityBelowZero=Density is below zero
+BasicElevationModel.ExceptionComputingElevation=Exception computing elevation at\u0020
+
+BasicFrameController.ExceptionWhilePickingInLayer=Exception while picking in layer\u0020
+BasicFrameController.ExceptionWhileApplyingView=Exception while applying view
+BasicFrameController.ExceptionWhileRenderingLayer=Exception while rendering layer\u0020
+BasicFrameController.ExceptionWhileTessellatingGlobe=Exception while tessellating globe
+
+BasicMemoryCache.CacheItemNotAdded=Cache item not added
+
+BasicModel.LayerNotFound=Layer not found:\u0020
+
+BasicRetrievalService.ResourceRejected=Retrieval service rejected resource\u0020
+BasicRetrievalService.EnteringBeforeExecute=Entering beforeExecute"
+BasicRetrievalService.CancellingDuplicateRetrieval=Cancelling duplicate retrieval of\u0020
+BasicRetrievalService.CancellingTooOldRetrieval=Cancelling request too long on the retrieval queue for\u0020
+BasicRetrievalService.LeavingBeforeExecute=Leaving beforeExecute
+BasicRetrievalService.EnteringAfterExecute=Entering afterExecute
+BasicRetrievalService.ExceptionDuringRetrieval=Exception during retrieval of\u0020
+BasicRetrievalService.ExecutionExceptionDuringRetrieval=Execution exception during retrieval of\u0020
+BasicRetrievalService.RetrievalOf_1=Retrieval of\u0020
+BasicRetrievalService.WasInterrupted_2=\u0020was interrupted
+BasicRetrievalService.WasCancelled_2=\u0020was cancelled
+BasicRetrievalService.LeavingAfterExecute=Leaving afterExecute
+BasicRetrievalService.ExcptnRetrievingContentSizes=Exception retrieving content sizes from Retriever\u0020
+BasicRetrievalService.RetrieverPoolSizeIsLessThanOne=Retriever pool size is less than 1
+BasicRetrievalService.UncaughtExceptionDuringRetrieval=Uncaught exception during retrieval on thread\u0020
+
+BasicSceneController.GLContextNullStartRedisplay=GLContext is null at start of repaint
+BasicSceneController.GLContextNullStartPick=GLContext is null at start of pick
+BasicSceneController.NoFrameControllerStartRepaint=No frame controller at start of repaint
+BasicSceneController.NoFrameControllerStartPick=No frame controller at start of picking
+BasicSceneController.ExceptionDuringRendering=Exception encountered while repainting
+BasicSceneController.ExceptionDuringPick=Exception encountered while picking
+
+BasicView.NoModelExtentOnViewReset=No model extent on view reset
+
+Color.ValueTooHigh=Color value is too high, must be less than or equal to 1
+Color.ValueTooLow=Color value is too low, must be greater than or equal to 0
+
+Configuration.ConversionError=Error parsing configuration value\u0020
+Configuration.UnavailablePropsFile=Unavailable properties file\u0020
+Configuration.ExceptionReadingPropsFile=Exception while reading properties file\u0020
+
+DDSConverter.UnsupportedMimeType=Unsupported mime type\u0020
+DDSConverter.NoFileOrNoPermission=File does not exist or does not have read permission
+
+FileCache.ConfigurationNotFound=The specified cache configuration cannot be found:\u0020
+FileCache.CacheLocationInvalid=A configured cache location is invalid\u0020
+FileCache.CacheLocationIsFile=A configured cache location is a file but must be a directory\u0020
+FileCache.CannotRemoveWriteLocationFromSearchList=Cannot remove file-cache write location from the cache's search list
+FileCache.ExceptionCreatingURLForFile=Exception creating URL for file\u0020
+FileCache.ExceptionRemovingFile=Exception removing \u0020
+FileCache.NoConfiguration=A cache configuration is specified.
+FileCache.NoFileCacheReadLocations=No readable cache locations were found.
+FileCache.NoFileCacheWriteLocation=No writable locations exist for the file cache. Continuing without caching.
+
+geom.Cylinder.RadiusIsZeroOrNegative=Radius is zero or negative
+geom.Cylinder.AxisNormalVectorIsNullOrZero=Axis normal vector is null or zero
+geom.Cylinder.CylinderHeightOrDepthIsNegative=Cylinder height or depth is negative
+geom.Cylinder.CylinderHeightIsZero=Cylinder height is zero
+
+geom.Frustum.NearPlaneDistanceIsLessThanOrEqualToZero=Near plane distance is less than or equal to zero
+geom.Frustum.FarPlaneDistanceIsLessThanOrEqualToZero=Far plane distance is less than or equal to zero
+geom.Frustum.FarPlaneDistanceIsLessThanNearPlaneDistance=Far plane distance is less than near plane distance
+geom.Line.DirectionIsZeroVector=Direction is zero vector
+geom.Plane.VectorIsZero=Vector is zero
+geom.Sphere.NoPointsSpecified=No points specified for sphere
+geom.Sphere.RadiusIsZeroOrNegative=Radius is zero or negative
+geom.Ellipsoid.SemiAxisZeroOrNegative=One or more semi-axes is zero or negative
+geom.Matrix4.ArrayTooShort=Array is too short
+geom.ViewFrusutm.ClippingDistanceOutOfRange=Clipping distance(s) are out of range, or opposing clip distances cross
+geom.ViewFrustum.FieldOfViewIsNull=Field-of-view is null
+geom.ViewFrustum.FieldOfViewOutOfRange=Field-of-view is less than 0, or greater than 180
+
+GeoRSS.MissingValue=GeoRSS item is missing a value\u0020
+GeoRSS.ExceptionParsing=Exception while parsing GeoRSS content\u0020
+GeoRSS.InvalidCoordinateCount=GeoRSS shape coordinates are not approrpriate for the specified shape\u0020
+GeoRSS.IOExceptionParsing=IO exception while parsing GeoRSS content\u0020
+GeoRSS.MissingElement=GeoRSS item is missing required element\u0020
+GeoRSS.MissingElementContent=GeoRSS item is missing required element content for\u0020
+GeoRSS.NoCoordinates=GeoRSS shape contains no coordinate values for\u0020
+GeoRSS.NoShapes=No recognizable shapes in GeoRSS content\u0020
+GeoRSS.NumberFormatException=GeoRSS shape contains unrecognizable value\u0020
+GeoRSS.ParserConfigurationException=Exception while creating GeoRSS parser
+
+HTTPRetriever.ExceptionWhileQueryingResponseCode=Exception while querying response code for\u0020
+HTTPRetriever.ExceptionWhileQueryingResponseMessage=Exception while querying response message
+HTTPRetriever.ResponseCode=Response code\u0020
+HTTPRetriever.ResponseContentLength=, content-length\u0020
+HTTPRetriever.Retrieving=\u0020retrieving\u0020
+
+layers.AbstractLayer.NoGlobeSpecifiedInDrawingContext=No globe specified in drawing context
+layers.AbstractLayer.NoViewSpecifiedInDrawingContext=No view specified in drawing context
+layers.CompassLayer.Name=Compass
+layers.IconLayer.Name=Nasa Icons
+layers.InvalidPickColorRead=Invalid pick color read from frame buffer
+layers.GraticuleLayer.Name=Graticule
+layers.Earth.BlueMarbleLayer.Name=Blue Marble
+layers.Earth.DOESantaBarbaraLayer.Name=Santa Barbara
+layers.Earth.DOEWashingtonDCLayer.Name=Washington, D.C.
+layers.Earth.GraticuleLayer.Name=Graticule
+layers.Earth.PlaceName.Name=Place Names
+layers.Earth.LandsatVisibleColorLayer.Name=Landsat Visible Color
+layers.Earth.Landsat7VisibleColorLayer.Name=Landsat 7 Visible Color
+layers.Earth.LandsatI3Layer.Name=i\u00b3 Landsat
+layers.Earth.USGSDigitalOrtho.Name=USGS Digital Ortho
+layers.Earth.USGSUrbanAreaOrtho.Name=USGS Urban Area Ortho
+layers.Earth.PoliticalBoundaries.Name=Political Boundaries
+layers.LevelSet.InvalidLevelSet=Invalid level set:\u0020
+layers.LevelSet.InvalidLevelDescriptorFields=Invalid level descriptor fields:\u0020
+layers.LevelSet.LevelsNotContiguous=levels are not contiguous
+layers.LevelSet.LevelListIsEmpty=level list is empty
+layers.MeridianAndParallelLinesLayer.OpacityOutOfRange=Opacity out of range
+layers.PlaceNameLayer.ExceptionAttemptingToDownloadFile=Exception while attempting to download file
+layers.PlaceNameLayer.ExceptionAttemptingToReadFile=Exception attempting to read file\u0020
+layers.PlaceNameLayer.ExceptionSavingRetrievedFile=Exception while saving retrieved file to\u0020
+layers.PlaceNameLayer.ExceptionRenderingTile=Exception while rendering place names tile
+layers.PlaceNameLayer.Name=Place Names
+layers.RenderableLayer.Name=Renderable
+layers.RpfLayer.BadFrameInput=Bad frame-file input:\u0020
+layers.RpfLayer.DownloadInterrupted=Download interrupted for:\u0020
+layers.RpfLayer.ExceptionParsingFileName=Exception while parsing frame file-name:\u0020
+layers.ShapeLayer.Name=Shape
+layers.TextureLayer.Name=Texture
+layers.TextureLayer.ExceptionAttemptingToReadTextureFile=Exception attempting to read texture file
+layers.TextureLayer.ExceptionCreatingTextureUrl=Exception creating texture URL for\u0020
+layers.TextureLayer.ExceptionSavingRetrievedTextureFile=Exception while saving retrieved texture file to\u0020
+layers.TextureLayer.MinPixelsPerTexelTooLow=Minimum pixels per texel must be at least zero
+layers.TextureLayer.TargetPixelsPerTexelTooLow=Target pixels per texel must be at least zero
+layers.TextureLayer.MaxPixelsPerTexelTooLow=Max pixels per texel must be at least zero
+layers.TextureTile.MinDistanceToEyeNegative=Minimum distance to eye must be at least zero
+layers.TrackLayer.IOExceptionDuringInitialization=IOException during track initialization
+layers.TrackLayer.Name=Track
+
+NitfsReader.NoFileOrNoPermission=File does not exist or does not have read permission\u0020
+NitfsReader.UnknownOrUnsupportedNitfsFormat=Unknown or unsupported NITFS file format\u0020
+
+PlaceNameService.MaxDisplayDistanceLessThanMinDisplayDistance=Max display distance is less than minimum display distance
+PlaceNameService.MinDisplayDistanceGrtrThanMaxDisplayDistance=Min display distance is greater than max display distance
+PlaceNameService.RowOrColumnOutOfRange=Row or column out of range
+
+RetrieveToFilePostProcessor.NullBufferPostprocessing=Null buffer postprocessing\u0020
+RetrieveToFilePostProcessor.ErrorPostprocessing=Error postprocessing
+
+RpfDataSeries.InavlidRpfDataType=Rpf data type is invalid:\u0020
+RpfDataSeries.InvalidScaleOrGSD=Scale or Ground Sample Distance is invalid:\u0020
+
+RpfFrameFilenameUtil.BadFilenameLength=Illegal filename length:\u0020
+RpfFrameFilenameUtil.Base34Error=Illegal base34 encoding
+RpfFrameFilenameUtil.EnumNotFound=Illegal data-series, zone, or producer code
+RpfFrameFilenameUtil.IntegerNotParsed=Illegal frame-number or version number
+RpfFrameFilenameUtil.UnknownRpfDataType=Unknown Rpf data-type constant:\u0020
+
+RpfFrameProperties.BadFrameNumber=Frame-number is less than zero:\u0020
+RpfFrameProperties.BadVersion=Version is less than zero:\u0020
+
+RpfZone.UnknownRpfDataType=Unknown Rpf data-type constant:\u0020
+
+RpfTocCrawler.BadStart=Attempted to start running or stopped crawler
+RpfTocCrawler.ExceptionParsingFilename=Exception while parsing filename:\u0020
+
+tessellators.GnomicTessellation.Geometry.illegalParamT=Parameter "double t" is either larger than 1 or less than 0
+tessellators.GnomicTessellation.Geometry.illegalParamS=Parameter "double s" is either larger than 1 or less than 0
+
+ThreadedTaskService.CancellingDuplicateTask=Cancelling duplicate task of\u0020
+ThreadedTaskService.EnteringAfterExecute=Entering afterExecute
+ThreadedTaskService.EnteringBeforeExecute=Entering beforeExecute"
+ThreadedTaskService.LeavingBeforeExecute=Leaving beforeExecute
+ThreadedTaskService.UncaughtExceptionDuringTask=Uncaught exception during task on thread\u0020
+ThreadedTaskService.ResourceRejected=Task service rejected resource\u0020
+
+TiledElevationModel.ExceptionAttemptingToReadTextureFile=Exception attempting to read elevation file\u0020
+TiledElevationModel.ExceptionCreatingElevationsUrl=Exception creating elevations URL for\u0020
+TiledElevationModel.ExceptionSavingRetrievedElevationFile=Exception while saving retrieved elevation file to\u0020
+
+TileKey.levelIsLessThanZero=Level is less than zero
+TileKey.cacheNameIsNullOrEmpty=cache name is null or empty
+
+URLRetriever.ConnectTimeoutLessThanZero=Connect timeout is less than 0
+URLRetriever.ContentLengthIsGrtrThanMaxSizeFor=Content length is greater than maximum size for\u0020
+URLRetriever.ContentLengthIsLessThanMinSizeFor=Content length is less than minimum size for\u0020
+URLRetriever.ErrorAttemptingToRetrieve=Error attempting to retrieve\u0020
+URLRetriever.ErrorOpeningConnection=Error opening connection to\u0020
+URLRetriever.ErrorPostProcessing=Error postprocessing\u0020
+URLRetriever.ErrorReadingFromConnection=Error reading from connection to\u0020
+URLRetriever.ExceptionClosingInputStreamToConnection=Exception closing InputStream to connection\u0020
+URLRetriever.InputStreamFromConnectionNull=InputStream is null from connection to\u0020
+URLRetriever.InputStreamNullFor=InputStream is null for\u0020
+URLRetriever.NoZipEntryFor=No zip entry for\u0020
+URLRetriever.NullReturnedFromOpenConnection=Null returned from openConnection for\u0020
+URLRetriever.RetrievalInterruptedFor=Retrieval interrupted for\u0020
+URLRetriever.ZIPFileSizeLessThanMinSize=ZIP file size is less than minimum size for\u0020
+URLRetriever.ZIPFileSizeGrtrThanMaxSize=ZIP file size is greater than maximum size for\u0020
+
+#WorldWind.ClassNameKeyNulZero=Class name key is null or zero length
+#WorldWind.ExceptionCreatingComponent=Exception while creating World Wind component\u0020
+#WorldWind.ErrorCreatingComponent=Error while creating World Wind component\u0020
+#WorldWind.NoClassNameInConfigurationForKey=No class name in configuration for key\u0020
+#WorldWind.UnableToCreateClassForConfigurationKey=Unable to create class for configuration key\u0020
+
+WorldWindowGLCanvas.DisplayEventListenersDisplayChangedMethodCalled=Display event listener's displayChanged() method called
+WorldWindowGLCanvas.ExceptionAttemptingRepaintWorldWindow=Exception while attempting to repaint WorldWindow
+WorldWindowGLCanvas.GLAutoDrawableNullToConstructor=GLAutoDrawable is null to WorldWindowGLCanvas constructor
+WorldWindowGLCanvas.ScnCntrllerNullOnRepaint=Scene controller is null at repaint
+
+WorldWindowGLAutoDrawable.ExceptionDuringGLEventListenerDisplay=Exception during call to GL event display listener
+WorldWindowGLAutoDrawable.ExceptionDuringGLEventListenerInit=Exception during call to GL event init listener
+WorldWindowGLAutoDrawable.ExceptionDuringGLEventListenerReshape=Exception during call to GL event reshape listener
+WorldWindowGLAutoDrawable.ExceptionDuringGLEventListenerDisplayChanged=Exception during call to GL event display-changed listener
+
+WWIO.ErrorSavingBufferTo=Error saving buffer to\u0020
+WWIO.ErrorTryingToClose=Error trying to close\u0020
+WWIO.ExceptionValidatingFileExpiration=Exception attempting to check file expiration for\u0020
+WWIO.NumberBytesTransferLessThanOne=Number of bytes to transfer is less than 1
+WWIO.UnableToAcquireLockFor=Unable to acquire lock for\u0020
+WWIO.ZipFileIsEmpty=ZIP file is empty\u0020
+WWIO.ZipFileEntryNIF=ZIP file entry not in file\u0020
+
diff --git a/ErrorStrings_de_DE.properties b/ErrorStrings_de_DE.properties
new file mode 100644
index 0000000..aca2c1c
--- /dev/null
+++ b/ErrorStrings_de_DE.properties
@@ -0,0 +1,6 @@
+awt.WorldWindowGLSurface.UnabletoCreateWindow=Unf\u00e4hig, WorldWindow zu schaffen
+
+
+nullValue.AngleIsNull=Winkel ist ung\u00fcltig
+
+nullValue.BoundingBoxIsNull=Ein oder mehr begrenzende Kasten-Punkte sind ung\u00fcltig
diff --git a/ErrorStrings_ja.properties b/ErrorStrings_ja.properties
new file mode 100644
index 0000000..7b932a6
--- /dev/null
+++ b/ErrorStrings_ja.properties
@@ -0,0 +1,2 @@
+
+nullValue.AngleIsNull=\u89d2\u5ea6\u306f\u30d6\u30e9\u30f3\u30af\u3067\u3042\u308b
diff --git a/ErrorStrings_zh_CN.properties b/ErrorStrings_zh_CN.properties
new file mode 100644
index 0000000..734d39b
--- /dev/null
+++ b/ErrorStrings_zh_CN.properties
@@ -0,0 +1,8 @@
+awt.WorldWindowGLSurface.UnabletoCreateWindow=\u65e0\u6cd5\u521b\u9020\u4e16\u754c\u7a97\u53e3
+
+
+nullValue.AngleIsNull=\u89d2\u5ea6\u662f\u7a7a\u7684
+
+
+
+
diff --git a/ThreadStrings.properties b/ThreadStrings.properties
new file mode 100644
index 0000000..513687a
--- /dev/null
+++ b/ThreadStrings.properties
@@ -0,0 +1,6 @@
+BasicRetrievalService.RUNNING_THREAD_NAME_PREFIX=Running World Wind Retriever:\u0020
+BasicRetrievalService.IDLE_THREAD_NAME_PREFIX=Idle World Wind Retriever
+BasicRetrievalService.WorldWindRetrievalProgress=World Wind Retrieval Progress\u0020
+
+ThreadedTaskService.RUNNING_THREAD_NAME_PREFIX=Running World Wind Task:\u0020
+ThreadedTaskService.IDLE_THREAD_NAME_PREFIX=Idle World Wind Task
diff --git a/config/worldwind.properties b/config/worldwind.properties
new file mode 100644
index 0000000..93728de
--- /dev/null
+++ b/config/worldwind.properties
@@ -0,0 +1,35 @@
+# Default World Wind Configuration Properties
+# @version $Id: worldwind.properties 1798 2007-05-09 02:08:41Z dcollins $
+# Specify configuration values here to override World Wind's default values.
+# Lines starting with # are comments and ignored by World Wind.
+#
+gov.nasa.worldwind.avkey.DataFileCacheClassName=gov.nasa.worldwind.BasicDataFileCache
+gov.nasa.worldwind.avkey.DataFileCacheConfigurationFileName=config/DataFileCache.xml
+gov.nasa.worldwind.avkey.FrameControllerClassName=gov.nasa.worldwind.BasicFrameController
+gov.nasa.worldwind.avkey.GlobeClassName=gov.nasa.worldwind.globes.Earth
+gov.nasa.worldwind.avkey.InputHandlerClassName=gov.nasa.worldwind.awt.AWTInputHandler
+gov.nasa.worldwind.avkey.LoggerName=gov.nasa.worldwind
+gov.nasa.worldwind.avkey.MemoryCacheClassName=gov.nasa.worldwind.BasicMemoryCache
+gov.nasa.worldwind.avkey.ModelClassName=gov.nasa.worldwind.BasicModel
+gov.nasa.worldwind.avkey.RetrievalServiceClassName=gov.nasa.worldwind.BasicRetrievalService
+gov.nasa.worldwind.avkey.SceneControllerClassName=gov.nasa.worldwind.BasicSceneController
+gov.nasa.worldwind.avkey.ThreadedTaskServiceClassName=gov.nasa.worldwind.ThreadedTaskService
+gov.nasa.worldwind.avkey.ViewClassName=gov.nasa.worldwind.BasicOrbitView
+gov.nasa.worldwind.avkey.LayersClassName=\
+ gov.nasa.worldwind.layers.Earth.BMNGSurfaceLayer\
+ ,gov.nasa.worldwind.layers.Earth.LandsatI3\
+ ,gov.nasa.worldwind.layers.Earth.USGSUrbanAreaOrtho\
+ ,gov.nasa.worldwind.layers.Earth.EarthNASAPlaceNameLayer\
+ ,gov.nasa.worldwind.layers.CompassLayer
+
+gov.nasa.worldwind.avkey.InitialLatitude=38
+gov.nasa.worldwind.avkey.RetrievalPoolSize=5
+gov.nasa.worldwind.avkey.RetrievalQueueSize=200
+gov.nasa.worldwind.avkey.RetrievalStaleRequestLimit=9000
+gov.nasa.worldwind.avkey.ThreadedTaskPoolSize=1
+gov.nasa.worldwind.avkey.ThreadedTaskQueueSize=5
+gov.nasa.worldwind.avkey.VerticalExaggeration=1
+gov.nasa.worldwind.avkey.CacheSize=2000000000
+gov.nasa.worldwind.avkey.CacheLowWater=140000000
+gov.nasa.worldwind.avkey.URLConnectTimeout=8000
+gov.nasa.worldwind.avkey.URLReadTimeout=5000
diff --git a/gov/nasa/worldwind/AVKey.java b/gov/nasa/worldwind/AVKey.java
new file mode 100644
index 0000000..ef5c691
--- /dev/null
+++ b/gov/nasa/worldwind/AVKey.java
@@ -0,0 +1,80 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: AVKey.java 1792 2007-05-08 21:28:37Z tgaskins $
+ */
+public interface AVKey // TODO: Eliminate unused constants, if any
+{
+ final String CACHE_SIZE = "gov.nasa.worldwind.avkey.CacheSize";
+ final String CACHE_LOW_WATER = "gov.nasa.worldwind.avkey.CacheLowWater";
+
+ final String ELEVATION_MODEL = "gov.nasa.worldwind.avkey.ElevationModel";
+ final String EXTENT = "gov.nasa.worldwind.avkey.Extent";
+
+ final String DATA_FILE_CACHE_CLASS_NAME = "gov.nasa.worldwind.avkey.DataFileCacheClassName";
+ final String DATA_FILE_CACHE_CONFIGURATION_FILE_NAME = "gov.nasa.worldwind.avkey.DataFileCacheConfigurationFileName";
+
+ final String DISPLAY_NAME = "gov.nasa.worldwind.avkey.DisplayName";
+ final String DISPLAY_ICON = "gov.nasa.worldwind.avkey.DisplayIcon";
+
+ final String FOV = "gov.nasa.worldwind.avkey.FieldOfView";
+
+ final String FRAME_CONTROLLER = "gov.nasa.worldwind.avkey.FrameControllerObject";
+ final String FRAME_CONTROLLER_CLASS_NAME = "gov.nasa.worldwind.avkey.FrameControllerClassName";
+
+ final String GLOBE = "gov.nasa.worldwind.avkey.GlobeObject";
+ final String GLOBE_CLASS_NAME = "gov.nasa.worldwind.avkey.GlobeClassName";
+
+ final String INITIAL_LATITUDE = "gov.nasa.worldwind.avkey.InitialLatitude";
+ final String INITIAL_LONGITUDE = "gov.nasa.worldwind.avkey.InitialLongitude";
+ final String INPUT_HANDLER_CLASS_NAME = "gov.nasa.worldwind.avkey.InputHandlerClassName";
+
+ final String LAYER = "gov.nasa.worldwind.avkey.LayerObject";
+ final String LAYERS = "gov.nasa.worldwind.avkey.LayersObject";
+ final String LAYERS_CLASS_NAMES = "gov.nasa.worldwind.avkey.LayersClassName";
+
+ final String LOGGER_NAME = "gov.nasa.worldwind.avkey.LoggerName";
+
+ final String MEMORY_CACHE_CLASS_NAME = "gov.nasa.worldwind.avkey.MemoryCacheClassName";
+
+ final String MODEL = "gov.nasa.worldwind.avkey.ModelObject";
+ final String MODEL_CLASS_NAME = "gov.nasa.worldwind.avkey.ModelClassName";
+
+ final String PICKED_OBJECT = "gov.nasa.worldwind.avkey.PickedObject";
+ final String PICKED_OBJECT_ID = "gov.nasa.worldwind.avkey.PickedObject.ID";
+ final String PICKED_OBJECT_PARENT_LAYER = "gov.nasa.worldwind.avkey.PickedObject.ParentLayer";
+ final String PICKED_OBJECT_PARENT_LAYER_NAME = "gov.nasa.worldwind.avkey.PickedObject.ParentLayer.Name";
+
+ final String POSITION = "gov.nasa.worldwind.avkey.Position";
+
+ final String RETRIEVAL_POOL_SIZE = "gov.nasa.worldwind.avkey.RetrievalPoolSize";
+ final String RETRIEVAL_QUEUE_SIZE = "gov.nasa.worldwind.avkey.RetrievalQueueSize";
+ final String RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT = "gov.nasa.worldwind.avkey.RetrievalStaleRequestLimit";
+ final String RETRIEVAL_SERVICE_CLASS_NAME = "gov.nasa.worldwind.avkey.RetrievalServiceClassName";
+
+ final String RETRIEVER_STATE = "gov.nasa.worldwind.avkey.RetrieverState";
+
+ final String SCENE_CONTROLLER = "gov.nasa.worldwind.avkey.SceneControllerObject";
+ final String SCENE_CONTROLLER_CLASS_NAME = "gov.nasa.worldwind.avkey.SceneControllerClassName";
+ final String SECTOR = "gov.nasa.worldwind.avKey.Sector";
+ final String SENDER = "gov.nasa.worldwind.avkey.Sender";
+
+ final String THREADED_TASK_POOL_SIZE = "gov.nasa.worldwind.avkey.ThreadedTaskPoolSize";
+ final String THREADED_TASK_QUEUE_SIZE = "gov.nasa.worldwind.avkey.ThreadedTaskQueueSize";
+ final String THREADED_TASK_SERVICE_CLASS_NAME = "gov.nasa.worldwind.avkey.ThreadedTaskServiceClassName";
+
+ final String URL_CONNECT_TIMEOUT = "gov.nasa.worldwind.avkey.URLConnectTimeout";
+ final String URL_READ_TIMEOUT = "gov.nasa.worldwind.avkey.URLReadTimeout";
+
+ final String VERTICAL_EXAGGERATION = "gov.nasa.worldwind.avkey.VerticalExaggeration";
+ final String VIEW = "gov.nasa.worldwind.avkey.ViewObject";
+ final String VIEW_CLASS_NAME = "gov.nasa.worldwind.avkey.ViewClassName";
+
+}
diff --git a/gov/nasa/worldwind/AVList.java b/gov/nasa/worldwind/AVList.java
new file mode 100644
index 0000000..fcd25be
--- /dev/null
+++ b/gov/nasa/worldwind/AVList.java
@@ -0,0 +1,122 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * An interface for managing an attribute-value pair collection.
+ *
+ * @author Tom Gaskins
+ * @version $Id: AVList.java 1742 2007-05-06 16:34:29Z tgaskins $
+ */
+public interface AVList
+{
+ /**
+ * Adds a key/value pair to the list. Replaces an existing key/value pair if the list already contains the key.
+ *
+ * @param key the attribute name. May not be null
.
+ * @param value the attribute value. May be null
, in which case any existing value for the key is
+ * removed from the collection.
+ * @throws NullPointerException if key
is null
.
+ */
+ void setValue(String key, Object value);
+
+ /**
+ * Returns the value for a specified key.
+ *
+ * @param key the attribute name. May not be null
.
+ * @return the attribute value if one exists in the collection, otherwise null
.
+ * @throws NullPointerException if key
is null
.
+ */
+ Object getValue(String key);
+
+ /**
+ * Returns the value for a specified key. The value must be a {@link String}.
+ *
+ * @param key the attribute name. May not be null
.
+ * @return the attribute value if one exists in the collection, otherwise null
.
+ * @throws NullPointerException if key
is null
.
+ * @throws WWRuntimeException if the value in the collection is not a String
type.
+ */
+ String getStringValue(String key);
+
+ /**
+ * Indicates whether a key is in the collection.
+ *
+ * @param key the attribute name. May not be null
.
+ * @return true
if the key exists in the collection, otherwise false
.
+ * @throws NullPointerException if key
is null
.
+ */
+ boolean hasKey(String key);
+
+ /**
+ * Removes a specified key from the collection if the key exists, otherwise returns without affecting the
+ * collection.
+ *
+ * @param key the attribute name. May not be null
.
+ * @throws NullPointerException if key
is null
.
+ */
+ void removeKey(String key);
+
+ /**
+ * Adds a property change listener for the specified key.
+ * @param propertyName the key to associate the listener with.
+ * @param listener the listener to associate with the key.
+ * @throws IllegalArgumentException if either propertyName
or listener
is null
+ * @see java.beans.PropertyChangeSupport
+ */
+ void addPropertyChangeListener(String propertyName, java.beans.PropertyChangeListener listener);
+
+ /**
+ * Removes a property change listener associated with the specified key.
+ * @param propertyName the key associated with the change listener.
+ * @param listener the listener to remove.
+ * @throws IllegalArgumentException if either propertyName
or listener
is null
+ * @see java.beans.PropertyChangeSupport
+ */
+ void removePropertyChangeListener(String propertyName, java.beans.PropertyChangeListener listener);
+
+ /**
+ * Adds the specified all-property property change listener that will be called for all list changes.
+ * @param listener the listener to call.
+ * @throws IllegalArgumentException if listener
is null
+ * @see java.beans.PropertyChangeSupport
+ */
+ void addPropertyChangeListener(java.beans.PropertyChangeListener listener);
+
+ /**
+ * Removes the specified all-property property change listener.
+ * @param listener the listener to remove.
+ * @throws IllegalArgumentException if listener
is null
+ * @see java.beans.PropertyChangeSupport
+ */
+ void removePropertyChangeListener(java.beans.PropertyChangeListener listener);
+
+ /**
+ * Calls all property change listeners associated with the specified key.
+ * No listeners are called if odValue
and newValue
are equal and non-null.
+ * @param propertyName the key
+ * @param oldValue the value associated with the key before the even causing the firing.
+ * @param newValue the new value associated with the key.
+ * @throws IllegalArgumentException if propertyName
is null
+ * @see java.beans.PropertyChangeSupport
+ */
+ void firePropertyChange(String propertyName, Object oldValue, Object newValue);
+
+ /**
+ * Calls all registered property change listeners with the specified property change event.
+ * @param propertyChangeEvent the event
+ * @throws IllegalArgumentException if propertyChangeEvent
is null
+ * @see java.beans.PropertyChangeSupport
+ */
+ void firePropertyChange(java.beans.PropertyChangeEvent propertyChangeEvent);
+
+ /**
+ * Returns a shallow copy of this AVList
instance: the keys and values themselves are not cloned.
+ * @return a shallow copy of this AVList
.
+ */
+ AVList copy();
+}
diff --git a/gov/nasa/worldwind/AVListImpl.java b/gov/nasa/worldwind/AVListImpl.java
new file mode 100644
index 0000000..b2367e9
--- /dev/null
+++ b/gov/nasa/worldwind/AVListImpl.java
@@ -0,0 +1,239 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * An implementation class for the {@link AVList} interface. Classes implementing AVList
can subclass or
+ * aggreate this class to provide default AVList
functionality. This class maintains a hash table of
+ * attribute-value pairs.
+ *
+ * This class implements a notification mechanism for attribute-value changes. The mechanism provides a means for
+ * objects to observe attribute changes or queries for certain keys without explicitly monitoring all keys. See {@link
+ * java.beans.PropertyChangeSupport}.
+ *
+ * @author Tom Gaskins
+ * @version $Id: AVListImpl.java 1742 2007-05-06 16:34:29Z tgaskins $
+ */
+public class AVListImpl implements AVList, java.beans.PropertyChangeListener
+{
+ // TODO: Make thread-safe
+ /**
+ * Available to sub-classes for further exposure of property-change functionality.
+ */
+ protected final java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(this);
+
+ // To avoid unnecessary overhead, this object's hash map is created only if needed.
+ private java.util.Map avList;
+
+ /**
+ * Creates an empty attribute-value list.
+ */
+ public AVListImpl()
+ {
+ }
+
+ private boolean hasAvList()
+ {
+ return this.avList != null;
+ }
+
+ private void createAvList()
+ {
+ if (!this.hasAvList())
+ {
+ this.avList = new java.util.HashMap();
+ }
+ }
+
+ private java.util.Map avList(boolean createIfNone)
+ {
+ if (createIfNone && !this.hasAvList())
+ this.createAvList();
+
+ return this.avList;
+ }
+
+ public final Object getValue(String key)
+ {
+ if (key == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.AttributeKeyIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (this.hasAvList())
+ return this.avList.get(key);
+
+ return null;
+ }
+
+ public final String getStringValue(String key)
+ {
+ if (key == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.AttributeKeyIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalStateException(msg);
+ }
+ try
+ {
+ return (String) this.getValue(key);
+ }
+ catch (ClassCastException e)
+ {
+ String msg = WorldWind.retrieveErrMsg("AVAAccessibleImpl.AttributeValueForKeyIsNotAString") + key;
+
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new WWRuntimeException(msg, e);
+ }
+ }
+
+ public final void setValue(String key, Object value)
+ {
+ if (key == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.AttributeKeyIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ // Capture the existing value if there is one, then set the new value.
+ this.avList(true).put(key, value);
+ }
+
+ public final boolean hasKey(String key)
+ {
+ if (key == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.KeyIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return this.hasAvList() && this.avList.containsKey(key);
+ }
+
+ public final void removeKey(String key)
+ {
+ if (key == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.KeyIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (this.hasKey(key))
+ this.avList.remove(key);
+ }
+
+ public AVList copy()
+ {
+ AVListImpl clone = new AVListImpl();
+
+ clone.createAvList();
+ clone.avList.putAll(this.avList);
+
+ return clone;
+ }
+
+ public void addPropertyChangeListener(String propertyName, java.beans.PropertyChangeListener listener)
+ {
+ if (propertyName == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PropertyNameIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (listener == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.ListenerIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ this.changeSupport.addPropertyChangeListener(propertyName, listener);
+ }
+
+ public void removePropertyChangeListener(String propertyName, java.beans.PropertyChangeListener listener)
+ {
+ if (propertyName == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PropertyNameIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (listener == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.ListenerIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ this.changeSupport.removePropertyChangeListener(propertyName, listener);
+ }
+
+ public void addPropertyChangeListener(java.beans.PropertyChangeListener listener)
+ {
+ if (listener == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.ListenerIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ this.changeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(java.beans.PropertyChangeListener listener)
+ {
+ if (listener == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.ListenerIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ this.changeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void firePropertyChange(java.beans.PropertyChangeEvent propertyChangeEvent)
+ {
+ if (propertyChangeEvent == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PropertyChangeEventIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ this.changeSupport.firePropertyChange(propertyChangeEvent);
+ }
+
+ public void firePropertyChange(String propertyName, Object oldValue, Object newValue)
+ {
+ if (propertyName == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PropertyNameIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ this.changeSupport.firePropertyChange(propertyName, oldValue, newValue);
+ }
+
+ /**
+ * The property change listener for this instance.
+ * Recieves property change notifications that this instance has registered with other proprty change notifiers.
+ * @param propertyChangeEvent the event
+ * @throws IllegalArgumentException if propertyChangeEvent
is null
+ */
+ public void propertyChange(java.beans.PropertyChangeEvent propertyChangeEvent)
+ {
+ if (propertyChangeEvent == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PropertyChangeEventIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ // Notify all *my* listeners of the change that I caught
+ this.changeSupport.firePropertyChange(propertyChangeEvent);
+ }
+}
diff --git a/gov/nasa/worldwind/AbsentResourceList.java b/gov/nasa/worldwind/AbsentResourceList.java
new file mode 100644
index 0000000..c7cb4d8
--- /dev/null
+++ b/gov/nasa/worldwind/AbsentResourceList.java
@@ -0,0 +1,84 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author tag
+ * @version $Id$
+ */
+public class AbsentResourceList
+{
+ // Absent resources: A resource is deemed absent if a specified maximum number of attempts have been made to retrieve it.
+ // Retrieval attempts are governed by a minimum time interval between successive attempts. If an attempt is made
+ // within this interval, the resource is still deemed to be absent until the interval expires.
+ private static final int DEFAULT_MAX_ABSENT_RESOURCE_TRIES = 2;
+ private static final int DEFAULT_MIN_ABSENT_RESOURCE_CHECK_INTERVAL = 10000;
+
+ private int maxTries = DEFAULT_MAX_ABSENT_RESOURCE_TRIES;
+ private int minCheckInterval = DEFAULT_MIN_ABSENT_RESOURCE_CHECK_INTERVAL;
+
+ private static class AbsentResoureEntry
+ {
+ long timeOfLastMark; // meant to be the time of the most recent attempt to find the resource
+ int numTries;
+ }
+
+ private final java.util.concurrent.ConcurrentHashMap possiblyAbsent =
+ new java.util.concurrent.ConcurrentHashMap();
+
+ private java.util.SortedSet definitelyAbsent = java.util.Collections.synchronizedSortedSet(
+ new java.util.TreeSet());
+
+ public AbsentResourceList()
+ {
+ }
+
+ public AbsentResourceList(int maxTries, int minCheckInterval)
+ {
+ this.maxTries = Math.max(maxTries, 1);
+ this.minCheckInterval = Math.max(minCheckInterval, 500);
+ }
+
+ public final void markResourceAbsent(long resourceID)
+ {
+ if (this.definitelyAbsent.contains(resourceID))
+ return;
+
+ AbsentResoureEntry entry = this.possiblyAbsent.get(resourceID);
+ if (entry == null)
+ this.possiblyAbsent.put(resourceID, entry = new AbsentResoureEntry());
+
+ ++entry.numTries;
+ entry.timeOfLastMark = System.currentTimeMillis();
+
+ if (entry.numTries >= this.maxTries)
+ {
+ this.definitelyAbsent.add(resourceID);
+ this.possiblyAbsent.remove(resourceID);
+ // entry can now be garbage collected
+ }
+ }
+
+ public final boolean isResourceAbsent(long resourceID)
+ {
+ if (this.definitelyAbsent.contains(resourceID))
+ return true;
+
+ AbsentResoureEntry entry = this.possiblyAbsent.get(resourceID);
+ //noinspection SimplifiableIfStatement
+ if (entry == null)
+ return false;
+
+ return (System.currentTimeMillis() - entry.timeOfLastMark) < this.minCheckInterval;
+ }
+
+ public final void unmarkResourceAbsent(long resourceID)
+ {
+ this.definitelyAbsent.remove(resourceID);
+ this.possiblyAbsent.remove(resourceID);
+ }
+}
diff --git a/gov/nasa/worldwind/AbstractFileCache.java b/gov/nasa/worldwind/AbstractFileCache.java
new file mode 100644
index 0000000..df1ad85
--- /dev/null
+++ b/gov/nasa/worldwind/AbstractFileCache.java
@@ -0,0 +1,500 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author tag
+ * @version $Id: AbstractFileCache.java 748 2007-02-03 19:22:54Z tgaskins $
+ */
+public class AbstractFileCache implements FileCache
+{
+ private final java.util.LinkedList cacheDirs = new java.util.LinkedList();
+ private java.io.File cacheWriteDir = null;
+
+ public AbstractFileCache()
+ {
+ }
+
+ protected void initialize(java.io.InputStream xmlConfigStream)
+ {
+ javax.xml.parsers.DocumentBuilderFactory docBuilderFactory =
+ javax.xml.parsers.DocumentBuilderFactory.newInstance();
+
+ try
+ {
+ javax.xml.parsers.DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
+ org.w3c.dom.Document doc = docBuilder.parse(xmlConfigStream);
+
+ // The order of the following two calls is important, because building the writable location may entail
+ // creating a location that's included in the specified read locations.
+ this.buildWritePaths(doc);
+ this.buildReadPaths(doc);
+
+ if (this.cacheWriteDir == null)
+ {
+ String message = WorldWind.retrieveErrMsg("FileCache.NoFileCacheWriteLocation");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ }
+
+ if (this.cacheDirs.size() == 0)
+ {
+ // This should not happen because the writable cache is added to the read list, but check nonetheless
+ String message = WorldWind.retrieveErrMsg("FileCache.NoFileCacheReadLocations");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+ }
+ catch (javax.xml.parsers.ParserConfigurationException e)
+ {
+ e.printStackTrace();
+ }
+ catch (org.xml.sax.SAXException e)
+ {
+ e.printStackTrace();
+ }
+ catch (java.io.IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void addCacheLocation(String newPath)
+ {
+ this.addCacheLocation(this.cacheDirs.size(), newPath);
+ }
+
+ public void addCacheLocation(int index, String newPath)
+ {
+ if (newPath == null || newPath.length() == 0)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.FileCachePathIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (index < 0)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.invalidIndex");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (index > 0 && index > this.cacheDirs.size())
+ index = this.cacheDirs.size();
+
+ java.io.File newFile = new java.io.File(newPath);
+
+ if (this.cacheDirs.contains(newFile))
+ this.cacheDirs.remove(newFile);
+
+ this.cacheDirs.add(index, newFile);
+ }
+
+ public void removeCacheLocation(String newPath)
+ {
+ if (newPath == null || newPath.length() == 0)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.FileCachePathIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ // Just warn and return.
+ return;
+ }
+
+ java.io.File newFile = new java.io.File(newPath);
+
+ if (newFile.equals(this.cacheWriteDir))
+ {
+ String message = WorldWind.retrieveErrMsg("FileCache.CannotRemoveWriteLocationFromSearchList");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.cacheDirs.remove(new java.io.File(newPath));
+ }
+
+ public java.util.List getCacheLocations()
+ {
+ // Return a copy.
+ return new java.util.LinkedList(this.cacheDirs);
+ }
+
+ public java.io.File getWriteLocation()
+ {
+ return this.cacheWriteDir;
+ }
+
+ private void buildReadPaths(org.w3c.dom.Node dataFileCacheNode)
+ {
+ javax.xml.xpath.XPathFactory pathFactory = javax.xml.xpath.XPathFactory.newInstance();
+ javax.xml.xpath.XPath pathFinder = pathFactory.newXPath();
+
+ try
+ {
+ org.w3c.dom.NodeList locationNodes = (org.w3c.dom.NodeList) pathFinder.evaluate(
+ "/dataFileCache/readLocations/location",
+ dataFileCacheNode.getFirstChild(),
+ javax.xml.xpath.XPathConstants.NODESET);
+ for (int i = 0; i < locationNodes.getLength(); i++)
+ {
+ org.w3c.dom.Node location = locationNodes.item(i);
+ String prop = pathFinder.evaluate("@property", location);
+ String wwDir = pathFinder.evaluate("@wwDir", location);
+ String append = pathFinder.evaluate("@append", location);
+
+ String path = buildLocationPath(prop, append, wwDir);
+ if (path == null)
+ {
+ String message = WorldWind.retrieveErrMsg("FileCache.CacheLocationInvalid");
+ message += prop != null ? prop : WorldWind.retrieveErrMsg("generic.unknown");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ continue;
+ }
+
+ // Even paths that don't exist or are otherwise problematic are added to the list because they may
+ // become readable during the session. E.g., removable media. So add them to the search list.
+
+ java.io.File pathFile = new java.io.File(path);
+ if (pathFile.exists() && !pathFile.isDirectory())
+ {
+ String message = WorldWind.retrieveErrMsg("FileCache.CacheLocationIsFile");
+ message += pathFile.getPath();
+ WorldWind.logger().log(java.util.logging.Level.FINER, message);
+ }
+
+ if (!this.cacheDirs.contains(pathFile)) // filter out duplicates
+ {
+ this.cacheDirs.add(pathFile);
+ }
+ }
+ }
+ catch (javax.xml.xpath.XPathExpressionException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private void buildWritePaths(org.w3c.dom.Node dataFileCacheNode)
+ {
+ javax.xml.xpath.XPathFactory pathFactory = javax.xml.xpath.XPathFactory.newInstance();
+ javax.xml.xpath.XPath pathFinder = pathFactory.newXPath();
+
+ try
+ {
+ org.w3c.dom.NodeList locationNodes = (org.w3c.dom.NodeList) pathFinder.evaluate(
+ "/dataFileCache/writeLocations/location",
+ dataFileCacheNode.getFirstChild(),
+ javax.xml.xpath.XPathConstants.NODESET);
+ for (int i = 0; i < locationNodes.getLength(); i++)
+ {
+ org.w3c.dom.Node location = locationNodes.item(i);
+ String prop = pathFinder.evaluate("@property", location);
+ String wwDir = pathFinder.evaluate("@wwDir", location);
+ String append = pathFinder.evaluate("@append", location);
+ String create = pathFinder.evaluate("@create", location);
+
+ String path = buildLocationPath(prop, append, wwDir);
+ if (path == null)
+ {
+ String message = WorldWind.retrieveErrMsg("FileCache.CacheLocationInvalid");
+ message += prop != null ? prop : WorldWind.retrieveErrMsg("generic.unknown");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ continue;
+ }
+
+ java.io.File pathFile = new java.io.File(path);
+ if (!pathFile.exists() && create != null && (create.contains("t") || create.contains("T")))
+ {
+ pathFile.mkdirs();
+ }
+
+ if (pathFile.isDirectory() && pathFile.canWrite() && pathFile.canRead())
+ {
+ this.cacheWriteDir = pathFile;
+ this.cacheDirs.addFirst(pathFile); // writable location is always first in search path
+ break; // only need one
+ }
+ }
+ }
+ catch (javax.xml.xpath.XPathExpressionException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private static String buildLocationPath(String property, String append, String wwDir)
+ {
+ String path = propertyToPath(property);
+
+ if (append != null && append.length() != 0)
+ path = appendPathPart(path, append.trim());
+
+ if (wwDir != null && wwDir.length() != 0)
+ path = appendPathPart(path, wwDir.trim());
+
+ return path;
+ }
+
+ private static String appendPathPart(String firstPart, String secondPart)
+ {
+ if (secondPart == null || secondPart.length() == 0)
+ return firstPart;
+ if (firstPart == null || secondPart.length() == 0)
+ return secondPart;
+
+ firstPart = stripTrailingSeparator(firstPart);
+ secondPart = stripLeadingSeparator(secondPart);
+
+ return firstPart + System.getProperty("file.separator") + secondPart;
+ }
+
+ private static String stripTrailingSeparator(String s)
+ {
+ if (s.endsWith("/") || s.endsWith("\\"))
+ return s.substring(0, s.length() - 1);
+ else
+ return s;
+ }
+
+ private static String stripLeadingSeparator(String s)
+ {
+ if (s.startsWith("/") || s.startsWith("\\"))
+ return s.substring(1, s.length());
+ else
+ return s;
+ }
+
+ private static String propertyToPath(String propName)
+ {
+ if (propName == null || propName.length() == 0)
+ return null;
+
+ String prop = System.getProperty(propName);
+ if (prop != null)
+ return prop;
+
+ if (propName.equalsIgnoreCase("gov.nasa.worldwind.platform.alluser.cache"))
+ return determineAllUserCacheDir();
+
+ if (propName.equalsIgnoreCase("gov.nasa.worldwind.platform.user.cache"))
+ return determineSingleUserCacheDir();
+
+ return null;
+ }
+
+ private static String determineAllUserCacheDir()
+ {
+ if (gov.nasa.worldwind.Configuration.isMacOS())
+ {
+ return "/Library/Caches";
+ }
+ else if (gov.nasa.worldwind.Configuration.isWindowsOS())
+ {
+ String path = System.getenv("ALLUSERSPROFILE");
+ if (path == null)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.AllUsersWindowsProfileNotKnown");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+ return path + "\\Application Data";
+ }
+ else if (gov.nasa.worldwind.Configuration.isLinuxOS() || gov.nasa.worldwind.Configuration.isUnixOS() || gov.nasa
+ .worldwind.Configuration
+ .isSolarisOS())
+ {
+ return "/var/cache/";
+ }
+ else
+ {
+ String message = WorldWind.retrieveErrMsg("generic.UnknownOperatingSystem");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+ }
+
+ private static String determineSingleUserCacheDir()
+ {
+ String home = getUserHomeDir();
+ if (home == null)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.UsersHomeDirectoryNotKnown");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ String path = null;
+
+ if (gov.nasa.worldwind.Configuration.isMacOS())
+ {
+ path = "/Library/Caches";
+ }
+ else if (gov.nasa.worldwind.Configuration.isWindowsOS())
+ {
+ path = System.getenv("USERPROFILE");
+ if (path == null)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.UsersWindowsProfileNotKnown");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+ path += "\\Application Data";
+ }
+ else if (gov.nasa.worldwind.Configuration.isLinuxOS() || gov.nasa.worldwind.Configuration.isUnixOS() || gov.nasa
+ .worldwind.Configuration
+ .isSolarisOS())
+ {
+ path = "/var/cache/";
+ }
+ else
+ {
+ String message = WorldWind.retrieveErrMsg("generic.UnknownOperatingSystem");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ }
+
+ if (path == null)
+ return null;
+
+ return home + path;
+ }
+
+ private static String getUserHomeDir()
+ {
+ return System.getProperty("user.home");
+ }
+
+ public boolean contains(String fileName)
+ {
+ if (fileName == null)
+ return false;
+
+ for (java.io.File cacheDir : this.cacheDirs)
+ {
+ java.io.File file;
+ if (fileName.startsWith(cacheDir.getAbsolutePath()))
+ file = new java.io.File(fileName);
+ else
+ file = this.cachePathForFile(cacheDir, fileName);
+
+ if (file.exists())
+ return true;
+ }
+
+ return false;
+ }
+
+ private java.io.File cachePathForFile(java.io.File file, String fileName)
+ {
+ return new java.io.File(file.getAbsolutePath() + "/" + fileName);
+ }
+
+ private String makeFullPath(java.io.File dir, String fileName)
+ {
+ return dir.getAbsolutePath() + "/" + fileName;
+ }
+
+ /**
+ * @param fileName the name to give the newly created file
+ * @return a handle to the newly created file if it could be created and added to the cache, otherwise null
+ * @throws IllegalArgumentException if fileName
is null
+ */
+ public java.io.File newFile(String fileName)
+ {
+ if (fileName == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.FilePathIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (this.cacheWriteDir != null)
+ {
+ String fullPath = this.makeFullPath(this.cacheWriteDir, fileName);
+ java.io.File file = new java.io.File(fullPath);
+ if (file.getParentFile().exists())
+ return file;
+ else if (file.getParentFile().mkdirs())
+ return file;
+ else
+ return null;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param fileName the name of the file to find
+ * @param checkClassPath if true
, the class path is first searched for the file, otherwise the class
+ * path is not searched unless it's one of the explicit paths in the cache search directories
+ * @return a handle to the requested file if it exists in the cache, otherwise null
+ * @throws IllegalArgumentException if fileName
is null
+ */
+ public java.net.URL findFile(String fileName, boolean checkClassPath)
+ {
+ if (fileName == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.FilePathIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (checkClassPath)
+ {
+ java.net.URL url = this.getClass().getClassLoader().getResource(fileName);
+ if (url != null)
+ return url;
+ }
+
+ for (java.io.File dir : this.cacheDirs)
+ {
+ if (!dir.exists())
+ continue;
+
+ java.io.File file = new java.io.File(this.makeFullPath(dir, fileName));
+ if (file.exists())
+ {
+ try
+ {
+ return file.toURI().toURL();
+ }
+ catch (java.net.MalformedURLException e)
+ {
+ String message = WorldWind.retrieveErrMsg("FileCache.ExceptionCreatingURLForFile");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message + file.getPath());
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param url the "file:" URL of the file to remove from the cache
+ * @throws IllegalArgumentException if url
is null
+ */
+ public void removeFile(java.net.URL url)
+ {
+ if (url == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.URLIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ try
+ {
+ java.io.File file = new java.io.File(url.toURI());
+
+ if (file.exists())
+ file.delete();
+ }
+ catch (java.net.URISyntaxException e)
+ {
+ String message = WorldWind.retrieveErrMsg("FileCache.ExceptionRemovingFile");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message + url);
+ }
+ }
+}
\ No newline at end of file
diff --git a/gov/nasa/worldwind/AbstractView.java b/gov/nasa/worldwind/AbstractView.java
new file mode 100644
index 0000000..7de4ce0
--- /dev/null
+++ b/gov/nasa/worldwind/AbstractView.java
@@ -0,0 +1,391 @@
+/*
+Copyright (C) 2001, 2006 United States Government as represented by
+the Administrator of the National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+import gov.nasa.worldwind.geom.Point;
+
+import javax.media.opengl.*;
+import javax.media.opengl.glu.*;
+import java.awt.*;
+import java.util.logging.Level;
+
+/**
+ * @author Paul Collins
+ * @version $Id: AbstractView.java 1794 2007-05-08 22:07:21Z dcollins $
+ */
+public abstract class AbstractView extends WWObjectImpl implements View
+{
+ private static final Double DefaultFov;
+
+ static
+ {
+ DefaultFov = Configuration.getDoubleValue(AVKey.FOV, 45d);
+ }
+
+ // Current OpenGL viewing state.
+ private Matrix4 modelView;
+ private Matrix4 projection;
+ private Rectangle viewport;
+ private Angle fieldOfView = Angle.fromDegrees(DefaultFov);
+ // Current DrawContext state.
+ private Globe globe = null;
+ private double verticalExaggeration = -1;
+ // Cached viewing attribute computations.
+ private Point eye = null;
+ private Point up = null;
+ private Point forward = null;
+ private Frustum frustumInModelCoords = null;
+ private double pixelSizeScale = -1;
+ private double horizonDistance = -1;
+ // Temporary state.
+ private static final int[] matrixMode = new int[1];
+ private static final int[] viewportArray = new int[4];
+
+ public void apply(DrawContext dc)
+ {
+ validateDrawContext(dc);
+ this.globe = dc.getGlobe();
+ this.verticalExaggeration = dc.getVerticalExaggeration();
+
+ // Get the current OpenGL viewport state.
+ dc.getGL().glGetIntegerv(GL.GL_VIEWPORT, viewportArray, 0);
+ this.viewport = new Rectangle(viewportArray[0], viewportArray[1], viewportArray[2], viewportArray[3]);
+
+ this.clearCachedAttributes();
+ this.doApply(dc);
+ }
+
+ protected abstract void doApply(DrawContext dc);
+
+ private void clearCachedAttributes()
+ {
+ this.eye = null;
+ this.up = null;
+ this.forward = null;
+ this.frustumInModelCoords = null;
+ this.pixelSizeScale = -1;
+ this.horizonDistance = -1;
+ }
+
+ protected void applyMatrixState(DrawContext dc, Matrix4 modelView, Matrix4 projection)
+ {
+ validateDrawContext(dc);
+ if (modelView == null)
+ {
+ String message = WorldWind.retrieveErrMsg("AbstractView.ModelViewIsNull");
+ WorldWind.logger().log(Level.FINE, message);
+ }
+ if (projection == null)
+ {
+ String message = WorldWind.retrieveErrMsg("AbstractView.ProjectionIsNull");
+ WorldWind.logger().log(Level.FINE, message);
+ }
+
+ GL gl = dc.getGL();
+
+ // Store the current matrix-mode state.
+ gl.glGetIntegerv(GL.GL_MATRIX_MODE, matrixMode, 0);
+ int newMatrixMode = matrixMode[0];
+
+ // Apply the model-view matrix to the current OpenGL context held by 'dc'.
+ if (newMatrixMode != GL.GL_MODELVIEW)
+ {
+ newMatrixMode = GL.GL_MODELVIEW;
+ gl.glMatrixMode(newMatrixMode);
+ }
+ if (modelView != null)
+ gl.glLoadMatrixd(modelView.getEntries(), 0);
+ else
+ gl.glLoadIdentity();
+
+ // Apply the projection matrix to the current OpenGL context held by 'dc'.
+ newMatrixMode = GL.GL_PROJECTION;
+ gl.glMatrixMode(newMatrixMode);
+ if (projection != null)
+ gl.glLoadMatrixd(projection.getEntries(), 0);
+ else
+ gl.glLoadIdentity();
+
+ // Restore matrix-mode state.
+ if (newMatrixMode != matrixMode[0])
+ gl.glMatrixMode(matrixMode[0]);
+
+ this.modelView = modelView;
+ this.projection = projection;
+ }
+
+ protected void validateDrawContext(DrawContext dc)
+ {
+ if (dc == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (dc.getGL() == null)
+ {
+ String message = WorldWind.retrieveErrMsg("AbstractView.DrawingContextGLIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+ }
+
+ public Matrix4 getModelViewMatrix()
+ {
+ return this.modelView;
+ }
+
+ public Matrix4 getProjectionMatrix()
+ {
+ return this.projection;
+ }
+
+ public java.awt.Rectangle getViewport()
+ {
+ return this.viewport;
+ }
+
+ public Frustum getFrustumInModelCoordinates()
+ {
+ if (this.frustumInModelCoords == null)
+ {
+ // Compute the current model-view coordinate frustum.
+ Frustum frust = this.getFrustum();
+ if (frust != null && this.modelView != null)
+ this.frustumInModelCoords = frust.getInverseTransformed(this.modelView);
+ }
+ return this.frustumInModelCoords;
+ }
+
+ public Angle getFieldOfView()
+ {
+ return this.fieldOfView;
+ }
+
+ public void setFieldOfView(Angle newFov)
+ {
+ if (newFov == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ this.fieldOfView = newFov;
+ }
+
+ public void pushReferenceCenter(DrawContext dc, Point referenceCenter)
+ {
+ validateDrawContext(dc);
+ if (referenceCenter == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ Matrix4 newModelView;
+ if (this.modelView != null)
+ {
+ newModelView = new Matrix4(this.modelView.getEntries());
+ Matrix4 reference = new Matrix4();
+ reference.translate(referenceCenter);
+ newModelView.multiply(reference);
+ }
+ else
+ {
+ newModelView = new Matrix4();
+ }
+
+ GL gl = dc.getGL();
+ // Store the current matrix-mode state.
+ gl.glGetIntegerv(GL.GL_MATRIX_MODE, matrixMode, 0);
+ // Push and load a new model-view matrix to the current OpenGL context held by 'dc'.
+ if (matrixMode[0] != GL.GL_MODELVIEW)
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPushMatrix();
+ gl.glLoadMatrixd(newModelView.getEntries(), 0);
+ // Restore matrix-mode state.
+ if (matrixMode[0] != GL.GL_MODELVIEW)
+ gl.glMatrixMode(matrixMode[0]);
+ }
+
+ public void popReferenceCenter(DrawContext dc)
+ {
+ validateDrawContext(dc);
+ GL gl = dc.getGL();
+ // Store the current matrix-mode state.
+ gl.glGetIntegerv(GL.GL_MATRIX_MODE, matrixMode, 0);
+ // Pop a model-view matrix off the current OpenGL context held by 'dc'.
+ if (matrixMode[0] != GL.GL_MODELVIEW)
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPopMatrix();
+ // Restore matrix-mode state.
+ if (matrixMode[0] != GL.GL_MODELVIEW)
+ gl.glMatrixMode(matrixMode[0]);
+ }
+
+ public Point getEyePoint()
+ {
+ if (this.eye == null)
+ {
+ Matrix modelViewInv;
+ if (this.modelView != null && (modelViewInv = this.modelView.getInverse()) != null)
+ this.eye = modelViewInv.transform(new Point(0, 0, 0, 1));
+ }
+ return this.eye;
+ }
+
+ public Point getUpVector()
+ {
+ if (this.up == null)
+ {
+ Matrix modelViewInv;
+ if (this.modelView != null && (modelViewInv = this.modelView.getInverse()) != null)
+ this.up = modelViewInv.transform(new Point(0, 1, 0, 0));
+ }
+ return this.up;
+ }
+
+ public Point getForwardVector()
+ {
+ if (this.forward == null)
+ {
+ Matrix modelViewInv;
+ if (this.modelView != null && (modelViewInv = this.modelView.getInverse()) != null)
+ this.forward = modelViewInv.transform(new Point(0, 0, -1, 0));
+ }
+ return this.forward;
+ }
+
+ // TODO: this should be expressed in OpenGL screen coordinates, not toolkit (e.g. AWT) coordinates
+ public Line computeRayFromScreenPoint(double x, double y)
+ {
+ if (this.viewport == null)
+ return null;
+ double yInv = this.viewport.height - y - 1; // TODO: should be computed by caller
+ Point a = this.unProject(new Point(x, yInv, 0, 0));
+ Point b = this.unProject(new Point(x, yInv, 1, 0));
+ if (a == null || b == null)
+ return null;
+ return new Line(a, b.subtract(a).normalize());
+ }
+
+ // TODO: rename?, remove?
+ public Position computePositionFromScreenPoint(double x, double y)
+ {
+ Line line = this.computeRayFromScreenPoint(x, y);
+ if (line == null)
+ return null;
+ if (this.globe == null)
+ return null;
+ return this.globe.getIntersectionPosition(line);
+ }
+
+ public double computePixelSizeAtDistance(double distance)
+ {
+ if (this.pixelSizeScale < 0)
+ {
+ // Compute the current coefficient for computing the size of a pixel.
+ if (this.fieldOfView != null && this.viewport.width > 0)
+ this.pixelSizeScale = 2 * fieldOfView.tanHalfAngle() / (double) this.viewport.width;
+ else if (this.viewport.width > 0)
+ this.pixelSizeScale = 1 / (double) this.viewport.width;
+ }
+ if (this.pixelSizeScale < 0)
+ return -1;
+ return this.pixelSizeScale * Math.abs(distance);
+ }
+
+ public double computeHorizonDistance()
+ {
+ if (this.horizonDistance < 0)
+ {
+ this.horizonDistance = this.computeHorizonDistance(this.globe, this.verticalExaggeration,
+ this.getEyePoint());
+ }
+ return this.horizonDistance;
+ }
+
+ protected double computeHorizonDistance(Globe globe, double verticalExaggeration, Point eyePoint)
+ {
+ if (globe == null || eyePoint == null)
+ return -1;
+
+ // Compute the current (approximate) distance from eye to globe horizon.
+ Position eyePosition = globe.computePositionFromPoint(eyePoint);
+ double elevation = verticalExaggeration
+ * globe.getElevation(eyePosition.getLatitude(), eyePosition.getLongitude());
+ Point surface = globe.computePointFromPosition(eyePosition.getLatitude(), eyePosition.getLongitude(),
+ elevation);
+ double altitude = eyePoint.length() - surface.length();
+ double radius = globe.getMaximumRadius();
+ return Math.sqrt(altitude * (2 * radius + altitude));
+ }
+
+ public Point project(Point modelPoint)
+ {
+ if (modelPoint == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ if (this.modelView == null || this.projection == null || this.viewport == null)
+ return null;
+ Point eyeCoord = this.modelView.transform(new Point(modelPoint.x(), modelPoint.y(), modelPoint.z(), 1));
+ Point clipCoord = this.projection.transform(eyeCoord);
+ if (clipCoord.w() == 0)
+ return null;
+ Point normDeviceCoord = new Point(clipCoord.x() / clipCoord.w(), clipCoord.y() / clipCoord.w(),
+ clipCoord.z() / clipCoord.w(), 0);
+ return new Point(
+ (normDeviceCoord.x() + 1) * (this.viewport.width / 2d) + this.viewport.x,
+ (normDeviceCoord.y() + 1) * (this.viewport.height / 2d) + this.viewport.y,
+ (normDeviceCoord.z() + 1) / 2d,
+ 0);
+ }
+
+ public Point unProject(Point windowPoint)
+ {
+ if (windowPoint == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (this.modelView == null || this.projection == null || this.viewport == null)
+ return null;
+ double[] projectionMatrix = this.projection.getEntries();
+ double[] modelViewMatrix = this.modelView.getEntries();
+ int[] viewport = new int[] {this.viewport.x, this.viewport.y, this.viewport.width, this.viewport.height};
+ double[] modelPoint = new double[3];
+ GLU glu = new GLU();
+ if (glu.gluUnProject(windowPoint.x(), windowPoint.y(), windowPoint.z(), modelViewMatrix, 0, projectionMatrix, 0,
+ viewport, 0, modelPoint, 0))
+ return new Point(modelPoint[0], modelPoint[1], modelPoint[2], 0d);
+ else
+ return null;
+
+ // TODO: uncomment this when Matrix4.getInverse() is fixed
+// if (projection == null || modelView == null || viewport == null)
+// return null;
+// Point ndCoord = new Point(
+// 2 * (windowPoint.x() - viewport.getX()) / (double) viewport.width - 1,
+// 2 * (windowPoint.y() - viewport.getY()) / (double) viewport.height - 1,
+// 2 * windowPoint.z() - 1,
+// 1);
+// Matrix m = new Matrix4(modelView.getEntries());
+// m.multiply(projection);
+// Point clipCoord = m.getInverse().transform(ndCoord);
+// if (clipCoord.w() == 0)
+// return null;
+// return new Point(clipCoord.x() / clipCoord.w(), clipCoord.y() / clipCoord.w(),
+// clipCoord.z() / clipCoord.w(), 0);
+ }
+}
diff --git a/gov/nasa/worldwind/BasicDataFileCache.java b/gov/nasa/worldwind/BasicDataFileCache.java
new file mode 100644
index 0000000..2f884c2
--- /dev/null
+++ b/gov/nasa/worldwind/BasicDataFileCache.java
@@ -0,0 +1,35 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: BasicDataFileCache.java 1792 2007-05-08 21:28:37Z tgaskins $
+ */
+public class BasicDataFileCache extends AbstractFileCache
+{
+ public BasicDataFileCache()
+ {
+ String cachePathName = Configuration.getStringValue(AVKey.DATA_FILE_CACHE_CONFIGURATION_FILE_NAME);
+ if (cachePathName == null)
+ {
+ String message = WorldWind.retrieveErrMsg("FileCache.NoConfiguration");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ java.io.InputStream is = this.getClass().getClassLoader().getResourceAsStream(cachePathName);
+ if (is == null)
+ {
+ String message = WorldWind.retrieveErrMsg("FileCache.ConfigurationNotFound");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ this.initialize(is);
+ }
+}
diff --git a/gov/nasa/worldwind/BasicElevationModel.java b/gov/nasa/worldwind/BasicElevationModel.java
new file mode 100644
index 0000000..9cbf993
--- /dev/null
+++ b/gov/nasa/worldwind/BasicElevationModel.java
@@ -0,0 +1,676 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+import java.net.*;
+import java.nio.*;
+import java.io.*;
+
+// Implementation notes, not for API doc:
+//
+// Implements an elevation model based on a quad tree of elevation tiles. Meant to be subclassed by very specific
+// classes, e.g. Earth/SRTM. A Descriptor passed in at construction gives the configuration parameters. Eventually
+// Descriptor will be replaced by an XML configuration document.
+//
+// A "tile" corresponds to one tile of the data set, which has a corresponding unique row/column address in the data
+// set. An inner class implements Tile. An inner class also implements TileKey, which is used to address the
+// corresponding Tile in the memory cache.
+
+// Clients of this class get elevations from it by first getting an Elevations object for a specific Sector, then
+// querying that object for the elevation at individual lat/lon positions. The Elevations object captures information
+// that is used to compute elevations. See in-line comments for a description.
+//
+// When an elevation tile is needed but is not in memory, a task is threaded off to find it. If it's in the file cache
+// then it's loaded by the task into the memory cache. If it's not in the file cache then a retrieval is initiated by
+// the task. The disk is never accessed during a call to getElevations(sector, resolution) because that method is
+// likely being called when a frame is being rendered. The details of all this are in-line below.
+
+/**
+ * This class represents a single tile in the data set and contains the information that needs to be cached.
+ *
+ * @author Tom Gaskins
+ * @version $Id: BasicElevationModel.java 1732 2007-05-05 07:47:37Z tgaskins $
+ */
+public class BasicElevationModel extends WWObjectImpl implements ElevationModel
+{
+ private boolean isEnabled = true;
+ private final LevelSet levels;
+ private final double minElevation;
+ private final double maxElevation;
+ private long numExpectedValues = 0;
+ private final Object fileLock = new Object();
+ private java.util.concurrent.ConcurrentHashMap levelZeroTiles =
+ new java.util.concurrent.ConcurrentHashMap();
+ private gov.nasa.worldwind.MemoryCache memoryCache = new gov.nasa.worldwind.BasicMemoryCache(8000000, 10000000);
+
+ private static final class Tile extends gov.nasa.worldwind.Tile implements Cacheable
+ {
+ private java.nio.ShortBuffer elevations; // the elevations themselves
+
+ private Tile(Sector sector, Level level, int row, int col)
+ {
+ super(sector, level, row, col);
+ }
+ }
+
+ /**
+ * @param levels
+ * @param minElevation
+ * @param maxElevation
+ * @throws IllegalArgumentException if levels
is null or invalid
+ */
+ public BasicElevationModel(LevelSet levels, double minElevation, double maxElevation)
+ {
+ if (levels == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LevelSetIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.levels = new LevelSet(levels); // the caller's levelSet may change internally, so we copy it.
+ this.minElevation = minElevation;
+ this.maxElevation = maxElevation;
+ }
+
+ public boolean isEnabled()
+ {
+ return this.isEnabled;
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ this.isEnabled = enabled;
+ }
+
+ public LevelSet getLevels()
+ {
+ return this.levels;
+ }
+
+ public final double getMaximumElevation()
+ {
+ return this.maxElevation;
+ }
+
+ public final double getMinimumElevation()
+ {
+ return this.minElevation;
+ }
+
+ public long getNumExpectedValuesPerTile()
+ {
+ return numExpectedValues;
+ }
+
+ public void setNumExpectedValuesPerTile(long numExpectedValues)
+ {
+ this.numExpectedValues = numExpectedValues;
+ }
+
+ // Create the tile corresponding to a specified key.
+ private Tile createTile(TileKey key)
+ {
+ Level level = this.levels.getLevel(key.getLevelNumber());
+
+ // Compute the tile's SW lat/lon based on its row/col in the level's data set.
+ Angle dLat = level.getTileDelta().getLatitude();
+ Angle dLon = level.getTileDelta().getLongitude();
+
+ Angle minLatitude = Tile.computeRowLatitude(key.getRow(), dLat);
+ Angle minLongitude = Tile.computeColumnLongitude(key.getColumn(), dLon);
+
+ Sector tileSector = new Sector(minLatitude, minLatitude.add(dLat), minLongitude, minLongitude.add(dLon));
+
+ return new Tile(tileSector, level, key.getRow(), key.getColumn());
+ }
+
+ // Thread off a task to determine whether the file is local or remote and then retrieve it either from the file
+ // cache or a remote server.
+ private void requestTile(TileKey key)
+ {
+ if (WorldWind.threadedTaskService().isFull())
+ return;
+
+ RequestTask request = new RequestTask(key, this);
+ WorldWind.threadedTaskService().addTask(request);
+ }
+
+ private static class RequestTask implements Runnable
+ {
+ private final BasicElevationModel elevationModel;
+ private final TileKey tileKey;
+
+ private RequestTask(TileKey tileKey, BasicElevationModel elevationModel)
+ {
+ this.elevationModel = elevationModel;
+ this.tileKey = tileKey;
+ }
+
+ public final void run()
+ {
+ // check to ensure load is still needed
+ if (this.elevationModel.areElevationsInMemory(this.tileKey))
+ return;
+
+ Tile tile = this.elevationModel.createTile(this.tileKey);
+ final java.net.URL url = WorldWind.dataFileCache().findFile(tile.getPath(), false);
+ if (url != null)
+ {
+ if (this.elevationModel.loadElevations(tile, url))
+ {
+ this.elevationModel.levels.unmarkResourceAbsent(tile);
+ this.elevationModel.firePropertyChange(AVKey.ELEVATION_MODEL, null, this);
+ return;
+ }
+ else
+ {
+ // Assume that something's wrong with the file and delete it.
+ gov.nasa.worldwind.WorldWind.dataFileCache().removeFile(url);
+ this.elevationModel.levels.markResourceAbsent(tile);
+ String message = WorldWind.retrieveErrMsg("generic.DeletedCorruptDataFile") + url;
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ }
+ }
+
+ this.elevationModel.downloadElevations(tile);
+ }
+
+ public final boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final RequestTask that = (RequestTask) o;
+
+ //noinspection RedundantIfStatement
+ if (this.tileKey != null ? !this.tileKey.equals(that.tileKey) : that.tileKey != null)
+ return false;
+
+ return true;
+ }
+
+ public final int hashCode()
+ {
+ return (this.tileKey != null ? this.tileKey.hashCode() : 0);
+ }
+
+ public final String toString()
+ {
+ return this.tileKey.toString();
+ }
+ }
+
+ // Reads a tile's elevations from the file cache and adds the tile to the memory cache.
+ private boolean loadElevations(Tile tile, java.net.URL url)
+ {
+ java.nio.ShortBuffer elevations = this.readElevations(url);
+ if (elevations == null)
+ return false;
+
+ if (this.numExpectedValues > 0 && elevations.capacity() != this.numExpectedValues)
+ return false; // corrupt file
+
+ tile.elevations = elevations;
+ this.addTileToCache(tile, elevations);
+
+ return true;
+ }
+
+ private void addTileToCache(Tile tile, java.nio.ShortBuffer elevations)
+ {
+ // Level 0 tiles are held in the model itself; other levels are placed in the memory cache.
+ if (tile.getLevelNumber() == 0)
+ this.levelZeroTiles.putIfAbsent(tile.getTileKey(), tile);
+ else
+ this.memoryCache.add(tile.getTileKey(), tile, elevations.limit() * 2);
+ }
+
+ private boolean areElevationsInMemory(TileKey key)
+ {
+ Tile tile = this.getTileFromMemory(key);
+ return (tile != null && tile.elevations != null);
+ }
+
+ private Tile getTileFromMemory(TileKey tileKey)
+ {
+ if (tileKey.getLevelNumber() == 0)
+ return this.levelZeroTiles.get(tileKey);
+ else
+ return (Tile) this.memoryCache.getObject(tileKey);
+ }
+
+ // Read elevations from the file cache. Don't be confused by the use of a URL here: it's used so that files can
+ // be read using System.getResource(URL), which will draw the data from a jar file in the classpath.
+ // TODO: Look into possibly moving the mapping to a URL into WWIO.
+ private java.nio.ShortBuffer readElevations(java.net.URL url)
+ {
+ try
+ {
+ java.nio.ByteBuffer buffer;
+ synchronized (this.fileLock)
+ {
+ buffer = gov.nasa.worldwind.WWIO.readURLContentToBuffer(url);
+ }
+ buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN); // TODO: byte order is format dependent
+ return buffer.asShortBuffer();
+ }
+ catch (java.io.IOException e)
+ {
+ String message = WorldWind.retrieveErrMsg("TiledElevationModel.ExceptionAttemptingToReadTextureFile");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message + url);
+ return null;
+ }
+ }
+
+ private void downloadElevations(final Tile tile)
+ {
+ if (WorldWind.retrievalService().isFull())
+ return;
+
+ java.net.URL url = null;
+ try
+ {
+ url = tile.getResourceURL();
+ }
+ catch (java.net.MalformedURLException e)
+ {
+ String message = WorldWind.retrieveErrMsg("TiledElevationModel.ExceptionCreatingElevationsUrl");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message + url, e);
+ return;
+ }
+
+ URLRetriever retriever = new HTTPRetriever(url, new DownloadPostProcessor(tile, this));
+ if (WorldWind.retrievalService().contains(retriever))
+ return;
+
+ WorldWind.retrievalService().runRetriever(retriever, 0d);
+ }
+
+ /**
+ * @param dc
+ * @param sector
+ * @param density
+ * @return
+ * @throws IllegalArgumentException if dc
is null, sector
is null or density is
+ * negative
+ */
+ public final int getTargetResolution(DrawContext dc, Sector sector, int density)
+ {
+ if (!this.isEnabled)
+ return 0;
+
+ if (dc == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (sector == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.SectorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (density < 0)
+ {
+ String msg = WorldWind.retrieveErrMsg("BasicElevationModel.DensityBelowZero");
+ WorldWind.logger().log(java.util.logging.Level.FINEST, msg);
+ }
+
+ LatLon c = this.levels.getSector().getCentroid();
+ double radius = dc.getGlobe().getRadiusAt(c.getLatitude(), c.getLongitude());
+ double sectorWidth = sector.getDeltaLatRadians() * radius;
+ double targetSize = 0.8 * sectorWidth / (density); // TODO: make scale of density configurable
+
+ for (Level level : this.levels.getLevels())
+ {
+ if (level.getTexelSize(radius) < targetSize)
+ {
+ return level.getLevelNumber();
+ }
+ }
+
+ return this.levels.getNumLevels(); // finest resolution available
+ }
+
+ private static class DownloadPostProcessor implements RetrievalPostProcessor
+ {
+ private Tile tile;
+ private BasicElevationModel elevationModel;
+
+ public DownloadPostProcessor(Tile tile, BasicElevationModel elevationModel)
+ {
+ // don't validate - constructor is only available to classes with private access.
+ this.tile = tile;
+ this.elevationModel = elevationModel;
+ }
+
+ /**
+ * @param retriever
+ * @return
+ * @throws IllegalArgumentException if retriever
is null
+ */
+ public ByteBuffer run(Retriever retriever)
+ {
+ if (retriever == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.RetrieverIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ try
+ {
+ if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL))
+ return null;
+
+ if (retriever instanceof HTTPRetriever)
+ {
+ HTTPRetriever htr = (HTTPRetriever) retriever;
+ if (htr.getResponseCode() != HttpURLConnection.HTTP_OK)
+ {
+ // Mark tile as missing so avoid excessive attempts
+ this.elevationModel.levels.markResourceAbsent(this.tile);
+ return null;
+ }
+ }
+
+ URLRetriever r = (URLRetriever) retriever;
+ ByteBuffer buffer = r.getBuffer();
+
+ final File outFile = WorldWind.dataFileCache().newFile(tile.getPath());
+ if (outFile == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.CantCreateCacheFile")
+ + this.tile.getPath();
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ return null;
+ }
+
+ if (outFile.exists())
+ return buffer;
+
+ if (buffer != null)
+ {
+ synchronized (elevationModel.fileLock)
+ {
+ WWIO.saveBuffer(buffer, outFile);
+ }
+ return buffer;
+ }
+ }
+ catch (java.io.IOException e)
+ {
+ String message = WorldWind.retrieveErrMsg("TiledElevationModel.ExceptionSavingRetrievedElevationFile");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message + tile.getPath(), e);
+ }
+ finally
+ {
+ this.elevationModel.firePropertyChange(AVKey.ELEVATION_MODEL, null, this);
+ }
+ return null;
+ }
+ }
+
+ private static class BasicElevations implements ElevationModel.Elevations
+ {
+ private final int resolution;
+ private final Sector sector;
+ private final BasicElevationModel elevationModel;
+ private java.util.Set tiles;
+
+ private BasicElevations(Sector sector, int resolution, BasicElevationModel elevationModel)
+ {
+ this.sector = sector;
+ this.resolution = resolution;
+ this.elevationModel = elevationModel;
+ }
+
+ public int getResolution()
+ {
+ return this.resolution;
+ }
+
+ public Sector getSector()
+ {
+ return this.sector;
+ }
+
+ public boolean hasElevations()
+ {
+ return this.tiles != null && this.tiles.size() > 0;
+ }
+
+ public double getElevation(double latRadians, double lonRadians)
+ {
+ if (this.tiles == null)
+ return 0;
+
+ try
+ {
+ // TODO: Tiles are sorted by level/row/column. Use that to find containing sector faster.
+ for (BasicElevationModel.Tile tile : this.tiles)
+ {
+ if (tile.getSector().containsRadians(latRadians, lonRadians))
+ return this.elevationModel.lookupElevation(latRadians, lonRadians, tile);
+ }
+
+ return 0;
+ }
+ catch (Exception e)
+ {
+ // Throwing an exception within what's likely to be the caller's geometry creation loop
+ // would be hard to recover from, and a reasonable response to the exception can be done here.
+ String message = WorldWind.retrieveErrMsg("BasicElevationModel.ExceptionComputingElevation");
+ message += "(" + latRadians + ", " + lonRadians + ")";
+ WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+
+ return 0;
+ }
+ }
+ }
+
+ /**
+ * @param latitude
+ * @param longitude
+ * @return
+ * @throws IllegalArgumentException if latitude
or longitude
is null
+ */
+ public final double getElevation(Angle latitude, Angle longitude)
+ {
+ if (!this.isEnabled())
+ return 0;
+
+ if (latitude == null || longitude == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ // TODO: Make level to draw elevations from configurable
+ final TileKey tileKey = new TileKey(latitude, longitude, this.levels.getLastLevel());
+ Tile tile = this.getTileFromMemory(tileKey);
+
+ if (tile == null)
+ {
+ int fallbackRow = tileKey.getRow();
+ int fallbackCol = tileKey.getColumn();
+ for (int fallbackLevelNum = tileKey.getLevelNumber() - 1; fallbackLevelNum >= 0; fallbackLevelNum--)
+ {
+ fallbackRow /= 2;
+ fallbackCol /= 2;
+ TileKey fallbackKey = new TileKey(fallbackLevelNum, fallbackRow, fallbackCol,
+ this.levels.getLevel(fallbackLevelNum).getCacheName());
+ tile = this.getTileFromMemory(fallbackKey);
+ if (tile != null)
+ break;
+ }
+ }
+
+ if (tile == null)
+ {
+ final TileKey zeroKey = new TileKey(latitude, longitude, this.levels.getFirstLevel());
+ this.requestTile(zeroKey);
+
+ return 0;
+ }
+
+ return this.lookupElevation(latitude.radians, longitude.radians, tile);
+ }
+
+ /**
+ * @param sector
+ * @param resolution
+ * @return
+ * @throws IllegalArgumentException if sector
is null
+ */
+ public final Elevations getElevations(Sector sector, int resolution)
+ {
+ if (sector == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.SectorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (!this.isEnabled())
+ return new BasicElevations(sector, Integer.MIN_VALUE, this);
+
+ // Collect all the elevation tiles intersecting the input sector. If a desired tile is not curently
+ // available, choose its next lowest resolution parent that is available.
+ final Level targetLevel = this.levels.getLevel(resolution);
+
+ final TileKey keyNW = new TileKey(sector.getMaxLatitude(), sector.getMinLongitude(), this.levels.getLevel(
+ resolution));
+ final TileKey keySE = new TileKey(sector.getMinLatitude(), sector.getMaxLongitude(), this.levels.getLevel(
+ resolution));
+
+ java.util.TreeSet tiles = new java.util.TreeSet();
+ java.util.ArrayList requested = new java.util.ArrayList();
+
+ boolean missingTargetTiles = false;
+ boolean missingLevelZeroTiles = false;
+ for (int row = keySE.getRow(); row <= keyNW.getRow(); row++)
+ {
+ for (int col = keyNW.getColumn(); col <= keySE.getColumn(); col++)
+ {
+ TileKey key = new TileKey(resolution, row, col, targetLevel.getCacheName());
+ Tile tile = this.getTileFromMemory(key);
+ if (tile != null)
+ {
+ tiles.add(tile);
+ continue;
+ }
+
+ missingTargetTiles = true;
+ this.requestTile(key);
+
+ // Determine the fallback to use. Simultaneously determine a fallback to request that is
+ // the next resolution higher than the fallback chosen, if any. This will progressively
+ // refine the display until the desired resolution tile arrives.
+ TileKey fallbackToRequest = null;
+ TileKey fallbackKey = null;
+
+ int fallbackRow = row;
+ int fallbackCol = col;
+ for (int fallbackLevelNum = key.getLevelNumber() - 1; fallbackLevelNum >= 0; fallbackLevelNum--)
+ {
+ fallbackRow /= 2;
+ fallbackCol /= 2;
+ fallbackKey = new TileKey(fallbackLevelNum, fallbackRow, fallbackCol, this.levels.getLevel(
+ fallbackLevelNum).getCacheName());
+ tile = this.getTileFromMemory(fallbackKey);
+ if (tile != null)
+ {
+ if (!tiles.contains(tile))
+ tiles.add(tile);
+ break;
+ }
+ else
+ {
+ if (fallbackLevelNum == 0)
+ missingLevelZeroTiles = true;
+ fallbackToRequest = fallbackKey; // keep track of lowest level to request
+ }
+ }
+
+ if (fallbackToRequest != null)
+ {
+ if (!requested.contains(fallbackKey))
+ {
+ this.requestTile(fallbackKey);
+ requested.add(fallbackKey); // keep track to avoid overhead of duplicte requests
+ }
+ }
+ }
+ }
+
+ BasicElevations elevations;
+
+ if (missingLevelZeroTiles || tiles.isEmpty())
+ {
+ // Integer.MIN_VALUE is a signal for no in-memory tile for a given region of the sector.
+ elevations = new BasicElevations(sector, Integer.MIN_VALUE, this);
+ }
+ else if (missingTargetTiles)
+ {
+ // Use the level of the the lowest resolution found as the resolution for this elevation set.
+ // The list of tiles is sorted first by level, so use the level of the list's first entry.
+ elevations = new BasicElevations(sector, tiles.first().getLevelNumber(), this);
+ }
+ else
+ {
+ elevations = new BasicElevations(sector, resolution, this);
+ }
+
+ elevations.tiles = tiles;
+
+ return elevations;
+ }
+
+ private double lookupElevation(final double latRadians, final double lonRadians, final Tile tile)
+ {
+ Sector sector = tile.getSector();
+ final int tileHeight = tile.getLevel().getTileHeight();
+ final int tileWidth = tile.getLevel().getTileWidth();
+ final double sectorDeltaLat = sector.getDeltaLat().radians;
+ final double sectorDeltaLon = sector.getDeltaLon().radians;
+ final double dLat = sector.getMaxLatitude().radians - latRadians;
+ final double dLon = lonRadians - sector.getMinLongitude().radians;
+ final double sLat = dLat / sectorDeltaLat;
+ final double sLon = dLon / sectorDeltaLon;
+
+ int j = (int) ((tileHeight - 1) * sLat);
+ int i = (int) ((tileWidth - 1) * sLon);
+ int k = j * tileWidth + i;
+
+ double eLeft = tile.elevations.get(k);
+ double eRight = i < (tileWidth - 1) ? tile.elevations.get(k + 1) : eLeft;
+
+ double dw = sectorDeltaLon / (tileWidth - 1);
+ double dh = sectorDeltaLat / (tileHeight - 1);
+ double ssLon = (dLon - i * dw) / dw;
+ double ssLat = (dLat - j * dh) / dh;
+
+ double eTop = eLeft + ssLon * (eRight - eLeft);
+
+ if (j < tileHeight - 1 && i < tileWidth - 1)
+ {
+ eLeft = tile.elevations.get(k + tileWidth);
+ eRight = tile.elevations.get(k + tileWidth + 1);
+ }
+
+ double eBot = eLeft + ssLon * (eRight - eLeft);
+ return eTop + ssLat * (eBot - eTop);
+ }
+}
diff --git a/gov/nasa/worldwind/BasicFrameController.java b/gov/nasa/worldwind/BasicFrameController.java
new file mode 100644
index 0000000..821c85e
--- /dev/null
+++ b/gov/nasa/worldwind/BasicFrameController.java
@@ -0,0 +1,324 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import javax.media.opengl.*;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: BasicFrameController.java 1561 2007-04-21 09:29:58Z tgaskins $
+ */
+public class BasicFrameController implements FrameController
+{
+ public void initializeFrame(DrawContext dc) // TODO:Recover matrix and attribute stacks in case of Exception
+ {
+ if (null == dc)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ javax.media.opengl.GL gl = dc.getGL();
+
+ gl.glPushAttrib(javax.media.opengl.GL.GL_VIEWPORT_BIT | javax.media.opengl.GL.GL_ENABLE_BIT
+ | javax.media.opengl.GL.GL_TRANSFORM_BIT);
+
+ gl.glMatrixMode(javax.media.opengl.GL.GL_MODELVIEW);
+ gl.glPushMatrix();
+ gl.glLoadIdentity();
+
+ gl.glMatrixMode(javax.media.opengl.GL.GL_PROJECTION);
+ gl.glPushMatrix();
+ gl.glLoadIdentity();
+
+ gl.glEnable(javax.media.opengl.GL.GL_DEPTH_TEST);
+ }
+
+ public void finalizeFrame(DrawContext dc)
+ {
+ if (null == dc)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ GL gl = dc.getGL();
+
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPopMatrix();
+
+ gl.glMatrixMode(GL.GL_PROJECTION);
+ gl.glPopMatrix();
+
+ gl.glPopAttrib();
+
+ gl.glFlush();
+
+// checkGLErrors(dc);
+ }
+
+ /**
+ * Called to check for openGL errors. This method includes a "round-trip" between the application and renderer,
+ * which is slow. Therefore, this method is excluded from the "normal" render pass. It is here as a matter of
+ * convenience to developers, and is not part of the API.
+ *
+ * @param dc the relevant DrawContext
+ */
+ @SuppressWarnings({"UNUSED_SYMBOL", "UnusedDeclaration"})
+ private void checkGLErrors(DrawContext dc)
+ {
+ GL gl = dc.getGL();
+ int err = gl.glGetError();
+ if (err != GL.GL_NO_ERROR)
+ {
+ String msg = dc.getGLU().gluErrorString(err);
+ msg += err;
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ }
+ }
+
+ public void drawFrame(DrawContext dc)
+ {
+ if (null == dc)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.clearFrame(dc);
+
+ if (dc.getView() == null || dc.getModel() == null || dc.getLayers() == null)
+ return;
+
+ try
+ {
+ dc.getView().apply(dc);
+ }
+ catch (Exception e)
+ {
+ String message = WorldWind.retrieveErrMsg("BasicFrameController.ExceptionWhileApplyingView");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+ return;
+ }
+
+ try
+ {
+ if (dc.getModel().getTessellator() != null)
+ {
+ SectorGeometryList sgl = dc.getModel().getTessellator().tessellate(dc);
+ dc.setSurfaceGeometry(sgl);
+ }
+
+ if (dc.getSurfaceGeometry() == null)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.NoSurfaceGeometry");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ // keep going because some layers, etc. may have meaning w/o surface geometry
+ }
+ }
+ catch (Exception e)
+ {
+ String message = WorldWind.retrieveErrMsg("BasicFrameController.ExceptionWhileTessellatingGlobe");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+ return;
+ }
+
+ gov.nasa.worldwind.LayerList layers = dc.getLayers();
+ java.util.Iterator iter = layers.iterator();
+ while (iter.hasNext())
+ {
+ Layer layer = null;
+ try
+ {
+ layer = iter.next();
+ if (layer != null)
+ layer.render(dc);
+ }
+ catch (Exception e)
+ {
+ String message = WorldWind.retrieveErrMsg("BasicFrameController.ExceptionWhileRenderingLayer");
+ message += (layer != null ? layer.getClass().getName() : WorldWind.retrieveErrMsg("term.unknown"));
+ WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+ // Don't abort the frame; continue on to the next layer.
+ }
+ }
+
+ while (dc.getOrderedRenderables().peek() != null)
+ {
+ dc.getOrderedRenderables().poll().render(dc);
+ }
+
+ // Diagnostic displays.
+ if (dc.getSurfaceGeometry() != null
+ && dc.getModel().isShowWireframeExterior()
+ || dc.getModel().isShowWireframeInterior()
+ || dc.getModel().isShowTessellationBoundingVolumes())
+ {
+ Model model = dc.getModel();
+
+ float[] previousColor = new float[4];
+ dc.getGL().glGetFloatv(GL.GL_CURRENT_COLOR, previousColor, 0);
+
+ for (SectorGeometry sg : dc.getSurfaceGeometry())
+ {
+ if (model.isShowWireframeInterior() || model.isShowWireframeExterior())
+ sg.renderWireframe(dc, model.isShowWireframeInterior(), model.isShowWireframeExterior());
+
+ if (model.isShowTessellationBoundingVolumes())
+ {
+ dc.getGL().glColor3d(1, 0, 0);
+ sg.renderBoundingVolume(dc);
+ }
+ }
+
+ dc.getGL().glColor4fv(previousColor, 0);
+ }
+ }
+
+ public void initializePicking(DrawContext dc)
+ {
+ if (null == dc)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ javax.media.opengl.GL gl = dc.getGL();
+
+ gl.glPushAttrib(javax.media.opengl.GL.GL_VIEWPORT_BIT | javax.media.opengl.GL.GL_ENABLE_BIT
+ | javax.media.opengl.GL.GL_TRANSFORM_BIT);
+
+ gl.glMatrixMode(javax.media.opengl.GL.GL_MODELVIEW);
+ gl.glPushMatrix();
+ gl.glLoadIdentity();
+
+ gl.glMatrixMode(javax.media.opengl.GL.GL_PROJECTION);
+ gl.glPushMatrix();
+ gl.glLoadIdentity();
+
+ gl.glEnable(javax.media.opengl.GL.GL_DEPTH_TEST);
+ }
+
+ public void pick(DrawContext dc, java.awt.Point pickPoint)
+ {
+ if (null == dc)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.clearFrame(dc);
+
+ if (dc.getView() == null || dc.getModel() == null || dc.getLayers() == null)
+ return;
+
+ try
+ {
+ dc.getView().apply(dc);
+ }
+ catch (Exception e)
+ {
+ String message = WorldWind.retrieveErrMsg("BasicFrameController.ExceptionWhileApplyingView");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+ return;
+ }
+
+ // Pick against the surface geometry that was used to draw the last frame.
+ if (dc.getSurfaceGeometry() != null && dc.getSurfaceGeometry().size() > 0)
+ {
+ dc.getSurfaceGeometry().pick(dc, pickPoint);
+ }
+
+ gov.nasa.worldwind.LayerList layers = dc.getLayers();
+ java.util.Iterator iter = layers.iterator();
+ while (iter.hasNext())
+ {
+ Layer layer = null;
+ try
+ {
+ layer = iter.next();
+ if (layer != null && layer.isPickEnabled())
+ layer.pick(dc, pickPoint);
+ }
+ catch (Exception e)
+ {
+ String message = WorldWind.retrieveErrMsg("BasicFrameController.ExceptionWhilePickingInLayer");
+ message += (layer != null ? layer.getClass().getName() : WorldWind.retrieveErrMsg("term.unknown"));
+ WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+ // Don't abort the frame; continue on to the next layer.
+ }
+ }
+
+ // Pick against the deferred/ordered renderables
+ while (dc.getOrderedRenderables().peek() != null)
+ {
+ dc.getOrderedRenderables().poll().pick(dc, pickPoint);
+ }
+
+ // let's make a last reading to find out which is a top (resultant) color
+ PickedObjectList pickedObjectsList = dc.getPickedObjects();
+ if (null != pickedObjectsList && (0 < pickedObjectsList.size())) // zz: garakl: put 1 here, if only one object
+ {
+ int[] viewport = new int[4];
+ java.nio.ByteBuffer pixel = com.sun.opengl.util.BufferUtil.newByteBuffer(3);
+ GL gl = dc.getGL();
+ gl.glGetIntegerv(javax.media.opengl.GL.GL_VIEWPORT, viewport, 0);
+ gl.glReadPixels(pickPoint.x, viewport[3] - pickPoint.y, 1, 1, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, pixel);
+
+ java.awt.Color topColor = new java.awt.Color(pixel.get(0) & 0xff, pixel.get(1) & 0xff,
+ pixel.get(2) & 0xff, 0);
+
+ if (null != topColor)
+ {
+ int colorCode = topColor.getRGB();
+ if (0 != colorCode)
+ { // let's find the picked object in the list and set "OnTop" flag
+ for (PickedObject po : pickedObjectsList)
+ {
+ if (null != po && po.getColorCode() == colorCode)
+ {
+ po.setOnTop();
+ break;
+ }
+ }
+ }
+ }
+ } // endf of top pixel reading
+ }
+
+ public void finalizePicking(DrawContext dc)
+ {
+ if (null == dc)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ GL gl = dc.getGL();
+
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPopMatrix();
+
+ gl.glMatrixMode(GL.GL_PROJECTION);
+ gl.glPopMatrix();
+
+ gl.glPopAttrib();
+ }
+
+ private void clearFrame(DrawContext dc)
+ {
+ java.awt.Color cc = dc.getClearColor();
+ dc.getGL().glClearColor(cc.getRed(), cc.getGreen(), cc.getBlue(), cc.getAlpha());
+ dc.getGL().glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
+ }
+}
diff --git a/gov/nasa/worldwind/BasicMemoryCache.java b/gov/nasa/worldwind/BasicMemoryCache.java
new file mode 100644
index 0000000..61d9657
--- /dev/null
+++ b/gov/nasa/worldwind/BasicMemoryCache.java
@@ -0,0 +1,411 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+//TODO: write class javadoc description. Remember to include info on: synchronisation, lowWater.
+
+/**
+ * @author Eric Dalgliesh
+ * @version $Id: BasicMemoryCache.java 1792 2007-05-08 21:28:37Z tgaskins $
+ */
+public final class BasicMemoryCache implements MemoryCache
+{
+ private static long FALLBACK_CACHE_SIZE = 60000000; // less than applet default max heap size
+
+ private static class CacheEntry implements Comparable
+ {
+ Object key;
+ Object clientObject;
+ private long lastUsed;
+ private long clientObjectSize;
+
+ CacheEntry(Object key, Object clientObject, long clientObjectSize)
+ {
+ this.key = key;
+ this.clientObject = clientObject;
+ this.lastUsed = System.nanoTime();
+ this.clientObjectSize = clientObjectSize;
+ }
+
+ /**
+ * @param that
+ * @return
+ * @throws IllegalArgumentException if that
is null
+ */
+ public int compareTo(CacheEntry that)
+ {
+ if (that == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.CacheEntryIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ return this.lastUsed < that.lastUsed ? -1 : this.lastUsed == that.lastUsed ? 0 : 1;
+ }
+
+ public String toString()
+ {
+ return key.toString() + " " + clientObject.toString() + " " + lastUsed + " " + clientObjectSize;
+ }
+ }
+
+ private java.util.concurrent.ConcurrentHashMap
javax.media.opengl.GLContext. May throw a
+ * NullPointerException
if glContext
is null.
+ *
+ * @param glContext the new javax.media.opengl.GLContext
+ * @throws NullPointerException if glContext is null
+ * @since 1.5
+ */
+ void setGLContext(javax.media.opengl.GLContext glContext);
+
+ /**
+ * Retrieves this DrawContext
s javax.media.opengl.GLContext. If this method returns null,
+ * then there are potentially no active GLContext
s and rendering should be aborted.
+ *
+ * @return this DrawContext
s javax.media.opengl.GLContext.
+ * @since 1.5
+ */
+ javax.media.opengl.GLContext getGLContext();
+
+ /**
+ * Retrieves the current javax.media.opengl.GL
. A GL
or GLU
is required for
+ * all graphical rendering in World Wind Raptor.
+ *
+ * @return the current GL
if available, null otherwise
+ * @since 1.5
+ */
+ javax.media.opengl.GL getGL();
+
+ /**
+ * Retrieves the current javax.media.opengl.glu.GLU
. A GLU
or GL
is required
+ * for all graphical rendering in World Wind Raptor.
+ *
+ * @return the current GLU
if available, null otherwise
+ * @since 1.5
+ */
+ javax.media.opengl.glu.GLU getGLU();
+
+ /**
+ * Retrieves the currentjavax.media.opengl.GLDrawable
. A GLDrawable
can be used to create
+ * a GLContext
, which can then be used for rendering.
+ *
+ * @return the current GLDrawable
, null if none available
+ * @since 1.5
+ */
+ javax.media.opengl.GLDrawable getGLDrawable();
+
+ /**
+ * Retrieves the drawable width of this DrawContext
.
+ *
+ * @return the drawable width of this DrawCOntext
+ * @since 1.5
+ */
+ int getDrawableWidth();
+
+ /**
+ * Retrieves the drawable height of this DrawContext
.
+ *
+ * @return the drawable height of this DrawCOntext
+ * @since 1.5
+ */
+ int getDrawableHeight();
+
+ /**
+ * Initializes this DrawContext
. This method should be called at the beginning of each frame to prepare
+ * the DrawContext
for the coming render pass.
+ *
+ * @param glContext the javax.media.opengl.GLContext
to use for this render pass
+ * @since 1.5
+ */
+ void initialize(javax.media.opengl.GLContext glContext);
+
+ /**
+ * Assigns a new View
. Some layers cannot function properly with a null View
. It is
+ * recommended that the View
is never set to null during a normal render pass.
+ *
+ * @param view the enw View
+ * @since 1.5
+ */
+ void setView(View view);
+
+ /**
+ * Retrieves the current View
, which may be null.
+ *
+ * @return the current View
, which may be null
+ * @since 1.5
+ */
+ View getView();
+
+ /**
+ * Assign a new Model
. Some layers cannot function properly with a null Model
. It is
+ * recommended that the Model
is never set to null during a normal render pass.
+ *
+ * @param model the new Model
+ * @since 1.5
+ */
+ void setModel(Model model);
+
+ /**
+ * Retrieves the current Model
, which may be null.
+ *
+ * @return the current Model
, which may be null
+ * @since 1.5
+ */
+ Model getModel();
+
+ /**
+ * Retrieves the current Globe
, which may be null.
+ *
+ * @return the current Globe
, which may be null
+ * @since 1.5
+ */
+ Globe getGlobe();
+
+ /**
+ * Retrieves a list containing all the current layers. No guarantee is made about the order of the layers.
+ *
+ * @return a LayerList
containing all the current layers
+ * @since 1.5
+ */
+ LayerList getLayers();
+
+ /**
+ * Retrieves a Sector
which is at least as large as the current visible sector. The value returned is
+ * the value passed to SetVisibleSector
. This method may return null.
+ *
+ * @return a Sector
at least the size of the curernt visible sector, null if unavailable
+ * @since 1.5
+ */
+ gov.nasa.worldwind.geom.Sector getVisibleSector();
+
+ /**
+ * Sets the visible Sector
. The new visible sector must completely encompass the Sector which is
+ * visible on the display.
+ *
+ * @param s the new visible Sector
+ * @since 1.5
+ */
+ void setVisibleSector(gov.nasa.worldwind.geom.Sector s);
+
+ /**
+ * Sets the vertical exaggeration. Vertical exaggeration affects the appearance of areas with varied elevation. A
+ * vertical exaggeration of zero creates a surface which exactly fits the shape of the underlying
+ * Globe
. A vertical exaggeration of 3 will create mountains and valleys which are three times as
+ * high/deep as they really are.
+ *
+ * @param verticalExaggeration the new vertical exaggeration.
+ * @since 1.5
+ */
+ void setVerticalExaggeration(double verticalExaggeration);
+
+ /**
+ * Retrieves the current vertical exaggeration. Vertical exaggeration affects the appearance of areas with varied
+ * elevation. A vertical exaggeration of zero creates a surface which exactly fits the shape of the underlying
+ * Globe
. A vertical exaggeration of 3 will create mountains and valleys which are three times as
+ * high/deep as they really are.
+ *
+ * @return the current vertical exaggeration
+ * @since 1.5
+ */
+ double getVerticalExaggeration();
+
+ // not used (12th January 2007)
+ final static String HIGH_PRIORITY = "gov.nasa.worldwind.DrawContext.HighPriority";
+ final static String LOW_PRIORITY = "gov.nasa.worldwind.DrawContext.LowPriority";
+
+ /**
+ * Retrieves a list of all the sectors rendered so far this frame.
+ *
+ * @return a SectorGeometryList
containing every SectorGeometry
rendered so far this
+ * render pass.
+ * @since 1.5
+ */
+ SectorGeometryList getSurfaceGeometry();
+
+ //
+// /**
+// * Sets the average render time per frame in milliseconds.
+// *
+// * @param timeMillis the new average time in milliseconds
+// * @since 1.5
+// */
+// void setAverageRenderTimeMillis(double timeMillis);
+//
+// /**
+// * Retrieves the current average render time for a frame. The average render time can be used to calculate the
+// * framerate.
+// *
+// * @return the current average render time for a frame
+// * @since 1.5
+// */
+// double getAverageRenderTimeMillis();
+
+ /**
+ * Returns the list of objects picked during the most recent pick traversal.
+ * @return the list of picked objects
+ */
+ gov.nasa.worldwind.PickedObjectList getPickedObjects();
+
+ /**
+ * Adds a collection of picked objects to the current picked-object list
+ * @param pickedObjects the objects to add
+ */
+ void addPickedObjects(PickedObjectList pickedObjects);
+
+ /**
+ * Adds a single insatnce of the picked object to the current picked-object list
+ * @param pickedObject the object to add
+ */
+ void addPickedObject(PickedObject pickedObject);
+
+ /**
+ * Returns a unique color to serve as a pick identifier during picking.
+ * @return a unique pick color
+ */
+ java.awt.Color getUniquePickColor();
+
+ java.awt.Color getClearColor();
+
+ /**
+ * Enables color picking mode
+ */
+ void enablePickingMode();
+
+ /**
+ * Returns true if the Picking mode is active, otherwise return false
+ * @return true for Picking mode, otherwise false
+ */
+ boolean isPickingMode();
+
+ /**
+ * Disables color picking mode
+ */
+ void disablePickingMode();
+
+ void addOrderedRenderable(OrderedRenderable orderedRenderable);
+
+ java.util.Queue getOrderedRenderables();
+
+ void drawUnitQuad();
+
+ void drawUnitQuad(TextureCoords texCoords);
+
+ int getNumTextureUnits();
+
+ void setNumTextureUnits(int numTextureUnits);
+
+ void setSurfaceGeometry(SectorGeometryList surfaceGeometry);
+
+ Point getPointOnGlobe(Angle latitude, Angle longitude);
+
+ SurfaceTileRenderer getSurfaceTileRenderer();
+}
diff --git a/gov/nasa/worldwind/DrawContextImpl.java b/gov/nasa/worldwind/DrawContextImpl.java
new file mode 100644
index 0000000..e76d7ef
--- /dev/null
+++ b/gov/nasa/worldwind/DrawContextImpl.java
@@ -0,0 +1,384 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+import javax.media.opengl.*;
+import java.util.*;
+
+import com.sun.opengl.util.texture.*;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: DrawContextImpl.java 1465 2007-04-14 01:05:37Z tgaskins $
+ */
+public class DrawContextImpl extends WWObjectImpl implements DrawContext
+{
+ private javax.media.opengl.GLContext glContext;
+ private javax.media.opengl.glu.GLU glu = new javax.media.opengl.glu.GLU();
+ private View view;
+ private Model model;
+ private Globe globe;
+ private double verticalExaggeration = 1d;
+ private gov.nasa.worldwind.geom.Sector visibleSector;
+ private SectorGeometryList surfaceGeometry;// = new SectorGeometryList();
+ private gov.nasa.worldwind.PickedObjectList pickedObjects = new gov.nasa.worldwind.PickedObjectList();
+ private int uniquePickNumber = 0;
+ private java.awt.Color clearColor = new java.awt.Color(0, 0, 0, 0);
+ private boolean isPickingMode = false;
+ private int numTextureUnits = -1;
+ private SurfaceTileRenderer surfaceTileRenderer = new SurfaceTileRenderer();
+
+ PriorityQueue orderedRenderables = new PriorityQueue(100,
+ new Comparator()
+ {
+ public int compare(OrderedRenderable orA, OrderedRenderable orB)
+ {
+ double eA = orA.getDistanceFromEye();
+ double eB = orB.getDistanceFromEye();
+
+ return eA > eB ? -1 : eA == eB ? 0 : 1;
+ }
+ });
+
+ public final javax.media.opengl.GL getGL()
+ {
+ return this.getGLContext().getGL();
+ }
+
+ public final javax.media.opengl.glu.GLU getGLU()
+ {
+ return this.glu;
+ }
+
+ public final javax.media.opengl.GLContext getGLContext()
+ {
+ return this.glContext;
+ }
+
+ public final int getDrawableHeight()
+ {
+ return this.getGLDrawable().getHeight();
+ }
+
+ public final int getDrawableWidth()
+ {
+ return this.getGLDrawable().getWidth();
+ }
+
+ public final javax.media.opengl.GLDrawable getGLDrawable()
+ {
+ return this.getGLContext().getGLDrawable();
+ }
+
+ public final void initialize(javax.media.opengl.GLContext glContext)
+ {
+ if (glContext == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.GLContextIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.glContext = glContext;
+
+ if(this.isPickingMode())
+ {
+ // do not clean rendered sectors for picking
+ }
+ else
+ {
+ this.visibleSector = null;
+ if (this.surfaceGeometry != null)
+ this.surfaceGeometry.clear();
+ this.surfaceGeometry = null;
+ }
+ this.pickedObjects.clear();
+ this.orderedRenderables.clear();
+ this.uniquePickNumber = 0;
+
+ if (this.numTextureUnits < 1)
+ this.numTextureUnits = queryMaxTextureUnits(glContext);
+ }
+
+ private static int queryMaxTextureUnits(GLContext glContext)
+ {
+ int[] mtu = new int[1];
+ glContext.getGL().glGetIntegerv(GL.GL_MAX_TEXTURE_UNITS, mtu, 0);
+ return mtu[0];
+ }
+
+ public final void setModel(gov.nasa.worldwind.Model model)
+ {
+ this.model = model;
+ if (this.model == null)
+ return;
+
+ Globe g = this.model.getGlobe();
+ if (g != null)
+ this.globe = g;
+ }
+
+ public final Model getModel()
+ {
+ return this.model;
+ }
+
+ public final LayerList getLayers()
+ {
+ return this.model.getLayers();
+ }
+
+ public final Sector getVisibleSector()
+ {
+ return this.visibleSector;
+ }
+
+ public final void setVisibleSector(Sector s)
+ {
+ // don't check for null - it is possible that no globe is active, no view is active, no sectors visible, etc.
+ this.visibleSector = s;
+ }
+
+ public void setSurfaceGeometry(SectorGeometryList surfaceGeometry)
+ {
+ this.surfaceGeometry = surfaceGeometry;
+ }
+
+ public SectorGeometryList getSurfaceGeometry()
+ {
+ return surfaceGeometry;
+ }
+
+ public final Globe getGlobe()
+ {
+ return this.globe != null ? this.globe : this.model.getGlobe();
+ }
+
+ public final void setView(gov.nasa.worldwind.View view)
+ {
+ this.view = view;
+ }
+
+ public final View getView()
+ {
+ return this.view;
+ }
+
+ public final void setGLContext(javax.media.opengl.GLContext glContext)
+ {
+ if (glContext == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.GLContextIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.glContext = glContext;
+ }
+
+ public final double getVerticalExaggeration()
+ {
+ return verticalExaggeration;
+ }
+
+ public final void setVerticalExaggeration(double verticalExaggeration)
+ {
+ this.verticalExaggeration = verticalExaggeration;
+ }
+
+ /**
+ * Add picked objects to the current list of picked objects.
+ * @param pickedObjects the list of picked objects to add
+ * @throws IllegalArgumentException if pickedObjects is null
+ */
+ public void addPickedObjects(gov.nasa.worldwind.PickedObjectList pickedObjects)
+ {
+ if (pickedObjects == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PickedObjectList");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (this.pickedObjects == null)
+ {
+ this.pickedObjects = pickedObjects;
+ return;
+ }
+
+ for (gov.nasa.worldwind.PickedObject po : pickedObjects)
+ {
+ this.pickedObjects.add(po);
+ }
+ }
+
+ /**
+ * Adds a single insatnce of the picked object to the current picked-object list
+ * @param pickedObject the object to add
+ * @throws IllegalArgumentException if picked Object is null
+ */
+ public void addPickedObject(PickedObject pickedObject)
+ {
+ if(null == pickedObject)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PickedObject");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if(null == this.pickedObjects)
+ this.pickedObjects = new gov.nasa.worldwind.PickedObjectList();
+
+ this.pickedObjects.add(pickedObject);
+ }
+
+ public gov.nasa.worldwind.PickedObjectList getPickedObjects()
+ {
+ return this.pickedObjects;
+ }
+
+ public java.awt.Color getUniquePickColor()
+ {
+ this.uniquePickNumber++;
+ int clearColorCode = this.getClearColor().getRGB();
+
+ if(clearColorCode == this.uniquePickNumber)
+ this.uniquePickNumber++;
+
+ if(this.uniquePickNumber >= 0x00FFFFFF)
+ {
+ this.uniquePickNumber = 1; // no black, no white
+ if(clearColorCode == this.uniquePickNumber)
+ this.uniquePickNumber++;
+ }
+
+ return new java.awt.Color(this.uniquePickNumber, true); // has alpha
+ }
+
+ public java.awt.Color getClearColor()
+ {
+ return this.clearColor;
+ }
+
+ /**
+ * Returns true if the Picking mode is active, otherwise return false
+ * @return true for Picking mode, otherwise false
+ */
+ public boolean isPickingMode()
+ {
+ return this.isPickingMode;
+ }
+
+ /**
+ * Enables color picking mode
+ */
+ public void enablePickingMode()
+ {
+ this.isPickingMode = true;
+ }
+
+ /**
+ * Disables color picking mode
+ */
+ public void disablePickingMode()
+ {
+ this.isPickingMode = false;
+ }
+
+ public void addOrderedRenderable(OrderedRenderable orderedRenderable)
+ {
+ if(null == orderedRenderable)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.OrderedRenderable");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ return; // benign event
+ }
+
+ this.orderedRenderables.add(orderedRenderable);
+ }
+
+ public java.util.Queue getOrderedRenderables()
+ {
+ return this.orderedRenderables;
+ }
+
+ public void drawUnitQuad()
+ {
+ GL gl = this.getGL();
+
+ gl.glBegin(GL.GL_QUADS); // TODO: use a vertex array or vertex buffer
+ gl.glVertex2d(0d, 0d);
+ gl.glVertex2d(1, 0d);
+ gl.glVertex2d(1, 1);
+ gl.glVertex2d(0d, 1);
+ gl.glEnd();
+ }
+
+ public void drawUnitQuad(TextureCoords texCoords)
+ {
+ GL gl = this.getGL();
+
+ gl.glBegin(GL.GL_QUADS); // TODO: use a vertex array or vertex buffer
+ gl.glTexCoord2d(texCoords.left(), texCoords.bottom());
+ gl.glVertex2d(0d, 0d);
+ gl.glTexCoord2d(texCoords.right(), texCoords.bottom());
+ gl.glVertex2d(1, 0d);
+ gl.glTexCoord2d(texCoords.right(), texCoords.top());
+ gl.glVertex2d(1, 1);
+ gl.glTexCoord2d(texCoords.left(), texCoords.top());
+ gl.glVertex2d(0d, 1);
+ gl.glEnd();
+ }
+
+ public int getNumTextureUnits()
+ {
+ return numTextureUnits;
+ }
+
+ public void setNumTextureUnits(int numTextureUnits)
+ {
+ // TODO: validate arg for >= 1
+ this.numTextureUnits = numTextureUnits;
+ }
+
+ public Point getPointOnGlobe(Angle latitude, Angle longitude)
+ {
+ if (latitude == null || longitude == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LatitudeOrLongitudeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (!this.getVisibleSector().contains(latitude, longitude))
+ return null;
+
+ SectorGeometryList sectorGeometry = this.getSurfaceGeometry();
+ if (sectorGeometry != null)
+ {
+ Point p = sectorGeometry.getSurfacePoint(latitude, longitude);
+ if (p != null)
+ return p;
+ }
+
+ return null;
+
+// Globe globe = this.getGlobe();
+// if (globe == null)
+// return null;
+//
+// double elevation = this.getVerticalExaggeration() * globe.getElevation(latitude, longitude);
+// return this.getGlobe().computeSurfacePoint(latitude, longitude, elevation);
+ }
+
+ public SurfaceTileRenderer getSurfaceTileRenderer()
+ {
+ return this.surfaceTileRenderer;
+ }
+}
diff --git a/gov/nasa/worldwind/ElevationModel.java b/gov/nasa/worldwind/ElevationModel.java
new file mode 100644
index 0000000..6f88de9
--- /dev/null
+++ b/gov/nasa/worldwind/ElevationModel.java
@@ -0,0 +1,126 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+/**
+ *
+ * Provides the elevations of all points on a {@link Globe} . Every Globe
has an elevation model
+ * implementing this interface.
+ *
+ * Elevations are organized by {@link Sector}. Elevation models return an {@link Elevations} object for requested
+ * sectors. This object is then used to query elevations at latitude/longitude positions within that sector.
+ *
+ * An ElevationModel
typically approximates elevations at multiple levels of spatial resolution. For any
+ * given viewing position the model determines an appropriate target resolution. That target resolution may not be
+ * immediately achievable, however, because the corresponding elevation data might not be locally available and must be
+ * retrieved from a remote location. When this is the case, the Elevations
object returned for a sector
+ * holds the resolution achievable with the data currently available. That resolution may not be the same as the target
+ * resolution. The target resolution and the actual resolution are made available in the interface so that users of this
+ * class may use the resolution values to compare previously computed elevation sectors with newly computed ones, and
+ * thereby enable effective caching of elevations computed for the sector.
+ *
+ *
+ * @author Tom Gaskins
+ * @version $Id: ElevationModel.java 1709 2007-05-03 23:39:21Z tgaskins $
+ */
+public interface ElevationModel extends WWObject
+{
+ boolean isEnabled();
+
+ void setEnabled(boolean enable);
+
+ /**
+ * Returns the maximum elevation contained in the elevevation model. This value is the height of the highest point
+ * on the globe.
+ *
+ * @return The maximum elevation of the model
+ */
+ double getMaximumElevation();
+
+ /**
+ * Returns the minimum elevation contained in the elevevation model. This value is the height of the lowest point on
+ * the globe. It may be negative, indicating a value below mean surface level. (Sea level in the case of Earth.)
+ *
+ * @return The minimum elevation of the model
+ */
+ double getMinimumElevation();
+
+ /**
+ * Computes and returns an {@link Elevations} object for the specified {@link Sector} and target resolution. If the
+ * target resolution can not currently be achieved, the best available elevations are returned.
+ *
+ * Implementing classes of ElevationModel
interpret resolution
in a class-specific way.
+ * See the descriptions of those classes to learn their use of this value. The elevations returned are in the form
+ * of an {@link Elevations} object. Specific elevations are returned by that object.
+ *
+ * @param sector the sector to return elevations for.
+ * @param resolution a value interpreted in a class-specific way by implementing classes.
+ * @return An object representing the elevations for the specified sector. Its resolution value will be either the
+ * specified resolution or the best available alternative.
+ */
+ Elevations getElevations(Sector sector, int resolution);
+
+ /**
+ * Returns the resolution appropriate to the given {@link Sector} and view parameters. The view parameters are read
+ * from the specified {@link DrawContext}. Implementing classes of ElevationModel
interpret
+ * resolution
in class-specific ways. See the descriptions of subclasses to learn their use of this
+ * value. This method is used to determine the resolution the model will use if all resources are available to
+ * compute that resolution. It is subsequently passed to {@link #getElevations(Sector, int)} when a sector's
+ * resolutions are queried.
+ *
+ * @param dc the draw context to read the view and rendering parameters from.
+ * @param sector the {@link Sector} to compute the target resolution for.
+ * @return The appropriate resolution for the sector and draw context values.
+ */
+ int getTargetResolution(DrawContext dc, Sector sector, int density);
+
+ double getElevation(Angle latitude, Angle longitude);
+
+ /**
+ * The Elevations
interface provides elevations at specified latitude and longitude positions. Objects
+ * implementing this interface are created by {@link ElevationModel#getElevations(Sector, int)}.
+ */
+ public interface Elevations
+ {
+ /**
+ * Indicates whether the object contains useful elevations. An Elevations
instance may exist
+ * without holding any elevations. This can occur when the resources needed to determine elevations are not yet
+ * local. This method enables the detection of that case. Callers typically use it to avoid time-consuming
+ * computations that require valid elevations.
+ *
+ * @return true
if a call to {@link #getElevation(double, double)} will return valid elevations,
+ * otherwise false
indicating that the value 0 will always be returned from that method.
+ */
+ boolean hasElevations();
+
+ /**
+ * Returns the elevation at a specific latitude and longitude, each specified in radians.
+ *
+ * @param latRadians the position's latitude in radians, in the range [-π/2, +π/2].
+ * @param lonRadians the position's longitude in radians, in the range [-π, +π].
+ * @return The elevation at the given position, or 0 if elevations are not available.
+ */
+ double getElevation(double latRadians, double lonRadians);
+
+ /**
+ * Returns the resolution value of the elevations. The meaning and use of this value is defined by subclasses of
+ * ElevationModel
.
+ *
+ * @return the resolution associated with this
.
+ */
+ int getResolution();
+
+ /**
+ * Returns the {@link Sector} the elevations pertain to.
+ *
+ * @return The sector the elevations pertain to.
+ */
+ Sector getSector();
+ }
+}
diff --git a/gov/nasa/worldwind/FileCache.java b/gov/nasa/worldwind/FileCache.java
new file mode 100644
index 0000000..7fcdc88
--- /dev/null
+++ b/gov/nasa/worldwind/FileCache.java
@@ -0,0 +1,34 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: FileCache.java 748 2007-02-03 19:22:54Z tgaskins $
+ */
+public interface FileCache
+{
+ public static final String OS_SPECIFIC_DATA_PATH = "FileCache.OSSpecificDataPathKey";
+
+ public boolean contains(String fileName);
+
+ public java.io.File newFile(String fileName);
+
+ java.net.URL findFile(String fileName, boolean checkClassPath);
+
+ void removeFile(java.net.URL url);
+
+ void addCacheLocation(String newPath);
+
+ void removeCacheLocation(String newPath);
+
+ java.util.List getCacheLocations();
+
+ java.io.File getWriteLocation();
+
+ void addCacheLocation(int index, String newPath);
+}
diff --git a/gov/nasa/worldwind/FrameController.java b/gov/nasa/worldwind/FrameController.java
new file mode 100644
index 0000000..6cbf2db
--- /dev/null
+++ b/gov/nasa/worldwind/FrameController.java
@@ -0,0 +1,26 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: FrameController.java 1416 2007-04-06 23:44:19Z tgaskins $
+ */
+public interface FrameController
+{
+ void initializeFrame(DrawContext dc);
+
+ void drawFrame(DrawContext dc);
+
+ void finalizeFrame(DrawContext dc);
+
+ void initializePicking(DrawContext dc);
+
+ void finalizePicking(DrawContext dc);
+
+ public void pick(DrawContext dc, java.awt.Point pickPoint);
+}
diff --git a/gov/nasa/worldwind/GeoRSSParser.java b/gov/nasa/worldwind/GeoRSSParser.java
new file mode 100644
index 0000000..f3717c7
--- /dev/null
+++ b/gov/nasa/worldwind/GeoRSSParser.java
@@ -0,0 +1,569 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+import org.w3c.dom.*;
+import org.xml.sax.*;
+
+import javax.xml.parsers.*;
+import javax.xml.namespace.*;
+import java.io.*;
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: GeoRSSParser.java 1686 2007-05-02 21:58:49Z tgaskins $
+ */
+
+public class GeoRSSParser
+{
+ public static final String GEORSS_URI = "http://www.georss.org/georss";
+ public static final String GML_URI = "http://www.opengis.net/gml";
+
+ public static List parseFragment(String fragmentString, NamespaceContext nsc)
+ {
+ return parseShapes(fixNamespaceQualification(fragmentString));
+ }
+
+ public static List parseShapes(String docString)
+ {
+ if (docString == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.StringIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (docString.length() < 1) // avoid empty strings
+ return null;
+
+ try
+ {
+ DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
+ docBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
+ Document doc = docBuilder.parse(new InputSource(new StringReader(docString)));
+
+ List shapes = parseShapes(doc);
+
+ if (shapes == null || shapes.size() < 1)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.NoShapes") + docString;
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ return shapes;
+ }
+ catch (ParserConfigurationException e)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.ParserConfigurationException");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+ throw new WWRuntimeException(message, e);
+ }
+ catch (IOException e)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.IOExceptionParsing") + docString;
+ WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+ throw new WWRuntimeException(message, e);
+ }
+ catch (SAXException e)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.IOExceptionParsing") + docString;
+ WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+ throw new WWRuntimeException(message, e);
+ }
+ }
+
+ private static String fixNamespaceQualification(String xmlString)
+ {
+ String lcaseString = xmlString.toLowerCase();
+ StringBuffer qualifiers = new StringBuffer();
+
+ if (lcaseString.contains("georss:") && !lcaseString.contains(GEORSS_URI))
+ {
+ qualifiers.append(" xmlns:georss=\"");
+ qualifiers.append(GEORSS_URI);
+ qualifiers.append("\"");
+ }
+
+ if (lcaseString.contains("gml:") && !lcaseString.contains(GML_URI))
+ {
+ qualifiers.append(" xmlns:gml=\"");
+ qualifiers.append(GML_URI);
+ qualifiers.append("\"");
+ }
+
+ if (qualifiers.length() > 0)
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append("");
+ sb.append(xmlString);
+ sb.append("");
+
+ return sb.toString();
+ }
+
+ return xmlString;
+ }
+
+ private static String surroundWithNameSpaces(String docString)
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append("");
+ sb.append(docString);
+ sb.append("");
+// System.out.println(sb.toString());
+
+ return sb.toString();
+ }
+
+ public static List parseShapes(File file)
+ {
+ if (file == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ try
+ {
+ DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
+ docBuilderFactory.setNamespaceAware(false);
+ DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
+ Document doc = docBuilder.parse(file);
+
+ List shapes = parseShapes(doc);
+
+ if (shapes == null || shapes.size() < 1)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.NoShapes") + file.getPath();
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ return shapes;
+ }
+ catch (ParserConfigurationException e)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.ParserConfigurationException");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+ throw new WWRuntimeException(message, e);
+ }
+ catch (IOException e)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.IOExceptionParsing") + file.getPath();
+ WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+ throw new WWRuntimeException(message, e);
+ }
+ catch (SAXException e)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.IOExceptionParsing") + file.getPath();
+ WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+ throw new WWRuntimeException(message, e);
+ }
+ }
+
+ public static List parseShapes(Document xmlDoc)
+ {
+ if (xmlDoc == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DocumentIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ ArrayList shapeNodes = new ArrayList();
+ ArrayList attributeNodes = new ArrayList();
+
+ // Shapes
+ NodeList nodes = xmlDoc.getElementsByTagNameNS(GEORSS_URI, "where");
+ if (nodes != null && nodes.getLength() > 0)
+ addNodes(shapeNodes, nodes);
+
+ nodes = xmlDoc.getElementsByTagNameNS(GEORSS_URI, "point");
+ if (nodes != null && nodes.getLength() > 0)
+ addNodes(shapeNodes, nodes);
+
+ nodes = xmlDoc.getElementsByTagNameNS(GEORSS_URI, "line");
+ if (nodes != null && nodes.getLength() > 0)
+ addNodes(shapeNodes, nodes);
+
+ nodes = xmlDoc.getElementsByTagNameNS(GEORSS_URI, "polygon");
+ if (nodes != null && nodes.getLength() > 0)
+ addNodes(shapeNodes, nodes);
+
+ nodes = xmlDoc.getElementsByTagNameNS(GEORSS_URI, "box");
+ if (nodes != null && nodes.getLength() > 0)
+ addNodes(shapeNodes, nodes);
+
+ // Attributes
+ nodes = xmlDoc.getElementsByTagNameNS(GEORSS_URI, "radius");
+ if (nodes != null && nodes.getLength() > 0)
+ addNodes(attributeNodes, nodes);
+
+ nodes = xmlDoc.getElementsByTagNameNS(GEORSS_URI, "elev");
+ if (nodes != null && nodes.getLength() > 0)
+ addNodes(attributeNodes, nodes);
+
+ ArrayList shapes = new ArrayList();
+
+ if (shapeNodes.size() < 1)
+ return null; // No warning here. Let the calling method inform of this case.
+
+ for (Node node : shapeNodes)
+ {
+ Renderable shape = null;
+ String localName = node.getLocalName();
+
+ if (localName.equals("point"))
+ shape = makePointShape(node, attributeNodes);
+
+ else if (localName.equals("where"))
+ shape = makeWhereShape(node);
+
+ else if (localName.equals("line"))
+ shape = makeLineShape(node, attributeNodes);
+
+ else if (localName.equals("polygon"))
+ shape = makePolygonShape(node, attributeNodes);
+
+ else if (localName.equals("box"))
+ shape = makeBoxShape(node, attributeNodes);
+
+ if (shape != null)
+ shapes.add(shape);
+ }
+
+ return shapes;
+ }
+
+ private static void addNodes(ArrayList nodeList, NodeList nodes)
+ {
+ for (int i = 0; i < nodes.getLength(); i++)
+ {
+ nodeList.add(nodes.item(i));
+ }
+ }
+
+ private static Renderable makeWhereShape(Node node)
+ {
+ Node typeNode = findChildByLocalName(node, "Polygon");
+ if (typeNode != null)
+ return makeGMLPolygonShape(typeNode);
+
+ typeNode = findChildByLocalName(node, "Envelope");
+ if (typeNode != null)
+ return makeGMLEnvelopeShape(typeNode);
+
+ typeNode = findChildByLocalName(node, "LineString");
+ if (typeNode != null)
+ return makeGMLineStringShape(typeNode);
+
+ typeNode = findChildByLocalName(node, "Point");
+ if (typeNode != null)
+ return makeGMLPointShape(typeNode);
+
+ String message = WorldWind.retrieveErrMsg("GeoRSS.MissingElementContent") + " where";
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ private static Renderable makeGMLPolygonShape(Node node)
+ {
+ Node n = findChildByLocalName(node, "exterior");
+ if (n == null)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.MissingElement") + " exterior";
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ n = findChildByLocalName(n, "LinearRing");
+ if (n == null)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.MissingElement") + " LinearRing";
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ return makePolygonShape(n, null);
+ }
+
+ private static Renderable makePolygonShape(Node node, Iterable attrs)
+ {
+ String valuesString = node.getTextContent();
+ if (valuesString == null)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.NoCoordinates" + node.getLocalName());
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ ArrayList values = getDoubleValues(valuesString);
+ if (values.size() < 8 || values.size() % 2 != 0)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.InvalidCoordinateCount" + node.getLocalName());
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ ArrayList positions = new ArrayList();
+ for (int i = 0; i < values.size(); i += 2)
+ {
+ positions.add(LatLon.fromDegrees(values.get(i), values.get(i + 1)));
+ }
+
+ double elevation = attrs != null ? getElevation(node, attrs) : 0d;
+ if (elevation != 0)
+ {
+ Polyline pl = new Polyline(positions, elevation);
+ pl.setFilled(true);
+ pl.setFollowGreatCircles(true);
+
+ return pl;
+ }
+ else
+ {
+ return new SurfacePolygon(positions);
+ }
+ }
+
+ private static Renderable makeGMLEnvelopeShape(Node node)
+ {
+ Node n = findChildByLocalName(node, "lowerCorner");
+ if (n == null)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.MissingElement") + " lowerCorner";
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ String lowerCornerString = n.getTextContent();
+ if (lowerCornerString == null)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.MissingElementContent") + " lowerCorner";
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ n = findChildByLocalName(node, "upperCorner");
+ if (n == null)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.MissingElement") + " upperCorner";
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ String upperCornerString = n.getTextContent();
+ if (upperCornerString == null)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.MissingElementContent") + " upperCorner";
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ ArrayList lv = getDoubleValues(lowerCornerString);
+ if (lv.size() != 2)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.InvalidCoordinateCount" + " lowerCorner");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ ArrayList uv = getDoubleValues(upperCornerString);
+ if (uv.size() != 2)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.InvalidCoordinateCount" + " upperCorner");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ return new SurfaceQuadrilateral(Sector.fromDegrees(lv.get(0), uv.get(0), lv.get(1), uv.get(1)));
+ }
+
+ private static Renderable makeBoxShape(Node node, Iterable attrs)
+ {
+ String valuesString = node.getTextContent();
+ if (valuesString == null)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.NoCoordinates" + node.getLocalName());
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ ArrayList p = getDoubleValues(valuesString);
+ if (p.size() != 4)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.InvalidCoordinateCount" + node.getLocalName());
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ double elevation = getElevation(node, attrs);
+ if (elevation != 0)
+ return new Quadrilateral(LatLon.fromDegrees(p.get(0), p.get(1)),
+ LatLon.fromDegrees(p.get(2), p.get(3)), elevation);
+ else
+ return new SurfaceQuadrilateral(Sector.fromDegrees(p.get(0), p.get(2), p.get(1), p.get(3)));
+ }
+
+ private static Renderable makeGMLineStringShape(Node node)
+ {
+ Node n = findChildByLocalName(node, "posList");
+ if (n == null)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.MissingElement") + " posList";
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ return makeLineShape(n, null);
+ }
+
+ private static Renderable makeLineShape(Node node, Iterable attrs)
+ {
+ String valuesString = node.getTextContent();
+ if (valuesString == null)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.NoCoordinates" + node.getLocalName());
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ ArrayList values = getDoubleValues(valuesString);
+ if (values.size() < 4)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.InvalidCoordinateCount" + node.getLocalName());
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ return null;
+ }
+
+ ArrayList positions = new ArrayList();
+ for (int i = 0; i < values.size(); i += 2)
+ {
+ positions.add(LatLon.fromDegrees(values.get(i), values.get(i + 1)));
+ }
+
+ double elevation = attrs != null ? getElevation(node, attrs) : 0d;
+ if (elevation != 0)
+ {
+ Polyline pl = new Polyline(positions, elevation);
+ pl.setFollowGreatCircles(true);
+ return pl;
+ }
+ else
+ {
+ return new SurfacePolyline(positions);
+ }
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ private static Renderable makeGMLPointShape(Node node)
+ {
+ return null; // No shape provided for points. Expect app to use icons.
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ private static Renderable makePointShape(Node node, Iterable attrs)
+ {
+ return null; // No shape provided for points. Expect app to use icons.
+ }
+
+ private static Node findChildByLocalName(Node parent, String localName)
+ {
+ NodeList children = parent.getChildNodes();
+ if (children == null || children.getLength() < 1)
+ return null;
+
+ for (int i = 0; i < children.getLength(); i++)
+ {
+ String ln = children.item(i).getLocalName();
+ if (ln != null && ln.equals(localName))
+ return children.item(i);
+ }
+
+ return null;
+ }
+
+ private static Node findSiblingAttribute(String attrName, Iterable attribs, Node shapeNode)
+ {
+ for (Node attrib : attribs)
+ {
+ if (!attrib.getLocalName().equals(attrName))
+ continue;
+
+ if (attrib.getParentNode().equals(shapeNode.getParentNode()))
+ return attrib;
+ }
+
+ return null;
+ }
+
+ private static ArrayList getDoubleValues(String stringValues)
+ {
+ String[] tokens = stringValues.trim().split("[ ,\n]");
+ if (tokens.length < 1)
+ return null;
+
+ ArrayList arl = new ArrayList();
+ for (String s : tokens)
+ {
+ if (s == null || s.length() < 1)
+ continue;
+
+ double d;
+ try
+ {
+ d = Double.parseDouble(s);
+ }
+ catch (NumberFormatException e)
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.NumberFormatException" + s);
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ continue;
+ }
+
+ arl.add(d);
+ }
+
+ return arl;
+ }
+
+ private static double getElevation(Node shapeNode, Iterable attrs)
+ {
+ double elevation = 0d;
+
+ Node elevNode = findSiblingAttribute("elev", attrs, shapeNode);
+ if (elevNode != null)
+ {
+ ArrayList ev = getDoubleValues(elevNode.getTextContent());
+ if (ev != null && ev.size() > 0)
+ {
+ elevation = ev.get(0);
+ }
+ else
+ {
+ String message = WorldWind.retrieveErrMsg("GeoRSS.MissingElementContent") + " elev";
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ }
+ }
+
+ return elevation;
+ }
+}
diff --git a/gov/nasa/worldwind/Globe.java b/gov/nasa/worldwind/Globe.java
new file mode 100644
index 0000000..f9c0cd9
--- /dev/null
+++ b/gov/nasa/worldwind/Globe.java
@@ -0,0 +1,46 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: Globe.java 1783 2007-05-08 06:31:19Z tgaskins $
+ */
+public interface Globe extends WWObject, Extent
+{
+ Point computePointFromPosition(Angle latitude, Angle longitude, double metersElevation);
+
+ Point computeSurfaceNormalAtPoint(Point p);
+
+ ElevationModel getElevationModel();
+
+ Extent getExtent();
+
+ double getEquatorialRadius();
+
+ double getPolarRadius();
+
+ double getMaximumRadius();
+
+ double getRadiusAt(Angle latitude, Angle longitude);
+
+ double getElevation(Angle latitude, Angle longitude);
+
+ double getMaxElevation();
+
+ double getMinElevation();
+
+ Position getIntersectionPosition(Line line);
+
+ double getEccentricitySquared();
+
+ Position computePositionFromPoint(Point point);
+
+ Point computePointFromPosition(Position position);
+}
diff --git a/gov/nasa/worldwind/HTTPRetriever.java b/gov/nasa/worldwind/HTTPRetriever.java
new file mode 100644
index 0000000..3bb10c2
--- /dev/null
+++ b/gov/nasa/worldwind/HTTPRetriever.java
@@ -0,0 +1,61 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import java.net.*;
+import java.nio.*;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: HTTPRetriever.java 1724 2007-05-05 04:05:40Z tgaskins $
+ */
+public class HTTPRetriever extends URLRetriever
+{
+ private int responseCode;
+ private String responseMessage;
+
+ public HTTPRetriever(URL url, RetrievalPostProcessor postProcessor)
+ {
+ super(url, postProcessor);
+ }
+
+ public int getResponseCode()
+ {
+ return this.responseCode;
+ }
+
+ public String getResponseMessage()
+ {
+ return this.responseMessage;
+ }
+
+ protected ByteBuffer doRead(URLConnection connection) throws Exception
+ {
+ if (connection == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.ConnectionIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ HttpURLConnection htpc = (HttpURLConnection) connection;
+ this.responseCode = htpc.getResponseCode();
+ this.responseMessage = htpc.getResponseMessage();
+ String contentType = connection.getContentType();
+
+ WorldWind.logger().log(java.util.logging.Level.FINER, WorldWind.retrieveErrMsg("HTTPRetriever.ResponseCode")
+ + this.responseCode
+ + WorldWind.retrieveErrMsg("HTTPRetriever.ResponseContentLength") + connection.getContentLength()
+ + (contentType != null ? " " + contentType : " content type not returned")
+ + WorldWind.retrieveErrMsg("HTTPRetriever.Retrieving") + connection.getURL());
+
+ if (this.responseCode == HttpURLConnection.HTTP_OK)
+ return super.doRead(connection);
+
+ return null;
+ }
+}
diff --git a/gov/nasa/worldwind/IconRenderer.java b/gov/nasa/worldwind/IconRenderer.java
new file mode 100644
index 0000000..8c62efe
--- /dev/null
+++ b/gov/nasa/worldwind/IconRenderer.java
@@ -0,0 +1,582 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import com.sun.opengl.util.j2d.*;
+import com.sun.opengl.util.texture.*;
+import gov.nasa.worldwind.geom.*;
+import gov.nasa.worldwind.geom.Point;
+
+import javax.media.opengl.*;
+import java.awt.*;
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: IconRenderer.java 1784 2007-05-08 06:33:06Z tgaskins $
+ */
+public class IconRenderer implements Disposable
+{
+ // TODO: Periodically clean unused textures from the texture map.
+ private java.util.HashMap iconTextures = new java.util.HashMap();
+ private Pedestal pedestal;
+ private PickSupport pickSupport = new PickSupport();
+ private HashMap toolTipRenderers = new HashMap();
+
+ public IconRenderer()
+ {
+ }
+
+ public void dispose()
+ {
+ for (Texture iconTexture : this.iconTextures.values())
+ {
+ if (iconTexture != null)
+ iconTexture.dispose();
+ }
+ }
+
+ public Pedestal getPedestal()
+ {
+ return pedestal;
+ }
+
+ public void setPedestal(Pedestal pedestal)
+ {
+ this.pedestal = pedestal;
+ }
+
+ private static boolean isIconValid(WWIcon icon, boolean checkPosition)
+ {
+ if (icon == null || icon.getPath() == null)
+ return false;
+
+ //noinspection RedundantIfStatement
+ if (checkPosition && icon.getPosition() == null)
+ return false;
+
+ return true;
+ }
+
+ public void pick(DrawContext dc, Iterator icons, java.awt.Point pickPoint, Layer layer)
+ {
+ this.drawMany(dc, icons);
+ }
+
+ public void pick(DrawContext dc, WWIcon icon, Point iconPoint, java.awt.Point pickPoint, Layer layer)
+ {
+ if (!isIconValid(icon, false))
+ return;
+
+ this.drawOne(dc, icon, iconPoint);
+ }
+
+ public void render(DrawContext dc, Iterator icons)
+ {
+ this.drawMany(dc, icons);
+ }
+
+ public void render(DrawContext dc, WWIcon icon, Point iconPoint)
+ {
+ if (!isIconValid(icon, false))
+ return;
+
+ this.drawOne(dc, icon, iconPoint);
+ }
+
+ private void drawMany(DrawContext dc, Iterator icons)
+ {
+ if (dc == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (dc.getVisibleSector() == null)
+ return;
+
+ SectorGeometryList geos = dc.getSurfaceGeometry();
+ //noinspection RedundantIfStatement
+ if (geos == null)
+ return;
+
+ if (icons == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.IconIterator");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (!icons.hasNext())
+ return;
+
+ while (icons.hasNext())
+ {
+ WWIcon icon = icons.next();
+ if (!isIconValid(icon, true))
+ continue;
+
+ if (!icon.isVisible())
+ continue;
+
+ // Determine Cartesian position from the surface geometry if the icon is near the surface,
+ // otherwise draw it from the globe.
+ Position pos = icon.getPosition();
+ Point iconPoint = null;
+ if (pos.getElevation() < dc.getGlobe().getMaxElevation())
+ iconPoint = dc.getSurfaceGeometry().getSurfacePoint(icon.getPosition());
+ if (iconPoint == null)
+ iconPoint = dc.getGlobe().computePointFromPosition(icon.getPosition());
+
+ // The icons aren't drawn here, but added to the ordered queue to be drawn back-to-front.
+ double eyeDistance = dc.getView().getEyePoint().distanceTo(iconPoint);
+ dc.addOrderedRenderable(new OrderedIcon(icon, iconPoint, eyeDistance));
+
+ if (icon.isShowToolTip())
+ this.addToolTip(dc, icon, iconPoint);
+ }
+ }
+
+ private void drawOne(DrawContext dc, WWIcon icon, Point iconPoint)
+ {
+ if (dc == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (dc.getVisibleSector() == null)
+ return;
+
+ SectorGeometryList geos = dc.getSurfaceGeometry();
+ //noinspection RedundantIfStatement
+ if (geos == null)
+ return;
+
+ if (!icon.isVisible())
+ return;
+
+ if (iconPoint == null)
+ {
+ Angle lat = icon.getPosition().getLatitude();
+ Angle lon = icon.getPosition().getLongitude();
+
+ if (!dc.getVisibleSector().contains(lat, lon))
+ return;
+
+ iconPoint = dc.getSurfaceGeometry().getSurfacePoint(lat, lon, icon.getPosition().getElevation());
+ if (iconPoint == null)
+ return;
+ }
+
+ if (!dc.getView().getFrustumInModelCoordinates().contains(iconPoint))
+ return;
+
+ double horizon = dc.getView().computeHorizonDistance();
+ double eyeDistance = dc.getView().getEyePoint().distanceTo(iconPoint);
+ if (eyeDistance > horizon)
+ return;
+
+ // The icon isn't drawn here, but added to the ordered queue to be drawn back-to-front.
+ dc.addOrderedRenderable(new OrderedIcon(icon, iconPoint, eyeDistance));
+
+ if (icon.isShowToolTip())
+ this.addToolTip(dc, icon, iconPoint);
+ }
+
+ private void addToolTip(DrawContext dc, WWIcon icon, Point iconPoint)
+ {
+ if (icon.getToolTipFont() == null && icon.getToolTipText() == null)
+ return;
+
+ final Point screenPoint = dc.getView().project(iconPoint);
+ if (screenPoint == null)
+ return;
+
+ OrderedText tip = new OrderedText(icon.getToolTipText(), icon.getToolTipFont(), screenPoint,
+ icon.getToolTipTextColor(), 0d);
+ dc.addOrderedRenderable(tip);
+ }
+
+ private class OrderedText implements OrderedRenderable
+ {
+ Font font;
+ String text;
+ Point point;
+ double eyeDistance;
+ java.awt.Point pickPoint;
+ Layer layer;
+ java.awt.Color color;
+
+ OrderedText(String text, Font font, Point point, java.awt.Color color, double eyeDistance)
+ {
+ this.text = text;
+ this.font = font;
+ this.point = point;
+ this.eyeDistance = eyeDistance;
+ this.color = color;
+ }
+
+ OrderedText(String text, Font font, Point point, java.awt.Point pickPoint, Layer layer, double eyeDistance)
+ {
+ this.text = text;
+ this.font = font;
+ this.point = point;
+ this.eyeDistance = eyeDistance;
+ this.pickPoint = pickPoint;
+ this.layer = layer;
+ }
+
+ public double getDistanceFromEye()
+ {
+ return this.eyeDistance;
+ }
+
+ public void render(DrawContext dc)
+ {
+ ToolTipRenderer tr = IconRenderer.this.toolTipRenderers.get(this.font);
+ if (tr == null)
+ {
+ if (this.font != null)
+ tr = new ToolTipRenderer(new TextRenderer(this.font, true, true));
+ else
+ tr = new ToolTipRenderer();
+ IconRenderer.this.toolTipRenderers.put(this.font, tr);
+ }
+
+ Rectangle vp = dc.getView().getViewport();
+ tr.setForeground(this.color);
+ tr.setUseSystemLookAndFeel(this.color == null);
+ tr.beginRendering(vp.width, vp.height, true);
+ tr.draw(this.text, (int) point.x(), (int) point.y());
+ tr.endRendering();
+ }
+
+ public void pick(DrawContext dc, java.awt.Point pickPoint)
+ {
+ }
+ }
+
+ private class OrderedIcon implements OrderedRenderable, Locatable
+ {
+ WWIcon icon;
+ Point point;
+ double eyeDistance;
+ java.awt.Point pickPoint;
+ Layer layer;
+
+ OrderedIcon(WWIcon icon, Point point, double eyeDistance)
+ {
+ this.icon = icon;
+ this.point = point;
+ this.eyeDistance = eyeDistance;
+ }
+
+ OrderedIcon(WWIcon icon, Point point, java.awt.Point pickPoint, Layer layer, double eyeDistance)
+ {
+ this.icon = icon;
+ this.point = point;
+ this.eyeDistance = eyeDistance;
+ this.pickPoint = pickPoint;
+ this.layer = layer;
+ }
+
+ public double getDistanceFromEye()
+ {
+ return this.eyeDistance;
+ }
+
+ public Position getPosition()
+ {
+ return this.icon.getPosition();
+ }
+
+ public void render(DrawContext dc)
+ {
+ IconRenderer.this.beginDrawIcons(dc);
+
+ try
+ {
+ IconRenderer.this.drawIcon(dc, this);
+ // Draw as many as we can in a batch to save ogl state switching.
+ while (dc.getOrderedRenderables().peek() instanceof OrderedIcon)
+ {
+ OrderedIcon oi = (OrderedIcon) dc.getOrderedRenderables().poll();
+ IconRenderer.this.drawIcon(dc, oi);
+ }
+ }
+ catch (WWRuntimeException e)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.ExceptionWhileRenderingIcon");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg + ":" + e.getMessage());
+ }
+ catch (Exception e)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.ExceptionWhileRenderingIcon");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg, e);
+ }
+ finally
+ {
+ IconRenderer.this.endDrawIcons(dc);
+ }
+ }
+
+ public void pick(DrawContext dc, java.awt.Point pickPoint)
+ {
+ IconRenderer.this.pickSupport.clearPickList();
+ IconRenderer.this.beginDrawIcons(dc);
+ try
+ {
+ IconRenderer.this.drawIcon(dc, this);
+ // Draw as many as we can in a batch to save ogl state switching.
+ while (dc.getOrderedRenderables().peek() instanceof OrderedIcon)
+ {
+ IconRenderer.this.drawIcon(dc, (OrderedIcon) dc.getOrderedRenderables().poll());
+ }
+ }
+ catch (WWRuntimeException e)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.ExceptionWhileRenderingIcon");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg + ":" + e.getMessage());
+ }
+ catch (Exception e)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.ExceptionWhilePickingIcon");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg, e);
+ }
+ finally
+ {
+ IconRenderer.this.endDrawIcons(dc);
+ IconRenderer.this.pickSupport.resolvePick(dc, pickPoint, layer);
+ IconRenderer.this.pickSupport.clearPickList(); // to ensure entries can be garbage collected
+ }
+ }
+ }
+
+ private void beginDrawIcons(DrawContext dc)
+ {
+ GL gl = dc.getGL();
+
+ int attributeMask =
+ GL.GL_DEPTH_BUFFER_BIT // for depth test, depth mask and depth func
+ | GL.GL_TRANSFORM_BIT // for modelview and perspective
+ | GL.GL_VIEWPORT_BIT // for depth range
+ | GL.GL_CURRENT_BIT // for current color
+ | GL.GL_COLOR_BUFFER_BIT // for alpha test func and ref, and blend
+ | GL.GL_TEXTURE_BIT // for texture env
+ | GL.GL_DEPTH_BUFFER_BIT // for depth func
+ | GL.GL_ENABLE_BIT; // for enable/disable changes
+ gl.glPushAttrib(attributeMask);
+
+ // Apply the depth buffer but don't change it.
+ gl.glEnable(GL.GL_DEPTH_TEST);
+ gl.glDepthMask(false);
+
+ // Suppress any fully transparent image pixels
+ gl.glEnable(GL.GL_ALPHA_TEST);
+ gl.glAlphaFunc(GL.GL_GREATER, 0.001f);
+
+ // Load a parallel projection with dimensions (viewportWidth, viewportHeight)
+ int[] viewport = new int[4];
+ gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);
+ gl.glMatrixMode(GL.GL_PROJECTION);
+ gl.glPushMatrix();
+ gl.glLoadIdentity();
+ gl.glOrtho(0d, viewport[2], 0d, viewport[3], -1d, 1d);
+
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPushMatrix();
+
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPushMatrix();
+
+ if (dc.isPickingMode())
+ {
+ this.pickSupport.beginPicking(dc);
+
+ // Set up to replace the non-transparent texture colors with the single pick color.
+ gl.glEnable(GL.GL_TEXTURE_2D);
+ gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_COMBINE);
+ gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC0_RGB, GL.GL_PREVIOUS);
+ gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_COMBINE_RGB, GL.GL_REPLACE);
+ }
+ else
+ {
+ gl.glEnable(GL.GL_TEXTURE_2D);
+ gl.glEnable(GL.GL_BLEND);
+ gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA);
+ }
+ }
+
+ private void endDrawIcons(DrawContext dc)
+ {
+ if (dc.isPickingMode())
+ this.pickSupport.endPicking(dc);
+
+ GL gl = dc.getGL();
+ gl.glMatrixMode(GL.GL_PROJECTION);
+ gl.glPopMatrix();
+
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPopMatrix();
+
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPopMatrix();
+
+ gl.glPopAttrib();
+ }
+
+ private Point drawIcon(DrawContext dc, OrderedIcon uIcon)
+ {
+ if (uIcon.point == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ return null;
+ }
+
+ WWIcon icon = uIcon.icon;
+
+ final Point screenPoint = dc.getView().project(uIcon.point);
+ if (screenPoint == null)
+ return null;
+
+ Texture iconTexture = this.iconTextures.get(icon.getPath());
+ if (iconTexture == null)
+ iconTexture = this.initializeTexture(dc, icon);
+
+ double pedestalScale;
+ double pedestalSpacing;
+ Texture pedestalTexture = null;
+ if (pedestal != null)
+ {
+ pedestalScale = this.pedestal.getScale();
+ pedestalSpacing = pedestal.getSpacingPixels();
+
+ pedestalTexture = this.iconTextures.get(pedestal.getPath());
+ if (pedestalTexture == null)
+ pedestalTexture = this.initializeTexture(dc, pedestal);
+ }
+ else
+ {
+ pedestalScale = 0d;
+ pedestalSpacing = 0d;
+ }
+
+ javax.media.opengl.GL gl = dc.getGL();
+
+ this.setDepthFunc(dc, screenPoint);
+
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glLoadIdentity();
+
+ Dimension size = icon.getSize();
+ double width = size != null ? size.getWidth() : iconTexture.getWidth();
+ double height = size != null ? size.getHeight() : iconTexture.getHeight();
+ gl.glTranslated(screenPoint.x() - width / 2, screenPoint.y() + (pedestalScale * height) + pedestalSpacing, 0d);
+
+ if (icon.isHighlighted())
+ {
+ double heightDelta = this.pedestal != null ? 0 : height / 2; // expand only above the pedestal
+ gl.glTranslated(width / 2, heightDelta, 0);
+ gl.glScaled(icon.getHighlightScale(), icon.getHighlightScale(), icon.getHighlightScale());
+ gl.glTranslated(-width / 2, -heightDelta, 0);
+ }
+
+ if (dc.isPickingMode())
+ {
+ java.awt.Color color = dc.getUniquePickColor();
+ int colorCode = color.getRGB();
+ this.pickSupport.addPickableObject(colorCode, icon, uIcon.getPosition(), false);
+ gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue());
+ }
+
+ iconTexture.bind();
+ TextureCoords texCoords = iconTexture.getImageTexCoords();
+ gl.glScaled(width, height, 1d);
+ dc.drawUnitQuad(texCoords);
+
+ if (pedestalTexture != null)
+ {
+ gl.glLoadIdentity();
+ gl.glTranslated(screenPoint.x() - (pedestalScale * (width / 2)), screenPoint.y(), 0d);
+ gl.glScaled(width * pedestalScale, height * pedestalScale, 1d);
+
+ pedestalTexture.bind();
+ texCoords = pedestalTexture.getImageTexCoords();
+ dc.drawUnitQuad(texCoords);
+ }
+
+ return screenPoint;
+ }
+
+ private void setDepthFunc(DrawContext dc, Point screenPoint)
+ {
+ GL gl = dc.getGL();
+
+ if (dc.getView().getAltitude() < dc.getGlobe().getMaxElevation() * dc.getVerticalExaggeration())
+ {
+ double depth = screenPoint.z() - 8d * 0.00048875809d;
+ depth = depth < 0d ? 0d : (depth > 1d ? 1d : depth);
+ gl.glDepthFunc(GL.GL_LESS);
+ gl.glDepthRange(depth, depth);
+ }
+ else if (screenPoint.z() >= 1d)
+ {
+ gl.glDepthFunc(GL.GL_EQUAL);
+ gl.glDepthRange(1d, 1d);
+ }
+ else
+ {
+ gl.glDepthFunc(GL.GL_ALWAYS);
+ }
+ }
+
+ private Texture initializeTexture(DrawContext dc, WWIcon icon)
+ {
+ try
+ {
+ java.io.InputStream iconStream = this.getClass().getResourceAsStream("/" + icon.getPath());
+ if (iconStream == null)
+ {
+ java.io.File iconFile = new java.io.File(icon.getPath());
+ if (iconFile.exists())
+ {
+ iconStream = new java.io.FileInputStream(iconFile);
+ }
+ }
+
+ // Icons with the same path are assumed to be identical textures, so key the texture id off the path.
+ Texture iconTexture = TextureIO.newTexture(iconStream, true, null);
+ this.iconTextures.put(icon.getPath(), iconTexture);
+ iconTexture.bind();
+
+ GL gl = dc.getGL();
+ gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE);
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR_MIPMAP_LINEAR);
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);
+
+ return iconTexture;
+ }
+ catch (java.io.IOException e)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.IOExceptionDuringTextureInitialization");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg, e);
+ throw new WWRuntimeException(msg, e);
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return WorldWind.retrieveErrMsg("layers.IconLayer.Name");
+ }
+}
diff --git a/gov/nasa/worldwind/InputHandler.java b/gov/nasa/worldwind/InputHandler.java
new file mode 100644
index 0000000..5b990af
--- /dev/null
+++ b/gov/nasa/worldwind/InputHandler.java
@@ -0,0 +1,30 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author tag
+ * @version $Id$
+ */
+public interface InputHandler extends AVList, java.beans.PropertyChangeListener
+{
+ void setEventSource(WorldWindow newWorldWindow);
+
+ WorldWindow getEventSource();
+
+ void setHoverDelay(int delay);
+
+ int getHoverDelay();
+
+ void addSelectListener(SelectListener listener);
+
+ void removeSelectListener(SelectListener listener);
+
+ void addPositionListener(PositionListener listener);
+
+ void removePositionListener(PositionListener listener);
+}
diff --git a/gov/nasa/worldwind/Layer.java b/gov/nasa/worldwind/Layer.java
new file mode 100644
index 0000000..2edb783
--- /dev/null
+++ b/gov/nasa/worldwind/Layer.java
@@ -0,0 +1,34 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: Layer.java 1424 2007-04-07 04:08:12Z tgaskins $
+ */
+public interface Layer extends WWObject, Disposable
+{
+ public boolean isEnabled();
+
+ public void setEnabled(boolean enabled);
+
+ String getName();
+
+ void setName(String name);
+
+ double getOpacity();
+
+ void setOpacity(double opacity);
+
+ boolean isPickEnabled();
+
+ void setPickEnabled(boolean isPickable);
+
+ public void render(DrawContext dc);
+
+ public void pick(DrawContext dc, java.awt.Point pickPoint);
+}
diff --git a/gov/nasa/worldwind/LayerList.java b/gov/nasa/worldwind/LayerList.java
new file mode 100644
index 0000000..0885996
--- /dev/null
+++ b/gov/nasa/worldwind/LayerList.java
@@ -0,0 +1,90 @@
+package gov.nasa.worldwind;
+
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: LayerList.java 1467 2007-04-14 01:24:51Z tgaskins $
+ */
+public class LayerList extends WWObjectImpl implements Iterable
+{
+ private java.util.List layerList = new java.util.ArrayList();
+
+ public LayerList()
+ {
+ }
+
+ public LayerList(Layer[] layers)
+ {
+ if (layers == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LayersIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ for (Layer layer : layers)
+ {
+ this.add(layer);
+ }
+ }
+
+ public void add(Layer layer)
+ {
+ if (layer == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LayerIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (this.layerList.contains(layer))
+ return;
+
+ this.layerList.add(layer);
+ layer.addPropertyChangeListener(this);
+ this.firePropertyChange(AVKey.LAYERS, null, this.layerList); // TODO: send old layer list content
+ }
+
+ public void remove(Layer layer)
+ {
+ if (layer == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.LayerIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (!this.layerList.contains(layer))
+ return;
+
+ layer.removePropertyChangeListener(this);
+ this.layerList.remove(layer);
+ this.firePropertyChange(AVKey.LAYERS, null, this.layerList); // TODO: send old layer list content
+ }
+
+ public java.util.Iterator iterator()
+ {
+ return this.layerList.iterator();
+ }
+
+ public int getSize()
+ {
+ return this.layerList.size();
+ }
+
+ @Override
+ public String toString()
+ {
+ String r = "";
+ for (Layer l : this.layerList)
+ {
+ r += l.toString() + ", ";
+ }
+ return r;
+ }
+}
diff --git a/gov/nasa/worldwind/Level.java b/gov/nasa/worldwind/Level.java
new file mode 100644
index 0000000..c1908fd
--- /dev/null
+++ b/gov/nasa/worldwind/Level.java
@@ -0,0 +1,345 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+import java.net.*;
+
+/**
+ * @author tag
+ * @version $Id: Level.java 1732 2007-05-05 07:47:37Z tgaskins $
+ */
+public class Level implements Comparable
+{
+ public static final String NUM_LEVELS = "gov.nasa.worldwind.Level.NumLevels";
+ public static final String NUM_EMPTY_LEVELS = "gov.nasa.worldwind.Level.NumEmptyLevels";
+ public static final String LEVEL_ZERO_TILE_DELTA = "gov.nasa.worldwind.Level.LevelZeroTileDelta";
+ public static final String LEVEL_NUMBER = "gov.nasa.worldwind.Level.LevelNumberKey";
+ public static final String LEVEL_NAME = "gov.nasa.worldwind.Level.LevelNameKey";
+ public static final String TILE_DELTA = "gov.nasa.worldwind.Level.TileDeltaKey";
+ public static final String TILE_WIDTH = "gov.nasa.worldwind.Level.TileWidthKey";
+ public static final String TILE_HEIGHT = "gov.nasa.worldwind.Level.TileHeightKey";
+ public static final String CACHE_NAME = "gov.nasa.worldwind.Level.CacheNameKey";
+ public static final String SERVICE = "gov.nasa.worldwind.Level.ServiceURLKey";
+ public static final String DATASET_NAME = "gov.nasa.worldwind.Level.DatasetNameKey";
+ public static final String FORMAT_SUFFIX = "gov.nasa.worldwind.Level.FormatSuffixKey";
+ public static final String EXPIRY_TIME = "gov.nasa.worldwind.Level.ExpiryTime";
+ public static final String TILE_URL_BUILDER = "gov.nasa.worldwind.Level.TileURLBuilder";
+ public static final String MAX_ABSENT_TILE_ATTEMPTS = "gov.nasa.worldwind.Level.MaxAbsentTileAttempts";
+ public static final String MIN_ABSENT_TILE_CHECK_INTERVAL = "gov.nasa.worldwind.Level.MinAbsentTileCheckInterval";
+
+ private final AVList params;
+ private final int levelNumber;
+ private final String levelName; // null or empty level name signifies no data resources associated with this level
+ private final LatLon tileDelta;
+ private final int tileWidth;
+ private final int tileHeight;
+ private final String cacheName;
+ private final String service;
+ private final String dataset;
+ private final String formatSuffix;
+ private final double averageTexelSize;
+ private final String path;
+ private final TileURLBuilder urlbuilder;
+ private long expiryTime = 0;
+
+ // Absent tiles: A tile is deemed absent if a specified maximum number of attempts have been made to retrieve it.
+ // Retrieval attempts are governed by a minimum time interval between successive attempts. If an attempt is made
+ // within this interval, the tile is still deemed to be absent until the interval expires.
+ private final AbsentResourceList absentTiles;
+ private static final int DEFAULT_MAX_ABSENT_TILE_ATTEMPTS = 2;
+ private static final int DEFAULT_MIN_ABSENT_TILE_CHECK_INTERVAL = 10000; // milliseconds
+
+ public Level(AVList params)
+ {
+ if (params == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LayerParams");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.params = params.copy(); // Private copy to insulate from subsequent changes by the app
+ String message = this.validate(params);
+ if (message != null)
+ {
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ String ln = this.params.getStringValue(LEVEL_NAME);
+ this.levelName = ln != null ? ln : "";
+
+ this.levelNumber = (Integer) this.params.getValue(LEVEL_NUMBER);
+ this.tileDelta = (LatLon) this.params.getValue(TILE_DELTA);
+ this.tileWidth = (Integer) this.params.getValue(TILE_WIDTH);
+ this.tileHeight = (Integer) this.params.getValue(TILE_HEIGHT);
+ this.cacheName = this.params.getStringValue(CACHE_NAME);
+ this.service = this.params.getStringValue(SERVICE);
+ this.dataset = this.params.getStringValue(DATASET_NAME);
+ this.formatSuffix = this.params.getStringValue(FORMAT_SUFFIX);
+ this.urlbuilder = (TileURLBuilder) this.params.getValue(TILE_URL_BUILDER);
+
+ double averageTileSize = 0.5 * (this.tileWidth + this.tileHeight);
+ double averageTileDelta =
+ 0.5 * (this.tileDelta.getLatitude().getRadians() + this.tileDelta.getLongitude().getRadians());
+ this.averageTexelSize = averageTileDelta / averageTileSize;
+
+ this.path = this.cacheName + "/" + this.levelName;
+
+ Integer maxAbsentTileAttempts = (Integer) this.params.getValue(MAX_ABSENT_TILE_ATTEMPTS);
+ if (maxAbsentTileAttempts == null)
+ maxAbsentTileAttempts = DEFAULT_MAX_ABSENT_TILE_ATTEMPTS;
+
+ Integer minAbsentTileCheckInterval = (Integer) this.params.getValue(MIN_ABSENT_TILE_CHECK_INTERVAL);
+ if (minAbsentTileCheckInterval == null)
+ minAbsentTileCheckInterval = DEFAULT_MIN_ABSENT_TILE_CHECK_INTERVAL;
+
+ this.absentTiles = new AbsentResourceList(maxAbsentTileAttempts, minAbsentTileCheckInterval);
+ }
+
+ /**
+ * Determines whether the constructor arguments are valid.
+ * @param params the list of parameters to validate.
+ * @return null if valid, otherwise a String
containing a description of why it's invalid.
+ */
+ protected String validate(AVList params)
+ {
+ StringBuffer sb = new StringBuffer();
+
+ Object o = params.getValue(LEVEL_NUMBER);
+ if (o == null || !(o instanceof Integer) || ((Integer) o) < 0)
+ sb.append(WorldWind.retrieveErrMsg("term.levelNumber"));
+
+ o = params.getValue(LEVEL_NAME);
+ if (o == null || !(o instanceof String))
+ sb.append(WorldWind.retrieveErrMsg("term.levelName"));
+
+ o = params.getValue(TILE_WIDTH);
+ if (o == null || !(o instanceof Integer) || ((Integer) o) < 0)
+ sb.append(WorldWind.retrieveErrMsg("term.tileWidth"));
+
+ o = params.getValue(TILE_HEIGHT);
+ if (o == null || !(o instanceof Integer) || ((Integer) o) < 0)
+ sb.append(WorldWind.retrieveErrMsg("term.tileHeight"));
+
+ o = params.getValue(TILE_DELTA);
+ if (o == null || !(o instanceof LatLon))
+ sb.append(WorldWind.retrieveErrMsg("term.tileDelta"));
+
+ o = params.getValue(CACHE_NAME);
+ if (o == null || !(o instanceof String) || ((String) o).length() < 1)
+ sb.append(WorldWind.retrieveErrMsg("term.cacheFolder"));
+
+ o = params.getValue(TILE_URL_BUILDER);
+ if (o == null || !(o instanceof TileURLBuilder))
+ sb.append(WorldWind.retrieveErrMsg("term.tileURLBuilder"));
+
+ o = params.getValue(EXPIRY_TIME);
+ if (o != null && (!(o instanceof Long) || ((Long) o) < 1))
+ sb.append(WorldWind.retrieveErrMsg("term.expiryTime"));
+
+ if (params.getStringValue(LEVEL_NAME).length() > 0)
+ {
+ o = params.getValue(SERVICE);
+ if (o == null || !(o instanceof String) || ((String) o).length() < 1)
+ sb.append(WorldWind.retrieveErrMsg("term.service"));
+
+ o = params.getValue(DATASET_NAME);
+ if (o == null || !(o instanceof String) || ((String) o).length() < 1)
+ sb.append(WorldWind.retrieveErrMsg("term.datasetName"));
+
+ o = params.getValue(FORMAT_SUFFIX);
+ if (o == null || !(o instanceof String) || ((String) o).length() < 1)
+ sb.append(WorldWind.retrieveErrMsg("term.formatSuffix"));
+ }
+
+ if (sb.length() == 0)
+ return null;
+
+ return sb.insert(0, WorldWind.retrieveErrMsg("layers.LevelSet.InvalidLevelDescriptorFields")).toString();
+ }
+
+ public final AVList getParams()
+ {
+ return params;
+ }
+
+ public String getPath()
+ {
+ return this.path;
+ }
+
+ public final int getLevelNumber()
+ {
+ return this.levelNumber;
+ }
+
+ public String getLevelName()
+ {
+ return this.levelName;
+ }
+
+ public LatLon getTileDelta()
+ {
+ return this.tileDelta;
+ }
+
+ public final int getTileWidth()
+ {
+ return this.tileWidth;
+ }
+
+ public final int getTileHeight()
+ {
+ return this.tileHeight;
+ }
+
+ public final String getFormatSuffix()
+ {
+ return this.formatSuffix;
+ }
+
+ public final String getService()
+ {
+ return this.service;
+ }
+
+ public final String getDataset()
+ {
+ return this.dataset;
+ }
+
+ public final String getCacheName()
+ {
+ return this.cacheName;
+ }
+
+ public final double getTexelSize(double radius)
+ {
+ return radius * this.averageTexelSize;
+ }
+
+ public final boolean isEmpty()
+ {
+ return this.levelName == null || this.levelName.equals("");
+ }
+
+ public final void markResourceAbsent(long tileNumber)
+ {
+ this.absentTiles.markResourceAbsent(tileNumber);
+ }
+
+ public final boolean isResourceAbsent(long tileNumber)
+ {
+ return this.absentTiles.isResourceAbsent(tileNumber);
+ }
+
+ public final void unmarkResourceAbsent(long tileNumber)
+ {
+ this.absentTiles.unmarkResourceAbsent(tileNumber);
+ }
+
+ public final long getExpiryTime()
+ {
+ return expiryTime;
+ }
+
+ public void setExpiryTime(long expiryTime) // TODO: remove
+ {
+ this.expiryTime = expiryTime;
+ }
+
+ public interface TileURLBuilder
+ {
+ public URL getURL(Tile tile) throws java.net.MalformedURLException;
+ }
+
+ /**
+ * Returns the URL necessary to retrieve the specified tile.
+ * @param tile the tile who's resources will be retrieved.
+ * @return the resource URL.
+ * @throws java.net.MalformedURLException if the URL cannot be formed from the tile's parameters.
+ * @throws IllegalArgumentException if tile
is null.
+ */
+ public java.net.URL getTileResourceURL(Tile tile) throws java.net.MalformedURLException
+ {
+ if (tile == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.TileIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ return this.urlbuilder.getURL(tile);
+ }
+
+ public int compareTo(Level that)
+ {
+ if (that == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.LevelIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return this.levelNumber < that.levelNumber ? -1 : this.levelNumber == that.levelNumber ? 0 : 1;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final gov.nasa.worldwind.Level level = (gov.nasa.worldwind.Level) o;
+
+ if (levelNumber != level.levelNumber)
+ return false;
+ if (tileHeight != level.tileHeight)
+ return false;
+ if (tileWidth != level.tileWidth)
+ return false;
+ if (cacheName != null ? !cacheName.equals(level.cacheName) : level.cacheName != null)
+ return false;
+ if (dataset != null ? !dataset.equals(level.dataset) : level.dataset != null)
+ return false;
+ if (formatSuffix != null ? !formatSuffix.equals(level.formatSuffix) : level.formatSuffix != null)
+ return false;
+ if (levelName != null ? !levelName.equals(level.levelName) : level.levelName != null)
+ return false;
+ if (service != null ? !service.equals(level.service) : level.service != null)
+ return false;
+ //noinspection RedundantIfStatement
+ if (tileDelta != null ? !tileDelta.equals(level.tileDelta) : level.tileDelta != null)
+ return false;
+
+ return true;
+ }
+
+ public int hashCode()
+ {
+ int result;
+ result = levelNumber;
+ result = 29 * result + (levelName != null ? levelName.hashCode() : 0);
+ result = 29 * result + (tileDelta != null ? tileDelta.hashCode() : 0);
+ result = 29 * result + tileWidth;
+ result = 29 * result + tileHeight;
+ result = 29 * result + (formatSuffix != null ? formatSuffix.hashCode() : 0);
+ result = 29 * result + (service != null ? service.hashCode() : 0);
+ result = 29 * result + (dataset != null ? dataset.hashCode() : 0);
+ result = 29 * result + (cacheName != null ? cacheName.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.path;
+ }
+}
diff --git a/gov/nasa/worldwind/LevelSet.java b/gov/nasa/worldwind/LevelSet.java
new file mode 100644
index 0000000..6ab97e4
--- /dev/null
+++ b/gov/nasa/worldwind/LevelSet.java
@@ -0,0 +1,234 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+import java.net.*;
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: LevelSet.java 1746 2007-05-06 17:21:30Z tgaskins $
+ */
+public class LevelSet extends WWObjectImpl
+{
+ private final Sector sector;
+ private final LatLon levelZeroTileDelta;
+ private final int numLevelZeroColumns;
+ private final java.util.ArrayList levels = new java.util.ArrayList();
+
+ public LevelSet(AVList params)
+ {
+ StringBuffer sb = new StringBuffer();
+
+ Object o = params.getValue(Level.LEVEL_ZERO_TILE_DELTA);
+ if (o == null || !(o instanceof LatLon))
+ sb.append(WorldWind.retrieveErrMsg("term.tileDelta"));
+
+ o = params.getValue(AVKey.SECTOR);
+ if (o == null || !(o instanceof Sector))
+ sb.append(WorldWind.retrieveErrMsg("term.sector"));
+
+ int numLevels = 0;
+ o = params.getValue(Level.NUM_LEVELS);
+ if (o == null || !(o instanceof Integer) || (numLevels = (Integer) o) < 1)
+ sb.append(WorldWind.retrieveErrMsg("term.numLevels"));
+
+ int numEmptyLevels = 0;
+ o = params.getValue(Level.NUM_EMPTY_LEVELS);
+ if (o == null || !(o instanceof Integer) || (numEmptyLevels = (Integer) o) < 0)
+ sb.append(WorldWind.retrieveErrMsg("term.numEMptyLevels"));
+
+ if (sb.length() > 0)
+ {
+ String message = WorldWind.retrieveErrMsg("layers.LevelSet.InvalidLevelDescriptorFields")
+ + " " + sb.toString();
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.levelZeroTileDelta = (LatLon) params.getValue(Level.LEVEL_ZERO_TILE_DELTA);
+ this.sector = (Sector) params.getValue(AVKey.SECTOR);
+
+ params = params.copy(); // copy so as not to modify the user's params
+
+ Level.TileURLBuilder tub = (Level.TileURLBuilder) params.getValue(Level.TILE_URL_BUILDER);
+ if (tub == null)
+ {
+ params.setValue(Level.TILE_URL_BUILDER, new Level.TileURLBuilder()
+ {
+ public URL getURL(Tile tile) throws MalformedURLException
+ {
+ StringBuffer sb = new StringBuffer(tile.getLevel().getService());
+ if (sb.lastIndexOf("?") != sb.length() - 1)
+ sb.append("?");
+ sb.append("T=");
+ sb.append(tile.getLevel().getDataset());
+ sb.append("&L=");
+ sb.append(tile.getLevel().getLevelName());
+ sb.append("&X=");
+ sb.append(tile.getColumn());
+ sb.append("&Y=");
+ sb.append(tile.getRow());
+
+ return new URL(sb.toString());
+ }
+ });
+ }
+
+ for (int i = 0; i < numLevels; i++)
+ {
+ params.setValue(Level.LEVEL_NAME, i < numEmptyLevels ? "" : Integer.toString(i - numEmptyLevels));
+ params.setValue(Level.LEVEL_NUMBER, i);
+
+ Angle latDelta = this.levelZeroTileDelta.getLatitude().divide(Math.pow(2, i));
+ Angle lonDelta = this.levelZeroTileDelta.getLongitude().divide(Math.pow(2, i));
+ params.setValue(Level.TILE_DELTA, new LatLon(latDelta, lonDelta));
+
+ this.levels.add(new Level(params));
+ }
+
+ this.numLevelZeroColumns =
+ (int) Math.round(this.sector.getDeltaLon().divide(this.levelZeroTileDelta.getLongitude()));
+ }
+
+ public LevelSet(LevelSet source)
+ {
+ if (source == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.LevelSetIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.levelZeroTileDelta = source.levelZeroTileDelta;
+ this.sector = source.sector;
+ this.numLevelZeroColumns = source.numLevelZeroColumns;
+
+ for (Level level : source.levels)
+ {
+ this.levels.add(level); // Levels are final, so it's safe to copy references.
+ }
+ }
+
+ public final Sector getSector()
+ {
+ return this.sector;
+ }
+
+ public final LatLon getLevelZeroTileDelta()
+ {
+ return this.levelZeroTileDelta;
+ }
+
+ public final ArrayList getLevels()
+ {
+ return this.levels;
+ }
+
+ public final Level getLevel(int levelNumber)
+ {
+ return (levelNumber >= 0 && levelNumber < this.levels.size()) ? this.levels.get(levelNumber) : null;
+ }
+
+ public final int getNumLevels()
+ {
+ return this.levels.size();
+ }
+
+ public final Level getFirstLevel()
+ {
+ return this.getLevel(0);
+ }
+
+ public final Level getLastLevel()
+ {
+ return this.getLevel(this.getNumLevels() - 1);
+ }
+
+ public final boolean isFinalLevel(int levelNum)
+ {
+ return levelNum == this.getNumLevels() - 1;
+ }
+
+ public final boolean isLevelEmpty(int levelNumber)
+ {
+ return this.levels.get(levelNumber).isEmpty();
+ }
+
+ private int numColumnsInLevel(Level level)
+ {
+ int levelDelta = level.getLevelNumber() - this.getFirstLevel().getLevelNumber();
+ double twoToTheN = Math.pow(2, levelDelta);
+ return (int) (twoToTheN * this.numLevelZeroColumns);
+ }
+
+ private long getTileNumber(Tile tile)
+ {
+ return tile.getRow() * this.numColumnsInLevel(tile.getLevel()) + tile.getColumn();
+ }
+
+ /**
+ * Instructs the level set that a tile is likely to be absent.
+ *
+ * @param tile The tile to mark as having an absent resource.
+ * @throws IllegalArgumentException if tile
is null
+ */
+ public final void markResourceAbsent(Tile tile)
+ {
+ if (tile == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.TileIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ tile.getLevel().markResourceAbsent(this.getTileNumber(tile));
+ }
+
+ /**
+ * Indicates whether a tile has been marked as absent.
+ *
+ * @param tile The tile in question.
+ * @return true
if the tile is marked absent, otherwise false
.
+ * @throws IllegalArgumentException if tile
is null
+ */
+ public final boolean isResourceAbsent(Tile tile)
+ {
+ if (tile == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.TileIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (tile.getLevel().isEmpty())
+ return true;
+
+ int tileNumber = tile.getRow() * this.numColumnsInLevel(tile.getLevel()) + tile.getColumn();
+ return tile.getLevel().isResourceAbsent(tileNumber);
+ }
+
+ /**
+ * Removes the absent-tile mark associated with a tile, if one is associatied.
+ *
+ * @param tile The tile to unmark.
+ * @throws IllegalArgumentException if tile
is null
+ */
+ public final void unmarkResourceAbsent(Tile tile)
+ {
+ if (tile == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.TileIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ tile.getLevel().unmarkResourceAbsent(this.getTileNumber(tile));
+ }
+}
diff --git a/gov/nasa/worldwind/Locatable.java b/gov/nasa/worldwind/Locatable.java
new file mode 100644
index 0000000..dca3a1a
--- /dev/null
+++ b/gov/nasa/worldwind/Locatable.java
@@ -0,0 +1,18 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+/**
+ * @author tag
+ * @version $Id: Locatable.java 1754 2007-05-06 23:19:22Z tgaskins $
+ */
+public interface Locatable
+{
+ public Position getPosition();
+}
diff --git a/gov/nasa/worldwind/Material.java b/gov/nasa/worldwind/Material.java
new file mode 100644
index 0000000..4c22855
--- /dev/null
+++ b/gov/nasa/worldwind/Material.java
@@ -0,0 +1,103 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import java.awt.Color;
+
+/**
+ * @author tag
+ * @version $Id: Material.java 1175 2007-03-07 21:16:55Z tgaskins $
+ */
+public class Material
+{
+ public static final int SPECULAR = 0;
+ public static final int DIFFUSE = 1;
+ public static final int AMBIENT = 2;
+ public static final int EMISSION = 3;
+
+ public static final Material WHITE = new Material(new Color(0.9f, 0.9f, 0.9f, 0.0f), new Color(0.8f, 0.8f, 0.8f,
+ 0.0f), new Color(0.2f, 0.2f, 0.2f, 0.0f), new Color(0.0f, 0.0f, 0.0f, 0.0f), 20f);
+
+ public static final Material RED = new Material(new Color(0.75f, 0.0f, 0.0f, 0.0f), new Color(0.8f, 0.0f, 0.0f,
+ 0.0f), new Color(0.2f, 0.0f, 0.0f, 0.0f), new Color(0.0f, 0.0f, 0.0f, 0.0f), 20f);
+
+ public static final Material GREEN = new Material(new Color(0.0f, 0.75f, 0.0f, 0.0f), new Color(0.0f, 0.8f, 0.0f,
+ 0.0f), new Color(0.0f, 0.2f, 0.0f, 0.0f), new Color(0.0f, 0.0f, 0.0f, 0.0f), 20f);
+
+ public static final Material BLUE = new Material(new Color(0.0f, 0.0f, 0.75f, 0.0f), new Color(0.0f, 0.0f, 0.8f,
+ 0.0f), new Color(0.0f, 0.0f, 0.2f, 0.0f), new Color(0.0f, 0.0f, 0.0f, 0.0f), 20f);
+
+ public static final Material YELLOW = new Material(new Color(0.75f, 0.75f, 0.55f, 0.0f), new Color(0.8f, 0.8f, 0.0f,
+ 0.0f), new Color(0.2f, 0.2f, 0.01f, 0.0f), new Color(0.0f, 0.0f, 0.0f, 0.0f), 20f);
+
+ private final Color specular;
+ private final Color diffuse;
+ private final Color ambient;
+ private final Color emission;
+ private final float shininess;
+
+ /**
+ * @param specular
+ * @param diffuse
+ * @param ambient
+ * @param emission
+ * @param shininess
+ * @throws IllegalArgumentException if specular
, diffuse
, ambient
or
+ * emission
is null
+ */
+ public Material(Color specular, Color diffuse, Color ambient, Color emission, float shininess)
+ {
+ if (specular == null || diffuse == null || ambient == null || emission == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.ColorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.specular = specular;
+ this.diffuse = diffuse;
+ this.ambient = ambient;
+ this.emission = emission;
+ this.shininess = shininess;
+ }
+
+ public Color getSpecular()
+ {
+ return this.specular;
+ }
+
+ public Color getDiffuse()
+ {
+ return this.diffuse;
+ }
+
+ public Color getAmbient()
+ {
+ return this.ambient;
+ }
+
+ public Color getEmission()
+ {
+ return this.emission;
+ }
+
+ public float getShininess()
+ {
+ return this.shininess;
+ }
+
+ public void apply(javax.media.opengl.GL gl, int face)
+ {
+ float[] rgba = new float[4];
+
+ gl.glMaterialfv(face, javax.media.opengl.GL.GL_SPECULAR, this.specular.getRGBComponents(rgba), 0);
+ gl.glMaterialfv(face, javax.media.opengl.GL.GL_DIFFUSE, this.diffuse.getRGBComponents(rgba), 0);
+ gl.glMaterialfv(face, javax.media.opengl.GL.GL_AMBIENT, this.ambient.getRGBComponents(rgba), 0);
+ gl.glMaterialf(face, javax.media.opengl.GL.GL_SHININESS, this.shininess);
+ gl.glMaterialfv(face, javax.media.opengl.GL.GL_EMISSION, this.emission.getRGBComponents(rgba), 0);
+ }
+}
diff --git a/gov/nasa/worldwind/MemoryCache.java b/gov/nasa/worldwind/MemoryCache.java
new file mode 100644
index 0000000..c6c147b
--- /dev/null
+++ b/gov/nasa/worldwind/MemoryCache.java
@@ -0,0 +1,174 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Eric Dalgliesh
+ * @version $Id: MemoryCache.java 403 2006-12-13 02:33:18Z ericdalgliesh $
+ */
+public interface MemoryCache /*extends gov.nasa.worldwind.MemoryCache*/
+{
+ /**
+ * retrieve an unordered Set
of the keys of the objects in this MemoryCache
.
+ *
+ * @return a Set
containing all the keys in the cache.
+ */
+ java.util.Set getKeySet();
+
+ /**
+ * Provides the interface for cache clients to be notified of key events. Currently the only key event is the
+ * removal of an entry from the cache. A client may need to know a removal instigated by the cache occurred in order
+ * to adjust its own state or to free resources associated with the removed entry.
+ */
+ public interface CacheListener
+ {
+ public void entryRemoved(Object key, Object clientObject);
+ }
+
+ /**
+ * Adds a new cacheListener
, which will be sent notification whenever an entry is removed from the
+ * cache.
+ *
+ * @param listener the new MemoryCache.CacheListener
+ */
+ void addCacheListener(CacheListener listener);
+
+ /**
+ * Removes a CacheListener
, notifications of events will no longer be sent to this listener.
+ *
+ * @param listener
+ */
+ void removeCacheListener(gov.nasa.worldwind.MemoryCache.CacheListener listener);
+
+ /**
+ * Discovers whether or not this cache contains the object referenced by key. Currently no interface exists
+ * to discover if an object resides in the cache by referencing itself.
+ *
+ * @param key the key which the object is referenced by.
+ * @return true if the key is found in the cache, false otherwise.
+ */
+ boolean contains(Object key);
+
+ /**
+ * Attempts to add the object clientObject
, with size objectSize
and referred to by
+ * key
to the cache. objectSize
is the size in bytes, but is not checked for accuracy.
+ * Returns whether or not the add was successful.
+ *
+ * Note that the size passed in may be used, rather than the real size of the object. In some implementations, the
+ * accuracy of the space used calls will depend on the collection of these sizes, rather than actual size.
+ *
+ * This method should be declared synchronized
when it is implemented.
+ *
+ * @param key an object used to reference the cached item
+ * @param clientObject the item to be cached
+ * @param objectSize the size of the item in bytes.
+ * @return true if object was added, false otherwise
+ */
+ boolean add(Object key, Object clientObject, long objectSize);
+
+ /**
+ * Attempts to add the Cacheable
object referenced by the key. No explicit size value is required as
+ * this method queries the Cacheable to discover the size.
+ *
+ * This method should be declared synchronized
when it is implemented.
+ *
+ * @param key
+ * @param clientObject
+ * @return true if object was added, false otherwise
+ * @see Cacheable
+ */
+ boolean add(Object key, Cacheable clientObject);
+
+ /**
+ * Remove an object from the MemoryCache referenced by key
. If the object is already absent, this
+ * method simply returns without indicating the absence.
+ *
+ * @param key an Object
used to represent the item to remove.
+ */
+ void remove(Object key);
+
+ /**
+ * Retrieves the requested item from the cache. If key
is null or the item is not found, this method
+ * returns null.
+ *
+ * @param key an Object
used to represent the item to retrieve
+ * @return the requested Object
if found, null otherwise
+ */
+ Object getObject(Object key);
+
+ /**
+ * Empties the cache. After calling clear()
on a MemoryCache
, calls relating to used
+ * capacity and number of items should return zero and the free capacity should be the maximum capacity.
+ *
+ * This method should be declared synchronized
when it is implemented and should notify all
+ * CacheListener
s of entries removed.
+ */
+ void clear();
+
+ /* *************************************************************************/
+ // capacity related accessors
+
+ /**
+ * Retrieve the number of items stored in the MemoryCache
.
+ *
+ * @return the number of items in the cache
+ */
+ int getNumObjects();
+
+ /**
+ * Retrieves the maximum size of the cache in bytes.
+ *
+ * @return the maximum size of the MemoryCache
in bytes.
+ */
+ long getCapacity();
+
+ /**
+ * Retrieves the amount of used MemoryCache
space. The value returned is in bytes.
+ *
+ * @return the long value of the number of bytes used by cached items.
+ */
+ long getUsedCapacity();
+
+ /**
+ * Retrieves the available space for storing new items.
+ *
+ * @return the long value of the remaining space for storing cached items.
+ */
+ long getFreeCapacity();
+
+ /**
+ * Retrieves the low water value of the MemoryCache
. When a MemoryCache
runs out of free
+ * space, it must remove some items if it wishes to add any more. It continues removing items until the low water
+ * level is reached. Not every MemoryCache
necessarily uses the low water system, so this may not
+ * return a useful value.
+ *
+ * @return the low water value of the MemoryCache
.
+ */
+ long getLowWater();
+
+ /* *******************************************************************************/
+ //capacity related mutators
+
+ /**
+ * Sets the new low water capacity value for this MemoryCache
. When a MemoryCache
runs out
+ * of free space, it must remove some items if it wishes to add any more. It continues removing items until the low
+ * water level is reached. Not every MemoryCache
necessarily uses the low water system, so this method
+ * may not have any actual effect in some implementations.
+ *
+ * @param loWater the new low water value in bytes
+ */
+ void setLowWater(long loWater);
+
+ /**
+ * Sets the maximum capacity for this cache
in bytes. This capacity has no impact on the number of
+ * items stored in the MemoryCache
, except that every item must have a positive size. Generally the
+ * used capacity is the total of the sizes of all stored items.
+ *
+ * @param capacity the new capacity in bytes
+ */
+ void setCapacity(long capacity);
+}
diff --git a/gov/nasa/worldwind/Model.java b/gov/nasa/worldwind/Model.java
new file mode 100644
index 0000000..2f5b13e
--- /dev/null
+++ b/gov/nasa/worldwind/Model.java
@@ -0,0 +1,40 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: Model.java 1561 2007-04-21 09:29:58Z tgaskins $
+ */
+public interface Model extends WWObject
+{
+ gov.nasa.worldwind.geom.Extent getExtent();
+
+ Globe getGlobe();
+
+ LayerList getLayers();
+
+ void setGlobe(Globe globe);
+
+ void setLayers(LayerList layers);
+
+ Tessellator getTessellator();
+
+ void setTessellator(Tessellator tessellator);
+
+ void setShowWireframeInterior(boolean show);
+
+ void setShowWireframeExterior(boolean show);
+
+ boolean isShowWireframeInterior();
+
+ boolean isShowWireframeExterior();
+
+ boolean isShowTessellationBoundingVolumes();
+
+ void setShowTessellationBoundingVolumes(boolean showTileBoundingVolumes);
+}
diff --git a/gov/nasa/worldwind/OrderedRenderable.java b/gov/nasa/worldwind/OrderedRenderable.java
new file mode 100644
index 0000000..65c5344
--- /dev/null
+++ b/gov/nasa/worldwind/OrderedRenderable.java
@@ -0,0 +1,16 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author tag
+ * @version $Id$
+ */
+public interface OrderedRenderable extends Renderable, Pickable
+{
+ double getDistanceFromEye();
+}
diff --git a/gov/nasa/worldwind/Pedestal.java b/gov/nasa/worldwind/Pedestal.java
new file mode 100644
index 0000000..997bc71
--- /dev/null
+++ b/gov/nasa/worldwind/Pedestal.java
@@ -0,0 +1,44 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+/**
+ * @author tag
+ * @version $Id$
+ */
+public class Pedestal extends UserFacingIcon
+{
+ private double spacingPixels = 2d;
+ private double scale = 1d;
+
+ public Pedestal(String iconPath, Position iconPosition)
+ {
+ super(iconPath, iconPosition);
+ }
+
+ public double getSpacingPixels()
+ {
+ return spacingPixels;
+ }
+
+ public void setSpacingPixels(double spacingPixels)
+ {
+ this.spacingPixels = spacingPixels;
+ }
+
+ public double getScale()
+ {
+ return scale;
+ }
+
+ public void setScale(double scale)
+ {
+ this.scale = scale;
+ }
+}
diff --git a/gov/nasa/worldwind/PickSupport.java b/gov/nasa/worldwind/PickSupport.java
new file mode 100644
index 0000000..7fde5e5
--- /dev/null
+++ b/gov/nasa/worldwind/PickSupport.java
@@ -0,0 +1,114 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+import com.sun.opengl.util.*;
+
+import javax.media.opengl.*;
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: PickSupport.java 1754 2007-05-06 23:19:22Z tgaskins $
+ */
+public class PickSupport
+{
+ private HashMap pickableObjects = new HashMap();
+
+ public void clearPickList()
+ {
+ this.pickableObjects.clear();
+ }
+
+ public void addPickableObject(int colorCode, Object o, Position position, boolean isTerrain)
+ {
+ this.pickableObjects.put(colorCode, new PickedObject(colorCode, o, position, isTerrain));
+ }
+
+ public void addPickableObject(int colorCode, Object o)
+ {
+ this.pickableObjects.put(colorCode, new PickedObject(colorCode, o));
+ }
+
+ public void addPickableObject(int colorCode, PickedObject po)
+ {
+ this.pickableObjects.put(colorCode, po);
+ }
+
+ public PickedObject getTopObject(DrawContext dc, java.awt.Point pickPoint, Layer layer)
+ {
+ if (this.pickableObjects.isEmpty())
+ return null;
+
+ int colorCode = this.getTopColor(dc, pickPoint);
+ if (colorCode == dc.getClearColor().getRGB())
+ return null;
+
+ PickedObject pickedObject = pickableObjects.get(colorCode);
+ if (pickedObject == null)
+ return null;
+
+ if (layer != null)
+ pickedObject.setParentLayer(layer);
+
+ return pickedObject;
+ }
+
+ public void resolvePick(DrawContext dc, java.awt.Point pickPoint, Layer layer)
+ {
+ PickedObject pickedObject = this.getTopObject(dc, pickPoint, layer);
+ if (pickedObject != null)
+ dc.addPickedObject(pickedObject);
+
+ this.clearPickList();
+ }
+
+ public int getTopColor(DrawContext dc, java.awt.Point pickPoint)
+ {
+ GL gl = dc.getGL();
+
+ int[] viewport = new int[4];
+ gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);
+
+ java.nio.ByteBuffer pixel = BufferUtil.newByteBuffer(3);
+ gl.glReadPixels(pickPoint.x, viewport[3] - pickPoint.y, 1, 1,
+ javax.media.opengl.GL.GL_RGB, GL.GL_UNSIGNED_BYTE, pixel);
+
+ java.awt.Color topColor = null;
+ try
+ {
+ topColor = new java.awt.Color(pixel.get(0) & 0xff, pixel.get(1) & 0xff, pixel.get(2) & 0xff, 0);
+ }
+ catch (Exception e)
+ {
+ String message = WorldWind.retrieveErrMsg("layers.InvalidPickColorRead");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ }
+
+ return topColor != null ? topColor.getRGB() : 0;
+ }
+
+ public void beginPicking(DrawContext dc)
+ {
+ javax.media.opengl.GL gl = dc.getGL();
+
+ gl.glPushAttrib(GL.GL_ENABLE_BIT);
+
+ gl.glDisable(GL.GL_DITHER);
+ gl.glDisable(GL.GL_LIGHTING);
+ gl.glDisable(GL.GL_FOG);
+ gl.glDisable(GL.GL_BLEND);
+ gl.glDisable(GL.GL_TEXTURE_2D);
+ }
+
+ public void endPicking(DrawContext dc)
+ {
+ dc.getGL().glPopAttrib();
+ }
+}
diff --git a/gov/nasa/worldwind/Pickable.java b/gov/nasa/worldwind/Pickable.java
new file mode 100644
index 0000000..939ec86
--- /dev/null
+++ b/gov/nasa/worldwind/Pickable.java
@@ -0,0 +1,16 @@
+package gov.nasa.worldwind;
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+
+/**
+ * @author lado
+ * @version $Id: Pickable Feb 4, 2007 11:46:48 PM
+ */
+public interface Pickable
+{
+ public void pick(DrawContext dc, java.awt.Point pickPoint);
+}
diff --git a/gov/nasa/worldwind/PickedObject.java b/gov/nasa/worldwind/PickedObject.java
new file mode 100644
index 0000000..4c52e69
--- /dev/null
+++ b/gov/nasa/worldwind/PickedObject.java
@@ -0,0 +1,131 @@
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+
+/**
+ * @author lado
+ * @version $Id: PickedObject Feb 5, 2007 12:47:00 AM
+ */
+public class PickedObject extends AVListImpl
+{
+ private final int colorCode;
+ private final Object userObject;
+ private boolean isOnTop = false;
+ private boolean isTerrain = false;
+
+ public PickedObject(int colorCode, Object userObject)
+ {
+ super();
+ this.colorCode = colorCode;
+ this.userObject = userObject;
+ this.isOnTop = false;
+ this.isTerrain = false;
+ }
+
+ public PickedObject(int colorCode, Object userObject, Position position, boolean isTerrain)
+ {
+ super();
+
+ this.colorCode = colorCode;
+ this.userObject = userObject;
+ this.isOnTop = false;
+ this.isTerrain = isTerrain;
+ this.setPosition(position);
+ }
+
+ public PickedObject(int colorCode, Object userObject, Angle lat, Angle lon, double elev, boolean isTerrain)
+ {
+ super();
+
+ this.colorCode = colorCode;
+ this.userObject = userObject;
+ this.isOnTop = false;
+ this.isTerrain = isTerrain;
+ this.setPosition(new Position(lat, lon, elev));
+ }
+
+ public int getColorCode()
+ {
+ return this.colorCode;
+ }
+
+ public Object getObject()
+ {
+ return userObject;
+ }
+
+ public void setOnTop()
+ {
+ this.isOnTop = true;
+ }
+
+ public boolean isOnTop()
+ {
+ return this.isOnTop;
+ }
+
+ public boolean isTerrain()
+ {
+ return this.isTerrain;
+ }
+
+ public void setParentLayer(Layer layer)
+ {
+ this.setValue(AVKey.PICKED_OBJECT_PARENT_LAYER, layer);
+ }
+
+ public Layer getParentLayer()
+ {
+ return (Layer) this.getValue(AVKey.PICKED_OBJECT_PARENT_LAYER);
+ }
+
+ public void setPosition(Position position)
+ {
+ this.setValue(AVKey.POSITION, position);
+ }
+
+ public Position getPosition()
+ {
+ return (Position) this.getValue(AVKey.POSITION);
+ }
+
+ public boolean hasPosition()
+ {
+ return this.hasKey(AVKey.POSITION);
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ PickedObject that = (PickedObject) o;
+
+ if (colorCode != that.colorCode)
+ return false;
+ if (isOnTop != that.isOnTop)
+ return false;
+ //noinspection RedundantIfStatement
+ if (userObject != null ? !userObject.equals(that.userObject) : that.userObject != null)
+ return false;
+
+ return true;
+ }
+
+ public int hashCode()
+ {
+ int result;
+ result = colorCode;
+ result = 31 * result + (userObject != null ? userObject.hashCode() : 0);
+ result = 31 * result + (isOnTop ? 1 : 0);
+ return result;
+ }
+}
diff --git a/gov/nasa/worldwind/PickedObjectList.java b/gov/nasa/worldwind/PickedObjectList.java
new file mode 100644
index 0000000..ee494f0
--- /dev/null
+++ b/gov/nasa/worldwind/PickedObjectList.java
@@ -0,0 +1,55 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author tag
+ * @version $Id$
+ */
+public class PickedObjectList extends java.util.ArrayList
+{
+ public PickedObjectList()
+ {
+ }
+
+ public PickedObjectList(PickedObjectList list) // clone a shallow copy
+ {
+ super(list);
+ }
+
+ public PickedObject getTopObject()
+ {
+ int size = this.size();
+
+ if(1 < size)
+ {
+ for (PickedObject po : this)
+ {
+ if (po.isOnTop())
+ return po;
+ }
+ }
+
+ if(0 < size)
+ { // if we are here, then no objects were mark as 'top'
+ return this.get(0);
+ }
+
+ return null;
+ }
+
+ public PickedObject getTerrainObject()
+ {
+ for (PickedObject po : this)
+ {
+ if (po.isTerrain())
+ return po;
+ }
+
+ return null;
+ }
+}
diff --git a/gov/nasa/worldwind/PlaceName.java b/gov/nasa/worldwind/PlaceName.java
new file mode 100644
index 0000000..9fc6b11
--- /dev/null
+++ b/gov/nasa/worldwind/PlaceName.java
@@ -0,0 +1,41 @@
+/*
+Copyright (C) 2001, 2006 United States Government as represented by
+the Administrator of the National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+import java.awt.*;
+
+/**
+ * @author dcollins
+ * @version $Id: PlaceName.java 1759 2007-05-07 19:27:49Z dcollins $
+ */
+public interface PlaceName
+{
+ String getText();
+
+ void setText(String text);
+
+ Position getPosition();
+
+ void setPosition(Position position);
+
+ Font getFont();
+
+ void setFont(Font font);
+
+ Color getColor();
+
+ void setColor(Color color);
+
+ boolean isVisible();
+
+ void setVisible(boolean visible);
+
+ WWIcon getIcon();
+
+ void setIcon(WWIcon icon);
+}
diff --git a/gov/nasa/worldwind/PlaceNameRenderer.java b/gov/nasa/worldwind/PlaceNameRenderer.java
new file mode 100644
index 0000000..ab50781
--- /dev/null
+++ b/gov/nasa/worldwind/PlaceNameRenderer.java
@@ -0,0 +1,333 @@
+/*
+Copyright (C) 2001, 2006 United States Government as represented by
+the Administrator of the National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import com.sun.opengl.util.j2d.*;
+import gov.nasa.worldwind.geom.*;
+import gov.nasa.worldwind.geom.Point;
+
+import javax.media.opengl.*;
+import javax.media.opengl.glu.*;
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.*;
+import java.util.logging.Level;
+
+/**
+ * @author dcollins
+ * @version $Id: PlaceNameRenderer.java 1774 2007-05-08 01:03:37Z dcollins $
+ */
+public class PlaceNameRenderer implements Disposable
+{
+ private static final Font defaultFont = Font.decode("Arial-12-PLAIN");
+ private static final Color defaultColor = Color.white;
+ private final Map textRenderers = new HashMap();
+ private TextRenderer lastTextRenderer = null;
+ private final GLU glu = new GLU();
+
+ public PlaceNameRenderer()
+ {
+ }
+
+ public void dispose()
+ {
+ for (TextRenderer textRenderer : textRenderers.values())
+ {
+ if (textRenderer != null)
+ textRenderer.dispose();
+ }
+ }
+
+ public void render(DrawContext dc, Iterator placeNames, boolean enableDepthTest)
+ {
+ this.drawMany(dc, placeNames, enableDepthTest);
+ }
+
+ public void render(DrawContext dc, PlaceName placeName, Point placeNamePoint, boolean enableDepthTest)
+ {
+ if (!isNameValid(placeName, false))
+ return;
+
+ this.drawOne(dc, placeName, placeNamePoint, enableDepthTest);
+ }
+
+ private void drawMany(DrawContext dc, Iterator placeNames, boolean enableDepthTest)
+ {
+ if (dc == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (dc.getVisibleSector() == null)
+ return;
+
+ SectorGeometryList geos = dc.getSurfaceGeometry();
+ if (geos == null)
+ return;
+
+ if (placeNames == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.Iterator");
+ WorldWind.logger().log(Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (!placeNames.hasNext())
+ return;
+
+ Frustum frustumInModelCoords = dc.getView().getFrustumInModelCoordinates();
+ double horizon = dc.getView().computeHorizonDistance();
+
+ this.beginDrawNames(dc, enableDepthTest);
+
+ while (placeNames.hasNext())
+ {
+ PlaceName placeName = placeNames.next();
+ if (!isNameValid(placeName, true))
+ continue;
+
+ if (!placeName.isVisible())
+ continue;
+
+ Angle lat = placeName.getPosition().getLatitude();
+ Angle lon = placeName.getPosition().getLongitude();
+
+ if (!dc.getVisibleSector().contains(lat, lon))
+ continue;
+
+ Point namePoint = geos.getSurfacePoint(lat, lon, placeName.getPosition().getElevation());
+ if (namePoint == null)
+ continue;
+
+ double eyeDistance = dc.getView().getEyePoint().distanceTo(namePoint);
+ if (eyeDistance > horizon)
+ continue;
+
+ if (!frustumInModelCoords.contains(namePoint))
+ continue;
+
+ this.drawName(dc, placeName, namePoint, enableDepthTest);
+ }
+
+ this.endDrawNames(dc);
+ }
+
+ private void drawOne(DrawContext dc, PlaceName placeName, Point namePoint, boolean enableDepthTest)
+ {
+ if (dc == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (dc.getView() == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.ViewIsNull");
+ WorldWind.logger().log(Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (dc.getVisibleSector() == null)
+ return;
+
+ SectorGeometryList geos = dc.getSurfaceGeometry();
+ if (geos == null)
+ return;
+
+ if (!placeName.isVisible())
+ return;
+
+ if (namePoint == null)
+ {
+ if (placeName.getPosition() == null)
+ return;
+
+ Angle lat = placeName.getPosition().getLatitude();
+ Angle lon = placeName.getPosition().getLongitude();
+
+ if (!dc.getVisibleSector().contains(lat, lon))
+ return;
+
+ namePoint = geos.getSurfacePoint(lat, lon, placeName.getPosition().getElevation());
+ if (namePoint == null)
+ return;
+ }
+
+ double horizon = dc.getView().computeHorizonDistance();
+ double eyeDistance = dc.getView().getEyePoint().distanceTo(namePoint);
+ if (eyeDistance > horizon)
+ return;
+
+ if (!dc.getView().getFrustumInModelCoordinates().contains(namePoint))
+ return;
+
+ this.beginDrawNames(dc, enableDepthTest);
+ this.drawName(dc, placeName, namePoint, enableDepthTest);
+ this.endDrawNames(dc);
+ }
+
+ private static boolean isNameValid(PlaceName placeName, boolean checkPosition)
+ {
+ if (placeName == null || placeName.getText() == null)
+ return false;
+
+ //noinspection RedundantIfStatement
+ if (checkPosition && placeName.getPosition() == null)
+ return false;
+
+ return true;
+ }
+
+ private final int[] viewportArray = new int[4];
+
+ private void beginDrawNames(DrawContext dc, boolean enableDepthTest)
+ {
+ GL gl = dc.getGL();
+ int attribBits =
+ GL.GL_ENABLE_BIT
+ | GL.GL_COLOR_BUFFER_BIT // for alpha test func and ref, and blend
+ | GL.GL_CURRENT_BIT // for current color
+ | GL.GL_TRANSFORM_BIT // for modelview and perspective
+ | (enableDepthTest ? GL.GL_VIEWPORT_BIT | GL.GL_DEPTH_BUFFER_BIT : 0); // for depth func, depth range
+ gl.glPushAttrib(attribBits);
+
+ gl.glGetIntegerv(GL.GL_VIEWPORT, viewportArray, 0);
+ gl.glMatrixMode(GL.GL_PROJECTION);
+ gl.glPushMatrix();
+ gl.glLoadIdentity();
+ glu.gluOrtho2D(0, viewportArray[2], 0, viewportArray[3]);
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPushMatrix();
+ gl.glLoadIdentity();
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPushMatrix();
+ gl.glLoadIdentity();
+
+ // Enable the depth test but don't write to the depth buffer.
+ if (enableDepthTest)
+ {
+ gl.glEnable(GL.GL_DEPTH_TEST);
+ gl.glDepthFunc(GL.GL_LESS);
+ gl.glDepthMask(false);
+ }
+ else
+ {
+ gl.glDisable(GL.GL_DEPTH_TEST);
+ }
+ // Suppress polygon culling.
+ gl.glDisable(GL.GL_CULL_FACE);
+ // Suppress any fully transparent image pixels
+ final float ALPHA_EPSILON = 0.001f;
+ gl.glEnable(GL.GL_ALPHA_TEST);
+ gl.glAlphaFunc(GL.GL_GREATER, ALPHA_EPSILON);
+ }
+
+ private void endDrawNames(DrawContext dc)
+ {
+ if (this.lastTextRenderer != null)
+ {
+ this.lastTextRenderer.end3DRendering();
+ this.lastTextRenderer = null;
+ }
+
+ GL gl = dc.getGL();
+
+ gl.glMatrixMode(GL.GL_PROJECTION);
+ gl.glPopMatrix();
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPopMatrix();
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPopMatrix();
+
+ gl.glPopAttrib();
+ }
+
+ private Point drawName(DrawContext dc, PlaceName name, Point namePoint, boolean enableDepthTest)
+ {
+ if (namePoint == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(Level.FINE, msg);
+ return null;
+ }
+
+ final String text = name.getText();
+ if (text == null)
+ return null;
+
+ final Point screenPoint = dc.getView().project(namePoint);
+ if (screenPoint == null)
+ return null;
+
+ if (enableDepthTest)
+ this.setDepthFunc(dc, screenPoint);
+
+ Font font = name.getFont();
+ if (font == null)
+ font = defaultFont;
+
+ TextRenderer textRenderer = this.textRenderers.get(font);
+ if (textRenderer == null)
+ textRenderer = this.initializeTextRenderer(font);
+ if (textRenderer != this.lastTextRenderer)
+ {
+ if (this.lastTextRenderer != null)
+ this.lastTextRenderer.end3DRendering();
+ textRenderer.begin3DRendering();
+ this.lastTextRenderer = textRenderer;
+ }
+
+ Rectangle2D nameBound = textRenderer.getBounds(text);
+ int x = (int) (screenPoint.x() - nameBound.getWidth() / 2d);
+ int y = (int) screenPoint.y();
+
+ Color color = name.getColor();
+ if (color == null)
+ color = defaultColor;
+
+ this.setBackgroundColor(textRenderer, color);
+ textRenderer.draw(text, x + 1, y - 1);
+ textRenderer.setColor(color);
+ textRenderer.draw(text, x, y);
+
+ return screenPoint;
+ }
+
+ private void setDepthFunc(DrawContext dc, Point screenPoint)
+ {
+ double depth = screenPoint.z() - 8d * 0.00048875809d;
+ depth = (depth < 0) ? 0 : ((depth > 1) ? 1 : depth);
+ dc.getGL().glDepthRange(depth, depth);
+ }
+
+ private final float[] compArray = new float[4];
+
+ private void setBackgroundColor(TextRenderer textRenderer, Color color)
+ {
+ Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), compArray);
+ if (compArray[2] > 0.5)
+ textRenderer.setColor(0, 0, 0, 0.7f);
+ else
+ textRenderer.setColor(1, 1, 1, 0.7f);
+ }
+
+ private TextRenderer initializeTextRenderer(Font font)
+ {
+ TextRenderer textRenderer = new TextRenderer(font, true, true);
+ TextRenderer oldTextRenderer;
+ oldTextRenderer = this.textRenderers.put(font, textRenderer);
+ if (oldTextRenderer != null)
+ oldTextRenderer.dispose();
+ return textRenderer;
+ }
+
+ public String toString()
+ {
+ return WorldWind.retrieveErrMsg("layers.PlaceNameLayer.Name");
+ }
+}
diff --git a/gov/nasa/worldwind/PlaceNameService.java b/gov/nasa/worldwind/PlaceNameService.java
new file mode 100644
index 0000000..93af36d
--- /dev/null
+++ b/gov/nasa/worldwind/PlaceNameService.java
@@ -0,0 +1,391 @@
+/*
+Copyright (C) 2001, 2006 United States Government as represented by
+the Administrator of the National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+/**
+ * @author Paul Collins
+ * @version $Id: PlaceNameService.java 1759 2007-05-07 19:27:49Z dcollins $
+ */
+public class PlaceNameService
+{
+ // Data retrieval and caching attributes.
+ private final String service;
+ private final String dataset;
+ private final String fileCachePath;
+ private static final String FORMAT_SUFFIX = ".xml.gz";
+ // Geospatial attributes.
+ private final Sector sector;
+ private final LatLon tileDelta;
+ private Extent extent = null;
+ private double extentVerticalExaggeration = Double.MIN_VALUE;
+ // Display attributes.
+ private final java.awt.Font font;
+ private boolean enabled;
+ private java.awt.Color color;
+ private double minDisplayDistance;
+ private double maxDisplayDistance;
+ private int numColumns;
+
+ private static final int MAX_ABSENT_TILE_TRIES = 2;
+ private static final int MIN_ABSENT_TILE_CHECK_INTERVAL = 10000;
+ private final AbsentResourceList absentTiles = new AbsentResourceList(MAX_ABSENT_TILE_TRIES,
+ MIN_ABSENT_TILE_CHECK_INTERVAL);
+
+ /**
+ * @param service
+ * @param dataset
+ * @param fileCachePath
+ * @param sector
+ * @param tileDelta
+ * @param font
+ * @throws IllegalArgumentException if any parameter is null
+ */
+ public PlaceNameService(String service, String dataset, String fileCachePath, Sector sector, LatLon tileDelta,
+ java.awt.Font font)
+ {
+ // Data retrieval and caching attributes.
+ this.service = service;
+ this.dataset = dataset;
+ this.fileCachePath = fileCachePath;
+ // Geospatial attributes.
+ this.sector = sector;
+ this.tileDelta = tileDelta;
+ // Display attributes.
+ this.font = font;
+ this.enabled = true;
+ this.color = java.awt.Color.white;
+ this.minDisplayDistance = Double.MIN_VALUE;
+ this.maxDisplayDistance = Double.MAX_VALUE;
+
+ String message = this.validate();
+ if (message != null)
+ {
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.numColumns = this.numColumnsInLevel();
+ }
+
+ /**
+ * @param row
+ * @param column
+ * @return
+ * @throws IllegalArgumentException if either row
or column
is less than zero
+ */
+ public String createFileCachePathFromTile(int row, int column)
+ {
+ if (row < 0 || column < 0)
+ {
+ String message = WorldWind.retrieveErrMsg("PlaceNameService.RowOrColumnOutOfRange");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ StringBuilder sb = new StringBuilder(this.fileCachePath);
+ sb.append(java.io.File.separator).append(this.dataset);
+ sb.append(java.io.File.separator).append(row);
+ sb.append(java.io.File.separator).append(row).append('_').append(column);
+
+ if (FORMAT_SUFFIX.charAt(0) != '.')
+ sb.append('.');
+ sb.append(FORMAT_SUFFIX);
+
+ String path = sb.toString();
+ return path.replaceAll("[:*?<>|]", "");
+ }
+
+ private int numColumnsInLevel()
+ {
+ int firstCol = Tile.computeColumn(this.tileDelta.getLongitude(), sector.getMinLongitude());
+ int lastCol = Tile.computeColumn(this.tileDelta.getLongitude(),
+ sector.getMaxLongitude().subtract(this.tileDelta.getLongitude()));
+
+ return lastCol - firstCol + 1;
+ }
+
+ public long getTileNumber(int row, int column)
+ {
+ return row * this.numColumns + column;
+ }
+
+ /**
+ * @param sector
+ * @return
+ * @throws java.net.MalformedURLException
+ * @throws IllegalArgumentException if sector
is null
+ */
+ public java.net.URL createServiceURLFromSector(Sector sector) throws java.net.MalformedURLException
+ {
+ if (sector == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.SectorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ StringBuilder sb = new StringBuilder(this.service);
+ if (sb.charAt(sb.length() - 1) != '?')
+ sb.append('?');
+ sb.append("TypeName=").append(dataset);
+ sb.append("&Request=GetFeature");
+ sb.append("&Service=WFS");
+ sb.append("&OUTPUTFORMAT=GML2-GZIP");
+ sb.append("&BBOX=");
+ sb.append(sector.getMinLongitude().getDegrees()).append(',');
+ sb.append(sector.getMinLatitude().getDegrees()).append(',');
+ sb.append(sector.getMaxLongitude().getDegrees()).append(',');
+ sb.append(sector.getMaxLatitude().getDegrees());
+ return new java.net.URL(sb.toString());
+ }
+
+ public synchronized final PlaceNameService deepCopy()
+ {
+ PlaceNameService copy = new PlaceNameService(this.service, this.dataset, this.fileCachePath, this.sector,
+ this.tileDelta,
+ this.font);
+ copy.enabled = this.enabled;
+ copy.color = this.color;
+ copy.minDisplayDistance = this.minDisplayDistance;
+ copy.maxDisplayDistance = this.maxDisplayDistance;
+ return copy;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || this.getClass() != o.getClass())
+ return false;
+
+ final PlaceNameService other = (PlaceNameService) o;
+
+ if (this.service != null ? !this.service.equals(other.service) : other.service != null)
+ return false;
+ if (this.dataset != null ? !this.dataset.equals(other.dataset) : other.dataset != null)
+ return false;
+ if (this.fileCachePath != null ? !this.fileCachePath.equals(other.fileCachePath) : other.fileCachePath != null)
+ return false;
+ if (this.sector != null ? !this.sector.equals(other.sector) : other.sector != null)
+ return false;
+ if (this.tileDelta != null ? !this.tileDelta.equals(other.tileDelta) : other.tileDelta != null)
+ return false;
+ if (this.font != null ? !this.font.equals(other.font) : other.font != null)
+ return false;
+ if (this.color != null ? !this.color.equals(other.color) : other.color != null)
+ return false;
+ if (this.minDisplayDistance != other.minDisplayDistance)
+ return false;
+ //noinspection RedundantIfStatement
+ if (this.maxDisplayDistance != other.maxDisplayDistance)
+ return false;
+
+ return true;
+ }
+
+ public synchronized final java.awt.Color getColor()
+ {
+ return this.color;
+ }
+
+ public final String getDataset()
+ {
+ return this.dataset;
+ }
+
+ /**
+ * @param dc
+ * @return
+ * @throws IllegalArgumentException if dc
is null
+ */
+ public final Extent getExtent(DrawContext dc)
+ {
+ if (dc == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (this.extent == null || this.extentVerticalExaggeration != dc.getVerticalExaggeration())
+ {
+ this.extentVerticalExaggeration = dc.getVerticalExaggeration();
+ this.extent = Sector.computeBoundingCylinder(dc.getGlobe(), this.extentVerticalExaggeration, this.sector);
+ }
+
+ return extent;
+ }
+
+ public final String getFileCachePath()
+ {
+ return this.fileCachePath;
+ }
+
+ public final java.awt.Font getFont()
+ {
+ return this.font;
+ }
+
+ public synchronized final double getMaxDisplayDistance()
+ {
+ return this.maxDisplayDistance;
+ }
+
+ public synchronized final double getMinDisplayDistance()
+ {
+ return this.minDisplayDistance;
+ }
+
+ public final LatLon getTileDelta()
+ {
+ return tileDelta;
+ }
+
+ public final Sector getSector()
+ {
+ return this.sector;
+ }
+
+ public final String getService()
+ {
+ return this.service;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result;
+ result = (service != null ? service.hashCode() : 0);
+ result = 29 * result + (this.dataset != null ? this.dataset.hashCode() : 0);
+ result = 29 * result + (this.fileCachePath != null ? this.fileCachePath.hashCode() : 0);
+ result = 29 * result + (this.sector != null ? this.sector.hashCode() : 0);
+ result = 29 * result + (this.tileDelta != null ? this.tileDelta.hashCode() : 0);
+ result = 29 * result + (this.font != null ? this.font.hashCode() : 0);
+ result = 29 * result + (this.color != null ? this.color.hashCode() : 0);
+ result = 29 * result + ((Double) minDisplayDistance).hashCode();
+ result = 29 * result + ((Double) maxDisplayDistance).hashCode();
+ return result;
+ }
+
+ public synchronized final boolean isEnabled()
+ {
+ return this.enabled;
+ }
+
+ /**
+ * @param color
+ * @throws IllegalArgumentException if color
is null
+ */
+ public synchronized final void setColor(java.awt.Color color)
+ {
+ if (color == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.ColorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.color = color;
+ }
+
+ public synchronized final void setEnabled(boolean enabled)
+ {
+ this.enabled = enabled;
+ }
+
+ /**
+ * @param maxDisplayDistance
+ * @throws IllegalArgumentException if maxDisplayDistance
is less than the current minimum display
+ * distance
+ */
+ public synchronized final void setMaxDisplayDistance(double maxDisplayDistance)
+ {
+ if (maxDisplayDistance < this.minDisplayDistance)
+ {
+ String message = WorldWind.retrieveErrMsg("PlaceNameService.MaxDisplayDistanceLessThanMinDisplayDistance");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.maxDisplayDistance = maxDisplayDistance;
+ }
+
+ /**
+ * @param minDisplayDistance
+ * @throws IllegalArgumentException if minDisplayDistance
is less than the current maximum display
+ * distance
+ */
+ public synchronized final void setMinDisplayDistance(double minDisplayDistance)
+ {
+ if (minDisplayDistance > this.maxDisplayDistance)
+ {
+ String message = WorldWind.retrieveErrMsg("PlaceNameService.MinDisplayDistanceGrtrThanMaxDisplayDistance");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.minDisplayDistance = minDisplayDistance;
+ }
+
+ public synchronized final void markResourceAbsent(long tileNumber)
+ {
+ this.absentTiles.markResourceAbsent(tileNumber);
+ }
+
+ public synchronized final boolean isResourceAbsent(long resourceNumber)
+ {
+ return this.absentTiles.isResourceAbsent(resourceNumber);
+ }
+
+ public synchronized final void unmarkResourceAbsent(long tileNumber)
+ {
+ this.absentTiles.unmarkResourceAbsent(tileNumber);
+ }
+
+ /**
+ * Determines if this PlaceNameService'
constructor arguments are valid.
+ *
+ * @return null if valid, otherwise a String
containing a description of why it is invalid.
+ */
+ public final String validate()
+ {
+ String msg = "";
+ if (this.service == null)
+ {
+ msg += WorldWind.retrieveErrMsg("nullValue.ServiceIsNull") + ", ";
+ }
+ if (this.dataset == null)
+ {
+ msg += WorldWind.retrieveErrMsg("nullValue.DataSetIsNull") + ", ";
+ }
+ if (this.fileCachePath == null)
+ {
+ msg += WorldWind.retrieveErrMsg("nullValue.FileCachePathIsNull") + ", ";
+ }
+ if (this.sector == null)
+ {
+ msg += WorldWind.retrieveErrMsg("nullValue.SectorIsNull") + ", ";
+ }
+ if (this.tileDelta == null)
+ {
+ msg += WorldWind.retrieveErrMsg("nullValue.TileDeltaIsNull") + ", ";
+ }
+ if (this.font == null)
+ {
+ msg += WorldWind.retrieveErrMsg("nullValue.FontIsNull") + ", ";
+ }
+
+ if (msg.length() == 0)
+ {
+ return null;
+ }
+
+ return msg;
+ }
+}
diff --git a/gov/nasa/worldwind/PlaceNameServiceSet.java b/gov/nasa/worldwind/PlaceNameServiceSet.java
new file mode 100644
index 0000000..e42ffbb
--- /dev/null
+++ b/gov/nasa/worldwind/PlaceNameServiceSet.java
@@ -0,0 +1,81 @@
+/*
+Copyright (C) 2001, 2006 United States Government as represented by
+the Administrator of the National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import java.util.*;
+
+/**
+ * @author Paul Collins
+ * @version $Id: PlaceNameServiceSet.java 1759 2007-05-07 19:27:49Z dcollins $
+ */
+public class PlaceNameServiceSet
+{
+ private final List serviceList = new LinkedList();
+
+ public PlaceNameServiceSet()
+ {
+ }
+
+ /**
+ * @param placeNameService
+ * @param replace
+ * @return
+ * @throws IllegalArgumentException if placeNameService
is null
+ */
+ public boolean addService(PlaceNameService placeNameService, boolean replace)
+ {
+ if (placeNameService == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PlaceNameServiceIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ for (int i = 0; i < this.serviceList.size(); i++)
+ {
+ final PlaceNameService other = this.serviceList.get(i);
+ if (placeNameService.getService().equals(other.getService()) && placeNameService.getDataset().equals(
+ other.getDataset()))
+ {
+ if (replace)
+ {
+ this.serviceList.set(i, placeNameService);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ this.serviceList.add(placeNameService);
+ return true;
+ }
+
+ public final PlaceNameServiceSet deepCopy()
+ {
+ PlaceNameServiceSet copy = new PlaceNameServiceSet();
+
+ // Creates a deep copy of this.serviceList in copy.serviceList.
+ for (int i = 0; i < this.serviceList.size(); i++)
+ {
+ copy.serviceList.add(i, this.serviceList.get(i).deepCopy());
+ }
+
+ return copy;
+ }
+
+ public final int getServiceCount()
+ {
+ return this.serviceList.size();
+ }
+
+ public final PlaceNameService getService(int index)
+ {
+ return this.serviceList.get(index);
+ }
+}
diff --git a/gov/nasa/worldwind/PositionEvent.java b/gov/nasa/worldwind/PositionEvent.java
new file mode 100644
index 0000000..4dedcb6
--- /dev/null
+++ b/gov/nasa/worldwind/PositionEvent.java
@@ -0,0 +1,55 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+import java.util.*;
+import java.awt.event.*;
+
+/**
+ * @author tag
+ * @version $Id: PositionEvent.java 1764 2007-05-07 20:01:57Z tgaskins $
+ */
+public class PositionEvent extends EventObject
+{
+ private final MouseEvent mouseEvent;
+ private final Position position;
+ private final Position previousPosition;
+
+ public PositionEvent(Object source, MouseEvent mouseEvent, Position previousPosition, Position position)
+ {
+ super(source);
+ this.mouseEvent = mouseEvent;
+ this.position = position;
+ this.previousPosition = previousPosition;
+ }
+
+ public Position getPosition()
+ {
+ return position;
+ }
+
+ public Position getPreviousPosition()
+ {
+ return previousPosition;
+ }
+
+ public MouseEvent getMouseEvent()
+ {
+ return mouseEvent;
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.getClass().getName() + " "
+ + (this.previousPosition != null ? this.previousPosition : "null")
+ + " --> "
+ + (this.position != null ? this.position : "null");
+ }
+}
diff --git a/gov/nasa/worldwind/PositionListener.java b/gov/nasa/worldwind/PositionListener.java
new file mode 100644
index 0000000..0bb9add
--- /dev/null
+++ b/gov/nasa/worldwind/PositionListener.java
@@ -0,0 +1,18 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: PositionListener.java 1757 2007-05-07 09:17:09Z tgaskins $
+ */
+public interface PositionListener extends EventListener
+{
+ public void moved(PositionEvent event);
+}
diff --git a/gov/nasa/worldwind/Renderable.java b/gov/nasa/worldwind/Renderable.java
new file mode 100644
index 0000000..cd03d57
--- /dev/null
+++ b/gov/nasa/worldwind/Renderable.java
@@ -0,0 +1,25 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: Renderable.java 403 2006-12-13 02:33:18Z ericdalgliesh $
+ */
+public interface Renderable
+{
+ /**
+ * Causes this Renderable
to render itself using the DrawContext
provided. The
+ * DrawContext
provides the elevation model, openGl instance, globe and other information required for
+ * drawing. It is recommended that the DrawContext
is non-null as most implementations do not support
+ * null DrawContext
s.
+ *
+ * @param dc the DrawContext
to be used
+ * @see DrawContext
+ */
+ public void render(DrawContext dc);
+}
diff --git a/gov/nasa/worldwind/RenderingEvent.java b/gov/nasa/worldwind/RenderingEvent.java
new file mode 100644
index 0000000..1eac84b
--- /dev/null
+++ b/gov/nasa/worldwind/RenderingEvent.java
@@ -0,0 +1,39 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: RenderingEvent.java 1764 2007-05-07 20:01:57Z tgaskins $
+ */
+public class RenderingEvent extends EventObject
+{
+ public static final String BEGIN = "gov.nasa.worldwind.RenderingEvent.BeginStage";
+ public static final String END = "gov.nasa.worldwind.RenderingEvent.EndStage";
+
+ private String stage;
+
+ public RenderingEvent(Object source, String stage)
+ {
+ super(source);
+ this.stage = stage;
+ }
+
+ public String getStage()
+ {
+ return this.stage != null ? this.stage : "gov.nasa.worldwind.RenderingEvent.UnknownStage";
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.getClass().getName() + " "
+ + this.stage != null ? this.stage : WorldWind.retrieveErrMsg("generic.unknown");
+ }
+}
diff --git a/gov/nasa/worldwind/RenderingListener.java b/gov/nasa/worldwind/RenderingListener.java
new file mode 100644
index 0000000..d9ac0b5
--- /dev/null
+++ b/gov/nasa/worldwind/RenderingListener.java
@@ -0,0 +1,18 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: RenderingListener.java 1757 2007-05-07 09:17:09Z tgaskins $
+ */
+public interface RenderingListener extends EventListener
+{
+ public void stageChanged(RenderingEvent event);
+}
diff --git a/gov/nasa/worldwind/RetrievalFuture.java b/gov/nasa/worldwind/RetrievalFuture.java
new file mode 100644
index 0000000..ac2f244
--- /dev/null
+++ b/gov/nasa/worldwind/RetrievalFuture.java
@@ -0,0 +1,16 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: RetrievalFuture.java 403 2006-12-13 02:33:18Z ericdalgliesh $
+ */
+public interface RetrievalFuture extends java.util.concurrent.Future
+{
+ public Retriever getRetriever();
+}
diff --git a/gov/nasa/worldwind/RetrievalPostProcessor.java b/gov/nasa/worldwind/RetrievalPostProcessor.java
new file mode 100644
index 0000000..1b55a1b
--- /dev/null
+++ b/gov/nasa/worldwind/RetrievalPostProcessor.java
@@ -0,0 +1,16 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: RetrievalPostProcessor.java 403 2006-12-13 02:33:18Z ericdalgliesh $
+ */
+public interface RetrievalPostProcessor
+{
+ public java.nio.ByteBuffer run(Retriever retriever);
+}
diff --git a/gov/nasa/worldwind/RetrievalService.java b/gov/nasa/worldwind/RetrievalService.java
new file mode 100644
index 0000000..f407fe1
--- /dev/null
+++ b/gov/nasa/worldwind/RetrievalService.java
@@ -0,0 +1,30 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: RetrievalService.java 693 2007-01-31 19:11:01Z tgaskins $
+ */
+public interface RetrievalService extends WWObject
+{
+ RetrievalFuture runRetriever(Retriever retriever);
+
+ RetrievalFuture runRetriever(Retriever retriever, double priority);
+
+ void setRetrieverPoolSize(int poolSize);
+
+ int getRetrieverPoolSize();
+
+ boolean hasActiveTasks();
+
+ boolean isFull();
+
+ boolean contains(gov.nasa.worldwind.Retriever retriever);
+
+ int getNumRetrieversPending();
+}
diff --git a/gov/nasa/worldwind/RetrieveToFilePostProcessor.java b/gov/nasa/worldwind/RetrieveToFilePostProcessor.java
new file mode 100644
index 0000000..b378d2b
--- /dev/null
+++ b/gov/nasa/worldwind/RetrieveToFilePostProcessor.java
@@ -0,0 +1,82 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: RetrieveToFilePostProcessor.java 510 2007-01-17 04:57:40Z ericdalgliesh $
+ */
+public final class RetrieveToFilePostProcessor implements RetrievalPostProcessor
+{
+ java.io.File destination;
+
+ /**
+ * @param destination
+ * @throws IllegalArgumentException if destination
is null
+ */
+ public RetrieveToFilePostProcessor(java.io.File destination)
+ {
+ if (destination == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DestNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.destination = destination;
+ }
+
+ /**
+ * @param retriever
+ * @return
+ * @throws IllegalArgumentException if retriever
is null
+ */
+ public java.nio.ByteBuffer run(Retriever retriever)
+ {
+ if (retriever == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.RetrieverIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ try
+ {
+ java.nio.ByteBuffer buffer = retriever.getBuffer();
+ if (buffer == null)
+ {
+ WorldWind.logger().log(java.util.logging.Level.FINE, WorldWind.retrieveErrMsg(
+ "RetrieveToFilePostProcessor.NullBufferPostprocessing") + retriever.getName());
+ return null;
+ }
+
+ java.io.FileOutputStream fos = null;
+ try
+ {
+ fos = new java.io.FileOutputStream(this.destination);
+ fos.getChannel().write(buffer);
+ return null;
+ }
+ catch (java.io.IOException e)
+ {
+ throw e;
+ }
+ finally
+ {
+ if (fos != null)
+ fos.close();
+ }
+ }
+ catch (java.io.IOException e)
+ {
+ String message = WorldWind.retrieveErrMsg("RetrieveToFilePostProcessor.ErrorPostprocessing") + retriever
+ .getName();
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new WWRuntimeException(message, e);
+ }
+ }
+}
diff --git a/gov/nasa/worldwind/Retriever.java b/gov/nasa/worldwind/Retriever.java
new file mode 100644
index 0000000..23a5c71
--- /dev/null
+++ b/gov/nasa/worldwind/Retriever.java
@@ -0,0 +1,46 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: Retriever.java 689 2007-01-31 02:49:58Z tgaskins $
+ */
+public interface Retriever extends WWObject, java.util.concurrent.Callable
+{
+ public final String RETRIEVER_STATE_NOT_STARTED = "gov.nasa.worldwind.RetrieverStatusNotStarted";
+ public final String RETRIEVER_STATE_STARTED = "gov.nasa.worldwind.RetrieverStatusStarted";
+ public final String RETRIEVER_STATE_CONNECTING = "gov.nasa.worldwind.RetrieverStatusConnecting";
+ public final String RETRIEVER_STATE_READING = "gov.nasa.worldwind.RetrieverStatusReading";
+ public final String RETRIEVER_STATE_INTERRUPTED = "gov.nasa.worldwind.RetrieverStatusInterrupted";
+ public final String RETRIEVER_STATE_ERROR = "gov.nasa.worldwind.RetrieverStatusError";
+ public final String RETRIEVER_STATE_SUCCESSFUL = "gov.nasa.worldwind.RetrieverStatusSuccessful";
+
+ public java.nio.ByteBuffer getBuffer();
+
+ public int getContentLength();
+
+ public int getContentLengthRead();
+
+ public String getName();
+
+ public String getState();
+
+ String getContentType();
+
+ long getSubmitTime();
+
+ void setSubmitTime(long submitTime);
+
+ long getBeginTime();
+
+ void setBeginTime(long beginTime);
+
+ long getEndTime();
+
+ void setEndTime(long endTime);
+}
diff --git a/gov/nasa/worldwind/SceneController.java b/gov/nasa/worldwind/SceneController.java
new file mode 100644
index 0000000..fe0ca32
--- /dev/null
+++ b/gov/nasa/worldwind/SceneController.java
@@ -0,0 +1,40 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: SceneController.java 1722 2007-05-05 04:03:46Z tgaskins $
+ */
+public interface SceneController extends WWObject
+{
+ public Model getModel();
+
+ public void setModel(Model model);
+
+ public View getView();
+
+ public void setView(View view);
+
+ public FrameController getFrameController();
+
+ public void setFrameController(FrameController frameController);
+
+ public void repaint();
+
+ void setVerticalExaggeration(double verticalExaggeration);
+
+ double getVerticalExaggeration();
+
+ PickedObjectList getPickedObjectList();
+
+ gov.nasa.worldwind.PickedObjectList pick(java.awt.Point pickPoint);
+
+ double getFramesPerSecond();
+
+ double getFrameTime();
+}
diff --git a/gov/nasa/worldwind/SectorGeometry.java b/gov/nasa/worldwind/SectorGeometry.java
new file mode 100644
index 0000000..d460681
--- /dev/null
+++ b/gov/nasa/worldwind/SectorGeometry.java
@@ -0,0 +1,30 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: SectorGeometry.java 1754 2007-05-06 23:19:22Z tgaskins $
+ */
+public interface SectorGeometry extends Renderable, Pickable, Cacheable
+{
+ public Extent getExtent();
+
+ public Sector getSector();
+
+ public Point getSurfacePoint(Angle latitude, Angle longitude, double metersOffset);
+
+ public void pick(DrawContext dc, java.awt.Point pickPoint);
+
+ void renderMultiTexture(DrawContext dc, int numTextureUnits);
+
+ public void renderWireframe(DrawContext dc, boolean interior, boolean exterior);
+
+ void renderBoundingVolume(DrawContext dc);
+}
diff --git a/gov/nasa/worldwind/SectorGeometryList.java b/gov/nasa/worldwind/SectorGeometryList.java
new file mode 100644
index 0000000..1485b21
--- /dev/null
+++ b/gov/nasa/worldwind/SectorGeometryList.java
@@ -0,0 +1,125 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+import gov.nasa.worldwind.geom.Point;
+
+import javax.media.opengl.*;
+import java.util.*;
+import java.util.List;
+import java.awt.*;
+
+/**
+ * @author tag
+ * @version $Id: SectorGeometryList.java 1782 2007-05-08 06:27:54Z tgaskins $
+ */
+public class SectorGeometryList extends ArrayList
+{
+ private PickSupport pickSupport = new PickSupport();
+
+ public SectorGeometryList()
+ {
+ }
+
+ public SectorGeometryList(SectorGeometryList list)
+ {
+ super(list);
+ }
+
+ public List getIntersectingSectors(Sector sector)
+ {
+ ArrayList list = null;
+
+ for (SectorGeometry sg: this)
+ {
+ if (sg.getSector().intersects(sector))
+ {
+ if (list == null)
+ list = new ArrayList();
+ list.add(sg);
+ }
+ else
+ {
+// System.out.println("no intersection");
+ }
+ }
+
+ return list;
+ }
+
+ public void pick(DrawContext dc, java.awt.Point pickPoint)
+ {
+ this.pickSupport.clearPickList();
+ this.pickSupport.beginPicking(dc);
+
+ GL gl = dc.getGL();
+ gl.glPushAttrib(GL.GL_LIGHTING_BIT | GL.GL_DEPTH_BUFFER_BIT | GL.GL_ENABLE_BIT | GL.GL_CURRENT_BIT);
+ gl.glEnable(GL.GL_DEPTH_TEST);
+ gl.glShadeModel(GL.GL_FLAT);
+ gl.glDisable(GL.GL_CULL_FACE);
+
+ try
+ {
+ // render each sector in unique color
+ for (SectorGeometry sector : this)
+ {
+ Color color = dc.getUniquePickColor();
+ dc.getGL().glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue());
+ sector.render(dc);
+ // lat/lon/elevation not used in this case
+ this.pickSupport.addPickableObject(color.getRGB(), sector, Position.ZERO, true);
+ }
+
+ PickedObject pickedSector = this.pickSupport.getTopObject(dc, pickPoint, null);
+ if (pickedSector == null || pickedSector.getObject() == null)
+ return; // no sector picked
+
+ SectorGeometry sector = (SectorGeometry) pickedSector.getObject();
+ gl.glDepthFunc(GL.GL_LEQUAL);
+ sector.pick(dc, pickPoint);
+ }
+ finally
+ {
+ gl.glPopAttrib();
+ this.pickSupport.endPicking(dc);
+ this.pickSupport.clearPickList();
+ }
+ }
+
+ public Point getSurfacePoint(Position position)
+ {
+ return this.getSurfacePoint(position.getLatitude(), position.getLongitude(), position.getElevation());
+ }
+
+ public Point getSurfacePoint(Angle latitude, Angle longitude)
+ {
+ return this.getSurfacePoint(latitude, longitude, 0d);
+ }
+
+ public Point getSurfacePoint(Angle latitude, Angle longitude, double metersOffset)
+ {
+ if (latitude == null || longitude == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.LatLonIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ for (SectorGeometry sg : this)
+ {
+ if (sg.getSector().contains(latitude, longitude))
+ {
+ Point point = sg.getSurfacePoint(latitude, longitude, metersOffset);
+ if (point != null)
+ return point;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/gov/nasa/worldwind/SelectEvent.java b/gov/nasa/worldwind/SelectEvent.java
new file mode 100644
index 0000000..4bc40ed
--- /dev/null
+++ b/gov/nasa/worldwind/SelectEvent.java
@@ -0,0 +1,74 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import java.util.*;
+import java.awt.event.*;
+
+/**
+ * @author tag
+ * @version $Id: SelectEvent.java 1764 2007-05-07 20:01:57Z tgaskins $
+ */
+public class SelectEvent extends EventObject
+{
+ public static final String LEFT_CLICK = "gov.nasa.worldwind.SelectEvent.LeftClick";
+ public static final String LEFT_DOUBLE_CLICK = "gov.nasa.worldwind.SelectEvent.LeftDoubleClick";
+ public static final String RIGHT_CLICK = "gov.nasa.worldwind.SelectEvent.RightClick";
+ public static final String HOVER = "gov.nasa.worldwind.SelectEvent.Hover";
+ public static final String ROLLOVER = "gov.nasa.worldwind.SelectEvent.Rollover";
+ public static final String DRAG = "gov.nasa.worldwind.SelectEvent.Drag";
+
+ private final String eventAction;
+ private final MouseEvent mouseEvent;
+ private final PickedObjectList pickedObjects;
+
+ public SelectEvent(Object source, String eventAction, MouseEvent mouseEvent, PickedObjectList pickedObjects)
+ {
+ super(source);
+ this.eventAction = eventAction;
+ this.mouseEvent = mouseEvent;
+ this.pickedObjects = pickedObjects;
+ }
+
+ public String getEventAction()
+ {
+ return this.eventAction != null ? this.eventAction : "gov.nasa.worldwind.SelectEvent.UnknownEventAction";
+ }
+
+ public MouseEvent getMouseEvent()
+ {
+ return mouseEvent;
+ }
+
+ public boolean hasObjects()
+ {
+ return this.pickedObjects != null && this.pickedObjects.size() > 0;
+ }
+
+ public PickedObjectList getObjects()
+ {
+ return this.pickedObjects;
+ }
+
+ public PickedObject getTopPickedObject()
+ {
+ return this.hasObjects() ? this.pickedObjects.getTopObject() : null;
+ }
+
+ public Object getTopObject()
+ {
+ PickedObject tpo = this.getTopPickedObject();
+ return tpo != null ? tpo.getObject() : null;
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.getClass().getName() + " "
+ + this.eventAction != null ? this.eventAction : WorldWind.retrieveErrMsg("generic.unknown");
+ }
+}
diff --git a/gov/nasa/worldwind/SelectListener.java b/gov/nasa/worldwind/SelectListener.java
new file mode 100644
index 0000000..1091036
--- /dev/null
+++ b/gov/nasa/worldwind/SelectListener.java
@@ -0,0 +1,18 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: SelectListener.java 1757 2007-05-07 09:17:09Z tgaskins $
+ */
+public interface SelectListener extends EventListener
+{
+ public void selected(SelectEvent event);
+}
diff --git a/gov/nasa/worldwind/StringUtil.java b/gov/nasa/worldwind/StringUtil.java
new file mode 100644
index 0000000..10f50fe
--- /dev/null
+++ b/gov/nasa/worldwind/StringUtil.java
@@ -0,0 +1,23 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+// @version $Id: StringUtil.java 1751 2007-05-06 21:21:44Z tgaskins $
+
+package gov.nasa.worldwind;
+
+public class StringUtil
+{
+ public static final String EMPTY = "";
+
+ public static boolean Equals(String s1, String s2)
+ {
+ if(null == s1 && null == s2)
+ return true;
+ if(null == s1 || null == s2)
+ return false;
+ return s1.equals(s2);
+ }
+}
diff --git a/gov/nasa/worldwind/SurfaceTileRenderer.java b/gov/nasa/worldwind/SurfaceTileRenderer.java
new file mode 100644
index 0000000..b08a967
--- /dev/null
+++ b/gov/nasa/worldwind/SurfaceTileRenderer.java
@@ -0,0 +1,309 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.layers.*;
+import gov.nasa.worldwind.geom.*;
+
+import javax.media.opengl.*;
+
+import com.sun.opengl.util.texture.*;
+
+import java.nio.*;
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: SurfaceTileRenderer.java 1664 2007-04-30 20:07:11Z tgaskins $
+ */
+public class SurfaceTileRenderer implements Disposable
+{
+ private static final int DEFAULT_ALPHA_TEXTURE_SIZE = 2;
+
+ private Texture alphaTexture;
+ private Texture outlineTexture;
+ private boolean showImageTileOutlines = false;
+
+ public void dispose() // TODO: but waiting to implement a global texture disposal system
+ {
+ }
+
+ public boolean isShowImageTileOutlines()
+ {
+ return showImageTileOutlines;
+ }
+
+ public void setShowImageTileOutlines(boolean showImageTileOutlines)
+ {
+ this.showImageTileOutlines = showImageTileOutlines;
+ }
+
+ public void renderTile(DrawContext dc, TextureTile tile)
+ {
+ if (tile == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.TileIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ ArrayList al = new ArrayList(1);
+ al.add(tile);
+ this.renderTiles(dc, al);
+ al.clear();
+ }
+
+ public void renderTiles(DrawContext dc, Iterable tiles)
+ {
+ if (tiles == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.TileIterableIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ if (dc == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ GL gl = dc.getGL();
+
+ gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT // for alpha func
+ | GL.GL_ENABLE_BIT
+ | GL.GL_CURRENT_BIT
+ | GL.GL_DEPTH_BUFFER_BIT // for depth func
+ | GL.GL_TEXTURE_BIT // for texture env
+ | GL.GL_TRANSFORM_BIT);
+
+ try
+ {
+ if (this.alphaTexture == null)
+ this.initAlphaTexture(DEFAULT_ALPHA_TEXTURE_SIZE); // TODO: choose size to match incoming tile sizes?
+
+ boolean showOutlines = this.showImageTileOutlines && dc.getNumTextureUnits() > 2;
+ if (showOutlines && this.outlineTexture == null)
+ this.initOutlineTexture(128);
+
+ gl.glEnable(GL.GL_DEPTH_TEST);
+ gl.glDepthFunc(GL.GL_LEQUAL);
+
+ gl.glEnable(GL.GL_ALPHA_TEST);
+ gl.glAlphaFunc(GL.GL_GREATER, 0.01f);
+
+ gl.glActiveTexture(GL.GL_TEXTURE0);
+ gl.glEnable(GL.GL_TEXTURE_2D);
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPushMatrix();
+ if (!dc.isPickingMode())
+ {
+ gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE);
+ }
+ else
+ {
+ gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_COMBINE);
+ gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC0_RGB, GL.GL_PREVIOUS);
+ gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_COMBINE_RGB, GL.GL_REPLACE);
+ }
+
+ int numTexUnitsUsed = 2;
+ int alphaTextureUnit = GL.GL_TEXTURE1;
+ if (showOutlines)
+ {
+ numTexUnitsUsed = 3;
+ alphaTextureUnit = GL.GL_TEXTURE2;
+ gl.glActiveTexture(GL.GL_TEXTURE1);
+ gl.glEnable(GL.GL_TEXTURE_2D);
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPushMatrix();
+ gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_ADD);
+ }
+
+ gl.glActiveTexture(alphaTextureUnit);
+ gl.glEnable(GL.GL_TEXTURE_2D);
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPushMatrix();
+ gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE);
+
+ // For each current geometry tile, find the intersecting image tiles and render the geometry
+ // tile once for each intersecting image tile.
+// System.out.printf("%d geo tiles\n", dc.getSurfaceGeometry().size());
+ for (SectorGeometry sg : dc.getSurfaceGeometry())
+ {
+ Iterable tilesToRender = this.getIntersectingTiles(sg, tiles);
+ if (tilesToRender == null)
+ continue;
+// System.out.printf("%d, ", tilesToRender.length);
+
+ // Pre-load info to compute the texture transform below
+ Sector st = sg.getSector();
+ double geoDeltaLat = st.getDeltaLatRadians();
+ double geoDeltaLon = st.getDeltaLonRadians();
+ double geoMinLat = st.getMinLatitude().radians;
+ double geoMinLon = st.getMinLongitude().radians;
+
+ // For each interesecting tile, establish the texture transform necessary to map the image tile
+ // into the geometry tile's texture space. Use an alpha texture as a mask to prevent changing the
+ // frame buffer where the image tile does not overlap the geometry tile. Render both the image and
+ // alpha textures via multi-texture rendering.
+ // TODO: Figure out how to apply multi-texture to more than one tile at a time, most likely via a
+ // fragment shader.
+ for (TextureTile tile : tilesToRender)
+ {
+ gl.glActiveTexture(GL.GL_TEXTURE0);
+
+ if (tile.bindTexture(dc))
+ {
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glLoadIdentity();
+ tile.applyTextureTransform(dc);
+
+ // Determine and apply texture transform to map image tile into geometry tile's texture space
+ Sector si = tile.getSector();
+ double latScale = si.getDeltaLatRadians() > 0 ? geoDeltaLat / si.getDeltaLatRadians() : 1;
+ double lonScale = si.getDeltaLonRadians() > 0 ? geoDeltaLon / si.getDeltaLonRadians() : 1;
+ gl.glScaled(lonScale, latScale, 1d);
+
+ double latShift = -(si.getMinLatitude().radians - geoMinLat) / geoDeltaLat;
+ double lonShift = -(si.getMinLongitude().radians - geoMinLon) / geoDeltaLon;
+ gl.glTranslated(lonShift, latShift, 0d);
+
+ if (showOutlines)
+ {
+ gl.glActiveTexture(GL.GL_TEXTURE1);
+ this.outlineTexture.bind();
+
+ // Apply the same texture transform to the outline texture. The outline textures uses a
+ // different texture unit than the tile, so the transform made above does not carry over.
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glLoadIdentity();
+ gl.glScaled(lonScale, latScale, 1d);
+ gl.glTranslated(lonShift, latShift, 0d);
+ }
+
+ // Prepare the alpha texture to be used as a mask where texture coords are outside [0,1]
+ gl.glActiveTexture(alphaTextureUnit);
+ this.alphaTexture.bind();
+
+ // Apply the same texture transform to the alpha texture. The alpha texture uses a
+ // different texture unit than the tile, so the transform made above does not carry over.
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glLoadIdentity();
+ gl.glScaled(lonScale, latScale, 1d);
+ gl.glTranslated(lonShift, latShift, 0d);
+
+ // Render the geometry tile
+ sg.renderMultiTexture(dc, numTexUnitsUsed);
+ }
+ }
+ }
+// System.out.println();
+
+ gl.glActiveTexture(alphaTextureUnit);
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPopMatrix();
+ gl.glDisable(GL.GL_TEXTURE_2D);
+
+ gl.glActiveTexture(GL.GL_TEXTURE0);
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPopMatrix();
+ gl.glDisable(GL.GL_TEXTURE_2D);
+
+ if (showOutlines)
+ {
+ gl.glActiveTexture(GL.GL_TEXTURE1);
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPopMatrix();
+ gl.glDisable(GL.GL_TEXTURE_2D);
+ }
+ }
+ catch (Exception e)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.ExceptionWhileRenderingLayer");
+ message += this.getClass().getName();
+ WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+ }
+ finally
+ {
+ gl.glPopAttrib();
+ }
+ }
+
+ private Iterable getIntersectingTiles(SectorGeometry sg, Iterable tiles)
+ {
+ ArrayList intersectingTiles = null;
+
+ for (TextureTile tile : tiles)
+ {
+ if (!tile.getSector().intersects(sg.getSector()))
+ continue;
+
+ if (intersectingTiles == null)
+ intersectingTiles = new ArrayList();
+
+ intersectingTiles.add(tile);
+ }
+
+ if (intersectingTiles == null)
+ return null;
+
+ return intersectingTiles;
+ }
+
+ private static void fillByteBuffer(ByteBuffer buffer, byte value)
+ {
+ for (int i = 0; i < buffer.capacity(); i++)
+ {
+ buffer.put(value);
+ }
+ }
+
+ private void initAlphaTexture(int size)
+ {
+ ByteBuffer textureBytes = com.sun.opengl.util.BufferUtil.newByteBuffer(size * size);
+ fillByteBuffer(textureBytes, (byte) 0xff);
+ TextureData textureData = new TextureData(GL.GL_ALPHA, size, size, 0, GL.GL_ALPHA,
+ GL.GL_UNSIGNED_BYTE, false, false, false, textureBytes.rewind(), null);
+ this.alphaTexture = TextureIO.newTexture(textureData);
+
+ this.alphaTexture.bind();
+ this.alphaTexture.setTexParameteri(GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
+ this.alphaTexture.setTexParameteri(GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
+ this.alphaTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_BORDER);
+ this.alphaTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_BORDER);
+ // Assume the default border color of (0, 0, 0, 0).
+ }
+
+ private void initOutlineTexture(int size)
+ {
+ ByteBuffer textureBytes = com.sun.opengl.util.BufferUtil.newByteBuffer(size * size);
+ for (int row = 0; row < size; row++)
+ {
+ for (int col = 0; col < size; col++)
+ {
+ byte p;
+ if (row == 0 || col == 0 || row == size - 1 || col == size - 1)
+ p = (byte) 0xff;
+ else
+ p = (byte) 0;
+ textureBytes.put(row * size + col, p);
+ }
+ }
+
+ TextureData textureData = new TextureData(GL.GL_LUMINANCE, size, size, 0, GL.GL_LUMINANCE,
+ GL.GL_UNSIGNED_BYTE, false, false, false, textureBytes.rewind(), null);
+ this.outlineTexture = TextureIO.newTexture(textureData);
+
+ this.outlineTexture.bind();
+ this.outlineTexture.setTexParameteri(GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
+ this.outlineTexture.setTexParameteri(GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
+ this.outlineTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
+ this.outlineTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);
+ }
+}
diff --git a/gov/nasa/worldwind/Tessellator.java b/gov/nasa/worldwind/Tessellator.java
new file mode 100644
index 0000000..367c72f
--- /dev/null
+++ b/gov/nasa/worldwind/Tessellator.java
@@ -0,0 +1,16 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author tag
+ * @version $Id: Tessellator.java 1230 2007-03-16 14:47:35Z tgaskins $
+ */
+public interface Tessellator extends WWObject
+{
+ SectorGeometryList tessellate(DrawContext dc);
+}
diff --git a/gov/nasa/worldwind/ThreadedTaskService.java b/gov/nasa/worldwind/ThreadedTaskService.java
new file mode 100644
index 0000000..ed92db5
--- /dev/null
+++ b/gov/nasa/worldwind/ThreadedTaskService.java
@@ -0,0 +1,172 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: ThreadedTaskService.java 1792 2007-05-08 21:28:37Z tgaskins $
+ */
+public class ThreadedTaskService extends WWObjectImpl // TODO: Extract interface
+ implements Thread.UncaughtExceptionHandler
+{
+ static final private int DEFAULT_CORE_POOL_SIZE = 1;
+ static final private int DEFAULT_QUEUE_SIZE = 10;
+ private static final String RUNNING_THREAD_NAME_PREFIX = WorldWind.retrieveMessage(
+ "ThreadedTaskService.RUNNING_THREAD_NAME_PREFIX", "ThreadStrings");
+ private static final String IDLE_THREAD_NAME_PREFIX = WorldWind.retrieveMessage(
+ "ThreadedTaskService.IDLE_THREAD_NAME_PREFIX", "ThreadStrings");
+ private java.util.concurrent.ConcurrentLinkedQueue activeTasks; // tasks currently allocated a thread
+ private TaskExecutor executor; // thread pool for running retrievers
+
+ public ThreadedTaskService()
+ {
+ Integer poolSize = Configuration.getIntegerValue(AVKey.THREADED_TASK_POOL_SIZE, DEFAULT_CORE_POOL_SIZE);
+ Integer queueSize = Configuration.getIntegerValue(AVKey.THREADED_TASK_QUEUE_SIZE, DEFAULT_QUEUE_SIZE);
+
+ // this.executor runs the tasks, each in their own thread
+ this.executor = new TaskExecutor(poolSize, queueSize);
+
+ // this.activeTasks holds the list of currently executing tasks
+ this.activeTasks = new java.util.concurrent.ConcurrentLinkedQueue();
+ }
+
+ public void uncaughtException(Thread thread, Throwable throwable)
+ {
+ if (throwable instanceof WWDuplicateRequestException)
+ return;
+
+ String message = WorldWind.retrieveErrMsg("ThreadedTaskService.UncaughtExceptionDuringTask") + thread.getName();
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ Thread.currentThread().getThreadGroup().uncaughtException(thread, throwable);
+ }
+
+ private class TaskExecutor extends java.util.concurrent.ThreadPoolExecutor
+ {
+ private static final long THREAD_TIMEOUT = 2; // keep idle threads alive this many seconds
+
+ private TaskExecutor(int poolSize, int queueSize)
+ {
+ super(poolSize, poolSize, THREAD_TIMEOUT, java.util.concurrent.TimeUnit.SECONDS,
+ new java.util.concurrent.ArrayBlockingQueue(queueSize),
+ new java.util.concurrent.ThreadFactory()
+ {
+ public Thread newThread(Runnable runnable)
+ {
+ Thread thread = new Thread(runnable);
+ thread.setDaemon(true);
+ thread.setPriority(Thread.MIN_PRIORITY);
+ thread.setUncaughtExceptionHandler(ThreadedTaskService.this);
+ return thread;
+ }
+ }, new java.util.concurrent.ThreadPoolExecutor.DiscardPolicy() // abandon task when queue is full
+ {
+ public void rejectedExecution(Runnable runnable,
+ java.util.concurrent.ThreadPoolExecutor threadPoolExecutor)
+ {
+ // Interposes logging for rejected execution
+ String message = WorldWind.retrieveErrMsg("ThreadedTaskService.ResourceRejected") + runnable;
+ WorldWind.logger().log(java.util.logging.Level.FINEST, message);
+ super.rejectedExecution(runnable, threadPoolExecutor);
+ }
+ });
+ }
+
+ protected void beforeExecute(Thread thread, Runnable runnable)
+ {
+ WorldWind.logger().log(java.util.logging.Level.FINEST, WorldWind.retrieveErrMsg(
+ "ThreadedTaskService.EnteringBeforeExecute"));
+
+ if (thread == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.ThreadIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (runnable == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.RunnableIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (ThreadedTaskService.this.activeTasks.contains(runnable))
+ {
+ String message = WorldWind.retrieveErrMsg("ThreadedTaskService.CancellingDuplicateTask") + runnable;
+ WorldWind.logger().log(java.util.logging.Level.FINER, message);
+ throw new WWDuplicateRequestException(message);
+ }
+
+ ThreadedTaskService.this.activeTasks.add(runnable);
+
+ if (RUNNING_THREAD_NAME_PREFIX != null)
+ thread.setName(RUNNING_THREAD_NAME_PREFIX + runnable);
+ thread.setPriority(Thread.MIN_PRIORITY);
+ thread.setUncaughtExceptionHandler(ThreadedTaskService.this);
+
+ super.beforeExecute(thread, runnable);
+
+ WorldWind.logger().log(java.util.logging.Level.FINEST, WorldWind.retrieveErrMsg(
+ "ThreadedTaskService.LeavingBeforeExecute"));
+ }
+
+ protected void afterExecute(Runnable runnable, Throwable throwable)
+ {
+ WorldWind.logger().log(java.util.logging.Level.FINEST, WorldWind.retrieveErrMsg(
+ "ThreadedTaskService.EnteringAfterExecute"));
+
+ if (runnable == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.RunnableIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ super.afterExecute(runnable, throwable);
+
+ ThreadedTaskService.this.activeTasks.remove(runnable);
+
+ if (throwable == null && IDLE_THREAD_NAME_PREFIX != null)
+ Thread.currentThread().setName(IDLE_THREAD_NAME_PREFIX);
+ }
+ }
+
+ public synchronized boolean contains(Runnable runnable)
+ {
+ //noinspection SimplifiableIfStatement
+ if (runnable == null)
+ return false;
+
+ return (this.activeTasks.contains(runnable) || this.executor.getQueue().contains(runnable));
+ }
+
+ /**
+ * Enqueues a task to run.
+ * @param runnable the task to add
+ * @throws IllegalArgumentException if runnable
is null
+ */
+ public synchronized void addTask(Runnable runnable)
+ {
+ if (runnable == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.RunnableIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ // Do not queue duplicates.
+ if (this.activeTasks.contains(runnable) || this.executor.getQueue().contains(runnable))
+ return;
+
+ this.executor.execute(runnable);
+ }
+
+ public boolean isFull()
+ {
+ return this.executor.getQueue().remainingCapacity() == 0;
+ }
+}
diff --git a/gov/nasa/worldwind/Tile.java b/gov/nasa/worldwind/Tile.java
new file mode 100644
index 0000000..51e3cfb
--- /dev/null
+++ b/gov/nasa/worldwind/Tile.java
@@ -0,0 +1,407 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+/**
+ * @author tag
+ * @version $Id: Tile.java 1334 2007-03-25 02:11:38Z tgaskins $
+ */
+public class Tile implements Comparable, Cacheable
+{
+ private final Sector sector;
+ private final gov.nasa.worldwind.Level level;
+ private final int row;
+ private final int column;
+ private final TileKey tileKey;
+ private double priority = Double.MAX_VALUE; // Default is minimum priority
+ // The following is late bound because it's only selectively needed and costly to create
+ private String path;
+
+ public Tile(Sector sector, gov.nasa.worldwind.Level level, int row, int column)
+ {
+ if (sector == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.SectorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (level == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.LevelIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (row < 0)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.rowIndexOutOfRange");
+ msg += WorldWind.retrieveErrMsg("punctuation.space");
+ msg += String.valueOf(row);
+
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (column < 0)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.columnIndexOutOfRange");
+ msg += WorldWind.retrieveErrMsg("punctuation.space");
+ msg += String.valueOf(row);
+
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.sector = sector;
+ this.level = level;
+ this.row = row;
+ this.column = column;
+ this.tileKey = new TileKey(this);
+ this.path = null;
+ }
+
+ public Tile(Sector sector, gov.nasa.worldwind.Level level)
+ {
+ if (sector == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.SectorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (level == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.LevelIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.sector = sector;
+ this.level = level;
+ this.row = Tile.computeRow(sector.getDeltaLat(), sector.getMinLatitude());
+ this.column = Tile.computeColumn(sector.getDeltaLon(), sector.getMinLongitude());
+ this.tileKey = new TileKey(this);
+ this.path = null;
+ }
+
+ public Tile(Sector sector)
+ {
+ if (sector == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.SectorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.sector = sector;
+ this.level = null;
+ this.row = 0;
+ this.column = 0;
+ this.tileKey = new TileKey(this);
+ this.path = null;
+ }
+
+ public long getSizeInBytes()
+ {
+ // Return just an approximate size
+ long size = 0;
+
+ if (this.sector != null)
+ size += this.sector.getSizeInBytes();
+
+ if (this.path != null)
+ size += this.getPath().length();
+
+ size += 32; // to account for the references and the TileKey size
+
+ return size;
+ }
+
+ public String getPath()
+ {
+ if (this.path == null)
+ {
+ this.path = this.level.getPath() + "/" + this.row + "/" + this.row + "_" + this.column;
+ if (!this.level.isEmpty())
+ path += this.level.getFormatSuffix();
+ }
+
+ return this.path;
+ }
+
+ public final Sector getSector()
+ {
+ return sector;
+ }
+
+ public gov.nasa.worldwind.Level getLevel()
+ {
+ return level;
+ }
+
+ public final int getLevelNumber()
+ {
+ return this.level != null ? this.level.getLevelNumber() : 0;
+ }
+
+ public final String getLevelName()
+ {
+ return this.level != null ? this.level.getLevelName() : "";
+ }
+
+ public final int getRow()
+ {
+ return row;
+ }
+
+ public final int getColumn()
+ {
+ return column;
+ }
+
+ public final String getCacheName()
+ {
+ return this.level != null ? this.level.getCacheName() : null;
+ }
+
+ public final String getFormatSuffix()
+ {
+ return this.level != null ? this.level.getFormatSuffix() : null;
+ }
+
+ public final TileKey getTileKey()
+ {
+ return this.tileKey;
+ }
+
+ public java.net.URL getResourceURL() throws java.net.MalformedURLException
+ {
+ return this.level != null ? this.level.getTileResourceURL(this) : null;
+ }
+
+ public String getLabel()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(this.getLevelNumber());
+ sb.append("(");
+ sb.append(this.getLevelName());
+ sb.append(")");
+ sb.append(", ").append(this.getRow());
+ sb.append(", ").append(this.getColumn());
+
+ return sb.toString();
+ }
+
+ public int compareTo(Tile tile)
+ {
+ if (tile == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.TileIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ // No need to compare Sectors or path because they are redundant with row and column
+ if (tile.getLevelNumber() == this.getLevelNumber() && tile.row == this.row && tile.column == this.column)
+ return 0;
+
+ if (this.getLevelNumber() < tile.getLevelNumber()) // Lower-res levels compare lower than higher-res
+ return -1;
+ if (this.getLevelNumber() > tile.getLevelNumber())
+ return 1;
+
+ if (this.row < tile.row)
+ return -1;
+ if (this.row > tile.row)
+ return 1;
+
+ if (this.column < tile.column)
+ return -1;
+
+ return 1; // tile.column must be > this.column because equality was tested above
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ // Equality based only on the tile key
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final Tile tile = (Tile) o;
+
+ return !(tileKey != null ? !tileKey.equals(tile.tileKey) : tile.tileKey != null);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return (tileKey != null ? tileKey.hashCode() : 0);
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.getPath();
+ }
+
+ /**
+ * Computes the row index of a latitude in the global tile grid corresponding to a specified grid interval.
+ *
+ * @param delta the grid interval
+ * @param latitude the latitude for which to compute the row index
+ * @return the row index of the row containing the specified latitude
+ * @throws IllegalArgumentException if delta
is null or non-positive, or latitude
is null,
+ * greater than positive 90 degrees, or less than negative 90 degrees
+ */
+ public static int computeRow(Angle delta, Angle latitude)
+ {
+ if (delta == null || latitude == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (delta.getDegrees() <= 0d)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.deltaAngleOutOfRange");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (latitude.getDegrees() < -90d || latitude.getDegrees() > 90d)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.angleOutOfRange");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (latitude.getDegrees() == 90d)
+ return (int) (180d / delta.getDegrees()) - 1;
+ else
+ return (int) ((latitude.getDegrees() + 90d) / delta.getDegrees());
+ }
+
+ /**
+ * Computes the column index of a longitude in the global tile grid corresponding to a specified grid interval.
+ *
+ * @param delta the grid interval
+ * @param longitude the longitude for which to compute the column index
+ * @return the column index of the column containing the specified latitude
+ * @throws IllegalArgumentException if delta
is null or non-positive, or longitude
is
+ * null, greater than positive 180 degrees, or less than negative 180 degrees
+ */
+ public static int computeColumn(Angle delta, Angle longitude)
+ {
+ if (delta == null || longitude == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (delta.getDegrees() <= 0d)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.deltaAngleOutOfRange");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (longitude.getDegrees() < -180d || longitude.getDegrees() > 180d)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.angleOutOfRange");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (longitude.getDegrees() == 180d)
+ return (int) (360d / delta.getDegrees()) - 1;
+ else
+ return (int) ((longitude.getDegrees() + 180d) / delta.getDegrees());
+ }
+
+ /**
+ * Determines the minimum latitude of a row in the global tile grid corresponding to a specified grid interval.
+ *
+ * @param row the row index of the row in question
+ * @param delta the grid interval
+ * @return the minimum latitude of the tile corresponding to the specified row
+ * @throws IllegalArgumentException if the grid interval (delta
) is null or zero or the row index is
+ * negative.
+ */
+ public static Angle computeRowLatitude(int row, Angle delta)
+ {
+ if (delta == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ if (row < 0)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.rowIndexOutOfRange");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (delta.getDegrees() <= 0d)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.deltaAngleOutOfRange");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return Angle.fromDegrees(-90d + delta.getDegrees() * row);
+ }
+
+ /**
+ * Determines the minimum longitude of a column in the global tile grid corresponding to a specified grid interval.
+ *
+ * @param column the row index of the row in question
+ * @param delta the grid interval
+ * @return the minimum longitude of the tile corresponding to the specified column
+ * @throws IllegalArgumentException if the grid interval (delta
) is null or zero or the column index is
+ * negative.
+ */
+ public static Angle computeColumnLongitude(int column, Angle delta)
+ {
+ if (delta == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ if (column < 0)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.columnIndexOutOfRange");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (delta.getDegrees() <= 0d)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.deltaAngleOutOfRange");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return Angle.fromDegrees(-180 + delta.getDegrees() * column);
+ }
+
+ public double getPriority()
+ {
+ return priority;
+ }
+
+ public void setPriority(double priority)
+ {
+ this.priority = priority;
+ }
+}
diff --git a/gov/nasa/worldwind/TileKey.java b/gov/nasa/worldwind/TileKey.java
new file mode 100644
index 0000000..6983c4f
--- /dev/null
+++ b/gov/nasa/worldwind/TileKey.java
@@ -0,0 +1,205 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import gov.nasa.worldwind.geom.*;
+
+/**
+ * @author tag
+ * @version $Id: TileKey.java 511 2007-01-17 07:26:02Z ericdalgliesh $
+ */
+public class TileKey implements Comparable
+{
+ private final int level;
+ private final int row;
+ private final int col;
+ private final String cacheName;
+ private final int hash;
+
+ /**
+ * @param level
+ * @param row
+ * @param col
+ * @param cacheName
+ * @throws IllegalArgumentException if level
, row
or column
is negative or if
+ * cacheName
is null or empty
+ */
+ public TileKey(int level, int row, int col, String cacheName)
+ {
+ if (level < 0)
+ {
+ String msg = WorldWind.retrieveErrMsg("TileKey.levelIsLessThanZero");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (row < 0)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.rowIndexOutOfRange");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (col < 0)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.columnIndexOutOfRange");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (cacheName == null || cacheName.length() < 1)
+ {
+ String msg = WorldWind.retrieveErrMsg("TileKey.cacheNameIsNullOrEmpty");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ this.level = level;
+ this.row = row;
+ this.col = col;
+ this.cacheName = cacheName;
+ this.hash = this.computeHash();
+ }
+
+ /**
+ * @param latitude
+ * @param longitude
+ * @param level
+ * @throws IllegalArgumentException if any parameter is null
+ */
+ public TileKey(Angle latitude, Angle longitude, Level level)
+ {
+ if (latitude == null || longitude == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (level == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.LevelIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ this.level = level.getLevelNumber();
+ this.row = Tile.computeRow(level.getTileDelta().getLatitude(), latitude);
+ this.col = Tile.computeColumn(level.getTileDelta().getLongitude(), longitude);
+ this.cacheName = level.getCacheName();
+ this.hash = this.computeHash();
+ }
+
+ /**
+ * @param tile
+ * @throws IllegalArgumentException if tile
is null
+ */
+ public TileKey(gov.nasa.worldwind.Tile tile)
+ {
+ if (tile == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.TileIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ this.level = tile.getLevelNumber();
+ this.row = tile.getRow();
+ this.col = tile.getColumn();
+ this.cacheName = tile.getCacheName();
+ this.hash = this.computeHash();
+ }
+
+ public int getLevelNumber()
+ {
+ return level;
+ }
+
+ public int getRow()
+ {
+ return row;
+ }
+
+ public int getColumn()
+ {
+ return col;
+ }
+
+ public String getCacheName()
+ {
+ return cacheName;
+ }
+
+ private int computeHash()
+ {
+ int result;
+ result = this.level;
+ result = 29 * result + this.row;
+ result = 29 * result + this.col;
+ result = 29 * result + (this.cacheName != null ? this.cacheName.hashCode() : 0);
+ return result;
+ }
+
+ /**
+ * @param key
+ * @return
+ * @throws IllegalArgumentException if key
is null
+ */
+ public final int compareTo(TileKey key)
+ {
+ if (key == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.KeyIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ // No need to compare Sectors because they are redundant with row and column
+ if (key.level == this.level && key.row == this.row && key.col == this.col)
+ return 0;
+
+ if (this.level < key.level) // Lower-res levels compare lower than higher-res
+ return -1;
+ if (this.level > key.level)
+ return 1;
+
+ if (this.row < key.row)
+ return -1;
+ if (this.row > key.row)
+ return 1;
+
+ if (this.col < key.col)
+ return -1;
+
+ return 1; // tile.col must be > this.col because equality was tested above
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final TileKey tileKey = (TileKey) o;
+
+ if (this.col != tileKey.col)
+ return false;
+ if (this.level != tileKey.level)
+ return false;
+ if (this.row != tileKey.row)
+ return false;
+
+ return !(this.cacheName != null ? !this.cacheName.equals(tileKey.cacheName) : tileKey.cacheName != null);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return this.hash;
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.cacheName + "/" + this.level + "/" + this.row + "/" + col;
+ }
+}
diff --git a/gov/nasa/worldwind/ToolTipRenderer.java b/gov/nasa/worldwind/ToolTipRenderer.java
new file mode 100644
index 0000000..866eae4
--- /dev/null
+++ b/gov/nasa/worldwind/ToolTipRenderer.java
@@ -0,0 +1,275 @@
+/*
+Copyright (C) 2001, 2006 United States Government as represented by
+the Administrator of the National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import com.sun.opengl.util.j2d.*;
+
+import javax.media.opengl.*;
+import javax.media.opengl.glu.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import java.awt.*;
+import java.awt.geom.*;
+import static java.util.logging.Level.*;
+
+/**
+ * @author dcollins
+ * @version $Id: ToolTipRenderer.java 1786 2007-05-08 14:05:34Z dcollins $
+ */
+public class ToolTipRenderer
+{
+ private static final Font toolTipFont = UIManager.getFont("ToolTip.font");
+ private static final Color toolTipFg = UIManager.getColor("ToolTip.foreground");
+ private static final Color toolTipBg = UIManager.getColor("ToolTip.background");
+ private static final Border toolTipBorder = UIManager.getBorder("ToolTip.border");
+
+ private Color foreground;
+ private Color background;
+ private float[] compArray;
+ private Insets insets;
+ private float borderWidth;
+ private boolean useSystemLookAndFeel = false;
+
+ private TextRenderer textRenderer;
+ private int orthoWidth;
+ private int orthoHeight;
+ private GLU glu = new GLU();
+
+ public ToolTipRenderer()
+ {
+ this(new TextRenderer(toolTipFont, true, true), false);
+ }
+
+ public ToolTipRenderer(TextRenderer textRenderer)
+ {
+ this(textRenderer, false);
+ }
+
+ public ToolTipRenderer(TextRenderer textRenderer, boolean useSystemLookAndFeel)
+ {
+ if (textRenderer == null)
+ {
+ String message = WorldWind.retrieveErrMsg(""); // TODO
+ WorldWind.logger().log(FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ this.textRenderer = textRenderer;
+ this.useSystemLookAndFeel = useSystemLookAndFeel;
+ this.borderWidth = 1;
+ }
+
+ public void beginRendering(int width, int height, boolean disableDepthTest)
+ {
+ GL gl = GLU.getCurrentGL();
+ int attribBits =
+ GL.GL_ENABLE_BIT
+ | GL.GL_COLOR_BUFFER_BIT // for alpha test func and ref, and blend
+ | GL.GL_CURRENT_BIT // for current color
+ | GL.GL_TRANSFORM_BIT // for modelview and perspective
+ | GL.GL_POLYGON_BIT; // for polygon mode
+ gl.glPushAttrib(attribBits);
+
+ gl.glMatrixMode(GL.GL_PROJECTION);
+ gl.glPushMatrix();
+ gl.glLoadIdentity();
+ glu.gluOrtho2D(0, width, 0, height);
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPushMatrix();
+ gl.glLoadIdentity();
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPushMatrix();
+ gl.glLoadIdentity();
+
+ gl.glDisable(GL.GL_LIGHTING);
+ gl.glDisable(GL.GL_TEXTURE_2D);
+ if (disableDepthTest)
+ gl.glDisable(GL.GL_DEPTH_TEST);
+ gl.glDisable(GL.GL_CULL_FACE);
+ gl.glEnable(GL.GL_BLEND);
+ gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA);
+ // Suppress any fully transparent image pixels
+ final float ALPHA_EPSILON = 0.001f;
+ gl.glEnable(GL.GL_ALPHA_TEST);
+ gl.glAlphaFunc(GL.GL_GREATER, ALPHA_EPSILON);
+
+ this.orthoWidth = width;
+ this.orthoHeight = height;
+ }
+
+ public void endRendering()
+ {
+ GL gl = GLU.getCurrentGL();
+
+ gl.glMatrixMode(GL.GL_PROJECTION);
+ gl.glPopMatrix();
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPopMatrix();
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPopMatrix();
+
+ gl.glPopAttrib();
+ }
+
+ private static Rectangle2D ensureVisibleBounds(Rectangle2D bounds, int orthoWidth, int orthoHeight)
+ {
+ double newX;
+ if (bounds.getMinX() < 0)
+ newX = 1;
+ else if (bounds.getMaxX() > orthoWidth)
+ newX = orthoWidth - bounds.getWidth() - 1;
+ else
+ newX = bounds.getX();
+
+ double newY;
+ if (bounds.getMinX() < 0)
+ newY = 1;
+ else if (bounds.getMaxY() > orthoHeight)
+ newY = orthoHeight - bounds.getHeight() - 1;
+ else
+ newY = bounds.getY();
+
+ return new Rectangle2D.Double(newX, newY, bounds.getWidth(), bounds.getHeight());
+ }
+
+ public Color getBackground()
+ {
+ return this.background;
+ }
+
+ public float getBorderWidth()
+ {
+ return this.borderWidth;
+ }
+
+ public Color getForeground()
+ {
+ return this.foreground;
+ }
+
+ public Insets getInsets()
+ {
+ return this.insets;
+ }
+
+ public boolean getUseSystemLookAndFeel()
+ {
+ return this.useSystemLookAndFeel;
+ }
+
+ public void draw(String str, int x, int y)
+ {
+ if (str == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.StringIsNull");
+ WorldWind.logger().log(FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ Color fg;
+ Color bg;
+ Insets insets;
+
+ GL gl = GLU.getCurrentGL();
+ if (this.useSystemLookAndFeel)
+ {
+ insets = toolTipBorder.getBorderInsets(null);
+ fg = toolTipFg;
+ bg = toolTipBg;
+ }
+ else
+ {
+ if (this.foreground != null)
+ {
+ fg = this.foreground;
+ }
+ else
+ {
+ gl.glGetFloatv(GL.GL_CURRENT_COLOR, compArray, 0);
+ fg = new Color(compArray[0], compArray[1], compArray[2], compArray[3]);
+ }
+
+ if (this.background != null)
+ {
+ bg = this.background;
+ }
+ else
+ {
+ if (compArray == null)
+ compArray = new float[4];
+ Color.RGBtoHSB(fg.getRed(), fg.getGreen(), fg.getBlue(), compArray);
+ bg = Color.getHSBColor(0, 0, (compArray[2] + 0.5f) % 1f);
+ }
+
+ if (this.insets != null)
+ insets = this.insets;
+ else
+ insets = new Insets(1, 1, 1, 1);
+ }
+
+ Rectangle2D strBounds = this.textRenderer.getBounds(str);
+ Rectangle2D ttBounds = new Rectangle2D.Double(
+ x, y,
+ strBounds.getWidth() + insets.left + insets.right,
+ strBounds.getHeight() + insets.bottom + insets.top);
+ ttBounds = ensureVisibleBounds(ttBounds, this.orthoWidth, this.orthoHeight);
+ double strX = ttBounds.getX() + insets.left - strBounds.getX();
+ double strY = ttBounds.getY() + insets.bottom + strBounds.getY() + strBounds.getHeight();
+
+ this.setDrawColor(bg);
+ gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
+ gl.glRectd(ttBounds.getMinX(), ttBounds.getMinY(), ttBounds.getMaxX(), ttBounds.getMaxY());
+
+ this.setDrawColor(fg);
+ gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE);
+ gl.glLineWidth(this.borderWidth);
+ gl.glRectd(ttBounds.getMinX(), ttBounds.getMinY(), ttBounds.getMaxX(), ttBounds.getMaxY());
+
+ this.textRenderer.setColor(fg);
+ gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
+ this.textRenderer.begin3DRendering();
+ this.textRenderer.draw(str, (int) strX, (int) strY);
+ this.textRenderer.end3DRendering();
+ }
+
+ public void setBackground(Color color)
+ {
+ this.background = color;
+ }
+
+ public void setBorderWidth(float borderWidth)
+ {
+ this.borderWidth = borderWidth;
+ }
+
+ private void setDrawColor(float r, float g, float b, float a)
+ {
+ GL gl = GLU.getCurrentGL();
+ gl.glColor4f(r * a, g * a, b * a, a);
+ }
+
+ private void setDrawColor(Color color)
+ {
+ if (this.compArray == null)
+ this.compArray = new float[4];
+ color.getRGBComponents(this.compArray);
+ this.setDrawColor(this.compArray[0], this.compArray[1], this.compArray[2], this.compArray[3]);
+ }
+
+ public void setForeground(Color color)
+ {
+ this.foreground = color;
+ }
+
+ public void setInsets(Insets insets)
+ {
+ this.insets = insets;
+ }
+
+ public void setUseSystemLookAndFeel(boolean useSystemLookAndFeel)
+ {
+ this.useSystemLookAndFeel = useSystemLookAndFeel;
+ }
+}
diff --git a/gov/nasa/worldwind/Track.java b/gov/nasa/worldwind/Track.java
new file mode 100644
index 0000000..a3ae51a
--- /dev/null
+++ b/gov/nasa/worldwind/Track.java
@@ -0,0 +1,20 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author tag
+ * @version $Id: Track.java 1016 2007-02-25 11:24:47Z tgaskins $
+ */
+public interface Track
+{
+ java.util.List getSegments();
+
+ String getName();
+
+ int getNumPoints();
+}
diff --git a/gov/nasa/worldwind/TrackPoint.java b/gov/nasa/worldwind/TrackPoint.java
new file mode 100644
index 0000000..8bcc915
--- /dev/null
+++ b/gov/nasa/worldwind/TrackPoint.java
@@ -0,0 +1,30 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author tag
+ * @version $Id: TrackPoint.java 288 2006-12-07 12:21:49Z tgaskins $
+ */
+public interface TrackPoint
+{
+ double getLatitude();
+
+ void setLatitude(double latitude);
+
+ double getLongitude();
+
+ void setLongitude(double longitude);
+
+ double getElevation();
+
+ void setElevation(double elevation);
+
+ String getTime();
+
+ void setTime(String time);
+}
diff --git a/gov/nasa/worldwind/TrackPointIterator.java b/gov/nasa/worldwind/TrackPointIterator.java
new file mode 100644
index 0000000..f8c1a28
--- /dev/null
+++ b/gov/nasa/worldwind/TrackPointIterator.java
@@ -0,0 +1,22 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+/**
+ * @author tag
+ * @version $Id$
+ */
+public interface TrackPointIterator extends java.util.Iterator
+{
+ boolean hasNext();
+
+ TrackPoint next();
+
+ void remove();
+
+ TrackPointIterator reset();
+}
diff --git a/gov/nasa/worldwind/TrackPointIteratorImpl.java b/gov/nasa/worldwind/TrackPointIteratorImpl.java
new file mode 100644
index 0000000..fd1a254
--- /dev/null
+++ b/gov/nasa/worldwind/TrackPointIteratorImpl.java
@@ -0,0 +1,99 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind;
+
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id$
+ */
+public class TrackPointIteratorImpl implements TrackPointIterator
+{
+ private java.util.List
+ *
+ * @author Tom Gaskins
+ * @version $Id: Frustum.java 1774 2007-05-08 01:03:37Z dcollins $
+ */
+public class Frustum
+{
+ private final Plane left;
+ private final Plane right;
+ private final Plane bottom;
+ private final Plane top;
+ private final Plane near;
+ private final Plane far;
+
+ /**
+ * Create a default frustum with six Plane
s. This defines a box of dimension (2, 2, 2) centered at the
+ * origin.
+ */
+ public Frustum()
+ {
+ this.near = new Plane(0d, 0d, 1d, 1d);
+ this.far = new Plane(0d, 0d, 0d - 1d, 1d);
+ this.left = new Plane(1d, 0d, 0d, 1d);
+ this.right = new Plane(0d - 1d, 0d, 0d, 1d);
+ this.bottom = new Plane(0d, 1d, 0d, 1d);
+ this.top = new Plane(0d, 0d - 1d, 0d, 1d);
+ }
+
+ /**
+ * Create a frustum from six Plane
s, which define its boundaries. Does not except null arguments.
+ *
+ * @param near the near plane
+ * @param far the far plane
+ * @param left the left side of the view frustum
+ * @param right the right side of the view frustm
+ * @param top the top of the view frustum
+ * @param bottom the bottom of the view frustum
+ * @throws IllegalArgumentException if any argument is null
+ */
+ public Frustum(Plane near, Plane far, Plane left, Plane right, Plane bottom, Plane top)
+ {
+ if (near == null || far == null || left == null || right == null || bottom == null || top == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PlaneIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ this.near = near;
+ this.far = far;
+ this.left = left;
+ this.right = right;
+ this.bottom = bottom;
+ this.top = top;
+ }
+
+ /**
+ * Obtain the near Plane
.
+ *
+ * @return the near Plane
+ */
+ public final Plane getNear()
+ {
+ return this.near;
+ }
+
+ /**
+ * Obtain the far Plane
.
+ *
+ * @return the far Plane
+ */
+ public final Plane getFar()
+ {
+ return this.far;
+ }
+
+ /**
+ * Obtain the left Plane
.
+ *
+ * @return the left Plane
+ */
+ public final Plane getLeft()
+ {
+ return this.left;
+ }
+
+ /**
+ * Obtain the right Plane
.
+ *
+ * @return the right Plane
+ */
+ public final Plane getRight()
+ {
+ return this.right;
+ }
+
+ /**
+ * Obtain the bottom Plane
.
+ *
+ * @return the bottom Plane
+ */
+ public final Plane getBottom()
+ {
+ return this.bottom;
+ }
+
+ /**
+ * Obtain the top Plane
.
+ *
+ * @return the top Plane
+ */
+ public final Plane getTop()
+ {
+ return this.top;
+ }
+
+ /**
+ * @param m
+ * @return
+ * @throws IllegalArgumentException if m
is null
+ */
+ public final Frustum getInverseTransformed(Matrix m)
+ {
+ if (m == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.MatrixIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ // Assumes orthogonal matrices with translation.
+ Matrix it = m.getTranspose();
+
+ Plane n = new Plane(it.transform(this.near.getVector()));
+ Plane f = new Plane(it.transform(this.far.getVector()));
+ Plane l = new Plane(it.transform(this.left.getVector()));
+ Plane r = new Plane(it.transform(this.right.getVector()));
+ Plane b = new Plane(it.transform(this.bottom.getVector()));
+ Plane t = new Plane(it.transform(this.top.getVector()));
+
+ return new Frustum(n, f, l, r, b, t);
+ }
+
+ /**
+ * @param extent
+ * @return
+ * @throws IllegalArgumentException if extent
is null
+ */
+ public final boolean intersects(Extent extent)
+ {
+ if (extent == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.ExtentIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ // See if the extent's bounding sphere is within or intersects the frustum.
+ Point c = extent.getCenter();
+ double nr = -extent.getRadius();
+
+ if (this.far.dot(c) <= nr)
+ return false;
+ if (this.left.dot(c) <= nr)
+ return false;
+ if (this.right.dot(c) <= nr)
+ return false;
+ if (this.top.dot(c) <= nr)
+ return false;
+ if (this.bottom.dot(c) <= nr)
+ return false;
+ //noinspection RedundantIfStatement
+ if (this.near.dot(c) <= nr)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * @param point
+ * @return
+ * @throws IllegalArgumentException if point
is null
+ */
+ public final boolean contains(Point point)
+ {
+ if (point == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (this.far.dot(point) < 0)
+ return false;
+ if (this.left.dot(point) < 0)
+ return false;
+ if (this.right.dot(point) < 0)
+ return false;
+ if (this.top.dot(point) < 0)
+ return false;
+ if (this.bottom.dot(point) < 0)
+ return false;
+ //noinspection RedundantIfStatement
+ if (this.near.dot(point) < 0)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "near: " + near.toString() + "... far: " + far.toString() + "... left: " + left.toString()
+ + "... right: " + right.toString() + "... bottom: " + bottom.toString() + "... top: " + top.toString();
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final gov.nasa.worldwind.geom.Frustum frustum = (gov.nasa.worldwind.geom.Frustum) o;
+
+ if (!bottom.equals(frustum.bottom))
+ return false;
+ if (!far.equals(frustum.far))
+ return false;
+ if (!left.equals(frustum.left))
+ return false;
+ if (!near.equals(frustum.near))
+ return false;
+ if (!right.equals(frustum.right))
+ return false;
+ //noinspection RedundantIfStatement
+ if (!top.equals(frustum.top))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result;
+ result = near.hashCode();
+ result = 31 * result + far.hashCode();
+ result = 29 * result + left.hashCode();
+ result = 23 * result + right.hashCode();
+ result = 19 * result + bottom.hashCode();
+ result = 17 * result + top.hashCode();
+ return result;
+ }
+}
diff --git a/gov/nasa/worldwind/geom/Intersection.java b/gov/nasa/worldwind/geom/Intersection.java
new file mode 100644
index 0000000..fc8987e
--- /dev/null
+++ b/gov/nasa/worldwind/geom/Intersection.java
@@ -0,0 +1,82 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.*;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: Intersection.java 510 2007-01-17 04:57:40Z ericdalgliesh $
+ */
+public final class Intersection // Instances are immutable
+{
+
+ private final Point intersectionPoint;
+ private final boolean isTangent;
+
+ /**
+ * @param intersectionPoint
+ * @param isTangent
+ * @throws IllegalArgumentException if intersectionpoint
is null
+ */
+ public Intersection(Point intersectionPoint, boolean isTangent)
+ {
+ if (intersectionPoint == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.IntersectionPointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ this.intersectionPoint = intersectionPoint;
+ this.isTangent = isTangent;
+ }
+
+ public final Point getIntersectionPoint()
+ {
+ return intersectionPoint;
+ }
+
+ public final boolean isTangent()
+ {
+ return isTangent;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final gov.nasa.worldwind.geom.Intersection that = (gov.nasa.worldwind.geom.Intersection) o;
+
+ if (isTangent != that.isTangent)
+ return false;
+ if (!intersectionPoint.equals(that.intersectionPoint))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result;
+ result = intersectionPoint.hashCode();
+ result = 29 * result + (isTangent ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ String pt = "Intersection Point: " + this.intersectionPoint;
+ String tang = this.isTangent ? " is a tangent." : " not a tangent";
+ return pt + tang;
+ }
+}
diff --git a/gov/nasa/worldwind/geom/LatLon.java b/gov/nasa/worldwind/geom/LatLon.java
new file mode 100644
index 0000000..46998b4
--- /dev/null
+++ b/gov/nasa/worldwind/geom/LatLon.java
@@ -0,0 +1,152 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.*;
+
+/**
+ * Represents a point on the two-dimensional surface of a globe. Latitude is the degrees North and ranges between [-90,
+ * 90], while longitude refers to degrees East, and ranges between (-180, 180].
+ *
+ * Instances of LatLon
are immutable.
+ *
+ * @author Tom Gaskins
+ * @version $Id: LatLon.java 1749 2007-05-06 19:48:14Z tgaskins $
+ */
+public class LatLon
+{
+ private final Angle latitude;
+ private final Angle longitude;
+
+ /**
+ * Factor method for obtaining a new LatLon
from two angles expressed in radians.
+ *
+ * @param latitude in radians
+ * @param longitude in radians
+ * @return a new LatLon
from the given angles, which are expressed as radians
+ */
+ public static LatLon fromRadians(double latitude, double longitude)
+ {
+ return new LatLon(Math.toDegrees(latitude), Math.toDegrees(longitude));
+ }
+
+ /**
+ * Factory method for obtaining a new LatLon
from two angles expressed in degrees.
+ *
+ * @param latitude in degrees
+ * @param longitude in degrees
+ * @return a new LatLon
from the given angles, which are expressed as degrees
+ */
+ public static LatLon fromDegrees(double latitude, double longitude)
+ {
+ return new LatLon(latitude, longitude);
+ }
+
+ private LatLon(double latitude, double longitude)
+ {
+ this.latitude = Angle.fromDegrees(latitude);
+ this.longitude = Angle.fromDegrees(longitude);
+ }
+
+ /**
+ * Contructs a new LatLon
from two angles. Neither angle may be null.
+ *
+ * @param latitude
+ * @param longitude
+ * @throws IllegalArgumentException if latitude
or longitude
is null
+ */
+ public LatLon(Angle latitude, Angle longitude)
+ {
+ if (latitude == null || longitude == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LatitudeOrLongitudeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.latitude = latitude;
+ this.longitude = longitude;
+ }
+
+ /**
+ * Obtains the latitude of this LatLon
.
+ *
+ * @return this LatLon
's latitude
+ */
+ public final Angle getLatitude()
+ {
+ return this.latitude;
+ }
+
+ /**
+ * Obtains the longitude of this LatLon
.
+ *
+ * @return this LatLon
's longitude
+ */
+ public final Angle getLongitude()
+ {
+ return this.longitude;
+ }
+
+ public static LatLon interpolate(double t, LatLon begin, LatLon end)
+ {
+ if (begin == null || end == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LatitudeOrLongitudeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (t < 0)
+ return begin;
+ else if (t > 1)
+ return end;
+ Quaternion beginQuat = Quaternion.EulerToQuaternion(begin.getLongitude().getRadians(),
+ begin.getLatitude().getRadians(), 0);
+ Quaternion endQuat = Quaternion.EulerToQuaternion(end.getLongitude().getRadians(),
+ end.getLatitude().getRadians(), 0);
+ Quaternion q = Quaternion.Slerp(beginQuat, endQuat, t);
+ Point v = Quaternion.QuaternionToEuler(q);
+ if (Double.isNaN(v.x()) || Double.isNaN(v.y()))
+ return null;
+ return LatLon.fromRadians(v.y(), v.x());
+ }
+
+ @Override
+ public String toString()
+ {
+ return "(" + this.latitude.toString() + ", " + this.longitude.toString() + ")";
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final gov.nasa.worldwind.geom.LatLon latLon = (gov.nasa.worldwind.geom.LatLon) o;
+
+ if (!latitude.equals(latLon.latitude))
+ return false;
+ //noinspection RedundantIfStatement
+ if (!longitude.equals(latLon.longitude))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result;
+ result = latitude.hashCode();
+ result = 29 * result + longitude.hashCode();
+ return result;
+ }
+}
diff --git a/gov/nasa/worldwind/geom/Line.java b/gov/nasa/worldwind/geom/Line.java
new file mode 100644
index 0000000..ea00eae
--- /dev/null
+++ b/gov/nasa/worldwind/geom/Line.java
@@ -0,0 +1,136 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.*;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: Line.java 1749 2007-05-06 19:48:14Z tgaskins $
+ */
+public final class Line// Instances are immutable
+{
+ private final Point origin;
+ private final Point direction;
+
+ /**
+ * @param origin
+ * @param direction
+ * @throws IllegalArgumentException if origin
is null, or direction
is null or has zero
+ * length
+ */
+ public Line(Point origin, Point direction)
+ {
+ String message = null;
+ if (origin == null)
+ message = "nullValue.OriginIsNull";
+ else if (direction == null)
+ message = "nullValue.DirectionIsNull";
+ else if (direction.length() <= 0)
+ message = "geom.Line.DirectionIsZeroVector";
+ if (message != null)
+ {
+ message = WorldWind.retrieveErrMsg(message);
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.origin = origin;
+ this.direction = direction;
+ }
+
+ public final Point getDirection()
+ {
+ return direction;
+ }
+
+ public final Point getOrigin()
+ {
+ return origin;
+ }
+
+ public final Point getPointAt(double t)
+ {
+ return Point.fromOriginAndDirection(t, this.direction, this.origin);
+ }
+
+ public final double selfDot()
+ {
+ return this.origin.dot(this.direction);
+ }
+
+ /**
+ * Performs a comparison to test whether this Object is internally identical to the other Object o
.
+ * This method takes into account both direction and origin, so two lines which may be equivalent may not be
+ * considered equal.
+ *
+ * @param o the object to be compared against.
+ * @return true if these two objects are equal, false otherwise
+ */
+ @Override
+ public final boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final gov.nasa.worldwind.geom.Line line = (gov.nasa.worldwind.geom.Line) o;
+
+ if (!direction.equals(line.direction))
+ return false;
+ if (!line.origin.equals(origin))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public final int hashCode()
+ {
+ int result;
+ result = origin.hashCode();
+ result = 29 * result + direction.hashCode();
+ return result;
+ }
+
+ public String toString()
+ {
+ return "Origin: " + this.origin + ", Direction: " + this.direction;
+ }
+
+ /**
+ * Calculate the shortests distance between this line and a specified Point
. This method returns a
+ * positive distance.
+ *
+ * @param p the Point
whose distance from this Line
will be calculated
+ * @return the distance between this Line
and the specified Point
+ * @throws IllegalArgumentException if p
is null
+ */
+ public final double distanceTo(Point p)
+ {
+ if (p == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ Point origin = this.getOrigin();
+ Point sideB = origin.subtract(p); // really a vector
+
+ double distanceToOrigin = sideB.dot(this.getDirection());
+ double divisor = distanceToOrigin / this.getDirection().selfDot();
+
+ Point sideA = this.getDirection().multiply(divisor);
+
+ double aSquared = sideA.selfDot();
+ double bSquared = sideB.selfDot();
+
+ return Math.sqrt(bSquared - aSquared);
+ }
+}
diff --git a/gov/nasa/worldwind/geom/Matrix.java b/gov/nasa/worldwind/geom/Matrix.java
new file mode 100644
index 0000000..3ad5011
--- /dev/null
+++ b/gov/nasa/worldwind/geom/Matrix.java
@@ -0,0 +1,32 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+/**
+ * The interface World Wind uses to interact with matrices. This interface can be implemented by an application's
+ * own matrix classes. World Wind will then use instances of those classes for matrix manipulation.
+ * @author Tom Gaskins
+ * @version $Id: Matrix.java 1749 2007-05-06 19:48:14Z tgaskins $
+ */
+public interface Matrix
+{
+ Matrix setToIdentity();
+ Matrix rotate(Angle rotation, double axisX, double axisY, double axisZ);
+ Matrix rotateX(Angle rotation);
+ Matrix rotateY(Angle rotation);
+ Matrix rotateZ(Angle rotation);
+ Matrix translate(double x, double y, double z);
+ Matrix translate(Point p);
+ Matrix multiply(Matrix m);
+ Matrix add(Matrix m);
+ Matrix getInverse();
+ Matrix getTranspose();
+ double determinant();
+ double[] getEntries();
+ boolean isOrthonormal();
+ Point transform(Point p);
+}
diff --git a/gov/nasa/worldwind/geom/Matrix4.java b/gov/nasa/worldwind/geom/Matrix4.java
new file mode 100644
index 0000000..8ec21db
--- /dev/null
+++ b/gov/nasa/worldwind/geom/Matrix4.java
@@ -0,0 +1,857 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.*;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: Matrix4.java 1749 2007-05-06 19:48:14Z tgaskins $
+ */
+public class Matrix4 implements Matrix
+{
+ // TODO: scalar operations
+ private double m11 = 1, m12, m13, m14, m21, m22 = 1, m23, m24, m31, m32, m33 = 1, m34, m41, m42, m43, m44 = 1;
+ private boolean isOrthonormal = true;
+
+ /**
+ * Creates a new Matrix4
as the identity matrix.
+ */
+ public Matrix4()
+ {
+ }
+
+ /**
+ * Creates a new Matrix4
from an array of double precision floating point values. The caller must
+ * provide at least sixteen values, and any values beyond the sixteenth are ignored. Values are assigned in the
+ * following order: (1, 1), (2, 1), (3, 1), (4, 1), (1, 2), (2, 2), (3, 2), (4, 2), (3, 3), (2, 3), (3, 3), (4, 3),
+ * (1, 4), (2, 4), (3, 4), (4, 4).
+ *
+ * @param entries the values, must contain at least 16 values and may not be null
+ * @throws IllegalArgumentException if entries
is too short or null
+ */
+ public Matrix4(double[] entries)
+ {
+ if (entries == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.EntriesIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (entries.length < 16)
+ {
+ String msg = WorldWind.retrieveErrMsg("geom.Matrix4.ArrayTooShort");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.m11 = entries[0];
+ this.m21 = entries[1];
+ this.m31 = entries[2];
+ this.m41 = entries[3];
+
+ this.m12 = entries[4];
+ this.m22 = entries[5];
+ this.m32 = entries[6];
+ this.m42 = entries[7];
+
+ this.m13 = entries[8];
+ this.m23 = entries[9];
+ this.m33 = entries[10];
+ this.m43 = entries[11];
+
+ this.m14 = entries[12];
+ this.m24 = entries[13];
+ this.m34 = entries[14];
+ this.m44 = entries[15];
+
+ // TODO: Determine this by checking key entries
+ this.isOrthonormal = false;
+ }
+
+ /**
+ * Creates a new Matrix4
from an array of single precision floating point values. The caller must
+ * provide at least sixteen values, and any values beyond the sixteenth are ignored. Values are assigned in the
+ * following order: (1, 1), (2, 1), (3, 1), (4, 1), (1, 2), (2, 2), (3, 2), (4, 2), (3, 3), (2, 3), (3, 3), (4, 3),
+ * (1, 4), (2, 4), (3, 4), (4, 4).
+ *
+ * @param entries the values, must contain at least 16 values and may not be null
+ * @throws IllegalArgumentException if entries
is too short or null
+ */
+ public Matrix4(float[] entries)
+ {
+ if (entries == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.EntriesIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (entries.length < 16)
+ {
+ String msg = WorldWind.retrieveErrMsg("geom.Matrix4.ArrayTooShort");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.m11 = entries[0];
+ this.m21 = entries[1];
+ this.m31 = entries[2];
+ this.m41 = entries[3];
+
+ this.m12 = entries[4];
+ this.m22 = entries[5];
+ this.m32 = entries[6];
+ this.m42 = entries[7];
+
+ this.m13 = entries[8];
+ this.m23 = entries[9];
+ this.m33 = entries[10];
+ this.m43 = entries[11];
+
+ this.m14 = entries[12];
+ this.m24 = entries[13];
+ this.m34 = entries[14];
+ this.m44 = entries[15];
+
+ // TODO: Optimize this by checking key entries
+ this.isOrthonormal = false;
+ }
+
+ /**
+ * Retrieves the entries comprising this Matrix
. The returned array is always 16 entries long. Values
+ * are place in as in the aray as follows: (1, 1), (2, 1), (3, 1), (4, 1), (1, 2), (2, 2), (3, 2), (4, 2), (3, 3),
+ * (2, 3), (3, 3), (4, 3), (1, 4), (2, 4), (3, 4), (4, 4).
+ *
+ * @return an array, of length 16, containing this Matrices' entries.
+ */
+ public final double[] getEntries()
+ {
+ double[] entries = new double[16];
+
+ entries[0] = this.m11;
+ entries[1] = this.m21;
+ entries[2] = this.m31;
+ entries[3] = this.m41;
+
+ entries[4] = this.m12;
+ entries[5] = this.m22;
+ entries[6] = this.m32;
+ entries[7] = this.m42;
+
+ entries[8] = this.m13;
+ entries[9] = this.m23;
+ entries[10] = this.m33;
+ entries[11] = this.m43;
+
+ entries[12] = this.m14;
+ entries[13] = this.m24;
+ entries[14] = this.m34;
+ entries[15] = this.m44;
+
+ return entries;
+ }
+
+ /**
+ * Sets this Matrix
to the identity matrix. This method causes internal changes to the
+ * Matrix
it operates on.
+ *
+ * @return this
, set to the identity
+ */
+ public final Matrix setToIdentity()
+ {
+ this.m11 = 1;
+ this.m12 = 0;
+ this.m13 = 0;
+ this.m14 = 0;
+ this.m21 = 0;
+ this.m22 = 1;
+ this.m23 = 0;
+ this.m24 = 0;
+ this.m31 = 0;
+ this.m32 = 0;
+ this.m33 = 1;
+ this.m34 = 0;
+ this.m41 = 0;
+ this.m42 = 0;
+ this.m43 = 0;
+ this.m44 = 1;
+
+ this.isOrthonormal = true;
+
+ return this;
+ }
+
+ /**
+ * Obtains whether or not this Matrix
is orthonormal. Orthonormal matrices possess unique properties
+ * that can make algorithms more efficient.
+ *
+ * @return true if this is orthonormal, false otherwise
+ */
+ public final boolean isOrthonormal()
+ {
+ return this.isOrthonormal;
+ }
+
+ /**
+ * Rotate this matrix by some angle around an arbitrary axis. A positive Angle
indicates an
+ * anti-clockwise direction. This method affects the internal state of this matrix.
+ *
+ * @param rotation the distance to rotate this matrix
+ * @param axisX the x component of the axis of rotation
+ * @param axisY the y component of the axis of rotation
+ * @param axisZ the z component of the axis of rotation
+ * @return this Matrix
, with the rotation applied
+ * @throws IllegalArgumentException if rotation
is null
+ */
+ public final Matrix rotate(Angle rotation, double axisX, double axisY, double axisZ)
+ {
+ if (rotation == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.RotationAngleIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ double ll = axisX * axisX + axisY * axisY + axisZ * axisZ;
+ if (rotation.getDegrees() == 0 || ll == 0)
+ return this;
+
+ if (ll != 1) // if axis not unit length, normalize it.
+ {
+ double l = Math.sqrt(ll);
+ axisX /= l;
+ axisY /= l;
+ axisZ /= l;
+ }
+
+ double c = rotation.cos();
+ double s = rotation.sin();
+ double c1 = 1 - c;
+ Matrix4 o = new Matrix4();
+
+ o.m11 = c + axisX * axisX * c1;
+ o.m12 = axisX * axisY * c1 - axisZ * s;
+ o.m13 = axisX * axisZ * c1 + axisY * s;
+ o.m14 = 0;
+ o.m21 = axisX * axisY * c1 + axisZ * s;
+ o.m22 = c + axisY * axisY * c1;
+ o.m23 = axisY * axisZ * c1 - axisX * s;
+ o.m24 = 0;
+ o.m31 = axisX * axisZ * c1 - axisY * s;
+ o.m32 = axisY * axisZ * c1 + axisX * s;
+ o.m33 = c + axisZ * axisZ * c1;
+ o.m34 = 0;
+ o.m41 = 0;
+ o.m42 = 0;
+ o.m43 = 0;
+ o.m44 = 1;
+
+ return this.multiply(o);
+ }
+
+ /**
+ * Rotate this Matrix
around the x-axis. A positive Angle
indicates an anti-clockwise
+ * direction. Changes the internal state of this Matrix
.
+ *
+ * @param rotation the distance to rotate
+ * @return this Matrix
, rotated around the x-axis by rotation
distance
+ * @throws IllegalArgumentException if rotation
is null
+ */
+ public final Matrix rotateX(Angle rotation)
+ {
+ if (rotation == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.RotationAngleIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ double c = rotation.cos();
+ double s = rotation.sin();
+
+ double n12 = this.m12 * c + this.m13 * s;
+ double n13 = this.m12 * -s + this.m13 * c;
+
+ double n22 = this.m22 * c + this.m23 * s;
+ double n23 = this.m22 * -s + this.m23 * c;
+
+ double n32 = this.m32 * c + this.m33 * s;
+ double n33 = this.m32 * -s + this.m33 * c;
+
+ double n42 = this.m42 * c + this.m43 * s;
+ double n43 = this.m42 * -s + this.m43 * c;
+
+ this.m12 = n12;
+ this.m13 = n13;
+ this.m22 = n22;
+ this.m23 = n23;
+ this.m32 = n32;
+ this.m33 = n33;
+ this.m42 = n42;
+ this.m43 = n43;
+
+ return this;
+ }
+
+ /**
+ * Rotate this Matrix
around the y-axis. A positive Angle
indicates an anti-clockwise
+ * direction. Changes the internal state of this Matrix
.
+ *
+ * @param rotation the distance to rotate
+ * @return this Matrix
, rotated around the y-axis by rotation
distance
+ * @throws IllegalArgumentException if rotation
is null
+ */
+ public final Matrix rotateY(Angle rotation)
+ {
+ if (rotation == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.RotationAngleIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ double c = rotation.cos();
+ double s = rotation.sin();
+
+ double n11 = this.m11 * c + this.m13 * -s;
+ double n13 = this.m11 * s + this.m13 * c;
+
+ double n21 = this.m21 * c + this.m23 * -s;
+ double n23 = this.m21 * s + this.m23 * c;
+
+ double n31 = this.m31 * c + this.m33 * -s;
+ double n33 = this.m31 * s + this.m33 * c;
+
+ double n41 = this.m41 * c + this.m43 * -s;
+ double n43 = this.m41 * s + this.m43 * c;
+
+ this.m11 = n11;
+ this.m13 = n13;
+ this.m21 = n21;
+ this.m23 = n23;
+ this.m31 = n31;
+ this.m33 = n33;
+ this.m41 = n41;
+ this.m43 = n43;
+
+ return this;
+ }
+
+ /**
+ * Rotate this Matrix
around the z-axis. A positive Angle
indicates an anti-clockwise
+ * direction. Changes the internal state of this Matrix
.
+ *
+ * @param rotation the distance to rotate
+ * @return this Matrix
, rotated around the z-axis by rotation
distance
+ * @throws IllegalArgumentException if rotation
is null
+ */
+ public final Matrix rotateZ(Angle rotation)
+ {
+ if (rotation == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.RotationAngleIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ double c = rotation.cos();
+ double s = rotation.sin();
+
+ double n11 = this.m11 * c + this.m12 * s;
+ double n12 = this.m11 * -s + this.m12 * c;
+
+ double n21 = this.m21 * c + this.m22 * s;
+ double n22 = this.m21 * -s + this.m22 * c;
+
+ double n31 = this.m31 * c + this.m32 * s;
+ double n32 = this.m31 * -s + this.m32 * c;
+
+ double n41 = this.m41 * c + this.m42 * s;
+ double n42 = this.m41 * -s + this.m42 * c;
+
+ this.m11 = n11;
+ this.m12 = n12;
+ this.m21 = n21;
+ this.m22 = n22;
+ this.m31 = n31;
+ this.m32 = n32;
+ this.m41 = n41;
+ this.m42 = n42;
+
+ return this;
+ }
+
+ /**
+ * Translates this Matrix
in three dimensional space. Changes the internal state of this
+ * Matrix
.
+ *
+ * @param x the distance to translate along the x-axis
+ * @param y the distance to translate along the y-axis
+ * @param z the distance to translate along the z-axis
+ * @return this matrix, translated by (x, y, z)
+ */
+ public Matrix translate(double x, double y, double z)
+ {
+ this.m14 = this.m11 * x + this.m12 * y + this.m13 * z + this.m14;
+ this.m24 = this.m21 * x + this.m22 * y + this.m23 * z + this.m24;
+ this.m34 = this.m31 * x + this.m32 * y + this.m33 * z + this.m34;
+ this.m44 = this.m41 * x + this.m42 * y + this.m43 * z + this.m44;
+
+ return this;
+ }
+
+ /**
+ * Translates this Matrix
in three dimansional space. Changes the internal state of this
+ * Matrix
. The x, y and z co-ordinates are used to translate along the x, y and z axes respectively.
+ *
+ * @param p the x, y and z distances to translate as a Point
+ * @return this Matrix
, translated by the distances defined in p
+ * @throws IllegalArgumentException if p
is null
+ */
+ public final Matrix translate(Point p)
+ {
+ if (p == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return this.translate(p.x(), p.y(), p.z());
+ }
+
+ /**
+ * Adds this another matrix to this one.
+ *
+ * @param m the Matrix
to add to this one
+ * @return this Matrix, with the m
added to it
+ * @throws IllegalArgumentException if m
is null
+ */
+ public final Matrix add(Matrix m)
+ {
+ if (m == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.MatrixIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ Matrix4 o = (Matrix4) m;
+
+ this.m11 += o.m11;
+ this.m12 += o.m12;
+ this.m13 += o.m13;
+ this.m14 += o.m14;
+ this.m21 += o.m21;
+ this.m22 += o.m22;
+ this.m23 += o.m23;
+ this.m24 += o.m24;
+ this.m31 += o.m31;
+ this.m32 += o.m32;
+ this.m33 += o.m33;
+ this.m34 += o.m34;
+ this.m41 += o.m41;
+ this.m42 += o.m42;
+ this.m43 += o.m43;
+ this.m44 += o.m44;
+
+ this.isOrthonormal = this.isOrthonormal || o.isOrthonormal;
+
+ return this;
+ }
+
+ /**
+ * Performs a cross multiplication with another Matrix
. Alters the state of this Matrix
.
+ *
+ * @param m another Matrix
+ * @return this, postmultiplied by m
+ * @throws IllegalArgumentException if m
is null
+ */
+ public final Matrix multiply(Matrix m)
+ {
+ if (m == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.MatrixIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ Matrix4 o = (Matrix4) m;
+
+ double n11 = this.m11 * o.m11 + this.m12 * o.m21 + this.m13 * o.m31 + this.m14 * o.m41;
+ double n12 = this.m11 * o.m12 + this.m12 * o.m22 + this.m13 * o.m32 + this.m14 * o.m42;
+ double n13 = this.m11 * o.m13 + this.m12 * o.m23 + this.m13 * o.m33 + this.m14 * o.m43;
+ double n14 = this.m11 * o.m14 + this.m12 * o.m24 + this.m13 * o.m34 + this.m14 * o.m44;
+
+ double n21 = this.m21 * o.m11 + this.m22 * o.m21 + this.m23 * o.m31 + this.m24 * o.m41;
+ double n22 = this.m21 * o.m12 + this.m22 * o.m22 + this.m23 * o.m32 + this.m24 * o.m42;
+ double n23 = this.m21 * o.m13 + this.m22 * o.m23 + this.m23 * o.m33 + this.m24 * o.m43;
+ double n24 = this.m21 * o.m14 + this.m22 * o.m24 + this.m23 * o.m34 + this.m24 * o.m44;
+
+ double n31 = this.m31 * o.m11 + this.m32 * o.m21 + this.m33 * o.m31 + this.m34 * o.m41;
+ double n32 = this.m31 * o.m12 + this.m32 * o.m22 + this.m33 * o.m32 + this.m34 * o.m42;
+ double n33 = this.m31 * o.m13 + this.m32 * o.m23 + this.m33 * o.m33 + this.m34 * o.m43;
+ double n34 = this.m31 * o.m14 + this.m32 * o.m24 + this.m33 * o.m34 + this.m34 * o.m44;
+
+ double n41 = this.m41 * o.m11 + this.m42 * o.m21 + this.m43 * o.m31 + this.m44 * o.m41;
+ double n42 = this.m41 * o.m12 + this.m42 * o.m22 + this.m43 * o.m32 + this.m44 * o.m42;
+ double n43 = this.m41 * o.m13 + this.m42 * o.m23 + this.m43 * o.m33 + this.m44 * o.m43;
+ double n44 = this.m41 * o.m14 + this.m42 * o.m24 + this.m43 * o.m34 + this.m44 * o.m44;
+
+ this.m11 = n11;
+ this.m12 = n12;
+ this.m13 = n13;
+ this.m14 = n14;
+ this.m21 = n21;
+ this.m22 = n22;
+ this.m23 = n23;
+ this.m24 = n24;
+ this.m31 = n31;
+ this.m32 = n32;
+ this.m33 = n33;
+ this.m34 = n34;
+ this.m41 = n41;
+ this.m42 = n42;
+ this.m43 = n43;
+ this.m44 = n44;
+
+ this.isOrthonormal = this.isOrthonormal || o.isOrthonormal;
+
+ return this;
+ }
+
+ /**
+ * Obtains the transpose of this Matrix
. Does not alter the state of this Matrix
.
+ *
+ * @return the transpoase of this Matrix
+ */
+ public final Matrix getTranspose()
+ {
+ Matrix4 transpose = new Matrix4();
+
+ transpose.m11 = this.m11;
+ transpose.m12 = this.m21;
+ transpose.m13 = this.m31;
+ transpose.m14 = this.m41;
+ transpose.m21 = this.m12;
+ transpose.m22 = this.m22;
+ transpose.m23 = this.m32;
+ transpose.m24 = this.m42;
+ transpose.m31 = this.m13;
+ transpose.m32 = this.m23;
+ transpose.m33 = this.m33;
+ transpose.m34 = this.m43;
+ transpose.m41 = this.m14;
+ transpose.m42 = this.m24;
+ transpose.m43 = this.m34;
+ transpose.m44 = this.m44;
+
+ transpose.isOrthonormal = this.isOrthonormal;
+
+ return transpose;
+ }
+
+ /**
+ * Obtain the inverse of this Matrix
.
+ *
+ * @return the inverse of this Matrix
.
+ */
+ public final Matrix getInverse()
+ {
+ Matrix4 inverse;
+
+ if (this.isOrthonormal)
+ {
+ inverse = this.orthonormalInverse();
+ }
+ else
+ {
+ inverse = this.generalInverse();
+ }
+
+ inverse.isOrthonormal = this.isOrthonormal;
+
+ return inverse;
+ }
+
+ private Matrix4 orthonormalInverse()
+ {
+ Matrix4 inverse = new Matrix4();
+
+ // Transpose of upper 3x3.
+ inverse.m11 = this.m11;
+ inverse.m12 = this.m21;
+ inverse.m13 = this.m31;
+
+ inverse.m21 = this.m12;
+ inverse.m22 = this.m22;
+ inverse.m23 = this.m32;
+
+ inverse.m31 = this.m13;
+ inverse.m32 = this.m23;
+ inverse.m33 = this.m33;
+
+ // Upper 3x3 inverse times current translation (4th column).
+ inverse.m14 = -(inverse.m11 * this.m14 + inverse.m12 * this.m24 + inverse.m13 * this.m34);
+ inverse.m24 = -(inverse.m21 * this.m14 + inverse.m22 * this.m24 + inverse.m23 * this.m34);
+ inverse.m34 = -(inverse.m31 * this.m14 + inverse.m32 * this.m24 + inverse.m33 * this.m34);
+
+ return inverse;
+ }
+
+ // TODO: Fix generalInverse. It's not producing correct inverses.
+ private Matrix4 generalInverse()
+ {
+ double d = this.determinant();
+ if (d == 0)
+ {
+ return null;
+ }
+
+ double id = 1 / d;
+ Matrix4 inverse = new Matrix4();
+
+ // Form the adjoint matrix.
+ double a1 = this.m33 * this.m44 - this.m34 * this.m43;
+ double a2 = this.m34 * this.m42 - this.m32 * this.m44;
+ double a3 = this.m32 * this.m43 - this.m33 * this.m42;
+ double a4 = this.m23 * this.m44 - this.m24 * this.m43;
+ double a5 = this.m24 * this.m42 - this.m22 * this.m44;
+ double a6 = this.m23 * this.m34 - this.m24 * this.m33;
+ double a7 = this.m22 * this.m33 - this.m23 * this.m32;
+ double a8 = this.m34 * this.m41 - this.m31 * this.m44;
+ double a9 = this.m31 * this.m43 - this.m33 * this.m41;
+ double a21 = this.m24 * this.m41 - this.m21 * this.m44;
+ double a22 = this.m24 * this.m31 - this.m21 * this.m34;
+ double a23 = this.m32 * this.m44 - this.m34 * this.m42;
+ double a24 = this.m31 * this.m42 - this.m32 * this.m41;
+ double a14 = this.m21 * this.m42 - this.m22 * this.m41;
+ double a15 = this.m21 * this.m32 - this.m22 * this.m31;
+ double a16 = this.m33 * this.m41 - this.m31 * this.m43;
+
+ inverse.m11 = id
+ * this.m22 * a1
+ + this.m23 * a2
+ + this.m24 * a3;
+ inverse.m12 = -id
+ * this.m12 * a1
+ + this.m13 * a2
+ + this.m14 * a3;
+ inverse.m13 = id
+ * this.m12 * a4
+ + this.m13 * (this.m24 * this.m42 - this.m22 * this.m44)
+ + this.m14 * a5;
+ inverse.m14 = -id
+ * this.m12 * a6
+ + this.m13 * (this.m24 * this.m32 - this.m22 * this.m34)
+ + this.m14 * a7;
+ inverse.m21 = -id
+ * this.m21 * a1
+ + this.m23 * a8
+ + this.m24 * a9;
+ inverse.m22 = id
+ * this.m11 * a1
+ + this.m13 * a8
+ + this.m14 * a9;
+ inverse.m23 = -id
+ * this.m11 * a4
+ + this.m13 * a21
+ + this.m14 * (this.m21 * this.m43 - this.m23 * this.m41);
+ inverse.m24 = -id
+ * this.m11 * a6
+ + this.m13 * a22
+ + this.m14 * (this.m21 * this.m33 - this.m23 * this.m31);
+ inverse.m31 = id
+ * this.m21 * a23
+ + this.m22 * a8
+ + this.m24 * a24;
+ inverse.m32 = -id
+ * this.m11 * a23
+ + this.m12 * a8
+ + this.m14 * a24;
+ inverse.m33 = -id
+ * this.m11 * (this.m22 * this.m44 - this.m24 * this.m42)
+ + this.m12 * a21
+ + this.m14 * a14;
+ inverse.m34 = id
+ * this.m11 * (this.m22 * this.m34 - this.m24 * this.m32)
+ + this.m12 * a22
+ + this.m14 * a15;
+ inverse.m41 = -id
+ * this.m21 * a3
+ + this.m22 * a16
+ + this.m23 * a24;
+ inverse.m42 = -id
+ * this.m11 * a3
+ + this.m12 * a16
+ + this.m13 * a24;
+ inverse.m43 = id
+ * this.m11 * a5
+ + this.m12 * (this.m23 * this.m41 - this.m21 * this.m43)
+ + this.m13 * a14;
+ inverse.m44 = -id
+ * this.m11 * a7
+ + this.m12 * (this.m23 * this.m31 - this.m21 * this.m33)
+ + this.m13 * a15;
+
+ return inverse;
+ }
+
+ /**
+ * Obtains the determinant of this Matrix
.
+ *
+ * @return the determinant
+ */
+ public final double determinant()
+ {
+ return
+ this.m11 * (
+ (this.m22 * this.m33 * this.m44 + this.m23 * this.m34 * this.m42 + this.m24 * this.m32 * this.m43)
+ - this.m24 * this.m33 * this.m42
+ - this.m22 * this.m34 * this.m43
+ - this.m23 * this.m32 * this.m44)
+ - this.m12 * (
+ (this.m21 * this.m33 * this.m44 + this.m23 * this.m34 * this.m41 + this.m24 * this.m31 * this.m43)
+ - this.m24 * this.m33 * this.m41
+ - this.m21 * this.m34 * this.m43
+ - this.m23 * this.m31 * this.m44)
+ + this.m13 * (
+ (this.m21 * this.m32 * this.m44 + this.m22 * this.m34 * this.m41 + this.m24 * this.m31 * this.m42)
+ - this.m24 * this.m32 * this.m41
+ - this.m21 * this.m34 * this.m42
+ - this.m22 * this.m31 * this.m44)
+ - this.m14 * (
+ (this.m21 * this.m32 * this.m43 + this.m22 * this.m33 * this.m41 + this.m23 * this.m31 * this.m42)
+ - this.m23 * this.m32 * this.m41
+ - this.m21 * this.m33 * this.m42
+ - this.m22 * this.m31 * this.m43);
+ }
+
+ /**
+ * Applies this Matrix
to a Point
.
+ *
+ * @param p the Point
to transform
+ * @return p, trasformed by the current state of this Matrix
+ * @throws IllegalArgumentException if p
is null
+ */
+ public final Point transform(Point p)
+ {
+ if (p == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ double x = this.m11 * p.x() + this.m12 * p.y() + this.m13 * p.z() + this.m14 * p.w();
+ double y = this.m21 * p.x() + this.m22 * p.y() + this.m23 * p.z() + this.m24 * p.w();
+ double z = this.m31 * p.x() + this.m32 * p.y() + this.m33 * p.z() + this.m34 * p.w();
+ double w = this.m41 * p.x() + this.m42 * p.y() + this.m43 * p.z() + this.m44 * p.w();
+
+ return new Point(x, y, z, w);
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final gov.nasa.worldwind.geom.Matrix4 matrix4 = (gov.nasa.worldwind.geom.Matrix4) o;
+
+ if (isOrthonormal != matrix4.isOrthonormal)
+ return false;
+ if (Double.compare(matrix4.m11, m11) != 0)
+ return false;
+ if (Double.compare(matrix4.m12, m12) != 0)
+ return false;
+ if (Double.compare(matrix4.m13, m13) != 0)
+ return false;
+ if (Double.compare(matrix4.m14, m14) != 0)
+ return false;
+ if (Double.compare(matrix4.m21, m21) != 0)
+ return false;
+ if (Double.compare(matrix4.m22, m22) != 0)
+ return false;
+ if (Double.compare(matrix4.m23, m23) != 0)
+ return false;
+ if (Double.compare(matrix4.m24, m24) != 0)
+ return false;
+ if (Double.compare(matrix4.m31, m31) != 0)
+ return false;
+ if (Double.compare(matrix4.m32, m32) != 0)
+ return false;
+ if (Double.compare(matrix4.m33, m33) != 0)
+ return false;
+ if (Double.compare(matrix4.m34, m34) != 0)
+ return false;
+ if (Double.compare(matrix4.m41, m41) != 0)
+ return false;
+ if (Double.compare(matrix4.m42, m42) != 0)
+ return false;
+ if (Double.compare(matrix4.m43, m43) != 0)
+ return false;
+ if (Double.compare(matrix4.m44, m44) != 0)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result;
+ long temp;
+ temp = m11 != +0.0d ? Double.doubleToLongBits(m11) : 0L;
+ result = (int) (temp ^ (temp >>> 32));
+ temp = m12 != +0.0d ? Double.doubleToLongBits(m12) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m13 != +0.0d ? Double.doubleToLongBits(m13) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m14 != +0.0d ? Double.doubleToLongBits(m14) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m21 != +0.0d ? Double.doubleToLongBits(m21) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m22 != +0.0d ? Double.doubleToLongBits(m22) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m23 != +0.0d ? Double.doubleToLongBits(m23) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m24 != +0.0d ? Double.doubleToLongBits(m24) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m31 != +0.0d ? Double.doubleToLongBits(m31) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m32 != +0.0d ? Double.doubleToLongBits(m32) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m33 != +0.0d ? Double.doubleToLongBits(m33) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m34 != +0.0d ? Double.doubleToLongBits(m34) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m41 != +0.0d ? Double.doubleToLongBits(m41) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m42 != +0.0d ? Double.doubleToLongBits(m42) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m43 != +0.0d ? Double.doubleToLongBits(m43) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = m44 != +0.0d ? Double.doubleToLongBits(m44) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ result = 29 * result + (isOrthonormal ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Matrix4 :\n[ " + this.m11 + ", " + this.m12 + ", " + this.m13 + ", " + this.m14 + ",\n "
+ + this.m21 + ", " + this.m22 + ", " + this.m23 + ", " + this.m24 + ",\n "
+ + this.m31 + ", " + this.m32 + ", " + this.m33 + ", " + this.m34 + ",\n "
+ + this.m41 + ", " + this.m42 + ", " + this.m43 + ", " + this.m44 + " ]";
+ }
+}
diff --git a/gov/nasa/worldwind/geom/Plane.java b/gov/nasa/worldwind/geom/Plane.java
new file mode 100644
index 0000000..261a1f3
--- /dev/null
+++ b/gov/nasa/worldwind/geom/Plane.java
@@ -0,0 +1,164 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.*;
+
+/**
+ * A Plane
object represents a mathematical plane in an arbitrary cartesian co-ordinate system. A
+ * Plane
is defined by a normal vector and a distance along that vector from the origin, where the distance
+ * represents the distance from the origin to the Plane
rather than from the Plane
to the
+ * origin.
+ *
+ *
+ * Instances of Plane
are immutable.
+ *
+ * @author Tom Gaskins
+ * @version $Id: Plane.java 1749 2007-05-06 19:48:14Z tgaskins $
+ */
+public final class Plane
+{
+
+ /**
+ * Represents all the information about this Plane
. The first three values (x, y, z
) of
+ * v
represent a normal Vector
to the Plane
, while the fourth
+ * (w
) represents the signed distance this Plane
has been shifted along that normal.
+ */
+// private final Vector v;
+ private final Point n;
+
+ /**
+ * Obtains a new instance of a Plane
whose information is contained in Vector
+ * vec
.
+ *
+ * @param vec the Vector
containing information about this Plane
's normal and distance
+ * @throws IllegalArgumentException if passed a null or zero-length Vector
+ */
+ public Plane(Point vec)
+ {
+ if (vec == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.VectorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (vec.selfDot() == 0)
+ {
+ String message = WorldWind.retrieveErrMsg("geom.Plane.VectorIsZero");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.n = vec;
+ }
+
+ /**
+ * Obtains a new Plane
whose normal is defined by the vector (a,b,c) and whose disance from that vector
+ * is d. The vector may not have zero length.
+ *
+ * @param a the x-parameter of the normal to this Plane
+ * @param b the y-parameter of the normal to this Plane
+ * @param c the z-parameter of the normal to this Plane
+ * @param d the distance of this Plane
from the origin along its normal.
+ * @throws IllegalArgumentException if 0==a==b==c
+ */
+ public Plane(double a, double b, double c, double d)
+ {
+ if (a == 0 && b == 0 && c == 0)
+ {
+ String message = WorldWind.retrieveErrMsg("geom.Plane.VectorIsZero");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.n = new Point(a, b, c, d);
+ }
+
+ /**
+ * Retrieves a Point
representing the normal to this Plane
.
+ *
+ * @return a Point
representing the normal to this Plane
+ */
+ public final Point getNormal()
+ {
+ return new Point(this.n.x(), this.n.y(), this.n.z());
+ }
+
+ /**
+ * Retrieves the distance from the origin to this Plane
. Two options exist for defining distance - the
+ * first represents the distance from the origin to the Plane
, the second represents the distance from
+ * the Plane
to the origin. This function uses the first method. The outcome of this is that depending
+ * on the caller's view of this method, the sign of distances may appear to be reversed.
+ *
+ * @return the distance between this Plane
and the origin
+ */
+ public final double getDistance()
+ {
+ return this.n.w();
+ }
+
+ /**
+ * Retrieves a vector representing the normal and distance to this Plane
. The
+ * vector has the structure (x, y, z, distance), where (x, y, z) represents the normal, and distance
+ * represents the distance from the origin.
+ *
+ * @return a Vector
representation of this Plane
+ */
+ public final Point getVector()
+ {
+ return this.n;
+ }
+
+ /**
+ * Calculates the dot product of this Plane
with Point p
.
+ *
+ * @param p the Point to dot with this Plane
+ * @return the dot product of p
and this Plane
+ * @throws IllegalArgumentException if p
is null
+ */
+ public final double dot(Point p)
+ {
+ if (p == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return this.n.x() * p.x() + this.n.y() * p.y() + this.n.z() * p.z() + this.n.w() * p.w();
+ }
+
+ @Override
+ public final String toString()
+ {
+ return this.n.toString();
+ }
+
+ @Override
+ public final boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final gov.nasa.worldwind.geom.Plane plane = (gov.nasa.worldwind.geom.Plane) o;
+
+ //noinspection RedundantIfStatement
+ if (!this.n.normalize().equals(plane.n.normalize()))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public final int hashCode()
+ {
+ return this.n.hashCode();
+ }
+}
\ No newline at end of file
diff --git a/gov/nasa/worldwind/geom/Point.java b/gov/nasa/worldwind/geom/Point.java
new file mode 100644
index 0000000..71fe6fe
--- /dev/null
+++ b/gov/nasa/worldwind/geom/Point.java
@@ -0,0 +1,556 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.*;
+
+/**
+ * Point
represents an homogeneous cartesian point in 3 dimensional space.
+ *
+ * Instances of Point
are immutable.
+ *
+ * @author Tom Gaskins
+ * @version $Id: Point.java 1750 2007-05-06 19:53:06Z tgaskins $
+ */
+public class Point implements gov.nasa.worldwind.Cacheable
+{
+ private final double x;
+ private final double y;
+ private final double z;
+ private final double w;
+
+ /**
+ * Value of ZERO
is (0,0,0)
+ */
+ public static final Point ZERO = new Point(0, 0, 0);
+ /**
+ * Value of UNIT_X
is (1,0,0)
+ */
+ public static final Point UNIT_X = new Point(1, 0, 0);
+ /**
+ * Value of UNIT_Y
is (0,1,0)
+ */
+ public static final Point UNIT_Y = new Point(0, 1, 0);
+ /**
+ * Value of UNIT_Z
is (0,0,1)
+ */
+ public static final Point UNIT_Z = new Point(0, 0, 1);
+
+ /**
+ * Constructs a new Point
from four parameters.
+ *
+ * @param x the x position of the Point
+ * @param y the y position of the Point
+ * @param z the z position of the Point
+ * @param w the w position of the Point
+ */
+ public Point(double x, double y, double z, double w)
+ {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+ }
+
+ /**
+ * Constructs a new Point
from three parameters. The w
field is set to 1.
+ *
+ * @param x the x position of the Point
+ * @param y the y position of the Point
+ * @param z the z position of the Point
+ */
+ public Point(double x, double y, double z)
+ {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = 1;
+ }
+
+ /**
+ * Returns the w element of this Point. This method differs from w()
in that subclasses may override
+ * it.
+ *
+ * @return the w element of this Point
+ * @see Point#w()
+ */
+ public double getW()
+ {
+ return w;
+ }
+
+ /**
+ * Returns the x element of this Point. This method differs from x()
in that subclasses may override
+ * it.
+ *
+ * @return the x element of this Point
+ * @see Point#x()
+ */
+ public double getX()
+ {
+ return x;
+ }
+
+ /**
+ * Returns the y element of this Point. This method differs from y()
in that subclasses may override
+ * it.
+ *
+ * @return the y element of this Point
+ * @see Point#y()
+ */
+ public double getY()
+ {
+ return y;
+ }
+
+ /**
+ * Returns the y element of this Point. This method differs from y()
in that subclasses may override
+ * it.
+ *
+ * @return the y element of this Point
+ * @see Point#z()
+ */
+ public double getZ()
+ {
+ return z;
+ }
+
+ /**
+ * Returns the x element of this Point
.
+ *
+ * @return the x element of this Point
+ */
+ public final double x()
+ {
+ return this.x;
+ }
+
+ /**
+ * Returns the y element of this Point
.
+ *
+ * @return the y element of this Point
+ */
+ public final double y()
+ {
+ return this.y;
+ }
+
+ /**
+ * Returns the z element of this Point
.
+ *
+ * @return the z element of this Point
+ */
+ public final double z()
+ {
+ return this.z;
+ }
+
+ /**
+ * Returns the w element of this Point
.
+ *
+ * @return the w element of this Point
+ */
+ public final double w()
+ {
+ return this.w;
+ }
+
+ /**
+ * Calculates the sum of these two Point
s. The resulting Point
has x value equivalent to
+ * this.x + p.x
, the results for y,z and w are calculated in the same way.
+ *
+ * @param p the Point
to be added to this Point
+ * @return a Point
resulting from the algebraic operation this + p
+ * @throws IllegalArgumentException if p
is null
+ */
+ public final Point add(Point p)
+ {
+ if (p == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return new Point(this.x + p.x, this.y + p.y, this.z + p.z, 1);
+ }
+
+ /**
+ * Calculates the difference between these two Point
s. The resulting Point
is equivalent
+ * to this.x - p.x
, the results for y, z and w are calculated in the same way.
+ *
+ * @param p the Point
to subtract from this Point
+ * @return a Point
resulting from the algebraic operationthis - p
+ * @throws IllegalArgumentException if p
is null
+ */
+ public final Point subtract(Point p)
+ {
+ if (p == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return new Point(this.x - p.x, this.y - p.y, this.z - p.z, 1);
+ }
+
+ /**
+ * Multiplies this Point
by a scalar quantity. This method simply returns a new Point
+ * whose values each equal the old Point
's corresponding value multiplied by this scalar.
+ *
+ * @param s the scalar to be multiplied by
+ * @return a Point
resulting from the scalar multiplication of this
and s
+ */
+ public final Point multiply(double s)
+ {
+ return new Point(this.x * s, this.y * s, this.z * s, 1);
+ }
+
+ public final Point scale(double sx, double sy, double sz)
+ {
+ return new Point(this.x * sx, this.y * sy, this.z * sz, this.w);
+ }
+
+ public final Point normalize()
+ {
+ double s = 1d / this.length();
+ return this.scale(s, s, s);
+ }
+
+ /**
+ * Performs a dot product of the x, y and z coorinates of this
and p
.
+ *
+ * @param p the Point
to perform a dot product with
+ * @return the scalar product of this
and p
+ * @throws IllegalArgumentException if p
is null
+ */
+ public final double dot(Point p)
+ {
+ if (p == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return this.x * p.x + this.y * p.y + this.z * p.z;
+ }
+
+ /**
+ * Performs a dot product of the x, y and z coorinates of this
with itself. This method is equivalent
+ * to this.dot(this)
.
+ *
+ *
+ * A useful characteristic of this method is that the resulting value is the square of this Point
's
+ * distance from ZERO
. Finding the square of the distance from the origin in this manner is preferred
+ * over finding the square by first finding the length and then squaring it because this is faster and less prone to
+ * loss of precision.
+ *
+ * @return this Point
dotted with itself
+ * @see Point#ZERO
+ */
+ public final double selfDot()
+ {
+ return this.x * this.x + this.y * this.y + this.z * this.z;
+ }
+
+ /**
+ * Performs a dot product of all four components of this
and p
.
+ *
+ * @param p the Point
to perform a dot product with
+ * @return the scalar product of this
and p
+ * @throws IllegalArgumentException if p
is null
+ */
+ public final double dot4(Point p)
+ {
+ if (p == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return this.x * p.x + this.y * p.y + this.z * p.z + this.w * this.w;
+ }
+
+ /**
+ * Calculates the distance between this Point
and the origin.
+ *
+ * @return the distance between this Point
and ZERO
+ * @see Point#ZERO
+ */
+ public final double length()
+ {
+ return Math.sqrt(this.selfDot());
+ }
+
+ /**
+ * Calculates the unsigned distance between this Point
and p
.
+ *
+ * @param p the Point
to find the distance from
+ * @return the distance between these two Point
s
+ * @throws IllegalArgumentException if p
is null
+ */
+ public final double distanceTo(Point p)
+ {
+ if (p == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ double dx = this.x - p.x;
+ double dy = this.y - p.y;
+ double dz = this.z - p.z;
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
+ }
+
+ /**
+ * Calculates the squared unsigned distance between this Point
and p
. This method is
+ * useful when actual distances are not required, but some measure is needed for comparison purposes. It avoids the
+ * square root required for computing actual distance.
+ *
+ * @param p the Point
to find the square distance from
+ * @return the square of the distance between these two Point
s
+ * @throws IllegalArgumentException if p
is null
+ */
+ public final double distanceToSquared(Point p)
+ {
+ if (p == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ double dx = this.x - p.x;
+ double dy = this.y - p.y;
+ double dz = this.z - p.z;
+ return (dx * dx + dy * dy + dz * dz);
+ }
+
+ /**
+ * Determines the midpoint of two Point
s.
+ *
+ * @param p1 the first Point
+ * @param p2 the second Point
+ * @return the midpoint of these two Point
s
+ * @throws IllegalArgumentException if either p1
or p2
is null
+ */
+ public static Point midPoint(Point p1, Point p2)
+ {
+ if (p1 == null || p2 == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return new Point(0.5 * (p1.x + p2.x), 0.5 * (p1.y + p2.y), 0.5 * (p1.z + p2.z));
+ }
+
+ /**
+ * Scales a Point
along a vector. The resulting Point
is affected by both the scale factor
+ * and the size of the vector direction. For example, a vector (2,2,2) and a vector (1,1,1) would produce a
+ * different result, if all other variables remain constant. For this reason, programmers may wish to normalize
+ * direction
before calling this function.
+ *
+ * @param scale the factor to be scaled by
+ * @param direction the direction of scaling
+ * @param origin the original Point
+ * @return origin
scaled by scale
in the direction specified
+ * @throws IllegalArgumentException if direction
or origin
is null
+ */
+ public static Point fromOriginAndDirection(double scale, Point direction, Point origin)
+ {
+ if (direction == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DirectionIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (origin == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.OriginIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ double x = scale * direction.x() + origin.x;
+ double y = scale * direction.y() + origin.y;
+ double z = scale * direction.z() + origin.z;
+
+ return new Point(x, y, z);
+ }
+
+ /**
+ * Calculate the extrema of a given array of Point
s. The resulting array is always of length 2, with
+ * the first element containing the minimum extremum, and the second containing the maximum. The minimum extremum is
+ * composed by taking the smallest x, y and z values from all the Point
s in the array. These values are
+ * not necessarily taken from the same Point
. The maximum extrema is composed in the same fashion.
+ *
+ * @param points any array of Point
s
+ * @return a array with length of 2, comprising the most extreme values in the given array
+ * @throws IllegalArgumentException if points
is null
+ */
+ public static Point[] composeExtrema(Point points[])
+ {
+ if (points == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointsArrayIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (points.length == 0)
+ return new Point[] {Point.ZERO, Point.ZERO};
+
+ double xmin = points[0].x;
+ double ymin = points[0].y;
+ double zmin = points[0].z;
+ double xmax = xmin;
+ double ymax = ymin;
+ double zmax = zmin;
+
+ for (int i = 1; i < points.length; i++)
+ {
+ double x = points[i].x;
+ if (x > xmax)
+ {
+ xmax = x;
+ }
+ else if (x < xmin)
+ {
+ xmin = x;
+ }
+
+ double y = points[i].y;
+ if (y > ymax)
+ {
+ ymax = y;
+ }
+ else if (y < ymin)
+ {
+ ymin = y;
+ }
+
+ double z = points[i].z;
+ if (z > zmax)
+ {
+ zmax = z;
+ }
+ else if (z < zmin)
+ {
+ zmin = z;
+ }
+ }
+
+ return new Point[] {new Point(xmin, ymin, zmin), new Point(xmax, ymax, zmax)};
+ }
+
+ /**
+ * Determines the cross product of these two Point
s. This is post multiplied by that.
+ *
+ * @param that the second Point
+ * @return the cross product of two Point
s
+ * @throws IllegalArgumentException if that
is null
+ */
+ public Point cross(Point that)
+ {
+ if (that == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return new Point(this.y * that.z - this.z * that.y, this.z * that.x - this.x * that.z,
+ this.x * that.y - this.y * that.x);
+ }
+
+ /**
+ * Compares this Point
to o
for equality.
+ *
+ * This method makes comparisons on private fields; overriding implementations should include a call to
+ * super.equals()
.
+ *
+ * @param o the Object
to be compared to for equality.
+ * @return true if the contents are equal, false otherwise
+ */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final gov.nasa.worldwind.geom.Point point = (gov.nasa.worldwind.geom.Point) o;
+
+ if (Double.compare(point.w, w) != 0)
+ return false;
+ if (Double.compare(point.x, x) != 0)
+ return false;
+ if (Double.compare(point.y, y) != 0)
+ return false;
+ //noinspection RedundantIfStatement
+ if (Double.compare(point.z, z) != 0)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Generates an integer that is always the same for identical objects, but usually different for different objects.
+ * This method overrides the one in Object
.
+ *
+ * This method makes comparisons on private fields; overriding implementations should include a call to
+ * super.hashCode()
.
+ *
+ * @return the hashCode for this Point
.
+ */
+ @Override
+ public int hashCode()
+ {
+ int result;
+ long temp;
+ temp = x != +0.0d ? Double.doubleToLongBits(x) : 0L;
+ result = (int) (temp ^ (temp >>> 32));
+ temp = y != +0.0d ? Double.doubleToLongBits(y) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = z != +0.0d ? Double.doubleToLongBits(z) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = w != +0.0d ? Double.doubleToLongBits(w) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ /**
+ * Generates a string representation of this object. The returned string has the format "(x, y, z, w)" where x, y, z
+ * and w correspond to their respective values in this Point
.
+ *
+ * @return a string representation of this Point
+ */
+ @Override
+ public final String toString()
+ {
+ return "(" + Double.toString(this.x) + ", " + Double.toString(this.y) + ", " + Double.toString(this.z) + ", "
+ + Double.toString(this.w) + ")";
+ }
+
+ /**
+ * Obtains the amount of memory this Point
consumes.
+ *
+ * @return the memory footprint of this Point
in bytes.
+ */
+ public final long getSizeInBytes()
+ {
+ // we could consider using a constant value here
+ return Double.SIZE << 2;
+ }
+}
diff --git a/gov/nasa/worldwind/geom/PolarPoint.java b/gov/nasa/worldwind/geom/PolarPoint.java
new file mode 100644
index 0000000..5c4b14f
--- /dev/null
+++ b/gov/nasa/worldwind/geom/PolarPoint.java
@@ -0,0 +1,214 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.*;
+
+/**
+ * Represents a point in space defined by a latitude, longitude and distance from the origin.
+ *
+ * Instances of PolarPoint
are immutable.
+ *
+ * @author Tom Gaskins
+ * @version $Id: PolarPoint.java 1754 2007-05-06 23:19:22Z tgaskins $
+ */
+public class PolarPoint
+{
+ public static final PolarPoint ZERO = new PolarPoint(Angle.ZERO, Angle.ZERO, 0d);
+
+ private final Angle latitude;
+ private final Angle longitude;
+ private final double radius;
+
+ /**
+ * Obtains a PolarPoint
from radians and a radius.
+ *
+ * @param latitude the latitude in radians
+ * @param longitude the longitude in radians
+ * @param radius the distance form the center
+ * @return a new PolarPoint
+ */
+ public static PolarPoint fromRadians(double latitude, double longitude, double radius)
+ {
+ return new PolarPoint(Angle.fromRadians(latitude), Angle.fromRadians(longitude), radius);
+ }
+
+ /**
+ * Obtains a PolarPoint
from degrees and a radius.
+ *
+ * @param latitude the latitude in degrees
+ * @param longitude the longitude in degrees
+ * @param radius the distance form the center
+ * @return a new PolarPoint
+ */
+ public static PolarPoint fromDegrees(double latitude, double longitude, double radius)
+ {
+ return new PolarPoint(Angle.fromDegrees(latitude), Angle.fromDegrees(longitude), radius);
+ }
+
+ /**
+ * Obtains a PolarPoint
from a cartesian point.
+ *
+ * @param cartesianPoint the point to convert
+ * @return the cartesian point expressed as a polar point
+ * @throws IllegalArgumentException if cartesianPoint
is null
+ */
+ public static PolarPoint fromCartesian(Point cartesianPoint)
+ {
+ if (cartesianPoint == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return PolarPoint.fromCartesian(cartesianPoint.x(), cartesianPoint.y(), cartesianPoint.z());
+ }
+
+ /**
+ * Obtains a PolarPoint
from cartesian coordinates.
+ *
+ * @param x the x coordinate of the cartesian point
+ * @param y the y coordinate of the cartesian point
+ * @param z the z coordinate of the cartesian point
+ * @return a polar point located at (x,y,z) in cartesian space
+ */
+ public static PolarPoint fromCartesian(double x, double y, double z)
+ {
+ double radius = Math.sqrt(x * x + y * y + z * z);
+ double latRads = Math.atan2(y, Math.sqrt(x * x + z * z));
+ double lonRads = Math.atan2(x, z);
+ return PolarPoint.fromRadians(latRads, lonRads, radius);
+ }
+
+ /**
+ * Obtains a PolarPoint
from two angles
and a radius.
+ *
+ * @param latitude the latitude
+ * @param longitude the longitude
+ * @param radius the distance from the center
+ * @throws IllegalArgumentException if latitude
or longitude
is null
+ */
+ public PolarPoint(Angle latitude, Angle longitude, double radius)
+ {
+ if (latitude == null || longitude == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LatitudeOrLongitudeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.radius = radius;
+ }
+
+ /**
+ * Obtains the latitude of this polar point
+ *
+ * @return this polar point's latitude
+ */
+ public final Angle getLatitude()
+ {
+ return this.latitude;
+ }
+
+ /**
+ * Obtains the longitude of this polar point
+ *
+ * @return this polar point's longitude
+ */
+ public final Angle getLongitude()
+ {
+ return this.longitude;
+ }
+
+ /**
+ * Obtains the radius of this polar point
+ *
+ * @return the distance from this polar point to its origin
+ */
+ public final double getRadius()
+ {
+ return radius;
+ }
+
+ /**
+ * Obtains a cartesian point equivalent to this PolarPoint
, except in cartesian space.
+ *
+ * @return this polar point in cartesian coordinates
+ */
+ public final Point toCartesian()
+ {
+ return toCartesian(this.latitude, this.longitude, this.radius);
+ }
+
+ /**
+ * Obtains a cartesian point from a given latitude, longitude and distance from center. This method is equivalent
+ * to, but may perform faster than Point p = new PolarPoint(latitude, longitude, radius).toCartesian()
+ *
+ * @param latitude the latitude
+ * @param longitude the longitude
+ * @param radius the distance from the origin
+ * @return a cartesian point from two angles and a radius
+ * @throws IllegalArgumentException if latitude
or longitude
is null
+ */
+ public static Point toCartesian(Angle latitude, Angle longitude, double radius)
+ {
+ if (latitude == null || longitude == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LatitudeOrLongitudeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ double x = radius * longitude.sin() * latitude.cos();
+ double y = radius * latitude.sin();
+ double z = radius * longitude.cos() * latitude.cos();
+ return new Point(x, y, z);
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final gov.nasa.worldwind.geom.PolarPoint that = (gov.nasa.worldwind.geom.PolarPoint) o;
+
+ if (Double.compare(that.radius, radius) != 0)
+ return false;
+ if (!latitude.equals(that.latitude))
+ return false;
+ //noinspection RedundantIfStatement
+ if (!longitude.equals(that.longitude))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result;
+ long temp;
+ result = latitude.hashCode();
+ result = 29 * result + longitude.hashCode();
+ temp = radius != +0.0d ? Double.doubleToLongBits(radius) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "(lat: " + this.latitude.toString() + ", lon: " + this.longitude.toString() + ", r: " + this.radius
+ + ")";
+ }
+}
diff --git a/gov/nasa/worldwind/geom/Polyline.java b/gov/nasa/worldwind/geom/Polyline.java
new file mode 100644
index 0000000..d882719
--- /dev/null
+++ b/gov/nasa/worldwind/geom/Polyline.java
@@ -0,0 +1,350 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import com.sun.opengl.util.*;
+import gov.nasa.worldwind.*;
+
+import javax.media.opengl.*;
+import java.awt.*;
+import java.nio.*;
+import java.util.*;
+import java.util.List;
+
+/**
+ * @author tag
+ * @version $Id: Polyline.java 1787 2007-05-08 17:11:30Z dcollins $
+ */
+public class Polyline implements Renderable
+{
+ private ArrayList positions;
+ private Point referenceCenter;
+ private DoubleBuffer vertices;
+ private int antiAliasHint = GL.GL_FASTEST;
+ private Color color = Color.WHITE;
+ private boolean filled = false; // makes it a polygon
+ private boolean followGreatCircles = true;
+ private int numEdgeIntervals = 10;
+
+ public Polyline(Iterable positions)
+ {
+ if (positions == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PositionsListIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.setPositions(positions);
+ }
+
+ public Polyline(Iterable positions, double elevation)
+ {
+ if (positions == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PositionsListIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.setPositions(positions, elevation);
+ }
+
+ public Color getColor()
+ {
+ return color;
+ }
+
+ public void setColor(Color color)
+ {
+ if (color == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.ColorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.color = color;
+ }
+
+ public int getAntiAliasHint()
+ {
+ return antiAliasHint;
+ }
+
+ public void setAntiAliasHint(int hint)
+ {
+ if (!(hint == GL.GL_DONT_CARE || hint == GL.GL_FASTEST || hint == GL.GL_NICEST))
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.InvalidHint");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.antiAliasHint = hint;
+ }
+
+ public boolean isFilled()
+ {
+ return filled;
+ }
+
+ public void setFilled(boolean filled)
+ {
+ this.filled = filled;
+ }
+
+ public boolean isFollowGreatCircles()
+ {
+ return followGreatCircles;
+ }
+
+ public void setFollowGreatCircles(boolean followGreatCircles)
+ {
+ this.followGreatCircles = followGreatCircles;
+ }
+
+ public int getNumEdgeIntervals()
+ {
+ return numEdgeIntervals;
+ }
+
+ public void setNumEdgeIntervals(int numEdgeIntervals)
+ {
+ this.numEdgeIntervals = numEdgeIntervals;
+ }
+
+ public void setPositions(Iterable inPositions)
+ {
+ if (inPositions == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PositionsListIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.positions = new ArrayList();
+ for (Position position : inPositions)
+ {
+ this.positions.add(position);
+ }
+
+ if ((this.filled && this.positions.size() < 3) || (!this.filled && this.positions.size() < 2))
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.InsufficientPositions");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.vertices = null;
+ }
+
+ public void setPositions(Iterable inPositions, double elevation)
+ {
+ if (inPositions == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PositionsListIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.positions = new ArrayList();
+ for (LatLon position : inPositions)
+ {
+ this.positions.add(new Position(position.getLatitude(), position.getLongitude(), elevation));
+ }
+
+ if (this.positions.size() < 2 || (this.filled && this.positions.size() < 3))
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.InsufficientPositions");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.vertices = null;
+ }
+
+ public Iterable getPositions()
+ {
+ return this.positions;
+ }
+
+ private void intializeGeometry(DrawContext dc)
+ {
+ if (this.positions.size() < 2)
+ return;
+
+ double[] refCenter = new double[3];
+ if (this.followGreatCircles)
+ this.vertices = this.makeGreatCircleVertices(dc, this.positions, refCenter);
+ else
+ this.vertices = makeVertices(dc, this.positions, refCenter);
+ this.referenceCenter = new Point(refCenter[0], refCenter[1], refCenter[2]);
+
+ double avgX = this.referenceCenter.getX();
+ double avgY = this.referenceCenter.getY();
+ double avgZ = this.referenceCenter.getZ();
+
+ this.vertices.rewind();
+ for (int i = 0; i < this.vertices.limit(); i += 3)
+ {
+ this.vertices.put(i, this.vertices.get(i) - avgX);
+ this.vertices.put(i + 1, this.vertices.get(i + 1) - avgY);
+ this.vertices.put(i + 2, this.vertices.get(i + 2) - avgZ);
+ }
+ }
+
+ protected DoubleBuffer makeVertices(DrawContext dc, List posList, double[] refCenter)
+ {
+ DoubleBuffer verts = BufferUtil.newDoubleBuffer(posList.size() * 3);
+
+ double avgX = 0;
+ double avgY = 0;
+ double avgZ = 0;
+
+ int n = 0;
+ for (Position p : posList)
+ {
+ Point pt = dc.getGlobe().computePointFromPosition(p.getLatitude(), p.getLongitude(), p.getElevation());
+ verts.put(pt.x()).put(pt.y()).put(pt.z());
+ avgX += pt.x();
+ avgY += pt.y();
+ avgZ += pt.z();
+ ++n;
+ }
+
+ refCenter[0] = avgX / (double) n;
+ refCenter[1] = avgY / (double) n;
+ refCenter[2] = avgZ / (double) n;
+
+ return verts;
+ }
+
+ protected DoubleBuffer makeGreatCircleVertices(DrawContext dc, List posList, double[] refCenter)
+ {
+ if (posList.size() < 1)
+ return null;
+
+ int size = posList.size() + (this.numEdgeIntervals - 1) * (posList.size() - 1);
+ DoubleBuffer verts = BufferUtil.newDoubleBuffer(size * 3);
+
+ double avgX = 0;
+ double avgY = 0;
+ double avgZ = 0;
+ int n = 0;
+
+ Iterator iter = posList.iterator();
+ Position pos = iter.next();
+ Point pt = dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), pos.getElevation());
+ verts.put(pt.x());
+ verts.put(pt.y());
+ verts.put(pt.z());
+ avgX += pt.x();
+ avgY += pt.y();
+ avgZ += pt.z();
+ ++n;
+
+ double delta = 1d / this.numEdgeIntervals;
+ while (iter.hasNext())
+ {
+ Position posNext = iter.next();
+ for (int i = 1; i < numEdgeIntervals; i++)
+ {
+ LatLon ll = LatLon.interpolate(i * delta, new LatLon(pos.getLatitude(), pos.getLongitude()),
+ new LatLon(posNext.getLatitude(), posNext.getLongitude()));
+ double elevation = (i * delta) * pos.getElevation() + (1d - i * delta) * posNext.getElevation();
+ pt = dc.getGlobe().computePointFromPosition(ll.getLatitude(), ll.getLongitude(), elevation);
+ verts.put(pt.x()).put(pt.y()).put(pt.z());
+ avgX += pt.x();
+ avgY += pt.y();
+ avgZ += pt.z();
+ ++n;
+ }
+
+ pt = dc.getGlobe().computePointFromPosition(posNext.getLatitude(), posNext.getLongitude(),
+ pos.getElevation());
+ verts.put(pt.x()).put(pt.y()).put(pt.z());
+ avgX += pt.x();
+ avgY += pt.y();
+ avgZ += pt.z();
+ ++n;
+
+ pos = posNext;
+ }
+
+ refCenter[0] = avgX / (double) n;
+ refCenter[1] = avgY / (double) n;
+ refCenter[2] = avgZ / (double) n;
+
+ return verts;
+ }
+
+ public void render(DrawContext dc)
+ {
+ if (dc == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ if (this.vertices == null)
+ {
+ this.intializeGeometry(dc);
+
+ if (this.vertices == null)
+ return; // TODO: log a warning
+ }
+
+ GL gl = dc.getGL();
+
+ int attrBits = GL.GL_HINT_BIT | GL.GL_CURRENT_BIT;
+ if (!dc.isPickingMode())
+ {
+ attrBits += GL.GL_CURRENT_BIT;
+ if (this.color.getAlpha() != 255)
+ attrBits += GL.GL_COLOR_BUFFER_BIT;
+ }
+
+ gl.glPushAttrib(attrBits);
+ gl.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT);
+ dc.getView().pushReferenceCenter(dc, this.referenceCenter);
+
+ try
+ {
+ if (!dc.isPickingMode())
+ {
+ if (this.color.getAlpha() != 255)
+ {
+ gl.glEnable(GL.GL_BLEND);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+ }
+ dc.getGL().glColor4ub((byte) this.color.getRed(), (byte) this.color.getGreen(),
+ (byte) this.color.getBlue(), (byte) this.color.getAlpha());
+ }
+
+ int hintAttr = GL.GL_LINE_SMOOTH_HINT;
+ if (this.filled)
+ hintAttr = GL.GL_POLYGON_SMOOTH_HINT;
+ gl.glHint(hintAttr, this.antiAliasHint);
+
+ gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
+ gl.glVertexPointer(3, GL.GL_DOUBLE, 0, this.vertices.rewind());
+
+ int primType = GL.GL_LINE_STRIP;
+ if (this.filled)
+ primType = GL.GL_POLYGON;
+ gl.glDrawArrays(primType, 0, this.vertices.capacity() / 3);
+ }
+ finally
+ {
+ gl.glPopClientAttrib();
+ gl.glPopAttrib();
+ dc.getView().popReferenceCenter(dc);
+ }
+ }
+}
diff --git a/gov/nasa/worldwind/geom/Position.java b/gov/nasa/worldwind/geom/Position.java
new file mode 100644
index 0000000..99d759f
--- /dev/null
+++ b/gov/nasa/worldwind/geom/Position.java
@@ -0,0 +1,76 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.*;
+
+/**
+ * @author tag
+ * @version $Id: Position.java 1755 2007-05-06 23:38:05Z tgaskins $
+ */
+public class Position
+{
+ private final Angle latitude;
+ private final Angle longitude;
+ private final double elevation;
+
+ public static final Position ZERO = new Position(Angle.ZERO, Angle.ZERO, 0d);
+
+ public static Position fromRadians(double latitude, double longitude, double elevation)
+ {
+ return new Position(Angle.fromRadians(latitude), Angle.fromRadians(longitude), elevation);
+ }
+
+ public static Position fromDegrees(double latitude, double longitude, double elevation)
+ {
+ return new Position(Angle.fromDegrees(latitude), Angle.fromDegrees(longitude), elevation);
+ }
+
+ public Position(Angle latitude, Angle longitude, double elevation)
+ {
+ if (latitude == null || longitude == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LatitudeOrLongitudeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.elevation = elevation;
+ }
+
+ /**
+ * Obtains the latitude of this position
+ *
+ * @return this position's latitude
+ */
+ public final Angle getLatitude()
+ {
+ return this.latitude;
+ }
+
+ /**
+ * Obtains the longitude of this position
+ *
+ * @return this position's longitude
+ */
+ public final Angle getLongitude()
+ {
+ return this.longitude;
+ }
+
+ /**
+ * Obtains the elevation of this position
+ *
+ * @return this position's elevation
+ */
+ public final double getElevation()
+ {
+ return this.elevation;
+ }
+}
diff --git a/gov/nasa/worldwind/geom/Quadrilateral.java b/gov/nasa/worldwind/geom/Quadrilateral.java
new file mode 100644
index 0000000..e7bc7ae
--- /dev/null
+++ b/gov/nasa/worldwind/geom/Quadrilateral.java
@@ -0,0 +1,190 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import com.sun.opengl.util.*;
+import gov.nasa.worldwind.*;
+
+import javax.media.opengl.*;
+import java.awt.*;
+import java.nio.*;
+
+/**
+ * @author tag
+ * @version $Id: Quadrilateral.java 1787 2007-05-08 17:11:30Z dcollins $
+ */
+public class Quadrilateral implements Renderable
+{
+ private LatLon southwestCorner;
+ private LatLon northeastCorner;
+ private double elevation;
+ private Point referenceCenter;
+ private DoubleBuffer vertices;
+ private int antiAliasHint = GL.GL_FASTEST;
+ private Color color = Color.WHITE;
+
+ public Quadrilateral(LatLon southwestCorner, LatLon northeastCorner, double elevation)
+ {
+ if (southwestCorner == null || northeastCorner == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PositionIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.southwestCorner = southwestCorner;
+ this.northeastCorner = northeastCorner;
+ this.elevation = elevation;
+ }
+
+ public Color getColor()
+ {
+ return color;
+ }
+
+ public void setColor(Color color)
+ {
+ if (color == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.ColorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.color = color;
+ }
+
+ public int getAntiAliasHint()
+ {
+ return antiAliasHint;
+ }
+
+ public void setAntiAliasHint(int hint)
+ {
+ if (!(hint == GL.GL_DONT_CARE || hint == GL.GL_FASTEST || hint == GL.GL_NICEST))
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.InvalidHint");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.antiAliasHint = hint;
+ }
+
+ public void setCorners(LatLon southWest, LatLon northEast)
+ {
+ this.southwestCorner = southWest;
+ this.northeastCorner = northEast;
+ this.vertices = null;
+ }
+
+ public LatLon[] getCorners()
+ {
+ LatLon[] retVal = new LatLon[2];
+
+ retVal[0] = this.southwestCorner;
+ retVal[1] = this.northeastCorner;
+
+ return retVal;
+ }
+
+ public double getElevation()
+ {
+ return elevation;
+ }
+
+ public void setElevation(double elevation)
+ {
+ this.elevation = elevation;
+ this.vertices = null;
+ }
+
+ private void intializeGeometry(DrawContext dc)
+ {
+ DoubleBuffer verts = BufferUtil.newDoubleBuffer(12);
+
+ Point[] p = new Point[4];
+
+ p[0] = dc.getGlobe().computePointFromPosition(this.southwestCorner.getLatitude(),
+ this.southwestCorner.getLongitude(), this.elevation);
+ p[1] = dc.getGlobe().computePointFromPosition(this.southwestCorner.getLatitude(),
+ this.northeastCorner.getLongitude(), this.elevation);
+ p[2] = dc.getGlobe().computePointFromPosition(this.northeastCorner.getLatitude(),
+ this.northeastCorner.getLongitude(), this.elevation);
+ p[3] = dc.getGlobe().computePointFromPosition(this.northeastCorner.getLatitude(),
+ this.southwestCorner.getLongitude(), this.elevation);
+
+ Point refcenter = Point.midPoint(p[0], p[2]);
+
+ for (int i = 0; i < 4; i++)
+ {
+ verts.put(p[i].x() - refcenter.x());
+ verts.put(p[i].y() - refcenter.y());
+ verts.put(p[i].z() - refcenter.z());
+ }
+
+ this.referenceCenter = refcenter;
+ this.vertices = verts;
+ }
+
+ public void render(DrawContext dc)
+ {
+ if (dc == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ if (this.vertices == null)
+ {
+ this.intializeGeometry(dc);
+
+ if (this.vertices == null)
+ return; // TODO: log a warning
+ }
+
+ GL gl = dc.getGL();
+
+ int attrBits = GL.GL_HINT_BIT | GL.GL_CURRENT_BIT;
+ if (!dc.isPickingMode())
+ {
+ attrBits += GL.GL_CURRENT_BIT;
+ if (this.color.getAlpha() != 255)
+ attrBits += GL.GL_COLOR_BUFFER_BIT;
+ }
+
+ gl.glPushAttrib(attrBits);
+ gl.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT);
+ dc.getView().pushReferenceCenter(dc, this.referenceCenter);
+
+ try
+ {
+ if (!dc.isPickingMode())
+ {
+ if (this.color.getAlpha() != 255)
+ {
+ gl.glEnable(GL.GL_BLEND);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+ }
+ dc.getGL().glColor4ub((byte) this.color.getRed(), (byte) this.color.getGreen(),
+ (byte) this.color.getBlue(), (byte) this.color.getAlpha());
+ }
+
+ gl.glHint(GL.GL_POLYGON_SMOOTH_HINT, this.antiAliasHint);
+ gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
+ gl.glVertexPointer(3, GL.GL_DOUBLE, 0, this.vertices.rewind());
+ gl.glDrawArrays(GL.GL_QUADS, 0, 4);
+ }
+ finally
+ {
+ gl.glPopClientAttrib();
+ gl.glPopAttrib();
+ dc.getView().popReferenceCenter(dc);
+ }
+ }
+}
diff --git a/gov/nasa/worldwind/geom/Quaternion.java b/gov/nasa/worldwind/geom/Quaternion.java
new file mode 100644
index 0000000..98f26eb
--- /dev/null
+++ b/gov/nasa/worldwind/geom/Quaternion.java
@@ -0,0 +1,577 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+/**
+ * @author Chris Maxwell
+ * @version $Id: Quaternion.java 1749 2007-05-06 19:48:14Z tgaskins $
+ */
+public class Quaternion
+{
+ private final double x;
+ private final double y;
+ private final double z;
+ private final double w;
+ private final static double epsilon = 0.0001;
+
+ public Quaternion(double x, double y, double z, double w)
+ {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final gov.nasa.worldwind.geom.Quaternion quaternion = (gov.nasa.worldwind.geom.Quaternion) o;
+
+ if (Double.compare(quaternion.w, w) != 0)
+ return false;
+ if (Double.compare(quaternion.x, x) != 0)
+ return false;
+ if (Double.compare(quaternion.y, y) != 0)
+ return false;
+ if (Double.compare(quaternion.z, z) != 0)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Generates an integer that is always the same for identical objects, but usually different for different objects.
+ * This method overrides the one in Object
.
+ *
+ * This method makes comparisons on private fields; overriding implementations should include a call to
+ * super.hashCode()
.
+ *
+ * @return the hashCode for this Point
.
+ */
+ @Override
+ public int hashCode()
+ {
+ int result;
+ long temp;
+ temp = x != +0.0d ? Double.doubleToLongBits(x) : 0L;
+ result = (int) (temp ^ (temp >>> 32));
+ temp = y != +0.0d ? Double.doubleToLongBits(y) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = z != +0.0d ? Double.doubleToLongBits(z) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ temp = w != +0.0d ? Double.doubleToLongBits(w) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public final String toString()
+ {
+ return "(" + Double.toString(this.x) + ", " + Double.toString(this.y) + ", " + Double.toString(
+ this.z) + ", " + Double.toString(this.w) + ")";
+ }
+
+ public static Quaternion EulerToQuaternion(double yaw, double pitch, double roll)
+ {
+ double cy = Math.cos(yaw * 0.5);
+ double cp = Math.cos(pitch * 0.5);
+ double cr = Math.cos(roll * 0.5);
+ double sy = Math.sin(yaw * 0.5);
+ double sp = Math.sin(pitch * 0.5);
+ double sr = Math.sin(roll * 0.5);
+
+ double qw = cy * cp * cr + sy * sp * sr;
+ double qx = sy * cp * cr - cy * sp * sr;
+ double qy = cy * sp * cr + sy * cp * sr;
+ double qz = cy * cp * sr - sy * sp * cr;
+
+ return new Quaternion(qx, qy, qz, qw);
+ }
+
+ /**
+ * Transforms a rotation in quaternion form to a set of Euler angles
+ *
+ * @param q
+ * @return The rotation transformed to Euler angles, X=Yaw, Y=Pitch, Z=Roll (radians)
+ * @throws IllegalArgumentException if q
is null
+ */
+ public static Point QuaternionToEuler(Quaternion q)
+ {
+ if (q == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ double q0 = q.w;
+ double q1 = q.x;
+ double q2 = q.y;
+ double q3 = q.z;
+
+ double x = Math.atan2(2 * (q2 * q3 + q0 * q1), (q0 * q0 - q1 * q1 - q2 * q2 + q3 * q3));
+ double y = Math.asin(-2 * (q1 * q3 - q0 * q2));
+ double z = Math.atan2(2 * (q1 * q2 + q0 * q3), (q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3));
+
+ return new Point(x, y, z);
+ }
+
+ public static Quaternion AxisAngleToQuaternion(Angle angle, double x, double y, double z)
+ {
+ if (angle == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ double length = Math.sqrt(x * x + y * y + z * z);
+ if (length > 0 && length != 1)
+ {
+ x = x / length;
+ y = y / length;
+ z = z / length;
+ }
+ double sinAngle = angle.sinHalfAngle();
+ double cosAngle = angle.cosHalfAngle();
+ return new Quaternion(x * sinAngle, y * sinAngle, z * sinAngle, cosAngle);
+ }
+
+ public static Point QuaternionToAxisAngle(Quaternion q)
+ {
+ if (q == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ double x, y, z;
+ q = q.Normalize();
+ double s = Math.sqrt(q.x * q.x + q.y * q.y + q.x * q.z);
+ if (s > 0)
+ {
+ x = q.x / s;
+ y = q.y / s;
+ z = q.z / s;
+
+ }
+ else
+ {
+ x = q.x;
+ y = q.y;
+ z = q.z;
+ }
+ double angle = 2 * Math.acos(q.w);
+ return new Point(x, y, z, angle);
+ }
+
+ /**
+ * @param a
+ * @param b
+ * @return
+ * @throws IllegalArgumentException if a
or b
is null
+ */
+ public static Quaternion Add(Quaternion a, Quaternion b)
+ {
+ if (a == null || b == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return new Quaternion(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w);
+ }
+
+ /**
+ * @param a
+ * @param b
+ * @return
+ * @throws IllegalArgumentException if a
or b
is null
+ */
+ public static Quaternion Subtract(Quaternion a, Quaternion b)
+ {
+ if (a == null || b == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return new Quaternion(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w);
+ }
+
+ /**
+ * @param a
+ * @param b
+ * @return
+ * @throws IllegalArgumentException if a
or b
is null
+ */
+ public static Quaternion Multiply(Quaternion a, Quaternion b)
+ {
+ if (a == null || b == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return new Quaternion(
+ a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
+ a.w * b.y + a.y * b.w + a.z * b.x - a.x * b.z,
+ a.w * b.z + a.z * b.w + a.x * b.y - a.y * b.x,
+ a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z);
+ }
+
+ /**
+ * @param q
+ * @return
+ * @throws IllegalArgumentException if q
is null
+ */
+ public static Quaternion Multiply(double s, Quaternion q)
+ {
+ if (q == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return new Quaternion(s * q.x, s * q.y, s * q.z, s * q.w);
+ }
+
+ /**
+ * @param q
+ * @param s
+ * @return
+ * @throws IllegalArgumentException if q
is null
+ */
+ public static Quaternion Multiply(Quaternion q, double s)
+ {
+ if (q == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return new Quaternion(s * q.x, s * q.y, s * q.z, s * q.w);
+ }
+
+ /**
+ * equivalent to multiplying by the quaternion (0, v)
+ *
+ * @param v
+ * @param q
+ * @return
+ * @throws IllegalArgumentException if q
or v
is null
+ */
+ public static Quaternion Multiply(Point v, Quaternion q)
+ {
+ if (v == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.VectorIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (q == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return new Quaternion(
+ v.x() * q.w + v.y() * q.z - v.z() * q.y,
+ v.y() * q.w + v.z() * q.x - v.x() * q.z,
+ v.z() * q.w + v.x() * q.y - v.y() * q.x,
+ -v.x() * q.x - v.y() * q.y - v.z() * q.z);
+ }
+
+ /**
+ * @param q
+ * @param s
+ * @return
+ * @throws IllegalArgumentException if q
is null
+ */
+ public static Quaternion Divide(Quaternion q, double s)
+ {
+ if (q == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return Quaternion.Multiply(q, (1 / s));
+ }
+
+ // conjugate operator
+ public Quaternion Conjugate()
+ {
+ return new Quaternion(-x, -y, -z, w);
+ }
+
+ /**
+ * @param q
+ * @return
+ * @throws IllegalArgumentException if q
is null
+ */
+ public static double Norm2(Quaternion q)
+ {
+ if (q == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w;
+ }
+
+ /**
+ * @param q
+ * @return
+ * @throws IllegalArgumentException if q
is null
+ */
+ public static double Abs(Quaternion q)
+ {
+ if (q == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return Math.sqrt(Norm2(q));
+ }
+
+ /**
+ * @param a
+ * @param b
+ * @return
+ * @throws IllegalArgumentException if a
or b
is null
+ */
+ public static Quaternion Divide(Quaternion a, Quaternion b)
+ {
+ if (a == null || b == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return Quaternion.Multiply(a, Quaternion.Divide(b.Conjugate(), Abs(b)));
+ }
+
+ /**
+ * @param a
+ * @param b
+ * @return
+ * @throws IllegalArgumentException if a
or b
is null
+ */
+ public static double Dot(Quaternion a, Quaternion b)
+ {
+ if (a == null || b == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
+ }
+
+ public Quaternion Normalize()
+ {
+ double L = this.Length();
+
+ return new Quaternion(
+ x / L,
+ y / L,
+ z / L,
+ w / L);
+ }
+
+ public double Length()
+ {
+ return Math.sqrt(
+ x * x + y * y + z * z + w * w);
+ }
+
+ /**
+ * @param q0
+ * @param q1
+ * @param t
+ * @return
+ * @throws IllegalArgumentException if either supplied Quaternion
is null
+ */
+ public static Quaternion Slerp(Quaternion q0, Quaternion q1, double t)
+ {
+ if (q0 == null || q1 == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ double cosom = q0.x * q1.x + q0.y * q1.y + q0.z * q1.z + q0.w * q1.w;
+ double tmp0, tmp1, tmp2, tmp3;
+ if (cosom < 0.0)
+ {
+ cosom = -cosom;
+ tmp0 = -q1.x;
+ tmp1 = -q1.y;
+ tmp2 = -q1.z;
+ tmp3 = -q1.w;
+ }
+ else
+ {
+ tmp0 = q1.x;
+ tmp1 = q1.y;
+ tmp2 = q1.z;
+ tmp3 = q1.w;
+ }
+
+ /* calc coeffs */
+ double scale0, scale1;
+
+ if ((1.0 - cosom) > epsilon)
+ {
+ // standard case (slerp)
+ double omega = Math.acos(cosom);
+ double sinom = Math.sin(omega);
+ scale0 = Math.sin((1.0 - t) * omega) / sinom;
+ scale1 = Math.sin(t * omega) / sinom;
+ }
+ else
+ {
+ /* just lerp */
+ scale0 = 1.0 - t;
+ scale1 = t;
+ }
+
+ return new Quaternion(
+ scale0 * q0.x + scale1 * tmp0,
+ scale0 * q0.y + scale1 * tmp1,
+ scale0 * q0.z + scale1 * tmp2,
+ scale0 * q0.w + scale1 * tmp3);
+ }
+
+ public Quaternion Ln()
+ {
+ return Ln(this);
+ }
+
+ /**
+ * @param q
+ * @return
+ * @throws IllegalArgumentException if q
is null
+ */
+ public static Quaternion Ln(Quaternion q)
+ {
+ if (q == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ double t;
+
+ double s = Math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z);
+ double om = Math.atan2(s, q.w);
+
+ if (Math.abs(s) < epsilon)
+ t = 0.0f;
+ else
+ t = om / s;
+
+ return new Quaternion(q.x * t, q.y * t, q.z * t, 0.0f);
+ }
+
+ /*****************the below functions have not been certified to work properly ******************/
+
+ /**
+ * @param q
+ * @return
+ * @throws IllegalArgumentException if q
is null
+ */
+ public static Quaternion Exp(Quaternion q)
+ {
+ if (q == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ double sinom;
+ double om = Math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z);
+
+ if (Math.abs(om) < epsilon)
+ sinom = 1.0;
+ else
+ sinom = Math.sin(om) / om;
+
+ return new Quaternion(q.x * sinom, q.y * sinom, q.z * sinom, Math.cos(om));
+ }
+
+ public Quaternion Exp()
+ {
+ return Ln(this);
+ }
+
+ /**
+ * @param q1
+ * @param a
+ * @param b
+ * @param c
+ * @param t
+ * @return
+ * @throws IllegalArgumentException if any argument is null
+ */
+ public static Quaternion Squad(
+ Quaternion q1,
+ Quaternion a,
+ Quaternion b,
+ Quaternion c,
+ double t)
+ {
+ if (q1 == null || a == null || b == null || c == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return Slerp(
+ Slerp(q1, c, t), Slerp(a, b, t), 2 * t * (1.0 - t));
+ }
+
+ //TODO: this needs to be accounted for before Squad() is used
+ /*public static Quaternion[] SquadSetup(
+ Quaternion4d q0,
+ Quaternion4d q1,
+ Quaternion4d q2,
+ Quaternion4d q3)
+ {
+ if(q0 == null || q1 == null || q2 == null || q3 == null)
+ {
+ String msg = gov.nasa.worldwind.WorldWind.retrieveErrMsg("nullValue.QuaternionIsNull");
+ gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ q0 = q0 + q1;
+ q0.Normalize();
+
+ q2 = q2 + q1;
+ q2.Normalize();
+
+ q3 = q3 + q1;
+ q3.Normalize();
+
+ q1.Normalize();
+
+ Quaternion[] ret = new Quaternion[3];
+
+ ret[0] = q1 * Exp(-0.25 * (Ln(Exp(q1) * q2) + Ln(Exp(q1) * q0))); // outA
+ ret[1] = q2 * Exp(-0.25 * (Ln(Exp(q2) * q3) + Ln(Exp(q2) * q1))); // outB
+ ret[2] = q2; // outC
+
+ }*/
+}
diff --git a/gov/nasa/worldwind/geom/Sector.java b/gov/nasa/worldwind/geom/Sector.java
new file mode 100644
index 0000000..640f3ad
--- /dev/null
+++ b/gov/nasa/worldwind/geom/Sector.java
@@ -0,0 +1,725 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.*;
+
+/**
+ * Sector
represents a rectangular reqion of latitude and longitude. The region is defined by four angles:
+ * its minimum and maximum latitude, its minimum and maximum longitude. The angles are assumed to be normalized to +/-
+ * 90 degrees latitude and +/- 180 degrees longitude. The minimums and maximums are relative to these ranges, e.g., -80
+ * is less than 20. Behavior of the class is undefined for angles outside these ranges. Normalization is not performed
+ * on the angles by this class, nor is it verifed by the class' methods. See {@link Angle} for a description of
+ * specifying angles.
+ *
+ * Sector
instances are immutable.
+ *
+ * @author Tom Gaskins
+ * @version $Id: Sector.java 1749 2007-05-06 19:48:14Z tgaskins $
+ * @see Angle
+ */
+public class Sector implements Cacheable, Comparable
+{
+ /**
+ * A Sector
of latitude [-90 degrees, + 90 degrees] and longitude [-180 degrees, + 180 degrees].
+ */
+ public static final Sector FULL_SPHERE = new Sector(Angle.NEG90, Angle.POS90, Angle.NEG180, Angle.POS180);
+ public static final Sector EMPTY_SECTOR = new Sector(Angle.ZERO, Angle.ZERO, Angle.ZERO, Angle.ZERO);
+
+ private final Angle minLatitude;
+ private final Angle maxLatitude;
+ private final Angle minLongitude;
+ private final Angle maxLongitude;
+ private final Angle deltaLat;
+ private final Angle deltaLon;
+
+ /**
+ * Creates a new Sector
and initializes it to the specified angles. The angles are assumed to be
+ * normalized to +/- 90 degrees latitude and +/- 180 degrees longitude, but this method does not verify that.
+ *
+ * @param minLatitude the sector's minimum latitude in degrees.
+ * @param maxLatitude the sector's maximum latitude in degrees.
+ * @param minLongitude the sector's minimum longitude in degrees.
+ * @param maxLongitude the sector's maximum longitude in degrees.
+ * @return the new Sector
+ */
+ public static Sector fromDegrees(double minLatitude, double maxLatitude, double minLongitude,
+ double maxLongitude)
+ {
+ return new Sector(Angle.fromDegrees(minLatitude), Angle.fromDegrees(maxLatitude), Angle.fromDegrees(
+ minLongitude), Angle.fromDegrees(maxLongitude));
+ }
+
+ /**
+ * Creates a new Sector
and initializes it to the specified angles. The angles are assumed to be
+ * normalized to +/- \u03c0/2 radians latitude and +/- \u03c0 radians longitude, but this method does not verify
+ * that.
+ *
+ * @param minLatitude the sector's minimum latitude in radians.
+ * @param maxLatitude the sector's maximum latitude in radians.
+ * @param minLongitude the sector's minimum longitude in radians.
+ * @param maxLongitude the sector's maximum longitude in radians.
+ * @return the new Sector
+ */
+ public static Sector fromRadians(double minLatitude, double maxLatitude, double minLongitude,
+ double maxLongitude)
+ {
+ return new Sector(Angle.fromRadians(minLatitude), Angle.fromRadians(maxLatitude), Angle.fromRadians(
+ minLongitude), Angle.fromRadians(maxLongitude));
+ }
+
+ public static Sector boundingSector(java.util.Iterator positions)
+ {
+ if (positions == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.TracksPointsIteratorNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (!positions.hasNext())
+ return EMPTY_SECTOR;
+
+ TrackPoint position = positions.next();
+ double minLat = position.getLatitude();
+ double minLon = position.getLongitude();
+ double maxLat = minLat;
+ double maxLon = minLon;
+
+ while (positions.hasNext())
+ {
+ TrackPoint p = positions.next();
+ double lat = p.getLatitude();
+ if (lat < minLat)
+ minLat = lat;
+ else if (lat > maxLat)
+ maxLat = lat;
+
+ double lon = p.getLongitude();
+ if (lon < minLon)
+ minLon = lon;
+ else if (lon > maxLon)
+ maxLon = lon;
+ }
+
+ return Sector.fromDegrees(minLat, maxLat, minLon, maxLon);
+ }
+
+ public static Sector boundingSector(Iterable positions)
+ {
+ if (positions == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PositionsListIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ double minLat = Angle.POS90.getDegrees();
+ double minLon = Angle.POS180.getDegrees();
+ double maxLat = Angle.NEG180.getDegrees();
+ double maxLon = Angle.NEG180.getDegrees();
+
+ for (LatLon p : positions)
+ {
+ double lat = p.getLatitude().getDegrees();
+ if (lat < minLat)
+ minLat = lat;
+ if (lat > maxLat)
+ maxLat = lat;
+
+ double lon = p.getLongitude().getDegrees();
+ if (lon < minLon)
+ minLon = lon;
+ if (lon > maxLon)
+ maxLon = lon;
+ }
+
+ if (minLat == maxLat && minLon == maxLon)
+ return EMPTY_SECTOR;
+
+ return Sector.fromDegrees(minLat, maxLat, minLon, maxLon);
+ }
+
+ /**
+ * Creates a new Sector
and initializes it to the specified angles. The angles are assumed to be
+ * normalized to +/- 90 degrees latitude and +/- 180 degrees longitude, but this method does not verify that.
+ *
+ * @param minLatitude the sector's minimum latitude.
+ * @param maxLatitude the sector's maximum latitude.
+ * @param minLongitude the sector's minimum longitude.
+ * @param maxLongitude the sector's maximum longitude.
+ * @throws IllegalArgumentException if any of the angles are null
+ */
+ public Sector(Angle minLatitude, Angle maxLatitude, Angle minLongitude, Angle maxLongitude)
+ {
+ if (minLatitude == null || maxLatitude == null || minLongitude == null || maxLongitude == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.InputAnglesNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.minLatitude = minLatitude;
+ this.maxLatitude = maxLatitude;
+ this.minLongitude = minLongitude;
+ this.maxLongitude = maxLongitude;
+ this.deltaLat = Angle.fromDegrees(this.maxLatitude.degrees - this.minLatitude.degrees);
+ this.deltaLon = Angle.fromDegrees(this.maxLongitude.degrees - this.minLongitude.degrees);
+ }
+
+ public Sector(Sector sector)
+ {
+ if (sector == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.SectorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.minLatitude = new Angle(sector.getMinLatitude());
+ this.maxLatitude = new Angle(sector.getMaxLatitude());
+ this.minLongitude = new Angle(sector.getMinLongitude());
+ this.maxLongitude = new Angle(sector.getMaxLongitude());
+ this.deltaLat = Angle.fromDegrees(this.maxLatitude.degrees - this.minLatitude.degrees);
+ this.deltaLon = Angle.fromDegrees(this.maxLongitude.degrees - this.minLongitude.degrees);
+ }
+
+ /**
+ * Returns the sector's minimum latitude.
+ *
+ * @return The sector's minimum latitude.
+ */
+ public final Angle getMinLatitude()
+ {
+ return minLatitude;
+ }
+
+ /**
+ * Returns the sector's minimum longitude.
+ *
+ * @return The sector's minimum longitude.
+ */
+ public final Angle getMinLongitude()
+ {
+ return minLongitude;
+ }
+
+ /**
+ * Returns the sector's maximum latitude.
+ *
+ * @return The sector's maximum latitude.
+ */
+ public final Angle getMaxLatitude()
+ {
+ return maxLatitude;
+ }
+
+ /**
+ * Returns the sector's maximum longitude.
+ *
+ * @return The sector's maximum longitude.
+ */
+ public final Angle getMaxLongitude()
+ {
+ return maxLongitude;
+ }
+
+ /**
+ * Returns the angular difference between the sector's minimum and maximum latitudes: max - min
+ *
+ * @return The angular difference between the sector's minimum and maximum latitudes.
+ */
+ public final Angle getDeltaLat()
+ {
+ return this.deltaLat;//Angle.fromDegrees(this.maxLatitude.degrees - this.minLatitude.degrees);
+ }
+
+ public final double getDeltaLatDegrees()
+ {
+ return this.deltaLat.degrees;//this.maxLatitude.degrees - this.minLatitude.degrees;
+ }
+
+ public final double getDeltaLatRadians()
+ {
+ return this.deltaLat.radians;//this.maxLatitude.radians - this.minLatitude.radians;
+ }
+
+ /**
+ * Returns the angular difference between the sector's minimum and maximum longitudes: max - min.
+ *
+ * @return The angular difference between the sector's minimum and maximum longitudes
+ */
+ public final Angle getDeltaLon()
+ {
+ return this.deltaLon;//Angle.fromDegrees(this.maxLongitude.degrees - this.minLongitude.degrees);
+ }
+
+ public final double getDeltaLonDegrees()
+ {
+ return this.deltaLon.degrees;//this.maxLongitude.degrees - this.minLongitude.degrees;
+ }
+
+ public final double getDeltaLonRadians()
+ {
+ return this.deltaLon.radians;//this.maxLongitude.radians - this.minLongitude.radians;
+ }
+
+ /**
+ * Returns the latitude and longitude of the sector's angular center: (minimum latitude + maximum latitude) / 2,
+ * (minimum longitude + maximum longitude) / 2.
+ *
+ * @return The latitude and longitude of the sector's angular center
+ */
+ public final LatLon getCentroid()
+ {
+ Angle la = Angle.fromDegrees(0.5 * (this.getMaxLatitude().degrees + this.getMinLatitude().degrees));
+ Angle lo = Angle.fromDegrees(0.5 * (this.getMaxLongitude().degrees + this.getMinLongitude().degrees));
+ return new LatLon(la, lo);
+ }
+
+ public Point computeCenterPoint(Globe globe)
+ {
+ if (globe == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.GlobeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ double lat = 0.5 * (this.minLatitude.degrees + this.maxLatitude.degrees);
+ double lon = 0.5 * (this.minLongitude.degrees + this.maxLongitude.degrees);
+
+ Angle cLat = Angle.fromDegrees(lat);
+ Angle cLon = Angle.fromDegrees(lon);
+ return globe.computePointFromPosition(cLat, cLon, globe.getElevation(cLat, cLon));
+ }
+
+ public Point[] computeCornerPoints(Globe globe)
+ {
+ if (globe == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.GlobeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ Point[] corners = new Point[4];
+
+ Angle minLat = this.minLatitude;
+ Angle maxLat = this.maxLatitude;
+ Angle minLon = this.minLongitude;
+ Angle maxLon = this.maxLongitude;
+
+ corners[0] = globe.computePointFromPosition(minLat, minLon, globe.getElevation(minLat, minLon));
+ corners[1] = globe.computePointFromPosition(minLat, maxLon, globe.getElevation(minLat, maxLon));
+ corners[2] = globe.computePointFromPosition(maxLat, maxLon, globe.getElevation(maxLat, maxLon));
+ corners[3] = globe.computePointFromPosition(maxLat, minLon, globe.getElevation(maxLat, minLon));
+
+ return corners;
+ }
+
+ /**
+ * Returns a sphere that minimally surrounds the sector at a specified vertical exaggeration.
+ *
+ * @param globe the globe the sector is associated with
+ * @param verticalExaggeration the vertical exaggeration to apply to the globe's elevations when computing the
+ * sphere.
+ * @param sector the sector to return the bounding sphere for.
+ * @return The minimal bounding sphere in Cartesian coordinates.
+ * @throws IllegalArgumentException if globe
or sector
is null
+ */
+ static public Extent computeBoundingSphere(Globe globe, double verticalExaggeration, Sector sector)
+ {
+ if (globe == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.GlobeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (sector == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.SectorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ LatLon center = sector.getCentroid();
+ double maxHeight = globe.getMaxElevation() * verticalExaggeration;
+ double minHeight = 0;//globe.getMinElevation() * verticalExaggeration;
+
+ Point[] points = new Point[9];
+ points[0] = globe.computePointFromPosition(center.getLatitude(), center.getLongitude(), maxHeight);
+ points[1] = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMinLongitude(), maxHeight);
+ points[2] = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMaxLongitude(), maxHeight);
+ points[3] = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMinLongitude(), maxHeight);
+ points[4] = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMaxLongitude(), maxHeight);
+ points[5] = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMinLongitude(), minHeight);
+ points[6] = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMaxLongitude(), minHeight);
+ points[7] = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMinLongitude(), minHeight);
+ points[8] = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMaxLongitude(), minHeight);
+
+ return Sphere.createBoundingSphere(points);
+ }
+
+ /**
+ * Returns a cylinder that minimally surrounds the sector at a specified vertical exaggeration.
+ *
+ * @param globe the globe the sector is associated with.
+ * @param verticalExaggeration the vertical exaggeration to apply to the globe's elevations when computing the
+ * cylinder.
+ * @param sector the sector to return the bounding cylinder for.
+ * @return The minimal bounding cylinder in Cartesian coordinates.
+ * @throws IllegalArgumentException if globe
or sector
is null
+ */
+ static public Cylinder computeBoundingCylinder(Globe globe, double verticalExaggeration, Sector sector)
+ {
+ if (globe == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.GlobeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (sector == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.SectorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ // Compute the center points of the bounding cylinder's top and bottom planes.
+ LatLon center = sector.getCentroid();
+ double maxHeight = globe.getMaxElevation() * verticalExaggeration;
+ double minHeight = 0;//globe.getMinElevation() * verticalExaggeration;
+ Point centroidTop = globe.computePointFromPosition(center.getLatitude(), center.getLongitude(), maxHeight);
+ Point lowPoint = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMinLongitude(), minHeight);
+ Point axis = centroidTop.normalize();
+ double lowDistance = axis.dot(lowPoint);
+ Point centroidBot = axis.scale(lowDistance, lowDistance, lowDistance);
+
+ // Compute radius of circumscribing circle around general quadrilateral.
+ Point northwest = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMinLongitude(), maxHeight);
+ Point southeast = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMaxLongitude(), maxHeight);
+ Point southwest = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMinLongitude(), maxHeight);
+ Point northeast = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMaxLongitude(), maxHeight);
+ double a = southwest.distanceTo(southeast);
+ double b = southeast.distanceTo(northeast);
+ double c = northeast.distanceTo(northwest);
+ double d = northwest.distanceTo(southwest);
+ double s = 0.5 * (a + b + c + d);
+ double area = Math.sqrt((s - a) * (s - b) * (s - c) * (s - d));
+ double radius = Math.sqrt((a * b + c * d) * (a * d + b * c) * (a * c + b * d)) / (4d * area);
+
+ return new Cylinder(centroidBot, centroidTop, radius);
+ }
+
+ public final boolean contains(Angle latitude, Angle longitude)
+ {
+ if (latitude == null || longitude == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LatLonIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return latitude.degrees >= this.minLatitude.degrees
+ && latitude.degrees <= this.maxLatitude.degrees
+ && longitude.degrees >= this.minLongitude.degrees
+ && longitude.degrees <= this.maxLongitude.degrees;
+ }
+
+ /**
+ * Determines whether a latitude/longitude position is within the sector. The sector's angles are assumed to be
+ * normalized to +/- 90 degrees latitude and +/- 180 degrees longitude. The result of the operation is undefined if
+ * they are not.
+ *
+ * @param latLon the position to test, with angles normalized to +/- π latitude and +/- 2π longitude.
+ * @return true
if the position is within the sector, false
otherwise.
+ * @throws IllegalArgumentException if latlon
is null.
+ */
+ public final boolean contains(LatLon latLon)
+ {
+ if (latLon == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LatLonIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return this.contains(latLon.getLatitude(), latLon.getLongitude());
+ }
+
+ /**
+ * Determines whether a latitude/longitude postion expressed in radians is within the sector. The sector's angles
+ * are assumed to be normalized to +/- 90 degrees latitude and +/- 180 degrees longitude. The result of the
+ * operation is undefined if they are not.
+ *
+ * @param radiansLatitude the latitude in radians of the position to test, normalized +/- π.
+ * @param radiansLongitude the longitude in radians of the position to test, normalized +/- 2π.
+ * @return true
if the position is within the sector, false
otherwise.
+ */
+ public final boolean containsRadians(double radiansLatitude, double radiansLongitude)
+ {
+ return radiansLatitude >= this.minLatitude.radians && radiansLatitude <= this.maxLatitude.radians
+ && radiansLongitude >= this.minLongitude.radians && radiansLongitude <= this.maxLongitude.radians;
+ }
+
+ public final boolean containsDegrees(double degreesLatitude, double degreesLongitude)
+ {
+ return degreesLatitude >= this.minLatitude.degrees && degreesLatitude <= this.maxLatitude.degrees
+ && degreesLongitude >= this.minLongitude.degrees && degreesLongitude <= this.maxLongitude.degrees;
+ }
+
+ /**
+ * Determines whether this sector intersects another sector's range of latitude and longitude. The sector's angles
+ * are assumed to be normalized to +/- 90 degrees latitude and +/- 180 degrees longitude. The result of the
+ * operation is undefined if they are not.
+ *
+ * @param that the sector to test for intersection.
+ * @return true
if the sectors intersect, otherwise false
.
+ */
+ public boolean intersects(Sector that)
+ {
+ if (that == null)
+ return false;
+
+ // Assumes normalized angles -- [-180, 180], [-90, 90] // TODO: have Angle normalize values when set
+ if (that.maxLongitude.degrees < this.minLongitude.degrees)
+ return false;
+ if (that.minLongitude.degrees > this.maxLongitude.degrees)
+ return false;
+ if (that.maxLatitude.degrees < this.minLatitude.degrees)
+ return false;
+ //noinspection RedundantIfStatement
+ if (that.minLatitude.degrees > this.maxLatitude.degrees)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Returns a new sector whose angles are the extremes of the this sector and another. The new sector's minimum
+ * latitude and longitude will be the minimum of the two sectors. The new sector's maximum latitude and longitude
+ * will be the maximum of the two sectors. The sectors are assumed to be normalized to +/- 90 degrees latitude and
+ * +/- 180 degrees longitude. The result of the operation is undefined if they are not.
+ *
+ * @param that the sector to join with this
.
+ * @return A new sector formed from the extremes of the two sectors, or this
if the incoming sector is
+ * null
.
+ */
+ public final Sector union(Sector that)
+ {
+ if (that == null)
+ return this;
+
+ Angle minLat = this.minLatitude;
+ Angle maxLat = this.maxLatitude;
+ Angle minLon = this.minLongitude;
+ Angle maxLon = this.maxLongitude;
+
+ if (that.minLatitude.degrees < this.minLatitude.degrees)
+ minLat = that.minLatitude;
+ if (that.maxLatitude.degrees > this.maxLatitude.degrees)
+ maxLat = that.maxLatitude;
+ if (that.minLongitude.degrees < this.minLongitude.degrees)
+ minLon = that.minLongitude;
+ if (that.maxLongitude.degrees > this.maxLongitude.degrees)
+ maxLon = that.maxLongitude;
+
+ return new Sector(minLat, maxLat, minLon, maxLon);
+ }
+
+ public final Sector union(Angle latitude, Angle longitude)
+ {
+ if (latitude == null || longitude == null)
+ return this;
+
+ Angle minLat = this.minLatitude;
+ Angle maxLat = this.maxLatitude;
+ Angle minLon = this.minLongitude;
+ Angle maxLon = this.maxLongitude;
+
+ if (latitude.degrees < this.minLatitude.degrees)
+ minLat = latitude;
+ if (latitude.degrees > this.maxLatitude.degrees)
+ maxLat = latitude;
+ if (longitude.degrees < this.minLongitude.degrees)
+ minLon = longitude;
+ if (longitude.degrees > this.maxLongitude.degrees)
+ maxLon = longitude;
+
+ return new Sector(minLat, maxLat, minLon, maxLon);
+ }
+
+ public final Sector intersection(Sector that)
+ {
+ if (that == null)
+ return this;
+
+ Angle minLat, maxLat;
+ minLat = (this.minLatitude.degrees > that.minLatitude.degrees) ? this.minLatitude : that.minLatitude;
+ maxLat = (this.maxLatitude.degrees < that.maxLatitude.degrees) ? this.maxLatitude : that.maxLatitude;
+ if (minLat.degrees > maxLat.degrees)
+ return null;
+
+ Angle minLon, maxLon;
+ minLon = (this.minLongitude.degrees > that.minLongitude.degrees) ? this.minLongitude : that.minLongitude;
+ maxLon = (this.maxLongitude.degrees < that.maxLongitude.degrees) ? this.maxLongitude : that.maxLongitude;
+ if (minLon.degrees > maxLon.degrees)
+ return null;
+
+ return new Sector(minLat, maxLat, minLon, maxLon);
+ }
+
+ public final Sector intersection(Angle latitude, Angle longitude)
+ {
+ if (latitude == null || longitude == null)
+ return this;
+
+ if (!this.contains(latitude, longitude))
+ return null;
+ return new Sector(latitude, latitude, longitude, longitude);
+ }
+
+ public final Sector[] subdivide()
+ {
+ Angle midLat = Angle.average(this.minLatitude, this.maxLatitude);
+ Angle midLon = Angle.average(this.minLongitude, this.maxLongitude);
+
+ Sector[] sectors = new Sector[4];
+ sectors[0] = new Sector(this.minLatitude, midLat, this.minLongitude, midLon);
+ sectors[1] = new Sector(this.minLatitude, midLat, midLon, this.maxLongitude);
+ sectors[2] = new Sector(midLat, this.maxLatitude, this.minLongitude, midLon);
+ sectors[3] = new Sector(midLat, this.maxLatitude, midLon, this.maxLongitude);
+
+ return sectors;
+ }
+
+ /**
+ * Returns a string indicating the sector's angles.
+ *
+ * @return A string indicating the sector's angles.
+ */
+ @Override
+ public String toString()
+ {
+ java.lang.StringBuffer sb = new java.lang.StringBuffer();
+ sb.append("(");
+ sb.append(this.minLatitude.toString());
+ sb.append(", ");
+ sb.append(this.minLongitude.toString());
+ sb.append(")");
+
+ sb.append(", ");
+
+ sb.append("(");
+ sb.append(this.maxLatitude.toString());
+ sb.append(", ");
+ sb.append(this.maxLongitude.toString());
+ sb.append(")");
+
+ return sb.toString();
+ }
+
+ /**
+ * Retrieve the size of this object in bytes. This implementation returns an exact value of the object's size.
+ *
+ * @return the size of this object in bytes
+ */
+ public long getSizeInBytes()
+ {
+ return 4 * minLatitude.getSizeInBytes(); // 4 angles
+ }
+
+ /**
+ * Compares this sector to a specified sector according to their minimum latitude, minimum longitude, maximum
+ * latitude, and maximum longitude, respectively.
+ *
+ * @param that the Sector
to compareTo with this
.
+ * @return -1 if this sector compares less than that specified, 0 if they're equal, and 1 if it compares greater.
+ * @throws IllegalArgumentException if that
is null
+ */
+ public int compareTo(Sector that)
+ {
+ if (that == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.SectorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (this.getMinLatitude().compareTo(that.getMinLatitude()) < 0)
+ return -1;
+
+ if (this.getMinLatitude().compareTo(that.getMinLatitude()) > 0)
+ return 1;
+
+ if (this.getMinLongitude().compareTo(that.getMinLongitude()) < 0)
+ return -1;
+
+ if (this.getMinLongitude().compareTo(that.getMinLongitude()) > 0)
+ return 1;
+
+ if (this.getMaxLatitude().compareTo(that.getMaxLatitude()) < 0)
+ return -1;
+
+ if (this.getMaxLatitude().compareTo(that.getMaxLatitude()) > 0)
+ return 1;
+
+ if (this.getMaxLongitude().compareTo(that.getMaxLongitude()) < 0)
+ return -1;
+
+ if (this.getMaxLongitude().compareTo(that.getMaxLongitude()) > 0)
+ return 1;
+
+ return 0;
+ }
+
+ /**
+ * Tests the equality of the sectors' angles. Sectors are equal if all of their corresponding angles are equal.
+ *
+ * @param o the sector to compareTo with this
.
+ * @return true
if the four corresponding angles of each sector are equal, false
+ * otherwise.
+ */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final gov.nasa.worldwind.geom.Sector sector = (gov.nasa.worldwind.geom.Sector) o;
+
+ if (!maxLatitude.equals(sector.maxLatitude))
+ return false;
+ if (!maxLongitude.equals(sector.maxLongitude))
+ return false;
+ if (!minLatitude.equals(sector.minLatitude))
+ return false;
+ //noinspection RedundantIfStatement
+ if (!minLongitude.equals(sector.minLongitude))
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Computes a hash code from the sector's four angles.
+ *
+ * @return a hash code incorporating the sector's four angles.
+ */
+ @Override
+ public int hashCode()
+ {
+ int result;
+ result = minLatitude.hashCode();
+ result = 29 * result + maxLatitude.hashCode();
+ result = 29 * result + minLongitude.hashCode();
+ result = 29 * result + maxLongitude.hashCode();
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/gov/nasa/worldwind/geom/Sphere.java b/gov/nasa/worldwind/geom/Sphere.java
new file mode 100644
index 0000000..14359d4
--- /dev/null
+++ b/gov/nasa/worldwind/geom/Sphere.java
@@ -0,0 +1,323 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.*;
+
+/**
+ * Represents a sphere in three dimensional space.
+ *
+ * Instances of Sphere
are immutable.
+ *
+ * @author Tom Gaskins
+ * @version $Id: Sphere.java 1749 2007-05-06 19:48:14Z tgaskins $
+ */
+public final class Sphere implements Extent, Renderable
+{
+ public final static Sphere UNIT_SPHERE = new Sphere(Point.ZERO, 1);
+
+ private final Point center;
+ private final double radius;
+
+ /**
+ * Creates a sphere that completely contains a set of points.
+ *
+ * @param points the Point
s to be enclosed by the new Sphere
+ * @return a Sphere
encompassing the given array of Point
s
+ * @throws IllegalArgumentException if points
is null or empty
+ */
+ public static Sphere createBoundingSphere(Point points[])
+ {
+ if (points == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointsArrayIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (points.length < 1)
+ {
+ String message = WorldWind.retrieveErrMsg("geom.Sphere.NoPointsSpecified");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ // Creates the sphere around the axis aligned bounding box of the input points.
+ Point extrema[] = Point.composeExtrema(points);
+ Point center = Point.midPoint(extrema[0], extrema[1]);
+ double radius = 0.5 * extrema[0].distanceTo(extrema[1]);
+
+ return new Sphere(center, radius);
+ }
+
+ /**
+ * Creates a new Sphere
from a given center and radius. radius
must be positive (that is,
+ * greater than zero), and center
may not be null.
+ *
+ * @param center the center of the new sphere
+ * @param radius the radius of the new sphere
+ * @throws IllegalArgumentException if center
is null or if radius
is non-positive
+ */
+ public Sphere(Point center, double radius)
+ {
+ if (center == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.CenterIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (radius <= 0)
+ {
+ String message = WorldWind.retrieveErrMsg("geom.Sphere.RadiusIsZeroOrNegative");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.center = center;
+ this.radius = radius;
+ }
+
+ /**
+ * Obtains the radius of this Sphere
. The radus is the distance from the center to the surface. If an
+ * object's distance to this sphere's center is less than or equal to the radius, then that object is at least
+ * partially within this Sphere
.
+ *
+ * @return the radius of this sphere
+ */
+ public final double getRadius()
+ {
+ return this.radius;
+ }
+
+ /**
+ * Obtains the diameter of this Sphere
. The diameter is twice the radius.
+ *
+ * @return the diameter of this Sphere
+ */
+ public final double getDiameter()
+ {
+ return 2 * this.radius;
+ }
+
+ /**
+ * Obtains the center of this Sphere
.
+ *
+ * @return the Point
situated at the center of this Sphere
+ */
+ public final Point getCenter()
+ {
+ return this.center;
+ }
+
+ /**
+ * Obtains the intersections of this sphere with a line. The returned array may be either null or of zero length if
+ * no intersections are discovered. It does not contain null elements and will have a size of 2 at most. Tangential
+ * intersections are marked as such. line
is considered to have infinite length in both directions.
+ *
+ * @param line the Line
with which to intersect this Sphere
+ * @return an array containing all the intersections of this Sphere
and line
+ * @throws IllegalArgumentException if line
is null
+ */
+ public final Intersection[] intersect(Line line)
+ {
+ if (line == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LineIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ double a = line.getDirection().selfDot();
+ double b = 2 * line.selfDot();
+ double c = line.getOrigin().selfDot() - this.radius * this.radius;
+
+ double discriminant = Sphere.discriminant(a, b, c);
+ if (discriminant < 0)
+ return null;
+
+ double discriminantRoot = Math.sqrt(discriminant);
+ if (discriminant == 0)
+ {
+ Point p = line.getPointAt((-b - discriminantRoot) / (2 * a));
+ return new Intersection[] {new Intersection(p, true)};
+ }
+ else // (discriminant > 0)
+ {
+ Point near = line.getPointAt((-b - discriminantRoot) / (2 * a));
+ Point far = line.getPointAt((-b + discriminantRoot) / (2 * a));
+ return new Intersection[] {new Intersection(near, false), new Intersection(far, false)};
+ }
+ }
+
+ /**
+ * Calculates a discriminant. A discriminant is useful to determine the number of roots to a quadratic equation. If
+ * the discriminant is less than zero, there are no roots. If it equals zero, there is one root. If it is greater
+ * than zero, there are two roots.
+ *
+ * @param a the coefficient of the second order pronumeral
+ * @param b the coefficient of the first order pronumeral
+ * @param c the constant parameter in the quadratic equation
+ * @return the discriminant "b squared minus 4ac"
+ */
+ private static double discriminant(double a, double b, double c)
+ {
+ return b * b - 4 * a * c;
+ }
+
+ /**
+ * tests for intersetion with a Frustum
. This operation is commutative, so
+ * someSphere.intersects(frustum)
and frustum.intersects(someSphere)
are equivalent.
+ *
+ * @param frustum the Frustum
with which to test for intersection
+ * @return true if either frustum
or this Sphere
wholly or partially contain the other,
+ * false otherwise.
+ * @throws IllegalArgumentException if frustum
is null
+ */
+ public boolean intersects(Frustum frustum)
+ {
+ if (frustum == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.FrustumIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return frustum.intersects(this);
+ }
+
+ /**
+ * Tests for intersection with a Line
.
+ *
+ * @param line the Line
with which to test for intersection
+ * @return true if line
intersects or makes a tangent with the surface of this Sphere
+ * @throws IllegalArgumentException if line
is null
+ */
+ public boolean intersects(Line line)
+ {
+ if (line == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.LineIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return line.distanceTo(this.center) <= this.radius;
+ }
+
+ /**
+ * Tests for intersection with a Plane
.
+ *
+ * @param plane the Plane
with which to test for intersection
+ * @return true if plane
intersects or makes a tangent with the surface of this Sphere
+ * @throws IllegalArgumentException if plane
is null
+ */
+ public boolean intersects(Plane plane)
+ {
+ if (plane == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PlaneIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ double dq1 = plane.dot(this.center);
+ return dq1 <= this.radius;
+ }
+
+ /**
+ * Causes this Sphere
to render itself using the DrawContext
provided. dc
may
+ * not be null.
+ *
+ * @param dc the DrawContext
to be used
+ * @throws IllegalArgumentException if dc
is null
+ */
+ public void render(gov.nasa.worldwind.DrawContext dc)
+ {
+ if (dc == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ javax.media.opengl.GL gl = dc.getGL();
+
+ gl.glPushAttrib(javax.media.opengl.GL.GL_TEXTURE_BIT | javax.media.opengl.GL.GL_ENABLE_BIT
+ | javax.media.opengl.GL.GL_CURRENT_BIT);
+ gl.glDisable(javax.media.opengl.GL.GL_TEXTURE_2D);
+ gl.glColor3d(1, 1, 0);
+
+ gl.glMatrixMode(javax.media.opengl.GL.GL_MODELVIEW);
+ gl.glPushMatrix();
+ gl.glTranslated(this.center.x(), this.center.y(), this.center.z());
+ javax.media.opengl.glu.GLUquadric quadric = dc.getGLU().gluNewQuadric();
+ dc.getGLU().gluQuadricDrawStyle(quadric, javax.media.opengl.glu.GLU.GLU_LINE);
+ dc.getGLU().gluSphere(quadric, this.radius, 10, 10);
+ gl.glPopMatrix();
+ dc.getGLU().gluDeleteQuadric(quadric);
+
+ gl.glPopAttrib();
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Sphere: center = " + this.center.toString() + " radius = " + Double.toString(this.radius);
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final gov.nasa.worldwind.geom.Sphere sphere = (gov.nasa.worldwind.geom.Sphere) o;
+
+ if (Double.compare(sphere.radius, radius) != 0)
+ return false;
+ if (!center.equals(sphere.center))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result;
+ long temp;
+ result = center.hashCode();
+ temp = radius != +0.0d ? Double.doubleToLongBits(radius) : 0L;
+ result = 29 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+// public final boolean intersects(Line line)
+// {
+// if (line == null)
+// {
+// String message = WorldWind.retrieveErrMsg("nullValue.LineIsNull");
+// WorldWind.logger().log(java.util.logging.Level.FINE, message);
+// throw new IllegalArgumentException(message);
+// }
+//
+// double a = line.getDirection().getLengthSquared();
+// double b = 2 * line.selfDot();
+// double c = line.getOrigin().selfDot() - this.radius * this.radius;
+//
+// double discriminant = Sphere.discriminant(a, b, c);
+// if (discriminant < 0)
+// {
+// return false;
+// }
+//
+// return true;
+//
+// }
+}
diff --git a/gov/nasa/worldwind/geom/SurfacePolygon.java b/gov/nasa/worldwind/geom/SurfacePolygon.java
new file mode 100644
index 0000000..6d9c4e9
--- /dev/null
+++ b/gov/nasa/worldwind/geom/SurfacePolygon.java
@@ -0,0 +1,92 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: SurfacePolygon.java 1688 2007-05-02 22:03:39Z tgaskins $
+ */
+public class SurfacePolygon extends SurfaceShape
+{
+
+ public SurfacePolygon(Iterable positions, Color color, Color borderColor)
+ {
+ super(positions, color, borderColor);
+ }
+
+ public SurfacePolygon(Iterable positions)
+ {
+ super(positions, null, null);
+ }
+
+ protected final BufferedImage drawShape(BufferedImage image)
+ {
+ double minLat = this.getSector().getMinLatitude().getDegrees();
+ double minLon = this.getSector().getMinLongitude().getDegrees();
+ double dLat = this.getSector().getDeltaLatDegrees();
+ double dLon = this.getSector().getDeltaLonDegrees();
+
+ double latScale = dLat > 0 ? image.getHeight() / dLat : 0;
+ double lonScale = dLon > 0 ? image.getWidth() / dLon : 0;
+
+ GeneralPath path = new GeneralPath();
+
+ Iterator positions = this.getPositions().iterator();
+ if (!positions.hasNext())
+ return image;
+
+ LatLon pos = positions.next();
+ path.moveTo(
+ (float) Math.min(lonScale * (pos.getLongitude().getDegrees() - minLon), image.getWidth() - 1),
+ (float) Math.min(latScale * (pos.getLatitude().getDegrees() - minLat), image.getHeight() - 1));
+
+ double delta = 1d / this.getNumEdgeIntervals();
+ while (positions.hasNext())
+ {
+ LatLon posNext = positions.next();
+ for (int i = 0; i < this.getNumEdgeIntervals(); i++)
+ {
+ LatLon p = LatLon.interpolate(i * delta, pos, posNext);
+ path.lineTo(
+ (float) Math.min(lonScale * (p.getLongitude().getDegrees() - minLon), image.getWidth() - 1),
+ (float) Math.min(latScale * (p.getLatitude().getDegrees() - minLat), image.getHeight() - 1));
+ }
+
+ // Set the last point directly to terminate any round-off error in the iteration above.
+ path.lineTo(
+ (float) Math.min(lonScale * (posNext.getLongitude().getDegrees() - minLon), image.getWidth() - 1),
+ (float) Math.min(latScale * (posNext.getLatitude().getDegrees() - minLat), image.getHeight() - 1));
+
+ pos = posNext;
+ }
+
+ Graphics2D g2 = image.createGraphics();
+
+ if (this.isAntiAlias())
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ if (this.isDrawInterior())
+ {
+ g2.setPaint(this.getPaint());
+ g2.fill(path);
+ }
+
+ if (this.isDrawBorder())
+ {
+ g2.setPaint(this.getBorderColor());
+ g2.setStroke(this.getStroke());
+ g2.draw(path);
+ }
+
+ return image;
+ }
+}
diff --git a/gov/nasa/worldwind/geom/SurfacePolyline.java b/gov/nasa/worldwind/geom/SurfacePolyline.java
new file mode 100644
index 0000000..cf91a17
--- /dev/null
+++ b/gov/nasa/worldwind/geom/SurfacePolyline.java
@@ -0,0 +1,31 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import java.awt.*;
+
+/**
+ * @author tag
+ * @version $Id: SurfacePolyline.java 1448 2007-04-12 00:20:38Z tgaskins $
+ */
+public class SurfacePolyline extends SurfacePolygon
+{
+ public SurfacePolyline(Iterable positions, Color color, Color borderColor)
+ {
+ super(positions, color, borderColor);
+ this.setDrawInterior(false);
+ this.setStroke(new BasicStroke(3f));
+ }
+
+ public SurfacePolyline(Iterable positions)
+ {
+ super(positions);
+ this.setDrawInterior(false);
+ this.setPaint(new Color(1f, 1f, 0f, .8f));
+ this.setStroke(new BasicStroke(3f));
+ }
+}
diff --git a/gov/nasa/worldwind/geom/SurfaceQuadrilateral.java b/gov/nasa/worldwind/geom/SurfaceQuadrilateral.java
new file mode 100644
index 0000000..546ddea
--- /dev/null
+++ b/gov/nasa/worldwind/geom/SurfaceQuadrilateral.java
@@ -0,0 +1,51 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.*;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: SurfaceQuadrilateral.java 1680 2007-05-02 00:30:25Z tgaskins $
+ */
+public class SurfaceQuadrilateral extends SurfacePolygon
+{
+
+ public SurfaceQuadrilateral(Sector sector, Color color, Color borderColor)
+ {
+ super(makePositions(sector), color, borderColor);
+ }
+
+ public SurfaceQuadrilateral(Sector sector)
+ {
+ super(makePositions(sector), null, null);
+ }
+
+ private static Iterable makePositions(Sector sector)
+ {
+ if (sector == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.SectorIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ ArrayList positions = new ArrayList(5);
+
+ positions.add(0, new LatLon(sector.getMinLatitude(), sector.getMinLongitude()));
+ positions.add(1, new LatLon(sector.getMinLatitude(), sector.getMaxLongitude()));
+ positions.add(2, new LatLon(sector.getMaxLatitude(), sector.getMaxLongitude()));
+ positions.add(3, new LatLon(sector.getMaxLatitude(), sector.getMinLongitude()));
+ positions.add(4, new LatLon(sector.getMinLatitude(), sector.getMinLongitude()));
+
+ return positions;
+ }
+}
diff --git a/gov/nasa/worldwind/geom/SurfaceShape.java b/gov/nasa/worldwind/geom/SurfaceShape.java
new file mode 100644
index 0000000..5470a7d
--- /dev/null
+++ b/gov/nasa/worldwind/geom/SurfaceShape.java
@@ -0,0 +1,218 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.layers.*;
+
+import javax.media.opengl.*;
+import java.awt.*;
+import java.awt.image.*;
+import java.util.*;
+
+import com.sun.opengl.util.texture.*;
+
+/**
+ * @author tag
+ * @version $Id: SurfaceShape.java 1767 2007-05-07 21:36:12Z tgaskins $
+ */
+public abstract class SurfaceShape implements Renderable, Disposable
+{
+ private static final Color DEFAULT_COLOR = new Color(1f, 1f, 0f, 0.4f);
+ private static final Color DEFAULT_BORDER_COLOR = new Color(1f, 1f, 0f, 0.7f);
+ private static final int DEFAULT_TEXTURE_SIZE = 512;
+ private static final int DEFAULT_NUM_EDGE_INTERVALS = 10;
+
+ private TextureTile tile;
+ private int textureSize = DEFAULT_TEXTURE_SIZE;
+ private Paint paint;
+ private Color borderColor;
+ private Stroke stroke = new BasicStroke();
+ private boolean drawBorder = true;
+ private boolean drawInterior = true;
+ private boolean antiAlias = true;
+ private int numEdgeIntervals = DEFAULT_NUM_EDGE_INTERVALS;
+ private ArrayList positions = new ArrayList();
+
+ protected abstract BufferedImage drawShape(BufferedImage image);
+
+ public SurfaceShape(Iterable positions, Color color, Color borderColor)
+ {
+ if (positions == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PositionsListIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ createTextureTiles(Sector.boundingSector(positions));
+ this.paint = color != null ? color : DEFAULT_COLOR;
+ this.borderColor = borderColor != null ? borderColor : DEFAULT_BORDER_COLOR;
+
+ for (LatLon p : positions)
+ {
+ this.positions.add(p);
+ }
+ }
+
+ private void createTextureTiles(Sector sector)
+ {
+ this.tile = new TextureTile(sector);
+ }
+
+ public void dispose()
+ {
+ if (this.tile != null)
+ this.tile.dispose();
+ }
+
+ public Sector getSector()
+ {
+ return this.tile.getSector();
+ }
+
+ public ArrayList getPositions()
+ {
+ return positions;
+ }
+
+ private TextureTile getTextureTile()
+ {
+ return this.tile;
+ }
+
+ public Paint getPaint()
+ {
+ return paint;
+ }
+
+ public void setPaint(Paint paint)
+ {
+ this.paint = paint;
+ this.getTextureTile().setTextureData(null);
+ }
+
+ public Color getBorderColor()
+ {
+ return borderColor;
+ }
+
+ public void setBorderColor(Color borderColor)
+ {
+ this.borderColor = borderColor;
+ this.getTextureTile().setTextureData(null);
+ }
+
+ public int getTextureSize()
+ {
+ return textureSize;
+ }
+
+ public void setTextureSize(int textureSize)
+ {
+ this.textureSize = textureSize;
+ this.getTextureTile().setTextureData(null);
+ }
+
+ public Stroke getStroke()
+ {
+ return stroke;
+ }
+
+ public void setStroke(Stroke stroke)
+ {
+ this.stroke = stroke;
+ this.getTextureTile().setTextureData(null);
+ }
+
+ public boolean isDrawBorder()
+ {
+ return drawBorder;
+ }
+
+ public void setDrawBorder(boolean drawBorder)
+ {
+ this.drawBorder = drawBorder;
+ }
+
+ public boolean isDrawInterior()
+ {
+ return drawInterior;
+ }
+
+ public void setDrawInterior(boolean drawInterior)
+ {
+ this.drawInterior = drawInterior;
+ }
+
+ public boolean isAntiAlias()
+ {
+ return antiAlias;
+ }
+
+ public void setAntiAlias(boolean antiAlias)
+ {
+ this.antiAlias = antiAlias;
+ }
+
+ public int getNumEdgeIntervals()
+ {
+ return numEdgeIntervals;
+ }
+
+ public void setNumEdgeIntervals(int numEdgeIntervals)
+ {
+ this.numEdgeIntervals = numEdgeIntervals;
+ }
+
+ private boolean intersects(Sector sector)
+ {
+ return this.tile.getSector().intersects(sector);
+ }
+
+ public void render(DrawContext dc)
+ {
+ if (!this.intersects(dc.getVisibleSector()))
+ return;
+
+ if (this.getTextureTile().getTextureData() == null)
+ this.tile.setTextureData(this.makeTextureData(this.textureSize));
+
+ GL gl = dc.getGL();
+
+ gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_POLYGON_BIT);
+
+ try
+ {
+ if (!dc.isPickingMode())
+ {
+ gl.glEnable(GL.GL_BLEND);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+ }
+
+ gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
+ gl.glEnable(GL.GL_CULL_FACE);
+ gl.glCullFace(GL.GL_BACK);
+
+ dc.getSurfaceTileRenderer().renderTile(dc, this.tile);
+ }
+ finally
+ {
+ gl.glPopAttrib();
+ }
+ }
+
+ private TextureData makeTextureData(int size)
+ {
+ BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR);
+
+ TextureData td = new TextureData(GL.GL_RGBA, GL.GL_RGBA, false, this.drawShape(image));
+ td.setMustFlipVertically(false);
+
+ return td;
+ }
+}
diff --git a/gov/nasa/worldwind/geom/Triangle.java b/gov/nasa/worldwind/geom/Triangle.java
new file mode 100644
index 0000000..b422a70
--- /dev/null
+++ b/gov/nasa/worldwind/geom/Triangle.java
@@ -0,0 +1,121 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.WorldWind;
+
+/**
+ * @author Eric Dalgliesh 30/11/2006
+ * @version $Id: Triangle.java 359 2006-12-12 02:43:47Z ericdalgliesh $
+ */
+public class Triangle
+{
+ private static final double EPSILON = 0.0000001; // used in intersects method
+
+ private final Point a;
+ private final Point b;
+ private final Point c;
+
+ public Triangle(Point a, Point b, Point c)
+ {
+ if (a == null || b == null || c == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+
+// private Plane getPlane()
+// {
+// Vector ab, ac;
+// ab = new Vector(this.b.subtract(this.a)).normalize();
+// ac = new Vector(this.c.subtract(this.a)).normalize();
+//
+// Vector n = new Vector(new Point(ab.x(), ab.y(), ab.z(), ab.w()).cross(new Point(ac.x(), ac.y(), ac.z(), ac.w())));
+//
+// return new gov.nasa.worldwind.geom.Plane(n);
+// }
+
+// private Point temporaryIntersectPlaneAndLine(Line line, Plane plane)
+// {
+// Vector n = line.getDirection();
+// Point v0 = Point.fromOriginAndDirection(plane.getDistance(), plane.getNormal(), Point.ZERO);
+// Point p0 = line.getPointAt(0);
+// Point p1 = line.getPointAt(1);
+//
+// double r1 = n.dot(v0.subtract(p0))/n.dot(p1.subtract(p0));
+// if(r1 >= 0)
+// return line.getPointAt(r1);
+// return null;
+// }
+//
+// private Triangle divide(double d)
+// {
+// d = 1/d;
+// return new Triangle(this.a.multiply(d), this.b.multiply(d), this.c.multiply(d));
+// }
+
+// public Point intersect(Line line)
+// {
+// // taken from Moller and Trumbore
+// // http://www.cs.virginia.edu/~gfx/Courses/2003/ImageSynthesis/papers/Acceleration/
+// // Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf
+//
+// Point origin = line.getOrigin();
+// Point dir = new Point(line.getDirection());
+//
+// double u, v;
+//
+// // find vectors for two edges sharing Point a
+// Point edge1 = this.c.subtract(this.a);
+// Point edge2 = this.b.subtract(this.a);
+//
+// // start calculating determinant
+// Point pvec = dir.cross(edge2);
+//
+// // get determinant.
+// double det = edge1.dot(pvec);
+//
+// if (det > -EPSILON && det < EPSILON)
+// {// If det is near zero, then ray lies on plane of triangle
+// return null;
+// }
+//
+// double detInv = 1d / det;
+//
+// // distance from vert0 to ray origin
+// Point tvec = origin.subtract(this.a);
+//
+// // calculate u parameter and test bounds
+// u = tvec.dot(pvec) * detInv;
+// if (u < 0 || u > 1)
+// {
+// return null;
+// }
+//
+// // prepare to test v parameter
+// Point qvec = tvec.cross(edge1);
+//
+// //calculate v parameter and test bounds
+// v = dir.dot(qvec) * detInv;
+// if (v < 0 || u + v > 1)
+// {
+// return null;
+// }
+//
+// double t = edge2.dot(qvec) * detInv;
+// return Point.fromOriginAndDirection(t, line.getDirection(), line.getOrigin());
+//
+//// return new Point(t, u, v);
+//// return line.getPointAt(t);
+// }
+}
diff --git a/gov/nasa/worldwind/geom/ViewFrustum.java b/gov/nasa/worldwind/geom/ViewFrustum.java
new file mode 100644
index 0000000..214f7d6
--- /dev/null
+++ b/gov/nasa/worldwind/geom/ViewFrustum.java
@@ -0,0 +1,182 @@
+/*
+Copyright (C) 2001, 2006 United States Government as represented by
+the Administrator of the National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.geom;
+
+import gov.nasa.worldwind.*;
+
+import java.util.logging.Level;
+
+/**
+ * @author Paul Collins
+ * @version $Id: ViewFrustum.java 1774 2007-05-08 01:03:37Z dcollins $
+ */
+public class ViewFrustum
+{
+ private final Frustum frustum;
+ private final Matrix4 projection;
+// private java.awt.geom.Rectangle2D nearRect;
+// private java.awt.geom.Rectangle2D farRect;
+
+ public ViewFrustum(Matrix4 projectionMatrix)
+ {
+ if (projectionMatrix == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.MatrixIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ double[] m = projectionMatrix.getEntries();
+ // Extract the near clipping plane from the projection-matrix.
+ double nearMag = Math.sqrt((m[3] + m[2]) * (m[3] + m[2]) + (m[7] + m[6]) * (m[7] + m[6])
+ + (m[11] + m[10]) * (m[11] + m[10]));
+ Plane nearPlane = new Plane((m[3] + m[2]) / nearMag, (m[7] + m[6]) / nearMag, (m[11] + m[10]) / nearMag,
+ m[15] + m[14]);
+ // Extract the far clipping plane from the projection-matrix.
+ double farMag = Math.sqrt((m[3] - m[2]) * (m[3] - m[2]) + (m[7] - m[6]) * (m[7] - m[6])
+ + (m[11] - m[10]) * (m[11] - m[10]));
+ Plane farPlane = new Plane((m[3] - m[2]) / farMag, (m[7] - m[6]) / farMag, (m[11] - m[10]) / farMag,
+ m[15] - m[14]);
+ // Extract the left clipping plane from the projection-matrix.
+ double leftMag = Math.sqrt((m[3] + m[0]) * (m[3] + m[0]) + (m[7] + m[4]) * (m[7] + m[4])
+ + (m[11] + m[8]) * (m[11] + m[8]));
+ Plane leftPlane = new Plane((m[3] + m[0]) / leftMag, (m[7] + m[4]) / leftMag, (m[11] + m[8]) / leftMag,
+ m[15] + m[12]);
+ // Extract the right clipping plane from the projection-matrix.
+ double rightMag = Math.sqrt((m[3] - m[0]) * (m[3] - m[0]) + (m[7] - m[4]) * (m[7] - m[4])
+ + (m[11] - m[8]) * (m[11] - m[8]));
+ Plane rightPlane = new Plane((m[3] - m[0]) / rightMag, (m[7] - m[4]) / rightMag, (m[11] - m[8]) / rightMag,
+ m[15] - m[12]);
+ // Extract the bottom clipping plane from the projection-matrix.
+ double bottomMag = Math.sqrt((m[3] + m[1]) * (m[3] + m[1]) + (m[7] + m[5]) * (m[7] + m[5])
+ + (m[11] + m[9]) * (m[11] + m[9]));
+ Plane bottomPlane = new Plane((m[3] + m[1]) / bottomMag, (m[7] + m[5]) / bottomMag, (m[11] + m[9]) / bottomMag,
+ m[15] + m[13]);
+ // Extract the top clipping plane from the projection-matrix.
+ double topMag = Math.sqrt((m[3] - m[1]) * (m[3] - m[1]) + (m[7] - m[5]) * (m[7] - m[5])
+ + (m[11] - m[9]) * (m[11] - m[9]));
+ Plane topPlane = new Plane((m[3] - m[1]) / topMag, (m[7] - m[5]) / topMag, (m[11] - m[9]) / topMag,
+ m[15] - m[13]);
+ this.frustum = new Frustum(nearPlane, farPlane, leftPlane, rightPlane, bottomPlane, topPlane);
+ this.projection = projectionMatrix;
+ }
+
+ /**
+ * Creates a Frustum
from a horizontal field-of-view, viewport aspect ratio and distance to near and
+ * far depth clipping planes. The near plane must be closer than the far plane, and both planes must be a positive
+ * distance away.
+ *
+ * @param fieldOfView horizontal field-of-view angle in the range (0, 180)
+ * @param viewportWidth the width of the viewport in screen pixels
+ * @param viewportHeight the height of the viewport in screen pixels
+ * @param near distance to the near depth clipping plane
+ * @param far distance to far depth clipping plane
+ * @throws IllegalArgumentException if fov is not in the range (0, 180), if either near or far are negative, or near
+ * is greater than or equal to far
+ */
+ public ViewFrustum(Angle fieldOfView, int viewportWidth, int viewportHeight, double near, double far)
+ {
+ if (fieldOfView == null)
+ {
+ String message = WorldWind.retrieveErrMsg("geom.ViewFrustum.FieldOfViewIsNull");
+ WorldWind.logger().log(Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ double fov = fieldOfView.getDegrees();
+ double farMinusNear = far - near;
+ String message = null;
+ if (fov <= 0 || fov > 180)
+ message = WorldWind.retrieveErrMsg("geom.ViewFrustum.FieldOfViewOutOfRange");
+ if (near <= 0 || farMinusNear <= 0)
+ message = WorldWind.retrieveErrMsg("geom.ViewFrusutm.ClippingDistanceOutOfRange");
+ if (message != null)
+ {
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ double focalLength = 1d / fieldOfView.tanHalfAngle();
+ double aspect = viewportHeight / (double) viewportWidth;
+ double lrLen = Math.sqrt(focalLength * focalLength + 1);
+ double btLen = Math.sqrt(focalLength * focalLength + aspect * aspect);
+ Plane nearPlane = new Plane(0d, 0d, 0d - 1d, 0d - near);
+ Plane farPlane = new Plane(0d, 0d, 1d, far);
+ Plane leftPlane = new Plane(focalLength / lrLen, 0d, 0d - 1d / lrLen, 0);
+ Plane rightPlane = new Plane(0d - focalLength / lrLen, 0d, 0d - 1d / lrLen, 0d);
+ Plane bottomPlane = new Plane(0d, focalLength / btLen, 0d - aspect / btLen, 0d);
+ Plane topPlane = new Plane(0d, 0d - focalLength / btLen, 0d - aspect / btLen, 0d);
+ double[] projectionMatrix = new double[] {
+ focalLength, 0d, 0d, 0d,
+ 0d, focalLength / aspect, 0d, 0d,
+ 0d, 0d, 0d - (far + near) / farMinusNear, 0d - 1d,
+ 0d, 0d, 0d - (2d * far * near) / farMinusNear, 0d
+ };
+ this.frustum = new Frustum(nearPlane, farPlane, leftPlane, rightPlane, bottomPlane, topPlane);
+ this.projection = new Matrix4(projectionMatrix);
+ }
+
+ /**
+ * Creates a Frustum
from three sets of parallel clipping planes (a parallel projection). In this case,
+ * the near and far depth clipping planes may be a negative distance away.
+ *
+ * @param left distance to the left vertical clipping plane
+ * @param right distance to the right vertical clipping plane
+ * @param bottom distance to the bottom horizontal clipping plane
+ * @param top distance to the top horizontal clipping plane
+ * @param near distance to the near depth clipping plane
+ * @param far distance to far depth clipping plane
+ * @throws IllegalArgumentException if the difference of any plane set (lright - left, top - bottom, far - near) is
+ * less than or equal to zero.
+ */
+ public ViewFrustum(double near, double far, double left, double right, double bottom,
+ double top)
+ {
+ double farMinusNear = far - near;
+ double rightMinusLeft = right - left;
+ double topMinusBottom = top - bottom;
+ if (rightMinusLeft <= 0 || topMinusBottom <= 0 || farMinusNear <= 0)
+ {
+ String message = WorldWind.retrieveErrMsg("geom.ViewFrusutm.ClippingDistanceOutOfRange");
+ WorldWind.logger().log(Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ Plane nearPlane = new Plane(0d, 0d, 0d - 1d, near < 0d ? near : 0d - near);
+ Plane farPlane = new Plane(0d, 0d, 1d, far < 0d ? 0d - far : far);
+ Plane leftPlane = new Plane(1d, 0d, 0d, left < 0d ? left : 0d - left);
+ Plane rightPlane = new Plane(0d - 1d, 0d, 0d, right < 0d ? 0d - right : right);
+ Plane bottomPlane = new Plane(0d, 1d, 0d, bottom < 0d ? bottom : 0d - bottom);
+ Plane topPlane = new Plane(0d, 0d - 1d, 0d, top < 0d ? 0d - top : top);
+ double[] projectionMatrix = new double[] {
+ 2d / rightMinusLeft, 0d, 0d, 0d - (right + left) / rightMinusLeft,
+ 0d, 0d / topMinusBottom, 0d, 0d - (top + bottom) / topMinusBottom,
+ 0d, 0d, 0d - 2d / farMinusNear, 0d - (far + near) / farMinusNear,
+ 0d, 0d, 0d, 1d
+ };
+ this.frustum = new Frustum(nearPlane, farPlane, leftPlane, rightPlane, bottomPlane, topPlane);
+ this.projection = new Matrix4(projectionMatrix);
+ }
+
+ public final Frustum getFrustum()
+ {
+ return this.frustum;
+ }
+
+ public final Matrix4 getProjectionMatrix()
+ {
+ return this.projection;
+ }
+
+// public final java.awt.geom.Rectangle2D getNearRectangle()
+// {
+// return this.nearRect;
+// }
+
+// public final java.awt.geom.Rectangle2D getFarRectangle()
+// {
+// return this.farRect;
+// }
+}
diff --git a/gov/nasa/worldwind/globes/Earth.java b/gov/nasa/worldwind/globes/Earth.java
new file mode 100644
index 0000000..47fe034
--- /dev/null
+++ b/gov/nasa/worldwind/globes/Earth.java
@@ -0,0 +1,25 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.globes;
+
+import gov.nasa.worldwind.globes.EarthElevationModel;
+/**
+ * @author Tom Gaskins
+ * @version $Id: Earth.java 1738 2007-05-06 13:40:51Z tgaskins $
+ */
+
+public class Earth extends EllipsoidalGlobe
+{
+ public static final double WGS84_EQUATORIAL_RADIUS = 6378137.0; // ellipsoid equatorial getRadius, in meters
+ public static final double WGS84_POLAR_RADIUS = 6378137.0; //6356752.3; // ellipsoid polar getRadius, in meters
+ public static final double WGS84_ES = 0;//0.00669437999013; // eccentricity squared, semi-major axis
+
+ public Earth()
+ {
+ super(WGS84_EQUATORIAL_RADIUS, WGS84_POLAR_RADIUS, WGS84_ES, new EarthElevationModel());
+ }
+}
diff --git a/gov/nasa/worldwind/globes/EarthElevationModel.java b/gov/nasa/worldwind/globes/EarthElevationModel.java
new file mode 100644
index 0000000..fc159f1
--- /dev/null
+++ b/gov/nasa/worldwind/globes/EarthElevationModel.java
@@ -0,0 +1,44 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.globes;
+
+import gov.nasa.worldwind.geom.*;
+import gov.nasa.worldwind.*;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: EarthElevationModel.java 1731 2007-05-05 05:57:22Z tgaskins $
+ */
+public class EarthElevationModel extends BasicElevationModel
+{
+ private static double HEIGHT_OF_MT_EVEREST = 8850d; // meters
+ private static double DEPTH_OF_MARIANAS_TRENCH = -11000d; // meters
+
+ public EarthElevationModel()
+ {
+ super(makeLevels(), DEPTH_OF_MARIANAS_TRENCH, HEIGHT_OF_MT_EVEREST);
+ this.setNumExpectedValuesPerTile(22500);
+ }
+
+ private static LevelSet makeLevels()
+ {
+ AVList params = new AVListImpl();
+
+ params.setValue(Level.TILE_WIDTH, 150);
+ params.setValue(Level.TILE_HEIGHT, 150);
+ params.setValue(Level.CACHE_NAME, "Earth/srtm30pluszip");
+ params.setValue(Level.SERVICE, "http://worldwind25.arc.nasa.gov/wwelevation/wwelevation.aspx");
+ params.setValue(Level.DATASET_NAME, "srtm30pluszip");
+ params.setValue(Level.FORMAT_SUFFIX, ".bil");
+ params.setValue(Level.NUM_LEVELS, 12);
+ params.setValue(Level.NUM_EMPTY_LEVELS, 0);
+ params.setValue(Level.LEVEL_ZERO_TILE_DELTA, new LatLon(Angle.fromDegrees(20d), Angle.fromDegrees(20d)));
+ params.setValue(AVKey.SECTOR, Sector.FULL_SPHERE);
+
+ return new LevelSet(params);
+ }
+}
diff --git a/gov/nasa/worldwind/globes/EllipsoidIcosahedralTessellator.java b/gov/nasa/worldwind/globes/EllipsoidIcosahedralTessellator.java
new file mode 100644
index 0000000..bb7de56
--- /dev/null
+++ b/gov/nasa/worldwind/globes/EllipsoidIcosahedralTessellator.java
@@ -0,0 +1,900 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.globes;
+
+import com.sun.opengl.util.*;
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+
+import javax.media.opengl.*;
+import java.nio.*;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id$
+ */
+public class EllipsoidIcosahedralTessellator extends WWObjectImpl implements Tessellator
+{
+ // TODO: This class works as of 3/15/07 but it is not complete. There is a problem with texture coordinate
+ // generation around +-20 degrees latitude, and picking and meridian/parallel lines are not implemented.
+ // Also needs skirt creation.
+ private static int DEFAULT_DENSITY = 20;
+ private static final int DEFAULT_MAX_LEVEL = 14;
+
+ private static class GlobeInfo
+ {
+ private final Globe globe; // TODO: remove the dependency on this
+ private final double level0EdgeLength;
+ private final double invAsq;
+ private final double invCsq;
+
+ static final double EDGE_FACTOR = Math.sqrt(10d + 2d * Math.sqrt(5d)) / 4d;
+
+ private GlobeInfo(Globe globe)
+ {
+ this.globe = globe;
+ double equatorialRadius = globe.getEquatorialRadius();
+ double polarRadius = globe.getPolarRadius();
+
+ this.invAsq = 1 / (equatorialRadius * equatorialRadius);
+ this.invCsq = 1 / (polarRadius * polarRadius);
+
+ this.level0EdgeLength = equatorialRadius / EDGE_FACTOR;
+ }
+ }
+
+ private static class IcosaTile implements SectorGeometry
+ {
+ private static java.util.HashMap parameterizations =
+ new java.util.HashMap();
+ private static java.util.HashMap indexLists =
+ new java.util.HashMap();
+
+ protected static double[] getParameterization(int density)
+ {
+ double[] p = parameterizations.get(density);
+ if (p != null)
+ return p;
+
+ int coordCount = (density * density + 3 * density + 2) / 2;
+ p = new double[2 * coordCount];
+ double delta = 1d / density;
+ int k = 0;
+ for (int j = 0; j <= density; j++)
+ {
+ double v = j * delta;
+ for (int i = 0; i <= density - j; i++)
+ {
+ p[k++] = i * delta; // u
+ p[k++] = v;
+ }
+ }
+
+ parameterizations.put(density, p);
+
+ return p;
+ }
+
+ protected static java.nio.IntBuffer getIndices(int density)
+ {
+ java.nio.IntBuffer buffer = indexLists.get(density);
+ if (buffer != null)
+ return buffer;
+
+ int indexCount = density * density + 4 * density - 2;
+ buffer = com.sun.opengl.util.BufferUtil.newIntBuffer(indexCount);
+ int k = 0;
+ for (int i = 0; i < density; i++)
+ {
+ buffer.put(k);
+ if (i > 0)
+ {
+ k = buffer.get(buffer.position() - 3);
+ buffer.put(k);
+ buffer.put(k);
+ }
+
+ if (i % 2 == 0) // even
+ {
+ for (int j = 0; j < density - i; j++)
+ {
+ ++k;
+ buffer.put(k);
+ k += density - j;
+ buffer.put(k);
+ }
+ }
+ else // odd
+ {
+ for (int j = density - i - 1; j >= 0; j--)
+ {
+ k -= density - j;
+ buffer.put(k);
+ --k;
+ buffer.put(k);
+ }
+ }
+ }
+
+ indexLists.put(density, buffer);
+
+ return buffer;
+ }
+
+ public static Point getUnitPoint(double u, double v, Point p0, Point p1, Point p2)
+ {
+ double w = 1d - u - v;
+ double x = u * p1.x() + v * p2.x() + w * p0.x();
+ double y = u * p1.y() + v * p2.y() + w * p0.y();
+ double z = u * p1.z() + v * p2.z() + w * p0.z();
+ double invLength = 1d / Math.sqrt(x * x + y * y + z * z);
+
+ return new Point(x * invLength, y * invLength, z * invLength);
+ }
+
+ protected final int level;
+ private final GlobeInfo globeInfo;
+ private final LatLon g0, g1, g2;
+ private Sector sector; // lazily evaluated
+ protected final Point unitp0, unitp1, unitp2; // points on unit sphere
+ private final Point p0;
+ private final Point p1;
+ private final Point p2;
+ private final Point pCentroid;
+ // private final Vector normal; // ellipsoids's normal vector at tile centroid
+ private final Cylinder extent; // extent of triangle in object coordinates
+ private final double edgeLength;
+ private int density = DEFAULT_DENSITY;
+ private long byteSize;
+
+ static final double ROOT3_OVER4 = Math.sqrt(3) / 4d;
+
+ public IcosaTile(GlobeInfo globeInfo, int level, Point unitp0, Point unitp1, Point unitp2)
+ {
+ // TODO: Validate args
+ this.level = level;
+ this.globeInfo = globeInfo;
+
+ this.unitp0 = unitp0;
+ this.unitp1 = unitp1;
+ this.unitp2 = unitp2;
+
+ // Compute lat/lon at tile vertices.
+ Angle lat = Angle.fromRadians(Math.asin(this.unitp0.y()));
+ Angle lon = Angle.fromRadians(Math.atan2(this.unitp0.x(), this.unitp0.z()));
+ this.g0 = new LatLon(lat, lon);
+ lat = Angle.fromRadians(Math.asin(this.unitp1.y()));
+ lon = Angle.fromRadians(Math.atan2(this.unitp1.x(), this.unitp1.z()));
+ this.g1 = new LatLon(lat, lon);
+ lat = Angle.fromRadians(Math.asin(this.unitp2.y()));
+ lon = Angle.fromRadians(Math.atan2(this.unitp2.x(), this.unitp2.z()));
+ this.g2 = new LatLon(lat, lon);
+
+ // Compute the triangle corner points on the ellipsoid at mean, max and min elevations.
+ this.p0 = this.scaleUnitPointToEllipse(this.unitp0, this.globeInfo.invAsq, this.globeInfo.invCsq);
+ this.p1 = this.scaleUnitPointToEllipse(this.unitp1, this.globeInfo.invAsq, this.globeInfo.invCsq);
+ this.p2 = this.scaleUnitPointToEllipse(this.unitp2, this.globeInfo.invAsq, this.globeInfo.invCsq);
+
+ double a = 1d / 3d;
+ Point unitCentroid = getUnitPoint(a, a, this.unitp0, this.unitp1, this.unitp2);
+ this.pCentroid = this.scaleUnitPointToEllipse(unitCentroid, this.globeInfo.invAsq, this.globeInfo.invCsq);
+
+// // Compute the tile normal, which is the gradient of the ellipse at the centroid.
+// double nx = 2 * this.pCentroid.x() * this.globeInfo.invAsq;
+// double ny = 2 * this.pCentroid.y() * this.globeInfo.invCsq;
+// double nz = 2 * this.pCentroid.z() * this.globeInfo.invAsq;
+// this.normal = new Vector(nx, ny, nz).normalize();
+
+ this.extent = Sector.computeBoundingCylinder(globeInfo.globe, 1d, this.getSector());
+
+ this.edgeLength = this.globeInfo.level0EdgeLength / Math.pow(2, this.level);
+ }
+
+ public IcosaTile(GlobeInfo globeInfo, int level, LatLon g0, LatLon g1, LatLon g2)
+ {
+ // TODO: Validate args
+ this.level = level;
+ this.globeInfo = globeInfo;
+
+ this.g0 = g0;
+ this.g1 = g1;
+ this.g2 = g2;
+
+ this.unitp0 = PolarPoint.toCartesian(this.g0.getLatitude(), this.g0.getLongitude(), 1);
+ this.unitp1 = PolarPoint.toCartesian(this.g1.getLatitude(), this.g1.getLongitude(), 1);
+ this.unitp2 = PolarPoint.toCartesian(this.g2.getLatitude(), this.g2.getLongitude(), 1);
+
+ // Compute the triangle corner points on the ellipsoid at mean, max and min elevations.
+ this.p0 = this.scaleUnitPointToEllipse(this.unitp0, this.globeInfo.invAsq, this.globeInfo.invCsq);
+ this.p1 = this.scaleUnitPointToEllipse(this.unitp1, this.globeInfo.invAsq, this.globeInfo.invCsq);
+ this.p2 = this.scaleUnitPointToEllipse(this.unitp2, this.globeInfo.invAsq, this.globeInfo.invCsq);
+
+ double a = 1d / 3d;
+ Point unitCentroid = getUnitPoint(a, a, this.unitp0, this.unitp1, this.unitp2);
+ this.pCentroid = this.scaleUnitPointToEllipse(unitCentroid, this.globeInfo.invAsq, this.globeInfo.invCsq);
+
+// // Compute the tile normal, which is the gradient of the ellipse at the centroid.
+// double nx = 2 * this.pCentroid.x() * this.globeInfo.invAsq;
+// double ny = 2 * this.pCentroid.y() * this.globeInfo.invCsq;
+// double nz = 2 * this.pCentroid.z() * this.globeInfo.invAsq;
+// this.normal = new Vector(nx, ny, nz).normalize();
+
+ this.extent = Sector.computeBoundingCylinder(globeInfo.globe, 1d, this.getSector());
+
+ this.edgeLength = this.globeInfo.level0EdgeLength / Math.pow(2, this.level);
+ }
+
+ public Sector getSector()
+ {
+ if (this.sector != null)
+ return this.sector;
+
+ double m;
+
+ m = this.g0.getLatitude().getRadians();
+ if (this.g1.getLatitude().getRadians() < m)
+ m = this.g1.getLatitude().getRadians();
+ if (this.g2.getLatitude().getRadians() < m)
+ m = this.g2.getLatitude().getRadians();
+ Angle minLat = Angle.fromRadians(m);
+
+ m = this.g0.getLatitude().getRadians();
+ if (this.g1.getLatitude().getRadians() > m)
+ m = this.g1.getLatitude().getRadians();
+ if (this.g2.getLatitude().getRadians() > m)
+ m = this.g2.getLatitude().getRadians();
+ Angle maxLat = Angle.fromRadians(m);
+
+ m = this.g0.getLongitude().getRadians();
+ if (this.g1.getLongitude().getRadians() < m)
+ m = this.g1.getLongitude().getRadians();
+ if (this.g2.getLongitude().getRadians() < m)
+ m = this.g2.getLongitude().getRadians();
+ Angle minLon = Angle.fromRadians(m);
+
+ m = this.g0.getLongitude().getRadians();
+ if (this.g1.getLongitude().getRadians() > m)
+ m = this.g1.getLongitude().getRadians();
+ if (this.g2.getLongitude().getRadians() > m)
+ m = this.g2.getLongitude().getRadians();
+ Angle maxLon = Angle.fromRadians(m);
+
+ return this.sector = new Sector(minLat, maxLat, minLon, maxLon);
+ }
+
+ private Point scaleUnitPointToEllipse(Point up, double invAsq, double invCsq)
+ {
+ double f = up.x() * up.x() * invAsq + up.y() * up.y() * invCsq + up.z() * up.z() * invAsq;
+ f = 1 / Math.sqrt(f);
+ return new Point(up.x() * f, up.y() * f, up.z() * f);
+ }
+
+ private IcosaTile[] split()
+ {
+ Point up01 = Point.midPoint(this.p0, this.p1);
+ Point up12 = Point.midPoint(this.p1, this.p2);
+ Point up20 = Point.midPoint(this.p2, this.p0);
+ up01 = up01.multiply(1d / up01.length());
+ up12 = up12.multiply(1d / up12.length());
+ up20 = up20.multiply(1d / up20.length());
+
+ IcosaTile[] subTiles = new IcosaTile[4];
+ subTiles[0] = new IcosaTile(this.globeInfo, this.level + 1, this.unitp0, up01, up20);
+ subTiles[1] = new IcosaTile(this.globeInfo, this.level + 1, up01, this.unitp1, up12);
+ subTiles[2] = new IcosaTile(this.globeInfo, this.level + 1, up20, up12, this.unitp2);
+ subTiles[3] = new IcosaTile(this.globeInfo, this.level + 1, up12, up20, up01);
+
+ return subTiles;
+ }
+
+ public String toString()
+ {
+ return this.level + ": (" + unitp0.toString() + ", " + unitp1.toString() + ", " + unitp2.toString() + ")";
+ }
+
+ public Extent getExtent()
+ {
+ return this.extent;
+ }
+//
+// private Point getPoint(double u, double v)
+// {
+// Point pu = this.unitp1;
+// Point pv = this.unitp2;
+// Point pw = this.unitp0;
+//
+// double w = 1d - u - v;
+//
+// double x = u * pu.x() + v * pv.x() + w * pw.x();
+// double y = u * pu.y() + v * pv.y() + w * pw.y();
+// double z = u * pu.z() + v * pv.z() + w * pw.z();
+// double f = x * x * this.globeInfo.invAsq + y * y * this.globeInfo.invAsq + z * z * this.globeInfo.invCsq;
+// f = 1 / Math.sqrt(f);
+//
+// return new Point(x * f, y * f, z * f);
+// }
+//
+// private Point computePoint(Angle lat, Angle lon)
+// {
+// double u = (lat.getRadians() - this.g0.getLatitude().getRadians())
+// / (this.g1.getLatitude().getRadians() - this.g0.getLatitude().getRadians());
+// double v = (lat.getRadians() - this.g0.getLatitude().getRadians())
+// / (this.g1.getLatitude().getRadians() - this.g0.getLatitude().getRadians());
+//
+// return null;
+// }
+
+ private static class RenderInfo
+ {
+ private final int density;
+ // private int[] bufferIds = new int[2];
+ private DoubleBuffer vertices;
+ private final DoubleBuffer texCoords;
+
+ private RenderInfo(int density, DoubleBuffer vertices, DoubleBuffer texCoords)
+ {
+ this.density = density;
+ this.vertices = vertices;
+ this.texCoords = texCoords;
+ }
+
+ private long getSizeInBytes()
+ {
+ return 12;// + this.vertices.limit() * 5 * Float.SIZE;
+ }
+ }
+
+ private RenderInfo makeVerts(DrawContext dc, int density)
+ {
+ ElevationModel elevationModel = dc.getGlobe().getElevationModel();
+ int resolution = elevationModel.getTargetResolution(dc, this.getSector(), density);
+ CacheKey key = new CacheKey(this, resolution, dc.getVerticalExaggeration(), density);
+ RenderInfo ri = (RenderInfo) WorldWind.memoryCache().getObject(key);
+ if (ri != null)
+ return ri;
+
+ ri = this.buildVerts(dc, resolution);
+ WorldWind.memoryCache().add(key, ri, this.byteSize = ri.getSizeInBytes());
+
+ return ri;
+ }
+
+ private RenderInfo buildVerts(DrawContext dc, int resolution)
+ {
+ // Density is intended to approximate closely the tessellation's number of intervals along a side.
+ double[] params = getParameterization(density); // Parameterization is independent of tile location.
+ int numVertexCoords = params.length + params.length / 2;
+ int numPositionCoords = params.length;
+ DoubleBuffer verts = BufferUtil.newDoubleBuffer(numVertexCoords);
+ DoubleBuffer positions = BufferUtil.newDoubleBuffer(numPositionCoords);
+
+ // Determine the elevation model's target resolution.
+ ElevationModel.Elevations elevations = dc.getGlobe().getElevationModel().getElevations(this.getSector(),
+ resolution);
+
+ Point pu = this.unitp1; // unit vectors at triangle vertices at sphere surface
+ Point pv = this.unitp2;
+ Point pw = this.unitp0;
+
+ int i = 0;
+ while (verts.hasRemaining())
+ {
+ double u = params[i++];
+ double v = params[i++];
+ double w = 1d - u - v;
+
+ // Compute point on triangle.
+ double x = u * pu.x() + v * pv.x() + w * pw.x();
+ double y = u * pu.y() + v * pv.y() + w * pw.y();
+ double z = u * pu.z() + v * pv.z() + w * pw.z();
+
+ // Compute latitude and longitude of the vector through point on triangle.
+ // Do this before applying ellipsoid eccentricity or elevation.
+ double lat = Math.atan2(y, Math.sqrt(x * x + z * z));
+ double lon = Math.atan2(x, z);
+
+ // Scale point to lie on the globe's mean ellilpsoid surface.
+ double f = 1d / Math.sqrt(
+ x * x * this.globeInfo.invAsq + y * y * this.globeInfo.invCsq + z * z * this.globeInfo.invAsq);
+ x *= f;
+ y *= f;
+ z *= f;
+
+ // Scale the point so that it lies at the given elevation.
+ double elevation = elevations.getElevation(lat, lon);
+ double nx = 2 * x * this.globeInfo.invAsq;
+ double ny = 2 * y * this.globeInfo.invCsq;
+ double nz = 2 * z * this.globeInfo.invAsq;
+ double scale = elevation * dc.getVerticalExaggeration() / Math.sqrt(nx * nx + ny * ny + nz * nz);
+ nx *= scale;
+ ny *= scale;
+ nz *= scale;
+ lat = Math.atan2(y, Math.sqrt(x * x + z * z));
+ lon = Math.atan2(x, z);
+ x += (nx - this.pCentroid.getX());
+ y += (ny - this.pCentroid.getY());
+ z += (nz - this.pCentroid.getZ());
+
+ // Store point and position
+ verts.put(x).put(y).put(z);
+ positions.put(lon).put(lat);
+ // TODO: store normal as well
+ }
+
+ verts.rewind();
+
+ return new RenderInfo(density, verts, positions);
+ }
+
+ public void renderMultiTexture(DrawContext dc, int numTextureUnits)
+ {
+ // TODO: Validate args
+ this.render(dc, this.density, numTextureUnits);
+ }
+
+ public void render(DrawContext dc)
+ {
+ // TODO: Validate args
+ this.render(dc, this.density, 1);
+ }
+
+ private long render(DrawContext dc, int density, int numTextureUnits)
+ {
+ RenderInfo ri = this.makeVerts(dc, density);
+ java.nio.IntBuffer indices = getIndices(ri.density);
+ indices.rewind();
+
+ dc.getView().pushReferenceCenter(dc, this.pCentroid);
+
+ GL gl = dc.getGL();
+ gl.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT);
+ gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
+ gl.glVertexPointer(3, GL.GL_DOUBLE, 0, ri.vertices.rewind());
+
+ for (int i = 0; i < numTextureUnits; i++)
+ {
+ gl.glClientActiveTexture(GL.GL_TEXTURE0 + i);
+ gl.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY);
+ gl.glTexCoordPointer(2, GL.GL_DOUBLE, 0, ri.texCoords.rewind());
+ }
+
+ gl.glDrawElements(javax.media.opengl.GL.GL_TRIANGLE_STRIP, indices.limit(),
+ javax.media.opengl.GL.GL_UNSIGNED_INT, indices.rewind());
+
+ gl.glPopClientAttrib();
+
+ dc.getView().popReferenceCenter(dc);
+
+ return indices.limit() - 2; // return number of triangles rendered
+ }
+
+ public void renderWireframe(DrawContext dc, boolean showTriangles, boolean showTileBoundary)
+ {
+ RenderInfo ri = this.makeVerts(dc, this.density);
+ java.nio.IntBuffer indices = getIndices(ri.density);
+ indices.rewind();
+
+ dc.getView().pushReferenceCenter(dc, this.pCentroid);
+
+ javax.media.opengl.GL gl = dc.getGL();
+ // TODO: Could be overdoing the attrib push here. Check that all needed and perhaps save/retore instead.
+ gl.glPushAttrib(
+ GL.GL_DEPTH_BUFFER_BIT | GL.GL_POLYGON_BIT | GL.GL_TEXTURE_BIT | GL.GL_ENABLE_BIT | GL.GL_CURRENT_BIT);
+ gl.glEnable(GL.GL_BLEND);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
+ gl.glDisable(javax.media.opengl.GL.GL_DEPTH_TEST);
+ gl.glEnable(javax.media.opengl.GL.GL_CULL_FACE);
+ gl.glCullFace(javax.media.opengl.GL.GL_BACK);
+ gl.glDisable(javax.media.opengl.GL.GL_TEXTURE_2D);
+ gl.glColor4d(1d, 1d, 1d, 0.2);
+ gl.glPolygonMode(javax.media.opengl.GL.GL_FRONT, javax.media.opengl.GL.GL_LINE);
+
+ if (showTriangles)
+ {
+ gl.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT);
+ gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
+
+ gl.glVertexPointer(3, GL.GL_DOUBLE, 0, ri.vertices);
+ gl.glDrawElements(javax.media.opengl.GL.GL_TRIANGLE_STRIP, indices.limit(),
+ javax.media.opengl.GL.GL_UNSIGNED_INT, indices);
+
+ gl.glPopClientAttrib();
+ }
+
+ dc.getView().popReferenceCenter(dc);
+
+ if (showTileBoundary)
+ this.renderPatchBoundary(gl);
+
+ gl.glPopAttrib();
+ }
+
+ private void renderPatchBoundary(javax.media.opengl.GL gl)
+ {
+ gl.glColor4d(1d, 0, 0, 1d);
+ gl.glBegin(javax.media.opengl.GL.GL_TRIANGLES);
+ gl.glVertex3d(this.p0.x(), this.p0.y(), this.p0.z());
+ gl.glVertex3d(this.p1.x(), this.p1.y(), this.p1.z());
+ gl.glVertex3d(this.p2.x(), this.p2.y(), this.p2.z());
+ gl.glEnd();
+ }
+
+ public void renderBoundingVolume(DrawContext dc)
+ {
+ }
+
+ public void renderBoundary(DrawContext dc)
+ {
+ this.renderWireframe(dc, false, true);
+ }
+
+ public Point getSurfacePoint(Angle latitude, Angle longitude, double metersOffset)
+ {
+ // TODO: Replace below with interpolation over containing triangle.
+ return this.globeInfo.globe.computePointFromPosition(latitude, longitude, metersOffset);
+ }
+
+ public void pick(DrawContext dc, java.awt.Point pickPoint)
+ {
+ }
+
+ public long getSizeInBytes()
+ {
+ return this.byteSize;
+ }
+
+ public int compareTo(SectorGeometry that)
+ {
+ if (that == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.GeometryIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return this.getSector().compareTo(that.getSector());
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ IcosaTile icosaTile = (IcosaTile) o;
+
+ if (density != icosaTile.density)
+ return false;
+ if (level != icosaTile.level)
+ return false;
+ if (g0 != null ? !g0.equals(icosaTile.g0) : icosaTile.g0 != null)
+ return false;
+ if (g1 != null ? !g1.equals(icosaTile.g1) : icosaTile.g1 != null)
+ return false;
+ if (g2 != null ? !g2.equals(icosaTile.g2) : icosaTile.g2 != null)
+ return false;
+ if (globeInfo != null ? !globeInfo.equals(icosaTile.globeInfo) : icosaTile.globeInfo != null)
+ return false;
+ if (p0 != null ? !p0.equals(icosaTile.p0) : icosaTile.p0 != null)
+ return false;
+ if (p1 != null ? !p1.equals(icosaTile.p1) : icosaTile.p1 != null)
+ return false;
+ if (p2 != null ? !p2.equals(icosaTile.p2) : icosaTile.p2 != null)
+ return false;
+ if (this.getSector() != null ? !this.getSector().equals(icosaTile.getSector()) :
+ icosaTile.getSector() != null)
+ return false;
+ if (unitp0 != null ? !unitp0.equals(icosaTile.unitp0) : icosaTile.unitp0 != null)
+ return false;
+ if (unitp1 != null ? !unitp1.equals(icosaTile.unitp1) : icosaTile.unitp1 != null)
+ return false;
+ //noinspection RedundantIfStatement
+ if (unitp2 != null ? !unitp2.equals(icosaTile.unitp2) : icosaTile.unitp2 != null)
+ return false;
+
+ return true;
+ }
+
+ public int hashCode()
+ {
+ int result;
+ result = level;
+ result = 31 * result + (globeInfo != null ? globeInfo.hashCode() : 0);
+ result = 31 * result + (g0 != null ? g0.hashCode() : 0);
+ result = 31 * result + (g1 != null ? g1.hashCode() : 0);
+ result = 31 * result + (g2 != null ? g2.hashCode() : 0);
+ result = 31 * result + (this.getSector().hashCode());
+ result = 31 * result + (unitp0 != null ? unitp0.hashCode() : 0);
+ result = 31 * result + (unitp1 != null ? unitp1.hashCode() : 0);
+ result = 31 * result + (unitp2 != null ? unitp2.hashCode() : 0);
+ result = 31 * result + (p0 != null ? p0.hashCode() : 0);
+ result = 31 * result + (p1 != null ? p1.hashCode() : 0);
+ result = 31 * result + (p2 != null ? p2.hashCode() : 0);
+ result = 31 * result + density;
+ return result;
+ }
+ }
+
+ // Angles used to form icosahedral triangles.
+ private static Angle P30 = Angle.fromDegrees(30);
+ private static Angle N30 = Angle.fromDegrees(-30);
+ private static Angle P36 = Angle.fromDegrees(36);
+ private static Angle N36 = Angle.fromDegrees(-36);
+ private static Angle P72 = Angle.fromDegrees(72);
+ private static Angle N72 = Angle.fromDegrees(-72);
+ private static Angle P108 = Angle.fromDegrees(108);
+ private static Angle N108 = Angle.fromDegrees(-108);
+ private static Angle P144 = Angle.fromDegrees(144);
+ private static Angle N144 = Angle.fromDegrees(-144);
+
+ // Lat/lon of vertices of icosahedron aligned with lat/lon domain boundaries.
+ private static final LatLon[] L0 = {
+ new LatLon(Angle.POS90, N144), // 0
+ new LatLon(Angle.POS90, N72), // 1
+ new LatLon(Angle.POS90, Angle.ZERO), // 2
+ new LatLon(Angle.POS90, P72), // 3
+ new LatLon(Angle.POS90, P144), // 4
+ new LatLon(P30, Angle.NEG180), // 5
+ new LatLon(P30, N144), // 6
+ new LatLon(P30, N108), // 7
+ new LatLon(P30, N72), // 8
+ new LatLon(P30, N36), // 9
+ new LatLon(P30, Angle.ZERO), // 10
+ new LatLon(P30, P36), // 11
+ new LatLon(P30, P72), // 12
+ new LatLon(P30, P108), // 13
+ new LatLon(P30, P144), // 14
+ new LatLon(P30, Angle.POS180), // 15
+ new LatLon(N30, N144), // 16
+ new LatLon(N30, N108), // 17
+ new LatLon(N30, N72), // 18
+ new LatLon(N30, N36), // 19
+ new LatLon(N30, Angle.ZERO), // 20
+ new LatLon(N30, P36), // 21
+ new LatLon(N30, P72), // 22
+ new LatLon(N30, P108), // 23
+ new LatLon(N30, P144), // 24
+ new LatLon(N30, Angle.POS180), // 25
+ new LatLon(N30, N144), // 26
+ new LatLon(Angle.NEG90, N108), // 27
+ new LatLon(Angle.NEG90, N36), // 28
+ new LatLon(Angle.NEG90, P36), // 29
+ new LatLon(Angle.NEG90, P108), // 30
+ new LatLon(Angle.NEG90, Angle.POS180), // 31
+ new LatLon(P30, Angle.NEG180), // 32
+ new LatLon(N30, Angle.NEG180), // 33
+ new LatLon(Angle.NEG90, Angle.NEG180),}; // 34
+
+ public static IcosaTile createTileFromAngles(GlobeInfo globeInfo, int level, LatLon g0, LatLon g1, LatLon g2)
+ {
+ return new IcosaTile(globeInfo, level, g0, g1, g2);
+ }
+
+ private static java.util.ArrayList makeLevelZeroEquilateralTriangles(GlobeInfo globeInfo)
+ {
+ java.util.ArrayList topLevels = new java.util.ArrayList(22);
+
+ // These lines form the level 0 icosahedral triangles. Two of the icosahedral triangles,
+ // however, are split to form right triangles whose sides align with the longitude domain
+ // limits (-180/180) so that no triangle spans the discontinuity between +180 and -180.
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[5], L0[7], L0[0]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[7], L0[9], L0[1]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[9], L0[11], L0[2]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[11], L0[13], L0[3]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[13], L0[15], L0[4]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[16], L0[7], L0[5]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[16], L0[18], L0[7]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[18], L0[9], L0[7]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[18], L0[20], L0[9]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[20], L0[11], L0[9])); // triangle centered on 0 lat, 0 lon
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[20], L0[22], L0[11]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[22], L0[13], L0[11]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[22], L0[24], L0[13]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[24], L0[15], L0[13]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[24], L0[25], L0[15])); // right triangle
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[33], L0[26], L0[32])); // right triangle
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[27], L0[18], L0[16]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[28], L0[20], L0[18]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[29], L0[22], L0[20]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[30], L0[24], L0[22]));
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[25], L0[24], L0[31])); // right triangle
+ topLevels.add(createTileFromAngles(globeInfo, 0, L0[26], L0[33], L0[34])); // right triangle
+
+ return topLevels;
+ }
+
+ @SuppressWarnings({"FieldCanBeLocal"})
+ private final Globe globe;
+ @SuppressWarnings({"FieldCanBeLocal"})
+ private final GlobeInfo globeInfo;
+ private final java.util.ArrayList topLevels;
+ private SectorGeometryList currentTiles = new SectorGeometryList();
+ private Frustum currentFrustum;
+ private int currentLevel;
+ private int maxLevel = DEFAULT_MAX_LEVEL;//14; // TODO: Make configurable
+ private Sector sector; // union of all tiles selected during call to render()
+ private int density = DEFAULT_DENSITY; // TODO: make configurable
+
+ public EllipsoidIcosahedralTessellator(Globe globe)
+ {
+ this.globe = globe;
+ this.globeInfo = new GlobeInfo(this.globe);
+ this.topLevels = makeLevelZeroEquilateralTriangles(this.globeInfo);
+ }
+
+ public Sector getSector()
+ {
+ return this.sector;
+ }
+
+ public SectorGeometryList tessellate(DrawContext dc)
+ {
+ View view = dc.getView();
+
+ this.currentTiles.clear();
+ this.currentLevel = 0;
+ this.sector = null;
+
+ this.currentFrustum = view.getFrustumInModelCoordinates();
+
+ for (IcosaTile tile : topLevels)
+ {
+ this.selectVisibleTiles(tile, view);
+ }
+
+ dc.setVisibleSector(this.getSector());
+
+ return this.currentTiles;
+ }
+
+ private boolean needToSplit(IcosaTile tile, View view)
+ {
+ double d1 = view.getEyePoint().distanceTo(tile.p0);
+ double d2 = view.getEyePoint().distanceTo(tile.p1);
+ double d3 = view.getEyePoint().distanceTo(tile.p2);
+ double d4 = view.getEyePoint().distanceTo(tile.pCentroid);
+
+ double minDistance = d1;
+ if (d2 < minDistance)
+ minDistance = d2;
+ if (d3 < minDistance)
+ minDistance = d3;
+ if (d4 < minDistance)
+ minDistance = d4;
+
+ // Meets criteria when the texel size is less than the size of some number of pixels.
+ double pixelSize = view.computePixelSizeAtDistance(minDistance);
+ return tile.edgeLength / this.density >= 30d * (2d * pixelSize); // 2x pixel size to compensate for view bug
+ }
+
+ private void selectVisibleTiles(IcosaTile tile, View view)
+ {
+ if (!tile.getExtent().intersects(this.currentFrustum))
+ return;
+
+ if (this.currentLevel < this.maxLevel && this.needToSplit(tile, view))
+ {
+ ++this.currentLevel;
+ IcosaTile[] subtiles = tile.split();
+ for (IcosaTile child : subtiles)
+ {
+ this.selectVisibleTiles(child, view);
+ }
+ --this.currentLevel;
+ return;
+ }
+ this.sector = tile.getSector().union(this.sector);
+ this.currentTiles.add(tile);
+ }
+
+ private static class CacheKey
+ {
+ private final Point centroid;
+ private int resolution;
+ private final double verticalExaggeration;
+ private int density;
+
+ private CacheKey(IcosaTile tile, int resolution, double verticalExaggeration, int density)
+ {
+ // private class, no validation required.
+ this.centroid = tile.pCentroid;
+ this.resolution = resolution;
+ this.verticalExaggeration = verticalExaggeration;
+ this.density = density;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "density " + this.density + " ve " + this.verticalExaggeration + " resolution " + this.resolution;
+// + " g0 " + this.g0 + " g1 " + this.g1 + " g2 " + this.g2;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ CacheKey cacheKey = (CacheKey) o;
+
+ if (density != cacheKey.density)
+ return false;
+ if (resolution != cacheKey.resolution)
+ return false;
+ if (Double.compare(cacheKey.verticalExaggeration, verticalExaggeration) != 0)
+ return false;
+ //noinspection RedundantIfStatement
+ if (centroid != null ? !centroid.equals(cacheKey.centroid) : cacheKey.centroid != null)
+ return false;
+
+ return true;
+ }
+
+ public int hashCode()
+ {
+ int result;
+ long temp;
+ result = (centroid != null ? centroid.hashCode() : 0);
+ result = 31 * result + resolution;
+ temp = verticalExaggeration != +0.0d ? Double.doubleToLongBits(verticalExaggeration) : 0L;
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ result = 31 * result + density;
+ return result;
+ }
+ }
+}
+//
+// private static java.util.ArrayList makeRightTrianglesLevel0(GlobeInfo globeInfo)
+// {
+// java.util.ArrayList topLevels = new java.util.ArrayList(40);
+//
+// // Creates all right triangles by splitting each of the 20 icosahedral triangles.
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[5], L0[6], L0[0]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[7], L0[6], L0[0]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[7], L0[8], L0[1]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[9], L0[8], L0[1]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[9], L0[10], L0[2]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[11], L0[10], L0[2]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[11], L0[12], L0[3]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[13], L0[12], L0[3]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[13], L0[14], L0[4]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[15], L0[14], L0[4]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[5], L0[6], L0[16]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[7], L0[6], L0[16]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[16], L0[17], L0[7]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[18], L0[17], L0[7]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[7], L0[8], L0[18]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[9], L0[8], L0[18]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[18], L0[19], L0[9]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[20], L0[19], L0[9]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[9], L0[10], L0[20]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[11], L0[10], L0[20]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[20], L0[21], L0[11]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[22], L0[21], L0[11]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[11], L0[12], L0[22]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[13], L0[12], L0[22]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[22], L0[23], L0[13]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[24], L0[23], L0[13]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[13], L0[14], L0[24]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[15], L0[14], L0[24]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[24], L0[25], L0[15]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[26], L0[25], L0[15]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[16], L0[17], L0[27]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[18], L0[17], L0[27]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[18], L0[19], L0[28]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[20], L0[19], L0[28]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[20], L0[21], L0[29]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[22], L0[21], L0[29]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[22], L0[23], L0[30]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[24], L0[23], L0[30]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[24], L0[25], L0[31]));
+// topLevels.add(createTileFromAngles(globeInfo, 0, L0[26], L0[25], L0[31]));
+//
+// return topLevels;
+// }
diff --git a/gov/nasa/worldwind/globes/EllipsoidRectangularTessellator.java b/gov/nasa/worldwind/globes/EllipsoidRectangularTessellator.java
new file mode 100644
index 0000000..8eba46a
--- /dev/null
+++ b/gov/nasa/worldwind/globes/EllipsoidRectangularTessellator.java
@@ -0,0 +1,944 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.globes;
+
+import com.sun.opengl.util.*;
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+
+import javax.media.opengl.*;
+import java.nio.*;
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: EllipsoidRectangularTessellator.java 1787 2007-05-08 17:11:30Z dcollins $
+ */
+public class EllipsoidRectangularTessellator extends WWObjectImpl implements Tessellator
+{
+ // TODO: Make all this configurable
+ private static final int DEFAULT_DENSITY = 24;
+ private static final double DEFAULT_LOG10_RESOLUTION_TARGET = 1.3;
+ private static final int DEFAULT_MAX_LEVEL = 12;
+ private static final int DEFAULT_NUM_LAT_SUBDIVISIONS = 5;
+ private static final int DEFAULT_NUM_LON_SUBDIVISIONS = 10;
+
+ private static class RenderInfo
+ {
+ private final int density;
+ private final Point referenceCenter;
+ private final DoubleBuffer vertices;
+ private final DoubleBuffer texCoords;
+ private final IntBuffer indices;
+ private final int resolution;
+
+ private RenderInfo(int density, DoubleBuffer vertices, DoubleBuffer texCoords, Point refCenter, int resolution)
+ {
+ this.density = density;
+ this.vertices = vertices;
+ this.texCoords = texCoords;
+ this.referenceCenter = refCenter;
+ this.indices = RectTile.getIndices(this.density);
+ this.resolution = resolution;
+ }
+
+ private long getSizeInBytes()
+ {
+ return 16 + (this.vertices.limit() + this.texCoords.limit()) * Double.SIZE;
+ }
+ }
+
+ private static class CacheKey
+ {
+ private final Sector sector;
+ private int resolution;
+ private final double verticalExaggeration;
+ private int density;
+
+ private CacheKey(RectTile tile, int resolution, double verticalExaggeration, int density)
+ {
+ this.sector = tile.sector;
+ this.resolution = resolution;
+ this.verticalExaggeration = verticalExaggeration;
+ this.density = density;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "density " + this.density + " ve " + this.verticalExaggeration + " resolution " + this.resolution
+ + " sector " + this.sector;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ CacheKey cacheKey = (CacheKey) o;
+
+ if (density != cacheKey.density)
+ return false;
+ if (resolution != cacheKey.resolution)
+ return false;
+ if (Double.compare(cacheKey.verticalExaggeration, verticalExaggeration) != 0)
+ return false;
+ //noinspection RedundantIfStatement
+ if (sector != null ? !sector.equals(cacheKey.sector) : cacheKey.sector != null)
+ return false;
+
+ return true;
+ }
+
+ public int hashCode()
+ {
+ int result;
+ long temp;
+ result = (sector != null ? sector.hashCode() : 0);
+ result = 31 * result + resolution;
+ temp = verticalExaggeration != +0.0d ? Double.doubleToLongBits(verticalExaggeration) : 0L;
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ result = 31 * result + density;
+ return result;
+ }
+ }
+
+ private static class RectTile implements SectorGeometry
+ {
+ private static final HashMap parameterizations = new HashMap();
+ private static final HashMap indexLists = new HashMap();
+
+ private final Globe globe;
+ private final int level;
+ private final Sector sector;
+ private final Cylinder extent; // extent of triangle in object coordinates
+ private final int density;
+ private final double log10CellSize;
+ private long byteSize;
+ private RenderInfo ri;
+
+ private PickSupport pickSupport = new PickSupport();
+ private int minColorCode = 0;
+ private int maxColorCode = 0;
+
+ public RectTile(Globe globe, int level, int density, Sector sector)
+ {
+ this.globe = globe;
+ this.level = level;
+ this.density = density;
+ this.sector = sector;
+ this.extent = Sector.computeBoundingCylinder(globe, 1d, this.getSector());
+ double cellSize = (sector.getDeltaLatRadians() * globe.getRadius()) / density;
+ this.log10CellSize = Math.log10(cellSize);
+ }
+
+ public Sector getSector()
+ {
+ return this.sector;
+ }
+
+ public Extent getExtent()
+ {
+ return this.extent;
+ }
+
+ public long getSizeInBytes()
+ {
+ return this.byteSize;
+ }
+
+ private RectTile[] split()
+ {
+ Sector[] sectors = this.sector.subdivide();
+
+ RectTile[] subTiles = new RectTile[4];
+ subTiles[0] = new RectTile(this.globe, this.level + 1, this.density, sectors[0]);
+ subTiles[1] = new RectTile(this.globe, this.level + 1, this.density, sectors[1]);
+ subTiles[2] = new RectTile(this.globe, this.level + 1, this.density, sectors[2]);
+ subTiles[3] = new RectTile(this.globe, this.level + 1, this.density, sectors[3]);
+
+ return subTiles;
+ }
+
+ private void makeVerts(DrawContext dc)
+ {
+ int resolution = dc.getGlobe().getElevationModel().getTargetResolution(dc, this.sector, this.density);
+
+ if (this.ri != null && this.ri.resolution >= resolution)
+ return;
+
+ CacheKey cacheKey = new CacheKey(this, resolution, dc.getVerticalExaggeration(), this.density);
+ this.ri = (RenderInfo) WorldWind.memoryCache().getObject(cacheKey);
+ if (this.ri != null)
+ return;
+
+ this.ri = this.buildVerts(dc, this.density, resolution, true);
+ if (this.ri != null && this.ri.resolution >= 0)//&& this.ri.elevationsFullyResolved)
+ WorldWind.memoryCache().add(this.ri.resolution, this.ri, this.byteSize = this.ri.getSizeInBytes());
+ }
+
+ private RenderInfo buildVerts(DrawContext dc, int density, int resolution, boolean makeSkirts)
+ {
+ int numVertices = (density + 3) * (density + 3);
+ java.nio.DoubleBuffer verts = BufferUtil.newDoubleBuffer(numVertices * 3);
+
+ Globe globe = dc.getGlobe();
+ ElevationModel.Elevations elevations = globe.getElevationModel().getElevations(this.sector, resolution);
+
+ double latMin = this.sector.getMinLatitude().radians;
+ double latMax = this.sector.getMaxLatitude().radians;
+ double dLat = (latMax - latMin) / density;
+
+ double lonMin = this.sector.getMinLongitude().radians;
+ double lonMax = this.sector.getMaxLongitude().radians;
+ double dLon = (lonMax - lonMin) / density;
+
+ int iv = 0;
+ double lat = latMin;
+ double verticalExaggeration = dc.getVerticalExaggeration();
+ double exaggeratedMinElevation = makeSkirts ? globe.getMinElevation() * verticalExaggeration : 0;
+ double equatorialRadius = globe.getEquatorialRadius();
+ double eccentricity = globe.getEccentricitySquared();
+
+ LatLon centroid = sector.getCentroid();
+ Point refCenter = globe.computePointFromPosition(centroid.getLatitude(), centroid.getLongitude(), 0d);
+
+ for (int j = 0; j <= density + 2; j++)
+ {
+ double cosLat = Math.cos(lat);
+ double sinLat = Math.sin(lat);
+ double rpm = equatorialRadius / Math.sqrt(1.0 - eccentricity * sinLat * sinLat);
+ double lon = lonMin;
+ for (int i = 0; i <= density + 2; i++)
+ {
+ double elevation = verticalExaggeration * elevations.getElevation(lat, lon);
+ if (j == 0 || j >= density + 2 || i == 0 || i >= density + 2)
+ { // use abs to account for negative elevation.
+ elevation -= exaggeratedMinElevation >= 0 ? exaggeratedMinElevation : -exaggeratedMinElevation;
+ }
+
+ double x = ((rpm + elevation) * cosLat * Math.sin(lon)) - refCenter.getX();
+ double y = ((rpm * (1.0 - eccentricity) + elevation) * sinLat) - refCenter.getY();
+ double z = ((rpm + elevation) * cosLat * Math.cos(lon)) - refCenter.getZ();
+
+ verts.put(iv++, x).put(iv++, y).put(iv++, z);
+
+ if (i > density)
+ lon = lonMax;
+ else if (i != 0)
+ lon += dLon;
+ }
+ if (j > density)
+ lat = latMax;
+ else if (j != 0)
+ lat += dLat;
+ }
+
+ return new RenderInfo(density, verts, getParameterization(density), refCenter, elevations.getResolution());
+ }
+
+ public void renderMultiTexture(DrawContext dc, int numTextureUnits)
+ {
+ if (dc == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (numTextureUnits < 1)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.NumTextureUnitsLessThanOne");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.render(dc, numTextureUnits);
+ }
+
+ public void render(DrawContext dc)
+ {
+ if (dc == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.render(dc, 1);
+ }
+
+ private long render(DrawContext dc, int numTextureUnits)
+ {
+ if (this.ri == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.RenderInfoIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ dc.getView().pushReferenceCenter(dc, ri.referenceCenter);
+
+ GL gl = dc.getGL();
+ gl.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT);
+ gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
+ gl.glVertexPointer(3, GL.GL_DOUBLE, 0, this.ri.vertices.rewind());
+
+ for (int i = 0; i < numTextureUnits; i++)
+ {
+ gl.glClientActiveTexture(GL.GL_TEXTURE0 + i);
+ gl.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY);
+ gl.glTexCoordPointer(2, GL.GL_DOUBLE, 0, ri.texCoords.rewind());
+ }
+
+ gl.glDrawElements(javax.media.opengl.GL.GL_TRIANGLE_STRIP, this.ri.indices.limit(),
+ javax.media.opengl.GL.GL_UNSIGNED_INT, this.ri.indices.rewind());
+
+ gl.glPopClientAttrib();
+
+ dc.getView().popReferenceCenter(dc);
+
+ return this.ri.indices.limit() - 2; // return number of triangles rendered
+ }
+
+ public void renderWireframe(DrawContext dc, boolean showTriangles, boolean showTileBoundary)
+ {
+ if (dc == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (this.ri == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.RenderInfoIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ java.nio.IntBuffer indices = getIndices(this.ri.density);
+ indices.rewind();
+
+ dc.getView().pushReferenceCenter(dc, this.ri.referenceCenter);
+
+ javax.media.opengl.GL gl = dc.getGL();
+ gl.glPushAttrib(
+ GL.GL_DEPTH_BUFFER_BIT | GL.GL_POLYGON_BIT | GL.GL_TEXTURE_BIT | GL.GL_ENABLE_BIT | GL.GL_CURRENT_BIT);
+ gl.glEnable(GL.GL_BLEND);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
+ gl.glDisable(javax.media.opengl.GL.GL_DEPTH_TEST);
+ gl.glEnable(javax.media.opengl.GL.GL_CULL_FACE);
+ gl.glCullFace(javax.media.opengl.GL.GL_BACK);
+ gl.glDisable(javax.media.opengl.GL.GL_TEXTURE_2D);
+ gl.glColor4d(1d, 1d, 1d, 0.2);
+ gl.glPolygonMode(javax.media.opengl.GL.GL_FRONT, javax.media.opengl.GL.GL_LINE);
+
+ if (showTriangles)
+ {
+ gl.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT);
+ gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
+
+ gl.glVertexPointer(3, GL.GL_DOUBLE, 0, this.ri.vertices);
+ gl.glDrawElements(javax.media.opengl.GL.GL_TRIANGLE_STRIP, indices.limit(),
+ javax.media.opengl.GL.GL_UNSIGNED_INT, indices);
+
+ gl.glPopClientAttrib();
+ }
+
+ dc.getView().popReferenceCenter(dc);
+
+ if (showTileBoundary)
+ this.renderPatchBoundary(dc, gl);
+
+ gl.glPopAttrib();
+ }
+
+ private void renderPatchBoundary(DrawContext dc, GL gl)
+ {
+ // TODO: Currently only works if called from renderWireframe because no state is set here.
+ // TODO: Draw the boundary using the vertices along the boundary rather than just at the corners.
+ gl.glColor4d(1d, 0, 0, 1d);
+ Point[] corners = this.sector.computeCornerPoints(dc.getGlobe());
+
+ gl.glBegin(javax.media.opengl.GL.GL_QUADS);
+ gl.glVertex3d(corners[0].x(), corners[0].y(), corners[0].z());
+ gl.glVertex3d(corners[1].x(), corners[1].y(), corners[1].z());
+ gl.glVertex3d(corners[2].x(), corners[2].y(), corners[2].z());
+ gl.glVertex3d(corners[3].x(), corners[3].y(), corners[3].z());
+ gl.glEnd();
+ }
+
+ public void renderBoundingVolume(DrawContext dc)
+ {
+ ((Cylinder) this.getExtent()).render(dc);
+ }
+
+ public void pick(DrawContext dc, java.awt.Point pickPoint)
+ {
+ if (this.ri == null)
+ return;
+
+ renderTrianglesWithUniqueColors(dc, ri);
+
+ int colorCode = pickSupport.getTopColor(dc, pickPoint);
+ if (colorCode < minColorCode || colorCode > maxColorCode)
+ return;
+
+ double EPSILON = (double) 0.00001f;
+
+ int triangleIndex = colorCode - minColorCode - 1;
+
+ if ((null != ri.indices) && (triangleIndex < ri.indices.capacity() - 2))
+ {
+ double centerX = ri.referenceCenter.getX();
+ double centerY = ri.referenceCenter.getY();
+ double centerZ = ri.referenceCenter.getZ();
+
+ int vIndex = 3 * ri.indices.get(triangleIndex);
+ Point v0 = new Point((ri.vertices.get(vIndex++) + centerX),
+ (ri.vertices.get(vIndex++) + centerY),
+ (ri.vertices.get(vIndex) + centerZ));
+
+ vIndex = 3 * ri.indices.get(triangleIndex + 1);
+ Point v1 = new Point((ri.vertices.get(vIndex++) + centerX),
+ (ri.vertices.get(vIndex++) + centerY),
+ (ri.vertices.get(vIndex) + centerZ));
+
+ vIndex = 3 * ri.indices.get(triangleIndex + 2);
+ Point v2 = new Point((ri.vertices.get(vIndex++) + centerX),
+ (ri.vertices.get(vIndex++) + centerY),
+ (ri.vertices.get(vIndex) + centerZ));
+
+ // get triangle edge vectors and plane normal
+ Point e1 = v1.subtract(v0);
+ Point e2 = v2.subtract(v0);
+ Point N = e1.cross(e2); // if N is 0, the triangle is degenerate, we are not dealing with it
+
+ Line ray = dc.getView().computeRayFromScreenPoint(pickPoint.getX(), pickPoint.getY());
+
+ Point w0 = ray.getOrigin().subtract(v0);
+ double a = -N.dot(w0);
+ double b = N.dot(ray.getDirection());
+ if (java.lang.Math.abs(b) < EPSILON) // ray is parallel to triangle plane
+ return; // if a == 0 , ray lies in triangle plane
+ double r = a / b;
+
+ Point intersect = ray.getOrigin().add(ray.getDirection().multiply(r));
+ Position pp = dc.getGlobe().computePositionFromPoint(intersect);
+
+ // Draw the elevation from the elevation model, not the geode.
+ double elev = dc.getGlobe().getElevation(pp.getLatitude(), pp.getLongitude());
+ Position p = new Position(pp.getLatitude(), pp.getLongitude(), elev);
+
+ PickedObject po = new PickedObject(colorCode, p, pp.getLatitude(), pp.getLongitude(), elev, true);
+ dc.addPickedObject(po);
+ }
+ }
+
+ private void renderTrianglesWithUniqueColors(DrawContext dc, RenderInfo ri)
+ {
+ if (dc == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ if (ri.vertices == null)
+ return;
+
+ ri.vertices.rewind();
+ ri.indices.rewind();
+
+ javax.media.opengl.GL gl = dc.getGL();
+
+ if (null != ri.referenceCenter)
+ dc.getView().pushReferenceCenter(dc, ri.referenceCenter);
+
+ minColorCode = dc.getUniquePickColor().getRGB();
+ int trianglesNum = ri.indices.capacity() - 2;
+
+ gl.glBegin(GL.GL_TRIANGLES);
+ for (int i = 0; i < trianglesNum; i++)
+ {
+ java.awt.Color color = dc.getUniquePickColor();
+ gl.glColor3ub((byte) (color.getRed() & 0xFF),
+ (byte) (color.getGreen() & 0xFF),
+ (byte) (color.getBlue() & 0xFF));
+
+ int vIndex = 3 * ri.indices.get(i);
+ gl.glVertex3d(ri.vertices.get(vIndex), ri.vertices.get(vIndex + 1), ri.vertices.get(
+ vIndex + 2));
+
+ vIndex = 3 * ri.indices.get(i + 1);
+ gl.glVertex3d(ri.vertices.get(vIndex), ri.vertices.get(vIndex + 1), ri.vertices.get(
+ vIndex + 2));
+
+ vIndex = 3 * ri.indices.get(i + 2);
+ gl.glVertex3d(ri.vertices.get(vIndex), ri.vertices.get(vIndex + 1), ri.vertices.get(
+ vIndex + 2));
+ }
+ gl.glEnd();
+ maxColorCode = dc.getUniquePickColor().getRGB();
+
+ if (null != ri.referenceCenter)
+ dc.getView().popReferenceCenter(dc);
+ }
+
+ public Point getSurfacePoint(Angle latitude, Angle longitude, double metersOffset)
+ {
+ if (latitude == null || longitude == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.LatLonIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (!this.sector.contains(latitude, longitude))
+ {
+ // not on this geometry
+ return null;
+ }
+
+ if (this.ri == null)
+ return null;
+
+ double lat = latitude.getDegrees();
+ double lon = longitude.getDegrees();
+
+ double bottom = this.sector.getMinLatitude().getDegrees();
+ double top = this.sector.getMaxLatitude().getDegrees();
+ double left = this.sector.getMinLongitude().getDegrees();
+ double right = this.sector.getMaxLongitude().getDegrees();
+
+ double leftDecimal = (lon - left) / (right - left);
+ double bottomDecimal = (lat - bottom) / (top - bottom);
+
+ int row = (int) (bottomDecimal * (this.density));
+ int column = (int) (leftDecimal * (this.density));
+
+ double l = createPosition(column, leftDecimal, ri.density);
+ double h = createPosition(row, bottomDecimal, ri.density);
+
+ Point result = RectTile.interpolate(row, column, l, h, ri);
+ result = result.add(ri.referenceCenter);
+ if (metersOffset != 0)
+ result = applyOffset(this.globe, result, metersOffset);
+
+ return result;
+ }
+
+ /**
+ * Offsets point
by metersOffset
meters.
+ *
+ * @param globe the Globe
from which to offset
+ * @param point the Point
to offset
+ * @param metersOffset the magnitude of the offset
+ * @return point
offset along its surface normal as if it were on globe
+ */
+ private static Point applyOffset(Globe globe, Point point, double metersOffset)
+ {
+ Point normal = globe.computeSurfaceNormalAtPoint(point);
+ point = Point.fromOriginAndDirection(metersOffset, normal, point);
+ return point;
+ }
+
+ /**
+ * Computes from a column (or row) number, and a given offset ranged [0,1] corresponding to the distance along
+ * the edge of this sector, where between this column and the next column the corresponding position will fall,
+ * in the range [0,1].
+ *
+ * @param start the number of the column or row to the left, below or on this position
+ * @param decimal the distance from the left or bottom of the current sector that this position falls
+ * @param density the number of intervals along the sector's side
+ * @return a decimal ranged [0,1] representing the position between two columns or rows, rather than between two
+ * edges of the sector
+ */
+ private static double createPosition(int start, double decimal, int density)
+ {
+ double l = ((double) start) / (double) density;
+ double r = ((double) (start + 1)) / (double) density;
+
+ return (decimal - l) / (r - l);
+ }
+
+ /**
+ * Calculates a Point
that sits at xDec
offset from column
to
+ * column + 1
and at yDec
offset from row
to row + 1
.
+ * Accounts for the diagonals.
+ *
+ * @param row represents the row which corresponds to a yDec
value of 0
+ * @param column represents the column which corresponds to an xDec
value of 0
+ * @param xDec constrained to [0,1]
+ * @param yDec constrained to [0,1]
+ * @param ri the render info holding the vertices, etc.
+ * @return a Point
geometrically within or on the boundary of the quadrilateral whose bottom left
+ * corner is indexed by (row
, column
)
+ */
+ private static Point interpolate(int row, int column, double xDec, double yDec, RenderInfo ri)
+ {
+ row++;
+ column++;
+
+ int numVerticesPerEdge = ri.density + 3;
+
+ int bottomLeft = row * numVerticesPerEdge + column;
+
+ bottomLeft *= 3;
+
+ int numVertsTimesThree = numVerticesPerEdge * 3;
+
+ Point bL = new Point(ri.vertices.get(bottomLeft), ri.vertices.get(bottomLeft + 1), ri.vertices.get(
+ bottomLeft + 2));
+ Point bR = new Point(ri.vertices.get(bottomLeft + 3), ri.vertices.get(bottomLeft + 4),
+ ri.vertices.get(bottomLeft + 5));
+
+ bottomLeft += numVertsTimesThree;
+
+ Point tL = new Point(ri.vertices.get(bottomLeft), ri.vertices.get(bottomLeft + 1), ri.vertices.get(
+ bottomLeft + 2));
+ Point tR = new Point(ri.vertices.get(bottomLeft + 3), ri.vertices.get(bottomLeft + 4),
+ ri.vertices.get(bottomLeft + 5));
+
+ return interpolate(bL, bR, tR, tL, xDec, yDec);
+ }
+
+ /**
+ * Calculates the point at (xDec, yDec) in the two triangles defined by {bL, bR, tL} and {bR, tR, tL}. If
+ * thought of as a quadrilateral, the diagonal runs from tL to bR. Of course, this isn't a quad, it's two
+ * triangles.
+ *
+ * @param bL the bottom left corner
+ * @param bR the bottom right corner
+ * @param tR the top right corner
+ * @param tL the top left corner
+ * @param xDec how far along, [0,1] 0 = left edge, 1 = right edge
+ * @param yDec how far along, [0,1] 0 = bottom edge, 1 = top edge
+ * @return the point xDec, yDec in the co-ordinate system defined by bL, bR, tR, tL
+ */
+ private static Point interpolate(Point bL, Point bR, Point tR, Point tL, double xDec, double yDec)
+ {
+ double pos = xDec + yDec;
+ if (pos == 1)
+ {
+ // on the diagonal - what's more, we don't need to do any "oneMinusT" calculation
+ return new Point(tL.x() * yDec + bR.x() * xDec, tL.y() * yDec + bR.y() * xDec,
+ tL.z() * yDec + bR.z() * xDec);
+ }
+ else if (pos > 1)
+ {
+ // in the "top right" half
+
+ // vectors pointing from top right towards the point we want (can be thought of as "negative" vectors)
+ Point horizontalVector = (tL.subtract(tR)).multiply(1 - xDec);
+ Point verticalVector = (bR.subtract(tR)).multiply(1 - yDec);
+
+ return tR.add(horizontalVector).add(verticalVector);
+ }
+ else
+ {
+ // pos < 1 - in the "bottom left" half
+
+ // vectors pointing from the bottom left towards the point we want
+ Point horizontalVector = (bR.subtract(bL)).multiply(xDec);
+ Point verticalVector = (tL.subtract(bL)).multiply(yDec);
+
+ return bL.add(horizontalVector).add(verticalVector);
+ }
+ }
+
+ public String toString()
+ {
+ return "level " + this.level + ", density " + this.density + ", sector " + this.sector;
+ }
+
+ protected static java.nio.DoubleBuffer getParameterization(int density)
+ {
+ if (density < 1)
+ {
+ density = 1;
+ }
+
+ // Approximate 1 to avoid shearing off of right and top skirts in SurfaceTileRenderer.
+ // TODO: dig into this more: why are the skirts being sheared off?
+ final double one = 0.999999;
+
+ java.nio.DoubleBuffer p = parameterizations.get(density);
+ if (p != null)
+ return p;
+
+ int coordCount = (density + 3) * (density + 3);
+ p = com.sun.opengl.util.BufferUtil.newDoubleBuffer(2 * coordCount);
+ double delta = 1d / density;
+ int k = 2 * (density + 3);
+ for (int j = 0; j < density; j++)
+ {
+ double v = j * delta;
+
+ // skirt column; duplicate first column
+ p.put(k++, 0d);
+ p.put(k++, v);
+
+ // interior columns
+ for (int i = 0; i < density; i++)
+ {
+ p.put(k++, i * delta); // u
+ p.put(k++, v);
+ }
+
+ // last interior column; force u to 1.
+ p.put(k++, one);//1d);
+ p.put(k++, v);
+
+ // skirt column; duplicate previous column
+ p.put(k++, one);//1d);
+ p.put(k++, v);
+ }
+
+ // Last interior row
+ //noinspection UnnecessaryLocalVariable
+ double v = one;//1d;
+ p.put(k++, 0d); // skirt column
+ p.put(k++, v);
+
+ for (int i = 0; i < density; i++)
+ {
+ p.put(k++, i * delta); // u
+ p.put(k++, v);
+ }
+ p.put(k++, one);//1d); // last interior column
+ p.put(k++, v);
+
+ p.put(k++, one);//1d); // skirt column
+ p.put(k++, v);
+
+ // last skirt row
+ int kk = k - 2 * (density + 3);
+ for (int i = 0; i < density + 3; i++)
+ {
+ p.put(k++, p.get(kk++));
+ p.put(k++, p.get(kk++));
+ }
+
+ // first skirt row
+ k = 0;
+ kk = 2 * (density + 3);
+ for (int i = 0; i < density + 3; i++)
+ {
+ p.put(k++, p.get(kk++));
+ p.put(k++, p.get(kk++));
+ }
+
+ parameterizations.put(density, p);
+
+ return p;
+ }
+
+ private static java.nio.IntBuffer getIndices(int density)
+ {
+ if (density < 1)
+ density = 1;
+
+ // return a pre-computed buffer if possible.
+ java.nio.IntBuffer buffer = indexLists.get(density);
+ if (buffer != null)
+ return buffer;
+
+ int sideSize = density + 2;
+
+ int indexCount = 2 * sideSize * sideSize + 4 * sideSize - 2;
+ buffer = com.sun.opengl.util.BufferUtil.newIntBuffer(indexCount);
+ int k = 0;
+ for (int i = 0; i < sideSize; i++)
+ {
+ buffer.put(k);
+ if (i > 0)
+ {
+ buffer.put(++k);
+ buffer.put(k);
+ }
+
+ if (i % 2 == 0) // even
+ {
+ buffer.put(++k);
+ for (int j = 0; j < sideSize; j++)
+ {
+ k += sideSize;
+ buffer.put(k);
+ buffer.put(++k);
+ }
+ }
+ else // odd
+ {
+ buffer.put(--k);
+ for (int j = 0; j < sideSize; j++)
+ {
+ k -= sideSize;
+ buffer.put(k);
+ buffer.put(--k);
+ }
+ }
+ }
+
+ indexLists.put(density, buffer);
+
+ return buffer;
+ }
+ }
+
+ private final java.util.ArrayList topLevels;
+ private SectorGeometryList currentTiles = new SectorGeometryList();
+ private Frustum currentFrustum;
+ private int currentLevel;
+ private int maxLevel = DEFAULT_MAX_LEVEL;//14; // TODO: Make configurable
+ private Sector sector; // union of all tiles selected during call to render()
+ private int density = DEFAULT_DENSITY; // TODO: make configurable
+
+ public EllipsoidRectangularTessellator(Globe globe)
+ {
+ this.topLevels = createTopLevelTiles(globe, DEFAULT_NUM_LAT_SUBDIVISIONS, DEFAULT_NUM_LON_SUBDIVISIONS);
+ }
+
+ public Sector getSector()
+ {
+ return this.sector;
+ }
+
+ private ArrayList createTopLevelTiles(Globe globe, int nRows, int nCols)
+ {
+ ArrayList tops = new ArrayList(nRows * nCols);
+
+ double deltaLat = 180d / nRows;
+ double deltaLon = 360d / nCols;
+ Angle lastLat = Angle.NEG90;
+
+ for (int row = 0; row < DEFAULT_NUM_LAT_SUBDIVISIONS; row++)
+ {
+ Angle lat = lastLat.addDegrees(deltaLat);
+ if (lat.getDegrees() + 1d > 90d)
+ lat = Angle.POS90;
+
+ Angle lastLon = Angle.NEG180;
+
+ for (int col = 0; col < DEFAULT_NUM_LON_SUBDIVISIONS; col++)
+ {
+ Angle lon = lastLon.addDegrees(deltaLon);
+ if (lon.getDegrees() + 1d > 180d)
+ lon = Angle.POS180;
+
+ tops.add(new RectTile(globe, 0, this.density, new Sector(lastLat, lat, lastLon, lon)));
+ lastLon = lon;
+ }
+ lastLat = lat;
+ }
+
+ return tops;
+ }
+
+ public SectorGeometryList tessellate(DrawContext dc)
+ {
+ if (dc == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (dc.getView() == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.ViewIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ this.currentTiles.clear();
+ this.currentLevel = 0;
+ this.sector = null;
+
+ this.currentFrustum = dc.getView().getFrustumInModelCoordinates();
+ for (RectTile tile : topLevels)
+ {
+ this.selectVisibleTiles(dc, tile);
+ }
+
+ dc.setVisibleSector(this.getSector());
+
+ for (SectorGeometry tile : this.currentTiles)
+ {
+ ((RectTile) tile).makeVerts(dc);
+ }
+
+ return this.currentTiles;
+ }
+
+ private void selectVisibleTiles(DrawContext dc, RectTile tile)
+ {
+ if (!tile.getExtent().intersects(this.currentFrustum))
+ return;
+
+ if (this.currentLevel < this.maxLevel && needToSplit(dc, tile))
+ {
+ ++this.currentLevel;
+ RectTile[] subtiles = tile.split();
+ for (RectTile child : subtiles)
+ {
+ this.selectVisibleTiles(dc, child);
+ }
+ --this.currentLevel;
+ return;
+ }
+ this.sector = tile.getSector().union(this.sector);
+ this.currentTiles.add(tile);
+ }
+
+ private static boolean needToSplit(DrawContext dc, RectTile tile)
+ {
+ Point[] corners = tile.sector.computeCornerPoints(dc.getGlobe());
+ Point centerPoint = tile.sector.computeCenterPoint(dc.getGlobe());
+
+ View view = dc.getView();
+ double d1 = view.getEyePoint().distanceTo(corners[0]);
+ double d2 = view.getEyePoint().distanceTo(corners[1]);
+ double d3 = view.getEyePoint().distanceTo(corners[2]);
+ double d4 = view.getEyePoint().distanceTo(corners[3]);
+ double d5 = view.getEyePoint().distanceTo(centerPoint);
+
+ double minDistance = d1;
+ if (d2 < minDistance)
+ minDistance = d2;
+ if (d3 < minDistance)
+ minDistance = d3;
+ if (d4 < minDistance)
+ minDistance = d4;
+ if (d5 < minDistance)
+ minDistance = d5;
+
+ double logDist = Math.log10(minDistance);
+ boolean useTile = tile.log10CellSize <= (logDist - DEFAULT_LOG10_RESOLUTION_TARGET);
+
+ return !useTile;
+ }
+
+ public static void main(String[] args)
+ {
+ int density = 5;
+
+ DoubleBuffer tcs = RectTile.getParameterization(density);
+ IntBuffer indices = RectTile.getIndices(density);
+
+ tcs.rewind();
+ indices.rewind();
+ for (int i = 0; i < indices.limit(); i++)
+ {
+ int index = indices.get(i);
+ System.out.println(index + ": " + tcs.get(2 * index) + ", " + tcs.get(2 * index + 1));
+ }
+ }
+}
diff --git a/gov/nasa/worldwind/globes/EllipsoidalGlobe.java b/gov/nasa/worldwind/globes/EllipsoidalGlobe.java
new file mode 100644
index 0000000..6947dcf
--- /dev/null
+++ b/gov/nasa/worldwind/globes/EllipsoidalGlobe.java
@@ -0,0 +1,380 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.globes;
+
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: EllipsoidalGlobe.java 1782 2007-05-08 06:27:54Z tgaskins $
+ */
+public class EllipsoidalGlobe extends WWObjectImpl implements Globe
+{
+ private final double equatorialRadius;
+ private final double polarRadius;
+ private final double es;
+ private final Point center;
+
+ private final ElevationModel elevationModel;
+
+ public EllipsoidalGlobe(double equatorialRadius, double polarRadius, double es, ElevationModel em)
+ {
+ if (em == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.ElevationModelIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.equatorialRadius = equatorialRadius;
+ this.polarRadius = polarRadius;
+ this.es = es; // assume it's consistent with the two radii
+ this.center = Point.ZERO;
+ this.elevationModel = em;
+ }
+
+ public final double getRadius()
+ {
+ return this.equatorialRadius;
+ }
+
+ public final double getEquatorialRadius()
+ {
+ return this.equatorialRadius;
+ }
+
+ public final double getPolarRadius()
+ {
+ return this.polarRadius;
+ }
+
+ public double getMaximumRadius()
+ {
+ return this.equatorialRadius;
+ }
+
+ public double getRadiusAt(Angle latitude, Angle longitude)
+ {
+ if (latitude == null || longitude == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ return this.computePointFromPosition(latitude, longitude, 0d).length();
+ }
+
+ public double getEccentricitySquared()
+ {
+ return this.es;
+ }
+
+ public final double getDiameter()
+ {
+ return this.equatorialRadius * 2;
+ }
+
+ public final Point getCenter()
+ {
+ return this.center;
+ }
+
+ public double getMaxElevation()
+ {
+ return this.elevationModel.getMaximumElevation();
+ }
+
+ public double getMinElevation()
+ {
+ return this.elevationModel.getMinimumElevation();
+ }
+
+ public final Extent getExtent()
+ {
+ return this;
+ }
+
+ public boolean intersects(Frustum frustum)
+ {
+ if (frustum == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.FrustumIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return frustum.intersects(this);
+ }
+
+ public Intersection[] intersect(Line line)
+ {
+ if (line == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LineIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ // Taken from Lengyel, 2Ed., Section 5.2.3, page 148.
+ double m = this.equatorialRadius / this.polarRadius;
+ double n = 1d; //this.equatorialRadius / this.equatorialRadius;
+ double m2 = m * m;
+ double n2 = n * n;
+
+ double vx = line.getDirection().getX();
+ double vy = line.getDirection().getY();
+ double vz = line.getDirection().getZ();
+ double sx = line.getOrigin().getX();
+ double sy = line.getOrigin().getY();
+ double sz = line.getOrigin().getZ();
+
+ double a = vx * vx + m2 * vy * vy + n2 * vz * vz;
+ double b = 2d * (sx * vx + m2 * sy * vy + n2 * sz * vz);
+ double c = sx * sx + m2 * sy * sy + n2 * sz * sz - this.equatorialRadius * this.equatorialRadius;
+
+ double discriminant = discriminant(a, b, c);
+ if (discriminant < 0)
+ return null;
+
+ double discriminantRoot = Math.sqrt(discriminant);
+ if (discriminant == 0)
+ {
+ Point p = line.getPointAt((-b - discriminantRoot) / (2 * a));
+ return new Intersection[] {new Intersection(p, true)};
+ }
+ else // (discriminant > 0)
+ {
+ Point near = line.getPointAt((-b - discriminantRoot) / (2 * a));
+ Point far = line.getPointAt((-b + discriminantRoot) / (2 * a));
+ return new Intersection[] {new Intersection(near, false), new Intersection(far, false)};
+ }
+ }
+
+ static private double discriminant(double a, double b, double c)
+ {
+ return b * b - 4 * a * c;
+ }
+
+ public boolean intersects(Line line)
+ {
+ if (line == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.LineIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ return line.distanceTo(this.center) <= this.equatorialRadius;
+ }
+
+ public boolean intersects(Plane plane)
+ {
+ if (plane == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PlaneIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ double dq1 = plane.dot(this.center);
+ return dq1 <= this.equatorialRadius;
+ }
+
+ public Point computeSurfaceNormalAtPoint(Point p)
+ {
+ if (p == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ p = p.subtract(this.center);
+
+ return new Point(p.x() / (this.equatorialRadius * this.equatorialRadius),
+ p.y() / (this.polarRadius * this.polarRadius),
+ p.z() / (this.equatorialRadius * this.equatorialRadius)).normalize();
+ }
+
+ public final ElevationModel getElevationModel()
+ {
+ return this.elevationModel;
+ }
+
+ public final double getElevation(Angle latitude, Angle longitude)
+ {
+ if (latitude == null || longitude == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LatitudeOrLongitudeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return this.elevationModel != null ? this.elevationModel.getElevation(latitude, longitude) : 0;
+ }
+
+ public final Point computePointFromPosition(Position position)
+ {
+ if (position == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PositionIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return this.geodeticToCartesian(position.getLatitude(), position.getLongitude(), position.getElevation());
+ }
+
+ public final Point computePointFromPosition(Angle latitude, Angle longitude, double metersElevation)
+ {
+ if (latitude == null || longitude == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LatitudeOrLongitudeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return this.geodeticToCartesian(latitude, longitude, metersElevation);
+ }
+
+ public final Position computePositionFromPoint(Point point)
+ {
+ if (point == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return this.cartesianToGeodetic(point);
+ }
+
+ public final Position getIntersectionPosition(Line line)
+ {
+ if (line == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.LineIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ Intersection[] intersections = this.intersect(line);
+ if (intersections == null)
+ return null;
+
+ return this.computePositionFromPoint(intersections[0].getIntersectionPoint());
+ }
+
+ // The code below maps latitude / longitude position to globe-centered Cartesian coordinates.
+ // The Y axis points to the north pole. The Z axis points to the intersection of the prime
+ // meridian and the equator, in the equatorial plane. The X axis completes a right-handed
+ // coordinate system, and is 90 degrees east of the Z axis and also in the equatorial plane.
+
+ private Point geodeticToCartesian(Angle latitude, Angle longitude, double metersElevation)
+ {
+ if (latitude == null || longitude == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LatitudeOrLongitudeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ double cosLat = latitude.cos();
+ double sinLat = latitude.sin();
+
+ double rpm = // getRadius (in meters) of vertical in prime meridian
+ this.equatorialRadius / Math.sqrt(1.0 - this.es * sinLat * sinLat);
+
+ double x = (rpm + metersElevation) * cosLat * longitude.sin();
+ double y = (rpm * (1.0 - this.es) + metersElevation) * sinLat;
+ double z = (rpm + metersElevation) * cosLat * longitude.cos();
+
+ return new Point(x, y, z);
+ }
+
+ private Position cartesianToGeodetic(Point cart)
+ {
+ if (cart == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ // according to
+ // H. Vermeille,
+ // Direct transformation from geocentric to geodetic ccordinates,
+ // Journal of Geodesy (2002) 76:451-454
+ double ra2 = 1 / (this.equatorialRadius * equatorialRadius);
+
+ double X = cart.z();
+ double Y = cart.x();
+ double Z = cart.y();
+ double e2 = this.es;
+ double e4 = e2 * e2;
+
+ double XXpYY = X * X + Y * Y;
+ double sqrtXXpYY = Math.sqrt(XXpYY);
+ double p = XXpYY * ra2;
+ double q = Z * Z * (1 - e2) * ra2;
+ double r = 1 / 6.0 * (p + q - e4);
+ double s = e4 * p * q / (4 * r * r * r);
+ double t = Math.pow(1 + s + Math.sqrt(s * (2 + s)), 1 / 3.0);
+ double u = r * (1 + t + 1 / t);
+ double v = Math.sqrt(u * u + e4 * q);
+ double w = e2 * (u + v - q) / (2 * v);
+ double k = Math.sqrt(u + v + w * w) - w;
+ double D = k * sqrtXXpYY / (k + e2);
+ double lon = 2 * Math.atan2(Y, X + sqrtXXpYY);
+ double sqrtDDpZZ = Math.sqrt(D * D + Z * Z);
+ double lat = 2 * Math.atan2(Z, D + sqrtDDpZZ);
+ double elevation = (k + e2 - 1) * sqrtDDpZZ / k;
+
+ return Position.fromRadians(lat, lon, elevation);
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ EllipsoidalGlobe that = (EllipsoidalGlobe) o;
+
+ if (Double.compare(that.equatorialRadius, equatorialRadius) != 0)
+ return false;
+ if (Double.compare(that.es, es) != 0)
+ return false;
+ if (Double.compare(that.polarRadius, polarRadius) != 0)
+ return false;
+ if (center != null ? !center.equals(that.center) : that.center != null)
+ return false;
+ //noinspection RedundantIfStatement
+ if (elevationModel != null ? !elevationModel.equals(that.elevationModel) : that.elevationModel != null)
+ return false;
+
+ return true;
+ }
+
+ public int hashCode()
+ {
+ int result;
+ long temp;
+ temp = equatorialRadius != +0.0d ? Double.doubleToLongBits(equatorialRadius) : 0L;
+ result = (int) (temp ^ (temp >>> 32));
+ temp = polarRadius != +0.0d ? Double.doubleToLongBits(polarRadius) : 0L;
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = es != +0.0d ? Double.doubleToLongBits(es) : 0L;
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ result = 31 * result + (center != null ? center.hashCode() : 0);
+ result = 31 * result + (elevationModel != null ? elevationModel.hashCode() : 0);
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/gov/nasa/worldwind/layers/AbstractLayer.java b/gov/nasa/worldwind/layers/AbstractLayer.java
new file mode 100644
index 0000000..2335096
--- /dev/null
+++ b/gov/nasa/worldwind/layers/AbstractLayer.java
@@ -0,0 +1,236 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers;
+
+import gov.nasa.worldwind.*;
+
+/**
+ * @author tag
+ * @version $Id: AbstractLayer.java 1735 2007-05-05 09:50:26Z tgaskins $
+ */
+public abstract class AbstractLayer extends WWObjectImpl implements Layer
+{
+ private boolean enabled = true;
+ private boolean pickable = true;
+ private double opacity = 1d;
+ private double minActiveAltitude = Double.MIN_VALUE;
+ private double maxActiveAltitude = Double.MAX_VALUE;
+
+ public boolean isEnabled()
+ {
+ return this.enabled;
+ }
+
+ public boolean isPickEnabled()
+ {
+ return pickable;
+ }
+
+ public void setPickEnabled(boolean pickable)
+ {
+ this.pickable = pickable;
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ this.enabled = enabled;
+ }
+
+ public String getName()
+ {
+ return this.myName();
+ }
+
+ public void setName(String name)
+ {
+ this.setValue(AVKey.DISPLAY_NAME, name);
+ }
+
+ public String toString()
+ {
+ return this.myName();
+ }
+
+ private String myName()
+ {
+ String myName = null;
+ Object name = this.getValue(AVKey.DISPLAY_NAME);
+ if (null != name)
+ myName = name.toString();
+ if (null == myName)
+ myName = this.toString();
+ return myName;
+ }
+
+ public double getOpacity()
+ {
+ return opacity;
+ }
+
+ public void setOpacity(double opacity)
+ {
+ this.opacity = opacity;
+ }
+
+ public double getMinActiveAltitude()
+ {
+ return minActiveAltitude;
+ }
+
+ public void setMinActiveAltitude(double minActiveAltitude)
+ {
+ this.minActiveAltitude = minActiveAltitude;
+ }
+
+ public double getMaxActiveAltitude()
+ {
+ return maxActiveAltitude;
+ }
+
+ public void setMaxActiveAltitude(double maxActiveAltitude)
+ {
+ this.maxActiveAltitude = maxActiveAltitude;
+ }
+
+ /**
+ * Indicates whether the layer is in the view. The method implemented here is a default indicating the layer is in
+ * view. Subclasses able to determine their presence in the view should override this implementation.
+ *
+ * @param dc the current draw context
+ * @return true
if the layer is in the view, false
otherwise.
+ */
+ public boolean isLayerInView(DrawContext dc)
+ {
+ if (dc == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ return true;
+ }
+
+ /**
+ * Indicates whether the layer is active based on arbitrary criteria. The method implemented here is a default
+ * indicating the layer is active if the current altitude is within the layer's min and max active altitudes.
+ * Subclasses able to consider more criteria should override this implementation.
+ * @param dc the current draw context
+ * @return true
if the layer is active, false
otherwise.
+ */
+ public boolean isLayerActive(DrawContext dc)
+ {
+ if (dc == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ if (null == dc.getView())
+ {
+ String message = WorldWind.retrieveErrMsg("layers.AbstractLayer.NoViewSpecifiedInDrawingContext");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ double altitude = dc.getView().getAltitude();
+
+ return altitude >= this.minActiveAltitude && altitude <= this.maxActiveAltitude;
+ }
+
+ /**
+ * @param dc the current draw context
+ * @throws IllegalArgumentException if dc
is null, or dc
's Globe
or
+ * View
is null
+ */
+ public void render(DrawContext dc)
+ {
+ if (!this.enabled)
+ return; // Don't check for arg errors if we're disabled
+
+ if (null == dc)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ if (null == dc.getGlobe())
+ {
+ String message = WorldWind.retrieveErrMsg("layers.AbstractLayer.NoGlobeSpecifiedInDrawingContext");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ if (null == dc.getView())
+ {
+ String message = WorldWind.retrieveErrMsg("layers.AbstractLayer.NoViewSpecifiedInDrawingContext");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ if (!this.isLayerActive(dc))
+ return;
+
+ if (!this.isLayerInView(dc))
+ return;
+
+ this.doRender(dc);
+ }
+
+ public void pick(DrawContext dc, java.awt.Point point)
+ {
+ if (!this.enabled)
+ return; // Don't check for arg errors if we're disabled
+
+ if (null == dc)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ if (null == dc.getGlobe())
+ {
+ String message = WorldWind.retrieveErrMsg("layers.AbstractLayer.NoGlobeSpecifiedInDrawingContext");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ if (null == dc.getView())
+ {
+ String message = WorldWind.retrieveErrMsg("layers.AbstractLayer.NoViewSpecifiedInDrawingContext");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ if (!this.isLayerActive(dc))
+ return;
+
+ if (!this.isLayerInView(dc))
+ return;
+
+ this.doPick(dc, point);
+ }
+
+ protected void doPick(DrawContext dc, java.awt.Point point)
+ {
+ // any state that could change the color needs to be disabled, such as GL_TEXTURE, GL_LIGHTING or GL_FOG.
+ // re-draw with unique colors
+ // store the object info in the selectable objects table
+ // read the color under the coursor
+ // use the color code as a key to retrieve a selected object from the selectable objects table
+ // create an instance of the PickedObject and add to the dc via the dc.addPickedObject() method
+ }
+
+ public void dispose() // override if disposal is a supported operation
+ {
+ }
+
+ protected abstract void doRender(DrawContext dc);
+}
diff --git a/gov/nasa/worldwind/layers/CompassLayer.java b/gov/nasa/worldwind/layers/CompassLayer.java
new file mode 100644
index 0000000..1e36ee9
--- /dev/null
+++ b/gov/nasa/worldwind/layers/CompassLayer.java
@@ -0,0 +1,436 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers;
+
+import com.sun.opengl.util.texture.*;
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+
+import javax.media.opengl.*;
+import java.io.*;
+
+/**
+ * @author tag
+ * @version $Id$
+ */
+public class CompassLayer extends AbstractLayer
+{
+ public final static String NORTHWEST = "gov.nasa.worldwind.CompassLayer.NorthWest";
+ public final static String SOUTHWEST = "gov.nasa.worldwind.CompassLayer.SouthWest";
+ public final static String NORTHEAST = "gov.nasa.worldwind.CompassLayer.NorthEast";
+ public final static String SOUTHEAST = "gov.nasa.worldwind.CompassLayer.SouthEast";
+
+ /**
+ * On window resize, scales the compass icon to occupy a constant relative size of the viewport.
+ */
+ public final static String RESIZE_STRETCH = "gov.nasa.worldwind.CompassLayer.ResizeStretch";
+ /**
+ * On window resize, scales the compass icon to occupy a constant relative size of the viewport, but not larger than
+ * the icon's inherent size scaled by the layer's icon scale factor.
+ */
+ public final static String RESIZE_SHRINK_ONLY = "gov.nasa.worldwind.CompassLayer.ResizeShrinkOnly";
+ /**
+ * Does not modify the compass icon size when the window changes size.
+ */
+ public final static String RESIZE_KEEP_FIXED_SIZE = "gov.nasa.worldwind.CompassLayer.ResizeKeepFixedSize";
+
+ private String iconFilePath = "images/notched-compass.png"; // TODO: make configurable
+ private double compassToViewportScale = 0.2; // TODO: make configurable
+ private double iconScale = 0.5;
+ private int borderWidth = 20; // TODO: make configurable
+ private String position = NORTHEAST; // TODO: make configurable
+ private String resizeBehavior = RESIZE_SHRINK_ONLY;
+ private Texture iconTexture = null;
+ private Point locationCenter = null;
+ private boolean showTilt = false;
+
+ public CompassLayer()
+ {
+ this.setOpacity(0.8); // TODO: make configurable
+ }
+
+ public CompassLayer(String iconFilePath)
+ {
+ this.setIconFilePath(iconFilePath);
+ this.setOpacity(0.8); // TODO: make configurable
+ }
+
+ /**
+ * Returns the layer's current icon file path.
+ *
+ * @return the icon file path
+ */
+ public String getIconFilePath()
+ {
+ return iconFilePath;
+ }
+
+ /**
+ * Sets the compass icon's image location. The layer first searches for this location in the current Java classpath.
+ * If not found then the specified path is assumed to refer to the local file system. found there then the
+ *
+ * @param iconFilePath the path to the icon's image file
+ */
+ public void setIconFilePath(String iconFilePath)
+ {
+ if (iconFilePath == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.IconFilePath");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ this.iconFilePath = iconFilePath;
+ }
+
+ /**
+ * Returns the layer's compass-to-viewport scale factor.
+ *
+ * @return the compass-to-viewport scale factor
+ */
+ public double getCompassToViewportScale()
+ {
+ return compassToViewportScale;
+ }
+
+ /**
+ * Sets the scale factor applied to the viewport size to determine the displayed size of the compass icon. This
+ * scale factor is used only when the layer's resize behavior is {@link #RESIZE_STRETCH} or {@link
+ * #RESIZE_SHRINK_ONLY}. The icon's width is adjusted to occupy the proportion of the viewport's width indicated by
+ * this factor. The icon's height is adjusted to maintain the compass image's native aspect ratio.
+ *
+ * @param compassToViewportScale the compass to viewport scale factor
+ */
+ public void setCompassToViewportScale(double compassToViewportScale)
+ {
+ this.compassToViewportScale = compassToViewportScale;
+ }
+
+ /**
+ * Returns the icon scale factor. See {@link #setIconScale(double)} for a description of the scale factor.
+ *
+ * @return the current icon scale
+ */
+ public double getIconScale()
+ {
+ return iconScale;
+ }
+
+ /**
+ * Sets the scale factor defining the displayed size of the compass icon relative to the icon's width and height in
+ * its image file. Values greater than 1 magify the image, values less than one minify it. If the layer's resize
+ * behavior is other than {@link #RESIZE_KEEP_FIXED_SIZE}, the icon's displayed sized is further affected by the
+ * value specified by {@link #setCompassToViewportScale(double)} and the current viewport size.
+ *
+ * @param iconScale the icon scale factor
+ */
+ public void setIconScale(double iconScale)
+ {
+ this.iconScale = iconScale;
+ }
+
+ /**
+ * Returns the compass icon's resize behavior.
+ *
+ * @return the icon's resize behavior
+ */
+ public String getResizeBehavior()
+ {
+ return resizeBehavior;
+ }
+
+ /**
+ * Sets the behavior the layer uses to size the compass icon when the viewport size changes, typically when the
+ * World Wind window is resized. If the value is {@link #RESIZE_KEEP_FIXED_SIZE}, the icon size is kept to the size
+ * specified in its image file scaled by the layer's current icon scale. If the value is {@link #RESIZE_STRETCH},
+ * the icon is resized to have a constant size relative to the current viewport size. If the viewport shrinks the
+ * icon size decreases; if it expands then the icon file enlarges. The relative size is determined by the current
+ * compass-to-viewport scale and by the icon's image file size scaled by the current icon scale. If the value is
+ * {@link #RESIZE_SHRINK_ONLY} (the default), icon sizing behaves as for {@link #RESIZE_STRETCH} but the icon will
+ * not grow larger than the size specified in its image file scaled by the current icon scale.
+ *
+ * @param resizeBehavior the desired resize behavior
+ */
+ public void setResizeBehavior(String resizeBehavior)
+ {
+ this.resizeBehavior = resizeBehavior;
+ }
+
+ public int getBorderWidth()
+ {
+ return borderWidth;
+ }
+
+ /**
+ * Sets the compass icon offset from the viewport border.
+ *
+ * @param borderWidth the number of pixels to offset the compass icon from the borders indicated by {@link
+ * #setPosition(String)}.
+ */
+ public void setBorderWidth(int borderWidth)
+ {
+ this.borderWidth = borderWidth;
+ }
+
+ /**
+ * Returns the current relative compass icon position.
+ *
+ * @return the current compass position
+ */
+ public String getPosition()
+ {
+ return position;
+ }
+
+ /**
+ * Sets the relative viewport location to display the compass icon. Can be one of {@link #NORTHEAST} (the default),
+ * {@link #NORTHWEST}, {@link #SOUTHEAST}, or {@link #SOUTHWEST}. These indicate the corner of the viewport to place
+ * the icon.
+ *
+ * @param position the desired compass position
+ */
+ public void setPosition(String position)
+ {
+ if (position == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.CompassPositionIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ this.position = position;
+ }
+
+ public Point getLocationCenter()
+ {
+ return locationCenter;
+ }
+
+ public void setLocationCenter(Point locationCenter)
+ {
+ this.locationCenter = locationCenter;
+ }
+
+ protected void doRender(DrawContext dc)
+ {
+ this.drawIcon(dc);
+ }
+
+ public boolean isShowTilt()
+ {
+ return showTilt;
+ }
+
+ public void setShowTilt(boolean showTilt)
+ {
+ this.showTilt = showTilt;
+ }
+
+ private void drawIcon(DrawContext dc)
+ {
+ if (this.iconFilePath == null)
+ return;
+
+ GL gl = dc.getGL();
+
+ boolean attribsPushed = false;
+ boolean modelviewPushed = false;
+ boolean projectionPushed = false;
+
+ try
+ {
+ gl.glPushAttrib(GL.GL_DEPTH_BUFFER_BIT
+ | GL.GL_COLOR_BUFFER_BIT
+ | GL.GL_ENABLE_BIT
+ | GL.GL_TEXTURE_BIT
+ | GL.GL_TRANSFORM_BIT
+ | GL.GL_VIEWPORT_BIT
+ | GL.GL_CURRENT_BIT);
+ attribsPushed = true;
+
+ if (this.iconTexture == null)
+ this.initializeTexture(dc);
+
+ gl.glEnable(GL.GL_TEXTURE_2D);
+ iconTexture.bind();
+
+ gl.glColor4d(1d, 1d, 1d, this.getOpacity());
+ gl.glEnable(GL.GL_BLEND);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+ gl.glDisable(GL.GL_DEPTH_TEST);
+
+ double width = this.getScaledIconWidth();
+ double height = this.getScaledIconHeight();
+
+ // Load a parallel projection with xy dimensions (viewportWidth, viewportHeight)
+ // into the GL projection matrix.
+ java.awt.Rectangle viewport = dc.getView().getViewport();
+ gl.glMatrixMode(javax.media.opengl.GL.GL_PROJECTION);
+ gl.glPushMatrix();
+ projectionPushed = true;
+ gl.glLoadIdentity();
+ double maxwh = width > height ? width : height;
+ gl.glOrtho(0d, viewport.width, 0d, viewport.height, -0.6 * maxwh, 0.6 * maxwh);
+
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPushMatrix();
+ modelviewPushed = true;
+ gl.glLoadIdentity();
+
+ double scale = this.computeScale(viewport);
+
+ Point locationSW = this.computeLocation(viewport, scale);
+
+ gl.glTranslated(locationSW.x(), locationSW.y(), locationSW.z());
+ gl.glScaled(scale, scale, 1);
+
+ gl.glTranslated(width / 2, height / 2, 0);
+ if (this.showTilt) // formula contributed by Ty Hayden
+ gl.glRotated(70d * (dc.getView().getPitch().getDegrees() / 90.0), 1d, 0d, 0d);
+ gl.glRotated(dc.getView().getHeading().getDegrees(), 0d, 0d, 1d);
+ gl.glTranslated(-width / 2, -height / 2, 0);
+
+ TextureCoords texCoords = this.iconTexture.getImageTexCoords();
+ gl.glScaled(width, height, 1d);
+ dc.drawUnitQuad(texCoords);
+ }
+ finally
+ {
+ if (projectionPushed)
+ {
+ gl.glMatrixMode(GL.GL_PROJECTION);
+ gl.glPopMatrix();
+ }
+ if (modelviewPushed)
+ {
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPopMatrix();
+ }
+ if (attribsPushed)
+ gl.glPopAttrib();
+ }
+ }
+
+ private double computeScale(java.awt.Rectangle viewport)
+ {
+ if (this.resizeBehavior.equals(RESIZE_SHRINK_ONLY))
+ {
+ return Math.min(1d, (this.compassToViewportScale) * viewport.width / this.getScaledIconWidth());
+ }
+ else if (this.resizeBehavior.equals(RESIZE_STRETCH))
+ {
+ return (this.compassToViewportScale) * viewport.width / this.getScaledIconWidth();
+ }
+ else if (this.resizeBehavior.equals(RESIZE_KEEP_FIXED_SIZE))
+ {
+ return 1d;
+ }
+ else
+ {
+ return 1d;
+ }
+ }
+
+ private double getScaledIconWidth()
+ {
+ return this.iconTexture.getWidth() * this.iconScale;
+ }
+
+ private double getScaledIconHeight()
+ {
+ return this.iconTexture.getHeight() * this.iconScale;
+ }
+
+ private Point computeLocation(java.awt.Rectangle viewport, double scale)
+ {
+ double width = this.getScaledIconWidth();
+ double height = this.getScaledIconHeight();
+
+ double scaledWidth = scale * width;
+ double scaledHeight = scale * height;
+
+ double x;
+ double y;
+
+ if (this.locationCenter != null)
+ {
+ x = viewport.getWidth() - scaledWidth / 2 - this.borderWidth;
+ y = viewport.getHeight() - scaledHeight / 2 - this.borderWidth;
+ }
+ else if (this.position.equals(NORTHEAST))
+ {
+ x = viewport.getWidth() - scaledWidth - this.borderWidth;
+ y = viewport.getHeight() - scaledHeight - this.borderWidth;
+ }
+ else if (this.position.equals(SOUTHEAST))
+ {
+ x = viewport.getWidth() - scaledWidth - this.borderWidth;
+ y = 0d + this.borderWidth;
+ }
+ else if (this.position.equals(NORTHWEST))
+ {
+ x = 0d + this.borderWidth;
+ y = viewport.getHeight() - scaledHeight - this.borderWidth;
+ }
+ else if (this.position.equals(SOUTHWEST))
+ {
+ x = 0d + this.borderWidth;
+ y = 0d + this.borderWidth;
+ }
+ else // use North East
+ {
+ x = viewport.getWidth() - scaledWidth / 2 - this.borderWidth;
+ y = viewport.getHeight() - scaledHeight / 2 - this.borderWidth;
+ }
+
+ return new gov.nasa.worldwind.geom.Point(x, y, 0);
+ }
+
+ private void initializeTexture(DrawContext dc)
+ {
+ if (this.iconTexture != null)
+ return;
+
+ try
+ {
+ InputStream iconStream = this.getClass().getResourceAsStream("/" + this.iconFilePath);
+ if (iconStream == null)
+ {
+ File iconFile = new File(this.iconFilePath);
+ if (iconFile.exists())
+ {
+ iconStream = new FileInputStream(iconFile);
+ }
+ }
+
+ this.iconTexture = TextureIO.newTexture(iconStream, true, null);
+ this.iconTexture.bind();
+ }
+ catch (IOException e)
+ {
+ String msg = WorldWind.retrieveErrMsg(
+ "layers.TrackLayer.IOExceptionDuringInitialization");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new WWRuntimeException(msg, e);
+ }
+
+ GL gl = dc.getGL();
+ gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE);
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR_MIPMAP_LINEAR);
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);
+ // Enable texture anisotropy, improves "tilted" compass quality.
+ int[] maxAnisotropy = new int[1];
+ gl.glGetIntegerv(GL.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy, 0);
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy[0]);
+ }
+
+ @Override
+ public String toString()
+ {
+ return WorldWind.retrieveErrMsg("layers.CompassLayer.Name");
+ }
+}
diff --git a/gov/nasa/worldwind/layers/Earth/BMNGSurfaceLayer.java b/gov/nasa/worldwind/layers/Earth/BMNGSurfaceLayer.java
new file mode 100644
index 0000000..10625b4
--- /dev/null
+++ b/gov/nasa/worldwind/layers/Earth/BMNGSurfaceLayer.java
@@ -0,0 +1,49 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers.Earth;
+
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+import gov.nasa.worldwind.layers.*;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: BMNGSurfaceLayer.java 1731 2007-05-05 05:57:22Z tgaskins $
+ */
+public class BMNGSurfaceLayer extends TiledImageLayer
+{
+ public BMNGSurfaceLayer()
+ {
+ super(makeLevels());
+ this.setForceLevelZeroLoads(true);
+ this.setRetainLevelZeroTiles(true);
+ }
+
+ private static LevelSet makeLevels()
+ {
+ AVList params = new AVListImpl();
+
+ params.setValue(Level.TILE_WIDTH, 512);
+ params.setValue(Level.TILE_HEIGHT, 512);
+ params.setValue(Level.CACHE_NAME, "Earth/BMNG/BMNG(Shaded + Bathymetry) Tiled - Version 1.1 - 5.2004");
+ params.setValue(Level.SERVICE, "http://worldwind25.arc.nasa.gov/tile/tile.aspx");
+ params.setValue(Level.DATASET_NAME, "bmng.topo.bathy.200405dds");
+ params.setValue(Level.FORMAT_SUFFIX, ".dds");
+ params.setValue(Level.NUM_LEVELS, 5);
+ params.setValue(Level.NUM_EMPTY_LEVELS, 0);
+ params.setValue(Level.LEVEL_ZERO_TILE_DELTA, new LatLon(Angle.fromDegrees(36d), Angle.fromDegrees(36d)));
+ params.setValue(AVKey.SECTOR, Sector.FULL_SPHERE);
+
+ return new LevelSet(params);
+ }
+
+ @Override
+ public String toString()
+ {
+ return WorldWind.retrieveErrMsg("layers.Earth.BlueMarbleLayer.Name");
+ }
+}
diff --git a/gov/nasa/worldwind/layers/Earth/EarthNASAPlaceNameLayer.java b/gov/nasa/worldwind/layers/Earth/EarthNASAPlaceNameLayer.java
new file mode 100644
index 0000000..f5acb80
--- /dev/null
+++ b/gov/nasa/worldwind/layers/Earth/EarthNASAPlaceNameLayer.java
@@ -0,0 +1,245 @@
+/*
+Copyright (C) 2001, 2006 United States Government as represented by
+the Administrator of the National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers.Earth;
+
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+
+/**
+ * @author Paul Collins
+ * @version $Id: EarthNASAPlaceNameLayer.java 1759 2007-05-07 19:27:49Z dcollins $
+ */
+public class EarthNASAPlaceNameLayer extends gov.nasa.worldwind.layers.PlaceNameLayer
+{
+ private static final double LEVEL_A = 0x1 << 25;
+ private static final double LEVEL_B = 0x1 << 24;
+ private static final double LEVEL_C = 0x1 << 23;
+ private static final double LEVEL_D = 0x1 << 22;
+ // private static final double LEVEL_E = 0x1 << 21;
+ private static final double LEVEL_F = 0x1 << 20;
+ private static final double LEVEL_G = 0x1 << 19;
+ // private static final double LEVEL_H = 0x1 << 18;
+ private static final double LEVEL_I = 0x1 << 17;
+ private static final double LEVEL_J = 0x1 << 16;
+ private static final double LEVEL_K = 0x1 << 15;
+ private static final double LEVEL_L = 0x1 << 14;
+ private static final double LEVEL_M = 0x1 << 13;
+// private static final double LEVEL_N = 0x1 << 12;
+
+ private static final LatLon GRID_1x1 = new LatLon(Angle.fromDegrees(180d), Angle.fromDegrees(360d));
+ // private static final LatLon GRID_2x4 = new LatLon(Angle.fromDegrees(90d), Angle.fromDegrees(90d));
+ private static final LatLon GRID_5x10 = new LatLon(Angle.fromDegrees(36d), Angle.fromDegrees(36d));
+ private static final LatLon GRID_10x20 = new LatLon(Angle.fromDegrees(18d), Angle.fromDegrees(18d));
+ private static final LatLon GRID_20x40 = new LatLon(Angle.fromDegrees(9d), Angle.fromDegrees(9d));
+
+ public EarthNASAPlaceNameLayer()
+ {
+ super(makePlaceNameServiceSet());
+ }
+
+ private static gov.nasa.worldwind.PlaceNameServiceSet makePlaceNameServiceSet()
+ {
+ final String service = "http://worldwind25.arc.nasa.gov/geoservercache/geoservercache.aspx";
+ final String fileCachePath = "Earth/NASA Geoserver Place Names";
+ PlaceNameServiceSet placeNameServiceSet = new PlaceNameServiceSet();
+ PlaceNameService placeNameService;
+
+ // Oceans
+ placeNameService = new PlaceNameService(service, "topp:wpl_oceans", fileCachePath, Sector.FULL_SPHERE, GRID_1x1,
+ java.awt.Font.decode("Arial-BOLDITALIC-12"));
+ placeNameService.setColor(new java.awt.Color(200, 200, 200));
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_A);
+ placeNameServiceSet.addService(placeNameService, false);
+ // Continents
+ placeNameService = new PlaceNameService(service, "topp:wpl_continents", fileCachePath, Sector.FULL_SPHERE,
+ GRID_1x1,
+ java.awt.Font.decode("Arial-BOLD-12"));
+ placeNameService.setColor(new java.awt.Color(255, 255, 240));
+ placeNameService.setMinDisplayDistance(LEVEL_G);
+ placeNameService.setMaxDisplayDistance(LEVEL_A);
+ placeNameServiceSet.addService(placeNameService, false);
+
+ // Water Bodies
+ placeNameService = new PlaceNameService(service, "topp:wpl_waterbodies", fileCachePath, Sector.FULL_SPHERE,
+ GRID_5x10,
+ java.awt.Font.decode("Arial-ITALIC-10"));
+ placeNameService.setColor(java.awt.Color.cyan);
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_B);
+ placeNameServiceSet.addService(placeNameService, false);
+ // Trenches & Ridges
+ placeNameService = new PlaceNameService(service, "topp:wpl_trenchesridges", fileCachePath, Sector.FULL_SPHERE,
+ GRID_5x10,
+ java.awt.Font.decode("Arial-BOLDITALIC-10"));
+ placeNameService.setColor(java.awt.Color.cyan);
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_B);
+ placeNameServiceSet.addService(placeNameService, false);
+ // Deserts & Plains
+ placeNameService = new PlaceNameService(service, "topp:wpl_desertsplains", fileCachePath, Sector.FULL_SPHERE,
+ GRID_5x10,
+ java.awt.Font.decode("Arial-BOLDITALIC-10"));
+ placeNameService.setColor(java.awt.Color.orange);
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_B);
+ placeNameServiceSet.addService(placeNameService, false);
+
+ // Lakes & Rivers
+ placeNameService = new PlaceNameService(service, "topp:wpl_lakesrivers", fileCachePath, Sector.FULL_SPHERE,
+ GRID_10x20,
+ java.awt.Font.decode("Arial-ITALIC-10"));
+ placeNameService.setColor(java.awt.Color.cyan);
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_C);
+ placeNameServiceSet.addService(placeNameService, false);
+ // Mountains & Valleys
+ placeNameService = new PlaceNameService(service, "topp:wpl_mountainsvalleys", fileCachePath, Sector.FULL_SPHERE,
+ GRID_10x20,
+ java.awt.Font.decode("Arial-BOLDITALIC-10"));
+ placeNameService.setColor(java.awt.Color.orange);
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_C);
+ placeNameServiceSet.addService(placeNameService, false);
+
+ // Countries
+ placeNameService = new PlaceNameService(service, "topp:countries", fileCachePath, Sector.FULL_SPHERE, GRID_5x10,
+ java.awt.Font.decode("Arial-BOLD-10"));
+ placeNameService.setColor(java.awt.Color.white);
+ placeNameService.setMinDisplayDistance(LEVEL_G);
+ placeNameService.setMaxDisplayDistance(LEVEL_D);
+ placeNameServiceSet.addService(placeNameService, false);
+ // GeoNet World Capitals
+ placeNameService = new PlaceNameService(service, "topp:wpl_geonet_p_pplc", fileCachePath, Sector.FULL_SPHERE,
+ GRID_10x20,
+ java.awt.Font.decode("Arial-BOLD-10"));
+ placeNameService.setColor(java.awt.Color.yellow);
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_D);
+ placeNameServiceSet.addService(placeNameService, false);
+ // US Cities (Population Over 500k)
+ placeNameService = new PlaceNameService(service, "topp:wpl_uscitiesover500k", fileCachePath, Sector.FULL_SPHERE,
+ GRID_10x20,
+ java.awt.Font.decode("Arial-BOLD-10"));
+ placeNameService.setColor(java.awt.Color.yellow);
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_D);
+ placeNameServiceSet.addService(placeNameService, false);
+
+ // US Cities (Population Over 100k)
+ placeNameService = new PlaceNameService(service, "topp:wpl_uscitiesover100k", fileCachePath, Sector.FULL_SPHERE,
+ GRID_10x20,
+ java.awt.Font.decode("Arial-PLAIN-10"));
+ placeNameService.setColor(java.awt.Color.yellow);
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_F);
+ placeNameServiceSet.addService(placeNameService, false);
+
+ // US Cities (Population Over 50k)
+ placeNameService = new PlaceNameService(service, "topp:wpl_uscitiesover50k", fileCachePath, Sector.FULL_SPHERE,
+ GRID_10x20,
+ java.awt.Font.decode("Arial-PLAIN-10"));
+ placeNameService.setColor(java.awt.Color.yellow);
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_I);
+ placeNameServiceSet.addService(placeNameService, false);
+
+ // US Cities (Population Over 10k)
+ placeNameService = new PlaceNameService(service, "topp:wpl_uscitiesover10k", fileCachePath, Sector.FULL_SPHERE,
+ GRID_10x20,
+ java.awt.Font.decode("Arial-PLAIN-10"));
+ placeNameService.setColor(java.awt.Color.yellow);
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_J);
+ placeNameServiceSet.addService(placeNameService, false);
+
+ // US Cities (Population Over 1k)
+ placeNameService = new PlaceNameService(service, "topp:wpl_uscitiesover1k", fileCachePath, Sector.FULL_SPHERE,
+ GRID_20x40,
+ java.awt.Font.decode("Arial-PLAIN-10"));
+ placeNameService.setColor(java.awt.Color.yellow);
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_K);
+ placeNameServiceSet.addService(placeNameService, false);
+
+ // US Cities (Population Over 0)
+ placeNameService = new PlaceNameService(service, "topp:wpl_uscitiesover0", fileCachePath, Sector.FULL_SPHERE,
+ GRID_20x40,
+ java.awt.Font.decode("Arial-PLAIN-10"));
+ placeNameService.setColor(java.awt.Color.yellow);
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_L);
+ placeNameServiceSet.addService(placeNameService, false);
+
+ // US Cities (No Population)
+ placeNameService = new PlaceNameService(service, "topp:wpl_uscities0", fileCachePath, Sector.FULL_SPHERE,
+ GRID_20x40,
+ java.awt.Font.decode("Arial-PLAIN-10"));
+ placeNameService.setColor(java.awt.Color.yellow);
+ placeNameService.setMinDisplayDistance(0d);
+ placeNameService.setMaxDisplayDistance(LEVEL_M);
+ placeNameServiceSet.addService(placeNameService, false);
+
+// // US Anthropogenic Features
+// placeNameService = new PlaceNameService(service, "topp:wpl_us_anthropogenic", fileCachePath, Sector.FULL_SPHERE, GRID_20x40,
+// java.awt.Font.decode("Arial-PLAIN-10"));
+// placeNameService.setColor(java.awt.Color.yellow);
+// placeNameService.setMinDisplayDistance(0d);
+// placeNameService.setMaxDisplayDistance(LEVEL_N);
+// placeNameServiceSet.addService(placeNameService, false);
+// // US Water Features
+// placeNameService = new PlaceNameService(service, "topp:wpl_us_water", fileCachePath, Sector.FULL_SPHERE, GRID_20x40,
+// java.awt.Font.decode("Arial-PLAIN-10"));
+// placeNameService.setColor(java.awt.Color.cyan);
+// placeNameService.setMinDisplayDistance(0d);
+// placeNameService.setMaxDisplayDistance(LEVEL_N);
+// placeNameServiceSet.addService(placeNameService, false);
+// // US Terrain Features
+// placeNameService = new PlaceNameService(service, "topp:wpl_us_terrain", fileCachePath, Sector.FULL_SPHERE, GRID_20x40,
+// java.awt.Font.decode("Arial-PLAIN-10"));
+// placeNameService.setColor(java.awt.Color.orange);
+// placeNameService.setMinDisplayDistance(0d);
+// placeNameService.setMaxDisplayDistance(LEVEL_N);
+// placeNameServiceSet.addService(placeNameService, false);
+
+// // GeoNET Administrative 1st Order
+// placeNameService = new PlaceNameService(service, "topp:wpl_geonet_a_adm1", fileCachePath, Sector.FULL_SPHERE, GRID_20x40,
+// java.awt.Font.decode("Arial-BOLD-10"));
+// placeNameService.setColor(java.awt.Color.yellow);
+// placeNameService.setMinDisplayDistance(0d);
+// placeNameService.setMaxDisplayDistance(LEVEL_N);
+// placeNameServiceSet.addService(placeNameService, false);
+// // GeoNET Administrative 2nd Order
+// placeNameService = new PlaceNameService(service, "topp:wpl_geonet_a_adm2", fileCachePath, Sector.FULL_SPHERE, GRID_20x40,
+// java.awt.Font.decode("Arial-BOLD-10"));
+// placeNameService.setColor(java.awt.Color.yellow);
+// placeNameService.setMinDisplayDistance(0d);
+// placeNameService.setMaxDisplayDistance(LEVEL_N);
+// placeNameServiceSet.addService(placeNameService, false);
+// // GeoNET Populated Place Administrative
+// placeNameService = new PlaceNameService(service, "topp:wpl_geonet_p_ppla", fileCachePath, Sector.FULL_SPHERE, GRID_20x40,
+// java.awt.Font.decode("Arial-BOLD-10"));
+// placeNameService.setColor(java.awt.Color.pink);
+// placeNameService.setMinDisplayDistance(0d);
+// placeNameService.setMaxDisplayDistance(LEVEL_N);
+// placeNameServiceSet.addService(placeNameService, false);
+// // GeoNET Populated Place
+// placeNameService = new PlaceNameService(service, "topp:wpl_geonet_p_ppl", fileCachePath, Sector.FULL_SPHERE, GRID_20x40,
+// java.awt.Font.decode("Arial-PLAIN-10"));
+// placeNameService.setColor(java.awt.Color.pink);
+// placeNameService.setMinDisplayDistance(0d);
+// placeNameService.setMaxDisplayDistance(LEVEL_N);
+// placeNameServiceSet.addService(placeNameService, false);
+
+ return placeNameServiceSet;
+ }
+
+ @Override
+ public String toString()
+ {
+ return gov.nasa.worldwind.WorldWind.retrieveErrMsg("layers.Earth.PlaceName.Name");
+ }
+}
diff --git a/gov/nasa/worldwind/layers/Earth/LandsatI3.java b/gov/nasa/worldwind/layers/Earth/LandsatI3.java
new file mode 100644
index 0000000..504bd50
--- /dev/null
+++ b/gov/nasa/worldwind/layers/Earth/LandsatI3.java
@@ -0,0 +1,51 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers.Earth;
+
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+import gov.nasa.worldwind.layers.*;
+
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: LandsatI3.java 1830 2007-05-11 18:30:24Z dcollins $
+ */
+public class LandsatI3 extends TiledImageLayer
+{
+ public LandsatI3()
+ {
+ super(makeLevels());
+ this.setUseTransparentTextures(true);
+ }
+
+ private static LevelSet makeLevels()
+ {
+ AVList params = new AVListImpl();
+
+ params.setValue(Level.TILE_WIDTH, 512);
+ params.setValue(Level.TILE_HEIGHT, 512);
+ params.setValue(Level.CACHE_NAME, "Earth/NASA LandSat I3");
+ params.setValue(Level.SERVICE, "http://worldwind25.arc.nasa.gov/lstile/lstile.aspx");
+ params.setValue(Level.DATASET_NAME, "esat_worlddds");
+ params.setValue(Level.FORMAT_SUFFIX, ".dds");
+ params.setValue(Level.NUM_LEVELS, 10);
+ params.setValue(Level.NUM_EMPTY_LEVELS, 4);
+ params.setValue(Level.LEVEL_ZERO_TILE_DELTA, new LatLon(Angle.fromDegrees(36d), Angle.fromDegrees(36d)));
+ params.setValue(AVKey.SECTOR, Sector.FULL_SPHERE);
+ params.setValue(Level.EXPIRY_TIME, new GregorianCalendar(2007, 2, 22).getTimeInMillis());
+
+ return new LevelSet(params);
+ }
+
+ @Override
+ public String toString()
+ {
+ return WorldWind.retrieveErrMsg("layers.Earth.LandsatI3Layer.Name");
+ }
+}
diff --git a/gov/nasa/worldwind/layers/Earth/PoliticalBoundariesLayer.java b/gov/nasa/worldwind/layers/Earth/PoliticalBoundariesLayer.java
new file mode 100644
index 0000000..5299367
--- /dev/null
+++ b/gov/nasa/worldwind/layers/Earth/PoliticalBoundariesLayer.java
@@ -0,0 +1,86 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers.Earth;
+
+import gov.nasa.worldwind.layers.*;
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+
+import java.net.*;
+
+/**
+ * @author tag
+ * @version $Id: PoliticalBoundariesLayer.java 1731 2007-05-05 05:57:22Z tgaskins $
+ */
+public class PoliticalBoundariesLayer extends TiledImageLayer
+{
+ public PoliticalBoundariesLayer()
+ {
+ super(makeLevels(new URLBuilder()));
+ this.setUseTransparentTextures(true);
+ }
+
+ private static LevelSet makeLevels(URLBuilder urlBuilder)
+ {
+ AVList params = new AVListImpl();
+
+ params.setValue(Level.TILE_WIDTH, 512);
+ params.setValue(Level.TILE_HEIGHT, 512);
+ params.setValue(Level.CACHE_NAME, "Earth/PoliticalBoundaries");
+ params.setValue(Level.SERVICE, "http://worldwind21.arc.nasa.gov/geoserver/wms");
+ params.setValue(Level.DATASET_NAME, "topp:cia");
+ params.setValue(Level.FORMAT_SUFFIX, ".png");
+ params.setValue(Level.NUM_LEVELS, 13);
+ params.setValue(Level.NUM_EMPTY_LEVELS, 0);
+ params.setValue(Level.LEVEL_ZERO_TILE_DELTA, new LatLon(Angle.fromDegrees(36d), Angle.fromDegrees(36d)));
+ params.setValue(AVKey.SECTOR, Sector.FULL_SPHERE);
+ params.setValue(Level.TILE_URL_BUILDER, urlBuilder);
+
+ return new LevelSet(params);
+ }
+
+ private static class URLBuilder implements Level.TileURLBuilder
+ {
+ public URL getURL(Tile tile) throws MalformedURLException
+ {
+ StringBuffer sb = new StringBuffer(tile.getLevel().getService());
+ if (sb.lastIndexOf("?") != sb.length() - 1)
+ sb.append("?");
+ sb.append("request=GetMap");
+ sb.append("&layers=");
+ sb.append(tile.getLevel().getDataset());
+ sb.append("&srs=EPSG:4326");
+ sb.append("&width=");
+ sb.append(tile.getLevel().getTileWidth());
+ sb.append("&height=");
+ sb.append(tile.getLevel().getTileHeight());
+
+ Sector s = tile.getSector();
+ sb.append("&bbox=");
+ sb.append(s.getMinLongitude().getDegrees());
+ sb.append(",");
+ sb.append(s.getMinLatitude().getDegrees());
+ sb.append(",");
+ sb.append(s.getMaxLongitude().getDegrees());
+ sb.append(",");
+ sb.append(s.getMaxLatitude().getDegrees());
+
+ sb.append("&format=image/png");
+ sb.append("&styles=countryboundaries");
+// sb.append("&bgcolor=0x000000");
+ sb.append("&transparent=true");
+
+ return new java.net.URL(sb.toString());
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return gov.nasa.worldwind.WorldWind.retrieveErrMsg("layers.Earth.PoliticalBoundaries.Name");
+ }
+}
diff --git a/gov/nasa/worldwind/layers/Earth/USGSDigitalOrtho.java b/gov/nasa/worldwind/layers/Earth/USGSDigitalOrtho.java
new file mode 100644
index 0000000..168c291
--- /dev/null
+++ b/gov/nasa/worldwind/layers/Earth/USGSDigitalOrtho.java
@@ -0,0 +1,54 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers.Earth;
+
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.layers.*;
+import gov.nasa.worldwind.geom.*;
+
+import java.util.*;
+
+/**
+ * @author tag
+ * @version $Id: USGSDigitalOrtho.java 1735 2007-05-05 09:50:26Z tgaskins $
+ */
+public class USGSDigitalOrtho extends TiledImageLayer
+{
+ public USGSDigitalOrtho()
+ {
+ super(makeLevels());
+ this.setMaxActiveAltitude(7e3d);
+ }
+
+ private static LevelSet makeLevels()
+ {
+ AVList params = new AVListImpl();
+
+ params.setValue(Level.TILE_WIDTH, 512);
+ params.setValue(Level.TILE_HEIGHT, 512);
+ params.setValue(Level.CACHE_NAME, "Earth/USGS Digital Ortho");
+ params.setValue(Level.SERVICE, "http://worldwind25.arc.nasa.gov/tile/tile.aspx");
+ params.setValue(Level.DATASET_NAME, "101dds");
+ params.setValue(Level.FORMAT_SUFFIX, ".dds");
+ params.setValue(Level.NUM_LEVELS, 10);
+ params.setValue(Level.NUM_EMPTY_LEVELS, 2);
+
+ Angle levelZeroDelta = Angle.fromDegrees(3.2);
+ params.setValue(Level.LEVEL_ZERO_TILE_DELTA, new LatLon(levelZeroDelta, levelZeroDelta));
+
+ params.setValue(AVKey.SECTOR, new Sector(Angle.fromDegrees(17.84), Angle.fromDegrees(71.55),
+ Angle.fromDegrees(-168.67), Angle.fromDegrees(-65.15)));
+
+ return new LevelSet(params);
+ }
+
+ @Override
+ public String toString()
+ {
+ return WorldWind.retrieveErrMsg("layers.Earth.USGSDigitalOrtho.Name");
+ }
+}
diff --git a/gov/nasa/worldwind/layers/Earth/USGSUrbanAreaOrtho.java b/gov/nasa/worldwind/layers/Earth/USGSUrbanAreaOrtho.java
new file mode 100644
index 0000000..7c8d0c4
--- /dev/null
+++ b/gov/nasa/worldwind/layers/Earth/USGSUrbanAreaOrtho.java
@@ -0,0 +1,52 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers.Earth;
+
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.layers.*;
+import gov.nasa.worldwind.geom.*;
+
+/**
+ * @author tag
+ * @version $Id: USGSUrbanAreaOrtho.java 1735 2007-05-05 09:50:26Z tgaskins $
+ */
+public class USGSUrbanAreaOrtho extends TiledImageLayer
+{
+ public USGSUrbanAreaOrtho()
+ {
+ super(makeLevels());
+ this.setMaxActiveAltitude(35e3d);
+ }
+
+ private static LevelSet makeLevels()
+ {
+ AVList params = new AVListImpl();
+
+ params.setValue(Level.TILE_WIDTH, 512);
+ params.setValue(Level.TILE_HEIGHT, 512);
+ params.setValue(Level.CACHE_NAME, "Earth/USGS Urban Area Ortho");
+ params.setValue(Level.SERVICE, "http://worldwind25.arc.nasa.gov/tile/tile.aspx");
+ params.setValue(Level.DATASET_NAME, "104dds");
+ params.setValue(Level.FORMAT_SUFFIX, ".dds");
+ params.setValue(Level.NUM_LEVELS, 12);
+ params.setValue(Level.NUM_EMPTY_LEVELS, 2);
+
+ Angle levelZeroDelta = Angle.fromDegrees(3.2);
+ params.setValue(Level.LEVEL_ZERO_TILE_DELTA, new LatLon(levelZeroDelta, levelZeroDelta));
+
+ params.setValue(AVKey.SECTOR, new Sector(Angle.fromDegrees(17.84), Angle.fromDegrees(71.55),
+ Angle.fromDegrees(-168.67), Angle.fromDegrees(-65.15)));
+
+ return new LevelSet(params);
+ }
+
+ @Override
+ public String toString()
+ {
+ return WorldWind.retrieveErrMsg("layers.Earth.USGSUrbanAreaOrtho.Name");
+ }
+}
diff --git a/gov/nasa/worldwind/layers/IconLayer.java b/gov/nasa/worldwind/layers/IconLayer.java
new file mode 100644
index 0000000..87d2e56
--- /dev/null
+++ b/gov/nasa/worldwind/layers/IconLayer.java
@@ -0,0 +1,105 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers;
+
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+
+/**
+ * @author tag
+ * @version $Id$
+ */
+public class IconLayer extends AbstractLayer
+{
+ private final java.util.Collection icons = new java.util.ArrayList();
+ private java.util.Iterator iconIterator;
+ private IconRenderer iconRenderer = new IconRenderer();
+ private Pedestal pedestal;
+
+ public IconLayer()
+ {
+ }
+
+ public void setIcons(java.util.Iterator iconIterator)
+ {
+ this.iconIterator = iconIterator;
+ }
+
+ public void addIcon(WWIcon icon)
+ {
+ if (icon == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.Icon");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.icons.add(icon);
+ }
+
+ public void removeIcon(WWIcon icon)
+ {
+ if (icon == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.Icon");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.icons.remove(icon);
+ }
+
+ public java.util.Collection getIcons()
+ {
+ return this.icons;
+ }
+
+ public void dispose()
+ {
+ this.iconRenderer.dispose();
+ }
+
+ public Pedestal getPedestal()
+ {
+ return pedestal;
+ }
+
+ public void setPedestal(Pedestal pedestal)
+ {
+ this.pedestal = pedestal;
+ }
+
+ @Override
+ protected void doPick(DrawContext dc, java.awt.Point pickPoint)
+ {
+ if (this.iconIterator == null)
+ this.iconIterator = this.icons.iterator();
+
+ this.iconRenderer.setPedestal(this.pedestal);
+ this.iconRenderer.pick(dc, this.iconIterator, pickPoint, this);
+
+ this.iconIterator = null;
+ }
+
+ @Override
+ protected void doRender(DrawContext dc)
+ {
+ if (this.iconIterator == null)
+ this.iconIterator = this.icons.iterator();
+
+ this.iconRenderer.setPedestal(this.pedestal);
+ this.iconRenderer.render(dc, this.iconIterator);
+
+ this.iconIterator = null;
+ }
+
+ @Override
+ public String toString()
+ {
+ return WorldWind.retrieveErrMsg("layers.IconLayer.Name");
+ }
+}
diff --git a/gov/nasa/worldwind/layers/PlaceNameLayer.java b/gov/nasa/worldwind/layers/PlaceNameLayer.java
new file mode 100644
index 0000000..f5fe0ed
--- /dev/null
+++ b/gov/nasa/worldwind/layers/PlaceNameLayer.java
@@ -0,0 +1,872 @@
+/*
+Copyright (C) 2001, 2006 United States Government as represented by
+the Administrator of the National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers;
+
+import com.sun.opengl.util.*;
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+import gov.nasa.worldwind.geom.Point;
+
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.*;
+import java.util.logging.Level;
+
+/**
+ * @author Paul Collins
+ * @version $Id: PlaceNameLayer.java 1788 2007-05-08 17:12:05Z dcollins $
+ */
+public class PlaceNameLayer extends AbstractLayer
+{
+ private final PlaceNameServiceSet placeNameServiceSet;
+ private final List tiles = new ArrayList();
+
+ /**
+ * @param placeNameServiceSet the set of PlaceNameService objects that PlaceNameLayer will render.
+ * @throws IllegalArgumentException if placeNameServiceSet
is null
+ */
+ public PlaceNameLayer(PlaceNameServiceSet placeNameServiceSet)
+ {
+ if (placeNameServiceSet == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.PlaceNameServiceSetIsNull");
+ WorldWind.logger().log(Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.placeNameServiceSet = placeNameServiceSet.deepCopy();
+ for (int i = 0; i < this.placeNameServiceSet.getServiceCount(); i++)
+ {
+ tiles.add(i, buildTiles(this.placeNameServiceSet.getService(i)));
+ }
+ }
+
+ public final PlaceNameServiceSet getPlaceNameServiceSet()
+ {
+ return this.placeNameServiceSet;
+ }
+
+ // ============== Tile Assembly ======================= //
+ // ============== Tile Assembly ======================= //
+ // ============== Tile Assembly ======================= //
+
+ private static class Tile
+ {
+ final PlaceNameService placeNameService;
+ final Sector sector;
+ final int row;
+ final int column;
+ final int hash;
+ // Computed data.
+ String fileCachePath = null;
+ Extent extent = null;
+ double extentVerticalExaggeration = Double.MIN_VALUE;
+
+ static int computeRow(Angle delta, Angle latitude)
+ {
+ if (delta == null || latitude == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ WorldWind.logger().log(Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return (int) ((latitude.getDegrees() + 90d) / delta.getDegrees());
+ }
+
+ static int computeColumn(Angle delta, Angle longitude)
+ {
+ if (delta == null || longitude == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ WorldWind.logger().log(Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return (int) ((longitude.getDegrees() + 180d) / delta.getDegrees());
+ }
+
+ static Angle computeRowLatitude(int row, Angle delta)
+ {
+ if (delta == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ WorldWind.logger().log(Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return Angle.fromDegrees(-90d + delta.getDegrees() * row);
+ }
+
+ static Angle computeColumnLongitude(int column, Angle delta)
+ {
+ if (delta == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ WorldWind.logger().log(Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return Angle.fromDegrees(-180 + delta.getDegrees() * column);
+ }
+
+ Tile(PlaceNameService placeNameService, Sector sector, int row, int column)
+ {
+ this.placeNameService = placeNameService;
+ this.sector = sector;
+ this.row = row;
+ this.column = column;
+ this.hash = this.computeHash();
+ }
+
+ int computeHash()
+ {
+ return this.getFileCachePath() != null ? this.getFileCachePath().hashCode() : 0;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || this.getClass() != o.getClass())
+ return false;
+
+ final Tile other = (Tile) o;
+
+ return this.getFileCachePath() != null ? !this.getFileCachePath().equals(other.getFileCachePath()) :
+ other.getFileCachePath() != null;
+ }
+
+ Extent getExtent(DrawContext dc)
+ {
+ if (dc == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (this.extent == null || this.extentVerticalExaggeration != dc.getVerticalExaggeration())
+ {
+ this.extentVerticalExaggeration = dc.getVerticalExaggeration();
+ this.extent = Sector.computeBoundingCylinder(dc.getGlobe(), this.extentVerticalExaggeration,
+ this.sector);
+ }
+
+ return extent;
+ }
+
+ String getFileCachePath()
+ {
+ if (this.fileCachePath == null)
+ this.fileCachePath = this.placeNameService.createFileCachePathFromTile(this.row, this.column);
+
+ return this.fileCachePath;
+ }
+
+ PlaceNameService getPlaceNameService()
+ {
+ return placeNameService;
+ }
+
+ java.net.URL getRequestURL() throws java.net.MalformedURLException
+ {
+ return this.placeNameService.createServiceURLFromSector(this.sector);
+ }
+
+ Sector getSector()
+ {
+ return sector;
+ }
+
+ public int hashCode()
+ {
+ return this.hash;
+ }
+ }
+
+ private Tile[] buildTiles(PlaceNameService placeNameService)
+ {
+ final Sector sector = placeNameService.getSector();
+ final Angle dLat = placeNameService.getTileDelta().getLatitude();
+ final Angle dLon = placeNameService.getTileDelta().getLongitude();
+
+ // Determine the row and column offset from the global tiling origin for the southwest tile corner
+ int firstRow = Tile.computeRow(dLat, sector.getMinLatitude());
+ int firstCol = Tile.computeColumn(dLon, sector.getMinLongitude());
+ int lastRow = Tile.computeRow(dLat, sector.getMaxLatitude().subtract(dLat));
+ int lastCol = Tile.computeColumn(dLon, sector.getMaxLongitude().subtract(dLon));
+
+ int nLatTiles = lastRow - firstRow + 1;
+ int nLonTiles = lastCol - firstCol + 1;
+
+ Tile[] tiles = new Tile[nLatTiles * nLonTiles];
+
+ Angle p1 = Tile.computeRowLatitude(firstRow, dLat);
+ for (int row = firstRow; row <= lastRow; row++)
+ {
+ Angle p2;
+ p2 = p1.add(dLat);
+
+ Angle t1 = Tile.computeColumnLongitude(firstCol, dLon);
+ for (int col = firstCol; col <= lastCol; col++)
+ {
+ Angle t2;
+ t2 = t1.add(dLon);
+
+ tiles[col + row * nLonTiles] = new Tile(placeNameService, new Sector(p1, p2, t1, t2), row, col);
+ t1 = t2;
+ }
+ p1 = p2;
+ }
+
+ return tiles;
+ }
+
+ // ============== Place Name Data Structures ======================= //
+ // ============== Place Name Data Structures ======================= //
+ // ============== Place Name Data Structures ======================= //
+
+ private static class PlaceNameChunk implements Cacheable
+ {
+ final PlaceNameService placeNameService;
+ final StringBuilder textArray;
+ final int[] textIndexArray;
+ final double[] latlonArray;
+ final int numEntries;
+ final long estimatedMemorySize;
+
+ PlaceNameChunk(PlaceNameService service, StringBuilder text, int[] textIndices,
+ double[] positions, int numEntries)
+ {
+ this.placeNameService = service;
+ this.textArray = text;
+ this.textIndexArray = textIndices;
+ this.latlonArray = positions;
+ this.numEntries = numEntries;
+ this.estimatedMemorySize = this.computeEstimatedMemorySize();
+ }
+
+ long computeEstimatedMemorySize()
+ {
+ long result = 0;
+ result += BufferUtil.SIZEOF_SHORT * textArray.capacity();
+ result += BufferUtil.SIZEOF_INT * textIndexArray.length;
+ result += BufferUtil.SIZEOF_DOUBLE * latlonArray.length;
+ return result;
+ }
+
+ Position getPosition(int index)
+ {
+ int latlonIndex = 2 * index;
+ return Position.fromDegrees(latlonArray[latlonIndex], latlonArray[latlonIndex + 1], 0);
+ }
+
+ PlaceNameService getPlaceNameService()
+ {
+ return this.placeNameService;
+ }
+
+ String getText(int index)
+ {
+ int beginIndex = textIndexArray[index];
+ int endIndex = (index + 1 < numEntries) ? textIndexArray[index + 1] : textArray.length();
+ return this.textArray.substring(beginIndex, endIndex);
+ }
+
+ public long getSizeInBytes()
+ {
+ return this.estimatedMemorySize;
+ }
+
+ Iterator createRenderIterator(final DrawContext dc)
+ {
+ return new Iterator()
+ {
+ PlaceNameImpl placeNameProxy = new PlaceNameImpl();
+ int index = -1;
+
+ public boolean hasNext()
+ {
+ return index < (PlaceNameChunk.this.numEntries - 1);
+ }
+
+ public PlaceName next()
+ {
+ if (!hasNext())
+ throw new NoSuchElementException();
+ this.updateProxy(placeNameProxy, ++index);
+ return placeNameProxy;
+ }
+
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ void updateProxy(PlaceNameImpl proxy, int index)
+ {
+ proxy.text = getText(index);
+ proxy.position = getPosition(index);
+ proxy.font = placeNameService.getFont();
+ proxy.color = placeNameService.getColor();
+ proxy.visible = isNameVisible(dc, placeNameService, proxy.position);
+ }
+ };
+ }
+ }
+
+ private static class PlaceNameImpl implements PlaceName
+ {
+ String text;
+ Position position;
+ Font font;
+ Color color;
+ boolean visible;
+
+ PlaceNameImpl()
+ {
+ }
+
+ public String getText()
+ {
+ return this.text;
+ }
+
+ public void setText(String text)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public Position getPosition()
+ {
+ return this.position;
+ }
+
+ public void setPosition(Position position)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public Font getFont()
+ {
+ return this.font;
+ }
+
+ public void setFont(Font font)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public Color getColor()
+ {
+ return this.color;
+ }
+
+ public void setColor(Color color)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isVisible()
+ {
+ return this.visible;
+ }
+
+ public void setVisible(boolean visible)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public WWIcon getIcon()
+ {
+ return null;
+ }
+
+ public void setIcon(WWIcon icon)
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ // ============== Rendering ======================= //
+ // ============== Rendering ======================= //
+ // ============== Rendering ======================= //
+
+ private final PlaceNameRenderer placeNameRenderer = new PlaceNameRenderer();
+
+ @Override
+ protected void doRender(DrawContext dc)
+ {
+ final boolean enableDepthTest = dc.getView().getAltitude()
+ < (dc.getVerticalExaggeration() * dc.getGlobe().getMaxElevation());
+
+ int serviceCount = this.placeNameServiceSet.getServiceCount();
+ for (int i = 0; i < serviceCount; i++)
+ {
+ PlaceNameService placeNameService = this.placeNameServiceSet.getService(i);
+ if (!isServiceVisible(dc, placeNameService))
+ continue;
+
+ double minDist = placeNameService.getMinDisplayDistance();
+ double maxDist = placeNameService.getMaxDisplayDistance();
+ double minDistSquared = minDist * minDist;
+ double maxDistSquared = maxDist * maxDist;
+
+ Tile[] tiles = this.tiles.get(i);
+ for (Tile tile : tiles)
+ {
+ try
+ {
+ drawOrRequestTile(dc, tile, minDistSquared, maxDistSquared, enableDepthTest);
+ }
+ catch (Exception e)
+ {
+ String message = WorldWind.retrieveErrMsg("layers.PlaceNameLayer.ExceptionRenderingTile");
+ WorldWind.logger().log(Level.FINE, message, e);
+ }
+ }
+ }
+
+ this.sendRequests();
+ }
+
+ private void drawOrRequestTile(DrawContext dc, Tile tile, double minDisplayDistanceSquared,
+ double maxDisplayDistanceSquared, boolean enableDepthTest)
+ {
+ if (!isTileVisible(dc, tile, minDisplayDistanceSquared, maxDisplayDistanceSquared))
+ return;
+
+ Object cacheObj = WorldWind.memoryCache().getObject(tile);
+ if (cacheObj == null)
+ {
+ this.requestTile(this.readQueue, tile);
+ return;
+ }
+
+ if (!(cacheObj instanceof PlaceNameChunk))
+ return;
+
+ PlaceNameChunk placeNameChunk = (PlaceNameChunk) cacheObj;
+ Iterator renderIter = placeNameChunk.createRenderIterator(dc);
+ placeNameRenderer.render(dc, renderIter, enableDepthTest);
+ }
+
+ private static boolean isServiceVisible(DrawContext dc, PlaceNameService placeNameService)
+ {
+ if (!placeNameService.isEnabled())
+ return false;
+ //noinspection SimplifiableIfStatement
+ if (dc.getVisibleSector() != null && !placeNameService.getSector().intersects(dc.getVisibleSector()))
+ return false;
+
+ return placeNameService.getExtent(dc).intersects(dc.getView().getFrustumInModelCoordinates());
+ }
+
+ private static boolean isTileVisible(DrawContext dc, Tile tile, double minDistanceSquared,
+ double maxDistanceSquared)
+ {
+ if (!tile.getSector().intersects(dc.getVisibleSector()))
+ return false;
+
+ Position viewPosition = dc.getView().getPosition();
+ Angle lat = clampAngle(viewPosition.getLatitude(), tile.getSector().getMinLatitude(),
+ tile.getSector().getMaxLatitude());
+ Angle lon = clampAngle(viewPosition.getLongitude(), tile.getSector().getMinLongitude(),
+ tile.getSector().getMaxLongitude());
+ Point p = dc.getGlobe().computePointFromPosition(lat, lon, 0d);
+ double distSquared = dc.getView().getEyePoint().distanceToSquared(p);
+ //noinspection RedundantIfStatement
+ if (minDistanceSquared > distSquared || maxDistanceSquared < distSquared)
+ return false;
+
+ return true;
+ }
+
+ private static boolean isNameVisible(DrawContext dc, PlaceNameService service, Position namePosition)
+ {
+ double elevation = dc.getVerticalExaggeration() * namePosition.getElevation();
+ Point namePoint = dc.getGlobe().computePointFromPosition(namePosition.getLatitude(),
+ namePosition.getLongitude(), elevation);
+ Point eye = dc.getView().getEyePoint();
+
+ double dist = eye.distanceTo(namePoint);
+ return dist >= service.getMinDisplayDistance() && dist <= service.getMaxDisplayDistance();
+ }
+
+ private static Angle clampAngle(Angle a, Angle min, Angle max)
+ {
+ return a.compareTo(min) < 0 ? min : (a.compareTo(max) > 0 ? max : a);
+ }
+
+ // ============== Image Reading and Downloading ======================= //
+ // ============== Image Reading and Downloading ======================= //
+ // ============== Image Reading and Downloading ======================= //
+
+ private static final int MAX_REQUESTS = 64;
+ private final Queue downloadQueue = new LinkedBlockingQueue(MAX_REQUESTS);
+ private final Queue readQueue = new LinkedBlockingQueue(MAX_REQUESTS);
+
+ private static class RequestTask implements Runnable
+ {
+ final PlaceNameLayer layer;
+ final Tile tile;
+
+ RequestTask(PlaceNameLayer layer, Tile tile)
+ {
+ this.layer = layer;
+ this.tile = tile;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || this.getClass() != o.getClass())
+ return false;
+
+ final RequestTask other = (RequestTask) o;
+
+ // Don't include layer in comparison so that requests are shared among layers
+ return !(this.tile != null ? !this.tile.equals(other.tile) : other.tile != null);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return (this.tile != null ? this.tile.hashCode() : 0);
+ }
+
+ public void run()
+ {
+ synchronized (tile)
+ {
+ if (WorldWind.memoryCache().getObject(this.tile) != null)
+ return;
+
+ final java.net.URL tileURL = WorldWind.dataFileCache().findFile(tile.getFileCachePath(), false);
+ if (tileURL != null)
+ {
+ if (this.layer.loadTile(this.tile, tileURL))
+ {
+ tile.getPlaceNameService().unmarkResourceAbsent(tile.getPlaceNameService().getTileNumber(
+ tile.row,
+ tile.column));
+ this.layer.firePropertyChange(AVKey.LAYER, null, this);
+ }
+ else
+ {
+ // Assume that something's wrong with the file and delete it.
+ WorldWind.dataFileCache().removeFile(tileURL);
+ tile.getPlaceNameService().markResourceAbsent(tile.getPlaceNameService().getTileNumber(tile.row,
+ tile.column));
+ String message = WorldWind.retrieveErrMsg("generic.DeletedCorruptDataFile") + tileURL;
+ WorldWind.logger().log(Level.FINE, message);
+ }
+ return;
+ }
+ }
+
+ this.layer.requestTile(this.layer.downloadQueue, this.tile);
+ }
+
+ public String toString()
+ {
+ return this.tile.toString();
+ }
+ }
+
+ private static class DownloadPostProcessor implements RetrievalPostProcessor
+ {
+ final PlaceNameLayer layer;
+ final Tile tile;
+
+ private DownloadPostProcessor(PlaceNameLayer layer, Tile tile)
+ {
+ this.layer = layer;
+ this.tile = tile;
+ }
+
+ public java.nio.ByteBuffer run(Retriever retriever)
+ {
+ if (retriever == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.RetrieverIsNull");
+ WorldWind.logger().log(Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL))
+ return null;
+
+ if (!(retriever instanceof URLRetriever))
+ return null;
+
+ try
+ {
+ if (retriever instanceof HTTPRetriever)
+ {
+ HTTPRetriever httpRetriever = (HTTPRetriever) retriever;
+ if (httpRetriever.getResponseCode() == java.net.HttpURLConnection.HTTP_NO_CONTENT)
+ {
+ // Mark tile as missing to avoid further attempts
+ tile.getPlaceNameService().markResourceAbsent(tile.getPlaceNameService().getTileNumber(tile.row,
+ tile.column));
+ return null;
+ }
+ }
+
+ URLRetriever urlRetriever = (URLRetriever) retriever;
+ java.nio.ByteBuffer buffer = urlRetriever.getBuffer();
+
+ synchronized (tile)
+ {
+ final java.io.File cacheFile = WorldWind.dataFileCache().newFile(this.tile.getFileCachePath());
+ if (cacheFile == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.CantCreateCacheFile")
+ + this.tile.getFileCachePath();
+ WorldWind.logger().log(Level.FINE, msg);
+ return null;
+ }
+
+ if (cacheFile.exists())
+ return buffer; // info is already here; don't need to do anything
+
+ if (buffer != null)
+ {
+ WWIO.saveBuffer(buffer, cacheFile);
+ this.layer.firePropertyChange(AVKey.LAYER, null, this);
+ return buffer;
+ }
+ }
+ }
+ catch (java.io.IOException e)
+ {
+ String message = WorldWind.retrieveErrMsg("layers.PlaceNameLayer.ExceptionSavingRetrievedFile");
+ WorldWind.logger().log(Level.FINE, message + this.tile.getFileCachePath(), e);
+ }
+
+ return null;
+ }
+ }
+
+ private static class GMLPlaceNameSAXHandler extends org.xml.sax.helpers.DefaultHandler
+ {
+ static final String GML_FEATURE_MEMBER = "gml:featureMember";
+ static final String TOPP_FULL_NAME_ND = "topp:full_name_nd";
+ static final String TOPP_LATITUDE = "topp:latitude";
+ static final String TOPP_LONGITUDE = "topp:longitude";
+ final LinkedList qNameStack = new LinkedList();
+ boolean inBeginEndPair = false;
+ StringBuilder latBuffer = new StringBuilder();
+ StringBuilder lonBuffer = new StringBuilder();
+
+ StringBuilder textArray = new StringBuilder();
+ int[] textIndexArray = new int[16];
+ double[] latlonArray = new double[16];
+ int numEntries = 0;
+
+ GMLPlaceNameSAXHandler()
+ {
+ }
+
+ PlaceNameChunk createPlaceNameChunk(PlaceNameService service)
+ {
+ return new PlaceNameChunk(service, this.textArray, this.textIndexArray, this.latlonArray, this.numEntries);
+ }
+
+ void beginEntry()
+ {
+ int textIndex = this.textArray.length();
+ this.textIndexArray = append(this.textIndexArray, this.numEntries, textIndex);
+ this.inBeginEndPair = true;
+ }
+
+ void endEntry()
+ {
+ double lat = this.parseDouble(this.latBuffer);
+ double lon = this.parseDouble(this.lonBuffer);
+ int numLatLon = 2 * this.numEntries;
+ this.latlonArray = this.append(this.latlonArray, numLatLon, lat);
+ numLatLon++;
+ this.latlonArray = this.append(this.latlonArray, numLatLon, lon);
+
+ this.latBuffer.delete(0, this.latBuffer.length());
+ this.lonBuffer.delete(0, this.lonBuffer.length());
+ this.inBeginEndPair = false;
+ this.numEntries++;
+ }
+
+ double parseDouble(StringBuilder sb)
+ {
+ double value = 0;
+ try
+ {
+ value = Double.parseDouble(sb.toString());
+ }
+ catch (NumberFormatException e)
+ {
+ String msg = WorldWind.retrieveErrMsg("layers.PlaceNameLayer.ExceptionAttemptingToReadFile");
+ WorldWind.logger().log(Level.FINE, msg, e);
+ }
+ return value;
+ }
+
+ int[] append(int[] array, int index, int value)
+ {
+ if (index >= array.length)
+ array = this.resizeArray(array);
+ array[index] = value;
+ return array;
+ }
+
+ int[] resizeArray(int[] oldArray)
+ {
+ int newSize = 2 * oldArray.length;
+ int[] newArray = new int[newSize];
+ System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
+ return newArray;
+ }
+
+ double[] append(double[] array, int index, double value)
+ {
+ if (index >= array.length)
+ array = this.resizeArray(array);
+ array[index] = value;
+ return array;
+ }
+
+ double[] resizeArray(double[] oldArray)
+ {
+ int newSize = 2 * oldArray.length;
+ double[] newArray = new double[newSize];
+ System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
+ return newArray;
+ }
+
+ public void characters(char ch[], int start, int length)
+ {
+ if (!this.inBeginEndPair)
+ return;
+
+ String top = this.qNameStack.getFirst();
+
+ StringBuilder sb = null;
+ if (TOPP_LATITUDE == top)
+ sb = this.latBuffer;
+ else if (TOPP_LONGITUDE == top)
+ sb = this.lonBuffer;
+ else if (TOPP_FULL_NAME_ND == top)
+ sb = this.textArray;
+
+ if (sb != null)
+ sb.append(ch, start, length);
+ }
+
+ public void startElement(String uri, String localName, String qName, org.xml.sax.Attributes attributes)
+ {
+ String internQName = qName.intern();
+ // Don't validate uri, localName or attributes because they aren't used.
+ if (GML_FEATURE_MEMBER == internQName)
+ this.beginEntry();
+ this.qNameStack.addFirst(qName);
+ }
+
+ public void endElement(String uri, String localName, String qName)
+ {
+ String internQName = qName.intern();
+ // Don't validate uri or localName because they aren't used.
+ if (GML_FEATURE_MEMBER == internQName)
+ this.endEntry();
+ this.qNameStack.removeFirst();
+ }
+ }
+
+ private boolean loadTile(Tile tile, java.net.URL url)
+ {
+ PlaceNameChunk placeNameChunk = readTile(tile, url);
+ if (placeNameChunk == null)
+ return false;
+
+ WorldWind.memoryCache().add(tile, placeNameChunk);
+ return true;
+ }
+
+ private static PlaceNameChunk readTile(Tile tile, java.net.URL url)
+ {
+ java.io.InputStream is = null;
+
+ try
+ {
+ String path = url.getFile();
+ path = path.replaceAll("%20", " "); // TODO: find a better way to get a path usable by FileInputStream
+
+ java.io.FileInputStream fis = new java.io.FileInputStream(path);
+ java.io.BufferedInputStream buf = new java.io.BufferedInputStream(fis);
+ is = new java.util.zip.GZIPInputStream(buf);
+
+ GMLPlaceNameSAXHandler handler = new GMLPlaceNameSAXHandler();
+ javax.xml.parsers.SAXParserFactory.newInstance().newSAXParser().parse(is, handler);
+ return handler.createPlaceNameChunk(tile.getPlaceNameService());
+ }
+ catch (Exception e)
+ {
+ String message = WorldWind.retrieveErrMsg("layers.PlaceNameLayer.ExceptionAttemptingToReadFile");
+ WorldWind.logger().log(Level.FINE, message, e);
+ }
+ finally
+ {
+ try
+ {
+ if (is != null)
+ is.close();
+ }
+ catch (java.io.IOException e)
+ {
+ String message = WorldWind.retrieveErrMsg("layers.PlaceNameLayer.ExceptionAttemptingToReadFile");
+ WorldWind.logger().log(Level.FINE, message, e);
+ }
+ }
+
+ return null;
+ }
+
+ private void requestTile(Queue queue, Tile tile)
+ {
+ if (tile.getPlaceNameService().isResourceAbsent(tile.getPlaceNameService().getTileNumber(
+ tile.row, tile.column)))
+ return;
+ if (!queue.contains(tile))
+ queue.offer(tile);
+ }
+
+ private void sendRequests()
+ {
+ Tile tile;
+ // Send threaded read tasks.
+ while (!WorldWind.threadedTaskService().isFull() && (tile = this.readQueue.poll()) != null)
+ {
+ WorldWind.threadedTaskService().addTask(new RequestTask(this, tile));
+ }
+ // Send retriever tasks.
+ while (!WorldWind.retrievalService().isFull() && (tile = this.downloadQueue.poll()) != null)
+ {
+ java.net.URL url;
+ try
+ {
+ url = tile.getRequestURL();
+ }
+ catch (java.net.MalformedURLException e)
+ {
+ String message = WorldWind.retrieveErrMsg("layers.PlaceNameLayer.ExceptionAttemptingToDownloadFile");
+ WorldWind.logger().log(Level.FINE, message + tile, e);
+ return;
+ }
+ WorldWind.retrievalService().runRetriever(new HTTPRetriever(url, new DownloadPostProcessor(this, tile)));
+ }
+ }
+}
diff --git a/gov/nasa/worldwind/layers/RenderableLayer.java b/gov/nasa/worldwind/layers/RenderableLayer.java
new file mode 100644
index 0000000..a46cd70
--- /dev/null
+++ b/gov/nasa/worldwind/layers/RenderableLayer.java
@@ -0,0 +1,137 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers;
+
+import gov.nasa.worldwind.*;
+
+import javax.media.opengl.*;
+import java.util.*;
+import java.awt.*;
+
+/**
+ * @author tag
+ * @version $Id: RenderableLayer.java 1435 2007-04-11 02:03:35Z tgaskins $
+ */
+public class RenderableLayer extends AbstractLayer
+{
+ private Collection renderables = new ArrayList();
+ private final PickSupport pickSupport = new PickSupport();
+ private final Layer delegateOwner;
+
+ public RenderableLayer()
+ {
+ this.delegateOwner = null;
+ }
+
+ public RenderableLayer(Layer delegateOwner)
+ {
+ this.delegateOwner = delegateOwner;
+ }
+
+ public void setRenderables(Iterable shapeIterator)
+ {
+ this.renderables = new ArrayList();
+
+ if (shapeIterator == null)
+ return;
+
+ for (Renderable renderable : shapeIterator)
+ {
+ this.renderables.add(renderable);
+ }
+ }
+
+ public void addRenderable(Renderable renderable)
+ {
+ if (renderable == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.Shape");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.renderables.add(renderable);
+ }
+
+ public void removeRenderable(Renderable renderable)
+ {
+ if (renderable == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.Shape");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.renderables.remove(renderable);
+ }
+
+ public Collection getRenderables()
+ {
+ return this.renderables;
+ }
+
+ public void dispose()
+ {
+ for (Renderable renderable : this.renderables)
+ {
+ if (renderable instanceof Disposable)
+ ((Disposable) renderable).dispose();
+ }
+ }
+
+ @Override
+ protected void doPick(DrawContext dc, java.awt.Point pickPoint)
+ {
+ this.pickSupport.clearPickList();
+ this.pickSupport.beginPicking(dc);
+
+ for (Renderable renderable : this.renderables)
+ {
+ float[] inColor = new float[4];
+ dc.getGL().glGetFloatv(GL.GL_CURRENT_COLOR, inColor, 0);
+ Color color = dc.getUniquePickColor();
+ dc.getGL().glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue());
+
+ renderable.render(dc);
+
+ dc.getGL().glColor4fv(inColor, 0);
+
+ if (renderable instanceof Locatable)
+ {
+ this.pickSupport.addPickableObject(color.getRGB(), renderable,
+ ((Locatable) renderable).getPosition(), false);
+ }
+ else
+ {
+ this.pickSupport.addPickableObject(color.getRGB(), renderable);
+ }
+ }
+
+ this.pickSupport.resolvePick(dc, pickPoint, this.delegateOwner != null ? this.delegateOwner : this);
+ this.pickSupport.endPicking(dc);
+ }
+
+ @Override
+ protected void doRender(DrawContext dc)
+ {
+ for (Renderable renderable : this.renderables)
+ {
+ renderable.render(dc);
+ }
+ }
+
+ public Layer getDelegateOwner()
+ {
+ return delegateOwner;
+ }
+
+ @Override
+ public String toString()
+ {
+ return WorldWind.retrieveErrMsg("layers.RenderableLayer.Name");
+ }
+}
diff --git a/gov/nasa/worldwind/layers/RpfLayer.java b/gov/nasa/worldwind/layers/RpfLayer.java
new file mode 100644
index 0000000..88c1ea1
--- /dev/null
+++ b/gov/nasa/worldwind/layers/RpfLayer.java
@@ -0,0 +1,1166 @@
+/*
+Copyright (C) 2001, 2006 United States Government as represented by
+the Administrator of the National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers;
+
+import com.sun.opengl.util.*;
+import com.sun.opengl.util.texture.*;
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.formats.rpf.*;
+import gov.nasa.worldwind.geom.*;
+
+import javax.media.opengl.*;
+import java.io.*;
+import java.net.*;
+import java.nio.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.*;
+import static java.util.logging.Level.*;
+
+/**
+ * @author dcollins
+ * @version $Id: RpfLayer.java 1774 2007-05-08 01:03:37Z dcollins $
+ */
+public class RpfLayer extends AbstractLayer
+{
+ public static final Angle DefaultDeltaLat = Angle.fromDegrees(0.05);
+ public static final Angle DefaultDeltaLon = Angle.fromDegrees(0.075);
+ private final RpfDataSeries dataSeries;
+ private final MemoryCache memoryCache;
+ private Angle deltaLat;
+ private Angle deltaLon;
+
+ public RpfLayer(RpfDataSeries dataSeries)
+ {
+ this(dataSeries, DefaultDeltaLat, DefaultDeltaLon);
+ }
+
+ public RpfLayer(RpfDataSeries dataSeries, Angle deltaLat, Angle deltaLon)
+ {
+ this(dataSeries, deltaLat, deltaLon, null);
+ }
+
+ public RpfLayer(RpfDataSeries dataSeries, Angle deltaLat, Angle deltaLon,
+ MemoryCache sharedMemoryCache)
+ {
+ if (dataSeries == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.RpfDataSeriesIsNull");
+ WorldWind.logger().log(FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ if (deltaLat == null || deltaLon == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull");
+ WorldWind.logger().log(FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ this.dataSeries = dataSeries;
+ this.deltaLat = deltaLat;
+ this.deltaLon = deltaLon;
+ // Initialize the MemoryCache.
+ if (sharedMemoryCache != null)
+ {
+ this.memoryCache = sharedMemoryCache;
+ }
+ else
+ {
+ this.memoryCache = new BasicMemoryCache();
+ this.memoryCache.addCacheListener(new MemoryCache.CacheListener()
+ {
+ public void entryRemoved(Object key, Object clientObject)
+ {
+ if (clientObject == null || !(clientObject instanceof TextureTile))
+ return;
+ TextureTile textureTile = (TextureTile) clientObject;
+ disposalQueue.offer(textureTile);
+ }
+ });
+ }
+ this.updateMemoryCache();
+ }
+
+ public static long estimateMemoryCacheCapacity(RpfDataSeries dataSeries, Angle deltaLat, Angle deltaLon)
+ {
+ RpfZone.ZoneValues zoneValues = RpfZone.Zone1.zoneValues(dataSeries);
+ long numRows = (long) Math.ceil(deltaLat.divide(zoneValues.latitudinalFrameExtent));
+ long numCols = (long) Math.ceil(deltaLon.divide(zoneValues.longitudinalFrameExtent));
+ long capacity = numRows * numCols * 4L * 1024L * 1024L;
+ return 32L * (long) Math.ceil(capacity / 32d);
+ }
+
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(this.dataSeries.seriesCode);
+ sb.append(": ");
+ sb.append(this.dataSeries.dataSeries);
+ return sb.toString();
+ }
+
+ public Angle getViewingDeltaLat()
+ {
+ return this.deltaLat;
+ }
+
+ public Angle getViewingDeltaLon()
+ {
+ return this.deltaLon;
+ }
+
+ public void setViewingDeltaLat(Angle deltaLat)
+ {
+ this.deltaLat = deltaLat;
+ this.updateMemoryCache();
+ }
+
+ public void setViewingDeltaLon(Angle deltaLon)
+ {
+ this.deltaLon = deltaLon;
+ this.updateMemoryCache();
+ }
+
+ private void updateMemoryCache()
+ {
+ long capacity = estimateMemoryCacheCapacity(this.dataSeries, this.deltaLat, this.deltaLon);
+ if (this.memoryCache.getCapacity() < capacity)
+ this.memoryCache.setCapacity(capacity);
+ }
+
+ // ============== Frame Directory ======================= //
+ // ============== Frame Directory ======================= //
+ // ============== Frame Directory ======================= //
+
+ private final static String RPF_OVERVIEW_EXTENSION = ".OVR";
+ private final Map frameDirectory
+ = new HashMap();
+ private Sector sector = Sector.EMPTY_SECTOR;
+ private int modCount = 0;
+ private int lastModCount = 0;
+
+ private static class FrameKey
+ {
+ public final RpfZone zone;
+ public final int frameNumber;
+ private final int hashCode;
+
+ public FrameKey(RpfZone zone, int frameNumber)
+ {
+ this.zone = zone;
+ this.frameNumber = frameNumber;
+ this.hashCode = this.computeHash();
+ }
+
+ private int computeHash()
+ {
+ int hash = 0;
+ if (this.zone != null)
+ hash = 29 * hash + this.zone.ordinal();
+ hash = 29 * hash + this.frameNumber;
+ return hash;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || !o.getClass().equals(this.getClass()))
+ return false;
+ final FrameKey that = (FrameKey) o;
+ return (this.zone == that.zone) && (this.frameNumber == that.frameNumber);
+ }
+
+ public int hashCode()
+ {
+ return this.hashCode;
+ }
+ }
+
+ private static class FrameRecord
+ {
+ public final RpfFrameProperties properties;
+ public final Sector sector;
+ public final String filePath;
+ public final String cacheFilePath;
+ private boolean corruptCache = false;
+ final Lock fileLock = new ReentrantLock();
+
+ public FrameRecord(RpfFrameProperties properties, Sector sector, String filePath, String cacheFilePath)
+ {
+ this.properties = properties;
+ this.sector = sector;
+ this.filePath = filePath;
+ this.cacheFilePath = cacheFilePath;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || !o.getClass().equals(this.getClass()))
+ return false;
+ final FrameRecord that = (FrameRecord) o;
+ return this.filePath.equals(that.filePath) && (this.properties.equals(that.properties));
+ }
+
+ public boolean isCorruptCache()
+ {
+ return this.corruptCache;
+ }
+
+ public void setCorruptCache(boolean corruptCache)
+ {
+ this.corruptCache = corruptCache;
+ }
+ }
+
+ public int addAll(Collection tocFiles)
+ {
+ if (tocFiles == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.CollectionIsNull");
+ WorldWind.logger().log(FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ int startModCount = this.modCount;
+ // Fill frame directory with contents in 'tocFiles'.
+ for (RpfTocFile file : tocFiles)
+ {
+ if (file != null)
+ this.addContents(file);
+ }
+ return this.modCount - startModCount;
+ }
+
+ public int addContents(RpfTocFile tocFile)
+ {
+ if (tocFile == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.RpfTocFileIsNull");
+ WorldWind.logger().log(FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ RpfFrameFileIndexSection indexSection = tocFile.getFrameFileIndexSection();
+ if (indexSection == null)
+ return 0;
+ ArrayList indexTable = indexSection.getFrameFileIndexTable();
+ if (indexTable == null)
+ return 0;
+ int startModCount = this.modCount;
+ for (RpfFrameFileIndexSection.RpfFrameFileIndexRecord indexRecord : indexTable)
+ {
+ if (!indexRecord.getFrameFileName().toUpperCase().endsWith(RPF_OVERVIEW_EXTENSION))
+ {
+ FrameRecord record = null;
+ try
+ {
+ record = createRecord(tocFile, indexRecord);
+ }
+ catch (Exception e)
+ {
+ String message = WorldWind.retrieveErrMsg("layers.RpfLayer.ExceptionParsingFileName")
+ + indexRecord.getFrameFileName();
+ WorldWind.logger().log(FINE, message, e);
+ }
+ if (record != null && this.dataSeries == record.properties.dataSeries)
+ {
+ this.addRecord(record);
+ }
+ }
+ }
+ return this.modCount - startModCount;
+ }
+
+ private void addRecord(FrameRecord record)
+ {
+ FrameKey key = keyFor(record);
+ this.frameDirectory.put(key, record);
+ ++this.modCount;
+ }
+
+ private static String cachePathFor(RpfFrameProperties properties)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Earth").append(File.separatorChar);
+ sb.append("RPF").append(File.separatorChar);
+ sb.append(properties.dataSeries.seriesCode).append(File.separatorChar);
+ sb.append(properties.zone.zoneCode).append(File.separatorChar);
+ sb.append(RpfFrameFilenameUtil.filenameFor(properties));
+ return sb.toString();
+ }
+
+ private static String createAbsolutePath(String... pathElem)
+ {
+ StringBuilder sb = new StringBuilder();
+ for (String str : pathElem)
+ {
+ if (str != null && str.length() > 0)
+ {
+ int startIndex = 0;
+ if (str.startsWith("./") || str.startsWith(".\\"))
+ startIndex = 1;
+ else if (!str.startsWith("/") && !str.startsWith("\\"))
+ sb.append(File.separatorChar);
+ int endIndex;
+ if (str.endsWith("/") || str.endsWith("\\"))
+ endIndex = str.length() - 1;
+ else
+ endIndex = str.length();
+ sb.append(str, startIndex, endIndex);
+ }
+ }
+ if (sb.length() <= 0)
+ return null;
+ return sb.toString();
+ }
+
+ private static FrameRecord createRecord(RpfTocFile tocFile,
+ RpfFrameFileIndexSection.RpfFrameFileIndexRecord indexRecord)
+ {
+ RpfFrameProperties frameProps = RpfFrameFilenameUtil.parseFilename(indexRecord.getFrameFileName());
+ Sector sector = sectorFor(frameProps);
+ String filePath = createAbsolutePath(tocFile.getFile().getParentFile().getAbsolutePath(),
+ indexRecord.getPathname(), indexRecord.getFrameFileName());
+ String cachePath = cachePathFor(frameProps);
+ if (frameProps == null || sector == null || filePath == null || cachePath == null)
+ {
+ String message = WorldWind.retrieveErrMsg("layers.RpfLayer.BadFrameInput") + indexRecord.getFrameFileName();
+ WorldWind.logger().log(FINE, message);
+ throw new WWRuntimeException(message);
+ }
+ return new FrameRecord(frameProps, sector, filePath, cachePath);
+ }
+
+ private static FrameKey keyFor(FrameRecord record)
+ {
+ return new FrameKey(record.properties.zone, record.properties.frameNumber);
+ }
+
+ public int removeAll(Collection tocFiles)
+ {
+ if (tocFiles == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.CollectionIsNull");
+ WorldWind.logger().log(FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ int startModCount = this.modCount;
+ // Fill frame directory with contents in 'tocFiles'.
+ for (RpfTocFile file : tocFiles)
+ {
+ if (file != null)
+ this.removeContents(file);
+ }
+ return startModCount - this.modCount;
+ }
+
+ public int removeContents(RpfTocFile tocFile)
+ {
+ if (tocFile == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.RpfTocFileIsNull");
+ WorldWind.logger().log(FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ RpfFrameFileIndexSection indexSection = tocFile.getFrameFileIndexSection();
+ if (indexSection == null)
+ return 0;
+ ArrayList indexTable = indexSection.getFrameFileIndexTable();
+ if (indexTable == null)
+ return 0;
+ int startModCount = this.modCount;
+ for (RpfFrameFileIndexSection.RpfFrameFileIndexRecord indexRecord : indexTable)
+ {
+ if (!indexRecord.getFrameFileName().toUpperCase().endsWith(RPF_OVERVIEW_EXTENSION))
+ {
+ RpfFrameProperties frameProps = null;
+ try
+ {
+ frameProps = RpfFrameFilenameUtil.parseFilename(indexRecord.getFrameFileName());
+ }
+ catch (IllegalArgumentException e)
+ {
+ String message = WorldWind.retrieveErrMsg("layers.RpfLayer.ExceptionParsingFileName")
+ + indexRecord.getFrameFileName();
+ WorldWind.logger().log(FINE, message, e);
+ }
+ if (frameProps != null && this.dataSeries == frameProps.dataSeries)
+ this.removeKey(new FrameKey(frameProps.zone, frameProps.frameNumber));
+ }
+ }
+ return startModCount - this.modCount;
+ }
+
+ private boolean removeKey(FrameKey key)
+ {
+ FrameRecord value = this.frameDirectory.remove(key);
+ --this.modCount;
+ return value != null;
+ }
+
+// private boolean removeRecord(FrameRecord record)
+// {
+// FrameKey key = keyFor(record);
+// FrameRecord value = this.frameDirectory.remove(key);
+// --this.modCount;
+// return value != null;
+// }
+
+ private static Sector sectorFor(RpfFrameProperties properties)
+ {
+ if (properties == null
+ || properties.zone == null
+ || properties.dataSeries == null)
+ return null;
+ RpfZone.ZoneValues zoneValues = properties.zone.zoneValues(properties.dataSeries);
+ if (properties.frameNumber < 0 || properties.frameNumber > (zoneValues.maximumFrameNumber - 1))
+ return null;
+ return zoneValues.frameExtent(properties.frameNumber);
+ }
+
+ private void updateSector()
+ {
+ Sector newSector = null;
+ for (FrameRecord record : this.frameDirectory.values())
+ {
+ if (record.sector != null)
+ newSector = (newSector != null) ? newSector.union(record.sector) : record.sector;
+ }
+ this.sector = newSector;
+ }
+
+ // ============== Tile Assembly ======================= //
+ // ============== Tile Assembly ======================= //
+ // ============== Tile Assembly ======================= //
+
+ private final Queue assemblyQueue = new LinkedList();
+ private final Queue assemblyRequestQueue = new LinkedList();
+
+ private void assembleFrameTiles(DrawContext dc, RpfDataSeries dataSeries, Sector viewingSector,
+ Queue tilesToRender, Queue framesToRequest)
+ {
+ for (RpfZone zone : RpfZone.values())
+ {
+ RpfZone.ZoneValues zoneValues = zone.zoneValues(dataSeries);
+ Sector sector = zoneValues.extent.intersection(viewingSector);
+ if (sector != null && isSectorVisible(dc, sector))
+ this.assembleZoneTiles(dc, zoneValues, sector, tilesToRender, framesToRequest);
+ }
+ }
+
+ private void assembleZoneTiles(DrawContext dc, RpfZone.ZoneValues zoneValues, Sector sector,
+ Queue tilesToRender, Queue framesToRequest)
+ {
+ int startRow = zoneValues.frameRowFromLatitude(sector.getMinLatitude());
+ int endRow = zoneValues.frameRowFromLatitude(sector.getMaxLatitude());
+ int startCol = zoneValues.frameColumnFromLongitude(sector.getMinLongitude());
+ int endCol = zoneValues.frameColumnFromLongitude(sector.getMaxLongitude());
+
+ for (int row = startRow; row <= endRow; row++)
+ {
+ for (int col = startCol; col <= endCol; col++)
+ {
+ int frameNum = zoneValues.frameNumber(row, col);
+ getOrRequestTile(dc, new FrameKey(zoneValues.zone, frameNum), tilesToRender, framesToRequest);
+ }
+ }
+ }
+
+ private void getOrRequestTile(DrawContext dc, FrameKey key, Queue tilesToRender,
+ Queue framesToRequest)
+ {
+ TextureTile tile = this.getTile(key);
+ if (tile != null)
+ {
+ if (isSectorVisible(dc, tile.getSector()))
+ tilesToRender.offer(tile);
+ }
+ else
+ {
+ FrameRecord record = this.frameDirectory.get(key);
+ if (record != null && isSectorVisible(dc, record.sector))
+ framesToRequest.offer(record);
+ }
+ }
+
+ private static Sector[] normalizeSector(Sector sector)
+ {
+ Angle minLat = clampAngle(sector.getMinLatitude(), Angle.NEG90, Angle.POS90);
+ Angle maxLat = clampAngle(sector.getMaxLatitude(), Angle.NEG90, Angle.POS90);
+ if (maxLat.degrees < minLat.degrees)
+ {
+ Angle tmp = minLat;
+ minLat = maxLat;
+ maxLat = tmp;
+ }
+
+ Angle minLon = normalizeAngle(sector.getMinLongitude(), Angle.NEG180, Angle.POS180);
+ Angle maxLon = normalizeAngle(sector.getMaxLongitude(), Angle.NEG180, Angle.POS180);
+ if (maxLon.degrees < minLon.degrees)
+ {
+ return new Sector[] {
+ new Sector(minLat, maxLat, minLon, Angle.POS180),
+ new Sector(minLat, maxLat, Angle.NEG180, maxLon),
+ };
+ }
+
+ return new Sector[] {new Sector(minLat, maxLat, minLon, maxLon)};
+ }
+
+ private static Sector createViewSector(Angle centerLat, Angle centerLon, Angle deltaLat, Angle deltaLon)
+ {
+ return new Sector(centerLat.subtract(deltaLat), centerLat.add(deltaLat),
+ centerLon.subtract(deltaLon), centerLon.add(deltaLon));
+ }
+
+ private static Angle clampAngle(Angle angle, Angle min, Angle max)
+ {
+ return (angle.degrees < min.degrees) ? min : ((angle.degrees > max.degrees) ? max : angle);
+ }
+
+ private static Angle normalizeAngle(Angle angle, Angle min, Angle max)
+ {
+ Angle range = max.subtract(min);
+ return (angle.degrees < min.degrees) ?
+ angle.add(range) : ((angle.degrees > max.degrees) ? angle.subtract(range) : angle);
+ }
+
+ // ============== Rendering ======================= //
+ // ============== Rendering ======================= //
+ // ============== Rendering ======================= //
+
+ private static final BlockingQueue disposalQueue = new LinkedBlockingQueue();
+ // private final List tileQueue = new ArrayList();
+ private final SurfaceTileRenderer tileRenderer = new SurfaceTileRenderer();
+ private final IconRenderer iconRenderer = new IconRenderer();
+ private TextureTile coverageTile;
+ private WWIcon coverageIcon;
+ private int tileGridDrawThreshold = 30;
+ private boolean drawCoverage = true;
+ private boolean drawCoverageIcon = true;
+
+ private static TextureData createCoverageTextureData(Sector sector, Collection records,
+ int width, int height, int fgColor, int bgColor)
+ {
+ IntBuffer buffer = BufferUtil.newIntBuffer(width * height);
+ for (int i = 0; i < width * height; i++)
+ {
+ buffer.put(i, bgColor);
+ }
+ buffer.rewind();
+
+ Angle latWidth = sector.getMaxLatitude().subtract(sector.getMinLatitude());
+ Angle lonWidth = sector.getMaxLongitude().subtract(sector.getMinLongitude());
+ for (FrameRecord record : records)
+ {
+ int x0 = (int) Math.round((width - 1)
+ * record.sector.getMinLongitude().subtract(sector.getMinLongitude()).divide(lonWidth));
+ int y0 = (int) Math.round((height - 1)
+ * record.sector.getMinLatitude().subtract(sector.getMinLatitude()).divide(latWidth));
+ int x1 = (int) Math.round((width - 1)
+ * record.sector.getMaxLongitude().subtract(sector.getMinLongitude()).divide(lonWidth));
+ int y1 = (int) Math.round((width - 1)
+ * record.sector.getMaxLatitude().subtract(sector.getMinLatitude()).divide(latWidth));
+ for (int y = y0; y <= y1; y++)
+ {
+ for (int x = x0; x <= x1; x++)
+ {
+ buffer.put(x + y * width, fgColor);
+ }
+ }
+ }
+ buffer.rewind();
+ return new TextureData(GL.GL_RGBA, width, height, 0, GL.GL_RGBA, GL.GL_UNSIGNED_INT_8_8_8_8,
+ false, false, false, buffer, null);
+ }
+
+ public void dispose()
+ {
+ if (this.tileRenderer != null)
+ disposalQueue.offer(this.tileRenderer);
+ if (this.iconRenderer != null)
+ disposalQueue.offer(this.iconRenderer);
+ if (this.coverageTile != null)
+ disposalQueue.offer(this.coverageTile);
+ this.coverageTile = null;
+ processDisposables();
+ }
+
+ protected void doRender(DrawContext dc)
+ {
+ // Process disposable queue.
+ processDisposables();
+
+ if (!isSectorVisible(dc, this.sector))
+ return;
+
+ // Update sector and coverage renderables when frame contents change.
+ if (this.modCount != this.lastModCount)
+ {
+ this.updateSector();
+ this.updateCoverage();
+ this.lastModCount = this.modCount;
+ }
+
+ GL gl = dc.getGL();
+
+ // Coverage tile.
+ if (this.drawCoverage && this.coverageTile != null)
+ {
+ int attribBits = GL.GL_ENABLE_BIT | GL.GL_COLOR_BUFFER_BIT | GL.GL_POLYGON_BIT;
+ gl.glPushAttrib(attribBits);
+ try
+ {
+ gl.glEnable(GL.GL_BLEND);
+ gl.glEnable(GL.GL_CULL_FACE);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+ gl.glCullFace(GL.GL_BACK);
+ gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
+ this.tileRenderer.renderTile(dc, this.coverageTile);
+ }
+ finally
+ {
+ gl.glPopAttrib();
+ }
+ }
+ // Assemble frame tiles.
+ this.assemblyQueue.clear();
+ this.assemblyRequestQueue.clear();
+ Position viewPosition = dc.getView().getPosition();
+ Sector viewingSector = createViewSector(viewPosition.getLatitude(), viewPosition.getLongitude(),
+ this.deltaLat, this.deltaLon);
+ for (Sector sector : normalizeSector(viewingSector))
+ {
+ this.assembleFrameTiles(dc, this.dataSeries, sector, this.assemblyQueue, this.assemblyRequestQueue);
+ }
+ Sector drawSector = null;
+ for (TextureTile tile : this.assemblyQueue)
+ {
+ drawSector = (drawSector != null) ? drawSector.union(tile.getSector()) : tile.getSector();
+ }
+
+ boolean drawFrameTiles = this.tileGridDrawThreshold <= pixelSizeOfSector(dc, viewingSector)
+ || (drawSector != null && this.tileGridDrawThreshold <= pixelSizeOfSector(dc, drawSector));
+ // Render frame tiles.
+ if (drawFrameTiles)
+ {
+ this.requestAllFrames(this.readQueue, this.assemblyRequestQueue);
+ int attribBits = GL.GL_ENABLE_BIT | GL.GL_POLYGON_BIT;
+ gl.glPushAttrib(attribBits);
+ try
+ {
+ gl.glEnable(GL.GL_CULL_FACE);
+ gl.glCullFace(GL.GL_BACK);
+ gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
+ this.tileRenderer.renderTiles(dc, this.assemblyQueue);
+ }
+ finally
+ {
+ gl.glPopAttrib();
+ }
+ }
+ // Render coverage icon.
+ else if (this.drawCoverageIcon && this.coverageIcon != null)
+ {
+ LatLon centroid = (drawSector != null) ? drawSector.getCentroid() : this.sector.getCentroid();
+ this.coverageIcon.setPosition(new Position(centroid.getLatitude(), centroid.getLongitude(), 0));
+ this.iconRenderer.render(dc, this.coverageIcon, null);
+ }
+
+ // Process request queue.
+ this.sendRequests(this.assemblyRequestQueue.size());
+ }
+
+ public WWIcon getCoverageIcon()
+ {
+ return this.coverageIcon;
+ }
+
+ public int getTileGridDrawThreshold()
+ {
+ return this.tileGridDrawThreshold;
+ }
+
+ private static void initializeFrameTexture(Texture texture)
+ {
+ texture.setTexParameteri(GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
+ texture.setTexParameteri(GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
+ texture.setTexParameteri(GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
+ texture.setTexParameteri(GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);
+ }
+
+ private static void initializeOtherTexture(Texture texture)
+ {
+ texture.setTexParameteri(GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
+ texture.setTexParameteri(GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
+ texture.setTexParameteri(GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
+ texture.setTexParameteri(GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);
+ }
+
+ public boolean isDrawCoverage()
+ {
+ return this.drawCoverage;
+ }
+
+ public boolean isDrawCoverageIcon()
+ {
+ return this.drawCoverageIcon;
+ }
+
+ private boolean isSectorVisible(DrawContext dc, Sector sector)
+ {
+ if (dc.getVisibleSector() != null && !sector.intersects(dc.getVisibleSector()))
+ return false;
+
+ Extent e = Sector.computeBoundingCylinder(dc.getGlobe(), dc.getVerticalExaggeration(), sector);
+ return e.intersects(dc.getView().getFrustumInModelCoordinates());
+ }
+
+ private static int pixelSizeOfSector(DrawContext dc, Sector sector)
+ {
+ LatLon centroid = sector.getCentroid();
+ Globe globe = dc.getGlobe();
+ Point centroidPoint = globe.computePointFromPosition(centroid.getLatitude(), centroid.getLongitude(), 0);
+ Point minPoint = globe.computePointFromPosition(sector.getMinLatitude(), sector.getMinLongitude(), 0);
+ Point maxPoint = globe.computePointFromPosition(sector.getMaxLatitude(), sector.getMaxLongitude(), 0);
+ double distanceToEye = centroidPoint.distanceTo(dc.getView().getEyePoint());
+ double sectorSize = minPoint.distanceTo(maxPoint);
+ double pixelSize = dc.getView().computePixelSizeAtDistance(distanceToEye);
+ return (int) Math.round(sectorSize / pixelSize);
+ }
+
+ private static void processDisposables()
+ {
+ Disposable disposable;
+ while ((disposable = disposalQueue.poll()) != null)
+ {
+ disposable.dispose();
+ }
+ }
+
+ private static int rgbaInt(int r, int g, int b, int a)
+ {
+ r = (int) (r * (a / 255d));
+ g = (int) (g * (a / 255d));
+ b = (int) (b * (a / 255d));
+ return ((0xFF & r) << 24) + ((0xFF & g) << 16) + ((0xFF & b) << 8) + (0xFF & a);
+ }
+
+ public void setCoverageIcon(WWIcon icon)
+ {
+ if (icon == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.Icon");
+ WorldWind.logger().log(FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ this.coverageIcon = icon;
+ }
+
+ public void setDrawCoverage(boolean drawCoverage)
+ {
+ this.drawCoverage = drawCoverage;
+ }
+
+ public void setDrawCoverageIcon(boolean drawCoverageIcon)
+ {
+ this.drawCoverageIcon = drawCoverageIcon;
+ }
+
+ public void setTileGridDrawThreshold(int pixelSize)
+ {
+ if (pixelSize <= 0)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.ValueOutOfRange") + String.valueOf(pixelSize);
+ WorldWind.logger().log(FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+ this.tileGridDrawThreshold = pixelSize;
+ }
+
+ private void updateCoverage()
+ {
+ if (this.coverageTile != null)
+ disposalQueue.offer(this.coverageTile);
+ TextureData textureData = createCoverageTextureData(this.sector, this.frameDirectory.values(), 1024, 1024,
+ rgbaInt(255, 0, 0, 102), rgbaInt(0, 0, 0, 0));
+ this.coverageTile = new TextureTile(this.sector)
+ {
+ public void initializeTexture(DrawContext dc)
+ {
+ if (this.getTexture() == null)
+ {
+ Texture tex = TextureIO.newTexture(this.getTextureData());
+ initializeOtherTexture(tex);
+ this.setTexture(tex);
+ }
+ }
+ };
+ this.coverageTile.setTextureData(textureData);
+ }
+
+ // ============== Image Reading and Conversion ======================= //
+ // ============== Image Reading and Conversion ======================= //
+ // ============== Image Reading and Conversion ======================= //
+
+ private final LinkedList downloadQueue = new LinkedList();
+ private final LinkedList readQueue = new LinkedList();
+
+ private static class RpfRetriever extends WWObjectImpl implements Retriever
+ {
+ private final RpfLayer layer;
+ private final FrameRecord record;
+ private volatile RpfImageFile rpfImageFile;
+ private volatile ByteBuffer buffer;
+ private volatile String state = RETRIEVER_STATE_NOT_STARTED;
+ private long submitTime;
+ private long beginTime;
+ private long endTime;
+
+ public RpfRetriever(RpfLayer layer, FrameRecord record)
+ {
+ this.layer = layer;
+ this.record = record;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || !o.getClass().equals(this.getClass()))
+ return false;
+ final RpfRetriever that = (RpfRetriever) o;
+ return this.record.equals(that.record);
+ }
+
+ public long getBeginTime()
+ {
+ return this.beginTime;
+ }
+
+ public ByteBuffer getBuffer()
+ {
+ return this.buffer;
+ }
+
+ public int getContentLength()
+ {
+ return 0;
+ }
+
+ public int getContentLengthRead()
+ {
+ return 0;
+ }
+
+ public String getContentType()
+ {
+ return null;
+ }
+
+ public long getEndTime()
+ {
+ return this.endTime;
+ }
+
+ public String getName()
+ {
+ return this.record.filePath;
+ }
+
+ public String getState()
+ {
+ return this.state;
+ }
+
+ public long getSubmitTime()
+ {
+ return this.submitTime;
+ }
+
+ private boolean interrupted()
+ {
+ if (Thread.currentThread().isInterrupted())
+ {
+ this.setState(RETRIEVER_STATE_INTERRUPTED);
+ String message = WorldWind.retrieveErrMsg("layers.RpfLayer.DownloadInterrupted")
+ + this.record.filePath;
+ WorldWind.logger().log(FINER, message);
+ return true;
+ }
+ return false;
+ }
+
+ public void setBeginTime(long beginTime)
+ {
+ this.beginTime = beginTime;
+ }
+
+ public void setEndTime(long endTime)
+ {
+ this.endTime = endTime;
+ }
+
+ private void setState(String state)
+ {
+ String oldState = this.state;
+ this.state = state;
+ this.firePropertyChange(AVKey.RETRIEVER_STATE, oldState, this.state);
+ }
+
+ public void setSubmitTime(long submitTime)
+ {
+ this.submitTime = submitTime;
+ }
+
+ public Retriever call() throws Exception
+ {
+ if (this.interrupted())
+ return this;
+
+ if (!this.record.fileLock.tryLock())
+ {
+ this.setState(RETRIEVER_STATE_SUCCESSFUL);
+ return this;
+ }
+ try
+ {
+ this.setState(RETRIEVER_STATE_STARTED);
+
+ if (!this.interrupted())
+ {
+ if (isFileResident(this.record.cacheFilePath))
+ {
+ this.setState(RETRIEVER_STATE_SUCCESSFUL);
+ return this;
+ }
+ }
+
+ if (!this.interrupted())
+ {
+ this.setState(RETRIEVER_STATE_CONNECTING);
+ File file = new File(this.record.filePath);
+ if (!file.exists())
+ {
+ String message = WorldWind.retrieveErrMsg("generic.fileNotFound") + this.record.filePath;
+ throw new IOException(message);
+ }
+ this.rpfImageFile = RpfImageFile.load(file);
+ }
+
+ if (!this.interrupted())
+ {
+ this.setState(RETRIEVER_STATE_READING);
+ File file = WorldWind.dataFileCache().newFile(this.record.cacheFilePath);
+ if (file == null)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.CantCreateCacheFile")
+ + this.record.cacheFilePath;
+ throw new IOException(message);
+ }
+ this.buffer = this.rpfImageFile.getImageAsDdsTexture();
+ WWIO.saveBuffer(this.buffer, file);
+ }
+
+ if (!this.interrupted())
+ {
+ this.setState(RETRIEVER_STATE_SUCCESSFUL);
+ this.layer.firePropertyChange(AVKey.LAYER, null, this.layer);
+ }
+ }
+ catch (Exception e)
+ {
+ this.setState(RETRIEVER_STATE_ERROR);
+ throw e;
+ }
+ finally
+ {
+ this.record.fileLock.unlock();
+ }
+
+ return this;
+ }
+ }
+
+ private static class ReadTask implements Runnable
+ {
+ public final RpfLayer layer;
+ public final FrameRecord record;
+
+ public ReadTask(RpfLayer layer, FrameRecord record)
+ {
+ this.layer = layer;
+ this.record = record;
+ }
+
+ private void deleteCorruptFrame(RpfLayer layer, FrameRecord record)
+ {
+ URL file = WorldWind.dataFileCache().findFile(record.cacheFilePath, false);
+ if (file != null)
+ WorldWind.dataFileCache().removeFile(file);
+ record.setCorruptCache(false);
+ layer.firePropertyChange(AVKey.LAYER, null, layer);
+ String message = WorldWind.retrieveErrMsg("generic.DeletedCorruptDataFile")
+ + ((file != null) ? file.getFile() : "null");
+ WorldWind.logger().log(FINE, message);
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || !o.getClass().equals(this.getClass()))
+ return false;
+ final ReadTask that = (ReadTask) o;
+ return (this.record != null) ? this.record.equals(that.record) : (that.record == null);
+ }
+
+ public void run()
+ {
+ FrameKey key = keyFor(this.record);
+ if (!this.layer.isTileResident(key))
+ {
+ this.readFrame(key, this.record);
+ this.layer.firePropertyChange(AVKey.LAYER, null, this.layer);
+ }
+ }
+
+ public void readFrame(FrameKey key, FrameRecord record)
+ {
+ URL dataFileURL = WorldWind.dataFileCache().findFile(record.cacheFilePath, false);
+ if (dataFileURL == null)
+ {
+ this.layer.requestFrame(this.layer.downloadQueue, this.record);
+ return; // Return when cached file does not exist.
+ }
+
+ if (!record.fileLock.tryLock())
+ return;
+ try
+ {
+ if (this.layer.isTileResident(key))
+ return;
+
+ TextureData textureData = null;
+ try
+ {
+ textureData = TextureIO.newTextureData(dataFileURL, false, "dds");
+ }
+ catch (IOException e)
+ {
+ String message = WorldWind.retrieveErrMsg("generic.TextureIOException") + record.cacheFilePath;
+ WorldWind.logger().log(FINE, message, e);
+ }
+
+ if (textureData == null)
+ {
+ this.deleteCorruptFrame(this.layer, this.record);
+ return;
+ }
+
+ TextureTile textureTile = new TextureTile(record.sector)
+ {
+ public void initializeTexture(DrawContext dc)
+ {
+ if (this.getTexture() == null)
+ {
+ Texture tex = TextureIO.newTexture(this.getTextureData());
+ initializeFrameTexture(tex);
+ this.setTexture(tex);
+ }
+ }
+ };
+ textureTile.setTextureData(textureData);
+ this.layer.makeTileResident(key, textureTile);
+ }
+ finally
+ {
+ record.fileLock.unlock();
+ }
+ }
+ }
+
+ private TextureTile getTile(FrameKey key)
+ {
+ synchronized (this.memoryCache)
+ {
+ Object obj = this.memoryCache.getObject(key);
+ if (obj != null && obj instanceof TextureTile)
+ return (TextureTile) obj;
+ return null;
+ }
+ }
+
+ private static boolean isFileResident(String fileName)
+ {
+ return WorldWind.dataFileCache().findFile(fileName, false) != null;
+ }
+
+ private boolean isTileResident(FrameKey key)
+ {
+ synchronized (this.memoryCache)
+ {
+ return this.memoryCache.getObject(key) != null;
+ }
+ }
+
+ private void makeTileResident(FrameKey key, TextureTile tile)
+ {
+ synchronized (this.memoryCache)
+ {
+ this.memoryCache.add(key, tile);
+ }
+ }
+
+ private void requestFrame(LinkedList queue, FrameRecord record)
+ {
+ synchronized (queue)
+ {
+ if (queue.contains(record))
+ return;
+ queue.addFirst(record);
+ }
+ }
+
+ private void requestAllFrames(LinkedList queue, Collection frameRecords)
+ {
+ for (FrameRecord record : frameRecords)
+ {
+ this.requestFrame(queue, record);
+ }
+ }
+
+ private void sendRequests(int maxRequests)
+ {
+ synchronized (this.readQueue)
+ {
+ while (this.readQueue.size() > maxRequests)
+ {
+ this.readQueue.removeLast();
+ }
+ // Send threaded read tasks.
+ FrameRecord record;
+ while (!WorldWind.threadedTaskService().isFull() && (record = this.readQueue.poll()) != null)
+ {
+ WorldWind.threadedTaskService().addTask(new ReadTask(this, record));
+ }
+ }
+
+ synchronized (this.downloadQueue)
+ {
+ while (this.downloadQueue.size() > maxRequests)
+ {
+ this.downloadQueue.removeLast();
+ }
+ // Send retriever tasks.
+ FrameRecord record;
+ while (!WorldWind.retrievalService().isFull() && (record = this.downloadQueue.poll()) != null)
+ {
+ WorldWind.retrievalService().runRetriever(new RpfRetriever(this, record));
+ }
+ }
+ }
+}
diff --git a/gov/nasa/worldwind/layers/TextureTile.java b/gov/nasa/worldwind/layers/TextureTile.java
new file mode 100644
index 0000000..8bdf93f
--- /dev/null
+++ b/gov/nasa/worldwind/layers/TextureTile.java
@@ -0,0 +1,408 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers;
+
+import com.sun.opengl.util.texture.*;
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+
+import javax.media.opengl.*;
+import java.util.concurrent.*;
+
+/**
+ * @author tag
+ * @version $Id: TextureTile.java 1767 2007-05-07 21:36:12Z tgaskins $
+ */
+public class TextureTile extends Tile implements Disposable
+{
+ private volatile TextureData textureData;
+ private Texture texture;
+ private TextureTile fallbackTile = null; // holds texture to use if own texture not available
+ private Point centroid; // Cartesian coordinate of lat/lon center
+ private Point[] corners; // Cartesian coordinate of lat/lon corners
+ private Extent extent = null; // bounding volume
+ private double extentVerticalExaggertion = Double.MIN_VALUE; // VE used to calculate the extent
+ private double minDistanceToEye = Double.MAX_VALUE;
+
+ private static ConcurrentLinkedQueue texturesToDispose = new ConcurrentLinkedQueue();
+
+ static
+ {
+ WorldWind.memoryCache().addCacheListener(new MemoryCache.CacheListener()
+ {
+ public synchronized void entryRemoved(Object key, Object clientObject)
+ {
+ // Unbind a tile's texture when the tile leaves the cache.
+ if (clientObject != null && clientObject instanceof TextureTile)
+ {
+ TextureTile tile = (TextureTile) clientObject;
+ if (tile.texture != null)
+ {
+ // Textures must be disposed of on a thread with a current OpenGL context,
+ // so just capture those to dispose here and perform the actual dispose during
+ // a rendering pass.
+ texturesToDispose.add(tile);
+ }
+ }
+ }
+ });
+ }
+
+ public synchronized static void disposeTextures()
+ {
+ TextureTile tile;
+ for (tile = texturesToDispose.poll(); tile != null && tile.texture != null; tile = texturesToDispose.poll())
+ {
+ tile.texture.dispose();
+ tile.texture = null;
+ }
+ }
+
+ public TextureTile(Sector sector)
+ {
+ super(sector);
+ }
+
+ public TextureTile(Sector sector, Level level, int row, int col)
+ {
+ super(sector, level, row, col);
+ }
+
+ @Override
+ public final long getSizeInBytes()
+ {
+ long size = super.getSizeInBytes();
+
+ if (this.textureData != null)
+ size += this.textureData.getEstimatedMemorySize();
+
+ return size;
+ }
+
+ public void dispose()
+ {
+ if (this.texture == null)
+ return;
+
+ if (GLContext.getCurrent() != null)
+ {
+ this.texture.dispose();
+ this.texture = null;
+ }
+ else if (!texturesToDispose.contains(this))
+ {
+ texturesToDispose.add(this);
+ }
+ }
+
+ public TextureTile getFallbackTile()
+ {
+ return this.fallbackTile;
+ }
+
+ public void setFallbackTile(TextureTile fallbackTile)
+ {
+ this.fallbackTile = fallbackTile;
+ }
+
+ public TextureData getTextureData()
+ {
+ return this.textureData;
+ }
+
+ public void setTextureData(TextureData textureData)
+ {
+ this.textureData = textureData;
+ }
+
+ public Texture getTexture()
+ {
+ return this.texture;
+ }
+
+ public boolean holdsTexture()
+ {
+ return this.getTexture() != null || this.getTextureData() != null;
+ }
+
+ public void setTexture(Texture texture)
+ {
+ if (texture == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.TextureIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.texture = texture;
+ this.textureData = null; // no more need for texture data; allow garbage collector to reclaim it
+ }
+
+ public Point getCentroidPoint(Globe globe)
+ {
+ if (globe == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.GlobeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (this.centroid == null)
+ {
+ gov.nasa.worldwind.geom.LatLon c = this.getSector().getCentroid();
+ this.centroid = globe.computePointFromPosition(c.getLatitude(), c.getLongitude(), 0);
+ }
+
+ return this.centroid;
+ }
+
+ public Point[] getCornerPoints(Globe globe)
+ {
+ if (globe == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.GlobeIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (this.corners == null)
+ {
+ Sector s = this.getSector();
+ this.corners = new gov.nasa.worldwind.geom.Point[4];
+ this.corners[0] = globe.computePointFromPosition(s.getMinLatitude(), s.getMinLongitude(), 0); // sw
+ this.corners[1] = globe.computePointFromPosition(s.getMinLatitude(), s.getMaxLongitude(), 0); // se
+ this.corners[2] = globe.computePointFromPosition(s.getMaxLatitude(), s.getMaxLongitude(), 0); // nw
+ this.corners[3] = globe.computePointFromPosition(s.getMaxLatitude(), s.getMinLongitude(), 0); // ne
+ }
+
+ return this.corners;
+ }
+
+ public double getMinDistanceToEye()
+ {
+ return this.minDistanceToEye;
+ }
+
+ public void setMinDistanceToEye(double minDistanceToEye)
+ {
+ if (minDistanceToEye < 0)
+ {
+ String msg = WorldWind.retrieveErrMsg("layers.TextureTile.MinDistanceToEyeNegative");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ this.minDistanceToEye = minDistanceToEye;
+ }
+
+ public Extent getExtent(DrawContext dc)
+ {
+ if (dc == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ if (this.extent == null || this.extentVerticalExaggertion != dc.getVerticalExaggeration())
+ {
+ this.extent = Sector.computeBoundingCylinder(dc.getGlobe(), dc.getVerticalExaggeration(), this.getSector());
+ this.extentVerticalExaggertion = dc.getVerticalExaggeration();
+ }
+
+ return this.extent;
+ }
+
+ public TextureTile[] createSubTiles(Level nextLevel)
+ {
+ if (nextLevel == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.LevelIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ Angle p0 = this.getSector().getMinLatitude();
+ Angle p2 = this.getSector().getMaxLatitude();
+ Angle p1 = Angle.midAngle(p0, p2);
+
+ Angle t0 = this.getSector().getMinLongitude();
+ Angle t2 = this.getSector().getMaxLongitude();
+ Angle t1 = Angle.midAngle(t0, t2);
+
+ String nextLevelCacheName = nextLevel.getCacheName();
+ int nextLevelNum = nextLevel.getLevelNumber();
+ int row = this.getRow();
+ int col = this.getColumn();
+
+ TextureTile[] subTiles = new TextureTile[4];
+
+ TileKey key = new TileKey(nextLevelNum, 2 * row, 2 * col, nextLevelCacheName);
+ TextureTile subTile = this.getTileFromMemoryCache(key);
+ if (subTile != null)
+ subTiles[0] = subTile;
+ else
+ subTiles[0] = new TextureTile(new Sector(p0, p1, t0, t1), nextLevel, 2 * row, 2 * col);
+
+ key = new TileKey(nextLevelNum, 2 * row, 2 * col + 1, nextLevelCacheName);
+ subTile = this.getTileFromMemoryCache(key);
+ if (subTile != null)
+ subTiles[1] = subTile;
+ else
+ subTiles[1] = new TextureTile(new Sector(p0, p1, t1, t2), nextLevel, 2 * row, 2 * col + 1);
+
+ key = new TileKey(nextLevelNum, 2 * row + 1, 2 * col, nextLevelCacheName);
+ subTile = this.getTileFromMemoryCache(key);
+ if (subTile != null)
+ subTiles[2] = subTile;
+ else
+ subTiles[2] = new TextureTile(new Sector(p1, p2, t0, t1), nextLevel, 2 * row + 1, 2 * col);
+
+ key = new TileKey(nextLevelNum, 2 * row + 1, 2 * col + 1, nextLevelCacheName);
+ subTile = this.getTileFromMemoryCache(key);
+ if (subTile != null)
+ subTiles[3] = subTile;
+ else
+ subTiles[3] = new TextureTile(new Sector(p1, p2, t1, t2), nextLevel, 2 * row + 1, 2 * col + 1);
+
+ return subTiles;
+ }
+
+ public void initializeTexture(DrawContext dc)
+ {
+ if (this.getTexture() == null)
+ {
+ int filter = GL.GL_LINEAR;
+// int filter = GL.GL_LINEAR_MIPMAP_LINEAR;
+
+ this.setTexture(TextureIO.newTexture(this.getTextureData()));
+ this.getTexture().bind();
+ GL gl = dc.getGL();
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, filter);
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, filter);
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);
+ }
+ }
+
+ public boolean bindTexture(DrawContext dc)
+ {
+ if (this.holdsTexture()) // use the tile's texture
+ {
+ if (this.getTexture() == null)
+ this.initializeTexture(dc);
+
+ if (this.getTexture() == null)
+ return false; // bad texture or something, skip it
+
+ this.getTexture().bind();
+ }
+ else if (this.getFallbackTile() != null) // use texture of resource tile
+ {
+ TextureTile resourceTile = this.getFallbackTile();
+ if (resourceTile.getTexture() == null)
+ resourceTile.initializeTexture(dc);
+
+ if (resourceTile.getTexture() == null)
+ return false; // bad texture or something, skip it
+
+ resourceTile.getTexture().bind();
+ }
+
+ return true;
+ }
+
+ public void applyTextureTransform(DrawContext dc)
+ {
+ GL gl = GLContext.getCurrent().getGL();
+
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glLoadIdentity();
+
+ if (this.holdsTexture()) // use the tile's texture
+ {
+ if (this.getTexture() == null)
+ this.initializeTexture(dc);
+
+ if (this.getTexture() == null)
+ return; // bad texture or something, skip it
+
+ if (this.getTexture().getMustFlipVertically())
+ {
+ gl.glScaled(1, -1, 1);
+ gl.glTranslated(0, -1, 0);
+ }
+
+// this.getTexture().bind();
+ }
+ else if (this.getFallbackTile() != null) // use texture of resource tile
+ {
+ TextureTile resourceTile = this.getFallbackTile();
+ if (resourceTile.getTexture() == null)
+ resourceTile.initializeTexture(dc);
+
+ if (resourceTile.getTexture() == null)
+ return; // bad texture or something, skip it
+
+ if (resourceTile.getTexture().getMustFlipVertically())
+ {
+ gl.glScaled(1, -1, 1);
+ gl.glTranslated(0, -1, 0);
+ }
+
+ this.applyResourceTextureTransform(dc);
+// resourceTile.getTexture().bind();
+ }
+ }
+
+ private void applyResourceTextureTransform(DrawContext dc)
+ {
+ if (this.getLevel() == null)
+ return;
+
+ int levelDelta = this.getLevelNumber() - this.getFallbackTile().getLevelNumber();
+ if (levelDelta <= 0)
+ return;
+
+ double twoToTheN = Math.pow(2, levelDelta);
+ double oneOverTwoToTheN = 1 / twoToTheN;
+
+ double sShift = oneOverTwoToTheN * (this.getColumn() % twoToTheN);
+ double tShift = oneOverTwoToTheN * (this.getRow() % twoToTheN);
+
+ dc.getGL().glTranslated(sShift, tShift, 0);
+ dc.getGL().glScaled(oneOverTwoToTheN, oneOverTwoToTheN, 1);
+ }
+
+ private TextureTile getTileFromMemoryCache(TileKey tileKey)
+ {
+ return (TextureTile) WorldWind.memoryCache().getObject(tileKey);
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final TextureTile tile = (TextureTile) o;
+
+ return !(this.getTileKey() != null ? !this.getTileKey().equals(tile.getTileKey()) : tile.getTileKey() != null);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return (this.getTileKey() != null ? this.getTileKey().hashCode() : 0);
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.getSector().toString();
+ }
+}
diff --git a/gov/nasa/worldwind/layers/TiledImageLayer.java b/gov/nasa/worldwind/layers/TiledImageLayer.java
new file mode 100644
index 0000000..9c68e81
--- /dev/null
+++ b/gov/nasa/worldwind/layers/TiledImageLayer.java
@@ -0,0 +1,1394 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers;
+
+import com.sun.opengl.util.j2d.*;
+import com.sun.opengl.util.texture.*;
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+import gov.nasa.worldwind.geom.Point;
+
+import javax.imageio.*;
+import javax.media.opengl.*;
+import java.awt.*;
+import java.awt.image.*;
+import java.io.*;
+import java.net.*;
+import java.nio.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+/**
+ * @author tag
+ * @version $Id: TiledImageLayer.java 1774 2007-05-08 01:03:37Z dcollins $
+ */
+public class TiledImageLayer extends AbstractLayer
+{
+ // Infrastructure
+ private static final LevelComparer levelComparer = new LevelComparer();
+ private final LevelSet levels;
+ private ArrayList topLevels;
+ private final Object fileLock = new Object();
+ private boolean forceLevelZeroLoads = false;
+ private boolean retainLevelZeroTiles = false;
+
+ // Diagnostic flags
+ private boolean showImageTileOutlines = false;
+ private boolean drawTileBoundaries = false;
+ private boolean drawWireframe = false;
+ private boolean useTransparentTextures = false;
+ private boolean drawTileIDs = false;
+ private boolean drawBoundingVolumes = false;
+ private TextRenderer textRenderer = null;
+
+ // Stuff computed each frame
+ private ArrayList currentTiles = new ArrayList();
+ private TextureTile currentResourceTile;
+ private Point referencePoint;
+ private PriorityBlockingQueue requestQ = new PriorityBlockingQueue(200);
+
+ public TiledImageLayer(LevelSet levelSet)
+ {
+ if (levelSet == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.LevelSetIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.levels = new LevelSet(levelSet); // the caller's levelSet may change internally, so we copy it.
+
+ this.createTopLevelTiles();
+ if (this.forceLevelZeroLoads)
+ this.loadAllTopLevelTextures();
+
+ this.setPickEnabled(false); // textures are assumed to be terrain unless specifically indicated otherwise.
+ }
+
+ @Override
+ public void dispose()
+ {
+ if (!this.retainLevelZeroTiles)
+ return;
+
+ for (TextureTile tile : this.topLevels)
+ {
+ tile.dispose();
+ }
+ }
+
+ public boolean isUseTransparentTextures()
+ {
+ return this.useTransparentTextures;
+ }
+
+ public void setUseTransparentTextures(boolean useTransparentTextures)
+ {
+ this.useTransparentTextures = useTransparentTextures;
+ }
+
+ public boolean isForceLevelZeroLoads()
+ {
+ return this.forceLevelZeroLoads;
+ }
+
+ public void setForceLevelZeroLoads(boolean forceLevelZeroLoads)
+ {
+ this.forceLevelZeroLoads = forceLevelZeroLoads;
+ if (this.forceLevelZeroLoads)
+ this.loadAllTopLevelTextures();
+ }
+
+ public boolean isRetainLevelZeroTiles()
+ {
+ return retainLevelZeroTiles;
+ }
+
+ public void setRetainLevelZeroTiles(boolean retainLevelZeroTiles)
+ {
+ this.retainLevelZeroTiles = retainLevelZeroTiles;
+ }
+
+ public boolean isDrawTileIDs()
+ {
+ return drawTileIDs;
+ }
+
+ public void setDrawTileIDs(boolean drawTileIDs)
+ {
+ this.drawTileIDs = drawTileIDs;
+ }
+
+ public boolean isDrawTileBoundaries()
+ {
+ return drawTileBoundaries;
+ }
+
+ public void setDrawTileBoundaries(boolean drawTileBoundaries)
+ {
+ this.drawTileBoundaries = drawTileBoundaries;
+ }
+
+ public boolean isDrawWireframe()
+ {
+ return drawWireframe;
+ }
+
+ public void setDrawWireframe(boolean drawWireframe)
+ {
+ this.drawWireframe = drawWireframe;
+ }
+
+ public boolean isShowImageTileOutlines()
+ {
+ return showImageTileOutlines;
+ }
+
+ public void setShowImageTileOutlines(boolean showImageTileOutlines)
+ {
+ this.showImageTileOutlines = showImageTileOutlines;
+ }
+
+ public boolean isDrawBoundingVolumes()
+ {
+ return drawBoundingVolumes;
+ }
+
+ public void setDrawBoundingVolumes(boolean drawBoundingVolumes)
+ {
+ this.drawBoundingVolumes = drawBoundingVolumes;
+ }
+
+ private void createTopLevelTiles()
+ {
+ Sector sector = this.levels.getSector();
+
+ Angle dLat = this.levels.getLevelZeroTileDelta().getLatitude();
+ Angle dLon = this.levels.getLevelZeroTileDelta().getLongitude();
+
+ // Determine the row and column offset from the common World Wind global tiling origin.
+ Level level = levels.getFirstLevel();
+ int firstRow = Tile.computeRow(level.getTileDelta().getLatitude(), sector.getMinLatitude());
+ int firstCol = Tile.computeColumn(level.getTileDelta().getLongitude(), sector.getMinLongitude());
+ int lastRow = Tile.computeRow(level.getTileDelta().getLatitude(), sector.getMaxLatitude());
+ int lastCol = Tile.computeColumn(level.getTileDelta().getLongitude(), sector.getMaxLongitude());
+
+ int nLatTiles = lastRow - firstRow + 1;
+ int nLonTiles = lastCol - firstCol + 1;
+
+ this.topLevels = new ArrayList(nLatTiles * nLonTiles);
+
+ Angle p1 = Tile.computeRowLatitude(firstRow, dLat);
+ for (int row = firstRow; row <= lastRow; row++)
+ {
+ Angle p2;
+ p2 = p1.add(dLat);
+
+ Angle t1 = Tile.computeColumnLongitude(firstCol, dLon);
+ for (int col = firstCol; col <= lastCol; col++)
+ {
+ Angle t2;
+ t2 = t1.add(dLon);
+
+ this.topLevels.add(new TextureTile(new Sector(p1, p2, t1, t2), level, row, col));
+ t1 = t2;
+ }
+ p1 = p2;
+ }
+ }
+
+ private void loadAllTopLevelTextures()
+ {
+ for (TextureTile tile : this.topLevels)
+ {
+ if (!tile.holdsTexture())
+ this.forceTextureLoad(tile);
+ }
+ }
+
+ // ============== Tile Assembly ======================= //
+ // ============== Tile Assembly ======================= //
+ // ============== Tile Assembly ======================= //
+
+ private void assembleTiles(DrawContext dc)
+ {
+ this.currentTiles.clear();
+// this.currentSpan = null;
+
+ for (TextureTile tile : this.topLevels)
+ {
+ if (this.isTileVisible(dc, tile))
+ {
+ this.currentResourceTile = null;
+ this.addTileOrDescendants(dc, tile);
+ }
+ }
+ }
+
+ private void addTileOrDescendants(DrawContext dc, TextureTile tile)
+ {
+ if (this.meetsRenderCriteria(dc, tile))
+ {
+ this.addTile(dc, tile);
+ return;
+ }
+
+ // The incoming tile does not meet the rendering criteria, so it must be subdivided and those
+ // subdivisions tested against the criteria.
+
+ // All tiles that meet the selection criteria are drawn, but some of those tiles will not have
+ // textures associated with them either because their texture isn't loaded yet or because they
+ // are finer grain than the layer has textures for. In these cases the tiles use the texture of
+ // the closest ancestor that has a texture loaded. This ancestor is called the currentResourceTile.
+ // A texture transform is applied during rendering to align the sector's texture coordinates with the
+ // appropriate region of the ancestor's texture.
+
+ TextureTile ancestorResource = null;
+
+ try
+ {
+ if (tile.holdsTexture() || tile.getLevelNumber() == 0)
+ {
+ ancestorResource = this.currentResourceTile;
+ this.currentResourceTile = tile;
+ }
+
+ // Ensure that levels finer than the finest image have the finest image around
+ // TODO: find finest level with a non-missing tile
+ if (this.levels.isFinalLevel(tile.getLevelNumber()) && !this.isTextureInMemory(tile))
+ this.requestTexture(dc, tile);
+
+ TextureTile[] subTiles = tile.createSubTiles(this.levels.getLevel(tile.getLevelNumber() + 1));
+ for (TextureTile child : subTiles)
+ {
+ if (this.isTileVisible(dc, child))
+ this.addTileOrDescendants(dc, child);
+ }
+ }
+ finally
+ {
+ if (ancestorResource != null) // Pop this tile as the currentResource ancestor
+ this.currentResourceTile = ancestorResource;
+ }
+ }
+
+ private void addTile(DrawContext dc, TextureTile tile)
+ {
+ tile.setFallbackTile(null);
+
+ if (tile.holdsTexture())
+ {
+ this.addTileToCurrent(tile);
+ return;
+ }
+
+ // Level 0 loads may be forced
+ if (tile.getLevelNumber() == 0 && this.forceLevelZeroLoads)
+ {
+ this.forceTextureLoad(tile);
+ if (tile.holdsTexture())
+ {
+ this.addTileToCurrent(tile);
+ return;
+ }
+ }
+
+ // Tile's texture isn't available, so request it
+ if (tile.getLevelNumber() < this.levels.getNumLevels())
+ {
+ // Request only tiles with data associated at this level
+ if (!this.levels.isResourceAbsent(tile))
+ this.requestTexture(dc, tile);
+ }
+
+ // Set up to use the currentResource tile's texture
+ if (this.currentResourceTile != null)
+ {
+ if (this.currentResourceTile.getLevelNumber() == 0 && this.forceLevelZeroLoads
+ && !this.currentResourceTile.holdsTexture())
+ this.forceTextureLoad(this.currentResourceTile);
+
+ if (this.currentResourceTile.holdsTexture())
+ {
+ tile.setFallbackTile(currentResourceTile);
+ this.addTileToCurrent(tile);
+ }
+ }
+ }
+
+ private void addTileToCurrent(TextureTile tile)
+ {
+ this.currentTiles.add(tile);
+ }
+
+ private boolean isTileVisible(DrawContext dc, TextureTile tile)
+ {
+ return tile.getExtent(dc).intersects(dc.getView().getFrustumInModelCoordinates())
+ && (dc.getVisibleSector() == null || dc.getVisibleSector().intersects(tile.getSector()));
+ }
+
+ private boolean meetsRenderCriteria(DrawContext dc, TextureTile tile)
+ {
+ return this.levels.isFinalLevel(tile.getLevelNumber()) || !needToSplit(dc, tile.getSector(), 20);
+ }
+
+ private static boolean needToSplit(DrawContext dc, Sector sector, int density)
+ {
+ Point[] corners = sector.computeCornerPoints(dc.getGlobe());
+ Point centerPoint = sector.computeCenterPoint(dc.getGlobe());
+
+ View view = dc.getView();
+ double d1 = view.getEyePoint().distanceTo(corners[0]);
+ double d2 = view.getEyePoint().distanceTo(corners[1]);
+ double d3 = view.getEyePoint().distanceTo(corners[2]);
+ double d4 = view.getEyePoint().distanceTo(corners[3]);
+ double d5 = view.getEyePoint().distanceTo(centerPoint);
+
+ double minDistance = d1;
+ if (d2 < minDistance)
+ minDistance = d2;
+ if (d3 < minDistance)
+ minDistance = d3;
+ if (d4 < minDistance)
+ minDistance = d4;
+ if (d5 < minDistance)
+ minDistance = d5;
+
+ double cellSize = (Math.PI * sector.getDeltaLatRadians() * dc.getGlobe().getRadius()) / density;
+
+ return !(Math.log10(cellSize) <= (Math.log10(minDistance) - 1));
+ }
+
+ // ============== Rendering ======================= //
+ // ============== Rendering ======================= //
+ // ============== Rendering ======================= //
+
+ @Override
+ protected final void doRender(DrawContext dc)
+ {
+ if (dc.getSurfaceGeometry() == null || dc.getSurfaceGeometry().size() < 1)
+ return; // TODO: throw an illegal state exception?
+
+ dc.getSurfaceTileRenderer().setShowImageTileOutlines(this.showImageTileOutlines);
+
+ draw(dc);
+ }
+
+ private void draw(DrawContext dc)
+ {
+ TextureTile.disposeTextures(); // Clean up any unused textures while within a OpenGl context thread.
+
+ if (!this.isEnabled())
+ return; // Don't check for arg errors if we're disabled
+
+ if (!this.isLayerActive(dc))
+ return;
+
+ if (!this.isLayerInView(dc))
+ return;
+
+ this.referencePoint = this.computeReferencePoint(dc);
+
+ this.assembleTiles(dc); // Determine the tiles to draw.
+
+ if (this.currentTiles.size() >= 1)
+ {
+ TextureTile[] sortedTiles = new TextureTile[this.currentTiles.size()];
+ sortedTiles = this.currentTiles.toArray(sortedTiles);
+ Arrays.sort(sortedTiles, levelComparer);
+
+ GL gl = dc.getGL();
+
+ gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_POLYGON_BIT);
+
+ if (this.isUseTransparentTextures())
+ {
+ gl.glEnable(GL.GL_BLEND);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+ }
+
+ gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
+ gl.glEnable(GL.GL_CULL_FACE);
+ gl.glCullFace(GL.GL_BACK);
+
+// System.out.println(this.getName() + " " + this.currentTiles.size()); // **************
+ dc.getSurfaceTileRenderer().renderTiles(dc, this.currentTiles);
+
+ gl.glPopAttrib();
+
+ if (this.drawTileIDs)
+ this.drawTileIDs(dc, this.currentTiles);
+
+ if (this.drawBoundingVolumes)
+ this.drawBoundingVolumes(dc, this.currentTiles);
+
+ this.currentTiles.clear();
+ }
+
+ this.sendRequests();
+ this.requestQ.clear();
+ }
+
+ private void sendRequests()
+ {
+ RequestTask task = this.requestQ.poll();
+ while (task != null)
+ {
+ if (!WorldWind.threadedTaskService().isFull())
+ {
+ WorldWind.threadedTaskService().addTask(task);
+ }
+ task = this.requestQ.poll();
+ }
+ }
+
+ public boolean isLayerInView(DrawContext dc)
+ {
+ if (dc == null)
+ {
+ String message = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ if (dc.getView() == null)
+ {
+ String message = WorldWind.retrieveErrMsg("layers.AbstractLayer.NoViewSpecifiedInDrawingContext");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ throw new IllegalStateException(message);
+ }
+
+ if (dc.getVisibleSector() != null && !this.levels.getSector().intersects(dc.getVisibleSector()))
+ return false;
+
+ Extent e = Sector.computeBoundingCylinder(dc.getGlobe(), dc.getVerticalExaggeration(), this.levels.getSector());
+ return e.intersects(dc.getView().getFrustumInModelCoordinates());
+ }
+
+ private Point computeReferencePoint(DrawContext dc)
+ {
+ java.awt.geom.Rectangle2D viewport = dc.getView().getViewport();
+ int x = (int) viewport.getWidth() / 2;
+ for (int y = (int) (0.75 * viewport.getHeight()); y >= 0; y--)
+ {
+ Position pos = dc.getView().computePositionFromScreenPoint(x, y);
+ if (pos == null)
+ continue;
+
+ return dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), 0d);
+ }
+
+ return null;
+ }
+
+ private static class LevelComparer implements Comparator
+ {
+ public int compare(TextureTile ta, TextureTile tb)
+ {
+ int la;
+ int lb;
+
+ if (ta.holdsTexture())
+ la = ta.getLevelNumber();
+ else
+ la = ta.getFallbackTile().getLevelNumber();
+
+ if (tb.holdsTexture())
+ lb = tb.getLevelNumber();
+ else
+ lb = tb.getFallbackTile().getLevelNumber();
+
+ return la < lb ? -1 : la == lb ? 0 : 1;
+ }
+ }
+
+ private void drawTileIDs(DrawContext dc, ArrayList tiles)
+ {
+ java.awt.Rectangle viewport = dc.getView().getViewport();
+ if (this.textRenderer == null)
+ this.textRenderer = new TextRenderer(java.awt.Font.decode("Arial-Plain-13"), true, true);
+
+ dc.getGL().glDisable(GL.GL_DEPTH_TEST);
+ dc.getGL().glDisable(GL.GL_BLEND);
+ dc.getGL().glDisable(GL.GL_TEXTURE_2D);
+
+ this.textRenderer.setColor(java.awt.Color.YELLOW);
+ this.textRenderer.beginRendering(viewport.width, viewport.height);
+ for (TextureTile tile : tiles)
+ {
+ String tileLabel = tile.getLabel();
+
+ if (tile.getFallbackTile() != null)
+ tileLabel += "/" + tile.getFallbackTile().getLabel();
+
+ LatLon ll = tile.getSector().getCentroid();
+ Point pt = dc.getGlobe().computePointFromPosition(ll.getLatitude(), ll.getLongitude(),
+ dc.getGlobe().getElevation(ll.getLatitude(), ll.getLongitude()));
+ pt = dc.getView().project(pt);
+ this.textRenderer.draw(tileLabel, (int) pt.x(), (int) pt.y());
+ }
+ this.textRenderer.endRendering();
+ }
+
+ private void drawBoundingVolumes(DrawContext dc, ArrayList tiles)
+ {
+ float[] previousColor = new float[4];
+ dc.getGL().glGetFloatv(GL.GL_CURRENT_COLOR, previousColor, 0);
+ dc.getGL().glColor3d(0, 1, 0);
+
+ for (TextureTile tile : tiles)
+ {
+ ((Cylinder) tile.getExtent(dc)).render(dc);
+ }
+
+ dc.getGL().glColor4fv(previousColor, 0);
+ }
+
+ // ============== Image Reading and Downloading ======================= //
+ // ============== Image Reading and Downloading ======================= //
+ // ============== Image Reading and Downloading ======================= //
+
+ private void requestTexture(DrawContext dc, TextureTile tile)
+ {
+ Point centroid = tile.getCentroidPoint(dc.getGlobe());
+ if (this.referencePoint != null)
+ tile.setPriority(centroid.distanceTo(this.referencePoint));
+
+ RequestTask task = new RequestTask(tile, this);
+ this.requestQ.add(task);
+ }
+
+ private void forceTextureLoad(TextureTile tile)
+ {
+ final java.net.URL textureURL = WorldWind.dataFileCache().findFile(tile.getPath(), true);
+
+ if (textureURL != null && WWIO.isFileOutOfDate(textureURL, tile.getLevel().getExpiryTime()))
+ {
+ // The file has expired. Delete it.
+ gov.nasa.worldwind.WorldWind.dataFileCache().removeFile(textureURL);
+ String message = WorldWind.retrieveErrMsg("generic.DataFileExpired") + textureURL;
+ WorldWind.logger().log(java.util.logging.Level.FINER, message);
+ }
+ else if (textureURL != null)
+ {
+ this.loadTexture(tile, textureURL);
+ }
+ }
+
+ private boolean loadTexture(TextureTile tile, java.net.URL textureURL)
+ {
+ TextureData textureData;
+
+ synchronized (this.fileLock)
+ {
+ textureData = readTexture(textureURL);
+ }
+
+ if (textureData == null)
+ return false;
+
+ tile.setTextureData(textureData);
+ if (tile.getLevelNumber() != 0 || !this.retainLevelZeroTiles)
+ this.addTileToCache(tile);
+
+ return true;
+ }
+
+ private static TextureData readTexture(java.net.URL url)
+ {
+ try
+ {
+ return TextureIO.newTextureData(url, false, null);
+ }
+ catch (Exception e)
+ {
+ String message = WorldWind.retrieveErrMsg("layers.TextureLayer.ExceptionAttemptingToReadTextureFile");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message + url, e);
+ return null;
+ }
+ }
+
+ private void addTileToCache(TextureTile tile)
+ {
+ WorldWind.memoryCache().add(tile.getTileKey(), tile);
+ }
+
+ private boolean isTextureInMemory(TextureTile tile)
+ {
+ return ((tile.getLevelNumber() == 0 && tile.holdsTexture())
+ || WorldWind.memoryCache().getObject(tile.getTileKey()) != null);
+ }
+
+ private void downloadTexture(final TextureTile tile)
+ {
+ if (WorldWind.retrievalService().isFull())
+ return;
+
+ java.net.URL url;
+ try
+ {
+ url = tile.getResourceURL();
+ }
+ catch (java.net.MalformedURLException e)
+ {
+ String message = WorldWind.retrieveErrMsg("layers.TextureLayer.ExceptionCreatingTextureUrl");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message + tile, e);
+ return;
+ }
+
+ URLRetriever retriever = new HTTPRetriever(url, new DownloadPostProcessor(tile, this));
+ WorldWind.retrievalService().runRetriever(retriever, tile.getPriority());
+ }
+
+ private void saveBuffer(java.nio.ByteBuffer buffer, java.io.File outFile) throws java.io.IOException
+ {
+ synchronized (this.fileLock) // sychronized with read of file in RequestTask.run()
+ {
+ gov.nasa.worldwind.WWIO.saveBuffer(buffer, outFile);
+ }
+ }
+
+ // ============== Inner classes ======================= //
+ // ============== Inner classes ======================= //
+ // ============== Inner classes ======================= //
+
+ private static class RequestTask implements Runnable, Comparable
+ {
+ private final TiledImageLayer layer;
+ private final TextureTile tile;
+
+ private RequestTask(TextureTile tile, TiledImageLayer layer)
+ {
+ this.layer = layer;
+ this.tile = tile;
+ }
+
+ public void run()
+ {
+ // check to ensure load is still needed
+ if (this.layer.isTextureInMemory(this.tile))
+ return;
+
+ final java.net.URL textureURL = WorldWind.dataFileCache().findFile(tile.getPath(), false);
+ if (textureURL != null)
+ {
+ if (WWIO.isFileOutOfDate(textureURL, this.tile.getLevel().getExpiryTime()))
+ {
+ // The file has expired. Delete it then request download of newer.
+ gov.nasa.worldwind.WorldWind.dataFileCache().removeFile(textureURL);
+ String message = WorldWind.retrieveErrMsg("generic.DataFileExpired") + textureURL;
+ WorldWind.logger().log(java.util.logging.Level.FINER, message);
+ }
+ else if (this.layer.loadTexture(tile, textureURL))
+ {
+ layer.levels.unmarkResourceAbsent(tile);
+ this.layer.firePropertyChange(gov.nasa.worldwind.AVKey.LAYER, null, this);
+ return;
+ }
+ else
+ {
+ // Assume that something's wrong with the file and delete it.
+ gov.nasa.worldwind.WorldWind.dataFileCache().removeFile(textureURL);
+ layer.levels.markResourceAbsent(tile);
+ String message = WorldWind.retrieveErrMsg("generic.DeletedCorruptDataFile") + textureURL;
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ }
+ }
+
+ this.layer.downloadTexture(this.tile);
+ }
+
+ /**
+ * @param that the task to compare
+ * @return -1 if this
less than that
, 1 if greater than, 0 if equal
+ * @throws IllegalArgumentException if that
is null
+ */
+ public int compareTo(RequestTask that)
+ {
+ if (that == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.RequestTaskIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ return this.tile.getPriority() == that.tile.getPriority() ? 0 :
+ this.tile.getPriority() < that.tile.getPriority() ? -1 : 1;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ final RequestTask that = (RequestTask) o;
+
+ // Don't include layer in comparison so that requests are shared among layers
+ return !(tile != null ? !tile.equals(that.tile) : that.tile != null);
+ }
+
+ public int hashCode()
+ {
+ return (tile != null ? tile.hashCode() : 0);
+ }
+
+ public String toString()
+ {
+ return this.tile.toString();
+ }
+ }
+
+ private static class DownloadPostProcessor implements RetrievalPostProcessor
+ {
+ // TODO: Rewrite this inner class, factoring out the generic parts.
+ private final TextureTile tile;
+ private final TiledImageLayer layer;
+
+ public DownloadPostProcessor(TextureTile tile, TiledImageLayer layer)
+ {
+ this.tile = tile;
+ this.layer = layer;
+ }
+
+ public ByteBuffer run(Retriever retriever)
+ {
+ if (retriever == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.RetrieverIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ try
+ {
+ if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL))
+ return null;
+
+ URLRetriever r = (URLRetriever) retriever;
+ ByteBuffer buffer = r.getBuffer();
+
+ if (retriever instanceof HTTPRetriever)
+ {
+ HTTPRetriever htr = (HTTPRetriever) retriever;
+ if (htr.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT)
+ {
+ // Mark tile as missing to avoid excessive attempts
+ this.layer.levels.markResourceAbsent(this.tile);
+ return null;
+ }
+ else if (htr.getResponseCode() != HttpURLConnection.HTTP_OK)
+ {
+ // Also mark tile as missing, but for an unknown reason.
+ this.layer.levels.markResourceAbsent(this.tile);
+ return null;
+ }
+ }
+
+ final File outFile = WorldWind.dataFileCache().newFile(this.tile.getPath());
+ if (outFile == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.CantCreateCacheFile") + this.tile.getPath();
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ return null;
+ }
+
+ if (outFile.exists())
+ return buffer;
+
+ // TODO: Better, more generic and flexible handling of file-format type
+ if (buffer != null)
+ {
+ String contentType = r.getContentType().toLowerCase();
+ if (contentType.contains("xml") || contentType.contains("html") || contentType.contains("text"))
+ {
+ this.layer.levels.markResourceAbsent(this.tile);
+
+ StringBuffer sb = new StringBuffer();
+ while (buffer.hasRemaining())
+ {
+ sb.append((char) buffer.get());
+ }
+ // TODO: parse out the message if the content is xml or html.
+ WorldWind.logger().log(java.util.logging.Level.FINE, sb.toString());
+
+ return null;
+ }
+ else if (contentType.contains("dds"))
+ {
+ this.layer.saveBuffer(buffer, outFile);
+ }
+ else if (contentType.contains("zip"))
+ {
+ // Assume it's zipped DDS, which the retriever would have unzipped into the buffer.
+ this.layer.saveBuffer(buffer, outFile);
+ }
+ else if (outFile.getName().endsWith(".dds"))
+ {
+ // Convert to DDS
+ if (this.layer.isUseTransparentTextures())
+ {
+ buffer = DDSConverter.convertToDxt3(buffer, contentType);
+ }
+ else
+ {
+ buffer = DDSConverter.convertToDxt1NoTransparency(buffer,
+ contentType);
+ }
+
+ if (buffer != null)
+ this.layer.saveBuffer(buffer, outFile);
+ }
+ else if (contentType.contains("image"))
+ {
+ // Just save whatever it is to the cache.
+ this.layer.saveBuffer(buffer, outFile);
+ }
+
+ if (buffer != null)
+ {
+ this.layer.firePropertyChange(AVKey.LAYER, null, this);
+ }
+ return buffer;
+ }
+ }
+ catch (java.io.IOException e)
+ {
+ this.layer.levels.markResourceAbsent(this.tile);
+ String message = WorldWind.retrieveErrMsg("layers.TextureLayer.ExceptionSavingRetrievedTextureFile");
+ WorldWind.logger().log(java.util.logging.Level.FINE, message + tile.getPath(), e);
+ }
+ return null;
+ }
+ }
+
+ public Color getColor(Angle latitude, Angle longitude, int levelNumber)
+ {
+ // TODO: check args
+
+ // Find the tile containing the position in the specified level.
+ TextureTile containingTile = null;
+ for (TextureTile tile : this.topLevels)
+ {
+ containingTile = this.getContainingTile(tile, latitude, longitude, levelNumber);
+ if (containingTile != null)
+ break;
+ }
+ if (containingTile == null)
+ return null;
+
+ String pathBase = containingTile.getPath().substring(0, containingTile.getPath().lastIndexOf("."));
+ String cacheKey = pathBase + ".BufferedImage";
+
+ // Look up the color if the image is in memory.
+ BufferedImage image = (BufferedImage) WorldWind.memoryCache().getObject(cacheKey);
+ if (image != null)
+ return this.resolveColor(containingTile, image, latitude, longitude);
+
+ // Read the image from disk since it's not in memory.
+ image = this.requestImage(containingTile, cacheKey);
+ if (image != null)
+ return this.resolveColor(containingTile, image, latitude, longitude);
+
+ // Retrieve it from the net since it's not on disk.
+ this.downloadImage(containingTile);
+
+ // Try to read from disk again after retrieving it from the net.
+ image = this.requestImage(containingTile, cacheKey);
+ if (image != null)
+ return this.resolveColor(containingTile, image, latitude, longitude);
+
+ // All attempts to find the image have failed.
+ return null;
+ }
+
+ private final static String[] formats = new String[] {"jpg", "jpeg", "png", "tiff"};
+ private final static String[] suffixes = new String[] {".jpg", ".jpg", ".png", ".tiff"};
+
+ private BufferedImage requestImage(TextureTile tile, String cacheKey)
+ {
+ URL url = null;
+ String pathBase = tile.getPath().substring(0, tile.getPath().lastIndexOf("."));
+ for (String suffix : suffixes)
+ {
+ String path = pathBase + suffix;
+ url = WorldWind.dataFileCache().findFile(path, false);
+ if (url != null)
+ break;
+ }
+
+ if (url == null)
+ return null;
+
+ if (WWIO.isFileOutOfDate(url, tile.getLevel().getExpiryTime()))
+ {
+ // The file has expired. Delete it then request download of newer.
+ gov.nasa.worldwind.WorldWind.dataFileCache().removeFile(url);
+ String message = WorldWind.retrieveErrMsg("generic.DataFileExpired") + url;
+ WorldWind.logger().log(java.util.logging.Level.FINER, message);
+ }
+ else
+ {
+ try
+ {
+ BufferedImage image = ImageIO.read(new File(url.toURI()));
+ if (image == null)
+ {
+ return null; // TODO: warn
+ }
+
+ WorldWind.memoryCache().add(cacheKey, image, image.getRaster().getDataBuffer().getSize());
+ this.levels.unmarkResourceAbsent(tile);
+ return image;
+ }
+ catch (IOException e)
+ {
+ // Assume that something's wrong with the file and delete it.
+ gov.nasa.worldwind.WorldWind.dataFileCache().removeFile(url);
+ this.levels.markResourceAbsent(tile);
+ String message = WorldWind.retrieveErrMsg("generic.DeletedCorruptDataFile") + url;
+ WorldWind.logger().log(java.util.logging.Level.FINE, message);
+ }
+ catch (URISyntaxException e)
+ {
+ e.printStackTrace(); // TODO
+ }
+ }
+
+ return null;
+ }
+
+ private void downloadImage(final TextureTile tile)
+ {
+ try
+ {
+ String urlString = tile.getResourceURL().toExternalForm().replace("dds", "");
+ final URL resourceURL = new URL(urlString);
+
+ URLRetriever retriever = new HTTPRetriever(resourceURL,
+ new RetrievalPostProcessor()
+ {
+ public ByteBuffer run(Retriever retriever)
+ {
+ if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL))
+ return null;
+
+ HTTPRetriever htr = (HTTPRetriever) retriever;
+ if (htr.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT)
+ {
+ // Mark tile as missing to avoid excessive attempts
+ TiledImageLayer.this.levels.markResourceAbsent(tile);
+ return null;
+ }
+
+ URLRetriever r = (URLRetriever) retriever;
+ ByteBuffer buffer = r.getBuffer();
+
+ String suffix = null;
+ for (int i = 0; i < formats.length; i++)
+ {
+ if (htr.getContentType().toLowerCase().contains(formats[i]))
+ {
+ suffix = suffixes[i];
+ break;
+ }
+ }
+ if (suffix == null)
+ {
+ return null; // TODO: log error
+ }
+
+ String path = tile.getPath().substring(0, tile.getPath().lastIndexOf("."));
+ path += suffix;
+
+ final File outFile = WorldWind.dataFileCache().newFile(path);
+ if (outFile == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("generic.CantCreateCacheFile")
+ + tile.getPath();
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ return null;
+ }
+
+ try
+ {
+ WWIO.saveBuffer(buffer, outFile);
+ return buffer;
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace(); // TODO: log error
+ return null;
+ }
+ }
+ });
+
+ retriever.call();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace(); // TODO
+ }
+ }
+
+ private TextureTile getContainingTile(TextureTile tile, Angle latitude, Angle longitude, int levelNumber)
+ {
+ if (!tile.getSector().contains(latitude, longitude))
+ return null;
+
+ if (tile.getLevelNumber() == levelNumber || this.levels.isFinalLevel(tile.getLevelNumber()))
+ return tile;
+
+ TextureTile containingTile;
+ TextureTile[] subTiles = tile.createSubTiles(this.levels.getLevel(tile.getLevelNumber() + 1));
+ for (TextureTile child : subTiles)
+ {
+ containingTile = this.getContainingTile(child, latitude, longitude, levelNumber);
+ if (containingTile != null)
+ return containingTile;
+ }
+
+ return null;
+ }
+
+ private Color resolveColor(TextureTile tile, BufferedImage image, Angle latitude, Angle longitude)
+ {
+ Sector sector = tile.getSector();
+
+ final double dLat = sector.getMaxLatitude().degrees - latitude.degrees;
+ final double dLon = longitude.degrees - sector.getMinLongitude().degrees;
+ final double sLat = dLat / sector.getDeltaLat().degrees;
+ final double sLon = dLon / sector.getDeltaLon().degrees;
+
+ final int tileHeight = tile.getLevel().getTileHeight();
+ final int tileWidth = tile.getLevel().getTileWidth();
+ int x = (int) ((tileWidth - 1) * sLon);
+ int y = (int) ((tileHeight - 1) * sLat);
+ int w = x < (tileWidth - 1) ? 1 : 0;
+ int h = y < (tileHeight - 1) ? 1 : 0;
+
+ double dh = sector.getDeltaLat().degrees / (tileHeight - 1);
+ double dw = sector.getDeltaLon().degrees / (tileWidth - 1);
+ double ssLat = (dLat - y * dh) / dh;
+ double ssLon = (dLon - x * dw) / dw;
+
+ int sw = image.getRGB(x, y);
+ int se = image.getRGB(x + w, y);
+ int ne = image.getRGB(x + w, y + h);
+ int nw = image.getRGB(x, y + h);
+
+ Color csw = new Color(sw);
+ Color cse = new Color(se);
+ Color cne = new Color(ne);
+ Color cnw = new Color(nw);
+
+ Color ctop = interpolateColors(cnw, cne, ssLon);
+ Color cbot = interpolateColors(csw, cse, ssLon);
+
+ return interpolateColors(cbot, ctop, ssLat);
+ }
+
+ private Color interpolateColors(Color ca, Color cb, double s)
+ {
+ int r = (int) (s * ca.getRed() + (1 - s) * cb.getRed());
+ int g = (int) (s * ca.getGreen() + (1 - s) * cb.getGreen());
+ int b = (int) (s * ca.getBlue() + (1 - s) * cb.getBlue());
+
+ return new Color(r, g, b);
+ }
+
+ @Override
+ public String toString()
+ {
+ return WorldWind.retrieveErrMsg("layers.TextureLayer.Name");
+ }
+}
+//
+// private void renderTiles2(DrawContext dc)
+// {
+// // Render all the tiles collected during assembleTiles()
+// GL gl = dc.getGL();
+//
+// gl.glPushAttrib(
+// GL.GL_COLOR_BUFFER_BIT // for blend func, current color, alpha func, color mask
+// | GL.GL_POLYGON_BIT // for face culling, polygon mode
+// | GL.GL_ENABLE_BIT
+// | GL.GL_CURRENT_BIT
+// | GL.GL_DEPTH_BUFFER_BIT // for depth mask
+// | GL.GL_TEXTURE_BIT // for texture env
+// | GL.GL_TRANSFORM_BIT);
+//
+// try
+// {
+// gl.glEnable(GL.GL_DEPTH_TEST);
+// gl.glDepthFunc(GL.GL_LEQUAL);
+//
+// if (this.useTransparentTextures)
+// {
+// gl.glEnable(GL.GL_BLEND);
+// gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+// }
+//
+// gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
+// gl.glEnable(GL.GL_CULL_FACE);
+// gl.glCullFace(GL.GL_BACK);
+// gl.glColor4d(0, 0, 0, 0);
+//
+// gl.glEnable(GL.GL_ALPHA_TEST);
+// gl.glAlphaFunc(GL.GL_GREATER, 0.01f);
+//
+// gl.glMatrixMode(javax.media.opengl.GL.GL_TEXTURE);
+// gl.glPushMatrix();
+// gl.glLoadIdentity();
+//
+//// System.out.printf("%d geo tiles, image tiles: ", dc.getSurfaceGeometry().size());
+// for (SectorGeometry sg : dc.getSurfaceGeometry())
+// {
+// TextureTile[] tilesToRender = this.getIntersectingTiles(sg);
+// if (tilesToRender == null)
+// continue;
+//// System.out.printf("%d, ", tilesToRender.length);
+//
+// int numTilesRendered = 0;
+// while (numTilesRendered < tilesToRender.length)
+// {
+// int numTexUnitsUsed = 0;
+// while (numTexUnitsUsed < dc.getNumTextureUnits() && numTilesRendered < tilesToRender.length)
+// {
+// if (this.setupTileTexture(dc, tilesToRender[numTilesRendered++],
+// GL.GL_TEXTURE0 + numTexUnitsUsed))
+// {
+// ++numTexUnitsUsed;
+// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_COMBINE);
+// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC0_RGB, GL.GL_TEXTURE);
+// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC1_RGB, GL.GL_PREVIOUS);
+// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC2_RGB, GL.GL_TEXTURE);
+// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_OPERAND2_RGB, GL.GL_SRC_ALPHA);
+// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_COMBINE_RGB, GL.GL_INTERPOLATE);
+//
+// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC0_ALPHA, GL.GL_TEXTURE);
+// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC1_ALPHA, GL.GL_PREVIOUS);
+// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC2_ALPHA, GL.GL_TEXTURE);
+// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_OPERAND2_ALPHA, GL.GL_SRC_ALPHA);
+// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_COMBINE_ALPHA, GL.GL_INTERPOLATE);
+// }
+// }
+//
+// sg.renderMultiTexture(dc, numTexUnitsUsed);
+//
+// // Turn all the multi-texture units off
+// for (int i = 0; i < dc.getNumTextureUnits(); i++)
+// {
+// gl.glActiveTexture(GL.GL_TEXTURE0 + i);
+// gl.glDisable(GL.GL_TEXTURE_2D);
+// }
+// gl.glActiveTexture(GL.GL_TEXTURE0);
+// }
+// }
+//// System.out.println();
+//
+// gl.glMatrixMode(javax.media.opengl.GL.GL_TEXTURE);
+// gl.glPopMatrix();
+// }
+// catch (Exception e)
+// {
+// String message = WorldWind.retrieveErrMsg("generic.ExceptionWhileRenderingLayer");
+// message += this.getClass().getName();
+// WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+// }
+// finally
+// {
+// gl.glPopAttrib();
+// }
+// }
+//
+// private static String[] buildFragmentShader(int numTexUnits)
+// {
+// ArrayList lines = new ArrayList();
+//
+// lines.add("uniform sampler2D tex[" + numTexUnits + "];");
+// lines.add("uniform int maxTexUnit;");
+// lines.add("void main()");
+// lines.add("{");
+// lines.add(" vec2 zero = vec2(0.0);");
+// lines.add(" vec2 one = vec2(1.0);");
+// lines.add(" vec4 color = vec4(0.0);");
+// lines.add(" bool hasColor = false;");
+//
+// for (int i = 0; i < numTexUnits; i++)
+// {
+// lines.add("if (" + i + " <= maxTexUnit)");
+// lines.add("{");
+// lines.add("if (all(greaterThanEqual(gl_TexCoord[" + i + "].st, zero))"
+// + " && all(lessThanEqual(gl_TexCoord[" + i + "].st, one)))");
+// lines.add("{");
+// lines.add(" color = texture2D(tex[" + i + "], gl_TexCoord[" + i + "].st);");
+// lines.add(" hasColor = true;");
+// lines.add("}");
+// }
+//
+// for (int i = 0; i < numTexUnits; i++)
+// {
+// lines.add("}");
+// }
+//
+// lines.add(" if(hasColor)");
+// lines.add(" gl_FragColor = color;");
+// lines.add(" else");
+// lines.add(" discard;");
+// lines.add("}");
+//
+// return lines.toArray(new String[lines.size()]);
+// }
+//
+// private int fProgram = -1;
+//
+// private void initFragmentProgram(int numTextureUnits)
+// {
+// String[] lines = buildFragmentShader(numTextureUnits);
+//
+// GL gl = GLContext.getCurrent().getGL();
+//
+// int fShader = gl.glCreateShader(GL.GL_FRAGMENT_SHADER);
+// int[] lineCounts = new int[lines.length];
+// for (int i = 0; i < lines.length; i++)
+// {
+// lineCounts[i] = lines[i].length();
+// }
+// gl.glShaderSource(fShader, lines.length, lines, lineCounts, 0);
+// gl.glCompileShader(fShader);
+//
+// int[] status = new int[1];
+// byte[] log = new byte[4096];
+// int[] logLength = new int[1];
+// gl.glGetShaderiv(fShader, GL.GL_COMPILE_STATUS, status, 0);
+// gl.glGetShaderInfoLog(fShader, log.length, logLength, 0, log, 0);
+// if (status[0] != 1)
+// {
+// gl.glGetShaderInfoLog(fShader, log.length, logLength, 0, log, 0);
+// String sLog = new String(log);
+// System.out.println("Compile : " + sLog);
+// }
+//
+// this.fProgram = gl.glCreateProgram();
+// gl.glAttachShader(this.fProgram, fShader);
+// gl.glLinkProgram(this.fProgram);
+//
+// gl.glGetProgramiv(this.fProgram, GL.GL_LINK_STATUS, status, 0);
+// if (status[0] != 1)
+// {
+// gl.glGetShaderInfoLog(fShader, log.length, logLength, 0, log, 0);
+// String sLog = new String(log);
+// System.out.println("Link : " + sLog);
+// }
+// }
+//
+// private String[] texUnitPositions;
+// private int[] texSamplers;
+//
+// private void renderTiles3(DrawContext dc)
+// {
+// // Render all the tiles collected during assembleTiles()
+// GL gl = dc.getGL();
+//
+// gl.glPushAttrib(
+// GL.GL_COLOR_BUFFER_BIT // for blend func, current color, alpha func, color mask
+// | GL.GL_POLYGON_BIT // for face culling, polygon mode
+// | GL.GL_ENABLE_BIT
+// | GL.GL_CURRENT_BIT
+// | GL.GL_DEPTH_BUFFER_BIT // for depth mask
+// | GL.GL_TEXTURE_BIT // for texture env
+// | GL.GL_TRANSFORM_BIT);
+//
+// try
+// {
+// dc.setNumTextureUnits(1);
+// if (this.fProgram == -1)
+// this.initFragmentProgram(dc.getNumTextureUnits());
+//
+// if (this.texUnitPositions == null)
+// {
+// this.texUnitPositions = new String[dc.getNumTextureUnits()];
+// for (int i = 0; i < dc.getNumTextureUnits(); i++)
+// {
+// this.texUnitPositions[i] = "tex[" + i + "]";
+// }
+// }
+//
+// if (this.texSamplers == null)
+// this.texSamplers = new int[dc.getNumTextureUnits()];
+//
+// gl.glUseProgram(this.fProgram);
+//
+// for (int i = 0; i < dc.getNumTextureUnits(); i++)
+// {
+// this.texSamplers[i] = gl.glGetUniformLocation(fProgram, "tex[" + i + "]");
+// gl.glUniform1i(this.texSamplers[i], i);
+// }
+//
+// int maxTexUnit = gl.glGetUniformLocation(fProgram, "maxTexUnit");
+//
+// gl.glEnable(GL.GL_DEPTH_TEST);
+// gl.glDepthFunc(GL.GL_LEQUAL);
+//
+// if (this.useTransparentTextures)
+// {
+// gl.glEnable(GL.GL_BLEND);
+// gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+// }
+//
+// gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
+// gl.glEnable(GL.GL_CULL_FACE);
+// gl.glCullFace(GL.GL_BACK);
+// gl.glColor4d(0, 0, 0, 0);
+//
+// gl.glMatrixMode(GL.GL_TEXTURE);
+// gl.glPushMatrix();
+//
+//// System.out.printf("%d geo tiles, image tiles: ", dc.getSurfaceGeometry().size());
+// for (SectorGeometry sg : dc.getSurfaceGeometry())
+// {
+// TextureTile[] tilesToRender = this.getIntersectingTiles(sg);
+// if (tilesToRender == null)
+// continue;
+//// System.out.printf("%d, ", tilesToRender.length);
+//
+// int numTilesRendered = 0;
+// while (numTilesRendered < tilesToRender.length)
+// {
+// int numTexUnitsUsed = 0;
+// while (numTexUnitsUsed < dc.getNumTextureUnits() && numTilesRendered < tilesToRender.length)
+// {
+// if (this.setupTileTexture(dc, tilesToRender[numTilesRendered++],
+// GL.GL_TEXTURE0 + numTexUnitsUsed))
+// {
+// ++numTexUnitsUsed;
+// }
+// }
+//
+// gl.glUniform1i(maxTexUnit, numTexUnitsUsed - 1);
+// sg.renderMultiTexture(dc, numTexUnitsUsed);
+// }
+// }
+//// System.out.println();
+//
+// gl.glActiveTexture(GL.GL_TEXTURE0);
+// gl.glUseProgram(0);
+// gl.glMatrixMode(javax.media.opengl.GL.GL_TEXTURE);
+// gl.glPopMatrix();
+// }
+// catch (Exception e)
+// {
+// String message = WorldWind.retrieveErrMsg("generic.ExceptionWhileRenderingLayer");
+// message += this.getClass().getName();
+// WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
+// }
+// finally
+// {
+// gl.glPopAttrib();
+// }
+// }
diff --git a/gov/nasa/worldwind/layers/TrackLayer.java b/gov/nasa/worldwind/layers/TrackLayer.java
new file mode 100644
index 0000000..fa1f32a
--- /dev/null
+++ b/gov/nasa/worldwind/layers/TrackLayer.java
@@ -0,0 +1,165 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package gov.nasa.worldwind.layers;
+
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+
+/**
+ * @author tag
+ * @version $Id: TrackLayer.java 1230 2007-03-16 14:47:35Z tgaskins $
+ */
+public class TrackLayer extends AbstractLayer
+{
+ private static final TrackRenderer trackRenderer = new TrackRenderer();
+ private java.util.List tracks = new java.util.ArrayList();
+ private Sector boundingSector;
+ private IconRenderer iconRenderer = new IconRenderer();
+ private UserFacingIcon icon;
+
+ public TrackLayer(java.util.List tracks)
+ {
+ if (tracks == null)
+ {
+ String msg = WorldWind.retrieveErrMsg("nullValue.TracksIsNull");
+ WorldWind.logger().log(java.util.logging.Level.FINE, msg);
+ throw new IllegalArgumentException(msg);
+ }
+
+ this.tracks = tracks;
+ this.boundingSector = Sector.boundingSector(this.iterator());
+ }
+
+ private TrackPointIteratorImpl iterator()
+ {
+ return new TrackPointIteratorImpl(this.tracks);
+ }
+
+ public void dispose()
+ {
+ this.trackRenderer.dispose();
+ }
+
+ public int getNumPoints()
+ {
+ return this.iterator().getNumPoints();
+ }
+
+ public double getMarkerPixels()
+ {
+ return this.trackRenderer.getMarkerPixels();
+ }
+
+ public void setMarkerPixels(double markerPixels)
+ {
+ this.trackRenderer.setMarkerPixels(markerPixels);
+ }
+
+ public double getMinMarkerSize()
+ {
+ return this.trackRenderer.getMinMarkerSize();
+ }
+
+ public void setMinMarkerSize(double minMarkerSize)
+ {
+ this.trackRenderer.setMinMarkerSize(minMarkerSize);
+ }
+
+ public double getMarkerElevation()
+ {
+ return this.trackRenderer.getMarkerElevation();
+ }
+
+ public void setMarkerElevation(double markerElevation)
+ {
+ this.trackRenderer.setMarkerElevation(markerElevation);
+ }
+
+ public Material getMaterial()
+ {
+ return this.trackRenderer.getMaterial();
+ }
+
+ public void setMaterial(Material material)
+ {
+ this.trackRenderer.setMaterial(material);
+ }
+
+ public String getIconFilePath()
+ {
+ return this.icon != null ? this.icon.getPath() : null;
+ }
+
+ public void setIconFilePath(String iconFilePath)
+ {
+ this.icon = iconFilePath != null ? new UserFacingIcon(iconFilePath, null) : null;
+ }
+
+ public int getLowerLimit()
+ {
+ return this.trackRenderer.getLowerLimit();
+ }
+
+ public void setLowerLimit(int lowerLimit)
+ {
+ this.trackRenderer.setLowerLimit(lowerLimit);
+ }
+
+ public int getUpperLimit()
+ {
+ return this.trackRenderer.getUpperLimit();
+ }
+
+ public void setUpperLimit(int upperLimit)
+ {
+ this.trackRenderer.setUpperLimit(upperLimit);
+ }
+
+ @Override
+ protected void doPick(DrawContext dc, java.awt.Point pickPoint)
+ {
+ this.draw(dc, pickPoint);
+ }
+
+ protected void doRender(DrawContext dc)
+ {
+ this.draw(dc, null);
+ }
+
+ private void draw(DrawContext dc, java.awt.Point pickPoint)
+ {
+ TrackPointIterator trackPoints = this.iterator();
+ if (!trackPoints.hasNext())
+ return;
+
+ if (dc.getVisibleSector() == null)
+ return;
+
+ SectorGeometryList geos = dc.getSurfaceGeometry();
+ if (geos == null)
+ return;
+
+ if (!dc.getVisibleSector().intersects(this.boundingSector))
+ return;
+
+ Point iconPoint = this.trackRenderer.render(dc, trackPoints);
+
+ if (iconPoint != null && this.icon != null)
+ {
+ if (dc.isPickingMode())
+ this.iconRenderer.pick(dc, this.icon, iconPoint, pickPoint, this);
+ else
+ this.iconRenderer.render(dc, this.icon, iconPoint);
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return gov.nasa.worldwind.WorldWind.retrieveErrMsg("layers.TrackLayer.Name");
+ }
+}
\ No newline at end of file
diff --git a/worldwinddemo/AWT1Up.java b/worldwinddemo/AWT1Up.java
new file mode 100644
index 0000000..67c33e0
--- /dev/null
+++ b/worldwinddemo/AWT1Up.java
@@ -0,0 +1,317 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package worldwinddemo;
+
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.awt.*;
+import gov.nasa.worldwind.geom.*;
+import gov.nasa.worldwind.layers.Earth.*;
+import gov.nasa.worldwind.layers.*;
+import org.w3c.dom.*;
+import org.xml.sax.*;
+
+import javax.swing.*;
+import javax.xml.parsers.*;
+import java.awt.*;
+import java.awt.font.*;
+import java.io.*;
+import java.util.*;
+import java.util.List;
+
+/**
+ * @author Tom Gaskins
+ * @version $Id: AWT1Up.java 1772 2007-05-07 23:05:47Z tgaskins $
+ */
+public class AWT1Up
+{
+ private static class AWT1UpFrame extends javax.swing.JFrame
+ {
+ StatusBar statusBar;
+ JLabel cursorPositionDisplay;
+ WorldWindowGLCanvas wwd;
+
+ public AWT1UpFrame()
+ {
+ try
+ {
+ System.out.println(gov.nasa.worldwind.Version.getVersion());
+
+ wwd = new gov.nasa.worldwind.awt.WorldWindowGLCanvas();
+ wwd.setPreferredSize(new java.awt.Dimension(800, 600));
+ this.getContentPane().add(wwd, java.awt.BorderLayout.CENTER);
+
+ this.statusBar = new StatusBar();
+ this.getContentPane().add(statusBar, BorderLayout.PAGE_END);
+
+ this.pack();
+
+ java.awt.Dimension prefSize = this.getPreferredSize();
+ java.awt.Dimension parentSize;
+ java.awt.Point parentLocation = new java.awt.Point(0, 0);
+ parentSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
+ int x = parentLocation.x + (parentSize.width - prefSize.width) / 2;
+ int y = parentLocation.y + (parentSize.height - prefSize.height) / 2;
+ this.setLocation(x, y);
+ this.setResizable(true);
+
+ Model m = (Model) WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME);
+ LayerList layers = m.getLayers();
+ for (Layer layer : layers)
+ {
+ if (layer instanceof TiledImageLayer)
+ ((TiledImageLayer) layer).setShowImageTileOutlines(false);
+ if (layer instanceof LandsatI3)
+ ((TiledImageLayer) layer).setDrawBoundingVolumes(false);
+ if (layer instanceof CompassLayer)
+ ((CompassLayer) layer).setShowTilt(true);
+ }
+
+ m.getLayers().add(this.buildShapesLayer());
+ m.getLayers().add(this.buildIconLayer());
+ m.getLayers().add(this.buildGeoRSSLayer());
+ m.setShowWireframeExterior(false);
+ m.setShowWireframeInterior(false);
+ wwd.setModel(m);
+
+ // Forward events to the status bar to provide the cursor position info.
+ this.statusBar.setEventSource(wwd);
+
+ this.wwd.addRenderingListener(new RenderingListener()
+ {
+ public void stageChanged(RenderingEvent event)
+ {
+ // Do nothing; just showing how to use it.
+ }
+ });
+
+ this.wwd.addSelectListener(new SelectListener()
+ {
+ private WWIcon lastToolTipIcon = null;
+
+ public void selected(SelectEvent event)
+ {
+ if (event.getEventAction().equals(SelectEvent.LEFT_CLICK))
+ {
+ if (event.hasObjects())
+ System.out.println("Single clicked " + event.getTopObject());
+ else
+ System.out.println("Single clicked " + "no object");
+ }
+ else if (event.getEventAction().equals(SelectEvent.LEFT_DOUBLE_CLICK))
+ {
+ if (event.hasObjects())
+ System.out.println("Double clicked " + event.getTopObject());
+ else
+ System.out.println("Double clicked " + "no object");
+ }
+ else if (event.getEventAction().equals(SelectEvent.RIGHT_CLICK))
+ {
+ if (event.hasObjects())
+ System.out.println("Right clicked " + event.getTopObject());
+ else
+ System.out.println("Right clicked " + "no object");
+ }
+ else if (event.getEventAction().equals(SelectEvent.HOVER))
+ {
+ if (lastToolTipIcon != null)
+ {
+ lastToolTipIcon.setShowToolTip(false);
+ this.lastToolTipIcon = null;
+ AWT1UpFrame.this.wwd.repaint();
+ }
+
+ if (event.hasObjects())
+ {
+ if (event.getTopObject() instanceof WWIcon)
+ {
+ this.lastToolTipIcon = (WWIcon) event.getTopObject();
+ lastToolTipIcon.setShowToolTip(true);
+ AWT1UpFrame.this.wwd.repaint();
+ }
+ }
+ }
+ else if (event.getEventAction().equals(SelectEvent.ROLLOVER))
+ {
+ AWT1UpFrame.this.highlight(event.getTopObject());
+ }
+ }
+ });
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ gov.nasa.worldwind.WWIcon lastPickedIcon;
+
+ private void highlight(Object o)
+ {
+ if (this.lastPickedIcon == o)
+ return; // same thing selected
+
+ if (this.lastPickedIcon != null)
+ {
+ this.lastPickedIcon.setHighlighted(false);
+ this.lastPickedIcon = null;
+ }
+
+ if (o != null && o instanceof gov.nasa.worldwind.WWIcon)
+ {
+ this.lastPickedIcon = (WWIcon) o;
+ this.lastPickedIcon.setHighlighted(true);
+ }
+ }
+
+ private IconLayer buildIconLayer()
+ {
+ IconLayer layer = new IconLayer();
+
+ for (double lat = 0; lat < 10; lat += 10)
+ {
+ for (double lon = -180; lon < 180; lon += 10)
+ {
+ double alt = 0;
+ if (lon % 90 == 0)
+ alt = 2000000;
+ WWIcon icon = new UserFacingIcon("images/32x32-icon-nasa.png",
+ new Position(Angle.fromDegrees(lat), Angle.fromDegrees(lon), alt));
+ icon.setHighlightScale(1.5);
+ icon.setToolTipFont(this.makeToolTipFont());
+ icon.setToolTipText(icon.getPath());
+ icon.setToolTipTextColor(java.awt.Color.YELLOW);
+ layer.addIcon(icon);
+ }
+ }
+
+ return layer;
+ }
+
+ private RenderableLayer buildShapesLayer()
+ {
+ RenderableLayer layer = new RenderableLayer();
+
+ Color interiorColor = new Color(1f, 1f, 0f, 0.3f);
+ Color borderColor = new Color(1f, 1f, 0f, 0.4f);
+
+ SurfaceQuadrilateral quad = new SurfaceQuadrilateral(new Sector(
+ Angle.fromDegrees(41.0), Angle.fromDegrees(41.6),
+ Angle.fromDegrees(-122.5), Angle.fromDegrees(-121.7)),
+ interiorColor, borderColor);
+ layer.addRenderable(quad);
+
+ quad = new SurfaceQuadrilateral(new Sector(
+ Angle.fromDegrees(38.9), Angle.fromDegrees(39.3),
+ Angle.fromDegrees(-120.2), Angle.fromDegrees(-119.9)),
+ new Color(0f, 1f, 1f, 0.3f), new Color(0.5f, 1f, 1f, 0.4f));
+ layer.addRenderable(quad);
+
+ double originLat = 28;
+ double originLon = -82;
+ ArrayList positions = new ArrayList();
+ positions.add(new LatLon(Angle.fromDegrees(originLat + 5.0), Angle.fromDegrees(originLon + 2.5)));
+ positions.add(new LatLon(Angle.fromDegrees(originLat + 5.0), Angle.fromDegrees(originLon - 2.5)));
+ positions.add(new LatLon(Angle.fromDegrees(originLat + 2.5), Angle.fromDegrees(originLon - 5.0)));
+ positions.add(new LatLon(Angle.fromDegrees(originLat - 2.5), Angle.fromDegrees(originLon - 5.0)));
+ positions.add(new LatLon(Angle.fromDegrees(originLat - 5.0), Angle.fromDegrees(originLon - 2.5)));
+ positions.add(new LatLon(Angle.fromDegrees(originLat - 5.0), Angle.fromDegrees(originLon + 2.5)));
+ positions.add(new LatLon(Angle.fromDegrees(originLat - 2.5), Angle.fromDegrees(originLon + 5.0)));
+ positions.add(new LatLon(Angle.fromDegrees(originLat + 2.5), Angle.fromDegrees(originLon + 5.0)));
+
+ SurfacePolygon polygon = new SurfacePolygon(positions,
+ new Color(1f, 0.11f, 0.2f, 0.4f), new Color(1f, 0f, 0f, 0.6f));
+ polygon.setStroke(new BasicStroke(2f));
+ layer.addRenderable(polygon);
+
+ return layer;
+ }
+
+ private static final String lineTestString =
+ " 45.256 -110.45 46.46 -109.48 43.84 -109.86";
+ private static final String itemTestString =
+ "- M 3.2, Mona Passage http://example.org/2005/09/09/atom01 Wed, 17 Aug 2005 07:02:32 GMT 45.256 -110.45 46.46 -109.48 43.84 -109.86 45.256 -110.45
";
+
+ private RenderableLayer buildGeoRSSLayer()
+ {
+ try
+ {
+ DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
+ docBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
+ Document doc = docBuilder.parse(new File("GeoRSSTestData.xml"));
+ List shapes = GeoRSSParser.parseShapes(doc);
+
+// List shapes = GeoRSSParser.parseFragment(itemTestString, null);
+
+// StringBuffer sb = new StringBuffer();
+// FileReader fr = new FileReader("feed.xml");
+// for (int c = fr.read(); c >=0; c = fr.read())
+// sb.append((char) c);
+// List shapes = GeoRSSParser.parseShapes(sb.toString());
+
+ RenderableLayer layer = new RenderableLayer();
+ if (shapes != null)
+ {
+ for (Renderable shape : shapes)
+ {
+ layer.addRenderable(shape);
+ }
+ }
+
+ return layer;
+ }
+ catch (ParserConfigurationException e)
+ {
+ e.printStackTrace();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ catch (SAXException e)
+ {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ private Font makeToolTipFont()
+ {
+ HashMap fontAttributes = new HashMap();
+
+ fontAttributes.put(TextAttribute.BACKGROUND, new java.awt.Color(0.4f, 0.4f, 0.4f, 1f));
+ return Font.decode("Arial-BOLD-14").deriveFont(fontAttributes);
+ }
+ }
+
+ static
+ {
+ if (gov.nasa.worldwind.Configuration.isMacOS())
+ {
+ System.setProperty("apple.laf.useScreenMenuBar", "true");
+ System.setProperty("com.apple.mrj.application.apple.menu.about.name", "World Wind AWT Canvas App");
+ System.setProperty("com.apple.mrj.application.growbox.intrudes", "false");
+ }
+ }
+
+ public static void main(String[] args)
+ {
+ System.out.println("Java run-time version: " + System.getProperty("java.version"));
+
+ try
+ {
+ AWT1UpFrame frame = new AWT1UpFrame();
+ frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
+ frame.setVisible(true);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/worldwinddemo/BasicDemo.java b/worldwinddemo/BasicDemo.java
new file mode 100644
index 0000000..171dac4
--- /dev/null
+++ b/worldwinddemo/BasicDemo.java
@@ -0,0 +1,163 @@
+/* Copyright (C) 2001, 2006 United States Government as represented by
+the Administrator of the National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package worldwinddemo;
+
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.awt.*;
+import gov.nasa.worldwind.layers.*;
+import gov.nasa.worldwind.layers.Earth.*;
+
+import javax.swing.*;
+import javax.swing.border.*;
+import java.awt.*;
+import java.awt.event.*;
+
+/**
+ * @author tag
+ * @version $Id: WWPieceMaker.java 1764 2007-05-07 20:01:57Z tgaskins $
+ */
+public class BasicDemo
+{
+ private BasicDemo.LayerAction[] layers = new BasicDemo.LayerAction[] {
+ new BasicDemo.LayerAction(new BMNGSurfaceLayer(), true),
+ new BasicDemo.LayerAction(new LandsatI3(), true),
+ new BasicDemo.LayerAction(new USGSDigitalOrtho(), false),
+ new BasicDemo.LayerAction(new USGSUrbanAreaOrtho(), true),
+ new BasicDemo.LayerAction(new EarthNASAPlaceNameLayer(), true),
+ new BasicDemo.LayerAction(new CompassLayer(), true),
+ };
+
+ private static class AppFrame extends JFrame
+ {
+ private final WorldWindowGLCanvas wwd = new WorldWindowGLCanvas();
+
+ public AppFrame(BasicDemo.LayerAction[] layers)
+ {
+ LayerList layerList = new LayerList();
+
+ try
+ {
+ JPanel mainPanel = new JPanel();
+ mainPanel.setLayout(new BorderLayout());
+ wwd.setPreferredSize(new Dimension(800, 600));
+ mainPanel.add(wwd, BorderLayout.CENTER);
+
+ StatusBar statusBar = new StatusBar();
+ statusBar.setEventSource(wwd);
+ mainPanel.add(statusBar, BorderLayout.PAGE_END);
+ this.getContentPane().add(mainPanel, BorderLayout.CENTER);
+
+ JPanel westContainer = new JPanel(new BorderLayout());
+ {
+ JPanel westPanel = new JPanel(new GridLayout(0, 1, 0, 10));
+ westPanel.setBorder(BorderFactory.createEmptyBorder(9, 9, 9, 9));
+ {
+ JPanel layersPanel = new JPanel(new GridLayout(0, 1, 0, 15));
+ layersPanel.setBorder(new TitledBorder("Layers"));
+ for (BasicDemo.LayerAction action : layers)
+ {
+ JCheckBox jcb = new JCheckBox(action);
+ jcb.setSelected(action.selected);
+ layersPanel.add(jcb);
+ layerList.add(action.layer);
+
+ if (action.layer instanceof TiledImageLayer)
+ ((TiledImageLayer) action.layer).setShowImageTileOutlines(false);
+
+ if (action.layer instanceof LandsatI3)
+ ((TiledImageLayer) action.layer).setDrawBoundingVolumes(false);
+
+ if (action.layer instanceof USGSDigitalOrtho)
+ ((TiledImageLayer) action.layer).setDrawTileIDs(false);
+ }
+ westPanel.add(layersPanel);
+ westContainer.add(westPanel, BorderLayout.NORTH);
+ }
+ }
+
+ this.getContentPane().add(westContainer, BorderLayout.WEST);
+ this.pack();
+
+ Dimension prefSize = this.getPreferredSize();
+ prefSize.setSize(prefSize.getWidth(), 1.1 * prefSize.getHeight());
+ this.setSize(prefSize);
+
+ // Center the app on the user's screen.
+ Dimension parentSize;
+ Point parentLocation = new Point(0, 0);
+ parentSize = Toolkit.getDefaultToolkit().getScreenSize();
+ int x = parentLocation.x + (parentSize.width - prefSize.width) / 2;
+ int y = parentLocation.y + (parentSize.height - prefSize.height) / 2;
+ this.setLocation(x, y);
+ this.setResizable(true);
+
+ Model m = (Model) WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME);
+ m.setLayers(layerList);
+ m.setShowWireframeExterior(false);
+ m.setShowWireframeInterior(false);
+ m.setShowTessellationBoundingVolumes(false);
+ wwd.setModel(m);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static class LayerAction extends AbstractAction
+ {
+ private Layer layer;
+ private boolean selected;
+
+ public LayerAction(Layer layer, boolean selected)
+ {
+ super(layer.getName());
+ this.layer = layer;
+ this.selected = selected;
+ this.layer.setEnabled(this.selected);
+ }
+
+ public void actionPerformed(ActionEvent actionEvent)
+ {
+ if (((JCheckBox) actionEvent.getSource()).isSelected())
+ this.layer.setEnabled(true);
+ else
+ this.layer.setEnabled(false);
+
+ appFrame.wwd.repaint();
+ }
+ }
+
+ static
+ {
+ if (Configuration.isMacOS())
+ {
+ System.setProperty("apple.laf.useScreenMenuBar", "true");
+ System.setProperty("com.apple.mrj.application.apple.menu.about.name", "World Wind Basic Demo");
+ System.setProperty("com.apple.mrj.application.growbox.intrudes", "false");
+ }
+ }
+
+ private static BasicDemo.AppFrame appFrame;
+
+ public static void main(String[] args)
+ {
+ System.out.println("Java run-time version: " + System.getProperty("java.version"));
+ System.out.println(gov.nasa.worldwind.Version.getVersion());
+
+ try
+ {
+ BasicDemo demo = new BasicDemo();
+ appFrame = new BasicDemo.AppFrame(demo.layers);
+ appFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ appFrame.setVisible(true);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/worldwinddemo/StatusBar.java b/worldwinddemo/StatusBar.java
new file mode 100644
index 0000000..1d90dc3
--- /dev/null
+++ b/worldwinddemo/StatusBar.java
@@ -0,0 +1,113 @@
+/*
+Copyright (C) 2001, 2006 United States Government
+as represented by the Administrator of the
+National Aeronautics and Space Administration.
+All Rights Reserved.
+*/
+package worldwinddemo;
+
+import gov.nasa.worldwind.*;
+import gov.nasa.worldwind.geom.*;
+
+import javax.swing.*;
+import java.awt.event.*;
+
+/**
+ * @author tag
+ * @version $Id: StatusBar.java 1764 2007-05-07 20:01:57Z tgaskins $
+ */
+public class StatusBar extends JPanel implements PositionListener
+{
+ private WorldWindow eventSource;
+ private final JLabel latDisplay = new JLabel("");
+ private final JLabel lonDisplay = new JLabel("Off globe");
+ private final JLabel eleDisplay = new JLabel("");
+
+ public StatusBar()
+ {
+ super(new java.awt.GridLayout(1, 0));
+
+ final JLabel heartBeat = new JLabel("Downloading");
+
+ latDisplay.setHorizontalAlignment(SwingConstants.CENTER);
+ lonDisplay.setHorizontalAlignment(SwingConstants.CENTER);
+ eleDisplay.setHorizontalAlignment(SwingConstants.CENTER);
+
+ this.add(new JLabel("")); // dummy label to visually balance with heartbeat
+ this.add(latDisplay);
+ this.add(lonDisplay);
+ this.add(eleDisplay);
+ this.add(heartBeat);
+
+ heartBeat.setHorizontalAlignment(SwingConstants.CENTER);
+ heartBeat.setForeground(new java.awt.Color(255, 0, 0, 0));
+
+ Timer downloadTimer = new Timer(50, new ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent actionEvent)
+ {
+
+ java.awt.Color color = heartBeat.getForeground();
+
+ int alpha = color.getAlpha();
+
+ if (WorldWind.retrievalService().hasActiveTasks())
+ {
+ if (alpha == 255)
+ alpha = 255;
+ else
+ alpha = alpha < 16 ? 16 : Math.min(255, alpha + 20);
+ }
+ else
+ {
+ alpha = Math.max(0, alpha - 20);
+ }
+ heartBeat.setForeground(new java.awt.Color(255, 0, 0, alpha));
+ }
+ });
+ downloadTimer.start();
+ }
+
+ public void setEventSource(WorldWindow newEventSource)
+ {
+ if (this.eventSource != null)
+ this.eventSource.removePositionListener(this);
+
+ if (newEventSource != null)
+ newEventSource.addPositionListener(this);
+
+ this.eventSource = newEventSource;
+ }
+
+ public void moved(PositionEvent event)
+ {
+ this.handleCursorPositionChange(event);
+ }
+
+ public WorldWindow getEventSource()
+ {
+ return this.eventSource;
+ }
+
+ private void handleCursorPositionChange(PositionEvent event)
+ {
+ Position newPos = (Position) event.getPosition();
+ if (newPos != null)
+ {
+ String las = String.format("Latitude %7.3f\u00B0",
+ newPos.getLatitude().getDegrees());
+ String los = String.format("Longitude %7.3f\u00B0",
+ newPos.getLongitude().getDegrees());
+ String els = String.format("Elevation %7d meters", (int) newPos.getElevation());
+ latDisplay.setText(las);
+ lonDisplay.setText(los);
+ eleDisplay.setText(els);
+ }
+ else
+ {
+ latDisplay.setText("");
+ lonDisplay.setText("Off globe");
+ eleDisplay.setText("");
+ }
+ }
+}
--
2.11.4.GIT