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 entries; + private java.util.concurrent.CopyOnWriteArrayList listeners; + private Long capacityInBytes; + private Long currentUsedCapacity; + private Long lowWater; + + /** + * Contructs a new self-configuring cache. Configuration can be done manually later. + */ + public BasicMemoryCache() + { + this.entries = new java.util.concurrent.ConcurrentHashMap(); + this.listeners = new java.util.concurrent.CopyOnWriteArrayList(); + this.capacityInBytes = Configuration.getLongValue(AVKey.CACHE_SIZE, FALLBACK_CACHE_SIZE); + this.lowWater = Configuration.getLongValue(AVKey.CACHE_LOW_WATER, (long)(0.7 * FALLBACK_CACHE_SIZE)); + this.currentUsedCapacity = (long) 0; + } + + /** + * Constructs a new cache using capacity for maximum size, and loWater for the low water. + * + * @param loWater the low water level + * @param capacity the maximum capacity + */ + public BasicMemoryCache(long loWater, long capacity) + { + this.entries = new java.util.concurrent.ConcurrentHashMap(); + this.listeners = new java.util.concurrent.CopyOnWriteArrayList(); + this.capacityInBytes = capacity; + this.lowWater = loWater; + this.currentUsedCapacity = (long) 0; + } + + /** + * @return the number of objects currently stored in this cache + */ + public int getNumObjects() + { + return this.entries.size(); + } + + /** + * @return the capacity of the cache in bytes + */ + public long getCapacity() + { + return this.capacityInBytes; + } + + /** + * @return the number of bytes that the cache currently holds + */ + public synchronized long getUsedCapacity() + { + return this.currentUsedCapacity; + } + + /** + * @return the amount of free space left in the cache (in bytes) + */ + public synchronized long getFreeCapacity() + { + return this.capacityInBytes - this.currentUsedCapacity; + } + + /** + * Adds a cache listener, MemoryCache listeners are used to notify classes when an item is removed from the cache. + * + * @param listener The new CacheListener + * @throws IllegalArgumentException is listener is null + */ + public synchronized void addCacheListener(MemoryCache.CacheListener listener) + { + if (listener == null) + { + String message = WorldWind.retrieveErrMsg("BasicMemoryCache.nullListenerAdded"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + this.listeners.add(listener); + } + + /** + * Removes a cache listener, objects using this listener will no longer receive notification of cache events. + * + * @param listener The CacheListener to remove + * @throws IllegalArgumentException if listener is null + */ + public synchronized void removeCacheListener(MemoryCache.CacheListener listener) + { + if (listener == null) + { + String message = WorldWind.retrieveErrMsg("BasicMemoryCache.nullListenerRemoved"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + this.listeners.remove(listener); + } + + /** + * Sets the new capacity (in bytes) for the cache. When decreasing cache size, it is recommended to check that the + * lowWater variable is suitable. If the capacity infringes on items stored in the cache, these items are removed. + * Setting a new low water is up to the user, that is, it remains unchanged and may be higher than the maximum + * capacity. When the low water level is higher than or equal to the maximum capacity, it is ignored, which can lead + * to poor performance when adding entries. + * + * @param newCapacity the new capacity of the cache. + */ + public synchronized void setCapacity(long newCapacity) + { + this.makeSpace(this.capacityInBytes - newCapacity); + this.capacityInBytes = newCapacity; + } + + /** + * Sets the new low water level in bytes, which controls how aggresively the cache discards items. + *

+ * When the cache fills, it removes items until it reaches the low water level. + *

+ * Setting a high loWater level will increase cache misses, but decrease average add time, but setting a low loWater + * will do the opposite. + * + * @param loWater the new low water level in bytes. + */ + public synchronized void setLowWater(long loWater) + { + if (loWater < this.capacityInBytes && loWater >= 0) + { + this.lowWater = loWater; + } + } + + /** + * Returns the low water level in bytes. When the cache fills, it removes items until it reaches the low water + * level. + * + * @return the low water level in bytes. + */ + public long getLowWater() + { + return this.lowWater; + } + + /** + * Returns true if the cache contains the item referenced by key. No guarantee is made as to whether or not the item + * will remain in the cache for any period of time. + *

+ * This function does not cause the object referenced by the key to be marked as accessed. getObject() + * should be used for that purpose + * + * @param key The key of a specific object + * @return true if the cache holds the item referenced by key + * @throws IllegalArgumentException if key is null + */ + public boolean contains(Object key) + { + if (key == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.KeyIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + return this.entries.containsKey(key); + } + + /** + * Adds an object to the cache. The add fails if the object or key is null, or if the size is zero, negative or + * greater than the maximmum capacity + * + * @param key The unique reference key that identifies this object. + * @param clientObject The actual object to be cached. + * @param clientObjectSize The size of the object in bytes. + * @return returns true if clientObject was added, false otherwise. + */ + public synchronized boolean add(Object key, Object clientObject, long clientObjectSize) + { + if (key == null || clientObject == null || clientObjectSize <= 0 || clientObjectSize > this.capacityInBytes) + { + String msg = WorldWind.retrieveErrMsg("BasicMemoryCache.CacheItemNotAdded"); + WorldWind.logger().log(java.util.logging.Level.FINER, msg); + + return false; + // the logic behind not throwing an exception is that whether we throw an exception or not, + // the object won't be added. This doesn't matter because that object could be removed before + // it is accessed again anyway. + } + + CacheEntry existing = this.entries.get(key); + if (existing != null) // replacing + { + this.removeEntry(existing); + } + + if (this.currentUsedCapacity + clientObjectSize > this.capacityInBytes) + { + this.makeSpace(clientObjectSize); + } + + this.currentUsedCapacity += clientObjectSize; + BasicMemoryCache.CacheEntry entry = new BasicMemoryCache.CacheEntry(key, clientObject, clientObjectSize); + this.entries.putIfAbsent(entry.key, entry); + return true; + } + + public synchronized boolean add(Object key, gov.nasa.worldwind.Cacheable clientObject) + { + return this.add(key, clientObject, clientObject.getSizeInBytes()); + } + + /** + * Remove the object reference by key from the cache. If no object with the corresponding key is found, this method + * returns immediately. + * + * @param key the key of the object to be removed + * @throws IllegalArgumentException if key is null + */ + public synchronized void remove(Object key) + { + if (key == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.KeyIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINER, msg); + + return; + } + + CacheEntry entry = this.entries.get(key); + if (entry != null) + this.removeEntry(entry); + } + + /** + * Obtain the object referenced by key without removing it. Apart from adding an object, this is the only way to + * mark an object as recently used. + * + * @param key The key for the object to be found. + * @return the object referenced by key if it is present, null otherwise. + * @throws IllegalArgumentException if key is null + */ + public synchronized Object getObject(Object key) + { + if (key == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.KeyIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINER, msg); + + return null; + } + + CacheEntry entry = this.entries.get(key); + + if (entry == null) + return null; + + entry.lastUsed = System.nanoTime(); // nanoTime overflows once every 292 years + // which will result in a slowing of the cache + // until ww is restarted or the cache is cleared. + return entry.clientObject; + } + + /** + * Obtain a list of all the keys in the cache. + * + * @return a Set of all keys in the cache. + */ + public java.util.Set getKeySet() + { + return this.entries.keySet(); + } + + /** + * Empties the cache. + */ + public synchronized void clear() + { + for (CacheEntry entry : this.entries.values()) + { + this.removeEntry(entry); + } + } + + /** + * Removes entry from the cache. To remove an entry using its key, use remove() + * + * @param entry The entry (as opposed to key) of the item to be removed + */ + private synchronized void removeEntry(CacheEntry entry) + { + // all removal passes through this function, + // so the reduction in "currentUsedCapacity" and listener notification is done here + + if (this.entries.remove(entry.key) != null) // returns null if entry does not exist + { + this.currentUsedCapacity -= entry.clientObjectSize; + + for (MemoryCache.CacheListener listener : this.listeners) + { + listener.entryRemoved(entry.key, entry.clientObject); + } + } + } + + /** + * Makes at least spaceRequired space in the cache. If spaceRequired is less than (capacity-lowWater), + * makes more space. Does nothing if capacity is less than spaceRequired. + * + * @param spaceRequired the amount of space required. + */ + private void makeSpace(long spaceRequired) + { + if (spaceRequired > this.capacityInBytes || spaceRequired < 0) + return; + + CacheEntry[] timeOrderedEntries = new CacheEntry[this.entries.size()]; + java.util.Arrays.sort(this.entries.values().toArray(timeOrderedEntries)); + + int i = 0; + while (this.getFreeCapacity() < spaceRequired || this.getUsedCapacity() > this.lowWater) + { + if (i < timeOrderedEntries.length) + { + this.removeEntry(timeOrderedEntries[i++]); + } + } + } + + /** + * a String representation of this object is returned.  This representation consists of maximum + * size, current used capacity and number of currently cached items. + * + * @return a String representation of this object + */ + @Override + public synchronized String toString() + { + return "MemoryCache max size = " + this.getCapacity() + " current size = " + this.currentUsedCapacity + + " number of items: " + this.getNumObjects(); + } + + @Override + protected void finalize() throws Throwable + { + try + { + // clear doesn't throw any checked exceptions + // but this is in case of an unchecked exception + // basically, we don't want to exit without calling super.finalize + this.clear(); + } + finally + { + super.finalize(); + } + } +} diff --git a/gov/nasa/worldwind/BasicModel.java b/gov/nasa/worldwind/BasicModel.java new file mode 100644 index 0000000..e5182af --- /dev/null +++ b/gov/nasa/worldwind/BasicModel.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; + +import gov.nasa.worldwind.geom.*; +import gov.nasa.worldwind.globes.*; + +/** + * @author Tom Gaskins + * @version $Id: BasicModel.java 1792 2007-05-08 21:28:37Z tgaskins $ + */ +public class BasicModel extends WWObjectImpl implements Model +{ + private Globe globe; + private Tessellator tessellator; + private LayerList layers; + private boolean showWireframeInterior = false; + private boolean showWireframeExterior = false; + private boolean showTessellationBoundingVolumes = false; + + public BasicModel() // TODO: Derive from configuration descriptor + { + // Create a default globe + Globe globe = (Globe) WorldWind.createConfigurationComponent(AVKey.GLOBE_CLASS_NAME); + this.setGlobe(globe); + this.createLayers(); + } + + private void createLayers() + { + LayerList layers = new LayerList(); + String layerNames = Configuration.getStringValue(AVKey.LAYERS_CLASS_NAMES, + "gov.nasa.worldwind.layers.Earth.BMNGSurfaceLayer"); + if (layerNames == null) + return; + + String[] names = layerNames.split(","); + for (String name : names) + { + try + { + if (name.length() > 0) + { + Layer l = (Layer) WorldWind.createComponent(name); + layers.add(l); + } + } + catch (Exception e) + { + String message = WorldWind.retrieveErrMsg("BasicModel.LayerNotFound") + name; + WorldWind.logger().log(java.util.logging.Level.FINE, message, e); + } + } + + this.setLayers(layers); + } + + public void setGlobe(gov.nasa.worldwind.Globe globe) + { + // don't raise an exception if globe == null. In that case, we are disassociating the model from any globe + + //remove property change listener "this" from the current globe. + if (this.globe != null) + this.globe.removePropertyChangeListener(this); + + // if the new globe is not null, add "this" as a property change listener. + if (globe != null) + globe.addPropertyChangeListener(this); + + Globe old = this.globe; + this.globe = globe; + this.firePropertyChange(AVKey.GLOBE, old, this.globe); + } + + public void setLayers(gov.nasa.worldwind.LayerList layers) + { + // don't raise an exception if layers == null. In that case, we are disassociating the model from any layer set + + if (this.layers != null) + this.layers.removePropertyChangeListener(this); + if (layers != null) + layers.addPropertyChangeListener(this); + + LayerList old = this.layers; + this.layers = layers; + this.firePropertyChange(AVKey.LAYERS, old, this.layers); + } + + public Globe getGlobe() + { + return this.globe; + } + + public LayerList getLayers() + { + return this.layers; + } + + public Tessellator getTessellator() + { + if (this.tessellator == null && this.globe != null) + this.tessellator = new EllipsoidRectangularTessellator(this.globe); // TODO: make configurable + return tessellator; + } + + public void setTessellator(Tessellator tessellator) + { + this.tessellator = tessellator; + } + + public void setShowWireframeInterior(boolean show) + { + this.showWireframeInterior = show; + } + + public void setShowWireframeExterior(boolean show) + { + this.showWireframeExterior = show; + } + + public boolean isShowWireframeInterior() + { + return this.showWireframeInterior; + } + + public boolean isShowWireframeExterior() + { + return this.showWireframeExterior; + } + + public boolean isShowTessellationBoundingVolumes() + { + return showTessellationBoundingVolumes; + } + + public void setShowTessellationBoundingVolumes(boolean showTessellationBoundingVolumes) + { + this.showTessellationBoundingVolumes = showTessellationBoundingVolumes; + } + + public Extent getExtent() + { + // See if the layers have it. + LayerList layers = BasicModel.this.getLayers(); + if (layers != null) + { + java.util.Iterator iter = layers.iterator(); + while (iter.hasNext()) + { + Layer layer = (Layer) iter.next(); + Extent e = (Extent) layer.getValue(AVKey.EXTENT); + if (e != null) + return e; + } + } + + // See if the Globe has it. + Globe globe = this.getGlobe(); + if (globe != null) + { + Extent e = globe.getExtent(); + if (e != null) + return e; + } + + return null; + } +} diff --git a/gov/nasa/worldwind/BasicOrbitView.java b/gov/nasa/worldwind/BasicOrbitView.java new file mode 100644 index 0000000..c609611 --- /dev/null +++ b/gov/nasa/worldwind/BasicOrbitView.java @@ -0,0 +1,442 @@ +/* +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 dcollins + * @version $Id: BasicOrbitView.java 1794 2007-05-08 22:07:21Z dcollins $ + */ +public class BasicOrbitView extends AbstractView +{ + private static final Double DefaultLatitude; + private static final Double DefaultLongitude; + private static final Double DefaultZoom; + private static final Double DefaultMinZoom; + private static final Double DefaultMaxZoom; + private static final Boolean DefaultEnableZoomConstraint; + private static final Double DefaultHeading; + private static final Double DefaultPitch; + private static final Double DefaultMinPitch; + private static final Double DefaultMaxPitch; + private static final Boolean DefaultEnablePitchConstraint; + + static + { + // Default latitude and longitude. + java.util.TimeZone tz = java.util.Calendar.getInstance().getTimeZone(); + DefaultLatitude = Configuration.getDoubleValue(AVKey.INITIAL_LATITUDE, 0d); + DefaultLongitude = Configuration.getDoubleValue(AVKey.INITIAL_LONGITUDE, + (180d * tz.getOffset(System.currentTimeMillis()) / (12d * 3.6e6))); + // Default zoom/eye-distance. + DefaultZoom = 0d; + DefaultMinZoom = 0d; + DefaultMaxZoom = Double.POSITIVE_INFINITY; + DefaultEnableZoomConstraint = Boolean.TRUE; + // Default heading and pitch. + DefaultHeading = 0d; + DefaultPitch = 0d; + DefaultMinPitch = 0d; + DefaultMaxPitch = 90d; + DefaultEnablePitchConstraint = Boolean.TRUE; + } + + // Geographic coordinate data. + private Angle focusLat = Angle.fromDegrees(DefaultLatitude); + private Angle focusLon = Angle.fromDegrees(DefaultLongitude); + private double eyeDist = DefaultZoom; + private Angle heading = Angle.fromDegrees(DefaultHeading); + private Angle pitch = Angle.fromDegrees(DefaultPitch); + private double altitude; + // Coordinate constraints. + private double minEyeDist = DefaultMinZoom; + private double maxEyeDist = DefaultMaxZoom; + private boolean enableZoomConstraint = DefaultEnableZoomConstraint; + private Angle minPitch = Angle.fromDegrees(DefaultMinPitch); + private Angle maxPitch = Angle.fromDegrees(DefaultMaxPitch); + private boolean enablePitchConstraint = DefaultEnablePitchConstraint; + // Current OpenGL projection state. + private ViewFrustum viewFrustum; + private double collisionRadius; + + private boolean isInitialized = false; + + protected void doApply(DrawContext dc) + { + if (!isInitialized) + { + this.doInitialize(dc); + isInitialized = true; + } + + Matrix4 modelView, projection = null; + // Compute the current model-view matrix and view eye point. + modelView = this.computeModelViewMatrix(dc); + Point eyePoint = modelView.getInverse().transform(new Point(0, 0, 0, 1)); + // Compute the current viewing frustum and projection matrix. + this.viewFrustum = this.computeViewFrustum(dc, eyePoint); + if (this.viewFrustum != null) + { + this.collisionRadius = this.computeCollisionRadius(this.viewFrustum); + projection = this.viewFrustum.getProjectionMatrix(); + } + // Set current GL matrix state. + this.applyMatrixState(dc, modelView, projection); + } + + private void doInitialize(DrawContext dc) + { + Globe globe = dc.getGlobe(); + + // Set the coordinate constraints to default values. + this.minEyeDist = this.collisionRadius = 1; + if (globe != null) + this.maxEyeDist = 6 * globe.getRadius(); + else + this.maxEyeDist = Double.POSITIVE_INFINITY; + + // Set the eye distance to a default value. + if (globe != null) + this.eyeDist = this.clampZoom(3 * globe.getRadius()); + } + + private Matrix4 computeModelViewMatrix(DrawContext dc) + { + Globe globe = dc.getGlobe(); + if (globe == null) + return null; + + Point focusPoint = globe.computePointFromPosition(this.focusLat, this.focusLon, 0); + if (focusPoint == null) + { + String message = WorldWind.retrieveErrMsg("BasicOrbitView.NullSurfacePoint"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalStateException(message); + } + + Matrix4 modelView = lookAt(this.focusLat, this.focusLon, focusPoint.length(), + this.eyeDist, this.heading, this.pitch); + + Point eye = modelView.getInverse().transform(new Point(0, 0, 0, 1)); + + Position polarEye = globe.computePositionFromPoint(eye); + Point surfacePoint = computeSurfacePoint(dc, polarEye.getLatitude(), polarEye.getLongitude()); + if (surfacePoint != null) + { + double distanceToSurface = eye.length() - this.collisionRadius - surfacePoint.length(); + if (distanceToSurface < 0) + { + Point surfaceNormal = eye.normalize(); + Point newEye = Point.fromOriginAndDirection(eye.length() - distanceToSurface, surfaceNormal, + Point.ZERO); + Point forward = eye.subtract(focusPoint); + Point newForward = newEye.subtract(focusPoint); + double dot = forward.dot(newForward) / (forward.length() * newForward.length()); + if (dot >= -1 && dot <= 1) + { + double pitchChange = Math.acos(dot); + this.pitch = this.clampPitch(this.pitch.subtract(Angle.fromRadians(pitchChange))); + this.eyeDist = this.clampZoom(newForward.length()); + modelView = lookAt(this.focusLat, this.focusLon, focusPoint.length(), this.eyeDist, + this.heading, this.pitch); + } + } + } + + // Compute the current eye altitude above sea level (Globe radius). + eye = modelView.getInverse().transform(new Point(0, 0, 0, 1)); + polarEye = globe.computePositionFromPoint(eye); + this.altitude = eye.length() - globe.getRadiusAt(polarEye.getLatitude(), polarEye.getLongitude()); + + return modelView; + } + + private static Matrix4 lookAt(Angle focusX, Angle focusY, double focusDistance, + double tiltDistance, Angle tiltZ, Angle tiltX) + { + Matrix4 m = new Matrix4(); + // Translate model away from eye. + m.translate(0, 0, -tiltDistance); + // Apply tilt by rotating about X axis at pivot point. + m.rotateX(tiltX.multiply(-1)); + m.rotateZ(tiltZ); + m.translate(0, 0, -focusDistance); + // Rotate model to lat/lon of eye point. + m.rotateX(focusX); + m.rotateY(focusY.multiply(-1)); + return m; + } + + private static Point computeSurfacePoint(DrawContext dc, Angle lat, Angle lon) + { + Point p = null; + + SectorGeometryList geom = dc.getSurfaceGeometry(); + if (geom != null) + p = geom.getSurfacePoint(lat, lon); + if (p != null) + return p; + + Globe globe = dc.getGlobe(); + if (globe != null) + { + double elevation = dc.getVerticalExaggeration() * globe.getElevation(lat, lon); + p = globe.computePointFromPosition(lat, lon, elevation); + } + + return p; + } + + public Frustum getFrustum() + { + if (this.viewFrustum == null) + return null; + return this.viewFrustum.getFrustum(); + } + + private ViewFrustum computeViewFrustum(DrawContext dc, Point eyePoint) + { + java.awt.Rectangle viewport = this.getViewport(); + Angle fov = this.getFieldOfView(); + if (viewport == null || fov == null) + return null; + // Compute the most distant near clipping plane. + double tanHalfFov = fov.tanHalfAngle(); + double near = Math.max(10, this.altitude / (2 * Math.sqrt(2 * tanHalfFov * tanHalfFov + 1))); + // Compute the closest allowable far clipping plane distance. + double far = this.computeHorizonDistance(dc.getGlobe(), dc.getVerticalExaggeration(), eyePoint); + // Compute the frustum from a standard perspective projection. + return new ViewFrustum(fov, viewport.width, viewport.height, near, far); + } + + private double computeCollisionRadius(ViewFrustum viewFrustum) + { + java.awt.Rectangle viewport = this.getViewport(); + Angle fov = this.getFieldOfView(); + if (viewport == null + || fov == null + || viewFrustum == null + || viewFrustum.getFrustum() == null + || viewFrustum.getFrustum().getNear() == null) + return 1; + + double near = Math.abs(viewFrustum.getFrustum().getNear().getDistance()); + if (near == 0) + near = 1; + + double tanHalfFov = fov.tanHalfAngle(); + // Compute the distance between the eye, and any corner on the near clipping rectangle. + double clipRectX = near * tanHalfFov; + double clipRectY = viewport.height * clipRectX / (double) viewport.width; + return 1 + Math.sqrt(clipRectX * clipRectX + clipRectY * clipRectY + near * near); + } + + public Position getPosition() + { + return new Position(this.focusLat, this.focusLon, 0); + } + + public void goToLatLon(LatLon newLatLon) + { + if (newLatLon == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.LatLonIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + LatLon clampedLatLon = this.clampCoordinate(newLatLon); + this.focusLat = clampedLatLon.getLatitude(); + this.focusLon = clampedLatLon.getLongitude(); + } + + public double getAltitude() + { + return this.altitude; + } + + public void goToAltitude(double newAltitude) + { + throw new UnsupportedOperationException(); + } + + public void goToCoordinate(LatLon newLatLon, double newAltitude) + { + throw new UnsupportedOperationException(); + } + + private LatLon clampCoordinate(LatLon latLon) + { + if (latLon == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.LatLonIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + double lat = latLon.getLatitude().getDegrees(); + if (lat < -90) + lat = -90; + else if (lat > 90) + lat = 90; + + double lon = latLon.getLongitude().getDegrees(); + if (lon < -180) + lon = lon + 360; + else if (lon > 180) + lon = lon - 360; + + return LatLon.fromDegrees(lat, lon); + } + + public Angle getHeading() + { + return this.heading; + } + + public void setHeading(Angle newHeading) + { + if (newHeading == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + this.heading = this.clampHeading(newHeading); + } + + private Angle clampHeading(Angle heading) + { + if (heading == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + double degrees = heading.getDegrees(); + if (degrees < 0) + degrees = degrees + 360; + else if (degrees > 360) + degrees = degrees - 360; + return Angle.fromDegrees(degrees); + } + + public Angle getPitch() + { + return this.pitch; + } + + public void setPitch(Angle newPitch) + { + if (newPitch == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + this.pitch = this.clampPitch(newPitch); + } + + public Angle[] getPitchConstraints() + { + return new Angle[] {this.minPitch, this.maxPitch}; + } + + public void setPitchConstraints(Angle newMinPitch, Angle newMaxPitch) + { + if (newMinPitch.compareTo(newMaxPitch) < 0) + { + String message = WorldWind.retrieveErrMsg("BasicOrbitView.InvalidConstraints"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + this.minPitch = newMinPitch; + this.maxPitch = newMaxPitch; + } + + public boolean isEnablePitchConstraints() + { + return this.enablePitchConstraint; + } + + public void setEnablePitchConstraints(boolean enabled) + { + this.enablePitchConstraint = enabled; + } + + private Angle clampPitch(Angle pitch) + { + Angle[] constraints = this.getPitchConstraints(); + if (pitch.compareTo(constraints[0]) < 0) + this.pitch = constraints[0]; + else if (pitch.compareTo(constraints[1]) > 0) + this.pitch = constraints[1]; + return this.pitch = pitch; + } + + public Angle getRoll() + { + return Angle.ZERO; + } + + public void setRoll(Angle newRoll) + { + } + + public double getZoom() + { + return this.eyeDist; + } + + public void setZoom(double newZoom) + { + this.eyeDist = this.clampZoom(newZoom); + } + + public double[] getZoomConstraints() + { + return new double[] {Math.max(this.minEyeDist, this.collisionRadius), this.maxEyeDist}; + } + + public void setZoomConstraints(double newMinZoom, double newMaxZoom) + { + if (newMinZoom < 0 || newMinZoom > newMaxZoom) + { + String message = WorldWind.retrieveErrMsg("BasicOrbitView.InvalidConstraints"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + this.minEyeDist = newMinZoom; + this.maxEyeDist = newMaxZoom; + } + + public boolean isEnableZoomConstraints() + { + return this.enableZoomConstraint; + } + + public void setEnableZoomConstraints(boolean enabled) + { + this.enableZoomConstraint = enabled; + } + + private double clampZoom(double zoom) + { + double x = zoom; + double[] constraints = this.getZoomConstraints(); + if (x < constraints[0]) + x = constraints[0]; + else if (x > constraints[1]) + x = constraints[1]; + return x; + } + + public LatLon computeVisibleLatLonRange() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/gov/nasa/worldwind/BasicRetrievalService.java b/gov/nasa/worldwind/BasicRetrievalService.java new file mode 100644 index 0000000..c2fa65f --- /dev/null +++ b/gov/nasa/worldwind/BasicRetrievalService.java @@ -0,0 +1,505 @@ +/* +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; + +/** + * Performs threaded retrieval of data. + * + * @author Tom Gaskins + * @version $Id: BasicRetrievalService.java 1792 2007-05-08 21:28:37Z tgaskins $ + */ +public final class BasicRetrievalService extends WWObjectImpl + implements RetrievalService, Thread.UncaughtExceptionHandler +{ + // These constants are last-ditch values in case Configuration lacks defaults + private static final int DEFAULT_QUEUE_SIZE = 100; + private static final int DEFAULT_POOL_SIZE = 5; + private static final long DEFAULT_STALE_REQUEST_LIMIT = 30000; // milliseconds + private static final int DEFAULT_TIME_PRIORITY_GRANULARITY = 500; // milliseconds + + private static final String RUNNING_THREAD_NAME_PREFIX = WorldWind.retrieveMessage( + "BasicRetrievalService.RUNNING_THREAD_NAME_PREFIX", "ThreadStrings"); + private static final String IDLE_THREAD_NAME_PREFIX = WorldWind.retrieveMessage( + "BasicRetrievalService.IDLE_THREAD_NAME_PREFIX", "ThreadStrings"); + + private RetrievalExecutor executor; // thread pool for running retrievers + private java.util.concurrent.ConcurrentLinkedQueue activeTasks; // tasks currently allocated a thread + private int queueSize; // maximum queue size + + /** + * Encapsulates a single threaded retrieval as a {@link java.util.concurrent.FutureTask}. + */ + private static class RetrievalTask extends java.util.concurrent.FutureTask + implements RetrievalFuture, Comparable + { + private Retriever retriever; + private double priority; // retrieval secondary priority (primary priority is submit time) + + private RetrievalTask(Retriever retriever, double priority) + { + super(retriever); + this.retriever = retriever; + this.priority = priority; + } + + public double getPriority() + { + return priority; + } + + public Retriever getRetriever() + { + return this.retriever; + } + + public void setException(Throwable throwable) + { + super.setException(throwable); + } + + @Override + public void run() + { + if (this.isDone() || this.isCancelled()) + return; + + super.run(); + } + + /** + * @param that the task to compare with this one + * @return 0 if task priorities are equal, -1 if priority of this is less than that, 1 otherwise + * @throws IllegalArgumentException if that is null + */ + public int compareTo(RetrievalTask that) + { + if (that == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.RetrieverIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + if (this.priority > 0 && that.priority > 0) // only secondary priority used if either is negative + { + // Requests submitted within different time-granularity periods are ordered exclusive of their + // client-specified priority. + long now = System.currentTimeMillis(); + long thisElapsedTime = now - this.retriever.getSubmitTime(); + long thatElapsedTime = now - that.retriever.getSubmitTime(); + if (((thisElapsedTime - thatElapsedTime) / DEFAULT_TIME_PRIORITY_GRANULARITY) != 0) + return thisElapsedTime < thatElapsedTime ? -1 : 1; + } + + // The client-pecified priority is compared for requests submitted within the same granularity period. + return this.priority == that.priority ? 0 : this.priority < that.priority ? -1 : 1; + } + + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final RetrievalTask that = (RetrievalTask) o; + + // Tasks are equal if their retrievers are equivalent + return this.retriever.equals(that.retriever); + // Priority and submint time are not factors in equality + } + + public int hashCode() + { + return this.retriever.getName().hashCode(); + } + } + + public void uncaughtException(Thread thread, Throwable throwable) + { + String message = WorldWind.retrieveErrMsg("BasicRetrievalService.UncaughtExceptionDuringRetrieval") + + thread.getName(); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + } + + private class RetrievalExecutor extends java.util.concurrent.ThreadPoolExecutor + { + private static final long THREAD_TIMEOUT = 2; // keep idle threads alive this many seconds + private long staleRequestLimit; // reject requests older than this + + private RetrievalExecutor(int poolSize, int queueSize) + { + super(poolSize, poolSize, THREAD_TIMEOUT, java.util.concurrent.TimeUnit.SECONDS, + new java.util.concurrent.PriorityBlockingQueue(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(BasicRetrievalService.this); + return thread; + } + }, new java.util.concurrent.ThreadPoolExecutor.DiscardPolicy() // abandon task when queue is full + { + // This listener is invoked only when the executor queue is a bounded queue and runs out of room. + // If the queue is a java.util.concurrent.PriorityBlockingQueue, this listener is never invoked. + public void rejectedExecution(Runnable runnable, + java.util.concurrent.ThreadPoolExecutor threadPoolExecutor) + { + // Interposes logging for rejected execution + RetrievalTask task = (RetrievalTask) runnable; + String name = task.getRetriever().getName(); + String message = WorldWind.retrieveErrMsg("BasicRetrievalService.ResourceRejected") + name; + WorldWind.logger().log(java.util.logging.Level.FINER, message); + + super.rejectedExecution(runnable, threadPoolExecutor); + } + }); + + this.staleRequestLimit = Configuration.getLongValue(AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT, + DEFAULT_STALE_REQUEST_LIMIT); + } + + /** + * @param thread the thread the task is running on + * @param runnable the Retriever running on the thread + * @throws IllegalArgumentException if either thread or runnable is null + */ + protected void beforeExecute(Thread thread, Runnable runnable) + { + WorldWind.logger().log(java.util.logging.Level.FINEST, WorldWind.retrieveErrMsg( + "BasicRetrievalService.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); + } + + RetrievalTask task = (RetrievalTask) runnable; + + task.retriever.setBeginTime(System.currentTimeMillis()); + if (task.retriever.getBeginTime() - task.retriever.getSubmitTime() > this.staleRequestLimit) + { + // Task has been sitting on the queue too long + String message = WorldWind.retrieveErrMsg("BasicRetrievalService.CancellingTooOldRetrieval") + + task.getRetriever().getName(); + WorldWind.logger().log(java.util.logging.Level.FINER, message); + task.cancel(true); + } + + if (BasicRetrievalService.this.activeTasks.contains(task)) + { + // Task is a duplicate + String message = WorldWind.retrieveErrMsg("BasicRetrievalService.CancellingDuplicateRetrieval") + task + .getRetriever().getName(); + WorldWind.logger().log(java.util.logging.Level.FINER, message); + task.cancel(true); + } + + BasicRetrievalService.this.activeTasks.add(task); + + thread.setName(RUNNING_THREAD_NAME_PREFIX + task.getRetriever().getName()); + thread.setPriority(Thread.MIN_PRIORITY); // Subordinate thread priority to rendering + thread.setUncaughtExceptionHandler(BasicRetrievalService.this); + + super.beforeExecute(thread, runnable); + + WorldWind.logger().log(java.util.logging.Level.FINEST, WorldWind.retrieveErrMsg( + "BasicRetrievalService.LeavingBeforeExecute")); + } + + /** + * @param runnable the Retriever running on the thread + * @param throwable an exception thrown during retrieval, will be null if no exception occurred + * @throws IllegalArgumentException if runnable is null + */ + protected void afterExecute(Runnable runnable, Throwable throwable) + { + WorldWind.logger().log(java.util.logging.Level.FINEST, WorldWind.retrieveErrMsg( + "BasicRetrievalService.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); + + RetrievalTask task = (RetrievalTask) runnable; + BasicRetrievalService.this.activeTasks.remove(task); + task.retriever.setEndTime(System.currentTimeMillis()); + + try + { + if (throwable != null) + { + String message = WorldWind.retrieveErrMsg("BasicRetrievalService.ExceptionDuringRetrieval") + task + .getRetriever().getName(); + WorldWind.logger().log(java.util.logging.Level.FINE, message, throwable); + } + + task.get(); // Wait for task to finish, cancel or break + } + catch (java.util.concurrent.ExecutionException e) + { + String message = WorldWind.retrieveErrMsg("BasicRetrievalService.ExecutionExceptionDuringRetrieval") + + task.getRetriever().getName(); + if (e.getCause() instanceof java.net.SocketTimeoutException) + { + WorldWind.logger() + .log(java.util.logging.Level.FINE, message + " " + e.getCause().getLocalizedMessage()); + } + else + { + WorldWind.logger().log(java.util.logging.Level.FINE, message, e); + } + } + catch (InterruptedException e) + { + String message = WorldWind.retrieveErrMsg("BasicRetrievalService.RetrievalOf_1") + + task.getRetriever().getName() + WorldWind + .retrieveErrMsg("BasicRetrievalService.WasInterrupted_2"); + + WorldWind.logger().log(java.util.logging.Level.FINE, message, e); + } + catch (java.util.concurrent.CancellationException e) + { + String message = WorldWind.retrieveErrMsg("BasicRetrievalService.RetrievalOf_1") + + task.getRetriever().getName() + WorldWind.retrieveErrMsg("BasicRetrievalService.WasCancelled_2"); + + WorldWind.logger().log(java.util.logging.Level.FINE, message); + } + finally + { + + Thread.currentThread().setName(IDLE_THREAD_NAME_PREFIX); + + WorldWind.logger().log(java.util.logging.Level.FINEST, WorldWind.retrieveErrMsg( + "BasicRetrievalService.LeavingAfterExecute")); + } + } + } + + public BasicRetrievalService() + { + Integer poolSize = Configuration.getIntegerValue(AVKey.RETRIEVAL_POOL_SIZE, DEFAULT_POOL_SIZE); + this.queueSize = Configuration.getIntegerValue(AVKey.RETRIEVAL_QUEUE_SIZE, DEFAULT_QUEUE_SIZE); + + // this.executor runs the retrievers, each in their own thread + this.executor = new RetrievalExecutor(poolSize, this.queueSize); + + // this.activeTasks holds the list of currently executing tasks (*not* those pending on the queue) + this.activeTasks = new java.util.concurrent.ConcurrentLinkedQueue(); + } + + /** + * @param retriever the retriever to run + * @return a future object that can be used to query the request status of cancel the request. + * @throws IllegalArgumentException if retrieer is null or has no name + */ + public RetrievalFuture runRetriever(Retriever retriever) + { + if (retriever == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.RetrieverIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (retriever.getName() == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.RetrieverNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + // Add with secondary priority that removes most recently added requests first. + return this.runRetriever(retriever, (double) (Long.MAX_VALUE - System.currentTimeMillis())); + } + + /** + * @param retriever the retriever to run + * @param priority the secondary priority of the retriever, or negative if it is to be the primary priority + * @return a future object that can be used to query the request status of cancel the request. + * @throws IllegalArgumentException if retriever is null or has no name + */ + public synchronized RetrievalFuture runRetriever(Retriever retriever, double priority) + { + if (retriever == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.RetrieverIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (retriever.getName() == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.RetrieverNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (this.isFull()) + { + String name = retriever.getName(); + String message = WorldWind.retrieveErrMsg("BasicRetrievalService.ResourceRejected") + name; + WorldWind.logger().log(java.util.logging.Level.FINER, message); + } + + RetrievalTask task = new RetrievalTask(retriever, priority); + retriever.setSubmitTime(System.currentTimeMillis()); + + // Do not queue duplicates. + if (this.activeTasks.contains(task) || this.executor.getQueue().contains(task)) + return null; + + this.executor.execute(task); + + return task; + } + + /** + * @param poolSize the number of threads in the thread pool + * @throws IllegalArgumentException if poolSize is non-positive + */ + public void setRetrieverPoolSize(int poolSize) + { + if (poolSize < 1) + { + String message = WorldWind.retrieveErrMsg("BasicRetrievalService.RetrieverPoolSizeIsLessThanOne"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + this.executor.setCorePoolSize(poolSize); + this.executor.setMaximumPoolSize(poolSize); + } + + public int getRetrieverPoolSize() + { + return this.executor.getCorePoolSize(); + } + + private boolean hasRetrievers() + { + Thread[] threads = new Thread[Thread.activeCount()]; + int numThreads = Thread.enumerate(threads); + for (int i = 0; i < numThreads; i++) + { + if (threads[i].getName().startsWith(RUNNING_THREAD_NAME_PREFIX)) + return true; + } + return false; + } + + public boolean hasActiveTasks() + { + return this.hasRetrievers(); + } + + public boolean isFull() + { + return this.executor.getQueue().size() >= this.queueSize; + } + + public int getNumRetrieversPending() + { + // Could use same method to determine active tasks as hasRetrievers() above, but this method only advisory. + return this.activeTasks.size() + this.executor.getQueue().size(); + } + + /** + * @param retriever the retriever to check + * @return true if the retriever is being run or pending execution + * @throws IllegalArgumentException if retriever is null + */ + public boolean contains(Retriever retriever) + { + if (retriever == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.RetrieverIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + RetrievalTask task = new RetrievalTask(retriever, 0d); + return (this.activeTasks.contains(task) || this.executor.getQueue().contains(task)); + } + + public double getProgress() + { + int totalContentLength = 0; + int totalBytesRead = 0; + + for (RetrievalTask task : this.activeTasks) + { + if (task.isDone()) + continue; + + Retriever retriever = task.getRetriever(); + try + { + double tcl = retriever.getContentLength(); + if (tcl > 0) + { + totalContentLength += tcl; + totalBytesRead += retriever.getContentLengthRead(); + } + } + catch (Exception e) + { + String message = WorldWind.retrieveErrMsg("BasicRetrievalService.ExcptnRetrievingContentSizes") + ( + retriever.getName() != null ? retriever.getName() : ""); + WorldWind.logger().log(java.util.logging.Level.FINE, message, e); + } + } + + for (Runnable runnable : this.executor.getQueue()) + { + gov.nasa.worldwind.BasicRetrievalService.RetrievalTask task = + (gov.nasa.worldwind.BasicRetrievalService.RetrievalTask) runnable; + + Retriever retriever = task.getRetriever(); + try + { + double tcl = retriever.getContentLength(); + if (tcl > 0) + { + totalContentLength += tcl; + totalBytesRead += retriever.getContentLengthRead(); + } + } + catch (Exception e) + { + String message = WorldWind.retrieveErrMsg("BasicRetrievalService.ExcptnRetrievingContentSizes") + ( + retriever.getName() != null ? retriever.getName() : ""); + WorldWind.logger().log(java.util.logging.Level.FINE, message, e); + } + } + + // Compute an aggregated progress notification. + + double progress; + + if (totalContentLength < 1) + progress = 0; + else + progress = Math.min(100.0, 100.0 * (double) totalBytesRead / (double) totalContentLength); + + return progress; + } +} diff --git a/gov/nasa/worldwind/BasicSceneController.java b/gov/nasa/worldwind/BasicSceneController.java new file mode 100644 index 0000000..a74ef60 --- /dev/null +++ b/gov/nasa/worldwind/BasicSceneController.java @@ -0,0 +1,228 @@ +/* +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: BasicSceneController.java 1792 2007-05-08 21:28:37Z tgaskins $ + */ +public class BasicSceneController extends WWObjectImpl implements SceneController +{ + private Model model; + private View view; + private FrameController frameController; + private double verticalExaggeration = 1d; + private DrawContext dc = new DrawContextImpl(); + private gov.nasa.worldwind.PickedObjectList lastPickedObjects; + + // These are for tracking performance + private long frame = 0; + private long timebase = System.currentTimeMillis(); + private double framesPerSecond; + private double frameTime; + + + public BasicSceneController() + { + // Establish a default frame controller and view. + this.setFrameController((FrameController) WorldWind.createConfigurationComponent( + AVKey.FRAME_CONTROLLER_CLASS_NAME)); + + this.setVerticalExaggeration(Configuration.getDoubleValue(AVKey.VERTICAL_EXAGGERATION, 1d)); + } + + public FrameController getFrameController() + { + return this.frameController; + } + + public Model getModel() + { + return this.model; + } + + public View getView() + { + return this.view; + } + + /** + * @param frameController the frame controller + * @throws IllegalArgumentException if frameController is null + */ + public void setFrameController(FrameController frameController) + { + if (frameController == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.FrameControllerIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + FrameController oldFrameControler = this.frameController; + this.frameController = frameController; + this.firePropertyChange(AVKey.FRAME_CONTROLLER, oldFrameControler, frameController); + } + + public void setModel(Model model) + { + if (this.model != null) + this.model.removePropertyChangeListener(this); + if (model != null) + model.addPropertyChangeListener(this); + + Model oldModel = this.model; + this.model = model; + this.firePropertyChange(AVKey.MODEL, oldModel, model); + } + + public void setView(View view) + { + if (this.view != null) + this.view.removePropertyChangeListener(this); + if (view != null) + view.addPropertyChangeListener(this); + + View oldView = this.view; + this.view = view; + + this.firePropertyChange(AVKey.VIEW, oldView, view); + } + + public void setVerticalExaggeration(double verticalExaggeration) + { + Double oldVE = this.verticalExaggeration; + this.verticalExaggeration = verticalExaggeration; + this.firePropertyChange(AVKey.VERTICAL_EXAGGERATION, oldVE, verticalExaggeration); + } + + public double getVerticalExaggeration() + { + return this.verticalExaggeration; + } + + public void repaint() + { + this.frameTime = System.currentTimeMillis(); + + this.dc.initialize(javax.media.opengl.GLContext.getCurrent()); + this.dc.setModel(this.model); + this.dc.setView(this.view); + this.dc.setVerticalExaggeration(this.verticalExaggeration); + + if (this.dc.getGLContext() == null) + { + String message = WorldWind.retrieveErrMsg("BasicSceneController.GLContextNullStartRedisplay"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalStateException(message); + } + + FrameController fc = this.getFrameController(); + if (fc == null) + { + String message = WorldWind.retrieveErrMsg("BasicSceneController.NoFrameControllerStartRepaint"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalStateException(message); + } + + try + { + fc.initializeFrame(this.dc); + fc.drawFrame(this.dc); + } + catch (Throwable e) + { + String message = WorldWind.retrieveErrMsg("BasicSceneController.ExceptionDuringRendering"); + WorldWind.logger().log(java.util.logging.Level.FINE, message, e); + } + finally + { + fc.finalizeFrame(this.dc); + + ++this.frame; + long time = System.currentTimeMillis(); + this.frameTime = System.currentTimeMillis() - this.frameTime; + if (time - this.timebase > 5000) + { + this.framesPerSecond = frame * 1000d / (time - timebase); + this.timebase = time; + this.frame = 0; +// System.out.printf("average frame rate is %f\n", this.framesPerSecond); + } + } + } + + public PickedObjectList pick(java.awt.Point pickPoint) + { + if (pickPoint == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.PickPoint"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + if (this.dc.getSurfaceGeometry() == null || this.dc.getVisibleSector() == null) + { + this.lastPickedObjects = null; + return null; + } + + // PickingMode should be enabled before the initialize() method + this.dc.enablePickingMode(); + this.dc.initialize(javax.media.opengl.GLContext.getCurrent()); + this.dc.setModel(this.model); + this.dc.setView(view); + this.dc.setVerticalExaggeration(this.verticalExaggeration); + + if (this.dc.getGLContext() == null) + { + String message = WorldWind.retrieveErrMsg("BasicSceneController.GLContextNullStartPick"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalStateException(message); + } + + FrameController fc = this.getFrameController(); + if (fc == null) + { + String message = WorldWind.retrieveErrMsg("BasicSceneController.NoFrameControllerStartPick"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalStateException(message); + } + + try + { + fc.initializePicking(this.dc); + fc.pick(this.dc, pickPoint); + } + catch (Throwable e) + { + String message = WorldWind.retrieveErrMsg("BasicSceneController.ExceptionDuringPick"); + WorldWind.logger().log(java.util.logging.Level.FINE, message, e); + } + finally + { + fc.finalizePicking(this.dc); + this.dc.disablePickingMode(); + } + + return this.lastPickedObjects = new PickedObjectList(this.dc.getPickedObjects()); + } + + public PickedObjectList getPickedObjectList() + { + return this.lastPickedObjects; + } + + public double getFramesPerSecond() + { + return framesPerSecond; + } + + public double getFrameTime() + { + return frameTime; + } +} diff --git a/gov/nasa/worldwind/Cacheable.java b/gov/nasa/worldwind/Cacheable.java new file mode 100644 index 0000000..3ca5e8f --- /dev/null +++ b/gov/nasa/worldwind/Cacheable.java @@ -0,0 +1,26 @@ +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: Cacheable.java 403 2006-12-13 02:33:18Z ericdalgliesh $ + */ +public interface Cacheable +{ + // TODO: search for size queries that do not used this interface and change them to do so. + // currently (22 Nov 2006), only BasicElevationModel.addTileToCache(Tile, ShortBuffer) does not use Cacheable + + /** + * Retrieves the approximate size of this object in bytes. Implementors are encouraged to calculate the exact size + * for smaller objects, but use approximate values for objects that include such large components that the + * approximation would produce an error so small that the extra computation would be wasteful. + * + * @return this Cacheable object's size in bytes + */ + long getSizeInBytes(); +} diff --git a/gov/nasa/worldwind/Configuration.java b/gov/nasa/worldwind/Configuration.java new file mode 100644 index 0000000..0e3412d --- /dev/null +++ b/gov/nasa/worldwind/Configuration.java @@ -0,0 +1,268 @@ +/* +Copyright (C) 2001, 2006 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ +/** + * + @version $Id: Configuration.java 1798 2007-05-09 02:08:41Z dcollins $ + @author Tom Gaskins + */ +package gov.nasa.worldwind; + +import java.util.*; +import java.io.*; + +public class Configuration // Singleton +{ + private static final String CONFIG_FILE_NAME = "config/worldwind.properties"; + private static final String CONFIG_FILE_PROPERTY_KEY = "gov.nasa.worldwind.config.file"; + + private static Configuration ourInstance = new Configuration(); + + private static Configuration getInstance() + { + return ourInstance; + } + + private Properties defaultSettings = new Properties(); + private Properties customSettings; + + private Configuration() + { + initializeDefaults(); + initializeCustom(); + } + + private void initializeDefaults() + { + defaultSettings.setProperty(AVKey.DATA_FILE_CACHE_CLASS_NAME, "gov.nasa.worldwind.BasicDataFileCache"); + defaultSettings.setProperty(AVKey.DATA_FILE_CACHE_CONFIGURATION_FILE_NAME, "config/DataFileCache.xml"); + defaultSettings.setProperty(AVKey.FRAME_CONTROLLER_CLASS_NAME, "gov.nasa.worldwind.BasicFrameController"); + defaultSettings.setProperty(AVKey.GLOBE_CLASS_NAME, "gov.nasa.worldwind.globes.Earth"); + defaultSettings.setProperty(AVKey.INPUT_HANDLER_CLASS_NAME, "gov.nasa.worldwind.awt.AWTInputHandler"); + defaultSettings.setProperty(AVKey.LOGGER_NAME, "gov.nasa.worldwind"); + defaultSettings.setProperty(AVKey.MEMORY_CACHE_CLASS_NAME, "gov.nasa.worldwind.BasicMemoryCache"); + defaultSettings.setProperty(AVKey.MODEL_CLASS_NAME, "gov.nasa.worldwind.BasicModel"); + defaultSettings.setProperty(AVKey.RETRIEVAL_SERVICE_CLASS_NAME, "gov.nasa.worldwind.BasicRetrievalService"); + defaultSettings.setProperty(AVKey.SCENE_CONTROLLER_CLASS_NAME, "gov.nasa.worldwind.BasicSceneController"); + defaultSettings.setProperty(AVKey.THREADED_TASK_SERVICE_CLASS_NAME, "gov.nasa.worldwind.ThreadedTaskService"); + defaultSettings.setProperty(AVKey.VIEW_CLASS_NAME, "gov.nasa.worldwind.BasicOrbitView"); + defaultSettings.setProperty(AVKey.LAYERS_CLASS_NAMES, + "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" + ); + + defaultSettings.setProperty(AVKey.INITIAL_LATITUDE, "0"); + defaultSettings.setProperty(AVKey.RETRIEVAL_POOL_SIZE, "5"); + defaultSettings.setProperty(AVKey.RETRIEVAL_QUEUE_SIZE, "200"); + defaultSettings.setProperty(AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT, "9000"); + defaultSettings.setProperty(AVKey.THREADED_TASK_POOL_SIZE, "1"); + defaultSettings.setProperty(AVKey.THREADED_TASK_QUEUE_SIZE, "5"); + defaultSettings.setProperty(AVKey.VERTICAL_EXAGGERATION, "1"); + defaultSettings.setProperty(AVKey.CACHE_SIZE, "2000000000"); + defaultSettings.setProperty(AVKey.CACHE_LOW_WATER, "140000000"); + defaultSettings.setProperty(AVKey.URL_CONNECT_TIMEOUT, "8000"); // milliseconds + defaultSettings.setProperty(AVKey.URL_READ_TIMEOUT, "5000"); // milliseconds + } + + private void initializeCustom() + { + this.customSettings = new Properties(defaultSettings); + + String configFileName = System.getProperty(CONFIG_FILE_PROPERTY_KEY, CONFIG_FILE_NAME); + try + { + InputStream propsStream = this.getClass().getResourceAsStream("/" + configFileName); + if (propsStream == null) + { + File propsFile = new File(configFileName); + if (propsFile.exists()) + { + propsStream = new FileInputStream(propsFile); + } + else + { + String message = WorldWind.retrieveErrMsg("Configuration.UnavailablePropsFile" + configFileName); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + } + } + + if (propsStream != null) + { + this.customSettings.load(propsStream); + } + } + catch (FileNotFoundException e) + { + String message = WorldWind.retrieveErrMsg("Configuration.UnavailablePropsFile" + configFileName); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + } + catch (IOException e) + { + String message = WorldWind.retrieveErrMsg("Configuration.ExceptionReadingPropsFile" + configFileName); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + } + catch (Exception e) + { + String message = WorldWind.retrieveErrMsg("Configuration.ExceptionReadingPropsFile" + configFileName + + " " + e.getLocalizedMessage()); + WorldWind.logger().log(java.util.logging.Level.FINE, message, e); + } + } + + public static String getStringValue(String key, String defaultValue) + { + String v = getStringValue(key); + return v != null ? v : defaultValue; + } + + public static String getStringValue(String key) + { + return getInstance().customSettings.getProperty(key); + } + + public static Integer getIntegerValue(String key, Integer defaultValue) + { + Integer v = getIntegerValue(key); + return v != null ? v : defaultValue; + } + + public static Integer getIntegerValue(String key) + { + String v = getStringValue(key); + if (v == null) + return null; + + try + { + return Integer.parseInt(v); + } + catch (NumberFormatException e) + { + String message = WorldWind.retrieveErrMsg("Configuration.ConversionError" + v); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + + return null; + } + } + + public static Long getLongValue(String key, Long defaultValue) + { + Long v = getLongValue(key); + return v != null ? v : defaultValue; + } + + public static Long getLongValue(String key) + { + String v = getStringValue(key); + if (v == null) + return null; + + try + { + return Long.parseLong(v); + } + catch (NumberFormatException e) + { + String message = WorldWind.retrieveErrMsg("Configuration.ConversionError" + v); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + + return null; + } + } + + public static Double getDoubleValue(String key, Double defaultValue) + { + Double v = getDoubleValue(key); + return v != null ? v : defaultValue; + } + + public static Double getDoubleValue(String key) + { + String v = getStringValue(key); + if (v == null) + return null; + + try + { + return Double.parseDouble(v); + } + catch (NumberFormatException e) + { + String message = WorldWind.retrieveErrMsg("Configuration.ConversionError" + v); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + + return null; + } + } + + public static boolean hasKey(String key) + { + return getInstance().customSettings.contains(key); + } + + public static void removeKey(String key) + { + getInstance().customSettings.remove(key); + } + + public static void setValue(String key, Object value) + { + getInstance().customSettings.put(key, value.toString()); + } + + // OS, user, and run-time specific system properties. // + + public static String currentWorkingDirectory() + { + String dir = System.getProperty("user.dir"); + return (dir != null) ? dir : "."; + } + + public static String getUserHomeDirectory() + { + String dir = System.getProperty("user.home"); + return (dir != null) ? dir : "."; + } + + public static String systemTempDirectory() + { + String dir = System.getProperty("java.io.tmpdir"); + return (dir != null) ? dir : "."; + } + + public static boolean isMacOS() + { + String osName = System.getProperty("os.name"); + return osName != null && osName.toLowerCase().contains("mac"); + } + + public static boolean isWindowsOS() + { + String osName = System.getProperty("os.name"); + return osName != null && osName.toLowerCase().contains("windows"); + } + + public static boolean isLinuxOS() + { + String osName = System.getProperty("os.name"); + return osName != null && osName.toLowerCase().contains("linux"); + } + + public static boolean isUnixOS() + { + String osName = System.getProperty("os.name"); + return osName != null && osName.toLowerCase().contains("unix"); + } + + public static boolean isSolarisOS() + { + String osName = System.getProperty("os.name"); + return osName != null && osName.toLowerCase().contains("solaris"); + } +} diff --git a/gov/nasa/worldwind/DDSConverter.java b/gov/nasa/worldwind/DDSConverter.java new file mode 100644 index 0000000..71587dd --- /dev/null +++ b/gov/nasa/worldwind/DDSConverter.java @@ -0,0 +1,844 @@ +/* +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: DDSConverter.java 1614 2007-04-23 22:38:20Z garakl $ + */ +public class DDSConverter +{ + static final int DDSD_CAPS = 0x0001; + static final int DDSD_HEIGHT = 0x0002; + static final int DDSD_WIDTH = 0x0004; + static final int DDSD_PIXELFORMAT = 0x1000; + static final int DDSD_MIPMAPCOUNT = 0x20000; + static final int DDSD_LINEARSIZE = 0x80000; + static final int DDPF_FOURCC = 0x0004; + static final int DDSCAPS_TEXTURE = 0x1000; + + protected static class Color + { + private int r, g, b; + + public Color() + { + this.r = this.g = this.b = 0; + } + + public Color(int r, int g, int b) + { + this.r = r; + this.g = g; + this.b = b; + } + + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final gov.nasa.worldwind.DDSConverter.Color color = (gov.nasa.worldwind.DDSConverter.Color) o; + + if (b != color.b) + return false; + if (g != color.g) + return false; + //noinspection RedundantIfStatement + if (r != color.r) + return false; + + return true; + } + + public int hashCode() + { + int result; + result = r; + result = 29 * result + g; + result = 29 * result + b; + return result; + } + } + + /** + * @param image + * @param mimeType + * @return + * @throws IllegalArgumentException if either image or mimeType is null or if + * mimeType does not yield a valid suffix + * @throws java.io.IOException + */ + public static java.nio.ByteBuffer convertToDxt1NoTransparency(java.nio.ByteBuffer image, String mimeType) + throws java.io.IOException + { + if (image == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.ByteBufferIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (mimeType == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.MimeTypeIsNull"); + gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + String suffix = WWIO.getSuffixForMimeType(mimeType); + if (suffix == null) + { + String message = WorldWind.retrieveErrMsg("DDSConverter.UnsupportedMimeType") + mimeType; + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + java.io.File tempFile = WWIO.saveBufferToTempFile(image, suffix); + + return convertToDxt1NoTransparency(tempFile); + } + + /** + * @param file + * @return + * @throws IllegalArgumentException if either file is null, does not exist, or cannot be read + * @throws java.io.IOException + */ + public static java.nio.ByteBuffer convertToDxt1NoTransparency(java.io.File file) throws java.io.IOException + { + if (file == null) + { // + String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (!file.exists() || !file.canRead()) + { + String message = WorldWind.retrieveErrMsg("DDSConverter.NoFileOrNoPermission"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file); + + if (image == null) + { + return null; + } + +// // Change its orientation to that of OpenGL. +// java.awt.geom.AffineTransform xform = java.awt.geom.AffineTransform.getScaleInstance(1, -1); +// xform.translate(0, -image.getHeight(null)); +// java.awt.image.AffineTransformOp op = new java.awt.image.AffineTransformOp( +// xform, java.awt.image.AffineTransformOp.TYPE_NEAREST_NEIGHBOR); +// image = op.filter(image, null); + + int[] pixels = new int[16]; + int bufferSize = 128 + image.getWidth() * image.getHeight() / 2; + java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocate(bufferSize); + buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN); + buildHeaderDxt1(buffer, image.getWidth(), image.getHeight()); + + int numTilesWide = image.getWidth() / 4; + int numTilesHigh = image.getHeight() / 4; + for (int i = 0; i < numTilesHigh; i++) + { + for (int j = 0; j < numTilesWide; j++) + { + java.awt.image.BufferedImage originalTile = image.getSubimage(j * 4, i * 4, 4, 4); + originalTile.getRGB(0, 0, 4, 4, pixels, 0, 4); + Color[] colors = getColors888(pixels); + + for (int k = 0; k < pixels.length; k++) + { + pixels[k] = getPixel565(colors[k]); + colors[k] = getColor565(pixels[k]); + } + + int[] extremaIndices = determineExtremeColors(colors); + if (pixels[extremaIndices[0]] < pixels[extremaIndices[1]]) + { + int t = extremaIndices[0]; + extremaIndices[0] = extremaIndices[1]; + extremaIndices[1] = t; + } + + buffer.putShort((short) pixels[extremaIndices[0]]); + buffer.putShort((short) pixels[extremaIndices[1]]); + + long bitmask = computeBitMask(colors, extremaIndices); + buffer.putInt((int) bitmask); + } + } + + return buffer; + } + + /** + * @param image + * @param mimeType + * @return + * @throws IllegalArgumentException if either image or mimeType is null, or if + * mimeType does not yield a valid suffix + * @throws java.io.IOException + */ + public static java.nio.ByteBuffer convertToDxt1WithTransparency(java.nio.ByteBuffer image, String mimeType) + throws java.io.IOException + { + if (image == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.ByteBufferIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (mimeType == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.MimeTypeIsNull"); + gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + String suffix = WWIO.getSuffixForMimeType(mimeType); + if (suffix == null) + { + String message = WorldWind.retrieveErrMsg("DDSConverter.UnsupportedMimeType") + mimeType; + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + java.io.File tempFile = WWIO.saveBufferToTempFile(image, suffix); + + return convertToDxt1WithTransparency(tempFile); + } + + /** + * @param image + * @param mimeType + * @return + * @throws IllegalArgumentException if either image or mimeType is null, or if + * mimeType does not yield a valid suffix + * @throws java.io.IOException + */ + public static java.nio.ByteBuffer convertToDxt3(java.nio.ByteBuffer image, String mimeType) + throws java.io.IOException + { + if (image == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.ByteBufferIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (mimeType == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.MimeTypeIsNull"); + gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + String suffix = WWIO.getSuffixForMimeType(mimeType); + if (suffix == null) + { + String message = WorldWind.retrieveErrMsg("DDSConverter.UnsupportedMimeType") + mimeType; + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + java.io.File tempFile = WWIO.saveBufferToTempFile(image, suffix); + + return convertToDxt3(tempFile); + } + + /** + * @param file + * @return + * @throws IllegalArgumentException if either file is null, does not exist, or cannot be read + * @throws java.io.IOException + */ + public static java.nio.ByteBuffer convertToDxt3(java.io.File file) throws java.io.IOException + { + if (file == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (!file.exists() || !file.canRead()) + { + String message = WorldWind.retrieveErrMsg("DDSConverter.NoFileOrNoPermission"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file); + if (image == null) + { + return null; + } + +// // Change its orientation to that of OpenGL. +// java.awt.geom.AffineTransform xform = java.awt.geom.AffineTransform.getScaleInstance(1, -1); +// xform.translate(0, -image.getHeight(null)); +// java.awt.image.AffineTransformOp op = new java.awt.image.AffineTransformOp( +// xform, java.awt.image.AffineTransformOp.TYPE_NEAREST_NEIGHBOR); +// image = op.filter(image, null); + + int[] pixels = new int[16]; + int bufferSize = 128 + image.getWidth() * image.getHeight() / 2; + bufferSize *= 2; + java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocate(bufferSize); + buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN); + buildHeaderDxt3(buffer, image.getWidth(), image.getHeight()); + + int numTilesWide = image.getWidth() / 4; + int numTilesHigh = image.getHeight() / 4; + for (int i = 0; i < numTilesHigh; i++) + { + for (int j = 0; j < numTilesWide; j++) + { + long alphas = makeAlphas(pixels); + buffer.putLong(alphas); + + java.awt.image.BufferedImage originalTile = image.getSubimage(j * 4, i * 4, 4, 4); + originalTile.getRGB(0, 0, 4, 4, pixels, 0, 4); + Color[] colors = getColors888(pixels); + + for (int k = 0; k < pixels.length; k++) + { + pixels[k] = getPixel565(colors[k]); + colors[k] = getColor565(pixels[k]); + } + + int[] extremaIndices = determineExtremeColors(colors); + if (pixels[extremaIndices[0]] < pixels[extremaIndices[1]]) + { + int t = extremaIndices[0]; + extremaIndices[0] = extremaIndices[1]; + extremaIndices[1] = t; + } + + buffer.putShort((short) pixels[extremaIndices[0]]); + buffer.putShort((short) pixels[extremaIndices[1]]); + + long bitmask = computeBitMask(colors, extremaIndices); + buffer.putInt((int) bitmask); + } + } + + return buffer; + } + + private static long makeAlphas(int[] pixels) + { + long sol = 0; + for (int i = 0; i < pixels.length; i++) + { + sol |= (pixels[i] & 0xC0000000) >> (i * 2); + } + + return sol; + } + + protected static void buildHeaderDxt1(java.nio.ByteBuffer buffer, int width, int height) + { + buffer.rewind(); + buffer.put((byte) 'D'); + buffer.put((byte) 'D'); + buffer.put((byte) 'S'); + buffer.put((byte) ' '); + buffer.putInt(124); + int flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT | DDSD_LINEARSIZE; + buffer.putInt(flag); + buffer.putInt(height); + buffer.putInt(width); + buffer.putInt(width * height / 2); + buffer.putInt(0); // depth + buffer.putInt(0); // mipmap count + buffer.position(buffer.position() + 44); // 11 unused double-words + buffer.putInt(32); // pixel format size + buffer.putInt(DDPF_FOURCC); + buffer.put((byte) 'D'); + buffer.put((byte) 'X'); + buffer.put((byte) 'T'); + buffer.put((byte) '1'); + buffer.putInt(0); // bits per pixel for RGB (non-compressed) formats + buffer.putInt(0); // rgb bit masks for RGB formats + buffer.putInt(0); // rgb bit masks for RGB formats + buffer.putInt(0); // rgb bit masks for RGB formats + buffer.putInt(0); // alpha mask for RGB formats + buffer.putInt(DDSCAPS_TEXTURE); + buffer.putInt(0); // ddsCaps2 + buffer.position(buffer.position() + 12); // 3 unused double-words + } + + protected static void buildHeaderDxt3(java.nio.ByteBuffer buffer, int width, int height) + { + buffer.rewind(); + buffer.put((byte) 'D'); + buffer.put((byte) 'D'); + buffer.put((byte) 'S'); + buffer.put((byte) ' '); + buffer.putInt(124); + int flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT | DDSD_LINEARSIZE; + buffer.putInt(flag); + buffer.putInt(height); + buffer.putInt(width); + buffer.putInt(width * height / 2); + buffer.putInt(0); // depth + buffer.putInt(0); // mipmap count + buffer.position(buffer.position() + 44); // 11 unused double-words + buffer.putInt(32); // pixel format size + buffer.putInt(DDPF_FOURCC); + buffer.put((byte) 'D'); + buffer.put((byte) 'X'); + buffer.put((byte) 'T'); + buffer.put((byte) '3'); + buffer.putInt(0); // bits per pixel for RGB (non-compressed) formats + buffer.putInt(0); // rgb bit masks for RGB formats + buffer.putInt(0); // rgb bit masks for RGB formats + buffer.putInt(0); // rgb bit masks for RGB formats + buffer.putInt(0); // alpha mask for RGB formats + buffer.putInt(DDSCAPS_TEXTURE); + buffer.putInt(0); // ddsCaps2 + buffer.position(buffer.position() + 12); // 3 unused double-words + } + + protected static int[] determineExtremeColors(Color[] colors) + { + int farthest = Integer.MIN_VALUE; + int[] ex = new int[2]; + + for (int i = 0; i < colors.length - 1; i++) + { + for (int j = i + 1; j < colors.length; j++) + { + int d = distance(colors[i], colors[j]); + if (d > farthest) + { + farthest = d; + ex[0] = i; + ex[1] = j; + } + } + } + + return ex; + } + + protected static long computeBitMask(Color[] colors, int[] extremaIndices) + { + Color[] colorPoints = new Color[] {null, null, new Color(), new Color()}; + colorPoints[0] = colors[extremaIndices[0]]; + colorPoints[1] = colors[extremaIndices[1]]; + if (colorPoints[0].equals(colorPoints[1])) + return 0; + +// colorPoints[0].r = (colorPoints[0].r & 0xF8) | (colorPoints[0].r >> 5 ); +// colorPoints[0].g = (colorPoints[0].g & 0xFC) | (colorPoints[0].g >> 6 ); +// colorPoints[0].b = (colorPoints[0].b & 0xF8) | (colorPoints[0].b >> 5 ); +// +// colorPoints[1].r = (colorPoints[1].r & 0xF8) | (colorPoints[1].r >> 5 ); +// colorPoints[1].g = (colorPoints[1].g & 0xFC) | (colorPoints[1].g >> 6 ); +// colorPoints[1].b = (colorPoints[1].b & 0xF8) | (colorPoints[1].b >> 5 ); + + colorPoints[2].r = (2 * colorPoints[0].r + colorPoints[1].r + 1) / 3; + colorPoints[2].g = (2 * colorPoints[0].g + colorPoints[1].g + 1) / 3; + colorPoints[2].b = (2 * colorPoints[0].b + colorPoints[1].b + 1) / 3; + colorPoints[3].r = (colorPoints[0].r + 2 * colorPoints[1].r + 1) / 3; + colorPoints[3].g = (colorPoints[0].g + 2 * colorPoints[1].g + 1) / 3; + colorPoints[3].b = (colorPoints[0].b + 2 * colorPoints[1].b + 1) / 3; + + long bitmask = 0; + for (int i = 0; i < colors.length; i++) + { + int closest = Integer.MAX_VALUE; + int mask = 0; + for (int j = 0; j < colorPoints.length; j++) + { + int d = distance(colors[i], colorPoints[j]); + if (d < closest) + { + closest = d; + mask = j; + } + } + bitmask |= mask << i * 2; + } + + return bitmask; + } + + + + + protected static int getPixel565(Color color) + { + int r = color.r >> 3; + int g = color.g >> 2; + int b = color.b >> 3; + return r << 11 | g << 5 | b; + } + + protected static Color getColor565(int pixel) + { + Color color = new Color(); + + color.r = (int) (((long) pixel) & 0xf800) >> 11; + color.g = (int) (((long) pixel) & 0x07e0) >> 5; + color.b = (int) (((long) pixel) & 0x001f); + + return color; + } + + protected static Color getColor888(int r8g8b8) + { + return new Color( + (int) (((long) r8g8b8) & 0xff0000) >> 16, + (int) (((long) r8g8b8) & 0x00ff00) >> 8, + (int) (((long) r8g8b8) & 0x0000ff) + ); + } + + + protected static Color[] getColors888(int[] pixels) + { + Color[] colors = new Color[pixels.length]; + + for (int i = 0; i < pixels.length; i++) + { + colors[i] = new Color(); + colors[i].r = (int) (((long) pixels[i]) & 0xff0000) >> 16; + colors[i].g = (int) (((long) pixels[i]) & 0x00ff00) >> 8; + colors[i].b = (int) (((long) pixels[i]) & 0x0000ff); + } + + return colors; + } + + protected static int distance(Color ca, Color cb) + { + return (cb.r - ca.r) * (cb.r - ca.r) + (cb.g - ca.g) * (cb.g - ca.g) + (cb.b - ca.b) * (cb.b - ca.b); + } + + /** + * @param file + * @return + * @throws IllegalArgumentException if either file is null, does not exist, or cannot be read + * @throws java.io.IOException + */ + public static java.nio.ByteBuffer convertToDxt1WithTransparency(java.io.File file) throws java.io.IOException + { + /*java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file); + + int bufferSize = 128 + image.getWidth() * image.getHeight() / 2; + java.nio.ByteBuffer testBuffer = java.nio.ByteBuffer.allocate(bufferSize); + testBuffer.order(java.nio.ByteOrder.LITTLE_ENDIAN); + buildHeaderDxt1(testBuffer, image.getWidth(), image.getHeight()); + + int numTilesWide = image.getWidth() / 4; + int numTilesHigh = image.getHeight() / 4; + + for (int i = 0; i < numTilesHigh; i++) + { + for (int j = 0; j < numTilesWide; j++) + { + short min = 255; + short max = 127; + + testBuffer.putShort(min); + testBuffer.putShort(max); + + int bitmask = 0x012345678;//0xffff43cf; + testBuffer.putInt(bitmask); + } + } + + return testBuffer; +*/ + + if (file == null) + { // + String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (!file.exists() || !file.canRead()) + { + String message = WorldWind.retrieveErrMsg("DDSConverter.NoFileOrNoPermission"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file); + + if (image == null) + return null; + + int[] pixels = new int[16]; + int bufferSize = 128 + image.getWidth() * image.getHeight() / 2; + java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocate(bufferSize); + buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN); + buildHeaderDxt1(buffer, image.getWidth(), image.getHeight()); + + int numTilesWide = image.getWidth() / 4; + int numTilesHigh = image.getHeight() / 4; + for (int i = 0; i < numTilesHigh; i++) + { + for (int j = 0; j < numTilesWide; j++) + { + java.awt.image.BufferedImage originalTile = image.getSubimage(j * 4, i * 4, 4, 4); + originalTile.getRGB(0, 0, 4, 4, pixels, 0, 4); // these are in format "TYPE_INT_ARGB" 16,5,6,5 + + TransparentColor[] colors = getColors5551(pixels); + + int[] extremaIndices = determineExtremeColors(colors); + + // we want extremaIndices[0] to be greater than extremaIndices[1] + short min = getShort5551(colors[extremaIndices[0]]); + short max = getShort5551(colors[extremaIndices[1]]); + if (min > max) + { + // we want min to be less than max. + int t = extremaIndices[0]; + extremaIndices[0] = extremaIndices[1]; + extremaIndices[1] = t; + + short temp = min; + min = max; + max = temp; + } + else if (min == max) + { + // because of this case, we don't get transparency. + equalTransparentCase(colors, extremaIndices, min); + + min = getShort5551(colors[extremaIndices[0]]); + max = getShort5551(colors[extremaIndices[1]]); + } + + buffer.putShort(min); + buffer.putShort(max); + + long bitmask = computeBitMask(colors, extremaIndices); + buffer.putInt((int) bitmask); + } + } + + return buffer; + } + + protected static void equalTransparentCase(TransparentColor[] colors, int[] extremaIndices, short value) + { + // we want extremaIndices[0] to be greater than extremaIndices[1] + + if (value == 0) + { + // transparent + colors[extremaIndices[0]] = TransparentColor.OFF_TRANSPARENT; + } + /*else + { + // not transparent anywhere - it's all one colour, so we don't need to bother making changes + }*/ + + } + + protected static int distance(TransparentColor ca, TransparentColor cb) + { + return (cb.r - ca.r) * (cb.r - ca.r) + (cb.g - ca.g) * (cb.g - ca.g) + (cb.b - ca.b) * (cb.b - ca.b) + + (cb.a - ca.a) * (cb.a - ca.a); + } + +// public static void main(String[] args) +// { +// try +// { +// String fileName = "testdata/0000_0001"; +// java.nio.ByteBuffer buffer = convertToDxt1NoTransparency(new java.io.File(fileName + ".jpg")); +// buffer.rewind(); +// java.io.FileOutputStream fos = new java.io.FileOutputStream(fileName + ".dds"); +// java.nio.channels.FileChannel channel = fos.getChannel(); +// channel.write(buffer); +// } +// catch (java.io.IOException e) +// { +// e.printStackTrace(); +// } +// +// return; +// } + + protected static long computeBitMask(TransparentColor[] colors, int[] extremaIndices) + { + TransparentColor[] colorPoints = {null, null, new TransparentColor(), new TransparentColor()}; + + colorPoints[0] = colors[extremaIndices[0]]; + colorPoints[1] = colors[extremaIndices[1]]; + + colorPoints[2].r = (byte) ((colorPoints[0].r + colorPoints[1].r) / 2); + colorPoints[2].g = (byte) ((colorPoints[0].g + colorPoints[1].g) / 2); + colorPoints[2].b = (byte) ((colorPoints[0].b + colorPoints[1].b) / 2); + colorPoints[2].a = 1; + + colorPoints[3].r = 0; + colorPoints[3].g = 0; + colorPoints[3].b = 0; + colorPoints[3].a = 0; + + long bitmask = 0; + for (int i = 0; i < colors.length; i++) + { + int closest = Integer.MAX_VALUE; + int mask = 0; + if (colors[i].a == 0) + { + mask = 3; + } + else + { + for (int j = 0; j < colorPoints.length; j++) + { + int d = distance(colors[i], colorPoints[j]); + if (d < closest) + { + closest = d; + mask = j; + } + } + } + bitmask |= mask << i * 2; + } + + return bitmask; + } + + protected static short getShort5551(TransparentColor color) + { + short s = 0; + s |= ((color.r & 0x0f8) << 8) | ((color.g & 0x0f8) << 4) | ((color.b & 0x0f8) >> 3) | ((color.a & 0x0f8) >> 7); +// System.out.println(Integer.toBinaryString(s)); + return s; + } + + protected static int[] determineExtremeColors(TransparentColor[] colors) + { + int farthest = Integer.MIN_VALUE; + int[] ex = {0, 0}; + + for (int i = 0; i < colors.length - 1; i++) + { + for (int j = i + 1; j < colors.length; j++) + { + int d = distance(colors[i], colors[j]); + if (d > farthest) + { + farthest = d; + ex[0] = i; + ex[1] = j; + } + } + } + + return ex; + } + + protected static TransparentColor[] getColors5551(int[] pixels) + { + TransparentColor colors[] = new TransparentColor[pixels.length]; + + for (int i = 0; i < pixels.length; i++) + { + colors[i] = generateColor5551(pixels[i]); + } + return colors; + } + + protected static TransparentColor generateColor5551(int pixel) + { + short alpha = (short) (pixel >> 24); + if ((alpha & 0xf0) == 0) + { + return TransparentColor.TRANSPARENT; + } + + // ok, it's not transparent - that's already been ruled out. + + TransparentColor tc = new TransparentColor(); + tc.a = (byte) 0x000000ff; + tc.r = (byte) ((pixel & 0x00ff0000) >> 16); + tc.g = (byte) ((pixel & 0x0000ff00) >> 8); + tc.b = (byte) (pixel & 0x000000ff); + + return tc; + } + + protected static class TransparentColor + { + private static final TransparentColor TRANSPARENT = new TransparentColor((byte) 0, (byte) 0, (byte) 0, + (byte) 0); + private static final TransparentColor OFF_TRANSPARENT = new TransparentColor((byte) 0, (byte) 0, (byte) 1, + (byte) 0); + private byte r, g, b, a; + + private TransparentColor() + { + } + + private TransparentColor(byte r, byte g, byte b, byte a) + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final gov.nasa.worldwind.DDSConverter.TransparentColor that = + (gov.nasa.worldwind.DDSConverter.TransparentColor) o; + + if (a != that.a) + return false; + if (b != that.b) + return false; + if (g != that.g) + return false; + //noinspection RedundantIfStatement + if (r != that.r) + return false; + + return true; + } + + public int hashCode() + { + int result; + result = r; + result = 29 * result + g; + result = 29 * result + b; + result = 29 * result + a; + return result; + } + + public String toString() + { + return "TransColor argb: " + this.a + ", " + this.r + ", " + this.g + ", " + this.b; + } + } +} diff --git a/gov/nasa/worldwind/DrawContext.java b/gov/nasa/worldwind/DrawContext.java new file mode 100644 index 0000000..3f092bd --- /dev/null +++ b/gov/nasa/worldwind/DrawContext.java @@ -0,0 +1,269 @@ +/* +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.texture.*; +import gov.nasa.worldwind.geom.*; + +/** + * @author Tom Gaskins + * @version $Id: DrawContext.java 1409 2007-04-06 23:06:36Z tgaskins $ + */ +public interface DrawContext extends WWObject +{ + /** + * Assigns this DrawContext a new 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 DrawContexts javax.media.opengl.GLContext. If this method returns null, + * then there are potentially no active GLContexts and rendering should be aborted. + * + * @return this DrawContexts 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 + * CacheListeners 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 DrawContexts. + * + * @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 tracksList; + private java.util.Iterator tracks; + private java.util.Iterator segments; + private java.util.Iterator positions; + + public TrackPointIteratorImpl(java.util.List tracksList) + { + this.tracksList = tracksList; + this.reset(); + } + + public TrackPointIteratorImpl reset() + { + if (this.tracksList == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.TracksIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + this.tracks = this.tracksList.iterator(); + this.segments = null; + this.positions = null; + this.loadNextPositions(); + + return this; + } + + public boolean hasNext() + { + if (this.positions != null && this.positions.hasNext()) + return true; + + this.loadNextPositions(); + + return (this.positions != null && this.positions.hasNext()); + } + + private void loadNextPositions() + { + if (this.segments != null && this.segments.hasNext()) + { + TrackSegment segment = this.segments.next(); + this.positions = segment.getPoints().iterator(); + return; + } + + if (this.tracks.hasNext()) + { + Track track = this.tracks.next(); + this.segments = track.getSegments().iterator(); + this.loadNextPositions(); + } + } + + public TrackPoint next() + { + if (!this.hasNext()) + { + String msg = WorldWind.retrieveErrMsg("iterator.NoMoreTrackPoints"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new NoSuchElementException(msg); + } + + return this.positions.next(); + } + + public void remove() + { + String msg = WorldWind.retrieveErrMsg("iterator.RemoveNotSupported"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new UnsupportedOperationException(msg); + } + + public int getNumPoints() + { + int numPoints; + for (numPoints = 0; this.hasNext(); this.next()) + ++numPoints; + + return numPoints; + } +} diff --git a/gov/nasa/worldwind/TrackRenderer.java b/gov/nasa/worldwind/TrackRenderer.java new file mode 100644 index 0000000..23b2f5f --- /dev/null +++ b/gov/nasa/worldwind/TrackRenderer.java @@ -0,0 +1,409 @@ +/* +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 javax.media.opengl.glu.*; + +/** + * @author tag + * @version $Id$ + */ +public class TrackRenderer implements Disposable +{ + private int lowerLimit = 0; + private int upperLimit = Integer.MAX_VALUE; + private double markerPixels = 10d; // TODO: these should all be configurable + private double minMarkerSize = 5d; + private double markerElevation = 10d; + private boolean overrideMarkerElevation = false; + private Material material = Material.WHITE; + private String iconFilePath; + private final TrackRenderer.Shape SPHERE = new TrackRenderer.Sphere(); + private final TrackRenderer.Shape CONE = new TrackRenderer.Cone(); + private final TrackRenderer.Shape CYLINDER = new TrackRenderer.Cylinder(); + private TrackRenderer.Shape shape = SPHERE; + + public TrackRenderer() + { + } + + public void dispose() + { + this.CONE.dispose(); + this.CYLINDER.dispose(); + this.SPHERE.dispose(); + } + + public double getMarkerPixels() + { + return markerPixels; + } + + public void setMarkerPixels(double markerPixels) + { + this.markerPixels = markerPixels; + } + + public double getMinMarkerSize() + { + return minMarkerSize; + } + + public void setMinMarkerSize(double minMarkerSize) + { + this.minMarkerSize = minMarkerSize; + } + + public double getMarkerElevation() + { + return markerElevation; + } + + public void setMarkerElevation(double markerElevation) + { + this.markerElevation = markerElevation; + } + + public boolean isOverrideMarkerElevation() + { + return overrideMarkerElevation; + } + + public void setOverrideMarkerElevation(boolean overrideMarkerElevation) + { + this.overrideMarkerElevation = overrideMarkerElevation; + } + + public Material getMaterial() + { + return material; + } + + public void setMaterial(Material material) + { + if (material == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.MaterialIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + // don't validate material's colors - material does that. + + this.material = material; + } + + public String getIconFilePath() + { + return iconFilePath; + } + + public void setIconFilePath(String iconFilePath) + { + //don't validate - a null iconFilePath cancels icon drawing + this.iconFilePath = iconFilePath; + } + + public int getLowerLimit() + { + return this.lowerLimit; + } + + public void setLowerLimit(int lowerLimit) + { + this.lowerLimit = lowerLimit; + } + + public int getUpperLimit() + { + return this.upperLimit; + } + + public void setUpperLimit(int upperLimit) + { + this.upperLimit = upperLimit; + } + + public void setShapeType(String shapeName) + { + if (shapeName.equalsIgnoreCase("Cone")) + this.shape = CONE; + else if (shapeName.equalsIgnoreCase("Cylinder")) + this.shape = CYLINDER; + else + this.shape = SPHERE; + } + + public void pick(DrawContext dc, java.util.Iterator trackPositions, java.awt.Point pickPoint, + Layer layer) + { + // TODO: picking + } + + public Point render(DrawContext dc, java.util.Iterator trackPositions) + { + return this.draw(dc, trackPositions); + } + + private Point draw(DrawContext dc, java.util.Iterator trackPositions) + { + if (dc.getVisibleSector() == null) + return null; + + SectorGeometryList geos = dc.getSurfaceGeometry(); + if (geos == null) + return null; + + if (!this.shape.isInitialized) + this.shape.initialize(dc); + + int index = 0; + java.util.List points = new java.util.ArrayList(); + while (trackPositions.hasNext()) + { + TrackPoint tp = trackPositions.next(); + if (index >= this.lowerLimit && index <= this.upperLimit) + { + Point point = this.computeSurfacePoint(dc, tp); + if (point != null) + { + points.add(point); + } + } + if (++index >= this.upperLimit) + break; + } + + if (points.size() < 1) + return null; + + Point firstPoint = points.get(0); + Point lastPointDrawn = firstPoint; + this.begin(dc); + { + Point previousDrawnPoint = null; + + double radius = this.computeMarkerRadius(dc, firstPoint); + this.shape.render(dc, firstPoint, radius); + for (Point point : points) + { + if (previousDrawnPoint == null) + { + previousDrawnPoint = firstPoint; + continue; // skip over first point + } + + // TODO: More sophisticated separation algorithm to gain frame-to-frame consistency + radius = this.computeMarkerRadius(dc, point); + double separation = point.distanceTo(previousDrawnPoint); + double minSeparation = 4d * radius; + if (separation > minSeparation) + { + if (!dc.isPickingMode()) + this.shape.render(dc, point, radius); + + previousDrawnPoint = point; + lastPointDrawn = point; + } + } + } + this.end(dc); + + Point iconPoint = points.get(points.size() - 1); + return iconPoint != null ? iconPoint : lastPointDrawn; + } + + private Point computeSurfacePoint(DrawContext dc, TrackPoint pos) + { + return dc.getSurfaceGeometry().getSurfacePoint(Angle.fromDegrees(pos.getLatitude()), + Angle.fromDegrees(pos.getLongitude()), + this.overrideMarkerElevation ? this.markerElevation : pos.getElevation()); + } + + private double computeMarkerRadius(DrawContext dc, Point point) + { + double d = point.distanceTo(dc.getView().getEyePoint()); + double radius = this.markerPixels * dc.getView().computePixelSizeAtDistance(d); + if (radius < this.minMarkerSize) + radius = this.minMarkerSize; + + return radius; + } + + private void begin(DrawContext dc) + { + GL gl = dc.getGL(); + Point cameraPosition = dc.getView().getEyePoint(); + + gl.glPushAttrib( + GL.GL_TEXTURE_BIT | GL.GL_ENABLE_BIT | GL.GL_CURRENT_BIT | GL.GL_LIGHTING_BIT | GL.GL_TRANSFORM_BIT); + gl.glDisable(GL.GL_TEXTURE_2D); + + float[] lightPosition = + {(float) (cameraPosition.x() * 2), (float) (cameraPosition.y() / 2), (float) (cameraPosition.z()), 0.0f}; + float[] lightDiffuse = {1.0f, 1.0f, 1.0f, 1.0f}; + float[] lightAmbient = {1.0f, 1.0f, 1.0f, 1.0f}; + float[] lightSpecular = {1.0f, 1.0f, 1.0f, 1.0f}; + + this.material.apply(gl, GL.GL_FRONT); + + gl.glLightfv(GL.GL_LIGHT1, GL.GL_POSITION, lightPosition, 0); + gl.glLightfv(GL.GL_LIGHT1, GL.GL_DIFFUSE, lightDiffuse, 0); + gl.glLightfv(GL.GL_LIGHT1, GL.GL_AMBIENT, lightAmbient, 0); + gl.glLightfv(GL.GL_LIGHT1, GL.GL_SPECULAR, lightSpecular, 0); + + gl.glDisable(GL.GL_LIGHT0); + gl.glEnable(GL.GL_LIGHT1); + gl.glEnable(GL.GL_LIGHTING); + gl.glEnable(GL.GL_NORMALIZE); + + gl.glMatrixMode(javax.media.opengl.GL.GL_MODELVIEW); + gl.glPushMatrix(); + } + + private void end(DrawContext dc) + { + GL gl = dc.getGL(); + + gl.glMatrixMode(javax.media.opengl.GL.GL_MODELVIEW); + gl.glPopMatrix(); + + gl.glDisable(GL.GL_LIGHT1); + gl.glEnable(GL.GL_LIGHT0); + gl.glDisable(GL.GL_LIGHTING); + gl.glDisable(GL.GL_NORMALIZE); + gl.glPopAttrib(); + } + + private static abstract class Shape + { + protected int objectId; + protected GLUquadric quadric; + protected boolean isInitialized = false; + + abstract protected void doRender(DrawContext dc, Point point, double radius); + + protected void initialize(DrawContext dc) + { + this.objectId = dc.getGL().glGenLists(1); + this.quadric = dc.getGLU().gluNewQuadric(); + dc.getGLU().gluQuadricDrawStyle(quadric, GLU.GLU_FILL); + dc.getGLU().gluQuadricNormals(quadric, GLU.GLU_SMOOTH); + dc.getGLU().gluQuadricOrientation(quadric, GLU.GLU_OUTSIDE); + dc.getGLU().gluQuadricTexture(quadric, false); + } + + private void dispose() + { + if (this.isInitialized) + { + GLU glu = new GLU(); + glu.gluDeleteQuadric(this.quadric); + // TODO: Determine how to release the opengl object ID. + } + } + + private void render(DrawContext dc, Point point, double radius) + { + dc.getView().pushReferenceCenter(dc, point); + this.doRender(dc, point, radius); + dc.getView().popReferenceCenter(dc); + } + } + + private static class Sphere extends Shape + { + protected void initialize(DrawContext dc) + { + super.initialize(dc); + + double radius = 1; + int slices = 36; + int stacks = 18; + + dc.getGL().glNewList(this.objectId, GL.GL_COMPILE); + dc.getGLU().gluSphere(quadric, radius, slices, stacks); + dc.getGL().glEndList(); + + this.isInitialized = true; + } + + protected void doRender(DrawContext dc, Point point, double radius) + { + dc.getGL().glScaled(radius, radius, radius); + dc.getGL().glCallList(this.objectId); + } + } + + private static class Cone extends Shape + { + protected void initialize(DrawContext dc) + { + super.initialize(dc); + + int slices = 30; + int stacks = 30; + int loops = 2; + + dc.getGL().glNewList(this.objectId, GL.GL_COMPILE); + dc.getGLU().gluCylinder(quadric, 1d, 0d, 2d, slices, (int) (2 * (Math.sqrt(stacks)) + 1)); + dc.getGLU().gluDisk(quadric, 0d, 1d, slices, loops); + dc.getGL().glEndList(); + + this.isInitialized = true; + } + + protected void doRender(DrawContext dc, Point point, double size) + { + PolarPoint p = PolarPoint.fromCartesian(point); + + dc.getGL().glLoadIdentity(); + dc.getGL().glScaled(size, size, size); + dc.getGL().glRotated(p.getLongitude().getDegrees(), 0, 1, 0); + dc.getGL().glRotated(Math.abs(p.getLatitude().getDegrees()), Math.signum(p.getLatitude().getDegrees()) * -1, + 0, 0); + dc.getGL().glCallList(this.objectId); + } + } + + private static class Cylinder extends Shape + { + protected void initialize(DrawContext dc) + { + super.initialize(dc); + + int slices = 30; + int stacks = 30; + int loops = 2; + + dc.getGL().glNewList(this.objectId, GL.GL_COMPILE); + dc.getGLU().gluCylinder(quadric, 1d, 1d, 2d, slices, (int) (2 * (Math.sqrt(stacks)) + 1)); + dc.getGLU().gluDisk(quadric, 0d, 1d, slices, loops); + dc.getGL().glTranslated(0, 0, 2); + dc.getGLU().gluDisk(quadric, 0d, 1d, slices, loops); + dc.getGL().glTranslated(0, 0, -2); + dc.getGL().glEndList(); + + this.isInitialized = true; + } + + protected void doRender(DrawContext dc, Point point, double size) + { + PolarPoint p = PolarPoint.fromCartesian(point); + + dc.getGL().glLoadIdentity(); + dc.getGL().glScaled(size, size, size); + dc.getGL().glRotated(p.getLongitude().getDegrees(), 0, 1, 0); + dc.getGL().glRotated(Math.abs(p.getLatitude().getDegrees()), Math.signum(p.getLatitude().getDegrees()) * -1, + 0, 0); + dc.getGL().glCallList(this.objectId); + } + } +} diff --git a/gov/nasa/worldwind/TrackSegment.java b/gov/nasa/worldwind/TrackSegment.java new file mode 100644 index 0000000..52276fa --- /dev/null +++ b/gov/nasa/worldwind/TrackSegment.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: TrackSegment.java 288 2006-12-07 12:21:49Z tgaskins $ + */ +public interface TrackSegment +{ + java.util.List getPoints(); +} diff --git a/gov/nasa/worldwind/URLRetriever.java b/gov/nasa/worldwind/URLRetriever.java new file mode 100644 index 0000000..02c7759 --- /dev/null +++ b/gov/nasa/worldwind/URLRetriever.java @@ -0,0 +1,475 @@ +/* +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.concurrent.atomic.*; +import java.util.zip.*; +import java.nio.*; +import java.nio.channels.*; +import java.net.*; +import java.io.*; + +/** + * @author Tom Gaskins + * @version $Id: URLRetriever.java 1792 2007-05-08 21:28:37Z tgaskins $ + */ +public abstract class URLRetriever extends WWObjectImpl implements Retriever +{ + private volatile String state = RETRIEVER_STATE_NOT_STARTED; + private volatile int contentLength = 0; + private AtomicInteger contentLengthRead = new AtomicInteger(0); + private volatile String contentType; + private volatile ByteBuffer byteBuffer; + private volatile URLConnection connection; + private final URL url; + private final RetrievalPostProcessor postProcessor; + private int connectTimeout = Configuration.getIntegerValue(AVKey.URL_CONNECT_TIMEOUT, 8000); + private int readTimeout = Configuration.getIntegerValue(AVKey.URL_READ_TIMEOUT, 5000); + private long submitTime; + private long beginTime; + private long endTime; + + /** + * @param url + * @param postProcessor + * @throws IllegalArgumentException if url or postProcessor is null + */ + public URLRetriever(URL url, RetrievalPostProcessor postProcessor) + { + if (url == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.URLIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + if (postProcessor == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.PostProcessorIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + this.url = url; + this.postProcessor = postProcessor; + } + + public final URL getUrl() + { + return url; + } + + public final int getContentLength() + { + return this.contentLength; + } + + protected void setContentLengthRead(int length) + { + this.contentLengthRead.set(length); + } + + public final int getContentLengthRead() + { + return this.contentLengthRead.get(); + } + + public final String getContentType() + { + return this.contentType; + } + + public final ByteBuffer getBuffer() + { + return this.byteBuffer; + } + + public final String getName() + { + return this.url.toString(); + } + + public final String getState() + { + return this.state; + } + + protected final URLConnection getConnection() + { + return this.connection; + } + + public final RetrievalPostProcessor getPostProcessor() + { + return postProcessor; + } + + public final int getConnectTimeout() + { + return connectTimeout; + } + + /** + * @param connectTimeout + * @throws IllegalArgumentException if connectTimeOut is less than zero + */ + public final void setConnectTimeout(int connectTimeout) + { + if (connectTimeout < 0) + { + String message = WorldWind.retrieveErrMsg("URLRetriever.ConnectTimeoutLessThanZero"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + this.connectTimeout = connectTimeout; + } + + public long getSubmitTime() + { + return submitTime; + } + + public void setSubmitTime(long submitTime) + { + this.submitTime = submitTime; + } + + public long getBeginTime() + { + return beginTime; + } + + public void setBeginTime(long beginTime) + { + this.beginTime = beginTime; + } + + public long getEndTime() + { + return endTime; + } + + public void setEndTime(long endTime) + { + this.endTime = endTime; + } + + public final Retriever call() throws Exception + { + if (this.interrupted()) + return this; + + try + { + this.setState(RETRIEVER_STATE_STARTED); + + if (!this.interrupted()) + { + this.setState(RETRIEVER_STATE_CONNECTING); + this.connection = this.openConnection(); + } + + if (!this.interrupted()) + { + this.setState(RETRIEVER_STATE_READING); + this.byteBuffer = this.read(); + } + + if (!this.interrupted()) + this.setState(RETRIEVER_STATE_SUCCESSFUL); + } + catch (Exception e) + { + this.setState(RETRIEVER_STATE_ERROR); + if (!(e instanceof java.net.SocketTimeoutException)) + { + String message = WorldWind.retrieveErrMsg("URLRetriever.ErrorAttemptingToRetrieve") + + this.url.toString(); + WorldWind.logger().log(java.util.logging.Level.FINE, message, e); + } + throw e; + } + finally + { + this.end(); + } + + return this; + } + + private void setState(String state) + { + String oldState = this.state; + this.state = state; + this.firePropertyChange(AVKey.RETRIEVER_STATE, oldState, this.state); + } + + private boolean interrupted() + { + if (Thread.currentThread().isInterrupted()) + { + this.setState(RETRIEVER_STATE_INTERRUPTED); + String message = WorldWind.retrieveErrMsg("URLRetriever.RetrievalInterruptedFor") + this.url.toString(); + WorldWind.logger().log(java.util.logging.Level.FINER, message); + return true; + } + return false; + } + + private URLConnection openConnection() throws IOException + { + try + { + this.connection = this.url.openConnection(); + } + catch (java.io.IOException e) + { + String message = WorldWind.retrieveErrMsg("URLRetriever.ErrorOpeningConnection") + this.url.toString() + + " " + e.getLocalizedMessage(); + WorldWind.logger().log(java.util.logging.Level.FINE, message, e); + throw e; + } + + if (this.connection == null) // java.net.URL docs imply that this won't happen. We check anyway. + { + String message = WorldWind.retrieveErrMsg("URLRetriever.NullReturnedFromOpenConnection") + this.url; + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalStateException(message); + } + + this.connection.setConnectTimeout(this.connectTimeout); + this.connection.setReadTimeout(this.readTimeout); + + return connection; + } + + private void end() throws Exception + { + try + { + if (this.postProcessor != null) + { + this.byteBuffer = this.postProcessor.run(this); + } + } + catch (Exception e) + { + this.setState(RETRIEVER_STATE_ERROR); + String message = WorldWind.retrieveErrMsg("URLRetriever.ErrorPostProcessing") + this.url.toString(); + WorldWind.logger().log(java.util.logging.Level.FINE, message + " " + e.getLocalizedMessage()); + throw e; + } + } + + private ByteBuffer read() throws Exception + { + try + { + ByteBuffer buffer = this.doRead(this.connection); + if (buffer == null) + this.contentLength = 0; + return buffer; + } + catch (Exception e) + { + if (!(e instanceof java.net.SocketTimeoutException)) + { + String message = WorldWind.retrieveErrMsg("URLRetriever.ErrorReadingFromConnection") + + this.url.toString() + e.getLocalizedMessage(); + WorldWind.logger().log(java.util.logging.Level.FINE, message, e); + } + throw e; + } + } + + /** + * @param connection + * @return + * @throws Exception + * @throws IllegalArgumentException if connection is null + */ + 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); + } + + this.contentLength = this.connection.getContentLength(); + + ByteBuffer buffer; + InputStream inputStream = null; + try + { + inputStream = this.connection.getInputStream(); + if (inputStream == null) + { + WorldWind.logger().log(java.util.logging.Level.FINE, WorldWind.retrieveErrMsg( + "URLRetriever.InputStreamFromConnectionNull") + connection.getURL()); + return null; + } + // TODO: Make decompression of zip file configurable + // TODO: Make this more generally configurable based on content type + // todo: make a zip reader that handles streams of unknown length + // TODO: add a gzip reader + // TODO: this code is checking content type for compression when it should be checking content encoding, + // but the WW servers are sending application/zip as the content type, and null for the content encoding. + if (connection.getContentType().equalsIgnoreCase("application/zip")) + buffer = this.readZipStream(inputStream, connection); // assume single file in zip and decompress it + else + buffer = this.readNonSpecificStream(inputStream, connection); + + this.contentType = connection.getContentType(); + } + finally + { + if (inputStream != null) + try + { + inputStream.close(); + } + catch (IOException e) + { + WorldWind.logger().log(java.util.logging.Level.FINE, WorldWind.retrieveErrMsg( + "URLRetriever.ExceptionClosingInputStreamToConnection") + connection.getURL()); + } + } + + return buffer; + } + + private ByteBuffer readNonSpecificStream(InputStream inputStream, URLConnection connection) throws IOException + { + if (inputStream == null) + { + String message = WorldWind.retrieveErrMsg("URLRetriever.InputStreamNullFor") + connection.getURL(); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (this.contentLength < 1) + { + return readNonSpecificStreamUnknownLength(inputStream); + } + + ReadableByteChannel channel = Channels.newChannel(inputStream); + ByteBuffer buffer = ByteBuffer.allocate(this.contentLength); + + int numBytesRead = 0; + while (!this.interrupted() && numBytesRead >= 0 && numBytesRead < buffer.limit()) + { + int count = channel.read(buffer); + if (count > 0) + this.contentLengthRead.getAndAdd(numBytesRead += count); + } + + if (buffer != null) + buffer.flip(); + + return buffer; + } + + private ByteBuffer readNonSpecificStreamUnknownLength(InputStream inputStream) throws IOException + { + final int PAGE_SIZE = 4096; + + ReadableByteChannel channel = Channels.newChannel(inputStream); + ByteBuffer buffer = ByteBuffer.allocate(PAGE_SIZE); + + int count = 0; + int numBytesRead = 0; + while (!this.interrupted() && count >= 0) + { + count = channel.read(buffer); + if (count > 0) + this.contentLengthRead.getAndAdd(numBytesRead += count); + + if (count > 0 && !buffer.hasRemaining()) + { + ByteBuffer biggerBuffer = ByteBuffer.allocate(buffer.limit() + PAGE_SIZE); + biggerBuffer.put((ByteBuffer) buffer.rewind()); + buffer = biggerBuffer; + } + } + + if (buffer != null) + buffer.flip(); + + return buffer; + } + + /** + * @param inputStream + * @param connection + * @return + * @throws java.io.IOException + * @throws IllegalArgumentException if inputStream is null + */ + private ByteBuffer readZipStream(InputStream inputStream, URLConnection connection) throws IOException + { + ZipInputStream zis = new ZipInputStream(inputStream); + ZipEntry ze = zis.getNextEntry(); + if (ze == null) + { + WorldWind.logger().log(java.util.logging.Level.FINE, WorldWind.retrieveErrMsg("URLRetriever.NoZipEntryFor") + + connection.getURL()); + return null; + } + + ByteBuffer buffer = null; + if (ze.getSize() > 0) + { + buffer = ByteBuffer.allocate((int) ze.getSize()); + + byte[] inputBuffer = new byte[8192]; + while (buffer.hasRemaining()) + { + int count = zis.read(inputBuffer); + if (count > 0) + { + buffer.put(inputBuffer, 0, count); + this.contentLengthRead.getAndAdd(buffer.position() + 1); + } + } + } + if (buffer != null) + buffer.flip(); + + return buffer; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + final URLRetriever that = (URLRetriever) o; + + // Retrievers are considered identical if they are for the same URL. This convention is used by the + // retrieval service to filter out duplicate retreival requests. + return !(url != null ? !url.equals(that.url) : that.url != null); + } + + @Override + public int hashCode() + { + int result; + result = (url != null ? url.hashCode() : 0); + return result; + } + + @Override + public String toString() + { + return this.getName() != null ? this.getName() : super.toString(); + } +} diff --git a/gov/nasa/worldwind/UserFacingIcon.java b/gov/nasa/worldwind/UserFacingIcon.java new file mode 100644 index 0000000..45ea632 --- /dev/null +++ b/gov/nasa/worldwind/UserFacingIcon.java @@ -0,0 +1,143 @@ +/* +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.*; +import java.awt.Color; + +/** + * @author tag + * @version $Id$ + */ +public class UserFacingIcon extends AVListImpl implements WWIcon +{ + private final String iconPath; + private Position iconPosition; // may be null because placement may be relative + private Dimension iconSize; // may be null to indicate "use native image size" + private boolean isHighlighted = false; + private boolean isVisible = true; + private double highlightScale = 1.2; // TODO: make configurable + private String toolTipText; + private Font toolTipFont; + private boolean showToolTip = false; + private java.awt.Color textColor; + + public UserFacingIcon(String iconPath, Position iconPosition) + { + // TODO: argument checking + this.iconPath = iconPath; + this.iconPosition = iconPosition; + } + + public String getPath() + { + return iconPath; + } + + public Position getPosition() + { + return iconPosition; + } + + public void setPosition(Position iconPosition) + { + this.iconPosition = iconPosition; + } +// +// public void setPosition(PolarPoint iconPosition) +// { +// this.iconPosition = new Position(iconPosition.getLatitude(), iconPosition.getLongitude(), +// iconPosition.getRadius()); +// } + + public boolean isHighlighted() + { + return isHighlighted; + } + + public void setHighlighted(boolean highlighted) + { + isHighlighted = highlighted; + } + + public double getHighlightScale() + { + return highlightScale; + } + + public void setHighlightScale(double highlightScale) + { + this.highlightScale = highlightScale; + } + + public Dimension getSize() + { + return this.iconSize; + } + + public void setSize(Dimension size) + { + this.iconSize = size; + } + + public boolean isVisible() + { + return isVisible; + } + + public void setVisible(boolean visible) + { + isVisible = visible; + } + + public String getToolTipText() + { + return toolTipText; + } + + public void setToolTipText(String toolTipText) + { + this.toolTipText = toolTipText; + } + + public Font getToolTipFont() + { + return toolTipFont; + } + + public void setToolTipFont(Font toolTipFont) + { + this.toolTipFont = toolTipFont; + } + + public boolean isShowToolTip() + { + return showToolTip; + } + + public void setShowToolTip(boolean showToolTip) + { + this.showToolTip = showToolTip; + } + + public Color getToolTipTextColor() + { + return textColor; + } + + public void setToolTipTextColor(Color textColor) + { + this.textColor = textColor; + } + + public String toString() + { + return this.iconPath != null ? this.iconPath : this.getClass().getName(); + } +} diff --git a/gov/nasa/worldwind/Version.java b/gov/nasa/worldwind/Version.java new file mode 100644 index 0000000..4796cf5 --- /dev/null +++ b/gov/nasa/worldwind/Version.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; + +/** + * @author tag + * @version $Id: Version.java 1806 2007-05-09 14:19:20Z tgaskins $ + */ + +public class Version +{ + private static final String MAJOR_VALUE = "0"; + private static final String MINOR_VALUE = "2"; + private static final String DOT_VALUE = "0"; + private static final String versionNumber = MAJOR_VALUE + "." + MINOR_VALUE + "." + DOT_VALUE; + private static final String versionName = "NASA World Wind Early Access 2"; + + public static String getVersion() + { + return versionName + " " + versionNumber; + } + + public static String getVersionName() + { + return versionName; + } + + public static String getVersionNumber() + { + return versionNumber; + } + + public static String getVersionMajorNumber() + { + return MAJOR_VALUE; + } + + public static String getVersionMinorNumber() + { + return MINOR_VALUE; + } + + public static String getVersionDotNumber() + { + return DOT_VALUE; + } +} diff --git a/gov/nasa/worldwind/View.java b/gov/nasa/worldwind/View.java new file mode 100644 index 0000000..298ef7a --- /dev/null +++ b/gov/nasa/worldwind/View.java @@ -0,0 +1,381 @@ +/* +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.*; + +/** + * The View interface provides basic methods for implementations to communicate state and state chages to + * the caller. Views provide a coordinate transformation from the object coordinates of the model to eye + * coordinates, which follow the OpenGL convention of a left-handed coordinate system with origin at the eye point and + * looking down the negative-z axis. + *

+ * View implementations are free to constrain position and orientation, as well as supply any projection from eye to + * clip coordinates they see fit. Therefore calls to {@link #setFieldOfView}, {@link #goToCoordinate}, {@link + * #goToLatLon} or {@link #goToAltitude} are not guaranteed to produce the requested transformation. When exact + * knowledge of these values in necessary, calling {@link #getFieldOfView}, {@link #getPosition} or {@link #getAltitude} + * immediately after a call to {@link #apply} guarantees the value to be synchronized with data structures used for view + * transformation (e.g. model-view and projection matrices). + *

+ * Views contain both fixed state and computed state. The computed state is typically updated during a call to the + * {@link #apply} method. Most accessor methods in this interface return the computed state that was set during the most + * recent call to apply(). + * + * @author Paul Collins + * @version $Id: View.java 1795 2007-05-08 22:08:29Z dcollins $ + * @see gov.nasa.worldwind.geom.Frustum + * @see gov.nasa.worldwind.geom.ViewFrustum + */ +public interface View extends WWObject +{ + /** + * Calculates and applies View's internal state to the graphics context in DrawContext. + * All subsequently rendered objects use this new state. Upon return, the OpenGL graphics context reflects the + * values of this view, as do any computed values of the view, such as the model-view matrix and + * Frustum. + * + * @param dc the current World Wind drawing context on which View's state will apply. + * @throws IllegalArgumentException if dc is null, or if the Globe or GL + * contained in dc is null. + */ + void apply(DrawContext dc); + + /** + * Returns the 'model-view' matrix computed in apply(), which transforms model coordinates to eye + * coordinates (where the eye is located at the origin, facing down the negative-z axis). This matrix is constructed + * using the model space translation and orientation specific to each implementation of View. + * + * @return the current model-view matrix. + */ + Matrix4 getModelViewMatrix(); + + /** + * Returns the 'projection' matrix computed in apply(), which transforms eye coordinates to homogeneous + * clip coordinates (a box of dimension (2, 2, 2) centered at the origin). This matrix is constructed using the + * projection parameters specific to each implementation of View (e.g. field-of-view, clipping plane + * distances). The {@link #getFrustum} method returns the planes corresponding to this matrix. + * + * @return the current projection matrix. + */ + Matrix4 getProjectionMatrix(); + + /** + * Returns a Rectangle representing the window bounds (x, y, width, height) of the viewport, computed in + * apply(). Implementations of View will configure themselves to render in this viewport. + * + * @return the current window bounds of the viewport, or null if none exists. + */ + java.awt.Rectangle getViewport(); + + /** + * Returns the viewing Frustum in eye coordinates, computed in apply(). The + * Frustum is the portion of viewable space defined by three sets of parallel 'clipping' planes. The + * method {@link #getFrustumInModelCoordinates} maintains the shape of this Frustum, but it has been + * translated and aligned with the eye in model space. + * + * @return the current viewing frustum in eye coordinates. + */ + Frustum getFrustum(); + + /** + * Returns the viewing Frustum transformed to model coordinates. Model coordinate frustums are useful + * for performing multiple intersection tests in model coordinates. + * + * @return the current viewing frustum in model coordinates. + */ + Frustum getFrustumInModelCoordinates(); + + /** + * Returns the horizontal field-of-view angle (the angle of visibility) associated with this View, or + * null if the View implementation does not support a field-of-view. + * + * @return horizontal field-of-view angle, or null if none exists. + */ + Angle getFieldOfView(); + + /** + * Sets the horiziontal field-of-view angle (the angle of visibillity) associated with this View. This + * call may be ignored by implementations that do not support a field-of-view. + * + * @param newFov the new horizontal field-of-view angle. + * @throws IllegalArgumentException if newFov is null. + */ + void setFieldOfView(Angle newFov); + + /** + * Generates a new coordinate system in which the View does not move, and model coordinates are reverse + * transformed into eye coordinates. The origin for these coordinates will be referenceCenter, + * therefore all objects drawn after a call to pushReferenceCenter should be with respect to this + * Point, rather than the customary origin (0, 0, 0). This creates a new model-view matrix, which is + * placed on the top of a matrix stack, and immediately applied to the current OpenGL context. In order to return to + * the original coordinate space, callers should invoke {@link #popReferenceCenter} after rendering is complete. + * Note that calls to {@link #getModelViewMatrix} will not return reference-center model-view matrix, but the + * original matrix. + * + * @param dc the current World Wind drawing context on which View's state will apply. + * @param referenceCenter the Point to become the new model space origin. + * @throws IllegalArgumentException if referenceCenter is null. + */ + void pushReferenceCenter(DrawContext dc, Point referenceCenter); + + /** + * Removes the model-view matrix on top of the matrix stack, and restores the matrix now on top. This has the effect + * of immediately replacing the current OpenGL model-view matrix with the matrix below the top. When the stack size + * is one, therefore containing the original model-view matrix computed by apply(), this method will + * throw an exception. + * + * @param dc the current World Wind drawing context on which View's state will apply. + * @throws IllegalStateException if the the refernce matrix stack is empty. + */ + void popReferenceCenter(DrawContext dc); + + /** + * Returns the eye position in model coordinates. + * + * @return the eye position in model coordinates. + */ + Point getEyePoint(); + + /** + * Returns the View y-axis orientation in model coordinates. + * + * @return the y-axis vector in model coordinates. + */ + Point getUpVector(); + + /** + * Returns the View z-axis orientation in model coordinates. + * + * @return the z-axis vector in model coordinates. + */ + Point getForwardVector(); + + /** + * Moves the View eye point to the new polar coordinate (latitude, longitude, elevation). + * + * @param newLatLon the new latitude and longitude of the eye point. + * @param newAltitude the new eye altitude (in meters) above the surface, + * @throws IllegalArgumentException if newLatlon is null. + */ + void goToCoordinate(LatLon newLatLon, double newAltitude); + + /** + * Returns the geographic (latitude, longitude, elevation) coordinate of the View's eye point. Latitude + * and longitude represent the coordinate directly beneath (or above) the View, while elevation + * represents the View altitude above the analytical Globe radius. + * + * @return the latitude and longitude coordinates of the eye point. + */ + Position getPosition(); + + /** + * Moves the View eye point to the new geographic (latitude, longitude) coordinate. Altitude is left + * unchanged. + * + * @param newLatLon the new latitude and longitude of the eye point. + * @throws IllegalArgumentException if newLatlon is null. + */ + void goToLatLon(LatLon newLatLon); + + /** + * Returns the View eye altitude (in meters) above the last rendered SectorGeometry, or + * the analytical Globe, depending on the implementation. + * + * @return the View's altitude (in meters) above the surface. + */ + double getAltitude(); + + /** + * Moves the View eye point to the new altitude (in meters) above the last rendered + * SectorGeometry, or the analytical Globe, depending on the implementation. + * + * @param newAltitude the new eye altitude (in meters) above the surface, + */ + void goToAltitude(double newAltitude); + + /** + * Returns the View's angle from true North. + * + * @return the angle from true North. + */ + Angle getHeading(); + + /** + * Sets the View's angle to true North. + * + * @param newHeading the new angle to true North. + * @throws IllegalArgumentException if newHeading is null. + */ + void setHeading(Angle newHeading); + + /** + * Returns the View's angle from the plane tangent to the surface. + * + * @return the angle from the surface tangent plane. + */ + Angle getPitch(); + + /** + * Sets the View's angle to the plane tangent to the surface. + * + * @param newPitch the new angle to the surface tangent plane. + * @throws IllegalArgumentException if newPitch is null. + */ + void setPitch(Angle newPitch); + + /** + * Returns a two-dimensional array containing the range of angles (inclusive) the View may limit + * its pitch to, if pitch constraints are enabled. + * + * @return a two-dimensional array, with the minimum and maximum pitch angles. + */ + Angle[] getPitchConstraints(); + + /** + * Sets the range of angles (inclusive) the View may limit its pitch to, if pitch constraints are + * enabled. + * + * @param newMinPitch the minimum pitch angle. + * @param newMaxPitch the maximum pitch angle. + */ + void setPitchConstraints(Angle newMinPitch, Angle newMaxPitch); + + /** + * Returns true when pitch constraints are enabled. + * + * @return true when pitch constraints are enabled. + */ + boolean isEnablePitchConstraints(); + + /** + * Enable or disable pitch constraints. + * + * @param enabled true when pitch constraints should be enabled, false otherwise. + */ + void setEnablePitchConstraints(boolean enabled); + + /** + * Returns the View's angle about its local z-axis. + * + * @return the angle about the local z-axis. + */ + Angle getRoll(); + + /** + * Sets the View's angle about its local z-axis. + * + * @param newRoll the new angle about the local z-axis. + * @throws IllegalArgumentException if newRoll is null. + */ + void setRoll(Angle newRoll); + + /** + * Returns the View's translation in its forward direction. + * + * @return translation along the forward direction. + */ + double getZoom(); + + /** + * Sets the View's translation in its forward direction. + * + * @param newZoom translation along the forward direction. + */ + void setZoom(double newZoom); + + /** + * Returns a two-dimensional array containing the range of values (inclusive) the View may limit + * its zoom to, if zoom constraints are enabled. + * + * @return two-dimensional array, with the minimum and maximum zoom values. + */ + double[] getZoomConstraints(); + + /** + * Sets the range of values (inclusive) the View may limit its zoom to, if zoom constraints are + * enabled. + * + * @param newMinZoom the minimum zoom value. + * @param newMaxZoom the maximum zoom value. + */ + void setZoomConstraints(double newMinZoom, double newMaxZoom); + + /** + * Returns true when zoom constraints are enabled. + * + * @return true when zoom constraints are enabled, false otherwise. + */ + boolean isEnableZoomConstraints(); + + /** + * Enable or disable zoom constraints. + * + * @param enabled trhe when zoom constraints should be enabled, false otherwise. + */ + void setEnableZoomConstraints(boolean enabled); + + /** + * Computes a line, in model coordinates, originating from the eye point, and passing throught the point contained + * by (x, y) on the View's projection plane (or after projection into model space). + * + * @param x the horizontal coordinate originating from the left side of View's projection plane. + * @param y the vertical coordinate originating from the top of View's projection plane. + * @return a line beginning at the View's eye point and passing throught (x, y) transformed into model + * space. + */ + Line computeRayFromScreenPoint(double x, double y); + + /** + * Computes the intersection of a line originating from the eye point (passing throught (x, y)) with the last + * rendered SectorGeometry, or the last analytical Globe if no rendered geometry exists. + * + * @param x the horizontal coordinate originating from the left side of View's projection plane. + * @param y the vertical coordinate originating from the top of View's projection plane. + * @return the point on the surface in polar coordiantes. + */ + Position computePositionFromScreenPoint(double x, double y); + + /** + * Computes the screen-aligned dimension (in meters) that a screen pixel would cover at a given distance (also in + * meters). This computation assumes that pixels dimensions are square, and therefore returns a single dimension. + * + * @param distance the distance from the eye point, in eye coordinates, along the z-axis. This value must be + * positive but is otherwise unbounded. + * @return the dimension of a pixel (in meters) at the given distance. + * @throws IllegalArgumentException if distance is negative. + */ + double computePixelSizeAtDistance(double distance); + + /** + * Returns the distance from the View's eye point to the horizon point on the last rendered + * Globe. + * + * @return the distance from the eye point to the horizon (in meters). + */ + double computeHorizonDistance(); + + /** + * Maps a Point in model (cartesian) coordinates to a Point in screen coordinates. The + * returned x and y are relative to the lower left hand screen corner, while z is the screen depth-coordinate. If + * the model point cannot be sucessfully mapped, this will return null. + * + * @param modelPoint the model coordinate Point to project. + * @return the mapped screen coordinate Point. + * @throws IllegalArgumentException if modelPoint is null. + */ + Point project(Point modelPoint); + + /** + * Maps a Point in screen coordinates to a Point in model coordinates. The input x and y + * are relative to the lower left hand screen corner, while z is the screen depth-coordinate. If the screen point + * cannot be sucessfully mapped, this will return null. + * + * @param windowPoint the window coordinate Point to project. + * @return the mapped screen coordinate Point. + * @throws IllegalArgumentException if windowPoint is null. + */ + Point unProject(Point windowPoint); +} diff --git a/gov/nasa/worldwind/WWDuplicateRequestException.java b/gov/nasa/worldwind/WWDuplicateRequestException.java new file mode 100644 index 0000000..23dd840 --- /dev/null +++ b/gov/nasa/worldwind/WWDuplicateRequestException.java @@ -0,0 +1,19 @@ +/* +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: WWDuplicateRequestException.java 1718 2007-05-05 00:51:15Z tgaskins $ + */ +public class WWDuplicateRequestException extends WWRuntimeException +{ + public WWDuplicateRequestException(String s) + { + super(s); + } +} diff --git a/gov/nasa/worldwind/WWIO.java b/gov/nasa/worldwind/WWIO.java new file mode 100644 index 0000000..6eaa591 --- /dev/null +++ b/gov/nasa/worldwind/WWIO.java @@ -0,0 +1,354 @@ +/* +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: WWIO.java 1832 2007-05-11 20:52:38Z tgaskins $ + */ +package gov.nasa.worldwind; + +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.io.*; +import java.util.zip.*; + +public class WWIO +{ + public static boolean saveBuffer(ByteBuffer buffer, File file) throws IOException + { + if (buffer == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.BufferNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (file == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + FileOutputStream fos = null; + FileChannel channel = null; + FileLock lock; + int numBytesWritten = 0; + try + { + fos = new FileOutputStream(file); + channel = fos.getChannel(); + lock = channel.tryLock(); + if (lock == null) + { + // The file is being written to, or some other process is keeping it to itself. + // This is an okay condition, but worth noting. + String message = WorldWind.retrieveErrMsg("WWIO.UnableToAcquireLockFor") + file.getPath(); + WorldWind.logger().log(java.util.logging.Level.FINER, message); + return false; + } + + for (buffer.rewind(); buffer.hasRemaining();) + { + numBytesWritten += channel.write(buffer); + } + + return true; + } + catch (IOException e) + { + String message = WorldWind.retrieveErrMsg("WWIO.ErrorSavingBufferTo") + file.getPath(); + WorldWind.logger().log(java.util.logging.Level.FINE, message, e); + + if (numBytesWritten > 0) // don't leave behind incomplete files + file.delete(); + + throw e; + } + finally + { + try + { + if (channel != null) + channel.close(); // also releases the lock + else if (fos != null) + fos.close(); + } + catch (java.io.IOException e) + { + String message = WorldWind.retrieveErrMsg("WWIO.ErrorTryingToClose") + file.getPath(); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + } + } + } + + public static MappedByteBuffer mapFile(File file) throws IOException + { + if (file == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + FileInputStream is = new FileInputStream(file); + try + { + return is.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length()); + } + finally + { + is.close(); + } + } + + public static ByteBuffer readURLContentToBuffer(URL url) throws IOException + { + if (url == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.URLIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + InputStream is = url.openStream(); + ByteBuffer buffer; + try + { + URLConnection connection = url.openConnection(); + int size = connection.getContentLength(); + buffer = ByteBuffer.allocateDirect(size); + byte inputBuffer[] = new byte[size]; + int n; + do + { + n = is.read(inputBuffer); + if (n > 0) + buffer.put(inputBuffer, 0, n); + } while (buffer.hasRemaining() && n >= 0); + buffer.flip(); + buffer.rewind(); + } + finally + { + if (is != null) + { + is.close(); + } + } + + return buffer; + } + + public static ByteBuffer readFileToBuffer(File file) throws IOException + { + if (file == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + FileInputStream is = new FileInputStream(file); + try + { + FileChannel fc = is.getChannel(); + ByteBuffer buffer = ByteBuffer.allocate((int) fc.size()); + for (int count = 0; count >= 0 && buffer.hasRemaining();) + { + count = fc.read(buffer); + } + buffer.flip(); + return buffer; + } + finally + { + is.close(); + } + } + + public static ByteBuffer readZipEntryToBuffer(File zipFile, String entryName) throws IOException + { + if (zipFile == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + InputStream is = null; + ZipEntry ze; + try + { + ZipFile zf = new ZipFile(zipFile); + if (zf.size() < 1) + { + String message = WorldWind.retrieveErrMsg("WWIO.ZipFileIsEmpty") + zipFile.getPath(); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new java.io.IOException(message); + } + + if (entryName != null) + { // Read the specified entry + ze = zf.getEntry(entryName); + if (ze == null) + { + String message = WorldWind.retrieveErrMsg("WWIO.ZipFileEntryNIF") + entryName + + WorldWind.retrieveErrMsg("punctuation.space") + zipFile.getPath(); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IOException(message); + } + } + else + { // Read the first entry + ze = zf.entries().nextElement(); // get the first entry + } + + is = zf.getInputStream(ze); + ByteBuffer buffer = null; + if (ze.getSize() > 0) + { + buffer = transferStreamToByteBuffer(is, (int) ze.getSize()); + buffer.flip(); + } + return buffer; + } + finally + { + if (is != null) + is.close(); + } + } + + private static ByteBuffer transferStreamToByteBuffer(InputStream stream, int numBytes) throws IOException + { + if (stream == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.InputStreamIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (numBytes < 1) + { + String message = WorldWind.retrieveErrMsg("WWIO.NumberBytesTransferLessThanOne"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + int bytesRead = 0; + int count = 0; + byte[] bytes = new byte[numBytes]; + while (count >= 0 && (numBytes - bytesRead) > 0) + { + count = stream.read(bytes, bytesRead, numBytes - bytesRead); + if (count > 0) + { + bytesRead += count; + } + } + return ByteBuffer.wrap(bytes); + } + + public static String levelRowColumnPath(StringBuffer prefix, String level, int row, int column, String suffix) + { + StringBuffer sb = prefix; + if (sb == null) + sb = new StringBuffer(); + + if (level != null) + sb.append(level); + String rowString = "/" + Integer.toString(row); + sb.append(rowString);//sb.append(f.format(row)); + sb.append(rowString);//sb.append(f.format(row)); + sb.append("_"); + sb.append(Integer.toString(column));//sb.append(f.format(column)); + if (suffix != null) + { + if (!suffix.startsWith(".")) + sb.append("."); + sb.append(suffix); + } + + return sb.toString(); + } + + public static String replaceSuffix(String in, String newSuffix) + { + if (in == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.InputFileNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + return in.substring(0, in.lastIndexOf(".")) + (newSuffix != null ? newSuffix : ""); + } + + public static File saveBufferToTempFile(ByteBuffer buffer, String suffix) throws IOException + { + if (buffer == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.ByteBufferIsNull"); + gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + File outputFile = java.io.File.createTempFile("WorldWind", suffix != null ? suffix : ""); + outputFile.deleteOnExit(); + buffer.rewind(); + WWIO.saveBuffer(buffer, outputFile); + + return outputFile; + } + + public static String getSuffixForMimeType(String mimeType) + { + if (mimeType == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.MimeTypeIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + if (mimeType.equalsIgnoreCase("image/jpeg") || mimeType.equalsIgnoreCase("image/jpg")) + return ".jpg"; + if (mimeType.equalsIgnoreCase("image/png")) + return ".png"; + + return null; + } + + public static boolean isFileOutOfDate(URL url, long expiryTime) + { + if (url == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.URLIsNull"); + gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + try + { + // Determine whether the file can be treated like a File, e.g., a jar entry. + URI uri = url.toURI(); + if (uri.isOpaque()) + return false; // TODO: Determine how to check the date of non-Files + + File file = new File(uri); + + return file.exists() && file.lastModified() < expiryTime; + } + catch (URISyntaxException e) + { + String message = WorldWind.retrieveErrMsg("WWIO.ExceptionValidatingFileExpiration"); + WorldWind.logger().log(java.util.logging.Level.FINE, message + url); + return false; + } + } +} diff --git a/gov/nasa/worldwind/WWIcon.java b/gov/nasa/worldwind/WWIcon.java new file mode 100644 index 0000000..6f42de7 --- /dev/null +++ b/gov/nasa/worldwind/WWIcon.java @@ -0,0 +1,59 @@ +/* +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.*; +import java.awt.Color; + +/** + * @author tag + * @version $Id$ + */ +public interface WWIcon // extends gov.nasa.worldwind.AVList +{ + String getPath(); + + Position getPosition(); + + void setPosition(Position iconPosition); + +// void setPosition(PolarPoint iconPosition); + + boolean isHighlighted(); + + void setHighlighted(boolean highlighted); + + Dimension getSize(); + + void setSize(Dimension size); + + boolean isVisible(); + + void setVisible(boolean visible); + + double getHighlightScale(); + + void setHighlightScale(double highlightScale); + + String getToolTipText(); + + void setToolTipText(String toolTipText); + + Font getToolTipFont(); + + void setToolTipFont(Font toolTipFont); + + boolean isShowToolTip(); + + void setShowToolTip(boolean showToolTip); + + Color getToolTipTextColor(); + + void setToolTipTextColor(Color textColor); +} diff --git a/gov/nasa/worldwind/WWObject.java b/gov/nasa/worldwind/WWObject.java new file mode 100644 index 0000000..6067aaa --- /dev/null +++ b/gov/nasa/worldwind/WWObject.java @@ -0,0 +1,19 @@ +/* +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 provided by the major World Wind components to provide attribute-value list management and + * property change management. Classifies implementors as property-change listeners, allowing them to receive + * property-change events. + * + * @author Tom Gaskins + * @version $Id: WWObject.java 1743 2007-05-06 16:40:45Z tgaskins $ + */ +public interface WWObject extends AVList, java.beans.PropertyChangeListener +{ +} diff --git a/gov/nasa/worldwind/WWObjectImpl.java b/gov/nasa/worldwind/WWObjectImpl.java new file mode 100644 index 0000000..bc467f7 --- /dev/null +++ b/gov/nasa/worldwind/WWObjectImpl.java @@ -0,0 +1,24 @@ +/* +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; + +/** + * Implements WWObject functionality. Meant to be either subclassed or aggretated by classes implementing + * WWObject. + * + * @author Tom Gaskins + * @version $Id: WWObjectImpl.java 1748 2007-05-06 17:22:05Z tgaskins $ + */ +public class WWObjectImpl extends AVListImpl implements WWObject +{ + /** + * Constructs a new WWObjectImpl. + */ + public WWObjectImpl() + { + } +} diff --git a/gov/nasa/worldwind/WWRuntimeException.java b/gov/nasa/worldwind/WWRuntimeException.java new file mode 100644 index 0000000..23da9e7 --- /dev/null +++ b/gov/nasa/worldwind/WWRuntimeException.java @@ -0,0 +1,33 @@ +/* +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: WWRuntimeException.java 1718 2007-05-05 00:51:15Z tgaskins $ + */ +public class WWRuntimeException extends RuntimeException +{ + public WWRuntimeException() + { + } + + public WWRuntimeException(String s) + { + super(s); + } + + public WWRuntimeException(String s, Throwable throwable) + { + super(s, throwable); + } + + public WWRuntimeException(Throwable throwable) + { + super(throwable); + } +} diff --git a/gov/nasa/worldwind/WorldWind.java b/gov/nasa/worldwind/WorldWind.java new file mode 100644 index 0000000..771b32e --- /dev/null +++ b/gov/nasa/worldwind/WorldWind.java @@ -0,0 +1,180 @@ +/* +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 Tom Gaskins + * @version $Id: WorldWind.java 1792 2007-05-08 21:28:37Z tgaskins $ + */ +//TODO: prevent circular exception loops when Resources unavailable (internationalisation) +// until that problem is solved, don't create international support for this class. +public final class WorldWind +{ + private static WorldWind instance = new WorldWind(); + + private WWObjectImpl wwo = new WWObjectImpl(); + private MemoryCache memoryCache; + private FileCache dataFileCache; + private BasicRetrievalService retrievalService; + private ThreadedTaskService threadedTaskService = new ThreadedTaskService(); + + private WorldWind() // Singleton, prevent instantiation. + { + this.retrievalService = (BasicRetrievalService) createConfigurationComponent( + AVKey.RETRIEVAL_SERVICE_CLASS_NAME); + this.threadedTaskService = (ThreadedTaskService) createConfigurationComponent( + AVKey.THREADED_TASK_SERVICE_CLASS_NAME); + this.dataFileCache = (FileCache) createConfigurationComponent(AVKey.DATA_FILE_CACHE_CLASS_NAME); + this.memoryCache = (MemoryCache) createConfigurationComponent(AVKey.MEMORY_CACHE_CLASS_NAME); + } + + public static java.util.logging.Logger logger() + { + String loggerName = Configuration.getStringValue(AVKey.LOGGER_NAME); + if (loggerName == null) + return java.util.logging.Logger.global; + + java.util.logging.Logger logger = java.util.logging.Logger.getLogger(loggerName); + return logger != null ? logger : java.util.logging.Logger.global; + } + + public static MemoryCache memoryCache() + { + return instance.memoryCache; + } + + public static FileCache dataFileCache() + { + return instance.dataFileCache; + } + + public static RetrievalService retrievalService() + { + return instance.retrievalService; + } + + public static ThreadedTaskService threadedTaskService() + { + return instance.threadedTaskService; + } + + /** + * @param className + * @return + * @throws WWRuntimeException if the Object could not be created + * @throws IllegalArgumentException if className is null or zero length + */ + public static Object createComponent(String className) throws WWRuntimeException + { + if (className == null || className.length() == 0) + { //WorldWind.ClassNameKeyNulZero + String message = "Class name key is null or zero length"; + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + try + { + Class c = Class.forName(className); + return c.newInstance(); + } + catch (Exception e) + { //WorldWind.ExceptionCreatingComponent + String message = "Exception while creating World Wind component " + className; + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new WWRuntimeException(message, e); + } + catch (Throwable t) + { //WorldWind.ErrorCreatingComponent + String message = "Error while creating World Wind component " + className; + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new WWRuntimeException(message, t); + } + } + + /** + * @param classNameKey + * @return + * @throws IllegalStateException if no name could be found which corresponds to classNameKey + * @throws IllegalArgumentException if classNameKey is null + * @throws WWRuntimeException if the component could not be created + */ + public static Object createConfigurationComponent(String classNameKey) + throws IllegalStateException, IllegalArgumentException + { + if (classNameKey == null) + { //nullValue.ClassNameKeyNull + String message = "Class name key is null"; + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + String name = Configuration.getStringValue(classNameKey); + if (name == null) + { //WorldWind.NoClassNameInConfigurationForKey + String message = "No class name in configuration for key " + classNameKey; + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalStateException(message); + } + + try + { + return WorldWind.createComponent(name); + } + catch (Throwable e) + { //WorldWind.UnableToCreateClassForConfigurationKey + String message = "Unable to create class for configuration key " + classNameKey; + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new WWRuntimeException(message, e); + } + } + + public static void setValue(String key, String value) + { + instance.wwo.setValue(key, value); + } + + public static Object getValue(String key) + { + return instance.wwo.getValue(key); + } + + public static String getStringValue(String key) + { + return instance.wwo.getStringValue(key); + } + + public static boolean hasKey(String key) + { + return instance.wwo.hasKey(key); + } + + public static void removeKey(String key) + { + instance.wwo.removeKey(key); + } + + /** + * Retrieve a String resource from the "error" bundle. + * + * @param property The property used to identify which String to return. + * @return the requested String. + */ + public static String retrieveErrMsg(String property) + { + ResourceBundle res = ResourceBundle.getBundle("ErrorStrings", Locale.getDefault()); + return (String) res.getObject(property); + } + + public static String retrieveMessage(String property, String bundle) + { + ResourceBundle res = ResourceBundle.getBundle(bundle, Locale.getDefault()); + return (String) res.getObject(property); + } +} diff --git a/gov/nasa/worldwind/WorldWindow.java b/gov/nasa/worldwind/WorldWindow.java new file mode 100644 index 0000000..cc5a0af --- /dev/null +++ b/gov/nasa/worldwind/WorldWindow.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; + +/** + * The top-level interface common to all toolkit-specific World Wind windows. + * + * @author Tom Gaskins + * @version $Id: WorldWindow.java 1765 2007-05-07 20:51:41Z tgaskins $ + */ +public interface WorldWindow +{ + /** + * Sets the model to display in this window. If null is specified for the model, the current model, if + * any, is disassociated with the window. + * + * @param model the model to display. May be null. + */ + void setModel(Model model); + + /** + * Returns the window's current model. + * + * @return the window's current model + */ + Model getModel(); + + /** + * Sets the view to use when displaying this window's model. If null is specified for the view, the + * current view, if any, is disassociated with the window. + * + * @param view the view to use to display this window's model. May be null. + */ + void setView(View view); + + /** + * Returns this window's current view. + * + * @return the window's current view + */ + View getView(); + + /** + * Sets the model to display in this window and the view used to display it. If null is specified for + * the model, the current model, if any, is disassociated with the window. If null is specified for the + * view, the current view, if any, is disassociated with the window. + * + * @param model the model to display. May benull. + * @param view the view to use to display this window's model. May benull. + */ + void setModelAndView(Model model, View view); + + /** + * Returns the scene controller assocciated with this instance. + * + * @return The scene controller associated with the instance, or null if no scene controller is associated. + */ + SceneController getSceneController(); + + PickedObjectList pick(java.awt.Point pickPoint); + + InputHandler getInputHandler(); + + void setInputHandler(InputHandler eventSource); + + void addRenderingListener(RenderingListener listener); + + void removeRenderingListener(RenderingListener listener); + + void addSelectListener(SelectListener listener); + + void removeSelectListener(SelectListener listener); + + void addPositionListener(PositionListener listener); + + void removePositionListener(PositionListener listener); +} diff --git a/gov/nasa/worldwind/WorldWindowGLAutoDrawable.java b/gov/nasa/worldwind/WorldWindowGLAutoDrawable.java new file mode 100644 index 0000000..8528172 --- /dev/null +++ b/gov/nasa/worldwind/WorldWindowGLAutoDrawable.java @@ -0,0 +1,187 @@ +/* +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.*; +import javax.swing.event.*; +import java.beans.*; + +/** + * A non-platform specific {@link WorldWindow} class. This class can be aggregated into platform-specific classes to + * provide the core functionality of World Wind. + * + * @author Tom Gaskins + * @version $Id: WorldWindowGLAutoDrawable.java 1757 2007-05-07 09:17:09Z tgaskins $ + */ +public class WorldWindowGLAutoDrawable extends WorldWindowImpl implements GLEventListener +{ + private final GLAutoDrawable drawable; + private final EventListenerList eventListeners = new EventListenerList(); + + private java.awt.Point pickPoint; // used to pass pick point to display method + + /** + * Construct a new WorldWindowGLCanvase for a specified {@link GLDrawable}. + * + * @param drawable the drawable associated with the window. + * @throws IllegalArgumentException if drawable is null. + */ + public WorldWindowGLAutoDrawable(GLAutoDrawable drawable) + { + if (drawable == null) + { + String message = WorldWind.retrieveErrMsg("WorldWindowGLCanvas.GLAutoDrawableNullToConstructor"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + this.drawable = drawable; + this.drawable.setAutoSwapBufferMode(false); // to prevent buffer swapping after a pick traversal + drawable.addGLEventListener(this); + + SceneController sc = this.getSceneController(); + if (sc != null) + { + sc.addPropertyChangeListener(this); + } + } + + @Override + public void propertyChange(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.drawable.repaint(); // Queue a JOGL repaint request. + } + + /** + * See {@link GLEventListener#init(GLAutoDrawable)}. + * + * @param glAutoDrawable the drawable + */ + public void init(GLAutoDrawable glAutoDrawable) + { + // This GLEventListener callback method is not used. +// this.drawable.setGL(new DebugGL(this.drawable.getGL())); + } + + /** + * See {@link GLEventListener#display(GLAutoDrawable)}. + * + * @param glAutoDrawable the drawable + * @throws IllegalStateException if no {@link SceneController} exists for this canvas + */ + public void display(GLAutoDrawable glAutoDrawable) + { + try + { + SceneController sc = this.getSceneController(); + if (sc == null) + { + String message = WorldWind.retrieveErrMsg("WorldWindowGLCanvas.ScnCntrllerNullOnRepaint"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalStateException(message); + } + + if (this.pickPoint != null) + { + sc.pick(this.pickPoint); + this.pickPoint = null; + // Keep going here in order to repair the back buffer and affect any highlighting the app may have made. + } + + try + { + this.callRenderingListeners(new RenderingEvent(this.drawable, RenderingEvent.BEGIN)); + } + catch (Exception e) + { + WorldWind.logger().log(java.util.logging.Level.FINE, WorldWind.retrieveErrMsg( + "WorldWindowGLAutoDrawable.ExceptionDuringGLEventListenerDisplay"), e); + } + + sc.repaint(); + try + { + this.callRenderingListeners(new RenderingEvent(this.drawable, RenderingEvent.END)); + } + catch (Exception e) + { + WorldWind.logger().log(java.util.logging.Level.FINE, WorldWind.retrieveErrMsg( + "WorldWindowGLAutoDrawable.ExceptionDuringGLEventListenerDisplay"), e); + } + + this.drawable.swapBuffers(); + } + catch (Exception e) + { + WorldWind.logger().log(java.util.logging.Level.FINE, WorldWind.retrieveErrMsg( + "WorldWindowGLCanvas.ExceptionAttemptingRepaintWorldWindow"), e); + } + finally + { + this.pickPoint = null; + } + } + + /** + * See {@link GLEventListener#reshape(GLAutoDrawable,int,int,int,int)}. + * + * @param glAutoDrawable the drawable + */ + public void reshape(GLAutoDrawable glAutoDrawable, int x, int y, int w, int h) + { + } + + /** + * See {@link GLEventListener#displayChanged(GLAutoDrawable,boolean,boolean)}. + * + * @param glAutoDrawable the drawable + */ + public void displayChanged(GLAutoDrawable glAutoDrawable, boolean b, boolean b1) + { + WorldWind.logger().log(java.util.logging.Level.FINEST, WorldWind.retrieveErrMsg( + "WorldWindowGLCanvas.DisplayEventListenersDisplayChangedMethodCalled")); + } + + public PickedObjectList pick(java.awt.Point pickPoint) + { + if (pickPoint == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.PickPoint"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + this.pickPoint = pickPoint; + this.drawable.display(); + + return this.getSceneController().getPickedObjectList(); + } + + public void addRenderingListener(RenderingListener listener) + { + this.eventListeners.add(RenderingListener.class, listener); + } + + public void removeRenderingListener(RenderingListener listener) + { + this.eventListeners.remove(RenderingListener.class, listener); + } + + private void callRenderingListeners(RenderingEvent event) + { + for (RenderingListener listener : this.eventListeners.getListeners(RenderingListener.class)) + { + listener.stageChanged(event); + } + } +} diff --git a/gov/nasa/worldwind/WorldWindowImpl.java b/gov/nasa/worldwind/WorldWindowImpl.java new file mode 100644 index 0000000..6fd63ee --- /dev/null +++ b/gov/nasa/worldwind/WorldWindowImpl.java @@ -0,0 +1,62 @@ +/* +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 WorldWindow} interface. Classes implementing WorldWindow can + * subclass or aggreate this object to provide default WorldWindow functionality. + * + * @author Tom Gaskins + * @version $Id: WorldWindowImpl.java 1764 2007-05-07 20:01:57Z tgaskins $ + */ +public class WorldWindowImpl extends WWObjectImpl +{ + SceneController sceneController; + + public WorldWindowImpl() + { + // TODO: Determine this setup layout from configuration information + + this.sceneController = (SceneController) WorldWind.createConfigurationComponent( + AVKey.SCENE_CONTROLLER_CLASS_NAME); + } + + public void setModel(Model model) + { + // model can be null, that's ok - it indicates no model. + if (this.sceneController != null) + this.sceneController.setModel(model); + } + + public gov.nasa.worldwind.Model getModel() + { + return this.sceneController != null ? this.sceneController.getModel() : null; + } + + public void setView(gov.nasa.worldwind.View view) + { + // view can be null, that's ok - it indicates no view. + if (this.sceneController != null) + this.sceneController.setView(view); + } + + public View getView() + { + return this.sceneController != null ? this.sceneController.getView() : null; + } + + public void setModelAndView(Model model, View view) + { + this.setModel(model); + this.setView(view); + } + + public SceneController getSceneController() + { + return this.sceneController; + } +} diff --git a/gov/nasa/worldwind/awt/AWTInputHandler.java b/gov/nasa/worldwind/awt/AWTInputHandler.java new file mode 100644 index 0000000..d41256c --- /dev/null +++ b/gov/nasa/worldwind/awt/AWTInputHandler.java @@ -0,0 +1,852 @@ +/* +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.awt; + +import gov.nasa.worldwind.*; +import gov.nasa.worldwind.geom.*; + +import javax.swing.event.*; +import java.awt.event.*; +import java.util.*; +import java.util.logging.Level; + +/** + * @author tag + * @version $Id: AWTInputHandler.java 1820 2007-05-10 16:05:29Z dcollins $ + */ +@SuppressWarnings({"UnnecessaryReturnStatement"}) +public class AWTInputHandler extends AVListImpl + implements KeyListener, MouseListener, MouseMotionListener, MouseWheelListener, InputHandler +{ + private WorldWindow worldWindow = null; + private final EventListenerList eventListeners = new EventListenerList(); + private Position previousPickPosition = null; + private PickedObjectList lastPickedObjects = null; + private PickedObjectList hoverObjects; + private boolean isHovering = false; + private javax.swing.Timer hoverTimer = new javax.swing.Timer(600, new ActionListener() + { + public void actionPerformed(ActionEvent actionEvent) + { + if (AWTInputHandler.this.hoverMatches()) + { + AWTInputHandler.this.isHovering = true; + AWTInputHandler.this.callSelectListeners(new SelectEvent(AWTInputHandler.this.worldWindow, + SelectEvent.HOVER, null, AWTInputHandler.this.hoverObjects)); + AWTInputHandler.this.hoverTimer.stop(); + } + } + }); + // Current AWT mouse state. + private java.awt.Point lastMousePoint = new java.awt.Point(); + // private volatile int mouseClickButton; + // private volatile int mouseClickCount; + // private final int mouseClickCoalesceTime = 350; + // Repeating AWT key timer. Used to simulate keyboard polling. + private final Integer[] POLLED_KEYS = + { + KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, + KeyEvent.VK_UP, KeyEvent.VK_DOWN, KeyEvent.VK_PAGE_UP, + KeyEvent.VK_PAGE_DOWN, KeyEvent.VK_ADD, KeyEvent.VK_EQUALS, + KeyEvent.VK_SUBTRACT, KeyEvent.VK_MINUS + }; + private KeyPollTimer keyPollTimer = new KeyPollTimer(25, Arrays.asList(POLLED_KEYS), + new ActionListener() + { + public void actionPerformed(ActionEvent actionEvent) + { + if (actionEvent == null) + return; + Object source = actionEvent.getSource(); + if (source != null && source instanceof Integer) + AWTInputHandler.this.keysPolled((Integer) source, actionEvent.getModifiers()); + } + }); + // Value interpolation timer. Smooths responses to certain input events. + private InterpolatorTimer interpolatorTimer = new InterpolatorTimer(15); + private java.beans.PropertyChangeListener viewPropertyListener = null; + private InterpolatorTimer.ViewProperties viewTarget = null; + private boolean smoothViewChange = true; + // View LatLon properties. + private final double viewLatLonMinChangeFactor = 0.00001; + private final double viewLatLonMaxChangeFactor = 0.2; + private final double viewLatLonErrorThresold = 0.000000001; + private final double viewLatLonStepCoefficient = 0.6; + private final double viewLatLonCenterStepCoefficient = 0.1; + // View heading and pitch (angle) properties. + private final double viewAngleChangeFactor = 0.25; + private final double viewAngleErrorThreshold = 0.0001; + private final double viewAngleStepCoefficient = 0.3; + private final double viewAngleResetStepCoefficient = 0.1; + // View zoom properties. + private final double viewZoomChangeFactor = 0.05; + private final double viewZoomErrorThreshold = 0.001; + private final double viewZoomStepCoefficient = 0.1; + + public void setEventSource(WorldWindow newWorldWindow) + { + if (newWorldWindow != null && !(newWorldWindow instanceof java.awt.Component)) + { + String message = WorldWind.retrieveErrMsg("awt.AWTInputHandler.EventSourceNotAComponent"); + WorldWind.logger().log(Level.FINER, message); + throw new IllegalArgumentException(message); + } + + if (newWorldWindow == this.worldWindow) + return; + + if (this.worldWindow != null) + { + java.awt.Component c = (java.awt.Component) this.worldWindow; + c.removeKeyListener(this); + c.removeMouseMotionListener(this); + c.removeMouseListener(this); + c.removeMouseWheelListener(this); + + if (this.keyPollTimer != null) + c.removeKeyListener(this.keyPollTimer); + } + + this.worldWindow = newWorldWindow; + + if (this.worldWindow == null) + return; + + java.awt.Component c = (java.awt.Component) this.worldWindow; + c.addKeyListener(this); + c.addMouseMotionListener(this); + c.addMouseListener(this); + c.addMouseWheelListener(this); + + if (this.keyPollTimer != null) + c.addKeyListener(this.keyPollTimer); + } + + public WorldWindow getEventSource() + { + return this.worldWindow; + } + + public void setHoverDelay(int delay) + { + this.hoverTimer.setDelay(delay); + } + + public int getHoverDelay() + { + return this.hoverTimer.getDelay(); + } + + public void keyTyped(KeyEvent keyEvent) + { + if (this.worldWindow == null) // include this test to ensure any derived implementation performs it + return; + } + + public void keyPressed(KeyEvent keyEvent) + { + if (this.worldWindow == null) // include this test to ensure any derived implementation performs it + return; + } + + public void keyReleased(KeyEvent keyEvent) + { + if (this.worldWindow == null) + return; + + if (keyEvent == null) + return; + + View view = this.worldWindow.getView(); + if (view == null) + return; + + int keyCode = keyEvent.getKeyCode(); + if (keyCode == KeyEvent.VK_SPACE) + { + this.interpolatorTimer.stop(); + } + else if (keyCode == KeyEvent.VK_N) + { + InterpolatorTimer.ViewProperties newProps = new InterpolatorTimer.ViewProperties(); + newProps.heading = Angle.fromDegrees(0); + this.setViewProperties(view, newProps, this.viewAngleResetStepCoefficient, this.viewAngleErrorThreshold, + true); + } + else if (keyCode == KeyEvent.VK_R) + { + InterpolatorTimer.ViewProperties newProps = new InterpolatorTimer.ViewProperties(); + newProps.heading = Angle.fromDegrees(0); + newProps.pitch = Angle.fromDegrees(0); + this.setViewProperties(view, newProps, this.viewAngleResetStepCoefficient, this.viewAngleErrorThreshold, + true); + } + } + + public void keysPolled(int keyCode, int modifiers) + { + if (this.worldWindow == null) + return; + + View view = this.worldWindow.getView(); + if (view == null) + return; + + int slowMask = (modifiers & InputEvent.ALT_DOWN_MASK); + boolean slow = slowMask != 0x0; + + if (areModifiersExactly(modifiers, slowMask)) + { + double sinHeading = view.getHeading().sin(); + double cosHeading = view.getHeading().cos(); + double latFactor = 0; + double lonFactor = 0; + if (keyCode == KeyEvent.VK_LEFT) + { + latFactor = sinHeading; + lonFactor = -cosHeading; + } + else if (keyCode == KeyEvent.VK_RIGHT) + { + latFactor = -sinHeading; + lonFactor = cosHeading; + } + else if (keyCode == KeyEvent.VK_UP) + { + latFactor = cosHeading; + lonFactor = sinHeading; + } + else if (keyCode == KeyEvent.VK_DOWN) + { + latFactor = -cosHeading; + lonFactor = -sinHeading; + } + if (latFactor != 0 || lonFactor != 0) + { + Globe globe = this.worldWindow.getModel().getGlobe(); + if (globe != null) + { + LatLon latLonChange = this.computeViewLatLonChange(view, globe, 10 * latFactor, 10 * lonFactor, + slow); + this.setViewLatLon(view, this.computeNewViewLatLon(view, latLonChange.getLatitude(), + latLonChange.getLongitude())); + return; + } + } + } + + double headingFactor = 0; + double pitchFactor = 0; + if (areModifiersExactly(modifiers, slowMask)) + { + if (keyCode == KeyEvent.VK_PAGE_DOWN) + pitchFactor = 1; + else if (keyCode == KeyEvent.VK_PAGE_UP) + pitchFactor = -1; + } + else if (areModifiersExactly(modifiers, InputEvent.SHIFT_DOWN_MASK | slowMask)) + { + if (keyCode == KeyEvent.VK_LEFT) + headingFactor = -1; + else if (keyCode == KeyEvent.VK_RIGHT) + headingFactor = 1; + else if (keyCode == KeyEvent.VK_UP) + pitchFactor = -1; + else if (keyCode == KeyEvent.VK_DOWN) + pitchFactor = 1; + } + if (headingFactor != 0) + { + this.setViewAngle(view, this.computeNewViewHeading(view, + this.computeViewAngleChange(4 * headingFactor, slow)), null); + return; + } + else if (pitchFactor != 0) + { + this.setViewAngle(view, null, this.computeNewViewPitch(view, + this.computeViewAngleChange(4 * pitchFactor, slow))); + return; + } + + double zoomFactor = 0; + if (areModifiersExactly(modifiers, slowMask)) + { + if (keyCode == KeyEvent.VK_ADD || + keyCode == KeyEvent.VK_EQUALS) + zoomFactor = -1; + else if (keyCode == KeyEvent.VK_SUBTRACT || + keyCode == KeyEvent.VK_MINUS) + zoomFactor = 1; + } + else if (areModifiersExactly(modifiers, InputEvent.CTRL_DOWN_MASK | slowMask) + || areModifiersExactly(modifiers, InputEvent.META_DOWN_MASK | slowMask)) + { + if (keyCode == KeyEvent.VK_UP) + zoomFactor = -1; + else if (keyCode == KeyEvent.VK_DOWN) + zoomFactor = 1; + } + if (zoomFactor != 0) + { + this.setViewZoom(view, this.computeNewViewZoom(view, this.computeZoomViewChange(zoomFactor, slow))); + return; + } + } + + public void mouseClicked(final MouseEvent mouseEvent) + { + if (this.worldWindow == null) // include this test to ensure any subsequent implementation performs it + return; + + if (mouseEvent == null) + return; + + View view = this.worldWindow.getView(); + if (view == null) + return; + +// int button = mouseEvent.getButton(); +// if (this.mouseClickButton != button) +// this.mouseClickCount = 1; +// else +// this.mouseClickCount = mouseEvent.getClickCount(); +// this.mouseClickButton = button; +// if (this.mouseClickCount == 1) +// { +// javax.swing.Timer clickTimer = new javax.swing.Timer(this.mouseClickCoalesceTime, new ActionListener() +// { +// public void actionPerformed(ActionEvent evt) +// { +// AWTInputHandler.this.mouseClickedCoalesced(mouseEvent, AWTInputHandler.this.mouseClickCount); +// } +// }); +// clickTimer.setRepeats(false); +// clickTimer.start(); +// } + +// if ( (null != this.lastPickedObjects) +// && (null != this.lastPickedObjects.getTopObject()) +// && this.lastPickedObjects.getTopObject().isTerrain()) +// return; + + if (this.lastPickedObjects != null && this.lastPickedObjects.size() > 0) + { + PickedObject top = this.lastPickedObjects.getTopObject(); + if (top != null && top.isTerrain()) + { + InterpolatorTimer.ViewProperties newProps = new InterpolatorTimer.ViewProperties(); + Position position = top.getPosition(); + newProps.latLon = new LatLon(position.getLatitude(), position.getLongitude()); + this.setViewProperties(view, newProps, this.viewLatLonCenterStepCoefficient, + this.viewLatLonErrorThresold, true); + return; + } + + // Something is under the cursor, so it's deemed "selected". + + if (MouseEvent.BUTTON1 == mouseEvent.getButton()) + { + if (mouseEvent.getClickCount() % 2 == 1) + { + this.callSelectListeners(new SelectEvent(this.worldWindow, SelectEvent.LEFT_CLICK, + mouseEvent, this.lastPickedObjects)); + } + else + { + this.callSelectListeners(new SelectEvent(this.worldWindow, SelectEvent.LEFT_DOUBLE_CLICK, + mouseEvent, this.lastPickedObjects)); + } + } + else if (MouseEvent.BUTTON3 == mouseEvent.getButton()) + { + this.callSelectListeners(new SelectEvent(this.worldWindow, SelectEvent.RIGHT_CLICK, + mouseEvent, this.lastPickedObjects)); + } + + view.firePropertyChange(AVKey.VIEW, null, view); + } + } + + public void mousePressed(MouseEvent mouseEvent) + { + if (this.worldWindow == null) + return; + + this.cancelHover(); + } + + public void mouseReleased(MouseEvent mouseEvent) + { + if (this.worldWindow == null) + return; + + this.doHover(true); + } + + public void mouseEntered(MouseEvent mouseEvent) + { + if (this.worldWindow == null) + return; + + this.cancelHover(); + } + + public void mouseExited(MouseEvent mouseEvent) + { + if (this.worldWindow == null) + return; + + this.cancelHover(); + } + + public void mouseDragged(MouseEvent mouseEvent) + { + if (this.worldWindow == null) + return; + + if (mouseEvent == null) + return; + + View view = this.worldWindow.getView(); + if (view == null) + return; + + if (this.worldWindow.getModel() == null) + return; + + java.awt.Point mouseMove = new java.awt.Point(mouseEvent.getPoint().x - this.lastMousePoint.x, + mouseEvent.getPoint().y - this.lastMousePoint.y); + + if (areModifiersExactly(mouseEvent, InputEvent.BUTTON1_DOWN_MASK)) + { + LatLon latLonChange = null; + + Position prev = view.computePositionFromScreenPoint(this.lastMousePoint.x, this.lastMousePoint.y); + Position cur = view.computePositionFromScreenPoint(mouseEvent.getPoint().x, mouseEvent.getPoint().y); + if (prev != null && cur != null) + { + latLonChange = new LatLon(prev.getLatitude().subtract(cur.getLatitude()), + prev.getLongitude().subtract(cur.getLongitude())); + } + else + { + Globe globe = this.worldWindow.getModel().getGlobe(); + if (globe != null) + { + double sinHeading = view.getHeading().sin(); + double cosHeading = view.getHeading().cos(); + double latFactor = cosHeading * mouseMove.y + sinHeading * mouseMove.x; + double lonFactor = sinHeading * mouseMove.y - cosHeading * mouseMove.x; + latLonChange = this.computeViewLatLonChange(view, globe, latFactor, lonFactor, false); + } + } + + if (latLonChange != null) + { + this.setViewLatLon(view, this.computeNewViewLatLon(view, latLonChange.getLatitude(), + latLonChange.getLongitude())); + } + } + else if (areModifiersExactly(mouseEvent, InputEvent.BUTTON3_DOWN_MASK) + || areModifiersExactly(mouseEvent, InputEvent.BUTTON1_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)) + { + double headingDirection = 1; + Object source = mouseEvent.getSource(); + if (source != null && source instanceof java.awt.Component) + { + java.awt.Component component = (java.awt.Component) source; + if (mouseEvent.getPoint().y < component.getHeight() / 2) + headingDirection = -1; + } + this.setViewAngle(view, + this.computeNewViewHeading(view, this.computeViewAngleChange(headingDirection * mouseMove.x, false)), + this.computeNewViewPitch(view, this.computeViewAngleChange(mouseMove.y, false))); + } + + this.lastMousePoint = mouseEvent.getPoint(); + } + + public void mouseMoved(MouseEvent mouseEvent) + { + if (this.worldWindow == null) + return; + + if (mouseEvent == null) + return; + + this.lastMousePoint = mouseEvent.getPoint(); + + View view = this.worldWindow.getView(); + if (view == null) + return; + + // Forward event to mouseDragged() for OS X. +// if (areModifiersExactly(mouseEvent, InputEvent.CTRL_DOWN_MASK)) +// { +// this.mouseDragged(mouseEvent); +// } + + Model model = this.worldWindow.getModel(); + if (model == null) + return; + + Globe globe = model.getGlobe(); + if (globe == null) + return; + + this.lastPickedObjects = this.worldWindow.pick(mouseEvent.getPoint()); + PickedObject top = null; + if (this.lastPickedObjects != null && this.lastPickedObjects.size() > 0) + top = this.lastPickedObjects.getTopObject(); + + PickedObjectList selected = null; + if (!(top == null || top.isTerrain())) // if not terrain + selected = this.lastPickedObjects; + + this.callSelectListeners(new SelectEvent(this.worldWindow, SelectEvent.ROLLOVER, mouseEvent, selected)); + + this.doHover(true); + + Position p = null; + if (null != top && top.hasPosition()) + p = top.getPosition(); + else if (this.lastPickedObjects != null && this.lastPickedObjects.getTerrainObject() != null) + p = this.lastPickedObjects.getTerrainObject().getPosition(); + + this.callPositionListeners(new PositionEvent(this.worldWindow, mouseEvent, this.previousPickPosition, p)); + this.previousPickPosition = p; + } + + public void mouseWheelMoved(MouseWheelEvent mouseWheelEvent) + { + if (this.worldWindow == null) + return; + + if (mouseWheelEvent == null) + return; + + View view = this.worldWindow.getView(); + if (view == null) + return; + + int wheelRotation = mouseWheelEvent.getWheelRotation(); + double wheelDirection = Math.signum(wheelRotation); + this.setViewZoom(view, this.computeNewViewZoom(view, this.computeZoomViewChange(wheelDirection, false))); + } + + private static boolean areModifiersExactly(InputEvent inputEvent, int mask) + { + return areModifiersExactly(inputEvent.getModifiersEx(), mask); + } + + private static boolean areModifiersExactly(int modifiersEx, int mask) + { + return modifiersEx == mask; + } + + private boolean isPickListEmpty(PickedObjectList pickList) + { + return pickList == null || pickList.size() < 1; + } + + private void doHover(boolean reset) + { + if (!(this.isPickListEmpty(this.hoverObjects) || this.isPickListEmpty(this.lastPickedObjects))) + { + PickedObject hover = this.hoverObjects.getTopObject(); + PickedObject last = this.lastPickedObjects.getTopObject(); + + if (hover != null && last != null && hover.getObject().equals(last.getObject())) + { + return; // object picked is the hover object. don't do anything but wait for the timer to expire. + } + } + + this.cancelHover(); + + if (!reset) + return; + + if ((null != this.lastPickedObjects) + && (null != this.lastPickedObjects.getTopObject()) + && this.lastPickedObjects.getTopObject().isTerrain()) + return; + + this.hoverObjects = this.lastPickedObjects; + this.hoverTimer.restart(); + } + + private void cancelHover() + { + if (this.isHovering) + { + this.callSelectListeners(new SelectEvent(this.worldWindow, SelectEvent.HOVER, null, null)); + } + + this.isHovering = false; + this.hoverObjects = null; + this.hoverTimer.stop(); + } + + private boolean hoverMatches() + { + if (this.isPickListEmpty(this.lastPickedObjects) || this.isPickListEmpty(this.hoverObjects)) + return false; + + PickedObject lastTop = this.lastPickedObjects.getTopObject(); + + if (null != lastTop && lastTop.isTerrain()) + return false; + + PickedObject newTop = this.hoverObjects.getTopObject(); + //noinspection SimplifiableIfStatement + if (lastTop == null || newTop == null || lastTop.getObject() == null || newTop.getObject() == null) + return false; + + return lastTop.getObject().equals(newTop.getObject()); + } + + public void addSelectListener(SelectListener listener) + { + this.eventListeners.add(SelectListener.class, listener); + } + + public void removeSelectListener(SelectListener listener) + { + this.eventListeners.remove(SelectListener.class, listener); + } + + public void addPositionListener(PositionListener listener) + { + this.eventListeners.add(PositionListener.class, listener); + } + + public void removePositionListener(PositionListener listener) + { + this.eventListeners.remove(PositionListener.class, listener); + } + + private void callSelectListeners(SelectEvent event) + { + for (SelectListener listener : this.eventListeners.getListeners(SelectListener.class)) + { + listener.selected(event); + } + } + + private void callPositionListeners(PositionEvent event) + { + for (PositionListener listener : this.eventListeners.getListeners(PositionListener.class)) + { + listener.moved(event); + } + } + + private void setViewProperties(final View view, InterpolatorTimer.ViewProperties newProperties, + double stepCoefficient, double errorThreshold, boolean forceSmooth) + { + if (view == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.ViewIsNull"); + WorldWind.logger().log(Level.FINE, message); + throw new IllegalArgumentException(message); + } + if (newProperties == null) + { + String message = WorldWind.retrieveErrMsg("awt.InterpolatorTimer.ViewPropertiesIsNull"); + WorldWind.logger().log(Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (forceSmooth || this.smoothViewChange) + { + if (this.viewPropertyListener == null) + { + this.viewPropertyListener = new java.beans.PropertyChangeListener() + { + public void propertyChange(java.beans.PropertyChangeEvent evt) + { + Object newValue = evt.getNewValue(); + if (newValue == null) + { + AWTInputHandler.this.viewTarget = null; + } + else if (newValue instanceof InterpolatorTimer.ViewProperties) + { + InterpolatorTimer.ViewProperties viewProps = (InterpolatorTimer.ViewProperties) newValue; + if (viewProps.latLon != null) + view.goToLatLon(viewProps.latLon); + if (viewProps.heading != null) + view.setHeading(viewProps.heading); + if (viewProps.pitch != null) + view.setPitch(viewProps.pitch); + if (viewProps.zoom != null) + view.setZoom(viewProps.zoom); + view.firePropertyChange(AVKey.VIEW, null, view); + } + } + }; + } + InterpolatorTimer.ViewProperties begin = new InterpolatorTimer.ViewProperties(); + if (newProperties.latLon != null) + begin.latLon = new LatLon(view.getPosition().getLatitude(), view.getPosition().getLongitude()); + if (newProperties.heading != null) + begin.heading = view.getHeading(); + if (newProperties.pitch != null) + begin.pitch = view.getPitch(); + if (newProperties.zoom != null) + begin.zoom = view.getZoom(); + this.viewTarget = newProperties; + this.interpolatorTimer.start(stepCoefficient, errorThreshold, begin, newProperties, + this.viewPropertyListener); + } + else + { + this.viewTarget = null; + this.interpolatorTimer.stop(); + if (newProperties.latLon != null) + view.goToLatLon(newProperties.latLon); + if (newProperties.heading != null) + view.setHeading(newProperties.heading); + if (newProperties.pitch != null) + view.setPitch(newProperties.pitch); + if (newProperties.zoom != null) + view.setZoom(newProperties.zoom); + view.firePropertyChange(AVKey.VIEW, null, view); + } + } + + private void setViewLatLon(final View view, LatLon newLatLon) + { + if (newLatLon == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.LatLonIsNull"); + WorldWind.logger().log(Level.FINE, message); + throw new IllegalArgumentException(message); + } + this.viewTarget = new InterpolatorTimer.ViewProperties(); + this.viewTarget.latLon = newLatLon; + this.setViewProperties(view, this.viewTarget, this.viewLatLonStepCoefficient, this.viewLatLonErrorThresold, + false); + } + + private LatLon computeNewViewLatLon(View view, Angle latChange, Angle lonChange) + { + double latDegrees; + double lonDegrees; + if (this.viewTarget != null && this.viewTarget.latLon != null) + { + latDegrees = this.viewTarget.latLon.getLatitude().getDegrees(); + lonDegrees = this.viewTarget.latLon.getLongitude().getDegrees(); + } + else + { + latDegrees = view.getPosition().getLatitude().getDegrees(); + lonDegrees = view.getPosition().getLongitude().getDegrees(); + } + latDegrees = latDegrees + latChange.getDegrees(); + lonDegrees = lonDegrees + lonChange.getDegrees(); + if (latDegrees < -90) + latDegrees = -90; + else if (latDegrees > 90) + latDegrees = 90; + if (lonDegrees < -180) + lonDegrees = lonDegrees + 360; + else if (lonDegrees > 180) + lonDegrees = lonDegrees - 360; + return LatLon.fromDegrees(latDegrees, lonDegrees); + } + + private LatLon computeViewLatLonChange(View view, Globe globe, double latFactor, double lonFactor, boolean slow) + { + Point eye = view.getEyePoint(); + if (eye == null) + return null; + + double normAlt = clamp((eye.length() / globe.getMaximumRadius()) - 1, 0, 1); + double factor = ((1 - normAlt) * this.viewLatLonMinChangeFactor + normAlt * this.viewLatLonMaxChangeFactor) + * (slow ? 2.5e-1 : 1); + return LatLon.fromDegrees(latFactor * factor, lonFactor * factor); + } + + private void setViewAngle(final View view, Angle newHeading, Angle newPitch) + { + if (newHeading == null && newPitch == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(Level.FINE, message); + throw new IllegalArgumentException(message); + } + this.viewTarget = new InterpolatorTimer.ViewProperties(); + this.viewTarget.heading = newHeading; + this.viewTarget.pitch = newPitch; + this.setViewProperties(view, this.viewTarget, this.viewAngleStepCoefficient, this.viewAngleErrorThreshold, + false); + } + + private Angle computeNewViewHeading(View view, Angle change) + { + double degrees; + if (this.viewTarget != null && this.viewTarget.heading != null) + degrees = this.viewTarget.heading.getDegrees(); + else + degrees = view.getHeading().getDegrees(); + degrees = degrees + change.getDegrees(); + if (degrees < 0) + degrees = degrees + 360; + else if (degrees > 360) + degrees = degrees - 360; + return Angle.fromDegrees(degrees); + } + + private Angle computeViewAngleChange(double factor, boolean slow) + { + return Angle.fromDegrees(factor * this.viewAngleChangeFactor * (slow ? 2.5e-1 : 1)); + } + + private Angle computeNewViewPitch(View view, Angle change) + { + double degrees; + if (this.viewTarget != null && this.viewTarget.pitch != null) + degrees = this.viewTarget.pitch.getDegrees(); + else + degrees = view.getPitch().getDegrees(); + degrees = degrees + change.getDegrees(); + Angle[] constraints = view.getPitchConstraints(); + return Angle.fromDegrees(clamp(degrees, constraints[0].getDegrees(), constraints[1].getDegrees())); + } + + private void setViewZoom(final View view, double newZoom) + { + this.viewTarget = new InterpolatorTimer.ViewProperties(); + this.viewTarget.zoom = newZoom; + this.setViewProperties(view, this.viewTarget, this.viewZoomStepCoefficient, this.viewZoomErrorThreshold, + false); + } + + private double computeNewViewZoom(View view, double change) + { + double logZoom; + if (this.viewTarget != null && this.viewTarget.zoom != null) + logZoom = Math.log(this.viewTarget.zoom); + else + logZoom = Math.log(view.getZoom()); + logZoom = logZoom + change; + double[] constraints = view.getZoomConstraints(); + return clamp(Math.exp(logZoom), constraints[0], constraints[1]); + } + + private double computeZoomViewChange(double factor, boolean slow) + { + return factor * this.viewZoomChangeFactor * (slow ? 2.5e-1 : 1); + } + + private static double clamp(double x, double min, double max) + { + return x < min ? min : (x > max ? max : x); + } +} diff --git a/gov/nasa/worldwind/awt/InterpolatorTimer.java b/gov/nasa/worldwind/awt/InterpolatorTimer.java new file mode 100644 index 0000000..4b4a546 --- /dev/null +++ b/gov/nasa/worldwind/awt/InterpolatorTimer.java @@ -0,0 +1,303 @@ +/* +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.awt; + +import gov.nasa.worldwind.*; +import gov.nasa.worldwind.geom.*; + +/** + * @author dcollins + * @version $Id: InterpolatorTimer.java 1818 2007-05-10 15:59:46Z dcollins $ + */ +class InterpolatorTimer +{ + static class ViewProperties + { + public LatLon latLon; + public Angle heading; + public Angle pitch; + public Double zoom; + } + + private java.util.TimerTask timerTask; + private volatile boolean isRunning; + private java.beans.PropertyChangeListener listener; + private double stepCoefficient; + private double errorThreshold; + private Object begin; + private Object end; + + public InterpolatorTimer(final int period) + { + if (period < 0) + { + String message = WorldWind.retrieveErrMsg("awt.InterpolatorTimer.PeriodLessThanZero"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + java.util.Timer timer = new java.util.Timer(); + this.timerTask = new java.util.TimerTask() + { + public void run() + { + long time = System.currentTimeMillis(); + if (time - this.scheduledExecutionTime() >= 2 * period) + return; + if (InterpolatorTimer.this.listener == null) + return; + InterpolatorTimer.this.updateAndNotify(InterpolatorTimer.this.listener); + } + }; + timer.schedule(timerTask, 0, period); + } + + public synchronized boolean isRunning() + { + return this.isRunning; + } + + private static double error(Object x, Object end) + { + if (x instanceof Angle) + return error((Angle) x, (Angle) end); + else if (x instanceof Double) + return error((Double) x, (Double) end); + else if (x instanceof LatLon) + return error((LatLon) x, (LatLon) end); + else if (x instanceof Point) + return error((Point) x, (Point) end); + else if (x instanceof ViewProperties) + return error((ViewProperties) x, (ViewProperties) end); + else + return 0; + } + + private static double error(Double x, Double end) + { + return Math.abs(end - x); + } + + private static double error(Angle x, Angle end) + { + return Math.abs(end.getDegrees() - x.getDegrees()) % 360d; + } + + private static double error(LatLon x, LatLon end) + { + double latError = Math.abs(end.getLatitude().getRadians() - x.getLatitude().getRadians()) % (Math.PI / 2); + double lonError = Math.abs(end.getLongitude().getRadians() - x.getLongitude().getRadians()) % Math.PI; + return latError > lonError ? latError : lonError; + } + + private static double error(Point x, Point end) + { + double maxError = 0; + maxError = Math.max(maxError, error(x.x(), end.x())); + maxError = Math.max(maxError, error(x.y(), end.y())); + maxError = Math.max(maxError, error(x.z(), end.z())); + maxError = Math.max(maxError, error(x.w(), end.w())); + return maxError; + } + + private static double error(ViewProperties x, ViewProperties end) + { + double maxError = 0; + if (x.latLon != null && end.latLon != null) + maxError = Math.max(maxError, error(x.latLon, end.latLon)); + if (x.heading != null && end.heading != null) + maxError = Math.max(maxError, error(x.heading, end.heading)); + if (x.pitch != null && end.pitch != null) + maxError = Math.max(maxError, error(x.pitch, end.pitch)); + if (x.zoom != null && end.zoom != null) + maxError = Math.max(maxError, error(x.zoom, end.zoom)); + return maxError; + } + + private static Object mix(double t, Object begin, Object end) + { + if (begin instanceof Angle) + return mix(t, (Angle) begin, (Angle) end); + else if (begin instanceof Double) + return mix(t, (Double) begin, (Double) end); + else if (begin instanceof LatLon) + return mix(t, (LatLon) begin, (LatLon) end); + else if (begin instanceof Point) + return mix(t, (Point) begin, (Point) end); + else if (begin instanceof ViewProperties) + return mix(t, (ViewProperties) begin, (ViewProperties) end); + else + return null; + } + + private static Angle mix(double t, Angle begin, Angle end) + { + if (t < 0) + return begin; + else if (t > 1) + return end; + Quaternion beginQuat = Quaternion.EulerToQuaternion(begin.getRadians(), 0, 0); + Quaternion endQuat = Quaternion.EulerToQuaternion(end.getRadians(), 0, 0); + Quaternion q = Quaternion.Slerp(beginQuat, endQuat, t); + Point v = Quaternion.QuaternionToEuler(q); + + if (Double.isNaN(v.x())) + return null; + + return Angle.fromRadians(v.x()); + } + + private static Double mix(double t, Double begin, Double end) + { + if (t < 0) + return begin; + else if (t > 1) + return end; + return (1 - t) * begin + t * end; + } + + private static LatLon mix(double t, LatLon begin, LatLon end) + { + 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()); + } + + private static Point mix(double t, Point begin, Point end) + { + return new Point(mix(t, begin.x(), end.x()), mix(t, begin.y(), end.y()), + mix(t, begin.z(), end.z()), mix(t, begin.w(), end.w())); + } + + private static ViewProperties mix(double t, ViewProperties begin, ViewProperties end) + { + ViewProperties x = new ViewProperties(); + if (begin.latLon != null && end.latLon != null) + x.latLon = mix(t, begin.latLon, end.latLon); + if (begin.heading != null && end.heading != null) + { + if (begin.pitch != null && end.pitch != null) + { + // TODO: this doesn't pan heading & pitch equally + Quaternion beginQuat = Quaternion.EulerToQuaternion(begin.heading.getRadians(), + begin.pitch.getRadians(), 0); + Quaternion endQuat = Quaternion.EulerToQuaternion(end.heading.getRadians(), + end.pitch.getRadians(), 0); + Quaternion q = Quaternion.Slerp(beginQuat, endQuat, t); + Point v = Quaternion.QuaternionToEuler(q); + if (!Double.isNaN(v.x()) && !Double.isNaN(v.y())) + { + x.heading = Angle.fromRadians(v.x()); + x.pitch = Angle.fromRadians(v.y()); + } + else + { + x.heading = null; + x.pitch = null; + } + } + else + { + x.heading = mix(t, begin.heading, end.heading); + } + } + else if (begin.pitch != null && end.pitch != null) + x.pitch = mix(t, begin.pitch, end.pitch); + if (begin.zoom != null && end.zoom != null) + x.zoom = mix(t, begin.zoom, end.zoom); + return x; + } + + public synchronized void start(double stepCoefficient, double errorThreshold, Object begin, Object end, + java.beans.PropertyChangeListener listener) + { + if (stepCoefficient < 0) + { + String message = WorldWind.retrieveErrMsg("awt.InterpolatorTimer.StepCoefficientLessThanZero"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + if (errorThreshold < 0) + { + String message = WorldWind.retrieveErrMsg("awt.InterpolatorTimer.ErrorThresholdLessThanZero"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + if (begin == null || end == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.ObjectIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + if (!end.getClass().isInstance(begin)) + { + String message = WorldWind.retrieveErrMsg("awt.InterpolatorTimer.DifferentMixTypes"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + if (listener == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.ListenerIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (this.isRunning && this.listener != null && !this.listener.equals(listener)) + this.listener.propertyChange(new java.beans.PropertyChangeEvent(this, null, null, null)); + + this.listener = listener; + this.stepCoefficient = stepCoefficient; + this.errorThreshold = errorThreshold; + this.begin = begin; + this.end = end; + this.isRunning = true; + } + + public synchronized void stop() + { + if (this.isRunning) + { + if (this.listener != null) + listener.propertyChange(new java.beans.PropertyChangeEvent(this, null, null, null)); + this.listener = null; + this.stepCoefficient = errorThreshold = -1; + this.begin = end = null; + this.isRunning = false; + } + } + + private synchronized void updateAndNotify(java.beans.PropertyChangeListener listener) + { + if (!this.isRunning || this.begin == null || listener == null) + return; + + Object newValue = mix(this.stepCoefficient, this.begin, this.end); + if (newValue == null) + { + this.stop(); + return; + } + + double error = error(newValue, this.end); + if (error < this.errorThreshold) + { + this.stop(); + return; + } + + listener.propertyChange(new java.beans.PropertyChangeEvent(this, null, this.begin, newValue)); + this.begin = newValue; + } +} diff --git a/gov/nasa/worldwind/awt/KeyPollTimer.java b/gov/nasa/worldwind/awt/KeyPollTimer.java new file mode 100644 index 0000000..d2a6515 --- /dev/null +++ b/gov/nasa/worldwind/awt/KeyPollTimer.java @@ -0,0 +1,129 @@ +/* +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.awt; + +import gov.nasa.worldwind.*; + +/** + * @author dcollins + * @version $Id: KeyPollTimer.java 1789 2007-05-08 17:26:45Z dcollins $ + */ +class KeyPollTimer implements java.awt.event.KeyListener +{ + private java.util.Timer timer; + private java.util.TimerTask timerTask; + private final int period; + private final java.awt.event.ActionListener listener; + private java.util.ArrayList keys = new java.util.ArrayList(); + private java.util.Stack keyStack = new java.util.Stack(); + private int modifiers; + + public KeyPollTimer(int period, java.util.Collection keys, java.awt.event.ActionListener listener) + { + if (period < 0) + { + String message = WorldWind.retrieveErrMsg("awt.KeyPollTimer.PeriodLessThanZero"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + if (keys == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.CollectionIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + this.timer = new java.util.Timer(); + this.period = period; + this.listener = listener; + this.keys.addAll(keys); + } + + public synchronized boolean isRunning() + { + return this.timerTask != null; + } + + public void keyTyped(java.awt.event.KeyEvent e) + { + } + + public void keyPressed(java.awt.event.KeyEvent e) + { + if (e == null) + return; + + if (this.onKeyEvent(e)) + { + if (this.timerTask == null) + this.start(); + } + } + + public void keyReleased(java.awt.event.KeyEvent e) + { + if (e == null) + return; + + if (this.onKeyEvent(e)) + { + if (this.timerTask != null && this.keyStack.empty()) + this.stop(); + } + } + + private synchronized boolean onKeyEvent(java.awt.event.KeyEvent e) + { + this.modifiers = e.getModifiersEx(); + + Integer keyCode = e.getKeyCode(); + if (this.keys.contains(keyCode)) + { + if (this.keyStack.contains(keyCode)) + this.keyStack.remove(keyCode); + if (e.getID() == java.awt.event.KeyEvent.KEY_PRESSED) + this.keyStack.push(keyCode); + return true; + } + + return false; + } + + public synchronized void start() + { + if (this.timerTask == null) + { + this.timerTask = new java.util.TimerTask() + { + public void run() + { + long time = System.currentTimeMillis(); + if (time - this.scheduledExecutionTime() >= 2 * KeyPollTimer.this.period) + return; + KeyPollTimer.this.updateAndNotify(KeyPollTimer.this.listener); + } + }; + this.timer.schedule(timerTask, 0, this.period); + } + } + + public synchronized void stop() + { + if (this.timerTask != null) + { + this.timerTask.cancel(); + this.timerTask = null; + } + } + + private synchronized void updateAndNotify(java.awt.event.ActionListener listener) + { + if (listener == null) + return; + if (this.keyStack.empty()) + return; + listener.actionPerformed(new java.awt.event.ActionEvent(keyStack.peek(), 0, null, modifiers)); + } +} diff --git a/gov/nasa/worldwind/awt/WorldWindowGLCanvas.java b/gov/nasa/worldwind/awt/WorldWindowGLCanvas.java new file mode 100644 index 0000000..5fe4161 --- /dev/null +++ b/gov/nasa/worldwind/awt/WorldWindowGLCanvas.java @@ -0,0 +1,216 @@ +/* +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.awt; + +import gov.nasa.worldwind.*; + +import javax.media.opengl.*; +import java.awt.*; + +/** + * WorldWindowGLCanvas is an AWT component for displaying World Wind {@link Model}s (globe and layers). + * This implementation is a heavyweight AWT component. It derives from {@link GLCanvas} and therefore also provides a + * general {@link GLDrawable} for OpenGL rendering. + * + * @author Tom Gaskins + * @version $Id: WorldWindowGLCanvas.java 1793 2007-05-08 21:28:57Z tgaskins $ + */ +public class WorldWindowGLCanvas extends GLCanvas implements WorldWindow +{ + private static final GLCapabilities caps = new GLCapabilities(); + + static + { + caps.setAlphaBits(8); + caps.setRedBits(8); + caps.setGreenBits(8); + caps.setBlueBits(8); +// caps.setStencilBits(8); + caps.setDepthBits(24); + } + + private final WorldWindowGLAutoDrawable wwd; // everything delegates to wwd + private InputHandler inputHandler; + + /** + * Constructs a new WorldWindowGLCanvas window on the default graphics device. + */ + public WorldWindowGLCanvas() + { + super(caps); + try + { + this.wwd = new WorldWindowGLAutoDrawable(this); // TODO: Make class configurable + this.createView(); + this.createDefaultInputHandler(); + } + catch (Exception e) + { + String message = WorldWind.retrieveErrMsg("awt.WorldWindowGLSurface.UnabletoCreateWindow"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new WWRuntimeException(message, e); + } + } + + /** + * Constructs a new WorldWindowGLCanvas window on the default graphics device that will share graphics + * resources with another WorldWindowGLCanvas window. The other window, sharewith, may not + * be null + * + * @param shareWith a WorldWindowGLCanvas with which to share graphics resources. + * @throws NullPointerException if shareWith is null + * @see GLCanvas#GLCanvas(GLCapabilities,GLCapabilitiesChooser,GLContext,GraphicsDevice) + */ + public WorldWindowGLCanvas(WorldWindowGLCanvas shareWith) + { + super(caps, null, shareWith.getContext(), null); + try + { + this.wwd = new WorldWindowGLAutoDrawable(this); + this.createView(); + this.createDefaultInputHandler(); + } + catch (Exception e) + { + String message = WorldWind.retrieveErrMsg("awt.WorldWindowGLSurface.UnabletoCreateWindow"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new WWRuntimeException(message, e); + } + } + + /** + * Constructs a new WorldWindowGLCanvas window that will share graphics resources with another + * WorldWindowGLCanvas window. The new window is created on the specified graphics device. Neither + * shareWith or device may be null. + * + * @param shareWith a WorldWindowGLCanvas with which to share graphics resources. + * @param device the GraphicsDevice on which to create the window. + * @throws NullPointerException if shareWith is null + * @throws IllegalArgumentException if deevice is null + * @see GLCanvas#GLCanvas(GLCapabilities,GLCapabilitiesChooser,GLContext,GraphicsDevice) + */ + public WorldWindowGLCanvas(WorldWindowGLCanvas shareWith, java.awt.GraphicsDevice device) + { + super(caps, null, shareWith.getContext(), device); + + if (device == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.DeviceIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + try + { + this.wwd = new WorldWindowGLAutoDrawable(this); + this.createView(); + this.createDefaultInputHandler(); + } + catch (Exception e) + { + String message = WorldWind.retrieveErrMsg("awt.WorldWindowGLSurface.UnabletoCreateWindow"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new WWRuntimeException(message, e); + } + } + + private void createView() + { + this.setView((View) WorldWind.createConfigurationComponent( + gov.nasa.worldwind.AVKey.VIEW_CLASS_NAME)); + } + + private void createDefaultInputHandler() + { +// this.inputHandler = new gov.nasa.worldwind.awt.AWTInputHandler(); + this.inputHandler = (InputHandler) WorldWind.createConfigurationComponent(AVKey.INPUT_HANDLER_CLASS_NAME); + this.inputHandler.setEventSource(this); + } + + public InputHandler getInputHandler() + { + return inputHandler; + } + + public void setInputHandler(InputHandler eventSource) + { + if (this.inputHandler != null) + this.inputHandler.setEventSource(null); // remove this window as a source of events + + this.inputHandler = eventSource; + if (this.inputHandler != null) + this.inputHandler.setEventSource(this); + } + + public SceneController getSceneController() + { + return this.wwd.getSceneController(); + } + + public PickedObjectList pick(java.awt.Point pickPoint) + { + return this.wwd.pick(pickPoint); + } + + public void setModel(Model model) + { + // null models are permissible + this.wwd.setModel(model); + } + + public Model getModel() + { + return this.wwd.getModel(); + } + + public void setView(View view) + { + // null views are permissible + if (view != null) + this.wwd.setView(view); + } + + public View getView() + { + return this.wwd.getView(); + } + + public void setModelAndView(Model model, View view) + { // null models/views are permissible + this.setModel(model); + this.setView(view); + } + + public void addRenderingListener(RenderingListener listener) + { + this.wwd.addRenderingListener(listener); + } + + public void removeRenderingListener(RenderingListener listener) + { + this.wwd.removeRenderingListener(listener); + } + + public void addSelectListener(SelectListener listener) + { + this.inputHandler.addSelectListener(listener); + } + + public void removeSelectListener(SelectListener listener) + { + this.inputHandler.removeSelectListener(listener); + } + + public void addPositionListener(PositionListener listener) + { + this.inputHandler.addPositionListener(listener); + } + + public void removePositionListener(PositionListener listener) + { + this.inputHandler.removePositionListener(listener); + } +} diff --git a/gov/nasa/worldwind/awt/WorldWindowGLJPanel.java b/gov/nasa/worldwind/awt/WorldWindowGLJPanel.java new file mode 100644 index 0000000..dff24b2 --- /dev/null +++ b/gov/nasa/worldwind/awt/WorldWindowGLJPanel.java @@ -0,0 +1,191 @@ +/* +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.awt; + +import gov.nasa.worldwind.*; + +import javax.media.opengl.*; +import java.awt.*; + +public class WorldWindowGLJPanel extends GLJPanel implements WorldWindow +{ + private final WorldWindowGLAutoDrawable wwd; // everything delegates to wwd + private gov.nasa.worldwind.InputHandler inputHandler; + + /** + * Constructs a new WorldWindowGLCanvas window on the default graphics device. + */ + public WorldWindowGLJPanel() + { + try + { + this.wwd = new WorldWindowGLAutoDrawable(this); // TODO: Make class configurable + this.createView(); + this.createDefaultEventSource(); + } + catch (Exception e) + { + String message = WorldWind.retrieveErrMsg("awt.WorldWindowGLSurface.UnabletoCreateWindow"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new gov.nasa.worldwind.WWRuntimeException(message, e); + } + } + + /** + * Constructs a new WorldWindowGLJPanel window on the default graphics device that will share graphics + * resources with another WorldWindowGLJPanel window. The other window, sharewith, may not be + * null + * + * @param shareWith a WorldWindowGLJPanel with which to share graphics resources. + * @throws NullPointerException if shareWith is null + * @see GLCanvas#GLCanvas(GLCapabilities,GLCapabilitiesChooser,GLContext,GraphicsDevice) + */ + public WorldWindowGLJPanel(WorldWindowGLCanvas shareWith) + { + super(null, null, shareWith.getContext()); + try + { + this.wwd = new WorldWindowGLAutoDrawable(this); + this.createView(); + this.createDefaultEventSource(); + } + catch (Exception e) + { + String message = WorldWind.retrieveErrMsg("awt.WorldWindowGLSurface.UnabletoCreateWindow"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new gov.nasa.worldwind.WWRuntimeException(message, e); + } + } + + /** + * Constructs a new WorldWindowGLJPanel window that will share graphics resources with another + * WorldWindowGLJPanel window. The new window is created on the specified graphics device. Neither + * shareWith or device may be null. + * + * @param shareWith a WorldWindowGLCanvas with which to share graphics resources. + * @param device the GraphicsDevice on which to create the window. + * @throws NullPointerException if shareWith is null + * @throws IllegalArgumentException if deevice is null + * @see GLCanvas#GLCanvas(GLCapabilities,GLCapabilitiesChooser,GLContext,GraphicsDevice) + */ + public WorldWindowGLJPanel(WorldWindowGLCanvas shareWith, java.awt.GraphicsDevice device) + { + super(null, null, shareWith.getContext()); + + if (device == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.DeviceIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + try + { + this.wwd = new WorldWindowGLAutoDrawable(this); + this.createView(); + this.createDefaultEventSource(); + } + catch (Exception e) + { + String message = WorldWind.retrieveErrMsg("awt.WorldWindowGLSurface.UnabletoCreateWindow"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new WWRuntimeException(message, e); + } + } + + private void createView() + { + this.setView((View) WorldWind.createConfigurationComponent(AVKey.VIEW_CLASS_NAME)); + } + + private void createDefaultEventSource() + { + this.inputHandler = new gov.nasa.worldwind.awt.AWTInputHandler(); + this.inputHandler.setEventSource(this); + } + + public InputHandler getInputHandler() + { + return inputHandler; + } + + public void setInputHandler(InputHandler eventSource) + { + if (this.inputHandler != null) + this.inputHandler.setEventSource(null); // remove this window as a source of events + + this.inputHandler = eventSource; + if (this.inputHandler != null) + this.inputHandler.setEventSource(this); + } + + public SceneController getSceneController() + { + return this.wwd.getSceneController(); + } + + public PickedObjectList pick(java.awt.Point pickPoint) + { + return this.wwd.pick(pickPoint); + } + + public void setModel(Model model) + { + // null models are permissible + this.wwd.setModel(model); + } + + public Model getModel() + { + return this.wwd.getModel(); + } + + public void setView(View view) + { + // null views are permissible + if (view != null) + this.wwd.setView(view); + } + + public View getView() + { + return this.wwd.getView(); + } + + public void setModelAndView(Model model, View view) + { // null models/views are permissible + this.setModel(model); + this.setView(view); + } + + public void addRenderingListener(RenderingListener listener) + { + this.wwd.addRenderingListener(listener); + } + + public void removeRenderingListener(RenderingListener listener) + { + this.wwd.removeRenderingListener(listener); + } + + public void addSelectListener(SelectListener listener) + { + this.inputHandler.addSelectListener(listener); + } + + public void removeSelectListener(SelectListener listener) + { + this.inputHandler.removeSelectListener(listener); + } + + public void addPositionListener(PositionListener listener) + { + } + + public void removePositionListener(PositionListener listener) + { + } +} diff --git a/gov/nasa/worldwind/formats/gpx/ElementParser.java b/gov/nasa/worldwind/formats/gpx/ElementParser.java new file mode 100644 index 0000000..727fc40 --- /dev/null +++ b/gov/nasa/worldwind/formats/gpx/ElementParser.java @@ -0,0 +1,178 @@ +/* +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.formats.gpx; + +import gov.nasa.worldwind.*; + +/** + * @author tag + * @version $Id: ElementParser.java 513 2007-01-18 00:33:16Z ericdalgliesh $ + */ +public class ElementParser +{ + protected final String elementName; + protected ElementParser currentElement = null; + protected String currentCharacters = null; + + /** + * @param elementName the element's name, may not be null + * @throws IllegalArgumentException if elementName is null + */ + protected ElementParser(String elementName) + { + if (elementName == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.ElementNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + this.elementName = elementName; + } + + public String getElementName() + { + return this.elementName; + } + + /** + * Starts an element. No parameters may be null. + * + * @param uri + * @param lname + * @param qname + * @param attributes + * @throws org.xml.sax.SAXException + * @throws IllegalArgumentException if any argument is null + */ + public void startElement(String uri, String lname, String qname, org.xml.sax.Attributes attributes) + throws org.xml.sax.SAXException + { + if (uri == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.URIIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (lname == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.LNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (qname == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.QNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (attributes == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.org.xml.sax.AttributesIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + if (this.currentElement != null) + this.currentElement.startElement(uri, lname, qname, attributes); + else + this.doStartElement(uri, lname, qname, attributes); + } + + /** + * Finishes an element. No parameters may be null. + * + * @param uri + * @param lname + * @param qname + * @throws org.xml.sax.SAXException + * @throws IllegalArgumentException if any argument is null + */ + public void endElement(String uri, String lname, String qname) throws org.xml.sax.SAXException + { + if (uri == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.URIIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (lname == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.LNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (qname == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.QNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (this.currentElement != null) + { + this.currentElement.endElement(uri, lname, qname); + if (lname.equalsIgnoreCase(this.currentElement.elementName)) + this.currentElement = null; + } + + this.doEndElement(uri, lname, qname); + + this.currentCharacters = null; + } + + protected void doStartElement(String uri, String lname, String qname, org.xml.sax.Attributes attributes) + throws org.xml.sax.SAXException + { + } + + protected void doEndElement(String uri, String lname, String qname) throws org.xml.sax.SAXException + { + } + + /** + * @param data + * @param start + * @param length + * @throws IllegalArgumentException if data has length less than 1 + */ + public void characters(char[] data, int start, int length) + { + if (data == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.ArrayIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (data.length < 1) + { + String msg = WorldWind.retrieveErrMsg("generic.arrayInvalidLength") + WorldWind.retrieveErrMsg( + "punctuation.space") + data.length; + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (start < 0 || length < 0) + { + String msg = WorldWind.retrieveErrMsg("generic.indexOutOfRange"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (start + length > data.length) + { + String msg = WorldWind.retrieveErrMsg("generic.indexOutOfRange") + WorldWind.retrieveErrMsg( + "punctuation.space") + String.valueOf(start + length); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + if (this.currentElement != null) + this.currentElement.characters(data, start, length); + else if (this.currentCharacters != null) + this.currentCharacters += new String(data, start, length); + else + this.currentCharacters = new String(data, start, length); + } +} diff --git a/gov/nasa/worldwind/formats/gpx/GpxReader.java b/gov/nasa/worldwind/formats/gpx/GpxReader.java new file mode 100644 index 0000000..0a34e74 --- /dev/null +++ b/gov/nasa/worldwind/formats/gpx/GpxReader.java @@ -0,0 +1,194 @@ +/* +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.formats.gpx; + +import gov.nasa.worldwind.*; + +/** + * @author tag + * @version $Id: GpxReader.java 513 2007-01-18 00:33:16Z ericdalgliesh $ + */ +public class GpxReader // TODO: I18N, proper exception handling, remove stack-trace prints +{ + private javax.xml.parsers.SAXParser parser; + private java.util.List tracks = new java.util.ArrayList(); + + public GpxReader() throws javax.xml.parsers.ParserConfigurationException, org.xml.sax.SAXException + { + javax.xml.parsers.SAXParserFactory factory = javax.xml.parsers.SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + + this.parser = factory.newSAXParser(); + } + + /** + * @param path + * @throws IllegalArgumentException if path is null + * @throws java.io.IOException if no file exists at the location specified by path + * @throws org.xml.sax.SAXException + */ + public void readFile(String path) throws java.io.IOException, org.xml.sax.SAXException + { + if (path == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.PathIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + java.io.File file = new java.io.File(path); + if (!file.exists()) + { + String msg = WorldWind.retrieveErrMsg("generic.fileNotFound"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new java.io.FileNotFoundException(path); + } + + java.io.FileInputStream fis = new java.io.FileInputStream(file); + this.doRead(fis); + } + + /** + * @param stream + * @throws IllegalArgumentException if stream is null + * @throws java.io.IOException + * @throws org.xml.sax.SAXException + */ + public void readStream(java.io.InputStream stream) throws java.io.IOException, org.xml.sax.SAXException + { + if (stream == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.InputStreamIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + this.doRead(stream); + } + + public java.util.List getTracks() + { + return this.tracks; + } + + private void doRead(java.io.InputStream fis) throws java.io.IOException, org.xml.sax.SAXException + { + this.parser.parse(fis, new Handler()); + } + + private class Handler extends org.xml.sax.helpers.DefaultHandler + { + // this is a private class used solely by the containing class, so no validation occurs in it. + + private gov.nasa.worldwind.formats.gpx.ElementParser currentElement = null; + + @Override + public void startDocument() throws org.xml.sax.SAXException + { + super.startDocument(); + } + + @Override + public void endDocument() throws org.xml.sax.SAXException + { + super.endDocument(); + } + + @Override + public void warning(org.xml.sax.SAXParseException saxParseException) throws org.xml.sax.SAXException + { + saxParseException.printStackTrace(); + super.warning(saxParseException); + } + + @Override + public void error(org.xml.sax.SAXParseException saxParseException) throws org.xml.sax.SAXException + { + saxParseException.printStackTrace(); + super.error(saxParseException); + } + + @Override + public void fatalError(org.xml.sax.SAXParseException saxParseException) throws org.xml.sax.SAXException + { + saxParseException.printStackTrace(); + super.fatalError(saxParseException); + } + + @Override + public void startElement(String uri, String lname, String qname, org.xml.sax.Attributes attributes) + throws org.xml.sax.SAXException + { + if (this.currentElement != null) + { + this.currentElement.startElement(uri, lname, qname, attributes); + } + else if (lname.equalsIgnoreCase("trk")) + { + GpxTrack track = new GpxTrack(uri, lname, qname, attributes); + this.currentElement = track; + GpxReader.this.tracks.add(track); + } + } + + @Override + public void endElement(String uri, String lname, String qname) throws org.xml.sax.SAXException + { + if (this.currentElement != null) + { + this.currentElement.endElement(uri, lname, qname); + + if (lname.equalsIgnoreCase(this.currentElement.getElementName())) + this.currentElement = null; + } + } + + @Override + public void characters(char[] data, int start, int length) throws org.xml.sax.SAXException + { + if (this.currentElement != null) + this.currentElement.characters(data, start, length); + } + } + + public static void main(String[] args) + { + try + { + gov.nasa.worldwind.formats.gpx.GpxReader reader = new gov.nasa.worldwind.formats.gpx.GpxReader(); + reader.readFile("src/worldwinddemo/track data/20061126.gpx"); + + System.out.printf("%d tracks\n", reader.getTracks().size()); + for (Track track : reader.getTracks()) + { + System.out.printf("GpxTrack %d segments\n", track.getSegments().size()); + int i = 0; + for (gov.nasa.worldwind.TrackSegment segment : track.getSegments()) + { + System.out.printf("\tSegment %d, %d points\n", i++, segment.getPoints().size()); + int j = 0; + for (gov.nasa.worldwind.TrackPoint point : segment.getPoints()) + { + System.out.printf("\t\t%4d: %s\n", j++, point); + } + } + } + } + catch (javax.xml.parsers.ParserConfigurationException e) + { + e.printStackTrace(); + } + catch (org.xml.sax.SAXException e) + { + e.printStackTrace(); + } + catch (java.io.IOException e) + { + e.printStackTrace(); + } + } +} diff --git a/gov/nasa/worldwind/formats/gpx/GpxTrack.java b/gov/nasa/worldwind/formats/gpx/GpxTrack.java new file mode 100644 index 0000000..307bf4b --- /dev/null +++ b/gov/nasa/worldwind/formats/gpx/GpxTrack.java @@ -0,0 +1,127 @@ +/* +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.formats.gpx; + +import gov.nasa.worldwind.*; + +/** + * @author tag + * @version $Id: GpxTrack.java 522 2007-01-22 01:00:26Z ericdalgliesh $ + */ +public class GpxTrack extends gov.nasa.worldwind.formats.gpx.ElementParser implements gov.nasa.worldwind.Track +{ + private String name; + private int numPoints = -1; + private java.util.List segments = + new java.util.ArrayList(); + + @SuppressWarnings({"UNUSED_SYMBOL", "UnusedDeclaration"}) + public GpxTrack(String uri, String lname, String qname, org.xml.sax.Attributes attributes) + { + super("trk"); + // don't validate uri, lname, qname or attributes - they aren't used. + } + + public java.util.List getSegments() + { + return segments; + } + + public String getName() + { + return name; + } + + public int getNumPoints() + { + if (this.segments == null) + return 0; + + if (this.numPoints >= 0) + return this.numPoints; + + this.numPoints = 0; + for (gov.nasa.worldwind.TrackSegment segment : this.segments) + { + //noinspection UNUSED_SYMBOL,UnusedDeclaration + for (gov.nasa.worldwind.TrackPoint point : segment.getPoints()) + { + ++this.numPoints; + } + } + + return this.numPoints; + } + + /** + * @param uri + * @param lname + * @param qname + * @param attributes + * @throws IllegalArgumentException if lname is null + * @throws org.xml.sax.SAXException + */ + @Override + public void doStartElement(String uri, String lname, String qname, org.xml.sax.Attributes attributes) + throws org.xml.sax.SAXException + { + // don't validate uri, qname or attributes - they aren't used + if (lname == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.LNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (qname == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.QNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (uri == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.URIIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (attributes == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.AttributesIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + if (lname.equalsIgnoreCase("trkSeg")) + { + this.currentElement = new GpxTrackSegment(uri, lname, qname, attributes); + this.segments.add((GpxTrackSegment) this.currentElement); + } + } + + /** + * @param uri + * @param lname + * @param qname + * @throws IllegalArgumentException if lname is null + * @throws org.xml.sax.SAXException + */ + @Override + public void doEndElement(String uri, String lname, String qname) throws org.xml.sax.SAXException + { + // don't validate uri or qname - they aren't used + if (lname == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.LNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (lname.equalsIgnoreCase("name")) + { + this.name = this.currentCharacters; + } + } +} diff --git a/gov/nasa/worldwind/formats/gpx/GpxTrackPoint.java b/gov/nasa/worldwind/formats/gpx/GpxTrackPoint.java new file mode 100644 index 0000000..b5385d2 --- /dev/null +++ b/gov/nasa/worldwind/formats/gpx/GpxTrackPoint.java @@ -0,0 +1,173 @@ +/* +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.formats.gpx; + +import gov.nasa.worldwind.*; + +/** + * @author tag + * @version $Id: GpxTrackPoint.java 515 2007-01-18 05:09:19Z ericdalgliesh $ + */ +public class GpxTrackPoint extends gov.nasa.worldwind.formats.gpx.ElementParser implements gov.nasa.worldwind.TrackPoint +{ + private double latitude; + private double longitude; + private double elevation; + private String time; + + /** + * @param uri + * @param lname + * @param qname + * @param attributes + * @throws IllegalArgumentException if attribues is null + */ + @SuppressWarnings({"UnusedDeclaration"}) + public GpxTrackPoint(String uri, String lname, String qname, org.xml.sax.Attributes attributes) + { + super("trkpt"); + + //don't validate uri, lname or qname - they aren't used. + + if (attributes == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.org.xml.sax.AttributesIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + for (int i = 0; i < attributes.getLength(); i++) + { + String attrName = attributes.getLocalName(i); + String attrValue = attributes.getValue(i); + if (attrName.equalsIgnoreCase("lat")) + { + this.latitude = Double.parseDouble(attrValue); + } + else if (attrName.equalsIgnoreCase("lon")) + { + this.longitude = Double.parseDouble(attrValue); + } + } + } + + @Override + public void doStartElement(String uri, String lname, String qname, org.xml.sax.Attributes attributes) + throws org.xml.sax.SAXException + { + //don't perform validation here - no parameters are actually used + } + + /** + * @param uri + * @param lname + * @param qname + * @throws IllegalArgumentException if lname is null + * @throws org.xml.sax.SAXException + */ + @Override + public void doEndElement(String uri, String lname, String qname) throws org.xml.sax.SAXException + { + if (lname == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.LNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + // don't validate uri or qname - they aren't used. + + if (lname.equalsIgnoreCase("ele")) + { + this.elevation = Double.parseDouble(this.currentCharacters); + } + else if (lname.equalsIgnoreCase("time")) + { + this.time = this.currentCharacters.trim(); + } + } + + public double getLatitude() + { + return latitude; + } + + /** + * @param latitude + * @throws IllegalArgumentException if latitude is less than -90 or greater than 90 + */ + public void setLatitude(double latitude) + { + if (latitude > 90 || latitude < -90) + { + String msg = WorldWind.retrieveErrMsg("generic.angleOutOfRange") + WorldWind.retrieveErrMsg( + "punctuation.space") + latitude; + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + this.latitude = latitude; + } + + public double getLongitude() + { + return longitude; + } + + /** + * @param longitude + * @throws IllegalArgumentException if longitude is less than -180 or greater than 180 + */ + public void setLongitude(double longitude) + { + if (longitude > 180 || longitude < -180) + { + String msg = WorldWind.retrieveErrMsg("generic.angleOutOfRange") + WorldWind.retrieveErrMsg( + "punctuation.space") + longitude; + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + this.longitude = longitude; + } + + public double getElevation() + { + return elevation; + } + + public void setElevation(double elevation) + { + this.elevation = elevation; + } + + public String getTime() + { + return time; + } + + /** + * @param time + * @throws IllegalArgumentException if time is null + */ + public void setTime(String time) + { + if (time == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.TimeIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + this.time = time; + } + + @Override + public String toString() + { + return String.format("(%10.6f\u00B0, %11.6f\u00B0, %10.4g m, %s)", this.latitude, this.longitude, + this.elevation, this.time); + } +} diff --git a/gov/nasa/worldwind/formats/gpx/GpxTrackSegment.java b/gov/nasa/worldwind/formats/gpx/GpxTrackSegment.java new file mode 100644 index 0000000..ba3aa9b --- /dev/null +++ b/gov/nasa/worldwind/formats/gpx/GpxTrackSegment.java @@ -0,0 +1,77 @@ +/* +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.formats.gpx; + +import gov.nasa.worldwind.*; + +/** + * @author tag + * @version $Id: GpxTrackSegment.java 524 2007-01-22 01:06:31Z ericdalgliesh $ + */ +public class GpxTrackSegment extends gov.nasa.worldwind.formats.gpx.ElementParser + implements gov.nasa.worldwind.TrackSegment +{ + private java.util.List points = + new java.util.ArrayList(); + + public GpxTrackSegment(String uri, String lname, String qname, org.xml.sax.Attributes attributes) + { + super("trkseg"); + + // dont' validate uri, lname, qname or attributes as they aren't used. + } + + public java.util.List getPoints() + { + return this.points; + } + + /** + * @param uri + * @param lname + * @param qname + * @param attributes + * @throws IllegalArgumentException if any parameter is null + * @throws org.xml.sax.SAXException + */ + @Override + public void doStartElement(String uri, String lname, String qname, org.xml.sax.Attributes attributes) + throws org.xml.sax.SAXException + { + if (lname == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.LNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + if (uri == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.URIIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (qname == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.QNameIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (attributes == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.AttributesIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + if (lname.equalsIgnoreCase("trkpt")) + { + this.currentElement = new gov.nasa.worldwind.formats.gpx.GpxTrackPoint(uri, lname, qname, attributes); + this.points.add((gov.nasa.worldwind.formats.gpx.GpxTrackPoint) this.currentElement); + } + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/AbstractRpf2DdsCompress.java b/gov/nasa/worldwind/formats/nitfs/AbstractRpf2DdsCompress.java new file mode 100644 index 0000000..b3d965a --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/AbstractRpf2DdsCompress.java @@ -0,0 +1,28 @@ +package gov.nasa.worldwind.formats.nitfs; + +import gov.nasa.worldwind.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: AbstractRpf2DdsCompress Apr 23, 2007 11:06:16 AM lado + */ +abstract class AbstractRpf2DdsCompress extends DDSConverter implements Rpf2DdsCompress +{ + private static DDSBlock4x4 Dxt1TransparentBlock4x4 = new DDSBlock4x4( (short)0, (short)0, 0xFFFFFFFF ); + + public DDSBlock4x4 getDxt1TransparentBlock4x4() + { + return Dxt1TransparentBlock4x4; + } + + public void writeDxt1Header(java.nio.ByteBuffer buffer, int width, int height) + { + DDSConverter.buildHeaderDxt1( buffer, width, height ); + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/Cadrg2DdsCompress.java b/gov/nasa/worldwind/formats/nitfs/Cadrg2DdsCompress.java new file mode 100644 index 0000000..1145ffa --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/Cadrg2DdsCompress.java @@ -0,0 +1,44 @@ +package gov.nasa.worldwind.formats.nitfs; + +import gov.nasa.worldwind.*; + +import java.nio.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: Cadrg2DdsCompress Apr 23, 2007 11:00:07 AM lado + */ +class Cadrg2DdsCompress extends AbstractRpf2DdsCompress +{ + public DDSBlock4x4 compressDxt1Block4x4(NitfsImageBand imageBand, byte[] pixelCodes, boolean hasTransparentPixels) + { + int[] pixels565 = new int[16]; + Color[] colors565 = new Color[16]; + for(int i = 0; i < pixelCodes.length; i++ ) + { + pixels565[i] = imageBand.lookupR5G6B5( 0x00FF & pixelCodes[i] ); + colors565[i] = DDSConverter.getColor565( pixels565[i] ); + } + + int[] extremaIndices = determineExtremeColors( colors565 ); + + if(pixels565[extremaIndices[0]] < pixels565[extremaIndices[1]]) + { + int t = extremaIndices[0]; + extremaIndices[0] = extremaIndices[1]; + extremaIndices[1] = t; + } + + return new DDSBlock4x4( + (short) pixels565[extremaIndices[0]], + (short) pixels565[extremaIndices[1]], + (int) DDSConverter.computeBitMask(colors565, extremaIndices) + ); + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/Cib2DdsCompress.java b/gov/nasa/worldwind/formats/nitfs/Cib2DdsCompress.java new file mode 100644 index 0000000..4ef228b --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/Cib2DdsCompress.java @@ -0,0 +1,59 @@ +package gov.nasa.worldwind.formats.nitfs; + +import gov.nasa.worldwind.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: Cib2DdsCompress Apr 23, 2007 10:59:01 AM lado + */ +class Cib2DdsCompress extends AbstractRpf2DdsCompress +{ + public DDSBlock4x4 compressDxt1Block4x4(NitfsImageBand imageBand, byte[] pixelCodes, boolean hasTransparentPixels) + { + int[] grayPixels = new int[16]; + int minColor = Integer.MAX_VALUE; + int maxColor = Integer.MIN_VALUE; + + for(int i = 0; i < pixelCodes.length; i++ ) + { + grayPixels[i] = imageBand.lookupGray( 0xFF & pixelCodes[i]); + if(grayPixels[i] < minColor) + minColor = grayPixels[i]; + if(grayPixels[i] > maxColor) + maxColor = grayPixels[i]; + } + + DDSBlock4x4 ddsBlock = new DDSBlock4x4( + (short) DDSConverter.getPixel565( new Color( maxColor, maxColor, maxColor ) ), + (short) DDSConverter.getPixel565( new Color( minColor, minColor, minColor ) ), + 0); + + if(maxColor != minColor) + { + int[] ext = new int[] { maxColor, minColor, (2 * maxColor + minColor)/3, (maxColor + 2 * minColor)/3 }; + ddsBlock.bitmask = 0; + for (int i = 0; i < grayPixels.length; i++) + { + int closest = Integer.MAX_VALUE; + int mask = 0; + for (int j = 0; j < ext.length; j++) + { + int d = ( ext[j] >= grayPixels[i] ) ? (ext[j] - grayPixels[i]) : (grayPixels[i] - ext[j]); + if (d < closest) + { + closest = d; + mask = j; + } + } + ddsBlock.bitmask |= mask << i * 2; + } + } + return ddsBlock; + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/CompressionLookupRecord.java b/gov/nasa/worldwind/formats/nitfs/CompressionLookupRecord.java new file mode 100644 index 0000000..315ae4a --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/CompressionLookupRecord.java @@ -0,0 +1,89 @@ +package gov.nasa.worldwind.formats.nitfs; + +import gov.nasa.worldwind.formats.rpf.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: CompressionLookupRecord Apr 17, 2007 3:29:33 PM lado + */ +class CompressionLookupRecord +{ + public int getTableID() + { + return this.tableID; + } + + public int getNumOfRecords() + { + return this.numOfRecords; + } + + public int getNumOfValuesPerRecord() + { + return this.numOfValuesPerRecord; + } + + public int getValueBitLength() + { + return this.valueBitLength; + } + + public short getBytesPerRecord() + { + return this.bytesPerRecord; + } + + public byte[] copyValues(byte [] dest, int destOffset, int idx, int len) + { + if(len != this.bytesPerRecord) + throw new NitfsRuntimeException("NitfsReader.AttemptToCopyWithInvalidSizeOfRecord"); + if(idx >= this.numOfRecords) + throw new NitfsRuntimeException("NitfsReader.AttemptToCopyOutOfBoundsAtSource"); + if(null == dest) + throw new NitfsRuntimeException("NitfsReader.AttemptCopyToIvalidDestination"); + if(dest.length < destOffset + len) + throw new NitfsRuntimeException("NitfsReader.AttemptToCopyOutOfBoundsAtDestination"); + + System.arraycopy(lut, idx * this.bytesPerRecord, dest, destOffset, this.bytesPerRecord); + + return dest; + } + + + private int tableID; + private int numOfRecords; + private int numOfValuesPerRecord; + private int valueBitLength; + private int tableLocation; + private short bytesPerRecord; + + private byte[] lut; + + public CompressionLookupRecord(java.nio.ByteBuffer buffer, + int compressionLookupSubsectionLocation, + RpfColorMap[] colormaps // TODO update LUT with the color mapped values to gain performance + ) + + { + this.tableID = NitfsUtil.getUShort(buffer); + this.numOfRecords = (int) NitfsUtil.getUInt(buffer); + this.numOfValuesPerRecord = NitfsUtil.getUShort(buffer); + this.valueBitLength = NitfsUtil.getUShort(buffer); + this.tableLocation = (int) (NitfsUtil.getUInt(buffer) + compressionLookupSubsectionLocation); + int saveOffset = buffer.position(); + + this.bytesPerRecord = (short) (this.numOfValuesPerRecord * this.valueBitLength/8L); + this.lut = new byte[ this.numOfRecords * this.bytesPerRecord ]; + + buffer.position(this.tableLocation); + buffer.get(this.lut, 0, this.numOfRecords * this.bytesPerRecord); + + buffer.position(saveOffset); + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/DDSBlock4x4.java b/gov/nasa/worldwind/formats/nitfs/DDSBlock4x4.java new file mode 100644 index 0000000..587f27d --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/DDSBlock4x4.java @@ -0,0 +1,36 @@ +package gov.nasa.worldwind.formats.nitfs; + +import java.nio.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author lado + * @version $Id: DDSBlock4x4 Apr 21, 2007 10:33:06 AM + */ +class DDSBlock4x4 +{ + public short color0, color1; + public int bitmask; + + public DDSBlock4x4( short color0, short color1, int bitmask ) + { + this.color0 = color0; + this.color1 = color1; + this.bitmask = bitmask; + } + + public void writeTo(ByteBuffer buffer) + { + if(null != buffer) + { + buffer.putShort( this.color0 ); + buffer.putShort( this.color1 ); + buffer.putInt( this.bitmask ); + } + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsDataExtensionSegment.java b/gov/nasa/worldwind/formats/nitfs/NitfsDataExtensionSegment.java new file mode 100644 index 0000000..95e11a2 --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsDataExtensionSegment.java @@ -0,0 +1,21 @@ +package gov.nasa.worldwind.formats.nitfs; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsDataExtensionSegment Mar 31, 2007 1:01:41 AM + */ +class NitfsDataExtensionSegment extends NitfsSegment +{ + public NitfsDataExtensionSegment(java.nio.ByteBuffer buffer, + int headerStartOffset, int headerLength, + int dataStartOffset, int dataLength) + { + super(NitfsSegmentType.DataExtensionSegment, buffer, headerStartOffset, headerLength, dataStartOffset, dataLength); + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsExtendedHeaderSegment.java b/gov/nasa/worldwind/formats/nitfs/NitfsExtendedHeaderSegment.java new file mode 100644 index 0000000..e050063 --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsExtendedHeaderSegment.java @@ -0,0 +1,21 @@ +package gov.nasa.worldwind.formats.nitfs; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsExtendedHeaderSegment Mar 31, 2007 1:06:23 AM + */ +class NitfsExtendedHeaderSegment extends NitfsSegment +{ + public NitfsExtendedHeaderSegment(java.nio.ByteBuffer buffer, int headerStartOffset, int headerLength, int dataStartOffset, int dataLength) + { + super(NitfsSegmentType.ExtendedHeaderSegment, buffer, headerStartOffset, headerLength, dataStartOffset, dataLength); + + this.restoreBufferPosition(); + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsFileHeader.java b/gov/nasa/worldwind/formats/nitfs/NitfsFileHeader.java new file mode 100644 index 0000000..2d7ac3a --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsFileHeader.java @@ -0,0 +1,275 @@ +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +package gov.nasa.worldwind.formats.nitfs; + +import gov.nasa.worldwind.*; + +/** + * @author Lado Garakanidze + * @version $Id: NitfsFileHeader.java 525 2007-01-22 01:09:20Z garakl $ + */ +public class NitfsFileHeader +{ + private String headerID; + private String version; + private String specialType; + private int headerLength; + private int fileLength; + private boolean isVersion0210; + private short complexityLevel ; + private String originationStationId; + private String dateTime; + private String title ; + + private String FSCLAS ; + private String FSCLSY ; + private String FSCODE ; + private String FSCTLH ; + private String FSREL ; + private String FSDCTP ; + private String FSDCDT ; + private String FSDCXM ; + private String FSDG ; + private String FSDGDT ; + private String FSCLTX ; + private String FSCATP ; + private String FSCAUT ; + private String FSCRSN ; + private String FSSRDT ; + private String FSCTLN ; + private String FSDWNG ; + private String FSDEVT ; + private String FSCOP ; + private String FSCPYS ; + private String ENCRYP ; + private String FBKGC ; + private String ONAME ; + private String OPHONE ; + + public NitfsFileHeader(java.nio.ByteBuffer buffer) + { + parseFileHeaderInfo(buffer); + } + + private void parseFileHeaderInfo(java.nio.ByteBuffer buffer) + { + this.headerID = NitfsUtil.getString(buffer, 0, 4); + this.version = NitfsUtil.getString(buffer, 5); + this.isVersion0210 = StringUtil.Equals("02.10", version); + this.complexityLevel = NitfsUtil.getShortNumeric(buffer, 2); + this.specialType = NitfsUtil.getString(buffer, 4); // offset 11, size 4 + this.originationStationId = NitfsUtil.getString(buffer, 10); // offset 15, size 10 + this.dateTime = NitfsUtil.getString(buffer, 14); // offset 25, size 14 + this.title = NitfsUtil.getString(buffer, 80); // offset 39, size 80 + + this.FSCLAS = NitfsUtil.getString(buffer, 1); // offset 119, size 1 + this.FSCLSY = (isVersion0210 ? NitfsUtil.getString(buffer, 2) : StringUtil.EMPTY); // offset 120, size 2 + this.FSCODE = NitfsUtil.getString(buffer, isVersion0210 ? 11 : 40); + this.FSCTLH = NitfsUtil.getString(buffer, isVersion0210 ? 2 : 40); + this.FSREL = NitfsUtil.getString(buffer, isVersion0210 ? 20 : 40); + + this.FSDCTP = (isVersion0210 ? NitfsUtil.getString(buffer, 2) : StringUtil.EMPTY); + this.FSDCDT = (isVersion0210 ? NitfsUtil.getString(buffer, 8) : StringUtil.EMPTY); // offset 157/ + this.FSDCXM = (isVersion0210 ? NitfsUtil.getString(buffer, 4) : StringUtil.EMPTY); // offset 165/ + this.FSDG = (isVersion0210 ? NitfsUtil.getString(buffer, 1) : StringUtil.EMPTY); // offset 169/ + this.FSDGDT = (isVersion0210 ? NitfsUtil.getString(buffer, 8) : StringUtil.EMPTY); // oofset 170/ + this.FSCLTX = (isVersion0210 ? NitfsUtil.getString(buffer, 43) : StringUtil.EMPTY); // offset 178/ + this.FSCATP = (isVersion0210 ? NitfsUtil.getString(buffer, 1) : StringUtil.EMPTY); // offset 221/ + + this.FSCAUT = NitfsUtil.getString(buffer, isVersion0210 ? 40 : 20); // offset 222/240 + + this.FSCRSN = (isVersion0210 ? NitfsUtil.getString(buffer, 1) : StringUtil.EMPTY); // offset 262/ + this.FSSRDT = (isVersion0210 ? NitfsUtil.getString(buffer, 8) : StringUtil.EMPTY); // offset 263/ + this.FSCTLN = NitfsUtil.getString(buffer, isVersion0210 ? 15 : 20); // offset 271/260 + this.FSDWNG = (isVersion0210) ? StringUtil.EMPTY : NitfsUtil.getString(buffer, 6); // offset /280 + + this.FSDEVT = (!isVersion0210 && StringUtil.Equals("999998", FSDWNG)) // offset /286 + ? NitfsUtil.getString(buffer, 40) : StringUtil.EMPTY; + + this.FSCOP = NitfsUtil.getString(buffer, 5); // offset 286/+40 + this.FSCPYS = NitfsUtil.getString(buffer, 5); // offset 291/+40 + this.ENCRYP = NitfsUtil.getString(buffer, 1); // offset 296/+40 + + this.FBKGC = (isVersion0210 ? NitfsUtil.getString(buffer, 297, 3) : StringUtil.EMPTY); // offset 297/ + this.ONAME = NitfsUtil.getString(buffer, isVersion0210 ? 24 : 27); // offset 300/297(+40) + this.OPHONE = NitfsUtil.getString(buffer, 18); // offset 324(+40) + + this.fileLength = NitfsUtil.getNumeric(buffer, 12); // offset 342(+40) + this.headerLength = NitfsUtil.getNumeric(buffer, 6); // offset 352(+40) + } + + public String getHeaderID() + { + return this.headerID; + } + + public String getVersion() + { + return this.version; + } + + public boolean isVersion0210() + { + return this.isVersion0210; + } + + public short getComplexityLevel() + { + return this.complexityLevel; + } + + public String getSpecialType() + { + return this.specialType; + } + + public String getOriginationStationId() + { + return this.originationStationId; + } + + public String getDateTime() + { + return this.dateTime; + } + + public String getTitle() + { + return this.title; + } + + public int getHeaderLength() + { + return this.headerLength; + } + + public String getFSCLAS() + { + return this.FSCLAS; + } + + public String getFSCLSY() + { + return this.FSCLSY; + } + + public String getFSCODE() + { + return this.FSCODE; + } + + public String getFSCTLH() + { + return this.FSCTLH; + } + + public String getFSREL() + { + return this.FSREL; + } + + public String getFSDCTP() + { + return this.FSDCTP; + } + + public String getFSDCDT() + { + return this.FSDCDT; + } + + public String getFSDCXM() + { + return this.FSDCXM; + } + + public String getFSDG() + { + return this.FSDG; + } + + public String getFSDGDT() + { + return this.FSDGDT; + } + + public String getFSCLTX() + { + return this.FSCLTX; + } + + public String getFSCATP() + { + return this.FSCATP; + } + + public String getFSCAUT() + { + return this.FSCAUT; + } + + public String getFSCRSN() + { + return this.FSCRSN; + } + + public String getFSSRDT() + { + return this.FSSRDT; + } + + public String getFSCTLN() + { + return this.FSCTLN; + } + + public String getFSDWNG() + { + return this.FSDWNG; + } + + public String getFSDEVT() + { + return this.FSDEVT; + } + + public String getFSCOP() + { + return this.FSCOP; + } + + public String getFSCPYS() + { + return this.FSCPYS; + } + + public String getENCRYP() + { + return this.ENCRYP; + } + + public String getFBKGC() + { + return this.FBKGC; + } + + public String getONAME() + { + return this.ONAME; + } + + public String getOPHONE() + { + return this.OPHONE; + } + + public int getFileLength() + { + return this.fileLength; + } +} \ No newline at end of file diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsImageBand.java b/gov/nasa/worldwind/formats/nitfs/NitfsImageBand.java new file mode 100644 index 0000000..a4f6583 --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsImageBand.java @@ -0,0 +1,126 @@ +package gov.nasa.worldwind.formats.nitfs; + +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsImageBand Apr 17, 2007 3:22:33 PM lado + */ +class NitfsImageBand +{ + private String representation; + private String significanceForImageCategory; + private String imageFilterCondition; + private String stdImageFilterCode; + private short numOfLookupTables; + private short numOfLookupTableEntries; + // public int[] lookupTablesOffset; // one byte per entry per band + private byte[][] lut; + + private boolean isGrayImage; + private boolean hasTransparentEntry; + + public boolean isGrayImage() + { + return this.isGrayImage; + } + + public boolean isHasTransparentEntry() + { + return this.hasTransparentEntry; + } + + public String getRepresentation() + { + return this.representation; + } + + public short getNumOfLookupTables() + { + return this.numOfLookupTables; + } + + public short getNumOfLookupTableEntries() + { + return this.numOfLookupTableEntries; + } + + public NitfsImageBand(java.nio.ByteBuffer buffer) + { + this.representation = NitfsUtil.getString(buffer, 2); + this.significanceForImageCategory = NitfsUtil.getString(buffer, 6); + this.imageFilterCondition = NitfsUtil.getString(buffer, 1); + this.stdImageFilterCode = NitfsUtil.getString(buffer, 3); + this.numOfLookupTables = NitfsUtil.getShortNumeric(buffer, 1); + this.numOfLookupTableEntries = NitfsUtil.getShortNumeric(buffer, 5); + if (0 < this.numOfLookupTables && 0 < this.numOfLookupTableEntries) + { + this.lut = new byte[this.numOfLookupTables][this.numOfLookupTableEntries]; + for (int j = 0; j < this.numOfLookupTables; j++) + { + buffer.get(this.lut[j], 0, this.numOfLookupTableEntries); + } + } + + this.isGrayImage = (1 == this.numOfLookupTables); + this.hasTransparentEntry = (217 == this.numOfLookupTableEntries); + } + + public final int lookupR5G6B5(int colorIndex) + { + int r, g, b; + if (3 == this.numOfLookupTables) + { + r = (0x00FF & this.lut[0][colorIndex]) >> 3; + g = (0x00FF & this.lut[1][colorIndex]) >> 2; + b = (0x00FF & this.lut[2][colorIndex]) >> 3; + } + else + { + int gray = 0x00FF & this.lut[0][ colorIndex ]; + r = gray >> 3; + g = gray >> 2; + b = gray >> 3; + } + return 0x00FFFF & ((r << 11) | (g << 5) | b ); + } + + + public final int lookupRGB(int colorIndex) + { + int r, g, b; + if (3 == this.numOfLookupTables) + { + r = (0x00FF & this.lut[0][colorIndex]); + g = (0x00FF & this.lut[1][colorIndex]); + b = (0x00FF & this.lut[2][colorIndex]); + } + else + { + r = g = b = 0x00FF & this.lut[0][ colorIndex ]; + } + return (int) (0x00FFFFFFL & (long)((r << 16) | (g << 8) | b )); + } + + public final int lookupGray(int colorIndex) + { + + if (3 == this.numOfLookupTables) + { + int r = (0x00FF & this.lut[0][colorIndex]); + int g = (0x00FF & this.lut[1][colorIndex]); + int b = (0x00FF & this.lut[2][colorIndex]); + + return (30 * r + 59 * g + 11 * b)/100; + } + else + { + return (0x00FF & this.lut[0][colorIndex]); + } + } +} \ No newline at end of file diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsImageSegment.java b/gov/nasa/worldwind/formats/nitfs/NitfsImageSegment.java new file mode 100644 index 0000000..66cff1d --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsImageSegment.java @@ -0,0 +1,654 @@ +package gov.nasa.worldwind.formats.nitfs; + +import gov.nasa.worldwind.*; +import gov.nasa.worldwind.formats.rpf.*; +import gov.nasa.worldwind.geom.*; + +import java.nio.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsImageSegment Mar 30, 2007 12:21:34 PM lado + */ +public class NitfsImageSegment extends NitfsSegment +{ + public static final String[] SupportedFormats = { "CIB", "CADRG", "ADRG" }; + // [ nitf identification , security, structure fields] + public String partType; + public String imageID; + public String dateTime; + public String targetID; + public String imageTitle; + public String securityClass; + public String codewords; + public String controlAndHandling; + public String releaseInstructions; + public String classAuthority; + public String securityCtrlNum; + public String ISDWNG; // image security downgrade + public String ISDEVT; // downgrading event + public short encryption; + public String imageSource; + public int numSignificantRows; + public int numSignificantCols; + public String pixelValueType; + public String imageRepresentation; + public String imageCategory; + public short bitsPerPixelPerBand; + public String pixelJustification; + public String imageCoordSystem; + // [ nitf image geographic location ] + public LatLon[] imageCoords; + // [ nitf comments ] + public String[] imageCommentRecords; + // [ nitf image compression structure ] + public String imageCompression; + public String compressionRateCode; + public short NBANDS; // number of bands { 1 for MONO and RGB/LUT, 3 for RGB; + // [ nitfs image bands ] + public NitfsImageBand[] imageBands; + // [ nitf image table structure fields ] + public short imageSyncCode; // ISYNC { 0 - No sync code, 1 - sync code } + public String imageMode; // IMODE { B, P, R, S } + public short numOfBlocksPerRow; // NBPR { 0001~9999 } + public short numOfBlocksPerCol; // NBPC { 0001~9999 } + public short numOfPixelsPerBlockH; // NPPBH { 0001~8192 } + public short numOfPixelsPerBlockV; // NPPBV { 0001~8192 } + public short numOfBitsPerPixelPerBand; // NBPP { 01~96 } + public short displayLevel; // IDLVL { 001~999 } + public short attachmentLevel; // IALVL { 001~998 } + // [ nitfs image location ] + public short imageRowOffset; // ILOC { -0001 ~ +9999 } + public short imageColOffset; // + + // [ nitf image magnification ] + public String imageMagnification; // IMAG + public short userDefinedSubheaderLength; + + // [ nitf user-defined image subheader ] + private UserDefinedImageSubheader userDefSubheader; + + // [ nitf-rpf image display parameter sub-header ] + private long numOfImageRows; + private long numOfImageCodesPerRow; + private short imageCodeBitLength; + + // [ nitf rpf compression section ] + // [ nitf-rpf compression section sub-header ] + private int compressionAlgorithmID; + private int numOfCompressionLookupOffsetRecords; + private int numOfCompressionParameterOffsetRecords; + + // [ nitf rpf compression lookup sub-section ] + private long compressionLookupOffsetTableOffset; + private int compressionLookupTableOffsetRecordLength; + + + // [ nitf-rpf mask subsection ] + private int subframeSequenceRecordLength; + private int transparencySequenceRecordLength; + private int transparentOutputPixelCodeLength; + private byte[] transparentOutputPixelCode; + private int[] subFrameOffsets = null; + + private boolean hasTransparentPixels = false; + private boolean hasMaskedSubframes = false; + + public static String[] getSupportedFormats() + { + return SupportedFormats; + } + + public boolean hasTransparentPixels() + { + return this.hasTransparentPixels; + } + + public boolean hasMaskedSubframes() + { + return this.hasMaskedSubframes; + } + + private CompressionLookupRecord[] compressionLUTS; + + public UserDefinedImageSubheader getUserDefinedImageSubheader() + { + return userDefSubheader; + } + + public RpfFrameFileComponents getRpfFrameFileComponents() + { + return (null != userDefSubheader) ? userDefSubheader.getRpfFrameFileComponents() : null; + } + + public NitfsImageSegment(java.nio.ByteBuffer buffer, int headerStartOffset, int headerLength,int dataStartOffset, int dataLength) + { + super(NitfsSegmentType.ImageSegment, buffer, headerStartOffset, headerLength, dataStartOffset, dataLength); + + int saveOffset = buffer.position(); + + buffer.position( headerStartOffset ); + // do not change order of parsing + this.parseIdentificationSecurityStructureFields(buffer); + this.parseImageGeographicLocation(buffer); + this.parseCommentRecords(buffer); + this.parseImageCompressionStructure(buffer); + this.parseImageBands(buffer); + this.parseImageTableStructure(buffer); + this.parseImageLocation(buffer); + this.parseImageSubheaders(buffer); + this.parseImageData(buffer); + this.validateImage(); + + buffer.position(saveOffset); // last line - restore buffer's position + } + + private void decompressBlock4x4(byte[][] block4x4, short code) + { + this.compressionLUTS[0].copyValues(block4x4[0], 0, code, 4); + this.compressionLUTS[1].copyValues(block4x4[1], 0, code, 4); + this.compressionLUTS[2].copyValues(block4x4[2], 0, code, 4); + this.compressionLUTS[3].copyValues(block4x4[3], 0, code, 4); + } + + private void decompressBlock16(byte[] block16, short code) + { + this.compressionLUTS[0].copyValues(block16, 0, code, 4); + this.compressionLUTS[1].copyValues(block16, 4, code, 4); + this.compressionLUTS[2].copyValues(block16, 8, code, 4); + this.compressionLUTS[3].copyValues(block16, 12, code, 4); + } + + + public ByteBuffer getImageAsDdsTexture() throws NitfsRuntimeException + { + RpfFrameFileComponents rpfComponents = this.getRpfFrameFileComponents(); + RpfLocationSection componentLocationTable = rpfComponents.componentLocationTable; + + int spatialDataSubsectionLocation = componentLocationTable.getSpatialDataSubsectionLocation(); + super.buffer.position( spatialDataSubsectionLocation ); + + short aa, ab, bb; + short[] codes = new short[(int) this.numOfImageCodesPerRow]; + byte[] block16 = new byte[16]; + int rowSize = (int) ((this.numOfImageCodesPerRow * this.imageCodeBitLength) / 8L); + byte[] rowBytes = new byte[rowSize]; + int subFrameOffset; + short subFrameIdx = 0; + + int band = 0; // for(int band = 0; band < rpfComponents.numOfSpectralBandTables; band++) + NitfsImageBand imageBand = this.imageBands[band]; + + Rpf2DdsCompress ddsCompress = (1 == imageBand.getNumOfLookupTables()) + ? new Cib2DdsCompress() : new Cadrg2DdsCompress(); + + boolean imageHasTransparentAreas = ( this.hasTransparentPixels || this.hasMaskedSubframes ); + if( imageHasTransparentAreas ) + { + + } + + // Allocate space for the DDS texture + int bufferSize = 128 + this.numSignificantCols * this.numSignificantRows / 2; + java.nio.ByteBuffer ddsBuffer = java.nio.ByteBuffer.allocateDirect(bufferSize); + ddsBuffer.order(java.nio.ByteOrder.LITTLE_ENDIAN); + ddsCompress.writeDxt1Header(ddsBuffer, this.numSignificantCols, this.numSignificantRows); + int ddsHeaderLength = ddsBuffer.position(); + + + for (int subFrameH = 0; subFrameH < this.numOfBlocksPerCol; subFrameH++) + { + for (int subFrameW = 0; subFrameW < this.numOfBlocksPerRow; subFrameW++, subFrameIdx++ ) + { + int blockY = (int) (subFrameH * rpfComponents.numOfOutputRowsPerSubframe); + int blockX = (int) (subFrameW * rpfComponents.numOfOutputColumnsPerSubframe); + + if(hasMaskedSubframes) + { + subFrameOffset = this.subFrameOffsets[subFrameIdx]; + if( -1 == subFrameOffset) + { // this is a masked / transparent(?) subframe + DDSBlock4x4 ddsBlock = ddsCompress.getDxt1TransparentBlock4x4(); + + for (int row = 0; row < this.numOfImageRows; row++) + { + int qy = blockY + row * 4; + for (int col = 0; col < this.numOfImageCodesPerRow; col++) + { + int qx = blockX + col * 4; + ddsBuffer.position(ddsHeaderLength + (qy * this.numSignificantCols) / 2 + 2 * qx); + ddsBlock.writeTo( ddsBuffer ); + } // end of column loop + } + continue; + } + else + { + super.buffer.position( spatialDataSubsectionLocation + subFrameOffset ); + } + } + + for (int row = 0; row < this.numOfImageRows; row++) + { + int qy = blockY + row * 4; + super.buffer.get(rowBytes, 0, rowSize); + + for (int i = 0, cidx = 0, bidx = 0; i < (int) this.numOfImageCodesPerRow / 2; i++) + { + aa = (short) ((0x00FF & (short) rowBytes[bidx++]) << 4); + ab = (short) (0x00FF & (short) rowBytes[bidx++]); + bb = (short) (0x00FF & (short) rowBytes[bidx++]); + + codes[cidx++] = (short) (aa | ((0x00F0 & ab) >> 4)); + codes[cidx++] = (short) (bb | ((0x000F & ab) << 8)); + } + + for (int col = 0; col < this.numOfImageCodesPerRow; col++) + { + int qx = blockX + col * 4; + this.decompressBlock16( block16, codes[col] ); + + DDSBlock4x4 ddsBlock = ddsCompress.compressDxt1Block4x4( imageBand, block16, false ); + + ddsBuffer.position( ddsHeaderLength + (qy * this.numSignificantCols)/2 + 2 * qx ); + ddsBlock.writeTo( ddsBuffer ); + } // end of column loop + } // end of row loop + } // end of subFrameW loop + } // end of subFrameH loop + + return ddsBuffer; + } + + public IntBuffer getImagePixelsAsArray(IntBuffer pixels, RpfImageType imageType) throws NitfsRuntimeException + { + RpfFrameFileComponents rpfComponents = this.getRpfFrameFileComponents(); + RpfLocationSection componentLocationTable = rpfComponents.componentLocationTable; + + int spatialDataSubsectionLocation = componentLocationTable.getSpatialDataSubsectionLocation(); + super.buffer.position( spatialDataSubsectionLocation ); + + int band = 0; // for(int band = 0; band < rpfComponents.numOfSpectralBandTables; band++) + NitfsImageBand imageBand = this.imageBands[band]; + + int rgbColor, colorCode; + short aa, ab, bb; + short[] codes = new short[(int) this.numOfImageCodesPerRow]; + byte[][] block4x4 = new byte[4][4]; + int rowSize = (short) ((this.numOfImageCodesPerRow * this.imageCodeBitLength) / 8L); + byte[] rowBytes = new byte[rowSize]; + int subFrameOffset; + short subFrameIdx = 0; + + for (int subFrameH = 0; subFrameH < this.numOfBlocksPerCol; subFrameH++) + { + for (int subFrameW = 0; subFrameW < this.numOfBlocksPerRow; subFrameW++, subFrameIdx++ ) + { + int blockY = (int) (subFrameH * rpfComponents.numOfOutputRowsPerSubframe); + int blockX = (int) (subFrameW * rpfComponents.numOfOutputColumnsPerSubframe); + + if(hasMaskedSubframes) + { + subFrameOffset = this.subFrameOffsets[subFrameIdx]; + if( -1 == subFrameOffset) + { // this is a masked / transparent(?) subframe + continue; + } + else + { + super.buffer.position( spatialDataSubsectionLocation + subFrameOffset ); + } + } + + for (int row = 0; row < this.numOfImageRows; row++) + { + int qy = blockY + row * 4; + + super.buffer.get(rowBytes, 0, rowSize); + + // short[] codes = new short[(int) this.numOfImageCodesPerRow]; + for (int i = 0, cidx = 0, bidx = 0; i < (int) this.numOfImageCodesPerRow / 2; i++) + { + aa = (short) ((0x00FF & (short) rowBytes[bidx++]) << 4); + ab = (short) (0x00FF & (short) rowBytes[bidx++]); + bb = (short) (0x00FF & (short) rowBytes[bidx++]); + + codes[cidx++] = (short) (aa | ((0x00F0 & ab) >> 4)); + codes[cidx++] = (short) (bb | ((0x000F & ab) << 8)); + } + + for (int col = 0; col < this.numOfImageCodesPerRow; col++) + { + this.decompressBlock4x4( block4x4, codes[col] ); + + int qx = blockX + col * 4; + + for (int h = 0; h < 4; h++) + { + for (int w = 0; w < 4; w++) + { + colorCode = 0x00FF & block4x4[h][w]; + rgbColor = imageBand.lookupRGB(colorCode); + switch (imageType) + { + case IMAGE_TYPE_ALPHA_RGB: + rgbColor = 0xFF000000 + rgbColor; + break; +// case IMAGE_TYPE_GRAY: +// break; +// case IMAGE_TYPE_RGB: +// break; + case IMAGE_TYPE_GRAY_ALPHA: + rgbColor = (rgbColor << 8) + 0xFF; + break; + case IMAGE_TYPE_RGB_ALPHA: + rgbColor = (rgbColor << 8) + 0xFF; + break; + } +// pixels[(qy + h) * this.numSignificantCols + (qx + w)] = rgbColor; + pixels.put((qy + h) * this.numSignificantCols + (qx + w), rgbColor); + } + } + } // end of column loop + } // end of row loop + } // end of subFrameW loop + } // end of subFrameH loop + + return pixels; + } + + private void validateImage() throws NitfsRuntimeException + { + RpfFrameFileComponents rpfComponents = this.getRpfFrameFileComponents(); + + if(1 != this.compressionAlgorithmID ) + throw new NitfsRuntimeException("NitfsReader.UnsupportedCompressionAlgorithm"); + if( !StringUtil.Equals( this.imageMode, "B")) + throw new NitfsRuntimeException("NitfsReader.UnsupportedImageMode"); + if( 1 != rpfComponents.numOfSpectralGroups ) + throw new NitfsRuntimeException("NitfsReader.UnsupportedNumberOfSpectralGroups."); + if( 12 != this.imageCodeBitLength ) + throw new NitfsRuntimeException("NitfsReader.UnsupportedImageCodeBitLength."); + + + + + } + + private void parseRpfMaskSubsection(java.nio.ByteBuffer buffer) throws NitfsRuntimeException + { + // parse [ nitf-rpf mask subsection ] + this.subframeSequenceRecordLength = NitfsUtil.getUShort(buffer); + this.transparencySequenceRecordLength = NitfsUtil.getUShort(buffer); + this.transparentOutputPixelCodeLength = NitfsUtil.getUShort(buffer); + + if( 0 != this.transparentOutputPixelCodeLength ) + { + this.transparentOutputPixelCode = new byte[(int)this.transparentOutputPixelCodeLength]; + buffer.get(this.transparentOutputPixelCode, 0, (int)this.transparentOutputPixelCodeLength); + } + + if(0 < this.subframeSequenceRecordLength) + { + RpfFrameFileComponents rpfComponents = this.getRpfFrameFileComponents(); + subFrameOffsets = new int[ this.numOfBlocksPerCol * this.numOfBlocksPerRow ]; + // parse [ nitf-rpf subframe mask table ] + int idx = 0; + for(int group = 0 ; group < rpfComponents.numOfSpectralGroups; group++ ) + { + for(int row = 0 ; row < this.numOfBlocksPerCol; row++ ) + { + for(int col = 0 ; col < this.numOfBlocksPerRow; col++ ) + subFrameOffsets[idx++] = (int) NitfsUtil.getUInt(buffer); + } + } + + hasMaskedSubframes = (null != this.subFrameOffsets && 0 < this.subFrameOffsets.length); + } + else + { + this.subFrameOffsets = null; + } + this.hasMaskedSubframes = (null != this.subFrameOffsets && 0 < this.subFrameOffsets.length); + this.hasTransparentPixels = (0 < this.transparencySequenceRecordLength); + } + + + private void parseImageData(java.nio.ByteBuffer buffer) throws NitfsRuntimeException + { + RpfLocationSection componentLocationTable = this.getRpfFrameFileComponents().componentLocationTable; + + buffer.position(this.dataStartOffset); + long spatialDataOffset = NitfsUtil.getUInt(buffer); + + if(0 < componentLocationTable.getMaskSubsectionLength()) + { + // parse nitf-rpf mask subsection + buffer.position( componentLocationTable.getMaskSubsectionLocation() ); + this.parseRpfMaskSubsection(buffer); + } + + if(0 < componentLocationTable.getImageDisplayParametersSubheaderLength()) + { // parse [ nitf-rpf image display parameter sub-header ] + buffer.position( componentLocationTable.getImageDisplayParametersSubheaderLocation() ); + this.parseImageDisplayParametersSubheader(buffer); + } + else + throw new NitfsRuntimeException("NitfsReader.ImageDisplayParametersSubheaderNotFound"); + + // [ nitf rpf compression section ] + if(0 < componentLocationTable.getCompressionSectionSubheaderLength()) + { // parse [ nitf-rpf compression section sub-header ] + buffer.position( componentLocationTable.getCompressionSectionSubheaderLocation() ); + this.parseRpfCompressionSectionSubheader(buffer); + } + else + throw new NitfsRuntimeException("NitfsReader.RpfCompressionSectionSubheaderNotFound"); + + // [ nitf rpf compression lookup sub-section ] + if(0 < componentLocationTable.getCompressionLookupSubsectionLength()) + { + buffer.position( componentLocationTable.getCompressionLookupSubsectionLocation() ); + this.parseRpfCompressionLookupSubsection(buffer); + } + else + throw new NitfsRuntimeException("NitfsReader.RpfCompressionLookupSubsectionNotFound"); + + // [ nitf rpf compression parameter subsection ] + if(0 < componentLocationTable.getCompressionParameterSubsectionLength()) + throw new NitfsRuntimeException("NitfsReader.RpfCompressionParameterSubsectionNotImplemented"); + + // [ nitf rpf spatial data subsection ] + if(0 < componentLocationTable.getSpatialDataSubsectionLength()) + { + + buffer.position( componentLocationTable.getSpatialDataSubsectionLocation() ); + this.parseRpfSpatialDataSubsection(buffer); + } + else + throw new NitfsRuntimeException("NitfsReader.RpfSpatialDataSubsectionNotFound"); + } + + private void parseRpfSpatialDataSubsection(java.nio.ByteBuffer buffer) throws NitfsRuntimeException + { + + + } + + private void parseRpfCompressionLookupSubsection(java.nio.ByteBuffer buffer) + throws NitfsRuntimeException + { + int compressionLookupSubsectionLocation = buffer.position(); + // [ nitf rpf compression lookup sub-section ] + this.compressionLookupOffsetTableOffset = NitfsUtil.getUInt(buffer); + this.compressionLookupTableOffsetRecordLength = NitfsUtil.getUShort(buffer); + + this.compressionLUTS = new CompressionLookupRecord[this.numOfCompressionLookupOffsetRecords]; + for(int i = 0 ; i < this.numOfCompressionLookupOffsetRecords; i++) + { + this.compressionLUTS[i] = new CompressionLookupRecord( buffer, + compressionLookupSubsectionLocation, + this.getRpfFrameFileComponents().rpfColorMaps); + } + } + + private void parseRpfCompressionSectionSubheader(java.nio.ByteBuffer buffer) throws NitfsRuntimeException + { + // parse [ nitf-rpf compression section sub-header ] + this.compressionAlgorithmID = NitfsUtil.getUShort(buffer); + this.numOfCompressionLookupOffsetRecords = NitfsUtil.getUShort(buffer); + this.numOfCompressionParameterOffsetRecords = NitfsUtil.getUShort(buffer); + } + + private void parseImageDisplayParametersSubheader(java.nio.ByteBuffer buffer) throws NitfsRuntimeException + { + // parse [ nitf-rpf image display parameter sub-header ] + this.numOfImageRows = NitfsUtil.getUInt(buffer); + this.numOfImageCodesPerRow = NitfsUtil.getUInt(buffer); + this.imageCodeBitLength = NitfsUtil.getByteAsShort(buffer); + } + + private void parseImageSubheaders(java.nio.ByteBuffer buffer) throws NitfsRuntimeException + { + this.userDefinedSubheaderLength = NitfsUtil.getShortNumeric(buffer, 5); + if (0 == this.userDefinedSubheaderLength) + { + this.userDefSubheader = null; + return; + } + + this.userDefSubheader = new UserDefinedImageSubheader(buffer); + } + private void parseImageLocation(java.nio.ByteBuffer buffer) throws NitfsRuntimeException + { + this.imageRowOffset = NitfsUtil.getShortNumeric(buffer, 5); + this.imageColOffset = NitfsUtil.getShortNumeric(buffer, 5); + // [ nitf image magnification ] + this.imageMagnification = NitfsUtil.getString(buffer, 4); + } + + private void parseImageTableStructure(java.nio.ByteBuffer buffer) throws NitfsRuntimeException + { + this.imageSyncCode = NitfsUtil.getShortNumeric(buffer, 1); + this.imageMode = NitfsUtil.getString(buffer, 1); + this.numOfBlocksPerRow = NitfsUtil.getShortNumeric(buffer, 4); + this.numOfBlocksPerCol = NitfsUtil.getShortNumeric(buffer, 4); + this.numOfPixelsPerBlockH = NitfsUtil.getShortNumeric(buffer, 4); + this.numOfPixelsPerBlockV = NitfsUtil.getShortNumeric(buffer, 4); + this.numOfBitsPerPixelPerBand = NitfsUtil.getShortNumeric(buffer, 2); + this.displayLevel = NitfsUtil.getShortNumeric(buffer, 3); + this.attachmentLevel = NitfsUtil.getShortNumeric(buffer, 3); + } + + private void parseImageBands(java.nio.ByteBuffer buffer) throws NitfsRuntimeException + { + if(0 == this.NBANDS) + throw new NitfsRuntimeException("NitfsReader.InvalidNumberOfImageBands"); + this.imageBands = new NitfsImageBand[this.NBANDS]; + for(int i = 0 ; i < this.NBANDS; i++) + this.imageBands[i] = new NitfsImageBand(buffer); + } + private void parseImageCompressionStructure(java.nio.ByteBuffer buffer) + { + this.imageCompression = NitfsUtil.getString(buffer, 2); + this.compressionRateCode = NitfsUtil.getString(buffer, 4); + this.NBANDS = NitfsUtil.getShortNumeric(buffer, 1); + } + + private void parseCommentRecords(java.nio.ByteBuffer buffer) + { + int numCommentRecords = NitfsUtil.getShortNumeric(buffer, 1); + if(0 < numCommentRecords) + { + this.imageCommentRecords = new String[numCommentRecords]; + for(int i = 0; i < numCommentRecords; i++) + this.imageCommentRecords[i] = NitfsUtil.getString(buffer, 80); + } + else + this.imageCommentRecords = null; + } + + private void parseImageGeographicLocation(java.nio.ByteBuffer buffer) + { + String hemisphere; + double deg, min, sec, lat, lon; + double sixty = (double) 60.0; + this.imageCoords = new LatLon[4]; + for (int i = 0; i < 4; i++) + { + deg = (double)NitfsUtil.getShortNumeric(buffer, 2); + min = (double)NitfsUtil.getShortNumeric(buffer, 2); + sec = (double)NitfsUtil.getShortNumeric(buffer, 2); + hemisphere = NitfsUtil.getString(buffer, 1); + lat = deg + (min + (sec / sixty)) / sixty; // deciaml latitude + if(StringUtil.Equals(hemisphere, "N")) + lat *= (double)-1.0; + + deg = (double)NitfsUtil.getShortNumeric(buffer, 3); + min = (double)NitfsUtil.getShortNumeric(buffer, 2); + sec = (double)NitfsUtil.getShortNumeric(buffer, 2); + hemisphere = NitfsUtil.getString(buffer, 1); + lon = deg + (min + (sec / sixty)) / sixty; // deciaml longitude + if(StringUtil.Equals(hemisphere, "W")) + lon *= (double)-1.0; + + // TODO Do not waste time on this calculations - the same info is repeated in the [ rpf coverage section ] + // TODO zz: garakl: convert to LatLon according to the CoordinateSystem + // if(0 == StringUtil.compare(imageCoordSystem, "G")) + this.imageCoords[i] = LatLon.fromDegrees(lat, lon); + } + } + + private void parseIdentificationSecurityStructureFields(java.nio.ByteBuffer buffer) + throws NitfsRuntimeException + { + // [ nitf identification , security, structure fields] + this.partType = NitfsUtil.getString(buffer, 2); + if(!StringUtil.Equals("IM", this.partType)) + throw new NitfsRuntimeException("NitfsReader.UnexpectedSegmentType", this.partType); + + this.imageID = NitfsUtil.getString(buffer, 10); + boolean isSupportedFormat = false; + for(String s : SupportedFormats) + { + if(0 == s.compareTo(this.imageID)) + { + isSupportedFormat = true; + break; + } + } + if(!isSupportedFormat) + throw new NitfsRuntimeException("NitfsReader.UnsupportedImageFormat", this.imageID); + + this.dateTime = NitfsUtil.getString(buffer, 14); + this.targetID = NitfsUtil.getString(buffer, 17); + this.imageTitle = NitfsUtil.getString(buffer, 80); + this.securityClass = NitfsUtil.getString(buffer, 1); + this.codewords = NitfsUtil.getString(buffer, 40); + this.controlAndHandling = NitfsUtil.getString(buffer, 40); + this.releaseInstructions = NitfsUtil.getString(buffer, 40); + this.classAuthority = NitfsUtil.getString(buffer, 20); // ISCAUT + this.securityCtrlNum = NitfsUtil.getString(buffer, 20); // ISCTLN + this.ISDWNG = NitfsUtil.getString(buffer, 6); + this.ISDEVT = StringUtil.Equals(this.ISDWNG, "999998") ? NitfsUtil.getString(buffer, 40) : StringUtil.EMPTY; + + this.encryption = NitfsUtil.getShortNumeric(buffer, 1); + this.imageSource = NitfsUtil.getString(buffer, 42); + this.numSignificantRows = NitfsUtil.getNumeric(buffer, 8); + this.numSignificantCols = NitfsUtil.getNumeric(buffer, 8); + this.pixelValueType = NitfsUtil.getString(buffer, 3); + this.imageRepresentation = NitfsUtil.getString(buffer, 8); + this.imageCategory = NitfsUtil.getString(buffer, 8); + this.bitsPerPixelPerBand = NitfsUtil.getShortNumeric(buffer, 2); + this.pixelJustification = NitfsUtil.getString(buffer, 1); + this.imageCoordSystem = NitfsUtil.getString(buffer, 1); + } + + +} diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsLabelSegment.java b/gov/nasa/worldwind/formats/nitfs/NitfsLabelSegment.java new file mode 100644 index 0000000..8af01ab --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsLabelSegment.java @@ -0,0 +1,21 @@ +package gov.nasa.worldwind.formats.nitfs; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsLabelSegment Mar 31, 2007 12:57:41 AM + */ +class NitfsLabelSegment extends NitfsSegment +{ + public NitfsLabelSegment(java.nio.ByteBuffer buffer, int headerStartOffset, int headerLength, int dataStartOffset, int dataLength) + { + super(NitfsSegmentType.LabelSegment, buffer, headerStartOffset, headerLength, dataStartOffset, dataLength); + + this.restoreBufferPosition(); + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsMessage.java b/gov/nasa/worldwind/formats/nitfs/NitfsMessage.java new file mode 100644 index 0000000..30be39c --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsMessage.java @@ -0,0 +1,208 @@ +package gov.nasa.worldwind.formats.nitfs; + +import gov.nasa.worldwind.*; +import gov.nasa.worldwind.formats.rpf.*; + +import java.io.*; +import java.text.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsMessage Apr 4, 2007 4:11:55 PM lado + */ +public class NitfsMessage +{ + private java.nio.ByteBuffer buffer; + private NitfsFileHeader fileHeader; + private java.util.ArrayList segments = new java.util.ArrayList(); + + + public NitfsSegment getSegment( NitfsSegmentType segmentType ) + { + for(NitfsSegment seg : segments) + { + if(null != seg && seg.segmentType.equals(segmentType)) + return seg; + } + return null; + } + + public NitfsFileHeader getNitfsFileHeader() + { + return this.fileHeader; + } + + private NitfsMessage(java.nio.ByteBuffer buffer) + { + this.buffer = buffer; + this.fileHeader = new NitfsFileHeader(buffer); + + // read ALL description groups and segments + this.readSegments(); + } + + private void readSegments() + { + int saveOffset = this.buffer.position(); + int nextSegmentOffset = this.fileHeader.getHeaderLength(); + + // parse Image Description Group + nextSegmentOffset = parseSegment(NitfsSegmentType.ImageSegment, nextSegmentOffset); + // parse Graphic/Symbol Description Group + nextSegmentOffset = parseSegment(NitfsSegmentType.SymbolSegment, nextSegmentOffset); + // parse Label Description Group + nextSegmentOffset = parseSegment(NitfsSegmentType.LabelSegment, nextSegmentOffset); + // parse Text Description Group + nextSegmentOffset = parseSegment(NitfsSegmentType.TextSegment, nextSegmentOffset); + // parse Data Extension Description Group + nextSegmentOffset = parseSegment(NitfsSegmentType.DataExtensionSegment, nextSegmentOffset); + // parse Reserved Extension Description Group + nextSegmentOffset = parseSegment(NitfsSegmentType.ReservedExtensionSegment, nextSegmentOffset); + // parse User Defined Header Description (UDHD) Group + NitfsUserDefinedHeaderSegment userHeaderSeg = new RpfUserDefinedHeaderSegment(this.buffer); + this.segments.add( userHeaderSeg ); + nextSegmentOffset += userHeaderSeg.headerLength + userHeaderSeg.dataLength; + // parse Extended Header Description Group + nextSegmentOffset = parseSegment(NitfsSegmentType.ExtendedHeaderSegment, nextSegmentOffset); + + // let's read each header + for(NitfsSegment segment : segments) + { + +// +// String segId = NitfsUtil.getString(buffer, segment.headerStartOffset, 2); +// System.out.println("Segment type=" + segment.segmentType + ", id=" + segId); + } + } + + private int parseSegment(NitfsSegmentType segType, int nextSegmentOffset) + { + int headerLengthSize = segType.getHeaderLengthSize(); + int dataLengthSize = segType.getDataLengthSize(); + + int numOfSegments = Integer.parseInt(NitfsUtil.getString(this.buffer, 3)); + for (int i = 0; i < numOfSegments; i++) + { + int segHeaderLength = Integer.parseInt(NitfsUtil.getString(this.buffer, headerLengthSize)); + int seqDataLength = Integer.parseInt(NitfsUtil.getString(this.buffer, dataLengthSize)); + + int saveOffset = this.buffer.position(); // pass buffer to NitfsSegment to parse their headers' contents + NitfsSegment segment; + switch (segType) + { + case ImageSegment: + segment = new NitfsImageSegment(this.buffer, nextSegmentOffset, segHeaderLength, + nextSegmentOffset + segHeaderLength, seqDataLength); + break; + case SymbolSegment: + segment = new NitfsSymbolSegment(this.buffer, nextSegmentOffset, segHeaderLength, + nextSegmentOffset + segHeaderLength, seqDataLength); + break; + case LabelSegment: + segment = new NitfsLabelSegment(this.buffer, nextSegmentOffset, segHeaderLength, + nextSegmentOffset + segHeaderLength, seqDataLength); + break; + case TextSegment: + segment = new NitfsTextSegment(this.buffer, nextSegmentOffset, segHeaderLength, + nextSegmentOffset + segHeaderLength, seqDataLength); + break; + case DataExtensionSegment: + segment = new NitfsDataExtensionSegment(this.buffer, nextSegmentOffset, segHeaderLength, + nextSegmentOffset + segHeaderLength, seqDataLength); + break; + case ReservedExtensionSegment: + segment = new NitfsReservedExtensionSegment(this.buffer, nextSegmentOffset, segHeaderLength, + nextSegmentOffset + segHeaderLength, seqDataLength); + break; + case UserDefinedHeaderSegment: + segment = new RpfUserDefinedHeaderSegment(this.buffer); + break; + case ExtendedHeaderSegment: // // throw exception - wrong parser for ExtendedHeaderSegment + segment = new NitfsExtendedHeaderSegment(this.buffer, nextSegmentOffset, segHeaderLength, + nextSegmentOffset + segHeaderLength, seqDataLength); + break; + + default: + throw new NitfsRuntimeException("NitfsReader.UnknownOrUnsupportedSegment", segType.toString()); + + } + this.segments.add(segment); + + nextSegmentOffset += segHeaderLength + seqDataLength; + buffer.position(saveOffset); // restore offset + } + return nextSegmentOffset; + } + + public static NitfsMessage load(java.io.File file) throws java.io.IOException + { + validateImageFile(file); + + java.nio.ByteBuffer roBuffer = NitfsUtil.readEntireFile(file).asReadOnlyBuffer(); + + // check if it is a NITFS format file (NITF or NSIF - for NATO Secondary Imagery Format) + String fmtId = NitfsUtil.getString(roBuffer, 0, 4); + if( 0 != "NITF".compareTo(fmtId) && 0 != "NSIF".compareTo(fmtId)) + { + roBuffer = null; + throw new NitfsRuntimeException("NitfsReader.UnknownOrUnsupportedNitfsFormat", file.getCanonicalPath()); + } + + return new NitfsMessage(roBuffer); + } + + private static void validateImageFile(java.io.File file) + throws IOException, IllegalArgumentException, NitfsRuntimeException + { + if (null == file) + { + String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + if (!file.exists() || !file.canRead()) + throw new NitfsRuntimeException("NitfsReader.NoFileOrNoPermission", file.getCanonicalPath()); + } + + public static void main(String args[]) + { + String testImageFilename = "/depot/WorldWindJ/utils/gdal/020g222a.i42"; + + if(Configuration.isWindowsOS()) + testImageFilename = "C:\\depot\\WorldWindJ\\utils\\gdal\\020g222a.i42"; + + try + { + long startTime = System.currentTimeMillis(); + + NitfsMessage img = NitfsMessage.load(new File(testImageFilename)); + + System.out.println(MessageFormat.format("Image loaded in {0} mSec", (System.currentTimeMillis() - startTime))); + + System.out.println(img.fileHeader.getHeaderLength()); + System.out.println(img.fileHeader.getVersion()); + System.out.println(img.fileHeader.getComplexityLevel()); + System.out.println(img.fileHeader.getSpecialType()); + System.out.println(img.fileHeader.getOriginationStationId()); + System.out.println(img.fileHeader.getDateTime()); + System.out.println(img.fileHeader.getTitle()); + System.out.println(img.fileHeader.getFSCLAS()); + + System.out.println("HeaderLength=" + img.fileHeader.getHeaderLength()); + System.out.println("Total FileLength=" + img.fileHeader.getFileLength()); + + // System.out.println(); + } + catch (Exception e) + { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsReservedExtensionSegment.java b/gov/nasa/worldwind/formats/nitfs/NitfsReservedExtensionSegment.java new file mode 100644 index 0000000..c1e1605 --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsReservedExtensionSegment.java @@ -0,0 +1,21 @@ +package gov.nasa.worldwind.formats.nitfs; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsReservedExtensionSegment Mar 31, 2007 1:04:02 AM + */ +public class NitfsReservedExtensionSegment extends NitfsSegment +{ + public NitfsReservedExtensionSegment(java.nio.ByteBuffer buffer, int headerStartOffset, int headerLength, int dataStartOffset, int dataLength) + { + super(NitfsSegmentType.ReservedExtensionSegment, buffer, headerStartOffset, headerLength, dataStartOffset, dataLength); + + this.restoreBufferPosition(); + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsRuntimeException.java b/gov/nasa/worldwind/formats/nitfs/NitfsRuntimeException.java new file mode 100644 index 0000000..5246772 --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsRuntimeException.java @@ -0,0 +1,51 @@ +package gov.nasa.worldwind.formats.nitfs; + +import gov.nasa.worldwind.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsRuntimeException Mar 31, 2007 7:41:31 AM + */ +public final class NitfsRuntimeException extends java.lang.RuntimeException +{ + public NitfsRuntimeException() + { + } + + public NitfsRuntimeException(String messageID) + { + super(WorldWind.retrieveErrMsg(messageID)); + log(this.getMessage()); + } + + public NitfsRuntimeException(String messageID, String params) + { + super(WorldWind.retrieveErrMsg(messageID) + params); + log(this.getMessage()); + } + public NitfsRuntimeException(Throwable throwable) + { + super(throwable); + log(this.getMessage()); + } + public NitfsRuntimeException(String messageID, Throwable throwable) + { + super(WorldWind.retrieveErrMsg(messageID), throwable); + log(this.getMessage()); + } + public NitfsRuntimeException(String messageID, String params, Throwable throwable) + { + super(WorldWind.retrieveErrMsg(messageID) + params, throwable); + log(this.getMessage()); + } + private void log(String s) + { + WorldWind.logger().log(java.util.logging.Level.FINE, s); + } +} \ No newline at end of file diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsSegment.java b/gov/nasa/worldwind/formats/nitfs/NitfsSegment.java new file mode 100644 index 0000000..8b63253 --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsSegment.java @@ -0,0 +1,41 @@ +package gov.nasa.worldwind.formats.nitfs; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsSegment.java Mar 21, 2007 5:44:57 PM lado + */ +public class NitfsSegment +{ + protected java.nio.ByteBuffer buffer; + protected NitfsSegmentType segmentType; + protected int savedBufferOffset; + + protected int headerStartOffset; + protected int headerLength; + protected int dataStartOffset; + protected int dataLength; + + public NitfsSegment(NitfsSegmentType segmentType, java.nio.ByteBuffer buffer, + int headerStartOffset, int headerLength, int dataStartOffset, int dataLength) + { + this.buffer = buffer; + this.segmentType = segmentType; + this.headerStartOffset = headerStartOffset; + this.headerLength = headerLength; + this.dataStartOffset = dataStartOffset; + this.dataLength = dataLength; + this.savedBufferOffset = buffer.position(); + } + + protected void restoreBufferPosition() + { + this.buffer.position(this.savedBufferOffset); + } +} + diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsSegmentType.java b/gov/nasa/worldwind/formats/nitfs/NitfsSegmentType.java new file mode 100644 index 0000000..3a6f028 --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsSegmentType.java @@ -0,0 +1,35 @@ +package gov.nasa.worldwind.formats.nitfs; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsSegmentType Mar 29, 2007 6:33:57 PM lado + */ +public enum NitfsSegmentType +{ + ImageSegment (6, 10), + SymbolSegment (4, 6), + LabelSegment (4, 3), + TextSegment (4, 5), + DataExtensionSegment (4, 9), + ReservedExtensionSegment (4, 7), + UserDefinedHeaderSegment (0, 0), + ExtendedHeaderSegment (0, 0); + + private final int fieldHeaderLengthSize; + private final int fieldDataLengthSize; + + private NitfsSegmentType(int fieldHeaderLengthSize, int fieldDataLengthSize) + { + this.fieldHeaderLengthSize = fieldHeaderLengthSize; + this.fieldDataLengthSize = fieldDataLengthSize; + } + + public int getHeaderLengthSize() { return fieldHeaderLengthSize; } + public int getDataLengthSize() { return fieldDataLengthSize; } +} diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsSymbolSegment.java b/gov/nasa/worldwind/formats/nitfs/NitfsSymbolSegment.java new file mode 100644 index 0000000..26fe331 --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsSymbolSegment.java @@ -0,0 +1,21 @@ +package gov.nasa.worldwind.formats.nitfs; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsSymbolSegment Mar 31, 2007 12:55:42 AM + */ +public class NitfsSymbolSegment extends NitfsSegment +{ + public NitfsSymbolSegment(java.nio.ByteBuffer buffer, int headerStartOffset, int headerLength, int dataStartOffset, int dataLength) + { + super(NitfsSegmentType.SymbolSegment, buffer, headerStartOffset, headerLength, dataStartOffset, dataLength); + + this.restoreBufferPosition(); + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsTextSegment.java b/gov/nasa/worldwind/formats/nitfs/NitfsTextSegment.java new file mode 100644 index 0000000..5a5c8b8 --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsTextSegment.java @@ -0,0 +1,21 @@ +package gov.nasa.worldwind.formats.nitfs; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsTextSegment Mar 31, 2007 12:59:42 AM + */ +class NitfsTextSegment extends NitfsSegment +{ + public NitfsTextSegment(java.nio.ByteBuffer buffer, int headerStartOffset, int headerLength, int dataStartOffset, int dataLength) + { + super(NitfsSegmentType.TextSegment, buffer, headerStartOffset, headerLength, dataStartOffset, dataLength); + + this.restoreBufferPosition(); + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsUserDefinedHeaderSegment.java b/gov/nasa/worldwind/formats/nitfs/NitfsUserDefinedHeaderSegment.java new file mode 100644 index 0000000..bdbf793 --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsUserDefinedHeaderSegment.java @@ -0,0 +1,30 @@ +package gov.nasa.worldwind.formats.nitfs; + +import gov.nasa.worldwind.formats.rpf.*; +import gov.nasa.worldwind.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsUserDefinedHeaderSegment Apr 4, 2007 6:07:10 PM lado + */ +public abstract class NitfsUserDefinedHeaderSegment extends NitfsSegment +{ + protected int overflow; + protected String dataTag; + + public NitfsUserDefinedHeaderSegment(java.nio.ByteBuffer buffer) + { + super(NitfsSegmentType.UserDefinedHeaderSegment, buffer, 0, 0, 0, 0); + + this.headerLength = Integer.parseInt(NitfsUtil.getString(buffer, 5)); + this.overflow = Integer.parseInt(NitfsUtil.getString(buffer, 3)); + this.dataTag = NitfsUtil.getString(buffer, 6); + this.dataLength = Integer.parseInt(NitfsUtil.getString(buffer, 5)); + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/NitfsUtil.java b/gov/nasa/worldwind/formats/nitfs/NitfsUtil.java new file mode 100644 index 0000000..a89c087 --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/NitfsUtil.java @@ -0,0 +1,133 @@ +package gov.nasa.worldwind.formats.nitfs; + +import gov.nasa.worldwind.*; + +import java.io.*; +import java.nio.channels.*; +import java.nio.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: NitfsUtil Mar 30, 2007 12:43:29 PM lado + */ +public class NitfsUtil +{ + public static String getString(java.nio.ByteBuffer buffer, int offset, int len) + { + String s = StringUtil.EMPTY; + if (null != buffer && buffer.capacity() >= offset + len) + { + byte[] dest = new byte[len]; + buffer.position(offset); + buffer.get(dest, 0, len); + s = new String(dest).trim(); + } + return s; + } + + public static String getString(java.nio.ByteBuffer buffer, int len) + { + String s = StringUtil.EMPTY; + if (null != buffer && buffer.remaining() >= len) + { + byte[] dest = new byte[len]; + buffer.get(dest, 0, len); + s = new String(dest).trim(); + } + return s; + } + + public static int getNumeric(java.nio.ByteBuffer buffer, int len) + { + String s = StringUtil.EMPTY; + if (null != buffer && buffer.remaining() >= len) + { + byte[] dest = new byte[len]; + buffer.get(dest, 0, len); + s = new String(dest); + } + return Integer.parseInt(s); + } + + public static short getShortNumeric(java.nio.ByteBuffer buffer, int len) + { + String s = StringUtil.EMPTY; + if (null != buffer && buffer.remaining() >= len) + { + byte[] dest = new byte[len]; + buffer.get(dest, 0, len); + s = new String(dest); + } + return (short) (0xFFFF & Integer.parseInt(s)); + } + + public static boolean getBoolean(java.nio.ByteBuffer buffer) + { + return !((byte) 0 == buffer.get()); // 0 = false, non-zero = true + } + + public static short getByteAsShort(java.nio.ByteBuffer buffer) + { + return (short) (0xFF & buffer.get()); + } + + public static int getUShort(java.nio.ByteBuffer buffer) + { + return 0xFFFF & buffer.getShort(); + } + + public static long getUInt(java.nio.ByteBuffer buffer) + { + return 0xFFFFFFFFL & (long) buffer.getInt(); + } + + private static final int PAGE_SIZE = 4096; + + + public static java.nio.ByteBuffer readEntireFile(java.io.File file) throws java.io.IOException + { + return NitfsUtil.memoryMapFile(file); + // return NitfsUtil.readFile(file); + } + + private static java.nio.ByteBuffer readFile(java.io.File file) throws java.io.IOException + { + java.io.FileInputStream fis = new java.io.FileInputStream(file); + java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocate(PAGE_SIZE); + java.nio.channels.ReadableByteChannel channel = java.nio.channels.Channels.newChannel(fis); + + int count = 0; + while (count >= 0) + { + count = channel.read(buffer); + if (count > 0 && !buffer.hasRemaining()) + { + java.nio.ByteBuffer biggerBuffer = java.nio.ByteBuffer.allocate(buffer.limit() + PAGE_SIZE); + biggerBuffer.put((java.nio.ByteBuffer) buffer.rewind()); + buffer = biggerBuffer; + } + } + + if (buffer != null) + buffer.flip(); + + return buffer; + } + + private static java.nio.ByteBuffer memoryMapFile(java.io.File file) throws IOException + { + FileChannel roChannel = new RandomAccessFile(file, "r").getChannel(); + long fileSize = roChannel.size(); + MappedByteBuffer mapFile = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize); + if (!mapFile.isLoaded()) + mapFile.load(); + roChannel.close(); + return mapFile; + } +} diff --git a/gov/nasa/worldwind/formats/nitfs/Rpf2DdsCompress.java b/gov/nasa/worldwind/formats/nitfs/Rpf2DdsCompress.java new file mode 100644 index 0000000..38498bf --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/Rpf2DdsCompress.java @@ -0,0 +1,18 @@ +package gov.nasa.worldwind.formats.nitfs; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: Rpf2DdsCompress Apr 23, 2007 10:56:39 AM lado + */ +interface Rpf2DdsCompress +{ + DDSBlock4x4 getDxt1TransparentBlock4x4 (); + void writeDxt1Header (java.nio.ByteBuffer buffer, int width, int height); + DDSBlock4x4 compressDxt1Block4x4 (NitfsImageBand imageBand, byte[] pixelCodes, boolean hasTransparentPixels ); +} diff --git a/gov/nasa/worldwind/formats/nitfs/UserDefinedImageSubheader.java b/gov/nasa/worldwind/formats/nitfs/UserDefinedImageSubheader.java new file mode 100644 index 0000000..14f59d1 --- /dev/null +++ b/gov/nasa/worldwind/formats/nitfs/UserDefinedImageSubheader.java @@ -0,0 +1,58 @@ +package gov.nasa.worldwind.formats.nitfs; + +import gov.nasa.worldwind.*; +import gov.nasa.worldwind.formats.rpf.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: UserDefinedImageSubheader Mar 31, 2007 9:42:33 PM + */ +public class UserDefinedImageSubheader +{ + public short getOverflow() + { + return this.overflow; + } + + public String getTag() + { + return this.tag; + } + + public int getDataLength() + { + return this.dataLength; + } + + public RpfFrameFileComponents getRpfFrameFileComponents() + { + return this.rpfFrameFileComponents; + } + + private RpfFrameFileComponents rpfFrameFileComponents = null; + + private short overflow; + private String tag; + private int dataLength; + + public UserDefinedImageSubheader(java.nio.ByteBuffer buffer) throws NitfsRuntimeException + { + this.overflow = NitfsUtil.getShortNumeric(buffer, 3); + this.tag = NitfsUtil.getString(buffer, 6); + this.dataLength = NitfsUtil.getShortNumeric(buffer, 5); + + if(0 < this.dataLength) + { + if(StringUtil.Equals(tag, RpfFrameFileComponents.DATA_TAG)) + this.rpfFrameFileComponents = new RpfFrameFileComponents(buffer); + else + throw new NitfsRuntimeException("NitfsReader.UnknownOrUnsupportedUserDefinedImageSubheader"); + } + } +} diff --git a/gov/nasa/worldwind/formats/nmea/NmeaReader.java b/gov/nasa/worldwind/formats/nmea/NmeaReader.java new file mode 100644 index 0000000..f1c4099 --- /dev/null +++ b/gov/nasa/worldwind/formats/nmea/NmeaReader.java @@ -0,0 +1,255 @@ +/* +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.formats.nmea; + +import gov.nasa.worldwind.*; + +// TODO: exception handling +/** + * @author Tom Gaskins + * @version $Id: NmeaReader.java 525 2007-01-22 01:09:20Z ericdalgliesh $ + */ +public class NmeaReader implements gov.nasa.worldwind.Track, gov.nasa.worldwind.TrackSegment +{ + private java.util.List tracks = new java.util.ArrayList(); + private java.util.List segments = + new java.util.ArrayList(); + private java.util.List points = + new java.util.ArrayList(); + private String name; + private int sentenceNumber = 0; + + public NmeaReader() + { + this.tracks.add(this); + this.segments.add(this); + } + + public java.util.List getSegments() + { + return this.segments; + } + + public String getName() + { + return this.name; + } + + public int getNumPoints() + { + return this.points.size(); + } + + public java.util.List getPoints() + { + return this.points; + } + + /** + * @param path + * @throws IllegalArgumentException if path is null + * @throws java.io.IOException + */ + public void readFile(String path) throws java.io.IOException + { + if (path == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.PathIsNull"); + gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + this.name = path; + + java.io.File file = new java.io.File(path); + if (!file.exists()) + { + String msg = WorldWind.retrieveErrMsg("generic.fileNotFound"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new java.io.FileNotFoundException(path); + } + + java.io.FileInputStream fis = new java.io.FileInputStream(file); + this.doReadStream(fis); +// java.nio.ByteBuffer buffer = this.doReadFile(fis); +// this.parseBuffer(buffer); + } + + /** + * @param stream + * @param name + * @throws IllegalArgumentException if stream is null + * @throws java.io.IOException + */ + public void readStream(java.io.InputStream stream, String name) throws java.io.IOException + { + if (stream == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.InputStreamIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + this.name = name != null ? name : "Un-named stream"; + this.doReadStream(stream); + } + + public java.util.List getTracks() + { + return this.tracks; + } + + private void doReadStream(java.io.InputStream stream) + { + String sentence; + + try + { + do + { + sentence = this.readSentence(stream); + if (sentence != null) + { + ++this.sentenceNumber; + this.parseSentence(sentence); + } + } while (sentence != null); + } + catch (java.io.IOException e) + { + e.printStackTrace(); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } +// +// private static final int PAGE_SIZE = 4096; +// +// private java.nio.ByteBuffer doReadFile(java.io.FileInputStream fis) throws java.io.IOException +// { +// java.nio.channels.ReadableByteChannel channel = java.nio.channels.Channels.newChannel(fis); +// java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocate(PAGE_SIZE); +// +// int count = 0; +// while (count >= 0) +// { +// count = channel.read(buffer); +// if (count > 0 && !buffer.hasRemaining()) +// { +// java.nio.ByteBuffer biggerBuffer = java.nio.ByteBuffer.allocate(buffer.limit() + PAGE_SIZE); +// biggerBuffer.put((java.nio.ByteBuffer) buffer.rewind()); +// buffer = biggerBuffer; +// } +// } +// +// if (buffer != null) +// buffer.flip(); +// +// return buffer; +// } +// +// private void parseBuffer(java.nio.ByteBuffer buffer) +// { +// while (buffer.hasRemaining()) +// { +// byte b = buffer.get(); +// if (b == '$') +// { +// String sentence = this.readSentence(buffer); +// if (sentence.length() > 0) +// { +// this.parseSentence(sentence); +// } +// } +// } +// } + + private String readSentence(java.io.InputStream stream) throws java.io.IOException, InterruptedException + { + StringBuilder sb = null; + boolean endOfSentence = false; + + while (!endOfSentence && !Thread.currentThread().isInterrupted()) + { + int b = stream.read(); + + if (b < 0) + return null; + else if (b == 0) + Thread.sleep(200); + else if (b == '$') + sb = new StringBuilder(100); + else if (b == '\r') + endOfSentence = true; + else if (sb != null) + sb.append((char) b); + } + + // TODO: check checksum + return sb != null ? sb.toString() : null; + } + + private String readSentence(java.nio.ByteBuffer buffer) + { + StringBuilder sb = new StringBuilder(100); + boolean endOfSentence = false; + while (!endOfSentence) + { + byte b = buffer.get(); + if (b == '\r') + endOfSentence = true; + else + sb.append((char) b); + } + + return sb.toString(); + } + + private void parseSentence(String sentence) + { + String[] words = sentence.split("[,*]"); + + if (words[0].equalsIgnoreCase("GPGGA")) + this.doTrackPoint(words); +// else if (words[0].equalsIgnoreCase("GPRMC")) +// this.doTrackPoint(words); + } + + private void doTrackPoint(String[] words) + { + try + { + gov.nasa.worldwind.formats.nmea.NmeaTrackPoint point = new gov.nasa.worldwind.formats.nmea.NmeaTrackPoint( + words); + this.points.add(point); + } + catch (Exception e) + { + System.out.printf("Exception %s at sentence number %d for %s\n", + e.getMessage(), this.sentenceNumber, this.name); + } + } + + public static void main(String[] args) + { + NmeaReader reader = new NmeaReader(); + try + { + reader.readFile("src/worldwinddemo/trackdemo/trackdata/capture.txt"); + for (gov.nasa.worldwind.TrackPoint p : reader.getTracks().get(0).getSegments().get(0).getPoints()) + { + System.out.println(p); + } + } + catch (java.io.IOException e) + { + e.printStackTrace(); + } + } +} diff --git a/gov/nasa/worldwind/formats/nmea/NmeaTrackPoint.java b/gov/nasa/worldwind/formats/nmea/NmeaTrackPoint.java new file mode 100644 index 0000000..ea69569 --- /dev/null +++ b/gov/nasa/worldwind/formats/nmea/NmeaTrackPoint.java @@ -0,0 +1,194 @@ +/* +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.formats.nmea; + +import gov.nasa.worldwind.*; + +/** + * @author tag + * @version $Id: NmeaTrackPoint.java 513 2007-01-18 00:33:16Z ericdalgliesh $ + */ +public class NmeaTrackPoint implements gov.nasa.worldwind.TrackPoint +{ + private double latitude; + private double longitude; + private double altitude; + private double geoidHeight; + private String time; + + /** + * @param words + * @throws IllegalArgumentException if words is null or has length less than 1 + */ + public NmeaTrackPoint(String[] words) + { + if (words == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.ArrayIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + if (words.length < 1) + { + String msg = WorldWind.retrieveErrMsg("generic.arrayInvalidLength") + WorldWind.retrieveErrMsg( + "punctuation.space") + words.length; + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + if (words[0].equalsIgnoreCase("GPGGA")) + this.doGGA(words); + else if (words[0].equalsIgnoreCase("GPRMC")) + this.doRMC(words); + } + + /** + * @param words + * @throws IllegalArgumentException if words is null or has length less than 6 + */ + private void doGGA(String[] words) + { + // words won't be null, but it could be the wrong length + if (words.length < 6) + { + String msg = WorldWind.retrieveErrMsg("generic.arrayInvalidLength") + WorldWind.retrieveErrMsg( + "punctuation.space") + words.length; + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + this.time = words[1]; + this.latitude = this.parseLatitude(words[2], words[3]); + this.longitude = this.parseLongitude(words[4], words[5]); + if (words.length >= 11) + this.altitude = this.parseElevation(words[9], words[10]); + if (words.length >= 13) + this.geoidHeight = this.parseElevation(words[11], words[12]); + } + + private void doRMC(String[] words) + { + } + + private double parseLatitude(String angle, String direction) + { + if (angle.length() == 0) + return 0; + + double minutes = angle.length() > 2 ? Double.parseDouble(angle.substring(2, angle.length())) : 0d; + double degrees = Double.parseDouble(angle.substring(0, 2)) + minutes / 60d; + + return direction.equalsIgnoreCase("S") ? -degrees : degrees; + } + + private double parseLongitude(String angle, String direction) + { + if (angle.length() == 0) + return 0; + + double minutes = angle.length() > 3 ? Double.parseDouble(angle.substring(3, angle.length())) : 0d; + double degrees = Double.parseDouble(angle.substring(0, 3)) + minutes / 60d; + + return direction.equalsIgnoreCase("W") ? -degrees : degrees; + } + + private double parseElevation(String height, String units) + { + if (height.length() == 0) + return 0; + + return Double.parseDouble(height) * unitsToMeters(units); + } + + private double unitsToMeters(String units) + { + double f; + + if (units.equals("M")) // meters + f = 1d; + else if (units.equals("f")) // feet + f = 3.2808399; + else if (units.equals("F")) // fathoms + f = 0.5468066528; + else + f = 1d; + + return f; + } + + public double getLatitude() + { + return latitude; + } + + /** + * @param latitude + * @throws IllegalArgumentException if latitude is less than -90 or greater than 90 + */ + public void setLatitude(double latitude) + { + if (latitude > 90 || latitude < -90) + { + String msg = WorldWind.retrieveErrMsg("generic.angleOutOfRange") + WorldWind.retrieveErrMsg( + "punctuation.space") + latitude; + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + this.latitude = latitude; + } + + public double getLongitude() + { + return longitude; + } + + /** + * @param longitude + * @throws IllegalArgumentException if longitude is less than -180 or greater than 180 + */ + public void setLongitude(double longitude) + { + if (longitude > 180 || longitude < -180) + { + String msg = WorldWind.retrieveErrMsg("generic.angleOutOfRange") + WorldWind.retrieveErrMsg( + "punctuation.space") + longitude; + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + this.longitude = longitude; + } + + public double getElevation() + { + return this.altitude + this.geoidHeight; + } + + public void setElevation(double elevation) + { + this.altitude = elevation; + this.geoidHeight = 0; + } + + public String getTime() + { + return time; + } + + public void setTime(String time) + { + this.time = time; + } + + @Override + public String toString() + { + return String.format("(%10.8f\u00B0, %11.8f\u00B0, %10.4g m, %10.4g m, %s)", this.latitude, this.longitude, + this.altitude, this.geoidHeight, this.time); + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfColorMap.java b/gov/nasa/worldwind/formats/rpf/RpfColorMap.java new file mode 100644 index 0000000..675a447 --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfColorMap.java @@ -0,0 +1,111 @@ +package gov.nasa.worldwind.formats.rpf; + +import gov.nasa.worldwind.formats.nitfs.*; +import java.nio.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author lado + * @version $Id: RpfColorMap Apr 2, 2007 9:43:32 PM + */ +public class RpfColorMap +{ + public int getTableID() + { + return tableID; + } + + public int getHistogramRecordLength() + { + return histogramRecordLength; + } + + public int getHistogramTableOffset() + { + return (int)(0xFFFFFFFFL & histogramTableOffset); + } + + public int getNumOfColorRecords() + { + return (int)(0xFFFFFFFFL & numOfColorRecords); + } + + public int getColorElementLength() + { + return (int)(0xFFFFFFFFL & colorElementLength); + } + + public byte getColor(int colorRec, int bytePosition) + { + long idx = colorRec * this.getNumOfColorRecords() * getColorElementLength() + bytePosition ; + return this.colorMap[(int)idx]; + } + + + + public byte[] getColorMap() + { + return this.colorMap; + } + + private byte[] colorMap; + // private byte[] histogramMap; + + private int tableID; + private long numOfColorRecords; + + private short colorElementLength; + private int histogramRecordLength; + private long colorTableOffset; + private long histogramTableOffset; + + public RpfColorMap(java.nio.ByteBuffer buffer, int colormapSubsectionOffset) + { + this.parseRpfColorOffsetRecord(buffer); + // now let's load color map and histogram + int saveOffset = buffer.position(); + this.loadColorMaps(buffer, colormapSubsectionOffset); + // ok, we can skip histogram for now + // this.loadHistogram(buffer, colormapSubsectionOffset); + buffer.position(saveOffset); + } + + private void parseRpfColorOffsetRecord(java.nio.ByteBuffer buffer) + { + this.tableID = NitfsUtil.getUShort(buffer); + this.numOfColorRecords = NitfsUtil.getUInt(buffer); + this.colorElementLength = NitfsUtil.getByteAsShort(buffer); + this.histogramRecordLength = NitfsUtil.getUShort(buffer); + this.colorTableOffset = NitfsUtil.getUInt(buffer); + this.histogramTableOffset = NitfsUtil.getUInt(buffer); + } + + private void loadColorMaps(java.nio.ByteBuffer buffer, int colormapSubsectionOffset) + { + if (0 == this.numOfColorRecords) + throw new NitfsRuntimeException("NitfsReader.InvalidNumberOfColorRecords"); + if (0 == this.colorElementLength) + throw new NitfsRuntimeException("NitfsReader.InvalidLengthOfColorRecordElement"); + + buffer.position((int) (colormapSubsectionOffset + this.colorTableOffset)); + int mapLength = (int)(this.numOfColorRecords * this.colorElementLength); + this.colorMap = new byte[mapLength]; + buffer.get(this.colorMap, 0, mapLength); + } + + private void loadHistogram(java.nio.ByteBuffer buffer, int colormapSubsectionOffset) + { + if (0 == this.numOfColorRecords) + throw new NitfsRuntimeException("NitfsReader.InvalidNumberOfColorRecords"); + if (0 == this.histogramRecordLength) + throw new NitfsRuntimeException("NitfsReader.InvalidLengthOfHistogramRecordElement"); + // skip the loading of the histogram table, just increment a position in the buffer + buffer.position((int) (colormapSubsectionOffset + this.histogramTableOffset + + (this.numOfColorRecords * this.histogramRecordLength))); + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfDataSeries.java b/gov/nasa/worldwind/formats/rpf/RpfDataSeries.java new file mode 100644 index 0000000..db45180 --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfDataSeries.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.formats.rpf; + +import gov.nasa.worldwind.*; + +import java.util.*; +import static java.util.logging.Level.*; + +/** + * @author dcollins + * @version $Id: RpfDataSeries.java 1762 2007-05-07 19:43:55Z dcollins $ + */ +public enum RpfDataSeries +{ + /* [Section 5.1.4, MIL-STD-2411-1] */ + DataSeriesGN("GN", "GNC", "1:5,000,000", "Global Navigation Chart", "CADRG", 5000000), + DataSeriesJN("JN", "JNC", "1:2,000,000", "Jet Navigation Chart", "CADRG", 2000000), + DataSeriesOH("OH", "VHRC", "1:1,000,000", "VFR Helicopter Route Chart", "CADRG", 1000000), + DataSeriesON("ON", "ONC", "1:1,000,000", "Operational Navigation Chart", "CADRG", 1000000), + DataSeriesOW("OW", "WAC", "1:1,000,000", "High Flying Chart - Host Nation", "CADRG", 1000000), + DataSeriesTP("TP", "TPC", "1:500,000", "Tactical Pilotage Chart", "CADRG", 500000), + DataSeriesLF("LF", "LFC-FR (Day)", "1:500,000", "Low Flying Chart (Day)- Host Nation", "CADRG", 500000), + DataSeriesL1("L1", "LFC-1", "1:500,000", "Low Flying Chart (TBD #1)", "CADRG", 500000), + DataSeriesL2("L2", "LFC-2", "1:500,000", "Low Flying Chart (TBD #2)", "CADRG", 500000), + DataSeriesL3("L3", "LFC-3", "1:500,000", "Low Flying Chart (TBD #3)", "CADRG", 500000), + DataSeriesL4("L4", "LFC-4", "1:500,000", "Low Flying Chart (TBD #4)", "CADRG", 500000), + DataSeriesL5("L5", "LFC-5", "1:500,000", "Low Flying Chart (TBD #5)", "CADRG", 500000), + DataSeriesLN("LN", "LFC (Night)", "1:500,000", "Low Flying Chart (Night) - Host Nation", "CADRG", 500000), + DataSeriesJG("JG", "JOG", "1:250,000", "Joint Operations Graphic", "CADRG", 250000), + DataSeriesJA("JA", "JOG-A", "1:250,000", "Joint Operations Graphic - Air", "CADRG", 250000), + DataSeriesJR("JR", "JOG-R", "1:250,000", "Joint Operations Graphic - Radar", "CADRG", 250000), + DataSeriesJO("JO", "OPG", "1:250,000", "Operational Planning Graphic", "CADRG", 250000), + DataSeriesVT("VT", "VTAC", "1:250,000", "VFR Terminal Area Chart", "CADRG", 250000), + DataSeriesF1("F1", "TFC-1", "1:250,000", "Transit Flying Chart (TBD #1)", "CADRG", 250000), + DataSeriesF2("F2", "TFC-2", "1:250,000", "Transit Flying Chart (TBD #2)", "CADRG", 250000), + DataSeriesF3("F3", "TFC-3", "1:250,000", "Transit Flying Chart (TBD #3)", "CADRG", 250000), + DataSeriesF4("F4", "TFC-4", "1:250,000", "Transit Flying Chart (TBD #4)", "CADRG", 250000), + DataSeriesF5("F5", "TFC-5", "1:250,000", "Transit Flying Chart (TBD #5)", "CADRG", 250000), + DataSeriesAT("AT", "ATC", "1:200,000", "Series 200 Air Target Chart", "CADRG", 200000), + DataSeriesVH("VH", "HRC", "1:125,000", "Helicopter Route Chart", "CADRG", 125000), + DataSeriesTN("TN", "TFC (Night)", "1:250,000", "Transit Flying Chart(Night)- Host nation", "CADRG", 250000), + DataSeriesTR("TR", "TLM200", "1:200,000", "Topographic Line Map 1:200,000 scale", "CADRG", 200000), + DataSeriesTC("TC", "TLM 100", "1:100,000", "Topographic Line Map 1:100,0000 scale", "CADRG", 100000), + DataSeriesRV("RV", "Riverine", "1:50,000", "Riverine Map 1:50,000 scale", "CADRG", 50000), + DataSeriesTL("TL", "TLM 50", "1:50,000", "Topographic Line Map", "CADRG", 50000), + DataSeriesUL("UL", "TLM50-Other", "1:50,000", "Topographic Line Map (other 1:50,000 scale)", "CADRG", 50000), + DataSeriesTT("TT", "TLM25", "1:25,000", "Topographic Line Map 1:25,000 scale", "CADRG", 25000), + DataSeriesTQ("TQ", "TLM24", "1:24,000", "Topographic Line Map 1:24,000 scale", "CADRG", 24000), +// DataSeriesHA("HA", "HA", "Various", "Harbor and Approach Charts", "CADRG", -1), +// DataSeriesCO("CO", "CO", "Various", "Coastal Charts", "CADRG", -1), +// DataSeriesOA("OA", "OPAREA", "Various", "Naval Range Operating Area Chart", "CADRG", -1), +// DataSeriesCG("CG", "CG", "Various", "City Graphics", "CADRG", -1), + DataSeriesC1("C1", "CG", "1:10,000", "City Graphics", "CADRG", 10000), + DataSeriesC2("C2", "CG", "1:10,560", "City Graphics", "CADRG", 10560), + DataSeriesC3("C3", "CG", "1:11,000", "City Graphics", "CADRG", 11000), + DataSeriesC4("C4", "CG", "1:11,800", "City Graphics", "CADRG", 11800), + DataSeriesC5("C5", "CG", "1:12,000", "City Graphics", "CADRG", 12000), + DataSeriesC6("C6", "CG", "1:12,500", "City Graphics", "CADRG", 12500), + DataSeriesC7("C7", "CG", "1:12,800", "City Graphics", "CADRG", 12800), + DataSeriesC8("C8", "CG", "1:14,000", "City Graphics", "CADRG", 14000), + DataSeriesC9("C9", "CG", "1:14,700", "City Graphics", "CADRG", 14700), + DataSeriesCA("CA", "CG", "1:15,000", "City Graphics", "CADRG", 15000), + DataSeriesCB("CB", "CG", "1:15,500", "City Graphics", "CADRG", 15500), + DataSeriesCC("CC", "CG", "1:16,000", "City Graphics", "CADRG", 16000), + DataSeriesCD("CD", "CG", "1:16,666", "City Graphics", "CADRG", 16666), + DataSeriesCE("CE", "CG", "1:17,000", "City Graphics", "CADRG", 17000), + DataSeriesCF("CF", "CG", "1:17,500", "City Graphics", "CADRG", 17500), + DataSeriesCH("CH", "CG", "1:18,000", "City Graphics", "CADRG", 18000), + DataSeriesCJ("CJ", "CG", "1:20,000", "City Graphics", "CADRG", 20000), + DataSeriesCK("CK", "CG", "1:21,000", "City Graphics", "CADRG", 21000), + DataSeriesCL("CL", "CG", "1:21,120", "City Graphics", "CADRG", 21120), + DataSeriesCN("CN", "CG", "1:22,000", "City Graphics", "CADRG", 22000), + DataSeriesCP("CP", "CG", "1:23,000", "City Graphics", "CADRG", 23000), + DataSeriesCQ("CQ", "CG", "1:25,000", "City Graphics", "CADRG", 25000), + DataSeriesCR("CR", "CG", "1:26,000", "City Graphics", "CADRG", 26000), + DataSeriesCS("CS", "CG", "1:35,000", "City Graphics", "CADRG", 35000), + DataSeriesCT("CT", "CG", "1:36,000", "City Graphics", "CADRG", 36000), +// DataSeriesCM("CM", "CM", "Various", "Combat Charts", "CADRG", -1), + DataSeriesA1("A1", "CM", "1:10,000", "Combat Charts, 1:10,000 scale", "CADRG", 10000), + DataSeriesA2("A2", "CM", "1:25,000", "Combat Charts, 1:25,000 scale", "CADRG", 25000), + DataSeriesA3("A3", "CM", "1:50,000", "Combat Charts, 1:50,000 scale", "CADRG", 50000), + DataSeriesA4("A4", "CM", "1:100,000", "Combat Charts, 1:100,000 scale", "CADRG", 100000), + DataSeriesMI("MI", "MIM", "1:50,000", "Military Installation Maps", "CADRG", 50000), +// DataSeriesM1("M1", "MIM", "Various", "Military Installation Map (TBD #1)", "CADRG", -1), +// DataSeriesM2("M2", "MIM", "Various", "Military Installation Map (TBD #2)", "CADRG", -1), + DataSeriesVN("VN", "VNC", "1:500,000", "Visual Navigation Charts", "CADRG", 500000), + DataSeriesMM("MM", "---", "Various", "(Miscellaneous Maps & Charts)", "CADRG", 12500), + DataSeriesI1("I1", "---", "10m", "Imagery, 10 meter resolution", "CIB", 10.0), + DataSeriesI2("I2", "---", "5m", "Imagery, 5 meter resolution", "CIB", 5.0), + DataSeriesI3("I3", "---", "2m", "Imagery, 2 meter resolution", "CIB", 2.0), + DataSeriesI4("I4", "---", "1m", "Imagery, 1 meter resolution", "CIB", 1.0), + DataSeriesI5("I5", "---", ".5m", "Imagery, .5 (half) meter resolution", "CIB", 0.5), +// DataSeriesIV("IV", "---", "Various>10m", "Imagery, greater than 10 meter resolution", "CIB", -1), + /* [Chart.php] */ + DataSeriesTF("TF", "---", "1:250000", "Transit Fly (UK)", "CADRG", 250000),; + + public final String seriesCode; + public final String seriesAbbreviation; + public final String scaleOrResolution; + public final String dataSeries; + public final String rpfDataType; + public final double scaleOrGSD; + + private RpfDataSeries(String seriesCode, String seriesAbbreviation, String scaleOrResolution, String dataSeries, + String rpfDataType, double scaleOrGSD) + { + validateScaleOrGSD(scaleOrGSD); + this.rpfDataType = validateRpfDataType(rpfDataType); + this.seriesCode = seriesCode; + this.seriesAbbreviation = seriesAbbreviation; + this.scaleOrResolution = scaleOrResolution; + this.dataSeries = dataSeries; + this.scaleOrGSD = scaleOrGSD; + } + + private static Map enumConstantDirectory = null; + + private static Map enumConstantDirectory() + { + if (enumConstantDirectory == null) + { + RpfDataSeries[] universe = RpfDataSeries.class.getEnumConstants(); + enumConstantDirectory = new HashMap(2 * universe.length); + for (RpfDataSeries dataSeries : universe) + { + enumConstantDirectory.put(dataSeries.seriesCode, dataSeries); + } + } + return enumConstantDirectory; + } + + public static RpfDataSeries dataSeriesFor(String seriesCode) + { + if (seriesCode == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.StringIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + RpfDataSeries dataSeries = enumConstantDirectory().get(seriesCode); + if (dataSeries == null) + { + String message = WorldWind.retrieveErrMsg("generic.EnumNotFound") + seriesCode; + WorldWind.logger().log(FINE, message); + throw new EnumConstantNotPresentException(RpfDataSeries.class, message); + } + return dataSeries; + } + + public static String[] validDataTypes = null; + + public static String[] validDataTypes() + { + if (validDataTypes == null) + validDataTypes = new String[] {"CADRG", "CIB"}; + return validDataTypes; + } + + private static String validateRpfDataType(String rpfDataType) + { + if (rpfDataType == null) + { + String message = WorldWind.retrieveErrMsg("RpfDataSeries.InavlidRpfDataType") + rpfDataType; + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + for (String validType : validDataTypes()) + { + if (validType.equalsIgnoreCase(rpfDataType)) + return validType; + } + String message = WorldWind.retrieveErrMsg("RpfDataSeries.InavlidRpfDataType") + rpfDataType; + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + + private static void validateScaleOrGSD(double scaleOrGSD) + { + if (scaleOrGSD <= 0) + { + String message = WorldWind.retrieveErrMsg("RpfDataSeries.InvalidScaleOrGSD") + scaleOrGSD; + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfFile.java b/gov/nasa/worldwind/formats/rpf/RpfFile.java new file mode 100644 index 0000000..e00ad6b --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfFile.java @@ -0,0 +1,42 @@ +package gov.nasa.worldwind.formats.rpf; + +import gov.nasa.worldwind.formats.nitfs.*; + +import java.io.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author lado + * @version $Id: RpfFile Apr 8, 2007 8:53:48 AM + */ +public class RpfFile +{ + private NitfsMessage nitfsMsg; + private java.io.File rpfFile; + + public File getFile() + { + return this.rpfFile; + } + + public NitfsFileHeader getNitfsFileHeader() + { + return (null != nitfsMsg) ? nitfsMsg.getNitfsFileHeader() : null; + } + + public NitfsSegment getNitfsSegment(NitfsSegmentType segmentType) + { + return (null != nitfsMsg) ? nitfsMsg.getSegment(segmentType) : null; + } + + protected RpfFile(java.io.File rpfFile) throws IOException + { + this.rpfFile = rpfFile; + this.nitfsMsg = NitfsMessage.load(rpfFile); + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfFileComponents.java b/gov/nasa/worldwind/formats/rpf/RpfFileComponents.java new file mode 100644 index 0000000..b35a935 --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfFileComponents.java @@ -0,0 +1,44 @@ +package gov.nasa.worldwind.formats.rpf; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: RpfFileComponents Apr 4, 2007 5:53:04 PM lado + */ +public class RpfFileComponents +{ + private java.nio.ByteBuffer buffer; + + private RpfHeaderSection headerSection; + private RpfLocationSection locationSection; + + public RpfFileComponents(java.nio.ByteBuffer buffer) + { + this.buffer = buffer; + this.headerSection = new RpfHeaderSection(buffer); + + buffer.position(this.headerSection.locationSectionLocation); + this.locationSection = new RpfLocationSection(buffer); + + } + + public RpfHeaderSection getRpfHeaderSection() + { + return this.headerSection; + } + + public RpfFrameFileIndexSection getRpfFrameFileIndexSection() + { + if( 0 < locationSection.getFrameFileIndexSectionSubheaderLength()) + { + this.buffer.position(locationSection.getFrameFileIndexSectionSubheaderLocation()); + return new RpfFrameFileIndexSection(buffer); + } + return null; + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfFrameFileComponents.java b/gov/nasa/worldwind/formats/rpf/RpfFrameFileComponents.java new file mode 100644 index 0000000..c9df4bb --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfFrameFileComponents.java @@ -0,0 +1,166 @@ +package gov.nasa.worldwind.formats.rpf; + +import gov.nasa.worldwind.formats.nitfs.*; +import gov.nasa.worldwind.geom.*; + +import java.nio.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author lado + * @version $Id: RpfFrameFileComponents Mar 31, 2007 10:06:22 PM + */ +public class RpfFrameFileComponents +{ + public static final String DATA_TAG = "RPFIMG"; + + // [ rpf location section ] + public RpfLocationSection componentLocationTable; + + // [ rpf coverage section ] + public LatLon nwUpperLeft, swLowerleft, neUpperRight, seLowerRight; + public double verticalResolutionNorthSouth; + public double horizontalResolutionEastWest; + public double verticalIntervalLatitude; + public double horizontalIntervalLongitude; + + // [ color / grayscale section ] + public RpfColorMap[] rpfColorMaps; + + // [ rpf color / grayscale section ] + public short numOfColorGrayscaleOffsetRecords; + public short numOfColorConverterOffsetRecords; + public String externalColorGrayscaleFilename; + // [ rpf colormap subsection ] + public long colormapOffsetTableOffset; + public int colormapGrayscaleOffsetRecordLength; + + // [ rpf color converter subsection ] + + // [ rpf image description subheader ] + public int numOfSpectralGroups; + public int numOfSubframeTables; + public int numOfSpectralBandTables; + public int numOfSpectralBandLinesPerImageRow; + public int numOfSubframesInEastWestDirection; + public int numOfSubframesInNorthSouthDirection; + public long numOfOutputColumnsPerSubframe; + public long numOfOutputRowsPerSubframe; + public long subframeMaskTableOffset; + public long transparencyMaskTableOffset; + + // [ rpf related images section ] + public RelatedImagesSection relatedImagesSection = null; + + public RpfFrameFileComponents(java.nio.ByteBuffer buffer) + { + this.componentLocationTable = new RpfLocationSection(buffer); + + if (0 < this.componentLocationTable.getCoverageSectionSubheaderLength()) + this.parseRpfCoverageSection(buffer); + + if (0 < this.componentLocationTable.getColorGrayscaleSectionSubheaderLength()) + this.parseColorGrayscaleSection(buffer); + + if (0 < this.componentLocationTable.getColormapSubsectionLength()) + this.parseColormapSubSection(buffer); + + if (0 < this.componentLocationTable.getColorConverterSubsectionLength()) + this.parseColorConverterSubsection(buffer); + + if (0 < this.componentLocationTable.getImageDescriptionSubheaderLength()) + { + buffer.position(this.componentLocationTable.getImageDescriptionSubheaderLocation()); + this.parseImageDescriptionSubheader(buffer); + } + if (0 < this.componentLocationTable.getRelatedImagesSectionSubheaderLength()) + { + buffer.position(this.componentLocationTable.getRelatedImagesSectionSubheaderLocation()); + this.relatedImagesSection = new RelatedImagesSection(buffer); + } + } + + private void parseImageDescriptionSubheader(java.nio.ByteBuffer buffer) + { + this.numOfSpectralGroups = NitfsUtil.getUShort(buffer); + this.numOfSubframeTables = NitfsUtil.getUShort(buffer); + this.numOfSpectralBandTables = NitfsUtil.getUShort(buffer); + this.numOfSpectralBandLinesPerImageRow = NitfsUtil.getUShort(buffer); + this.numOfSubframesInEastWestDirection = NitfsUtil.getUShort(buffer); + this.numOfSubframesInNorthSouthDirection = NitfsUtil.getUShort(buffer); + this.numOfOutputColumnsPerSubframe = NitfsUtil.getUInt(buffer); + this.numOfOutputRowsPerSubframe = NitfsUtil.getUInt(buffer); + this.subframeMaskTableOffset = NitfsUtil.getUInt(buffer); + this.transparencyMaskTableOffset = NitfsUtil.getUInt(buffer); + } + + private void parseColorConverterSubsection(java.nio.ByteBuffer buffer) + { + buffer.position(this.componentLocationTable.getColorConverterSubsectionLocation()); +// if (0 < this.numOfColorConverterOffsetRecords) +// throw new NitfsRuntimeException("NitfsReader.NotImplemented.ColorConvertorSubsectionReader"); + } + + private void parseColormapSubSection(java.nio.ByteBuffer buffer) + { + buffer.position(this.componentLocationTable.getColormapSubsectionLocation()); + + this.colormapOffsetTableOffset = NitfsUtil.getUInt(buffer); + this.colormapGrayscaleOffsetRecordLength = NitfsUtil.getUShort(buffer); + // read color / grayscale AND histogram records; builds a ColorMap (LUT) + if (0 < this.numOfColorGrayscaleOffsetRecords) + { + rpfColorMaps = new RpfColorMap[this.numOfColorGrayscaleOffsetRecords]; + for (int i = 0; i < this.numOfColorGrayscaleOffsetRecords; i++) + { + rpfColorMaps[i] = new RpfColorMap(buffer, this.componentLocationTable.getColormapSubsectionLocation()); + } + } + else + throw new NitfsRuntimeException("NitfsReader.InvalidNumberOfRpfColorGrayscaleRecords"); + } + + private void parseColorGrayscaleSection(java.nio.ByteBuffer buffer) + { + buffer.position(this.componentLocationTable.getColorGrayscaleSectionSubheaderLocation()); + + this.numOfColorGrayscaleOffsetRecords = NitfsUtil.getByteAsShort(buffer); + this.numOfColorConverterOffsetRecords = NitfsUtil.getByteAsShort(buffer); + this.externalColorGrayscaleFilename = NitfsUtil.getString(buffer, 12); + } + + private void parseRpfCoverageSection(java.nio.ByteBuffer buffer) + { + buffer.position(this.componentLocationTable.getCoverageSectionSubheaderLocation()); + + this.nwUpperLeft = LatLon.fromDegrees(buffer.getDouble(), buffer.getDouble()); + this.swLowerleft = LatLon.fromDegrees(buffer.getDouble(), buffer.getDouble()); + this.neUpperRight = LatLon.fromDegrees(buffer.getDouble(), buffer.getDouble()); + this.seLowerRight = LatLon.fromDegrees(buffer.getDouble(), buffer.getDouble()); + + this.verticalResolutionNorthSouth = buffer.getDouble(); + this.horizontalResolutionEastWest = buffer.getDouble(); + this.verticalIntervalLatitude = buffer.getDouble(); + this.horizontalIntervalLongitude = buffer.getDouble(); + } + + public class RelatedImagesSection + { + // [ rpf related images section subheader ] + public long relatedImageDescriptionTableOffset; + public int numOfRelatedImageDescriptionRecords; + public int relatedImageDescriptionRecordLength; + + public RelatedImagesSection(java.nio.ByteBuffer buffer) + { + this.relatedImageDescriptionTableOffset = NitfsUtil.getUInt(buffer); + this.numOfRelatedImageDescriptionRecords = NitfsUtil.getUShort(buffer); + this.relatedImageDescriptionRecordLength = NitfsUtil.getUShort(buffer); + } + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfFrameFileIndexSection.java b/gov/nasa/worldwind/formats/rpf/RpfFrameFileIndexSection.java new file mode 100644 index 0000000..9724bcc --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfFrameFileIndexSection.java @@ -0,0 +1,193 @@ +package gov.nasa.worldwind.formats.rpf; + +import gov.nasa.worldwind.formats.nitfs.*; +import gov.nasa.worldwind.*; + +import java.util.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: RpfFileIndexSection Apr 4, 2007 6:32:20 PM lado + */ +public class RpfFrameFileIndexSection +{ + // [ frame file index section subheader ] + private String highestSecurityClassification; + private long frameFileIndexTableOffset; + private long numOfFrameFileIndexRecords; + private int numOfPathnameRecords; + private int frameFileIndexRecordLength; + + // [ frame file index subsection ] + + // [ frame file index table ] + private ArrayList frameFileIndexTable = new ArrayList(); + // [ pathname table ] + // private ArrayList pathnameTable = new ArrayList(); + + + public String getHighestSecurityClassification() + { + return highestSecurityClassification; + } + + public long getFrameFileIndexTableOffset() + { + return frameFileIndexTableOffset; + } + + public long getNumOfFrameFileIndexRecords() + { + return numOfFrameFileIndexRecords; + } + + public int getNumOfPathnameRecords() + { + return numOfPathnameRecords; + } + + public int getFrameFileIndexRecordLength() + { + return frameFileIndexRecordLength; + } + + public ArrayList getFrameFileIndexTable() + { + return frameFileIndexTable; + } + +// public ArrayList getPathnameTable() +// { +// return pathnameTable; +// } + + public RpfFrameFileIndexSection(java.nio.ByteBuffer buffer) + { + // [ frame file index section subheader ] + this.highestSecurityClassification = NitfsUtil.getString(buffer, 1); + this.frameFileIndexTableOffset = NitfsUtil.getUInt(buffer); + this.numOfFrameFileIndexRecords = NitfsUtil.getUInt(buffer); + this.numOfPathnameRecords = NitfsUtil.getUShort(buffer); + this.frameFileIndexRecordLength = NitfsUtil.getUShort(buffer); + + this.parseFrameFileIndexAndPathnameTables( buffer ); + } + + private void parseFrameFileIndexAndPathnameTables(java.nio.ByteBuffer buffer) + { + int theSectionOffset = buffer.position(); + Hashtable pathnames = new Hashtable(); + + for(int i = 0 ; i < this.numOfFrameFileIndexRecords; i++) + this.frameFileIndexTable.add(new RpfFrameFileIndexRecord(buffer)); + + for(int i = 0 ; i < this.numOfPathnameRecords; i++) + { + int relOffset = buffer.position() - theSectionOffset; + int len = NitfsUtil.getUShort(buffer); + pathnames.put(relOffset, NitfsUtil.getString(buffer, len)); + } + + if(0 < this.frameFileIndexTable.size() && 0 < pathnames.size()) + { // update pathname field in every RpfFrameFileIndexRecord + for (RpfFrameFileIndexRecord rec : this.frameFileIndexTable) + { + int offset = (int) rec.getPathnameRecordOffset(); + if (pathnames.containsKey(offset)) + rec.setPathname(pathnames.get(offset)); + else + throw new NitfsRuntimeException("NitfsReader.CorrespondingPathnameWasNotFound"); + } + } + } + + public class RpfFrameFileIndexRecord + { + public int getBoundaryRectangleRecordNumber() + { + return boundaryRectangleRecordNumber; + } + + public int getFrameLocationRowNumber() + { + return frameLocationRowNumber; + } + + public int getFrameLocationColumnNumber() + { + return frameLocationColumnNumber; + } + + public String getFrameFileName() + { + return frameFileName; + } + + public String getGeoLocation() + { + return geoLocation; + } + + public String getSecurityClass() + { + return securityClass; + } + + public String getSecurityCountryCode() + { + return securityCountryCode; + } + + public String getSecurityReleaseMark() + { + return securityReleaseMark; + } + + public long getPathnameRecordOffset() + { + return pathnameRecordOffset; + } + + public String getPathname() + { + return pathname; + } + + public void setPathname(String pathname) + { + this.pathname = pathname; + } + + private int boundaryRectangleRecordNumber; + private int frameLocationRowNumber; + private int frameLocationColumnNumber; + private long pathnameRecordOffset; + private String frameFileName; + private String geoLocation; + private String securityClass; + private String securityCountryCode; + private String securityReleaseMark; + private String pathname; // this field is not part of the NITFS spec + + + public RpfFrameFileIndexRecord(java.nio.ByteBuffer buffer) + { + this.boundaryRectangleRecordNumber = NitfsUtil.getUShort(buffer); + this.frameLocationRowNumber = NitfsUtil.getUShort(buffer); + this.frameLocationColumnNumber = NitfsUtil.getUShort(buffer); + this.pathnameRecordOffset = NitfsUtil.getUInt(buffer); + this.frameFileName = NitfsUtil.getString(buffer, 12); + this.geoLocation = NitfsUtil.getString(buffer, 6); + this.securityClass = NitfsUtil.getString(buffer, 1); + this.securityCountryCode = NitfsUtil.getString(buffer, 2); + this.securityReleaseMark = NitfsUtil.getString(buffer, 2); + this.pathname = StringUtil.EMPTY; + } + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfFrameFilenameFormatException.java b/gov/nasa/worldwind/formats/rpf/RpfFrameFilenameFormatException.java new file mode 100644 index 0000000..e0cc117 --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfFrameFilenameFormatException.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.formats.rpf; + +/** + * @author dcollins + * @version $Id: RpfFrameFilenameFormatException.java 1762 2007-05-07 19:43:55Z dcollins $ + */ +public class RpfFrameFilenameFormatException extends IllegalArgumentException +{ + public RpfFrameFilenameFormatException() + { + } + + public RpfFrameFilenameFormatException(String message) + { + super(message); + } + + public RpfFrameFilenameFormatException(String message, Throwable cause) + { + super(message, cause); + } + + public RpfFrameFilenameFormatException(Throwable cause) + { + super(cause); + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfFrameFilenameUtil.java b/gov/nasa/worldwind/formats/rpf/RpfFrameFilenameUtil.java new file mode 100644 index 0000000..f859f2b --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfFrameFilenameUtil.java @@ -0,0 +1,261 @@ +/* +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.formats.rpf; + +import gov.nasa.worldwind.*; + +import static java.util.logging.Level.*; + +/** + * @author dcollins + * @version $Id: RpfFrameFilenameUtil.java 1762 2007-05-07 19:43:55Z dcollins $ + */ +public class RpfFrameFilenameUtil +{ + /* [Section 30.6, MIL-C-89038] */ + /* [Section A.3.6, MIL-PRF-89041A] */ + public static final char[] BASE34_ALPHABET = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + private static char[] charArray; + + /* [Section 30.6, MIL-C-89038] */ + /* [Section A.3.6, MIL-PRF-89041A] */ + public static char[] base34ValueOf(int i, char[] dest, int offset, int count) + { + if (dest == null || dest.length < count) + dest = new char[count]; + for (int digit = count + offset - 1; digit >= offset; digit--) + { + dest[digit] = BASE34_ALPHABET[i % 34]; + i /= 34; + } + return dest; + } + + private static void ensureCharArray(int length) + { + if (charArray == null || charArray.length < length) + charArray = new char[length]; + } + + public static String filenameFor(RpfFrameProperties frameProperties, String rpfDataType) + { + validateFrameProperties(frameProperties); + if (rpfDataType == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.StringIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + ensureCharArray(12); + if (rpfDataType.compareToIgnoreCase("CADRG") == 0) + filenameForCadrgOrCib(frameProperties, 5, 2, charArray); + else if (rpfDataType.compareToIgnoreCase("CIB") == 0) + filenameForCadrgOrCib(frameProperties, 6, 1, charArray); + else + { + String message = WorldWind.retrieveErrMsg("RpfFrameFilenameUtil.UnknownRpfDataType") + rpfDataType; + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + return new String(charArray, 0, 12); + } + + public static String filenameFor(RpfFrameProperties frameProperties) + { + validateFrameProperties(frameProperties); + return filenameFor(frameProperties, frameProperties.dataSeries.rpfDataType); + } + + /* [Section 30.6, MIL-C-89038] */ + /* [Section A.3.6, MIL-PRF-89041A] */ + private static void filenameForCadrgOrCib(RpfFrameProperties frameProperties, int frameChars, int versionChars, + char[] dest) + { + int index = 0; + base34ValueOf(frameProperties.frameNumber, dest, index, frameChars); + index += frameChars; + base34ValueOf(frameProperties.version, dest, index, versionChars); + index += versionChars; + dest[index++] = frameProperties.producer.id; + dest[index++] = '.'; + frameProperties.dataSeries.seriesCode.getChars(0, 2, dest, index); + index += 2; + dest[index] = frameProperties.zone.zoneCode; + } + + /* [Section 30.6, MIL-C-89038] */ + /* [Section A.3.6, MIL-PRF-89041A] */ + public static int parseBase34(char[] src, int offset, int count) + { + int i = 0; + for (int digit = offset; digit < offset + count; digit++) + { + int index; + char charUpper = Character.toUpperCase(src[digit]); + if (charUpper >= '0' && charUpper <= '9') + index = charUpper - '0'; + else if (charUpper >= 'A' && charUpper <= 'H') + index = 10 + charUpper - 'A'; + else if (charUpper >= 'J' && charUpper <= 'N') + index = 18 + charUpper - 'J'; + else if (charUpper >= 'P' && charUpper <= 'Z') + index = 23 + charUpper - 'P'; + else + { + String message = WorldWind.retrieveErrMsg("RpfFrameFilenameUtil.Base34Error"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + i = (i * 34) + index; + } + return i; + } + + public static RpfFrameProperties parseFilename(String filename, String rpfDataType) + { + if (filename == null || rpfDataType == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.StringIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + if (filename.length() != 12) + { + String message = WorldWind.retrieveErrMsg("RpfFrameFilenameUtil.BadFilenameLength") + filename; + WorldWind.logger().log(FINE, message); + throw new RpfFrameFilenameFormatException(message); + } + ensureCharArray(12); + filename.getChars(0, 12, charArray, 0); + RpfFrameProperties frameProperties; + if (rpfDataType.compareToIgnoreCase("CADRG") == 0) + frameProperties = parseFilenameCadrgOrCib(charArray, 5, 2); + else if (rpfDataType.compareToIgnoreCase("CIB") == 0) + frameProperties = parseFilenameCadrgOrCib(charArray, 6, 1); + else + { + String message = WorldWind.retrieveErrMsg("RpfFrameFilenameUtil.UnknownRpfDataType") + rpfDataType; + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + return frameProperties; + } + + public static RpfFrameProperties parseFilename(String filename) + { + if (filename == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.StringIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + if (filename.length() != 12) + { + String message = WorldWind.retrieveErrMsg("RpfFrameFilenameUtil.BadFilenameLength") + filename; + WorldWind.logger().log(FINE, message); + throw new RpfFrameFilenameFormatException(message); + } + RpfDataSeries dataSeries; + try + { + dataSeries = RpfDataSeries.dataSeriesFor(filename.substring(9, 11)); + } + catch (EnumConstantNotPresentException e) + { + String message = WorldWind.retrieveErrMsg("RpfFrameFilenameUtil.EnumNotFound"); + WorldWind.logger().log(FINE, message); + throw new RpfFrameFilenameFormatException(message, e); + } + return parseFilename(filename, dataSeries.rpfDataType); + } + + /* [Section 30.6, MIL-C-89038] */ + /* [Section A.3.6, MIL-PRF-89041A] */ + private static RpfFrameProperties parseFilenameCadrgOrCib(char[] src, int frameChars, int versionChars) + { + int index = 0; + + int frameNumber; + int version; + try + { + frameNumber = parseBase34(src, index, frameChars); + index += frameChars; + version = parseBase34(src, index, versionChars); + index += versionChars; + } + catch (IllegalArgumentException e) + { + String message = WorldWind.retrieveErrMsg("RpfFrameFilenameUtil.IntegerNotParsed"); + WorldWind.logger().log(FINE, message); + throw new RpfFrameFilenameFormatException(message, e); + } + + RpfProducer producer; + RpfDataSeries dataSeries; + RpfZone zone; + try + { + producer = RpfProducer.producerFor(src[index]); + index += 2; + dataSeries = RpfDataSeries.dataSeriesFor(new String(src, index, 2)); + index += 2; + zone = RpfZone.zoneFor(src[index]); + } + catch (EnumConstantNotPresentException e) + { + String message = WorldWind.retrieveErrMsg("RpfFrameFilenameUtil.EnumNotFound"); + WorldWind.logger().log(FINE, message); + throw new RpfFrameFilenameFormatException(message, e); + } + + return new RpfFrameProperties(zone, frameNumber, dataSeries, producer, version); + } + + private static void validateFrameProperties(RpfFrameProperties frameProperties) + { + if (frameProperties == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.RpfFramePropertiesIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + if (frameProperties.zone == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.RpfZoneIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + if (frameProperties.frameNumber < 0) + { + String message = WorldWind.retrieveErrMsg("RpfFrameProperties.BadFrameNumber") + + frameProperties.frameNumber; + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + if (frameProperties.dataSeries == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.RpfDataSeriesIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + if (frameProperties.producer == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.RpfProducerIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + if (frameProperties.version < 0) + { + String message = WorldWind.retrieveErrMsg("RpfFrameProperties.BadVersion") + + frameProperties.version; + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfFrameProperties.java b/gov/nasa/worldwind/formats/rpf/RpfFrameProperties.java new file mode 100644 index 0000000..f657a6e --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfFrameProperties.java @@ -0,0 +1,85 @@ +/* +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.formats.rpf; + +/** + * @author dcollins + * @version $Id: RpfFrameProperties.java 1762 2007-05-07 19:43:55Z dcollins $ + */ +public class RpfFrameProperties +{ + public final RpfZone zone; + public final int frameNumber; + public final RpfDataSeries dataSeries; + public final RpfProducer producer; + public final int version; +// private final int hashCode; + + public RpfFrameProperties(RpfZone zone, int frameNumber, RpfDataSeries dataSeries, RpfProducer producer, + int version) + { + this.zone = zone; + this.frameNumber = frameNumber; + this.dataSeries = dataSeries; + this.producer = producer; + this.version = version; +// this.hashCode = this.computeHash(); + } + + private int computeHash() + { + int hash = 0; + if (this.zone != null) + hash = 29 * hash + this.zone.hashCode(); + if (this.dataSeries != null) + hash = 29 * hash + this.dataSeries.hashCode(); + if (this.producer != null) + hash = 29 * hash + producer.hashCode(); + hash = 29 * hash + version; + hash = 29 * hash + frameNumber; + return hash; + } + + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || o.getClass() != this.getClass()) + return false; + final RpfFrameProperties that = (RpfFrameProperties) o; + if (this.zone != that.zone) + return false; + if (this.frameNumber != that.frameNumber) + return false; + if (this.dataSeries != that.dataSeries) + return false; + if (this.producer != that.producer) + return false; + if (this.version != that.version) + return false; + return true; + } + + public int hashCode() + { +// return this.hashCode; + return this.computeHash(); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(this.getClass().getName()); + sb.append('['); + sb.append("zone=").append(this.zone); + sb.append(", frameNumber=").append(this.frameNumber); + sb.append(", dataSeries=").append(this.dataSeries); + sb.append(", producer=").append(this.producer); + sb.append(", version=").append(this.version); + sb.append(']'); + return sb.toString(); + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfFramePropertyType.java b/gov/nasa/worldwind/formats/rpf/RpfFramePropertyType.java new file mode 100644 index 0000000..a506a55 --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfFramePropertyType.java @@ -0,0 +1,68 @@ +/* +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.formats.rpf; + +/** + * @author dcollins + * @version $Id: RpfFramePropertyType.java 1762 2007-05-07 19:43:55Z dcollins $ + */ +public enum RpfFramePropertyType +{ + DataSeries(RpfDataSeries.class) + { + public Object getInstance(RpfFrameProperties frameProperties) + { + if (frameProperties == null) + return null; + return frameProperties.dataSeries; + } + }, + FrameNumber(Integer.class) + { + public Object getInstance(RpfFrameProperties frameProperties) + { + if (frameProperties == null) + return null; + return frameProperties.frameNumber; + } + }, + Producer(RpfProducer.class) + { + public Object getInstance(RpfFrameProperties frameProperties) + { + if (frameProperties == null) + return null; + return frameProperties.producer; + } + }, + Version(Integer.class) + { + public Object getInstance(RpfFrameProperties frameProperties) + { + if (frameProperties == null) + return null; + return frameProperties.version; + } + }, + Zone(RpfZone.class) + { + public Object getInstance(RpfFrameProperties frameProperties) + { + if (frameProperties == null) + return null; + return frameProperties.zone; + } + }; + + public final Class classType; + + private RpfFramePropertyType(Class classType) + { + this.classType = classType; + } + + public abstract Object getInstance(RpfFrameProperties frameProperties); +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfHeaderSection.java b/gov/nasa/worldwind/formats/rpf/RpfHeaderSection.java new file mode 100644 index 0000000..e18f21f --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfHeaderSection.java @@ -0,0 +1,43 @@ +package gov.nasa.worldwind.formats.rpf; + +import gov.nasa.worldwind.formats.nitfs.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: RpfHeaderSection Apr 4, 2007 4:37:01 PM lado + */ +class RpfHeaderSection +{ + public static final String DATA_TAG = "RPFHDR"; + + public boolean endianIndicator; + public short headerLength; + public String filename; + public short updateIndicator; // new | replacement | update + public String govSpecNumber; + public String govSpecDate; + public String securityClass; + public String securityCountryCode; + public String securityReleaseMark; + public int locationSectionLocation; + + public RpfHeaderSection(java.nio.ByteBuffer buffer) + { + this.endianIndicator = ((byte) 0 != buffer.get()); // reads 1 byte, 0 for big endian + this.headerLength = buffer.getShort(); // reads 2 bytes + this.filename = NitfsUtil.getString(buffer, 12); + this.updateIndicator = NitfsUtil.getByteAsShort(buffer); // reads 1 byte (short) + this.govSpecNumber = NitfsUtil.getString(buffer, 15); + this.govSpecDate = NitfsUtil.getString(buffer, 8); + this.securityClass = NitfsUtil.getString(buffer, 1); + this.securityCountryCode = NitfsUtil.getString(buffer, 2); + this.securityReleaseMark = NitfsUtil.getString(buffer, 2); + this.locationSectionLocation = buffer.getInt(); // read 4 bytes (int) + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfImageFile.java b/gov/nasa/worldwind/formats/rpf/RpfImageFile.java new file mode 100644 index 0000000..d641127 --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfImageFile.java @@ -0,0 +1,188 @@ +package gov.nasa.worldwind.formats.rpf; + +import gov.nasa.worldwind.formats.nitfs.*; +import gov.nasa.worldwind.*; + +import java.io.*; +import java.text.*; +import java.awt.image.*; + +import java.awt.*; +import java.awt.image.*; +import java.io.*; +import java.net.*; +import java.nio.*; +import javax.imageio.*; +import javax.swing.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author lado + * @version $Id: RpfImageFile Apr 8, 2007 8:51:57 AM + */ +public class RpfImageFile extends RpfFile +{ + private NitfsImageSegment imageSegment = null; + private UserDefinedImageSubheader imageSubheader = null; + private RpfFrameFileComponents rpfFrameFileComponents = null; + + public RpfFrameFileComponents getRpfFrameFileComponents() + { + return this.rpfFrameFileComponents; + } + + public UserDefinedImageSubheader getImageSubheader() + { + return this.imageSubheader; + } + + public NitfsImageSegment getImageSegment() + { + return this.imageSegment; + } + + + + private RpfImageFile(java.io.File rpfFile) throws java.io.IOException, NitfsRuntimeException + { + super(rpfFile); + + this.imageSegment = (NitfsImageSegment) this.getNitfsSegment(NitfsSegmentType.ImageSegment); + this.validateRpfImage(); + + this.imageSubheader = this.imageSegment.getUserDefinedImageSubheader(); + this.rpfFrameFileComponents = this.imageSubheader.getRpfFrameFileComponents(); + } + + private void validateRpfImage() throws NitfsRuntimeException + { + if ( null == this.imageSegment ) + throw new NitfsRuntimeException("NitfsReader.ImageSegmentWasNotFound"); + if( null == this.imageSegment.getUserDefinedImageSubheader()) + throw new NitfsRuntimeException("NitfsReader.UserDefinedImageSubheaderWasNotFound"); + if( null == this.imageSegment.getUserDefinedImageSubheader().getRpfFrameFileComponents()) + throw new NitfsRuntimeException("NitfsReader.RpfFrameFileComponentsWereNotFoundInUserDefinedImageSubheader"); + } + + public int[] getImagePixelsAsArray(int[] dest, RpfImageType imageType) + { + IntBuffer buffer = IntBuffer.wrap(dest); + this.getImagePixelsAsBuffer(buffer, imageType); + return dest; + } + + public ByteBuffer getImageAsDdsTexture() + { + if (null != this.imageSegment) + return this.imageSegment.getImageAsDdsTexture(); + return null; + } + + + public IntBuffer getImagePixelsAsBuffer(IntBuffer dest, RpfImageType imageType) + { + if (null != this.imageSegment) + this.imageSegment.getImagePixelsAsArray(dest, imageType); + return dest; + } + + public BufferedImage getBufferedImage() + { + if (null == this.imageSegment) + return null; + + BufferedImage bimage = new BufferedImage( + this.getImageSegment().numSignificantCols, + this.getImageSegment().numSignificantRows, + BufferedImage.TYPE_INT_ARGB); + + WritableRaster raster = bimage.getRaster(); + java.awt.image.DataBufferInt dataBuffer = (java.awt.image.DataBufferInt) raster.getDataBuffer(); + + IntBuffer buffer = IntBuffer.wrap(dataBuffer.getData()); + this.getImageSegment().getImagePixelsAsArray(buffer, RpfImageType.IMAGE_TYPE_ALPHA_RGB); + return bimage; + } + + public boolean hasTransparentAreas() + { + if(null != this.imageSegment) + return (this.imageSegment.hasTransparentPixels() || this.imageSegment.hasMaskedSubframes()); + return false; + } + + public static RpfImageFile load(java.io.File rpfFile) throws java.io.IOException, NitfsRuntimeException + { + return new RpfImageFile(rpfFile); + } + + public static void main(String args[]) throws IOException + { + String cibFilename = (Configuration.isWindowsOS()) + ? "C:\\depot\\WorldWindJ\\utils\\gdal\\020g222a.i42" : "/depot/WorldWindJ/utils/gdal/020g222a.i42"; + String cadrgFilename = (Configuration.isWindowsOS()) + ? "C:\\depot\\nitfs\\CADRG\\CTLM50\\CT50Z02\\02F7W053.TL2" : "/depot/nitfs/CADRG/CTLM50/CT50Z02/02F7W053.TL2"; + String transparentCadrgFilename = (Configuration.isWindowsOS()) + ? "C:\\depot\\nitfs\\CADRG\\CTLM50\\CT50Z02\\0D6MM013.TL1" : "/depot/nitfs/CADRG/CTLM50/CT50Z02/0D6MM013.TL1"; + + String rpfFilename = cibFilename; // cibFilename; // cadrgFilename; transparentCadrgFilename; + + try + { + long startTime = System.currentTimeMillis(); + + RpfImageFile rpfImageFile = RpfImageFile.load(new File(rpfFilename)); + +// ---------- getImageAsDdsTexture example ----------------------- + + ByteBuffer ddsBuffer = rpfImageFile.getImageAsDdsTexture(); + System.out.println(MessageFormat.format("RPF file loaded in {0} mSec", (System.currentTimeMillis() - startTime))); + WWIO.saveBuffer(ddsBuffer, new File( + (Configuration.isWindowsOS()) ? "c:\\depot\\nitfs\\DDS\\test.dds" : "/depot/nitfs/DDS/test.dds" + )); + +// ---------- getImageAsArray example ----------------------- +// +// int size = rpfImageFile.getImageSegment().numSignificantCols +// * rpfImageFile.getImageSegment().numSignificantRows; +// IntBuffer rgbaBuffer = IntBuffer.allocate(size); +// rpfImageFile.getImagePixelsAsArray(rgbaBuffer.array(), RpfImageType.IMAGE_TYPE_RGB_ALPHA); +// +// System.out.println(MessageFormat.format("RPF file loaded in {0} mSec", +// (System.currentTimeMillis() - startTime))); + +// ---------- getBufferedImage example ----------------------- +// BufferedImage bimage = rpfImageFile.getBufferedImage(); +// +// System.out.println(MessageFormat.format("RPF file loaded in {0} mSec", +// (System.currentTimeMillis() - startTime))); +// +// Icon icon = new ImageIcon(bimage); +// JLabel label = new JLabel(icon); +// +// final JFrame f = new JFrame("RPF Viewer"); +// f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); +// f.getContentPane().add(label); +// f.pack(); +// SwingUtilities.invokeLater(new Runnable() +// { +// public void run() +// { +// f.setLocationRelativeTo(null); +// f.setVisible(true); +// } +// }); + } + catch (Exception e) + { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/gov/nasa/worldwind/formats/rpf/RpfImageType.java b/gov/nasa/worldwind/formats/rpf/RpfImageType.java new file mode 100644 index 0000000..74a1f61 --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfImageType.java @@ -0,0 +1,20 @@ +package gov.nasa.worldwind.formats.rpf; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author lado + * @version $Id: RpfImageType Apr 18, 2007 11:35:23 PM + */ +public enum RpfImageType +{ + IMAGE_TYPE_RGB, + IMAGE_TYPE_RGB_ALPHA, + IMAGE_TYPE_ALPHA_RGB, + IMAGE_TYPE_GRAY, + IMAGE_TYPE_GRAY_ALPHA +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfLocationSection.java b/gov/nasa/worldwind/formats/rpf/RpfLocationSection.java new file mode 100644 index 0000000..89837ec --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfLocationSection.java @@ -0,0 +1,303 @@ +package gov.nasa.worldwind.formats.rpf; + +import gov.nasa.worldwind.formats.nitfs.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: RpfLocationSection Apr 4, 2007 5:57:23 PM lado + */ +public class RpfLocationSection +{ + private java.util.Hashtable table = + new java.util.Hashtable(); + + public int getHeaderComponentLocation() + { + return this.getLocation(128); + } + public int getHeaderComponentLength() + { + return this.getLength(128); + } + public int getLocationComponentLocation() + { + return this.getLocation(129); + } + public int getLocationComponentLength() + { + return this.getLength(129); + } + public int getCoverageSectionSubheaderLocation() + { + return this.getLocation(130); + } + public int getCoverageSectionSubheaderLength() + { + return this.getLength(130); + } + public int getCompressionSectionSubheaderLocation() + { + return this.getLocation(131); + } + public int getCompressionSectionSubheaderLength() + { + return this.getLength(131); + } + public int getCompressionLookupSubsectionLocation() + { + return this.getLocation(132); + } + public int getCompressionLookupSubsectionLength() + { + return this.getLength(132); + } + public int getCompressionParameterSubsectionLocation() + { + return this.getLocation(133); + } + public int getCompressionParameterSubsectionLength() + { + return this.getLength(133); + } + public int getColorGrayscaleSectionSubheaderLocation() + { + return this.getLocation(134); + } + public int getColorGrayscaleSectionSubheaderLength() + { + return this.getLength(134); + } + public int getColormapSubsectionLocation() + { + return this.getLocation(135); + } + public int getColormapSubsectionLength() + { + return this.getLength(135); + } + public int getImageDescriptionSubheaderLocation() + { + return this.getLocation(136); + } + public int getImageDescriptionSubheaderLength() + { + return this.getLength(136); + } + public int getImageDisplayParametersSubheaderLocation() + { + return this.getLocation(137); + } + public int getImageDisplayParametersSubheaderLength() + { + return this.getLength(137); + } + public int getMaskSubsectionLocation() + { + return this.getLocation(138); + } + public int getMaskSubsectionLength() + { + return this.getLength(138); + } + public int getColorConverterSubsectionLocation() + { + return this.getLocation(139); + } + public int getColorConverterSubsectionLength() + { + return this.getLength(139); + } + + public int getSpatialDataSubsectionLocation() + { + return this.getLocation(140); + } + public int getSpatialDataSubsectionLength() + { + return this.getLength(140); + } + public int getAttributeSectionSubheaderLocation() + { + return this.getLocation(141); + } + public int getAttributeSectionSubheaderLength() + { + return this.getLength(141); + } + public int getAttributeSubsectionLocation() + { + return this.getLocation(142); + } + public int getAttributeSubsectionLength() + { + return this.getLength(142); + } + public int getExplicitArealCoverageTableLocation() + { + return this.getLocation(143); + } + public int getExplicitArealCoverageTableLength() + { + return this.getLength(143); + } + public int getRelatedImagesSectionSubheaderLocation() + { + return this.getLocation(144); + } + public int getRelatedImagesSectionSubheaderLength() + { + return this.getLength(144); + } + public int getRelatedImagesSubsectionLocation() + { + return this.getLocation(145); + } + public int getRelatedImagesSubsectionLength() + { + return this.getLength(145); + } + public int getReplaceUpdateSectionSubheaderLocation() + { + return this.getLocation(146); + } + public int getReplaceUpdateSectionSubheaderLength() + { + return this.getLength(146); + } + public int getReplaceUpdateTableLocation() + { + return this.getLocation(147); + } + public int getReplaceUpdateTableLength() + { + return this.getLength(147); + } + public int getBoundaryRectangleSectionSubheaderLocation() + { + return this.getLocation(148); + } + public int getBoundaryRectangleSectionSubheaderLength() + { + return this.getLength(148); + } + public int getBoundaryRectangleTableLocation() + { + return this.getLocation(149); + } + public int getBoundaryRectangleTableLength() + { + return this.getLength(149); + } + public int getFrameFileIndexSectionSubheaderLocation() + { + return this.getLocation(150); + } + public int getFrameFileIndexSectionSubheaderLength() + { + return this.getLength(150); + } + public int getFrameFileIndexSubsectionLocation() + { + return this.getLocation(151); + } + public int getFrameFileIndexSubsectionLength() + { + return this.getLength(151); + } + public int getColorTableIndexSectionSubheaderLocation() + { + return this.getLocation(152); + } + public int getColorTableIndexSectionSubheaderLength() + { + return this.getLength(152); + } + public int getColorTableIndexRecordLocation() + { + return this.getLocation(153); + } + public int getColorTableIndexRecordLength() + { + return this.getLength(153); + } + // because of lack of "unsigned" in java, we store UINT as int, and UINT as long + public int locationSectionLength; + public long componentLocationTableOffset; + public int numOfComponentLocationRecords; + public int componentLocationRecordLength; + public long componentAggregateLength; + + public RpfLocationSection(java.nio.ByteBuffer buffer) + { + this.locationSectionLength = NitfsUtil.getUShort(buffer); + this.componentLocationTableOffset = NitfsUtil.getUInt(buffer); + this.numOfComponentLocationRecords = NitfsUtil.getUShort(buffer); + this.componentLocationRecordLength = NitfsUtil.getUShort(buffer); + this.componentAggregateLength = NitfsUtil.getUInt(buffer); + + if (this.numOfComponentLocationRecords < 2) + throw new NitfsRuntimeException("NitfsReader:InvalidNumberOfComponentLocationRecords"); + + for (int i = 0; i < this.numOfComponentLocationRecords; i++) + { + int id = NitfsUtil.getUShort(buffer); + table.put(id, new ComponentLocationRecord(id, + NitfsUtil.getUInt(buffer), // read uint:4 as "length" + NitfsUtil.getUInt(buffer) // read uint:4 as "location" + )); + } + } + + private int getLocation(int componentID) + { + ComponentLocationRecord rec = this.getRecord(componentID); + return (int) ((null != rec) ? (0xFFFFFFFFL & rec.getLocation()) : 0); + } + private int getLength(int componentID) + { + ComponentLocationRecord rec = this.getRecord(componentID); + return (int) ((null != rec) ? (0xFFFFFFFFL & rec.getLength()) : 0); + } + private ComponentLocationRecord getRecord(int componentID) + { + if(table.containsKey(componentID)) + return table.get(componentID); + return null; + } + + public class ComponentLocationRecord + { + private int id; + private long length; + private long location; + + public int getId() + { + return id; + } + + public long getLength() + { + return length; + } + + public long getLocation() + { + return location; + } + + public ComponentLocationRecord(int id, long length, long location) + { + this.id = id; + this.length = length; + this.location = location; + } + } + +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfProducer.java b/gov/nasa/worldwind/formats/rpf/RpfProducer.java new file mode 100644 index 0000000..6bd6004 --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfProducer.java @@ -0,0 +1,94 @@ +/* +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.formats.rpf; + +import gov.nasa.worldwind.*; + +import static java.util.logging.Level.*; + +/** + * @author dcollins + * @version $Id: RpfProducer.java 1762 2007-05-07 19:43:55Z dcollins $ + */ +public enum RpfProducer +{ + /* [Section 5.2.1, MIL-STD-2411-1] */ + Producer1('1', "AFACC", "Air Force Air Combat Command"), + Producer2('2', "AFESC", "Air Force Electronic Systems Center"), + Producer3('3', "NIMA", "National Imagery and Mapping Agency, Primary"), + Producer4('4', "NIMA1", "NIMA, Alternate Site 1"), + Producer5('5', "NIMA2", "NIMA, Alternate Site 2"), + Producer6('6', "NIMA3", "NIMA, Alternate Site 3"), + Producer7('7', "SOCAF", "Air Force Special Operations Command"), + Producer8('8', "SOCOM", "United States Special Operations Command"), + Producer9('9', "PACAF", "Pacific Air Forces"), + ProducerA('A', "USAFE", "United States Air Force, Europe"), + ProducerB('B', "Non-DoD (NonDD)", "US producer outside the Department of Defense"), + ProducerC('C', "Non-US (NonUS)", "Non-US producer"), + ProducerD('D', "NIMA", "DCHUM (DCHUM) NIMA produced Digital CHUM file"), + ProducerE('E', "Non-NIMA DCHUM (DCHMD)", "DoD producer of Digital CHUM file otherthan NIMA "), + ProducerF('F', "Non-US DCHUM (DCHMF)", "Non-US (foreign)producer of Digital CHUMfiles"), + ProducerG('G', "Non-DoD DCHUM (DCHMG)", "US producer of Digital CHUM files outsideDoD"), + ProducerH('H', "IMG2RPF", "Non-specified, Imagery formatted to RPF"), +// Producer?('I'-'Z', "", "Reserved for future standardization"), + ; + + public final Character id; + public final String producerCode; + public final String producer; + + private RpfProducer(Character id, String producerCode, String producer) + { + this.id = id; + this.producer = producer; + this.producerCode = producerCode; + } + + private static RpfProducer[] enumConstantAlphabet = null; + + private static RpfProducer[] enumConstantAlphabet() + { + if (enumConstantAlphabet == null) + { + RpfProducer[] universe = RpfProducer.class.getEnumConstants(); + enumConstantAlphabet = new RpfProducer[36]; + for (RpfProducer producer : universe) + { + enumConstantAlphabet[indexFor(producer.id)] = producer; + } + } + return enumConstantAlphabet; + } + + private static int indexFor(Character id) + { + if (id >= '0' && id <= '9') + return id - '0'; + else if (id >= 'A' && id <= 'Z') + return 10 + id - 'A'; + return -1; + } + + public static RpfProducer producerFor(Character id) + { + if (id == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.CharacterIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + RpfProducer zone; + RpfProducer[] alphabet = enumConstantAlphabet(); + int index = indexFor(Character.toUpperCase(id)); + if (index < 0 || index >= alphabet.length || (zone = alphabet[index]) == null) + { + String message = WorldWind.retrieveErrMsg("generic.EnumNotFound") + id; + WorldWind.logger().log(FINE, message); + throw new EnumConstantNotPresentException(RpfZone.class, message); + } + return zone; + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfTocCrawler.java b/gov/nasa/worldwind/formats/rpf/RpfTocCrawler.java new file mode 100644 index 0000000..bbba63d --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfTocCrawler.java @@ -0,0 +1,294 @@ +/* + 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.formats.rpf; + +import gov.nasa.worldwind.*; + +import java.io.*; +import java.util.*; +import java.util.concurrent.locks.*; +import static java.util.logging.Level.*; + +/** + * @author dcollins + * @version $Id: RpfTocCrawler.java 1762 2007-05-07 19:43:55Z dcollins $ + */ +public class RpfTocCrawler +{ + public static final String RPF_DIRECTORY = "RPF"; + public static final String RPF_OVERVIEW_EXTENSION = ".OVR"; + public static final String RPF_TOC_EXTENSION = ".TOC"; + + public static interface RpfTocCrawlerListener + { + void fileFound(File tocFile); + + void finished(); + } + + public abstract static class RpfTocGrouper implements RpfTocCrawlerListener + { + private final RpfFramePropertyType groupType; + + public RpfTocGrouper(RpfFramePropertyType groupType) + { + if (groupType == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.RpfFramePropertyTypeIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + this.groupType = groupType; + } + + public void fileFound(File tocFile) + { + RpfTocFile rpfTocFile = null; + try + { + rpfTocFile = RpfTocFile.load(tocFile); + } + catch (IOException e) + { + WorldWind.logger().log(FINE, e.getMessage()); + } + + if (rpfTocFile == null) + return; + + String firstFrameFilename = findFirstRpfFrameFilename(rpfTocFile); + if (firstFrameFilename == null) + return; + + RpfFrameProperties firstFrameProperties = null; + try + { + firstFrameProperties = RpfFrameFilenameUtil.parseFilename(firstFrameFilename); + } + catch (Exception e) + { + String message = WorldWind.retrieveErrMsg("RpfTocCrawler.ExceptionParsingFilename"); + WorldWind.logger().log(FINE, message, e); + } + + if (firstFrameProperties != null) + this.addToGroup(this.groupType.getInstance(firstFrameProperties), rpfTocFile); + } + + public void finished() + { + } + + public abstract void addToGroup(Object groupKey, RpfTocFile rpfTocFile); + } + + private static class RpfTocRunner implements Runnable + { + private final RpfTocCrawler context; + private final File directory; + private final RpfTocCrawlerListener listener; + + public RpfTocRunner(RpfTocCrawler context, File directory, RpfTocCrawlerListener listener) + { + this.context = context; + this.directory = directory; + this.listener = listener; + } + + public void run() + { + this.context.process(this.directory, listener, true); + this.context.threadLock.lock(); + try + { + if (this.context.thread != this.context.deadThread) + { + listener.finished(); + this.context.thread = this.context.deadThread; + } + } + finally + { + this.context.threadLock.unlock(); + } + } + } + + private final Thread deadThread = new Thread(); + private final Lock threadLock = new ReentrantLock(); + private volatile Thread thread = null; + + public RpfTocCrawler() + { + } + + public static String findFirstRpfFrameFilename(RpfTocFile tocFile) + { + if (tocFile != null + && tocFile.getFrameFileIndexSection() != null + && tocFile.getFrameFileIndexSection().getFrameFileIndexTable() != null + && tocFile.getFrameFileIndexSection().getFrameFileIndexTable().size() > 0) + { + for (RpfFrameFileIndexSection.RpfFrameFileIndexRecord frameFileIndexRecord + : tocFile.getFrameFileIndexSection().getFrameFileIndexTable()) + { + if (frameFileIndexRecord != null + && frameFileIndexRecord.getFrameFileName() != null + && !frameFileIndexRecord.getFrameFileName().toUpperCase().endsWith(RPF_OVERVIEW_EXTENSION)) + { + return frameFileIndexRecord.getFrameFileName(); + } + } + } + return null; + } + + private void process(File file, RpfTocCrawlerListener listener, boolean inOwnThread) + { + this.threadLock.lock(); + try + { + if (inOwnThread && this.thread == deadThread) + return; + } + finally + { + this.threadLock.unlock(); + } + + File[] children = file.listFiles(); + if (children == null) + return; + if (RPF_DIRECTORY.compareToIgnoreCase(file.getName()) == 0) + this.processRpfDirectory(children, listener, inOwnThread); + else + this.processUnknownDirectory(children, listener, inOwnThread); + } + + private void processRpfDirectory(File[] contents, RpfTocCrawlerListener listener, boolean inOwnThread) + { + for (File file : contents) + { + this.threadLock.lock(); + try + { + if (inOwnThread && this.thread == deadThread) + return; + } + finally + { + this.threadLock.unlock(); + } + + if (file.getName().toUpperCase().endsWith(RPF_TOC_EXTENSION)) + listener.fileFound(file); + } + } + + private void processUnknownDirectory(File[] contents, RpfTocCrawlerListener listener, boolean inOwnThread) + { + for (File file : contents) + { + this.threadLock.lock(); + try + { + if (inOwnThread && this.thread == deadThread) + return; + } + finally + { + this.threadLock.unlock(); + } + + if (file.isDirectory()) + this.process(file, listener, inOwnThread); + } + } + + public File[] invoke(File directory) + { + File validDir = this.validateDirectory(directory); + final Collection results = new ArrayList(); + this.process(validDir, new RpfTocCrawlerListener() + { + public void fileFound(File file) + { + if (file.exists()) + results.add(file); + } + + public void finished() + { + } + }, false); + File[] tocFileArray = new File[results.size()]; + results.toArray(tocFileArray); + return tocFileArray; + } + + public void invoke(File directory, RpfTocCrawlerListener listener) + { + File validDir = this.validateDirectory(directory); + this.process(validDir, listener, false); + } + + public void start(File directory, RpfTocCrawlerListener listener) + { + this.threadLock.lock(); + try + { + if (this.thread != null || this.thread == deadThread) + { + String message = WorldWind.retrieveErrMsg("RpfTocCrawler.BadStart"); + WorldWind.logger().log(FINE, message); + throw new IllegalStateException(message); + } + File validDir = this.validateDirectory(directory); + this.thread = new Thread(new RpfTocRunner(this, validDir, listener)); + this.thread.start(); + } + finally + { + this.threadLock.unlock(); + } + } + + public void stop() + { + this.threadLock.lock(); + try + { + this.thread = deadThread; + } + finally + { + this.threadLock.unlock(); + } + } + + private File validateDirectory(File directory) + { + if (directory == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + String path = directory.getAbsolutePath(); + if (!path.endsWith("/") && !path.endsWith("\\")) + { + path = path + File.separatorChar; + directory = new File(path); + } + if (!directory.exists()) + { + String message = WorldWind.retrieveErrMsg("generic.fileNotFound"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + return directory; + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfTocFile.java b/gov/nasa/worldwind/formats/rpf/RpfTocFile.java new file mode 100644 index 0000000..f389d59 --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfTocFile.java @@ -0,0 +1,75 @@ +package gov.nasa.worldwind.formats.rpf; + +import gov.nasa.worldwind.*; +import gov.nasa.worldwind.formats.nitfs.*; + +import java.io.*; +import java.text.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: RpfTocFile Apr 4, 2007 2:24:00 PM lado + */ +public class RpfTocFile extends RpfFile +{ + private RpfFileComponents rpfFileComponents; + + public RpfHeaderSection getHeaderSection() + { + return (null != this.rpfFileComponents) ? this.rpfFileComponents.getRpfHeaderSection() : null; + } + + public RpfFrameFileIndexSection getFrameFileIndexSection() + { + return (null != this.rpfFileComponents) ? this.rpfFileComponents.getRpfFrameFileIndexSection() : null; + } + + public RpfFileComponents getRpfFileComponents() + { + return this.rpfFileComponents; + } + + protected RpfTocFile(java.io.File rpfFile) throws IOException, NitfsRuntimeException + { + super(rpfFile); + + RpfUserDefinedHeaderSegment segment = + (RpfUserDefinedHeaderSegment)this.getNitfsSegment( NitfsSegmentType.UserDefinedHeaderSegment ); + + if(null == segment) + throw new NitfsRuntimeException("NitfsReader.UserDefinedHeaderSegmentWasNotFound"); + + this.rpfFileComponents = segment.getRpfFileComponents(); + if(null == this.rpfFileComponents) + throw new NitfsRuntimeException("NitfsReader.RpfFileComponents.Were.Not.Found.In.UserDefinedHeaderSegment"); + } + + public static RpfTocFile load(java.io.File tocFile) throws java.io.IOException + { + return new RpfTocFile(tocFile); + } + + public static void main(String args[]) + { + String testTocFilename = (Configuration.isWindowsOS()) ? "C:\\RPF\\A.TOC" : "/depot/WorldWindJ/utils/rpf/A.TOC"; + try + { + long startTime = System.currentTimeMillis(); + + RpfTocFile toc = RpfTocFile.load(new File(testTocFilename)); + + System.out.println(MessageFormat.format("TOC file loaded in {0} mSec", (System.currentTimeMillis() - startTime))); + } + catch (Exception e) + { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfUserDefinedHeaderSegment.java b/gov/nasa/worldwind/formats/rpf/RpfUserDefinedHeaderSegment.java new file mode 100644 index 0000000..c9a0d51 --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfUserDefinedHeaderSegment.java @@ -0,0 +1,37 @@ +package gov.nasa.worldwind.formats.rpf; + +import gov.nasa.worldwind.formats.nitfs.*; +import gov.nasa.worldwind.*; +/* +Copyright (C) 2001, 2007 United States Government +as represented by the Administrator of the +National Aeronautics and Space Administration. +All Rights Reserved. +*/ + +/** + * @author Lado Garakanidze + * @version $Id: RpfUserDefinedHeaderSegment Apr 17, 2007 6:55:38 PM lado + */ +public class RpfUserDefinedHeaderSegment extends NitfsUserDefinedHeaderSegment +{ + private RpfFileComponents components; + + public RpfUserDefinedHeaderSegment(java.nio.ByteBuffer buffer) + { + super(buffer); + + if(StringUtil.Equals(RpfHeaderSection.DATA_TAG, this.dataTag)) + { + this.components = new RpfFileComponents(buffer); + } + else + throw new NitfsRuntimeException("NitfsReader.RpfHeaderNotFoundInUserDefinedSegment", this.dataTag); + this.restoreBufferPosition(); + } + + public RpfFileComponents getRpfFileComponents() + { + return this.components; + } +} diff --git a/gov/nasa/worldwind/formats/rpf/RpfZone.java b/gov/nasa/worldwind/formats/rpf/RpfZone.java new file mode 100644 index 0000000..500e454 --- /dev/null +++ b/gov/nasa/worldwind/formats/rpf/RpfZone.java @@ -0,0 +1,526 @@ +/* +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.formats.rpf; + +import gov.nasa.worldwind.*; +import gov.nasa.worldwind.geom.*; + +import java.util.*; +import static java.util.logging.Level.*; + +/** + * @author dcollins + * @version $Id: RpfZone.java 1762 2007-05-07 19:43:55Z dcollins $ + */ +public enum RpfZone +{ + /* [Table III, Section 70, MIL-A-89007] */ + Zone1('1', 369664, 400384, 0, 32), + Zone2('2', 302592, 400384, 32, 48), + Zone3('3', 245760, 400384, 48, 56), + Zone4('4', 199168, 400384, 56, 64), + Zone5('5', 163328, 400384, 64, 68), + Zone6('6', 137216, 400384, 68, 72), + Zone7('7', 110080, 400384, 72, 76), + Zone8('8', 82432, 400384, 76, 80), +// Zone9('9', 400384, 400384, 80, 90), + ZoneA('A', 369664, 400384, 0, -32), + ZoneB('B', 302592, 400384, -32, -48), + ZoneC('C', 245760, 400384, -48, -56), + ZoneD('D', 199168, 400384, -56, -64), + ZoneE('E', 163328, 400384, -64, -68), + ZoneF('F', 137216, 400384, -68, -72), + ZoneG('G', 110080, 400384, -72, -76), + ZoneH('H', 82432, 400384, -76, -80), +// ZoneJ('J', 400384, 400384, -80, -90), + ; + // TODO: computations for polar zones + + private static class ZoneKey + { + public final RpfZone zone; + public final RpfDataSeries dataSeries; + private int hashCode; + + public ZoneKey(RpfZone zone, RpfDataSeries dataSeries) + { + this.zone = zone; + this.dataSeries = dataSeries; + this.hashCode = this.computeHash(); + } + + private int computeHash() + { + int hash = 0; + if (this.zone != null) + hash = 29 * hash + this.zone.ordinal(); + if (this.dataSeries != null) + hash = 29 * hash + this.dataSeries.ordinal(); + return hash; + } + + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || !o.getClass().equals(this.getClass())) + return false; + final ZoneKey that = (ZoneKey) o; + return (this.zone == that.zone) && (this.dataSeries == that.dataSeries); + } + + public int hashCode() + { + return this.hashCode; + } + } + + public static class ZoneValues + { + public final RpfZone zone; + public final RpfDataSeries dataSeries; + public final int eastWestPixelConstant; + public final int northSouthPixelConstant; + public final Angle equatorwardExtent; + public final Angle polewardExtent; + public final Sector extent; + public final int latitudinalFrames; + public final int longitudinalFrames; + public final Angle latitudinalFrameExtent; + public final Angle longitudinalFrameExtent; + public final int maximumFrameNumber; + + private ZoneValues(RpfZone zone, RpfDataSeries dataSeries) + { + if (zone == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.RpfZoneIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + if (dataSeries == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.RpfDataSeriesIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + + this.zone = zone; + this.dataSeries = dataSeries; + this.northSouthPixelConstant = computeNorthSouthPixelConstant(zone.northSouthPixelSpacingConstant, + dataSeries); + this.eastWestPixelConstant = computeEastWestPixelConstant(zone.eastWestPixelSpacingConstant, dataSeries); + this.equatorwardExtent = computeEquatorwardExtent(zone.equatorwardNominalBoundary, + this.northSouthPixelConstant, PixelRowsPerFrame); + this.polewardExtent = computePolewardExtent(zone.polewardNominalBoundary, this.northSouthPixelConstant, + PixelRowsPerFrame); + this.extent = computeZoneExtent(this.equatorwardExtent.degrees, this.polewardExtent.degrees); + this.latitudinalFrames = computeLatitudinalFrames(this.polewardExtent.degrees, + this.equatorwardExtent.degrees, this.northSouthPixelConstant, PixelRowsPerFrame); + this.longitudinalFrames = computeLongitudinalFrames(this.eastWestPixelConstant, PixelRowsPerFrame); + this.latitudinalFrameExtent = computeLatitudinalFrameExtent(this.polewardExtent.degrees, + this.equatorwardExtent.degrees, this.latitudinalFrames); + this.longitudinalFrameExtent = computeLongitudinalFrameExtent(this.longitudinalFrames); + this.maximumFrameNumber = computeMaximumFrameNumber(this.latitudinalFrames, this.longitudinalFrames); + } + + private static double clamp(double x, double min, double max) + { + return (x < min) ? min : ((x > max) ? max : x); + } + + // ============== Constant Zone Computations ======================= // + // ============== Constant Zone Computations ======================= // + // ============== Constant Zone Computations ======================= // + + private static int computeEastWestPixelConstant(double eastWestPixelSpacingConstant, + RpfDataSeries dataSeries) + { + if (dataSeries.rpfDataType.equalsIgnoreCase("CADRG")) + return computeEastWestPixelConstantCADRG(eastWestPixelSpacingConstant, dataSeries.scaleOrGSD); + else if (dataSeries.rpfDataType.equalsIgnoreCase("CIB")) + return computeEastWestPixelConstantCIB(eastWestPixelSpacingConstant, dataSeries.scaleOrGSD); + else + { + String message = WorldWind.retrieveErrMsg("RpfZone.UnknownRpfDataType") + dataSeries.rpfDataType; + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + } + + /* [Section 60.1.2 MIL-C-89038] */ + private static int computeEastWestPixelConstantCADRG(double eastWestPixelSpacingConstant, double scale) + { + double S = 1000000d / scale; + double tmp = eastWestPixelSpacingConstant * S; + tmp = 512d * (int) Math.ceil(tmp / 512d); + tmp /= (150d / 100d); + return 256 * (int) Math.round(tmp / 256d); + } + + /* [Section A.5.1.1, MIL-PRF-89041A] */ + private static int computeEastWestPixelConstantCIB(double eastWestPixelSpacingConstant, + double groundSampleDistance) + { + double S = 100d / groundSampleDistance; + double tmp = eastWestPixelSpacingConstant * S; + return 512 * (int) Math.ceil(tmp / 512d); + } + + /* [Section 60.1.5.c MIL-C-89038] */ + /* [Section A.5.1.2.c MIL-PRF-89041A] */ + private static Angle computeEquatorwardExtent(double equatorwardNominalBoundary, + double northSouthPixelConstant, double pixelRowsPerFrame) + { + double nsPixelsPerDegree = northSouthPixelConstant / 90d; + double degrees = Math.signum(equatorwardNominalBoundary) + * clamp((int) (nsPixelsPerDegree * Math.abs(equatorwardNominalBoundary) / pixelRowsPerFrame) + * pixelRowsPerFrame / nsPixelsPerDegree, 0, 90); + return Angle.fromDegrees(degrees); + } + + private static Angle computeLatitudinalFrameExtent(double polewardExtent, double equatorwardExtent, + double latitudinalFrames) + { + double degrees = Math.abs(polewardExtent - equatorwardExtent) / latitudinalFrames; + return Angle.fromDegrees(degrees); + } + + /* [Section 60.1.6 MIL-C-89038] */ + /* [Section A.5.1.3 MIL-PRF-89041A] */ + private static int computeLatitudinalFrames(double polewardExtent, double equatorwardExtent, + double northSouthPixelConstant, double pixelRowsPerFrame) + { + double nsPixelsPerDegree = northSouthPixelConstant / 90d; + double extent = Math.abs(polewardExtent - equatorwardExtent); + return (int) Math.rint(extent * nsPixelsPerDegree / pixelRowsPerFrame); + } + + public static Angle computeLongitudinalFrameExtent(double longitudinalFrames) + { + double degrees = 360d / longitudinalFrames; + return Angle.fromDegrees(degrees); + } + + /* [Section 60.1.7 MIL-C-89038] */ + /* [Section A.5.1.4 MIL-PRF-89041A] */ + private static int computeLongitudinalFrames(double eastWestPixelConstant, double pixelRowsPerFrame) + { + return (int) Math.ceil(eastWestPixelConstant / pixelRowsPerFrame); + } + + /* [Section 30.6 MIL-C-89038] */ + /* [Section A.3.6, MIL-PRF-89041A] */ + private static int computeMaximumFrameNumber(int latitudinalFrames, int longitudinalFrames) + { + return (latitudinalFrames * longitudinalFrames) - 1; + } + + private static int computeNorthSouthPixelConstant(double northSouthPixelSpacingConstant, + RpfDataSeries dataSeries) + { + if (dataSeries.rpfDataType.equalsIgnoreCase("CADRG")) + return computeNorthSouthPixelConstantCADRG(northSouthPixelSpacingConstant, dataSeries.scaleOrGSD); + else if (dataSeries.rpfDataType.equalsIgnoreCase("CIB")) + return computeNorthSouthPixelConstantCIB(northSouthPixelSpacingConstant, dataSeries.scaleOrGSD); + else + { + String message = WorldWind.retrieveErrMsg("RpfZone.UnknownRpfDataType") + dataSeries.rpfDataType; + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + } + + /* [Section 60.1.1 MIL-C-89038] */ + private static int computeNorthSouthPixelConstantCADRG(double northSouthPixelConstant, double scale) + { + double S = 1000000d / scale; + double tmp = northSouthPixelConstant * S; + tmp = 512d * (int) Math.ceil(tmp / 512d); + tmp /= 4d; + tmp /= (150d / 100d); + return 256 * (int) Math.round(tmp / 256d); + } + + /* [Section A.5.1.1, MIL-PRF-89041A] */ + private static int computeNorthSouthPixelConstantCIB(double northSouthPixelSpacingConstant, + double groundSampleDistance) + { + double S = 100d / groundSampleDistance; + double tmp = northSouthPixelSpacingConstant * S; + tmp = 512d * (int) Math.ceil(tmp / 512d); + tmp /= 4d; + return 256 * (int) Math.round(tmp / 256d); + } + + /* [Section 60.1.5.b MIL-C-89038] */ + /* [Section A.5.1.2.b MIL-PRF-89041A] */ + private static Angle computePolewardExtent(double polewardNominalBoundary, double northSouthPixelConstant, + double pixelRowsPerFrame) + { + double nsPixelsPerDegree = northSouthPixelConstant / 90d; + double degrees = Math.signum(polewardNominalBoundary) + * clamp(Math.ceil(nsPixelsPerDegree * Math.abs(polewardNominalBoundary) / pixelRowsPerFrame) + * pixelRowsPerFrame / nsPixelsPerDegree, 0, 90); + return Angle.fromDegrees(degrees); + } + + private static Sector computeZoneExtent(double equatorwardExtent, double polewardExtent) + { + double minLatitude, maxLatitude; + if (equatorwardExtent < polewardExtent) + { + minLatitude = equatorwardExtent; + maxLatitude = polewardExtent; + } + else + { + minLatitude = polewardExtent; + maxLatitude = equatorwardExtent; + } + return Sector.fromDegrees(minLatitude, maxLatitude, -180, 180); + } + + // ============== Varying Zone Computations ======================= // + // ============== Varying Zone Computations ======================= // + // ============== Varying Zone Computations ======================= // + + /* [Section 30.6 MIL-C-89038] */ + /* [Section A.3.6, MIL-PRF-89041A] */ + + public int frameColumnFromFrameNumber(int frameNumber) + { + int row = this.frameRowFromFrameNumber(frameNumber); + return frameNumber - row * this.longitudinalFrames; + } + + /* [Section 30.3.2 MIL-C-89038] */ + /* [Section A.3.3.2, MIL-PRF-89041A] */ + public int frameColumnFromLongitude(Angle longitude) + { + if (longitude == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + return (int) (((longitude.degrees + 180d) / 360d) + * (this.eastWestPixelConstant / (double) PixelRowsPerFrame)); + } + + public Sector frameExtent(int frameNumber) + { + Angle latOrigin = latitudinalFrameOrigin(this.frameRowFromFrameNumber(frameNumber)); + Angle lonOrigin = longitudinalFrameOrigin(this.frameColumnFromFrameNumber(frameNumber)); + return new Sector( + latOrigin.subtract(this.latitudinalFrameExtent), latOrigin, + lonOrigin, lonOrigin.add(this.longitudinalFrameExtent)); + } + + /* [Section 30.6 MIL-C-89038] */ + /* [Section A.3.6, MIL-PRF-89041A] */ + public int frameNumber(int row, int column) + { + return column + row * this.longitudinalFrames; + } + + /* [Section 30.6 MIL-C-89038] */ + /* [Section A.3.6, MIL-PRF-89041A] */ + public int frameRowFromFrameNumber(int frameNumber) + { + return (int) (frameNumber / (double) this.longitudinalFrames); + } + + /* [Section 30.3.1 MIL-C-89038] */ + /* [Section A.3.3.1, MIL-PRF-89041A] */ + public int frameRowFromLatitude(Angle latitude) + { + if (latitude == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + return (int) (((latitude.degrees - this.equatorwardExtent.degrees) / 90d) + * (this.northSouthPixelConstant / (double) PixelRowsPerFrame)); + } + + /* [Section 30.3.1 MIL-C-89038] */ + /* [Section A.3.3.1, MIL-PRF-89041A] */ + public Angle latitudinalFrameOrigin(int row) + { + double degrees = (90d / (double) this.northSouthPixelConstant) * ((double) PixelRowsPerFrame) * (row + 1) + + this.equatorwardExtent.degrees; + return Angle.fromDegrees(degrees); + } + + /* [Section 30.3.2 MIL-C-89038] */ + /* [Section A.3.3.2, MIL-PRF-89041A] */ + public Angle longitudinalFrameOrigin(int column) + { + double degrees = (360d / (double) this.eastWestPixelConstant) * ((double) PixelRowsPerFrame) + * column - 180d; + return Angle.fromDegrees(degrees); + } + } + + public static final int PixelRowsPerFrame = 1536; + public static final int SubframeRowsPerFrame = 6; + public final int eastWestPixelSpacingConstant; + public final int equatorwardNominalBoundary; + public final int northSouthPixelSpacingConstant; + public final int polewardNominalBoundary; + public final Character zoneCode; + + private RpfZone(Character zoneCode, int eastWestPixelSpacingConstant, int northSouthPixelSpacingConstant, + int equatorwardNominalBoundary, int polewardNominalBoundary) + { + this.zoneCode = zoneCode; + this.eastWestPixelSpacingConstant = eastWestPixelSpacingConstant; + this.northSouthPixelSpacingConstant = northSouthPixelSpacingConstant; + this.equatorwardNominalBoundary = equatorwardNominalBoundary; + this.polewardNominalBoundary = polewardNominalBoundary; + } + + private static RpfZone[] enumConstantAlphabet = null; + + private static RpfZone[] enumConstantAlphabet() + { + if (enumConstantAlphabet == null) + { + RpfZone[] universe = RpfZone.class.getEnumConstants(); + enumConstantAlphabet = new RpfZone[36]; + for (RpfZone zone : universe) + { + enumConstantAlphabet[indexFor(zone.zoneCode)] = zone; + } + } + return enumConstantAlphabet; + } + + private static int indexFor(Character zoneCode) + { + if (zoneCode >= '0' && zoneCode <= '9') + return zoneCode - '0'; + else if (zoneCode >= 'A' && zoneCode <= 'Z') + return 10 + zoneCode - 'A'; + return -1; + } + + private static RpfZone[] northernHemisphereZones = null; + + private static RpfZone[] northernHemisphereZones() + { + if (northernHemisphereZones == null) + { + RpfZone[] universe = RpfZone.class.getEnumConstants(); + northernHemisphereZones = new RpfZone[universe.length / 2]; + int index = 0; + for (RpfZone zone : universe) + { + if (zone.polewardNominalBoundary >= 0) + northernHemisphereZones[index++] = zone; + } + } + return northernHemisphereZones; + } + + private static RpfZone[] southernHemisphereZones = null; + + private static RpfZone[] southernHemisphereZones() + { + if (southernHemisphereZones == null) + { + RpfZone[] universe = RpfZone.class.getEnumConstants(); + southernHemisphereZones = new RpfZone[universe.length / 2]; + int index = 0; + for (RpfZone zone : universe) + { + if (zone.polewardNominalBoundary < 0) + southernHemisphereZones[index++] = zone; + } + } + return southernHemisphereZones; + } + + public static RpfZone zoneFor(Character zoneCode) + { + if (zoneCode == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.CharacterIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + RpfZone zone; + RpfZone[] alphabet = enumConstantAlphabet(); + int index = indexFor(Character.toUpperCase(zoneCode)); + if (index < 0 || index >= alphabet.length || (zone = alphabet[index]) == null) + { + String message = WorldWind.retrieveErrMsg("generic.EnumNotFound") + zoneCode; + WorldWind.logger().log(FINE, message); + throw new EnumConstantNotPresentException(RpfZone.class, message); + } + return zone; + } + + public static RpfZone zoneFor(RpfDataSeries dataSeries, Angle latitude) + { + if (latitude == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(FINE, message); + throw new IllegalArgumentException(message); + } + double latDegrees = latitude.degrees; + if (latDegrees < 0) + return zoneForSouthernHemisphere(dataSeries, latDegrees); + else + return zoneForNorthernHemisphere(dataSeries, latDegrees); + } + + private static RpfZone zoneForNorthernHemisphere(RpfDataSeries dataSeries, double latitude) + { + for (RpfZone zone : northernHemisphereZones()) + { + ZoneValues values = zone.zoneValues(dataSeries); + if (latitude >= values.equatorwardExtent.degrees && latitude <= values.polewardExtent.degrees) + return zone; + } + return null; + } + + private static RpfZone zoneForSouthernHemisphere(RpfDataSeries dataSeries, double latitude) + { + for (RpfZone zone : southernHemisphereZones()) + { + ZoneValues values = zone.zoneValues(dataSeries); + if (latitude <= values.equatorwardExtent.degrees && latitude >= values.polewardExtent.degrees) + return zone; + } + return null; + } + + public RpfZone.ZoneValues zoneValues(RpfDataSeries dataSeries) + { + RpfZone.ZoneKey key = new RpfZone.ZoneKey(this, dataSeries); + RpfZone.ZoneValues value = zoneValuesDirectory().get(key); + if (value == null) + { + value = new RpfZone.ZoneValues(this, dataSeries); + zoneValuesDirectory().put(key, value); + } + return value; + } + + private static Map zoneValuesDirectory = null; + + private static Map zoneValuesDirectory() + { + if (zoneValuesDirectory == null) + zoneValuesDirectory = new HashMap(); + return zoneValuesDirectory; + } +} diff --git a/gov/nasa/worldwind/geom/Angle.java b/gov/nasa/worldwind/geom/Angle.java new file mode 100644 index 0000000..a6690fb --- /dev/null +++ b/gov/nasa/worldwind/geom/Angle.java @@ -0,0 +1,463 @@ +/* +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 geometric angle. Instances of Angle are immutable. An Angle can be obtained + * through the factory methods fromDegrees and fromRadians. + * + * @author Tom Gaskins + * @version $Id: Angle.java 1645 2007-04-27 00:16:09Z tgaskins $ + */ +public class Angle implements Comparable +{ + /** + * Represents an angle of zero degrees + */ + public final static Angle ZERO = Angle.fromDegrees(0); + + /** + * Represents a right angle of positive 90 degrees + */ + public final static Angle POS90 = Angle.fromDegrees(90); + + /** + * Represents a right angle of negative 90 degrees + */ + public final static Angle NEG90 = Angle.fromDegrees(-90); + + /** + * Represents an angle of positive 180 degrees + */ + public final static Angle POS180 = Angle.fromDegrees(180); + + /** + * Represents an angle of negative 180 degrees + */ + public final static Angle NEG180 = Angle.fromDegrees(-180); + + /** + * Represents an angle of positive 360 degrees + */ + public final static Angle POS360 = Angle.fromDegrees(360); + + private final static double DEGREES_TO_RADIANS = Math.PI / 180d; + private final static double RADIANS_TO_DEGREES = 180d / Math.PI; + + /** + * Obtains an Angle from a specified number of degrees. + * + * @param degrees the size in degrees of the Angle to be obtained + * @return a new Angle, whose size in degrees is given by degrees + */ + public static Angle fromDegrees(double degrees) + { + return new Angle(degrees, DEGREES_TO_RADIANS * degrees); + } + + /** + * Obtains an Angle from a specified number of radians. + * + * @param radians the size in radians of the Angle to be obtained + * @return a new Angle, whose size in radians is given by radians + */ + public static Angle fromRadians(double radians) + { + return new Angle(RADIANS_TO_DEGREES * radians, radians); + } + + private static final double PIOver2 = Math.PI / 2; + + public static Angle fromDegreesLatitude(double degrees) + { + degrees = degrees < -90 ? -90 : degrees > 90 ? 90 : degrees; + double radians = DEGREES_TO_RADIANS * degrees; + radians = radians < -PIOver2 ? -PIOver2 : radians > PIOver2 ? PIOver2 : radians; + + return new Angle(degrees, radians); + } + + public static Angle fromRadiansLatitude(double radians) + { + radians = radians < -PIOver2 ? -PIOver2 : radians > PIOver2 ? PIOver2 : radians; + double degrees = RADIANS_TO_DEGREES * radians; + degrees = degrees < -90 ? -90 : degrees > 90 ? 90 : degrees; + + return new Angle(degrees, radians); + } + + public static Angle fromDegreesLongitude(double degrees) + { + degrees = degrees < -180 ? -180 : degrees > 180 ? 180 : degrees; + double radians = DEGREES_TO_RADIANS * degrees; + radians = radians < -Math.PI ? -Math.PI : radians > Math.PI ? Math.PI : radians; + + return new Angle(degrees, radians); + } + + public static Angle fromRadiansLongitude(double radians) + { + radians = radians < -Math.PI ? -Math.PI : radians > Math.PI ? Math.PI : radians; + double degrees = RADIANS_TO_DEGREES * radians; + degrees = degrees < -180 ? -180 : degrees > 180 ? 180 : degrees; + + return new Angle(degrees, radians); + } + + /** + * Obtains an Angle from rectangular coordinates. + * + * @param x the abscissa coordinate + * @param y the ordinate coordinate + * @return a new Angle, whose size is determined from x and y + */ + public static Angle fromXY(double x, double y) + { + double radians = Math.atan2(y, x); + return new Angle(RADIANS_TO_DEGREES * radians, radians); + } + + public final double degrees; + public final double radians; + + public Angle(Angle angle) + { + this.degrees = angle.degrees; + this.radians = angle.radians; + } +// +// private Angle(double degrees) +// { +// this.degrees = degrees; +// this.radians = DEGREES_TO_RADIANS * this.degrees; +// } + + private Angle(double degrees, double radians) + { + this.degrees = degrees; + this.radians = radians; + } + + /** + * Retrieves the size of this Angle in degrees. This method may be faster than first obtaining the + * radians and then converting to degrees. + * + * @return the size of this Angle in degrees + */ + public final double getDegrees() + { + return this.degrees; + } + + /** + * Retrieves the size of this Angle in radians. This may be useful for java.lang.Math + * functions, which generally take radians as trigonometric arguments. This method may be faster that first + * obtaining the degrees and then converting to radians. + * + * @return the size of this Angle in radians. + */ + public final double getRadians() + { + return this.radians; + } + + /** + * Obtains the sum of these two Angles. Does not accept a null argument. This method is commutative, so + * a.add(b) and b.add(a) are equivalent. Neither this Angle nor + * angle is changed, instead the result is returned as a new Angle. + * + * @param angle the Angle to add to this one. + * @return an Angle whose size is the total of this Angles and angles size + * @throws IllegalArgumentException if angle is null + */ + public final Angle add(Angle angle) + { + if (angle == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + return Angle.fromDegrees(this.degrees + angle.degrees); + } + + /** + * Obtains the difference of these two Angles. Does not accept a null argument. This method is not + * commutative. Neither this Angle nor angle is changed, instead the result is returned as + * a new Angle. + * + * @param angle the Angle to subtract from this Angle + * @return a new Angle correpsonding to this Angle's size minus angle's size + * @throws IllegalArgumentException if angle is null + */ + public final Angle subtract(Angle angle) + { + if (angle == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + return Angle.fromDegrees(this.degrees - angle.degrees); + } + + /** + * Multiplies this Angle by multiplier. This Angle remains unchanged. The + * result is returned as a new Angle. + * + * @param multiplier a scalar by which this Angle is multiplied + * @return a new Angle whose size equals this Angle's size multiplied by + * multiplier + */ + public final Angle multiply(double multiplier) + { + return Angle.fromDegrees(this.degrees * multiplier); + } + + /** + * Divides this Angle by another angle. This Angle remains unchanged, instead the + * resulting value in degrees is returned. + * + * @param angle the Angle by which to divide + * @return this Angle's degrees divided by angle's degrees + * @throws IllegalArgumentException if angle is null + */ + public final double divide(Angle angle) + { + if (angle == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + return this.degrees / angle.degrees; + } + + public final Angle addDegrees(double degrees) + { //Tom: this method is not used, should we delete it? (13th Dec 06) + return Angle.fromDegrees(this.degrees + degrees); + } + + public final Angle subtractDegrees(double degrees) + { //Tom: this method is not used, should we delete it? (13th Dec 06) + return Angle.fromDegrees(this.degrees - degrees); + } + + /** + * Divides this Angle by divisor. This Angle remains unchanged. The result is + * returned as a new Angle. Behaviour is undefined if divisor equals zero. + * + * @param divisor the number to be divided by + * @return a new Angle equivalent to this Angle divided by divisor + */ + public final Angle divide(double divisor) + { + return Angle.fromDegrees(this.degrees / divisor); + } + + public final Angle addRadians(double radians) + { + return Angle.fromRadians(this.radians + radians); + } + + public final Angle subtractRadians(double radians) + { + return Angle.fromRadians(this.radians - radians); + } + + /** + * Obtains the sine of this Angle. + * + * @return the trigonometric sine of this Angle + */ + public final double sin() + { + return Math.sin(this.radians); + } + + public final double sinHalfAngle() + { //Tom: this method is not used, should we delete it? (13th Dec 06) + return Math.sin(0.5 * this.radians); + } + + public static Angle asin(double sine) + { //Tom: this method is not used, should we delete it? (13th Dec 06) + return Angle.fromRadians(Math.asin(sine)); + } + + /** + * Obtains the cosine of this Angle + * + * @return the trigonometric cosine of this Angle + */ + public final double cos() + { + return Math.cos(this.radians); + } + + public final double cosHalfAngle() + { //Tom: this method is not used, should we delete it? (13th Dec 06) + return Math.cos(0.5 * this.radians); + } + + public static Angle acos(double cosine) + { //Tom: this method is not used, should we delete it? (13th Dec 06) + return Angle.fromRadians(Math.acos(cosine)); + } + + /** + * Obtains the tangent of half of this Angle. + * + * @return the trigonometric tangent of half of this Angle + */ + public final double tanHalfAngle() + { + return Math.tan(0.5 * this.radians); + } + + public static Angle atan(double tan) + { //Tom: this method is not used, should we delete it? (13th Dec 06) + return Angle.fromRadians(Math.atan(tan)); + } + + /** + * Obtains the average of two Angles. This method is commutative, so midAngle(m, n) and + * midAngle(n, m) are equivalent. + * + * @param a1 the first Angle + * @param a2 the second Angle + * @return the average of a1 and a2 throws IllegalArgumentException if either angle is + * null + */ + public static Angle midAngle(Angle a1, Angle a2) + { + if (a1 == null || a2 == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + return Angle.fromDegrees(0.5 * (a1.degrees + a2.degrees)); + } + + /** + * Obtains the average of three Angles. The order of parameters does not matter. + * + * @param a the first Angle + * @param b the second Angle + * @return the average of a1, a2 and a3 + * @throws IllegalArgumentException if a or b is null + */ + public static Angle average(Angle a, Angle b) + { + if (a == null || b == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + return Angle.fromDegrees(0.5 * (a.degrees + b.degrees)); + } + + /** + * Obtains the average of three Angles. The order of parameters does not matter. + * + * @param a the first Angle + * @param b the second Angle + * @param c the third Angle + * @return the average of a1, a2 and a3 + * @throws IllegalArgumentException if a, b or c is null + */ + public static Angle average(Angle a, Angle b, Angle c) + { + if (a == null || b == null || c == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + return Angle.fromDegrees((a.degrees + b.degrees + c.degrees) / 3); + } + + /** + * Compares this Angle with angle for order. Returns a negative integer if this is the + * smaller Angle, a positive integer if this is the larger, and zero if both Angles are + * equal. + * + * @param angle the Angle to compare against + * @return -1 if this Angle is smaller, 0 if both are equal and +1 if this Angle is + * larger. + * @throws IllegalArgumentException if angle is null + */ + public final int compareTo(Angle angle) + { + if (angle == null) + { + String msg = WorldWind.retrieveErrMsg("nullValue.AngleIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, msg); + throw new IllegalArgumentException(msg); + } + + if (this.degrees < angle.degrees) + return -1; + + if (this.degrees > angle.degrees) + return 1; + + return 0; + } + + /** + * Obtains a String representation of this Angle. + * + * @return the value of this Angle in degrees and as a String + */ + @Override + public final String toString() + { + return Double.toString(this.degrees) + '\u00B0'; + } + + /** + * Obtains the amount of memory this Angle consumes. + * + * @return the memory footprint of this Angle in bytes. + */ + public long getSizeInBytes() + { + return Double.SIZE; + } + + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Angle angle = (Angle) o; + + if (angle.degrees!= this.degrees) + return false; + + return true; + } + + public int hashCode() + { + long temp = degrees != +0.0d ? Double.doubleToLongBits(degrees) : 0L; + return (int) (temp ^ (temp >>> 32)); + } +} diff --git a/gov/nasa/worldwind/geom/Cylinder.java b/gov/nasa/worldwind/geom/Cylinder.java new file mode 100644 index 0000000..349c2e2 --- /dev/null +++ b/gov/nasa/worldwind/geom/Cylinder.java @@ -0,0 +1,308 @@ +/* +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 javax.media.opengl.*; +import javax.media.opengl.glu.*; + +/** + * Represents a geometric cylinder. Cylinders are immutable. + * + * @author Tom Gaskins + * @version $Id: Cylinder.java 1749 2007-05-06 19:48:14Z tgaskins $ + */ +public class Cylinder implements Extent, Renderable +{ + private final Point bottomCenter; // point at center of cylinder base + private final Point topCenter; // point at center of cylinder top + private final Point axisUnitDirection; // axis as unit vector from bottomCenter to topCenter + private final double cylinderRadius; + private final double cylinderHeight; + + /** + * Create a Cylinder from two points and a radius. Does not accept null arguments. + * + * @param bottomCenter represents the centrepoint of the base disc of the Cylinder + * @param topCenter represents the centrepoint of the top disc of the Cylinder + * @param cylinderRadius the radius of the Cylinder + * @throws IllegalArgumentException if either the top or bottom point is null + */ + public Cylinder(Point bottomCenter, Point topCenter, double cylinderRadius) + { + if (bottomCenter == null || topCenter == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.EndPointIsNull"); + + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + if (cylinderRadius <= 0) + { + String message = WorldWind.retrieveErrMsg("geom.Cylinder.RadiusIsZeroOrNegative"); + + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + this.bottomCenter = bottomCenter; + this.topCenter = topCenter; + this.cylinderHeight = this.bottomCenter.distanceTo(this.topCenter); + this.cylinderRadius = cylinderRadius; + this.axisUnitDirection = this.topCenter.subtract(this.bottomCenter).normalize(); + } + + public String toString() + { + return this.cylinderRadius + ", " + this.bottomCenter.toString() + ", " + this.topCenter.toString() + ", " + + this.axisUnitDirection.toString(); + } + + public Intersection[] intersect(Line line) // TODO: test this method + { + if (line == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.LineIsNull"); + + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + + Point ld = line.getDirection(); + Point lo = line.getOrigin(); + double a = ld.x() * ld.x() + ld.y() * ld.y(); + double b = 2 * (lo.x() * ld.x() + lo.y() * ld.y()); + double c = lo.x() * lo.x() + lo.y() * lo.y() - this.cylinderRadius * this.cylinderRadius; + + double discriminant = Cylinder.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)); + boolean n = false, f = false; + boolean nTangent = false, fTangent = false; + if (near.z() >= 0 && near.z() <= this.getHeight()) + { + n = true; + nTangent = near.z() == 0; + } + if (far.z() >= 0 && far.z() <= this.getHeight()) + { + f = true; + fTangent = far.z() == 0; + } + + // TODO: Test for intersection with planes at cylinder's top and bottom + + Intersection[] intersections = null; + if (n && f) + intersections = new Intersection[] {new Intersection(near, nTangent), new Intersection(far, fTangent)}; + else if (n) + intersections = new Intersection[] {new Intersection(near, nTangent)}; + else if (f) + intersections = new Intersection[] {new Intersection(far, fTangent)}; + + return intersections; + } + } + + public 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); + } + + Point ld = line.getDirection(); + Point lo = line.getOrigin(); + double a = ld.x() * ld.x() + ld.y() * ld.y(); + double b = 2 * (lo.x() * ld.x() + lo.y() * ld.y()); + double c = lo.x() * lo.x() + lo.y() * lo.y() - this.cylinderRadius * this.cylinderRadius; + + double discriminant = Cylinder.discriminant(a, b, c); + + return discriminant >= 0; + } + + static private double discriminant(double a, double b, double c) + { + return b * b - 4 * a * c; + } + + private double intersectsAt(Plane plane, double effectiveRadius, double parameter) + { + // Test the distance from the first cylinder end-point. + double dq1 = plane.dot(this.bottomCenter); + boolean bq1 = dq1 <= -effectiveRadius; + + // Test the distance from the possibly reduced second cylinder end-point. + Point newTop; + if (parameter < 1) + newTop = this.bottomCenter.add(this.topCenter.subtract(this.bottomCenter).multiply(parameter)); + else + newTop = this.topCenter; + double dq2 = plane.dot(newTop); + boolean bq2 = dq2 <= -effectiveRadius; + + if (bq1 && bq2) // both <= effective radius; cylinder is on negative side of plane + return -1; + + if (bq1 == bq2) // both >= effective radius; can't draw any conclusions + return parameter; + + // Compute and return the parameter value at which the plane intersects the cylinder's axis. + return (effectiveRadius + plane.dot(this.bottomCenter)) + / plane.getNormal().dot(this.bottomCenter.subtract(newTop)); + } + + private double getEffectiveRadius(Plane plane) + { + // Determine the effective radius of the cylinder axis relative to the plane. + double dot = plane.getNormal().dot(this.axisUnitDirection); + double scale = 1d - dot * dot; + if (scale <= 0) + return 0; + else + return this.cylinderRadius * Math.sqrt(scale); + } + + public boolean intersects(Plane plane) + { + if (plane == null) + { + String message = WorldWind.retrieveErrMsg("nullValue.PlaneIsNull"); + WorldWind.logger().log(java.util.logging.Level.FINE, message); + throw new IllegalArgumentException(message); + } + double effectiveRadius = this.getEffectiveRadius(plane); + double intersectionPoint = this.intersectsAt(plane, effectiveRadius, 1d); + return intersectionPoint >= 0; + } + + 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); + } + + double intersectionPoint; + + double effectiveRadius = this.getEffectiveRadius(frustum.getNear()); + intersectionPoint = this.intersectsAt(frustum.getNear(), effectiveRadius, 1d); + if (intersectionPoint < 0) + return false; + + // Near and far have the same effective radius. + intersectionPoint = this.intersectsAt(frustum.getFar(), effectiveRadius, intersectionPoint); + if (intersectionPoint < 0) + return false; + + effectiveRadius = this.getEffectiveRadius(frustum.getLeft()); + intersectionPoint = this.intersectsAt(frustum.getLeft(), effectiveRadius, intersectionPoint); + if (intersectionPoint < 0) + return false; + + effectiveRadius = this.getEffectiveRadius(frustum.getRight()); + intersectionPoint = this.intersectsAt(frustum.getRight(), effectiveRadius, intersectionPoint); + if (intersectionPoint < 0) + return false; + + effectiveRadius = this.getEffectiveRadius(frustum.getTop()); + intersectionPoint = this.intersectsAt(frustum.getTop(), effectiveRadius, intersectionPoint); + if (intersectionPoint < 0) + return false; + + effectiveRadius = this.getEffectiveRadius(frustum.getBottom()); + intersectionPoint = this.intersectsAt(frustum.getBottom(), effectiveRadius, intersectionPoint); + return intersectionPoint >= 0; + } + + public Point getCenter() + { + Point b = this.bottomCenter; + Point t = this.topCenter; + return new Point(0.5 * (b.x() + t.x()), 0.5 * (b.y() + t.y()), 0.5 * (b.z() + t.z())); + } + + public double getDiameter() + { + return 2 * this.getRadius(); + } + + public double getRadius() + { + // return the radius of the enclosing sphere + double halfHeight = 0.5 * this.bottomCenter.distanceTo(this.topCenter); + return Math.sqrt(halfHeight * halfHeight + this.cylinderRadius * this.cylinderRadius); + } + + /** + * Obtain the height of this Cylinder. + * + * @return the distance between the bottom and top of this Cylinder + */ + public final double getHeight() + { + return this.cylinderHeight; + } + + 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); + } + + Point center = this.getCenter(); + PolarPoint p = PolarPoint.fromCartesian(center); + + javax.media.opengl.GL gl = dc.getGL(); + + gl.glPushAttrib(GL.GL_ENABLE_BIT | GL.GL_TRANSFORM_BIT); + + gl.glBegin(javax.media.opengl.GL.GL_LINES); + gl.glVertex3d(this.bottomCenter.x(), this.bottomCenter.y(), this.bottomCenter.z()); + gl.glVertex3d(this.topCenter.x(), this.topCenter.y(), this.topCenter.z()); + gl.glEnd(); + + gl.glEnable(javax.media.opengl.GL.GL_DEPTH_TEST); + gl.glMatrixMode(javax.media.opengl.GL.GL_MODELVIEW); + gl.glPushMatrix(); + gl.glTranslated(this.bottomCenter.x(), this.bottomCenter.y(), this.bottomCenter.z()); + dc.getGL().glRotated(p.getLongitude().getDegrees(), 0, 1, 0); + dc.getGL().glRotated(Math.abs(p.getLatitude().getDegrees()), Math.signum(p.getLatitude().getDegrees()) * -1, + 0, 0); + + GLUquadric quadric = dc.getGLU().gluNewQuadric(); + dc.getGLU().gluQuadricDrawStyle(quadric, GLU.GLU_LINE); + dc.getGLU().gluCylinder(quadric, this.cylinderRadius, this.cylinderRadius, this.cylinderHeight, 30, 30); + dc.getGLU().gluDeleteQuadric(quadric); + + gl.glPopMatrix(); + gl.glPopAttrib(); + } +} diff --git a/gov/nasa/worldwind/geom/Extent.java b/gov/nasa/worldwind/geom/Extent.java new file mode 100644 index 0000000..199b3f8 --- /dev/null +++ b/gov/nasa/worldwind/geom/Extent.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.geom; + +/** + * Represents an enclosing volume. + * + * @author Tom Gaskins + * @version $Id: Extent.java 1416 2007-04-06 23:44:19Z tgaskins $ + */ +public interface Extent +{ + Point getCenter(); + + double getDiameter(); + + double getRadius(); + + /** + * Tests whether or not this Extent intersects frustum. Returns true if any part of these + * two objects intersect, including the case where either object wholly contains the other, false otherwise. + * + * @param frustum the Frustum with which to test for intersection + * @return true if there is an intersection, false otherwise + */ + boolean intersects(Frustum frustum); + + /** + * Obtain the intersections of this extent with line. The returned array may be either null or of zero + * length if no intersections are discovered. It does not contain null elements. 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 Extent + * @return an array of intersections representing all the points where line enters or leave this + * Extent + */ + gov.nasa.worldwind.geom.Intersection[] intersect(gov.nasa.worldwind.geom.Line line); + + /** + * Calculate whether or not line intersects this Extent. This method may be faster than + * checking the size of the arary returned by intersect(Line). Implementing methods must ensure that + * this method returns true if and only if intersect(Line) returns a non-null array containing at least + * one element. + * + * @param line the Line with which to test for intersection + * @return true if an intersection is found, false otherwise + */ + boolean intersects(gov.nasa.worldwind.geom.Line line); + + /** + * Calculate whether or not this Extent is intersected by plane. + * + * @param plane the Plane with which to test for intersection + * @return true if plane is found to intersect this Extent + */ + boolean intersects(gov.nasa.worldwind.geom.Plane plane); +} diff --git a/gov/nasa/worldwind/geom/Frustum.java b/gov/nasa/worldwind/geom/Frustum.java new file mode 100644 index 0000000..7c817d0 --- /dev/null +++ b/gov/nasa/worldwind/geom/Frustum.java @@ -0,0 +1,268 @@ +/* +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.*; + +//todo: check javadoc accuracy, + +/** + * Instances of Frustum are immutable.

+ * + * @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 Planes. 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 Planes, 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 Points. 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 Points. 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 Points + * @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 Points + * @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 Points. + * + * @param p1 the first Point + * @param p2 the second Point + * @return the midpoint of these two Points + * @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 Points. 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 Points 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 Points + * @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 Points. This is post multiplied by that. + * + * @param that the second Point + * @return the cross product of two Points + * @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 Points to be enclosed by the new Sphere + * @return a Sphere encompassing the given array of Points + * @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