All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.ogc.kml.KMLRegion Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gov.nasa.worldwind.ogc.kml;

import gov.nasa.worldwind.*;
import gov.nasa.worldwind.cache.ShapeDataCache;
import gov.nasa.worldwind.event.Message;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.ogc.kml.impl.*;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.util.Logging;

import java.util.*;

/**
 * Represents the KML Region element and provides access to its contents. Regions define an area of interest
 * described by a geographic bounding box and an optional minimum and maximum altitude.
 * 

* Bounding Box
A Region's bounding box controls when the Region is active by defining a volume * that must intersect the viewing frustum. The bounding box is computed according to the altitudeMode * attribute of a Region's geographic LatLonAltBox as follows: *

*

  • clampToGround (default): The bounding box encloses the terrain surface in the sector * defined by the north, south, east, and west limits of this Region's LatLonAltBox.
  • *
  • relativeToGround: The bounding box encloses the volume in the sector defined by the north, * south, east, and west limits of the Region's LatLonAltBox, and who's upper and lower altitude are * specified by its minAltitude and maxAltitude, relative to ground level.
  • absolute: The * bounding box encloses the volume in the sector defined by the north, south, east, and west limits of the Region's * LatLonAltBox, and who's upper and lower altitude are specified by its minAltitude and maxAltitude, * relative to mean sea level.
*

* Level of Detail
A Region's level of detail determines when it is active by defining an upper * and lower boundary on the Region's screen area or the Region's distance to the view. The level of detail is computed * according to the altitudeMode attribute of a Region's geographic LatLonAltBox as follows: *

*

  • clampToGround (default): The level of detail is determined by computing the distance from * the eye point and Region sector, scaling the distance by the KMLTraversalContext's detail hint, then * comparing that scaled distance to the Region's min and max pixel sizes in meters (the Region sector's area divided by * minLodPixels and maxLodPixels, respectively). The Region is active when the scaled distance * is less than or equal to the min pixel size in meters and greater than the max pixel size in meters. The detail hint * may be specified by calling setDetailHint on the top level KMLRoot (the KMLRoot loaded by * the application).
  • relativeToGround: The level of detail is determined by computing the * number of pixels the Region occupies on screen, and comparing that pixel count to the Region's * minLodPixels and maxLodPixels. The Region is active when the pixel count is greater or * equal to minLodPixels and less than maxLodPixels.
  • absolute: The * level of detail is determined by computing the number of pixels the Region occupies on screen, and comparing that * pixel count to the Region's minLodPixels and maxLodPixels. The Region is active when the * pixel count is greater or equal to minLodPixels and less than maxLodPixels.
*

* In order to prevent Regions with adjacent level of detail ranges from activating at the same time, Region gives * priority to higher level of detail ranges. For example, suppose that two KML features representing different detail * levels of a Collada model have Regions with LOD range 100-200 and 200-300. Region avoids activating both features in * the event that both their level of detail criteria are met by giving priority to the second range: 200-300. *

* KML Feature Hierarchies
When a Region is attached to a KML feature, the feature and its * descendants are displayed only when the Region is active. A Region is active when its bounding box is in view and its * level of detail criteria are met. Region provides the isActive method for determining if a Region is * active for a specified DrawContext. *

* Regions do not apply directly to KML containers, because a descendant feature can override the container's Region * with its own Region. If a feature does not specify a Region it inherits the Region of its nearest ancestor. Since a * child feature's Region may be larger or have a less restrictive level of detail range than its ancestor's Region, the * visibility of an entire KML feature tree cannot be determined based on a container's Region. Instead, visibility must * be determined at each leaf feature. *

* Limitations
The Region bounding box must lie between -90 to 90 degrees latitude, and -180 to * 180 degrees longitude. Regions that span the date line are currently not supported. * * @author tag * @version $Id: KMLRegion.java 2029 2014-05-23 21:22:23Z pabercrombie $ */ public class KMLRegion extends KMLAbstractObject { /** * The default time in milliseconds a RegionData element may exist in this Region's * regionDataCache before it must be regenerated: 6 seconds. */ protected static final int DEFAULT_DATA_GENERATION_INTERVAL = 6000; /** * The default time in milliseconds a RegionData element may exist in this Region's * regionDataCache without being used before it is evicted: 1 minute. */ protected static final int DEFAULT_UNUSED_DATA_LIFETIME = 60000; /** * The default value that configures KML scene resolution to screen resolution as the viewing distance changes: * 2.8. */ protected static final double DEFAULT_DETAIL_HINT_ORIGIN = 2.8; /** * RegionData holds a Region's computed data used during a single call to Region.isActive, * and is unique to a particular Globe. *

* RegionData entries are places in a Region's regionDataCache, and are retrieved during each call to * isActive using the current Globe as the cache key. RegionData's elements depend on the * Globe's ElevationModel, and therefore cannot be permanently cached. Each RegionData * entry is valid for a random amount of time between its minExpiryTime and its * maxExpiryTime, after which it must be regenerated. The time is randomized to amortize the cost of * regenerating data for multiple Regions over multiple frames. *

* isActive
RegionData's isActive property indicates whether the Region * associated with a RegionData entry is active. This is used to share the result of computing isActive * among multiple calls during the same frame. For example, the preRender and render passes need not each compute * isActive, and can therefore share the same computation by ensuring that this property is set at most * once per frame. Callers determine when to recompute isActive by comparing the * DrawContext's current frame number against the RegionData's activeFrameNumber. This * property is accessed by calling isActive and setActive. *

* extent
RegionData's extent property is an Extent used to * determine if a Region's bounding box is in view. This property is accessed by calling getExtent and * setExtent. May be null. *

* sector
RegionData's sector property is a Sector used to * determine if Regions with an altitudeMode of clampToGround are in view. Accessed by * calling getSector and setSector. When a Region's altitudeMode is * clampToGround, the Region's sector can be used to determine visibility because the Region is defined * to be on the Globe's surface. *

* points
RegionData's points property indicates a list of model-coordinate * points representing the corners and interior of the Region. These points are used to determine the distance * between the Region and the View's eye point. If the Region has altitude mode of * clampToGround, this list must contain five points: the model-coordinate points of the Region's four * corners and center point on the surface terrain. */ protected static class RegionData extends ShapeDataCache.ShapeDataCacheEntry { /** Identifies the frame used to calculate this entry's values. Initially -1. */ protected long frameNumber = -1; /** Identifies the frame used to determine if this entry's Region is active. Initially -1. */ protected long activeFrameNumber = -1; /** Identifies whether this entry's Region is active. Initially false. */ protected boolean isActive; /** * Indicates the vertical datum against which the altitudes values in this entry's Region are interpreted. One * of WorldWind.ABSOLUTE, WorldWind.CLAMP_TO_GROUND, or * WorldWind.RELATIVE_TO_GROUND. Initially -1. */ protected int altitudeMode = -1; /** * Indicates the Sector used to determine if a Region who's altitudeMode is * clampToGround is visible. Initially null. */ protected Sector sector; /** * Indicates the model-coordinate points representing the corners and interior of this entry's Region. These * points are used to determine the distance between this entry's Region and the View's eye point. * Initially null. */ protected List points; /** * Constructs a new RegionData entry from the Globe and vertical exaggeration of a * specified draw context. * * @param dc the draw context. Must contain a Globe. * @param minExpiryTime the minimum expiration duration, in milliseconds. * @param maxExpiryTime the maximum expiration duration, in milliseconds. */ public RegionData(DrawContext dc, long minExpiryTime, long maxExpiryTime) { super(dc, minExpiryTime, maxExpiryTime); } /** * Identifies the frame used to calculate this entry's values. * * @return the frame used to calculate this entry's values. */ public long getFrameNumber() { return frameNumber; } /** * Specifies the frame used to calculate this entry's values. * * @param frameNumber the frame used to calculate this entry's values. */ public void setFrameNumber(long frameNumber) { this.frameNumber = frameNumber; } /** * Identifies the frame used to determine if this entry's Region is active. * * @return the frame used to determine if this entry's Region is active. */ public long getActiveFrameNumber() { return activeFrameNumber; } /** * Specifies the frame used to determine if this entry's Region is active. * * @param frameNumber the frame used to determine if this entry's Region is active. */ public void setActiveFrameNumber(long frameNumber) { this.activeFrameNumber = frameNumber; } /** * Indicates whether this entry's Region is active. * * @return true if this entry's Region is active, otherwise false. */ public boolean isActive() { return this.isActive; } /** * Specifies whether this entry's Region is active. * * @param active true to specify that this entry's Region is active, otherwise false. */ public void setActive(boolean active) { this.isActive = active; } /** * Indicates the vertical datum against which the altitudes values in this entry's Region are interpreted. * * @return the altitude mode of this entry's Region. One of WorldWind.ABSOLUTE, * WorldWind.CLAMP_TO_GROUND, or WorldWind.RELATIVE_TO_GROUND. */ public int getAltitudeMode() { return this.altitudeMode; } /** * Specifies the vertical datum against which the altitudes values in this entry's Region should be interpreted. * Must be one of WorldWind.ABSOLUTE, WorldWind.CLAMP_TO_GROUND, or * WorldWind.RELATIVE_TO_GROUND. * * @param altitudeMode the vertical datum to use. */ public void setAltitudeMode(int altitudeMode) { this.altitudeMode = altitudeMode; } /** * Indicates the Sector used to determine if a Region who's altitudeMode is * clampToGround is visible. This returns null if this entry's Region's has no * geographic bounding box. * * @return the Sector used to determine if a Region is visible, or null to specify * that this entry's Region has no bounding box. */ public Sector getSector() { return this.sector; } /** * Specifies the Sector that defines a Region's surface sector on the Globe. Specify * null to indicate that this entry' Region has no geographic bounding box. * * @param sector the Sector that is used to determine if a clampToGround Region is * visible, or null to specify that the entry's Region's has no bounding box. */ public void setSector(Sector sector) { this.sector = sector; } /** * Indicates the model-coordinate points representing the corners and interior of this entry's Region. This * returns null if this entry's Region has no geographic bounding box. * * @return the points representing the corners and interior of this entry's Region, or null if the * Region has no bounding box. */ public List getPoints() { return this.points; } /** * Specifies the model-coordinate points representing the corners and interior of this entry's Region. These * points are used to determine the distance between this entry's Region and the View's eye point. * Specify null to indicate that this entry' Region has no geographic bounding box. *

* If this entry's Region has altitude mode clampToGround, this list must contain five points: the * model-coordinate points of the Region's four corners and center point on the surface terrain. * * @param points the points representing the corners and interior of this entry's Region, or null * to specify that this entry's Region has no bounding box. */ public void setPoints(List points) { this.points = points; } } /** * The maximum lifespan of this Region's computed data, in milliseconds. Initialized to * DEFAULT_DATA_GENERATION_INTERVAL. */ protected long maxExpiryTime = DEFAULT_DATA_GENERATION_INTERVAL; /** * The minimum lifespan of this Region's computed data, in milliseconds. Initialized to * DEFAULT_DATA_GENERATION_INTERVAL - 1000. */ protected long minExpiryTime = Math.max(DEFAULT_DATA_GENERATION_INTERVAL - 1000, 0); /** * Holds globe-dependent computed Region data. One entry per globe encountered during isActive. * Initialized to a new ShapeDataCache with maxTimeSinceLastUsed set to * DEFAULT_UNUSED_DATA_LIFETIME. */ protected ShapeDataCache regionDataCache = new ShapeDataCache(DEFAULT_UNUSED_DATA_LIFETIME); /** * Identifies the active globe-dependent data for the current invocation of isActive. The active data * is drawn from the regionDataCache at the beginning of the isActive method. */ protected RegionData currentData; /** * The default value that configures KML scene resolution to screen resolution as the viewing distance changes. The * KMLRoot's detail hint specifies deviations from this default. Initially * DEFAULT_DETAIL_HINT_ORIGIN. */ protected double detailHintOrigin = DEFAULT_DETAIL_HINT_ORIGIN; /** * Creates a new KMLRegion with the specified namespace URI, but otherwise does nothing. The new Region * has no bounding box and no level of detail range. * * @param namespaceURI the qualifying namespace URI. May be null to indicate no namespace * qualification. */ public KMLRegion(String namespaceURI) { super(namespaceURI); } /** * Indicates the bounding box that must be in view for this this Region to be considered active. May be * null. * * @return this Region's geographic bounding box, or null indicating that this Region has no bounding * box restriction. */ public KMLLatLonAltBox getLatLonAltBox() { return (KMLLatLonAltBox) this.getField("LatLonAltBox"); } /** * Indicates the level of detail criteria that must be satisfied for this Region to be considered active. May be * null. * * @return this Region's level of detail range, or null indicating that this Region has no level of * detail restriction. */ public KMLLod getLod() { return (KMLLod) this.getField("Lod"); } /** * Indicates whether this Region is active on the specified DrawContext. A Region is active if its * bounding box intersects the viewing frustum, and its level of detail criteria are met for the specified traversal * context and draw context. *

* This always returns true if this Region has no bounding box, or if its bounding box is in the * viewing frustum and this Region has no lod criteria. * * @param tc the current KML traversal context. * @param dc the DrawContext used to determine whether this Region is active. * * @return true if this Region is active; otherwise false. * * @throws IllegalArgumentException if either the KMLTraversalContext or the DrawContext * is null. */ public boolean isActive(KMLTraversalContext tc, DrawContext dc) { if (tc == null) { String message = Logging.getMessage("nullValue.TraversalContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.makeRegionData(dc); // Attempt to re-use the result of isActive during the preRender and render passes for the same Globe. These // calls use the same frustum to determine visibility and can therefore share the result of isActive. We // recompute isActive when the frame changes or when Globe changes, and return the computed value below. // Note that we use the same frustum intersection for both picking and rendering. We cannot cull against // the pick frustums because content (e.g. an open balloon) may extend beyond the region's bounding box. if (dc.getFrameTimeStamp() != this.getCurrentData().getActiveFrameNumber()) { this.getCurrentData().setActive(this.isRegionActive(tc, dc)); this.getCurrentData().setActiveFrameNumber(dc.getFrameTimeStamp()); } return this.getCurrentData().isActive(); } /** * Produces the data used to determine whether this Region is active for the specified DrawContext. * This attempts to re-use RegionData already been calculated this frame, or previously calculated * RegionData that is still valid and has not expired. This method is called by isActive * prior to determining if this Region is actually active. * * @param dc the current draw context. * * @see #isActive */ protected void makeRegionData(DrawContext dc) { // Retrieve the cached data for the current globe. If it doesn't yet exist, create it. Most code subsequently // executed depends on currentData being non-null. this.currentData = (RegionData) this.regionDataCache.getEntry(dc.getGlobe()); if (this.currentData == null) { this.currentData = this.createCacheEntry(dc); this.regionDataCache.addEntry(this.currentData); } // Re-use values already calculated this frame. if (dc.getFrameTimeStamp() != this.getCurrentData().getFrameNumber()) { // Regenerate the region and data at a specified frequency. if (this.mustRegenerateData(dc)) { this.doMakeRegionData(dc); this.getCurrentData().restartTimer(dc); this.getCurrentData().setGlobeStateKey(dc.getGlobe().getGlobeStateKey(dc)); this.getCurrentData().setVerticalExaggeration(dc.getVerticalExaggeration()); } this.getCurrentData().setFrameNumber(dc.getFrameTimeStamp()); } } /** * Returns the data cache entry for the current invocation of isActive. * * @return the data cache entry for the current invocation of isActive. */ protected RegionData getCurrentData() { return this.currentData; } /** * Creates and returns a new RegionData instance specific to this Region instance. * * @param dc the current draw context. * * @return data cache entry for the state in the specified draw context. */ protected RegionData createCacheEntry(DrawContext dc) { return new RegionData(dc, this.minExpiryTime, this.maxExpiryTime); } /** * Indicates whether this Region's data must be recomputed, either as a result of a change in the * Globe's state or the expiration of the geometry regeneration interval. *

* A {@link gov.nasa.worldwind.ogc.kml.KMLRegion.RegionData} must be current when this method is * called. * * @param dc the current draw context. * * @return true if this Region's data must be regenerated, otherwise false. */ protected boolean mustRegenerateData(DrawContext dc) { return this.getCurrentData().isExpired(dc) || !this.getCurrentData().isValid(dc); } /** * Produces the data used to determine whether this Region is active for the specified DrawContext. * This method is called by makeRegionData upon determining that the current RegionData must be * recomputed, either as a result of a change in the Globe's state or the expiration of the geometry * regeneration interval. A {@link gov.nasa.worldwind.ogc.kml.KMLRegion.RegionData} must be current * when this method is called. * * @param dc the current draw context. * * @see #makeRegionData */ protected void doMakeRegionData(DrawContext dc) { this.getCurrentData().setExtent(null); this.getCurrentData().setSector(null); this.getCurrentData().setPoints(null); KMLLatLonAltBox box = this.getLatLonAltBox(); if (box == null) return; int altitudeMode = KMLUtil.convertAltitudeMode(box.getAltitudeMode(), WorldWind.CLAMP_TO_GROUND); // KML default this.getCurrentData().setAltitudeMode(altitudeMode); if (altitudeMode == WorldWind.CLAMP_TO_GROUND) { this.doMakeClampToGroundRegionData(dc, box); } else if (altitudeMode == WorldWind.RELATIVE_TO_GROUND) { this.doMakeRelativeToGroundRegionData(dc, box); } else // Default to WorldWind.ABSOLUTE. { this.doMakeAbsoluteRegionData(dc, box); } } /** * Produces the Extent and the Sector for this Region. Assumes this region's altitude mode * is clampToGround. A {@link gov.nasa.worldwind.ogc.kml.KMLRegion.RegionData} must be * current when this method is called. * * @param dc the current draw context. * @param box the Region's geographic bounding box. */ protected void doMakeClampToGroundRegionData(DrawContext dc, KMLLatLonAltBox box) { Sector sector = KMLUtil.createSectorFromLatLonBox(box); if (sector == null) return; // TODO: Regions outside of the normal lat/lon bounds ([-90, 90], [-180, 180]) are not supported. Remove // TODO: this warning when such regions are supported. See WWJINT-482. if (!this.isSectorSupported(sector)) { String message = Logging.getMessage("KML.UnsupportedRegion", sector); Logging.logger().warning(message); return; } double[] extremeElevations = dc.getGlobe().getMinAndMaxElevations(sector); Extent extent = Sector.computeBoundingBox(dc.getGlobe(), dc.getVerticalExaggeration(), sector, extremeElevations[0], extremeElevations[1]); this.getCurrentData().setExtent(extent); this.getCurrentData().setSector(sector); // Cache the model-coordinate points of the Region's four corners and center point on the surface terrain. It is // safe to cache this value since these points are regenerated every 5-6 seconds along with the RegionData's // extent and sector. Caching these points rather than computing them every frame reduces the average time of // Region.isActive by 50%. Vec4[] corners = sector.computeCornerPoints(dc.getGlobe(), dc.getVerticalExaggeration()); Vec4 centerPoint = sector.computeCenterPoint(dc.getGlobe(), dc.getVerticalExaggeration()); this.getCurrentData().setPoints(Arrays.asList(corners[0], corners[1], corners[2], corners[3], centerPoint)); } /** * Produces the Extent and the Sector for this Region. Assumes this region's altitude mode * is relativeToGround. A {@link gov.nasa.worldwind.ogc.kml.KMLRegion.RegionData} must be * current when this method is called. * * @param dc the current draw context. * @param box the Region's geographic bounding box. */ protected void doMakeRelativeToGroundRegionData(DrawContext dc, KMLLatLonAltBox box) { Sector sector = KMLUtil.createSectorFromLatLonBox(box); if (sector == null) return; if (!this.isSectorSupported(sector)) { String message = Logging.getMessage("KML.UnsupportedRegion", sector); Logging.logger().warning(message); return; } Double minAltitude = box.getMinAltitude(); if (minAltitude == null) minAltitude = 0d; // The default minAltitude is zero. Double maxAltitude = box.getMaxAltitude(); if (maxAltitude == null) maxAltitude = 0d; // The default maxAltitude is zero. double[] extremeElevations = dc.getGlobe().getMinAndMaxElevations(sector); Extent extent = Sector.computeBoundingBox(dc.getGlobe(), dc.getVerticalExaggeration(), sector, extremeElevations[0] + minAltitude, extremeElevations[1] + maxAltitude); this.getCurrentData().setExtent(extent); this.getCurrentData().setSector(sector); } /** * Produces the Extent and the Sector for this Region. Assumes this region's altitude mode * is absolute. A {@link gov.nasa.worldwind.ogc.kml.KMLRegion.RegionData} must be current * when this method is called. * * @param dc the current draw context. * @param box the Region's geographic bounding box. */ protected void doMakeAbsoluteRegionData(DrawContext dc, KMLLatLonAltBox box) { Sector sector = KMLUtil.createSectorFromLatLonBox(box); if (sector == null) return; if (!this.isSectorSupported(sector)) { String message = Logging.getMessage("KML.UnsupportedRegion", sector); Logging.logger().warning(message); return; } Double minAltitude = box.getMinAltitude(); if (minAltitude == null) minAltitude = 0d; // The default minAltitude is zero. Double maxAltitude = box.getMaxAltitude(); if (maxAltitude == null) maxAltitude = 0d; // The default maxAltitude is zero. Extent extent = Sector.computeBoundingBox(dc.getGlobe(), dc.getVerticalExaggeration(), sector, minAltitude, maxAltitude); this.getCurrentData().setExtent(extent); this.getCurrentData().setSector(sector); } /** * Determines if a Sector is supported by this Region implementation. This implementation does not support sectors * with latitude values outside of [-90, 90], or longitude values outside of [-180, 180]. * * @param sector Sector to test. * * @return {@code true} if {@code sector} is with [-90, 90] latitude and [-180, 180] longitude. */ protected boolean isSectorSupported(Sector sector) { return sector.isWithinLatLonLimits(); } /** * Indicates whether this Region is active on the specified DrawContext. A Region is active if its * bounding box intersects the viewing frustum, and its level of detail criteria are met for the specified traversal * context and draw context. This is called by isActive and its return value may be cached during a * single frame. * * @param tc the current KML traversal context. * @param dc the DrawContext used to determine whether this Region is active. * * @return true if this Region is active; otherwise false. */ protected boolean isRegionActive(KMLTraversalContext tc, DrawContext dc) { return this.isRegionVisible(dc) && this.meetsLodCriteria(tc, dc); } /** * Indicates whether this Region is visible for the specified DrawContext. If this Region's * altitudeMode is clampToGround, this Region is considered visible if its sector * intersects the DrawContext's visible sector and its frustum intersects the * DrawContext's viewing frustum. Otherwise, this Region is considered visible its frustum intersects * the DrawContext's viewing frustum. * * @param dc the DrawContext used to test this Region for visibility. * * @return true if this Region is visible, otherwise false. */ protected boolean isRegionVisible(DrawContext dc) { // If this Region's altitude mode is clampToGround and it has a non-null sector, compare its sector against the // DrawContext's visible sector to determine if the Region is visible. In this case the sector can be used to // determine visibility because the Region is defined to be on the Globe's surface. if (this.getCurrentData().getAltitudeMode() == WorldWind.CLAMP_TO_GROUND && dc.getVisibleSector() != null && this.getCurrentData().getSector() != null && !dc.getVisibleSector().intersects(this.getCurrentData().getSector())) { return false; } // Treat this Region as not visible if its extent occupies less than one pixel on screen. Features that exceed // the Region's boundary - such as a ScreenOverlay - are not displayed when the Region is insignificantly // visible. Though it may be the intent of a KML document's author to display such a feature whenever the Region // is in the frustum, we treat the Region just as though its outside of the frustum and prevent the display of // any features associated with it. //noinspection SimplifiableIfStatement if (this.getCurrentData().getExtent() != null && dc.isSmall(this.getCurrentData().getExtent(), 1)) return false; return this.intersectsFrustum(dc); } /** * Indicates whether this Region intersects the viewing frustum for the specified DrawContext. A * {@link gov.nasa.worldwind.ogc.kml.KMLRegion.RegionData} must be current when this method is called. *

* This returns true if this Region has no bounding box, or if its bounding box cannot be computed for * any reason. * * @param dc the DrawContext who's frustum is tested against this Region's bounding box. * * @return true if this Region's bounding box intersects the DrawContext's frustum, * otherwise false. */ protected boolean intersectsFrustum(DrawContext dc) { Extent extent = this.getCurrentData().getExtent(); //noinspection SimplifiableIfStatement if (extent == null) return true; // We do not know the visibility; assume it intersects the frustum. // Test against the view frustum even in picking mode. We cannot cull against the pick frustums because visible // content (e.g. an open balloon) may extend beyond the region's bounding box. return dc.getView().getFrustumInModelCoordinates().intersects(extent); } /** * Indicates whether the specified DrawContext meets this Region's level of detail criteria. A * {@link gov.nasa.worldwind.ogc.kml.KMLRegion.RegionData} must be current when this method is called. *

* This returns true if this Region has no level of criteria, or if its level of detail cannot be * compared against the bounding box for any reason. * * @param tc the current KML traversal context. * @param dc the DrawContext to test. * * @return true if the DrawContext's meets this Region's level of detail criteria, * otherwise false. */ protected boolean meetsLodCriteria(KMLTraversalContext tc, DrawContext dc) { KMLLod lod = this.getLod(); if (lod == null) return true; // No level of detail specified; assume the DrawContext meets the level of detail criteria. if ((lod.getMinLodPixels() == null || lod.getMinLodPixels() <= 0d) && (lod.getMaxLodPixels() == null || lod.getMaxLodPixels() < 0d)) return true; // The level of detail range is infinite, so this Region always meets the lod criteria. if (lod.getMaxLodPixels() != null && lod.getMaxLodPixels() == 0d) return false; // The maximum number of pixels is zero, so this Region never meets the lod criteria. int altitudeMode = this.getCurrentData().getAltitudeMode(); if (altitudeMode == WorldWind.CLAMP_TO_GROUND) { return this.meetsClampToGroundLodCriteria(tc, dc, lod); } else if (altitudeMode == WorldWind.RELATIVE_TO_GROUND) { return this.meetsRelativeToGroundLodCriteria(tc, dc, lod); } else // Default to WorldWind.ABSOLUTE. { return this.meetsAbsoluteLodCriteria(tc, dc, lod); } } /** * Indicates whether the specified DrawContext meets this Region's level of detail criteria. Assumes * this region's altitude mode is clampToGround. A {@link gov.nasa.worldwind.ogc.kml.KMLRegion.RegionData} * must be current when this method is called. * * @param tc the current KML traversal context. * @param dc the DrawContext to test. * @param lod the level of detail criteria that must be met. * * @return true if the DrawContext's meets this Region's level of detail criteria, * otherwise false. */ protected boolean meetsClampToGroundLodCriteria(KMLTraversalContext tc, DrawContext dc, KMLLod lod) { // Neither the OGC KML specification nor the Google KML reference specify how to compute a clampToGround // Region's projected screen area. However, the Google Earth outreach tutorials, and an official post from a // Google engineer on the Google forums both indicate that clampToGround Regions are represented by a flat // rectangle on the terrain surface: // KML Specification version 2.2, section 6.3.4. // http://groups.google.com/group/kml-support-getting-started/browse_thread/thread/bbba32541bace3cc/df4e1dc64a3018d4?lnk=gst#df4e1dc64a3018d4 // http://earth.google.com/outreach/tutorial_region.html Sector sector = this.getCurrentData().getSector(); List points = this.getCurrentData().getPoints(); if (sector == null || points == null || points.size() != 5) return true; // Assume the criteria is met if we don't know this Region's sector or its surface points. // Get the eye distance for each of the sector's corners and its center. View view = dc.getView(); double d1 = view.getEyePoint().distanceTo3(points.get(0)); double d2 = view.getEyePoint().distanceTo3(points.get(1)); double d3 = view.getEyePoint().distanceTo3(points.get(2)); double d4 = view.getEyePoint().distanceTo3(points.get(3)); double d5 = view.getEyePoint().distanceTo3(points.get(4)); // Find the minimum eye distance. Compute the sector's size in meters by taking the square root of the sector's // area in radians, and multiplying that by the globe's radius at the nearest corner. We take the square root // of the area in radians to match the units of this Region's minLodPixels and maxLodPixels, which are the // square root of a screen area. double minDistance = d1; double numRadians = Math.sqrt(sector.getDeltaLatRadians() * sector.getDeltaLonRadians()); double numMeters = points.get(0).getLength3() * numRadians; if (d2 < minDistance) { minDistance = d2; numMeters = points.get(1).getLength3() * numRadians; } if (d3 < minDistance) { minDistance = d3; numMeters = points.get(2).getLength3() * numRadians; } if (d4 < minDistance) { minDistance = d4; numMeters = points.get(3).getLength3() * numRadians; } if (d5 < minDistance) { minDistance = d5; numMeters = points.get(4).getLength3() * numRadians; } // Compare the scaled distance to the minimum and maximum pixel size in meters, according to the sector's size // and this Region's minLodPixels and maxLodPixels. This Region's level of detail criteria are met when the // scaled distance is less than or equal to the minimum pixel size, and greater than the maximum pixel size. // Said another way, this Region is used when a pixel in the Region's sector is close enough to meet the minimum // pixel size criteria, yet far enough away not to exceed the maximum pixel size criteria. // NOTE: It's tempting to instead compare a screen pixel count to the minLodPixels and maxLodPixels, but that // calculation is window-size dependent and results in activating an excessive number of Regions when a KML // super overlay is displayed, especially if the window size is large. Double lodMinPixels = lod.getMinLodPixels(); Double lodMaxPixels = lod.getMaxLodPixels(); double distanceFactor = minDistance * Math.pow(10, -this.getDetailFactor(tc)); // We ignore minLodPixels if it's unspecified, zero, or less than zero. We ignore maxLodPixels if it's // unspecified or less than 0 (infinity). In these cases any distance passes the test against minLodPixels or // maxLodPixels. return (lodMinPixels == null || lodMinPixels <= 0d || (numMeters / lodMinPixels) >= distanceFactor) && (lodMaxPixels == null || lodMaxPixels < 0d || (numMeters / lodMaxPixels) < distanceFactor); } /** * Indicates whether the specified DrawContext meets this Region's level of detail criteria. Assumes * this region's altitude mode is relativeToGround. A {@link * gov.nasa.worldwind.ogc.kml.KMLRegion.RegionData} must be current when this method is called. * * @param tc the current KML traversal context. * @param dc the DrawContext to test. * @param lod the level of detail criteria that must be met. * * @return true if the DrawContext's meets this Region's level of detail criteria, * otherwise false. */ @SuppressWarnings({"UnusedDeclaration"}) protected boolean meetsRelativeToGroundLodCriteria(KMLTraversalContext tc, DrawContext dc, KMLLod lod) { return this.meetsScreenAreaCriteria(dc, lod); } /** * Indicates whether the specified DrawContext meets this Region's level of detail criteria. Assumes * this region's altitude mode is absolute. A {@link gov.nasa.worldwind.ogc.kml.KMLRegion.RegionData} * must be current when this method is called. * * @param tc the current KML traversal context. * @param dc the DrawContext to test. * @param lod the level of detail criteria that must be met. * * @return true if the DrawContext's meets this Region's level of detail criteria, * otherwise false. */ @SuppressWarnings({"UnusedDeclaration"}) protected boolean meetsAbsoluteLodCriteria(KMLTraversalContext tc, DrawContext dc, KMLLod lod) { return this.meetsScreenAreaCriteria(dc, lod); } /** * Indicates whether this Region's projected screen area on the specified DrawContext is in the range * specified by lod. * * @param dc the DrawContext to test. * @param lod the level of detail criteria that must be met. * * @return true if this Region's screen area meets the level of detail criteria, otherwise * false. */ protected boolean meetsScreenAreaCriteria(DrawContext dc, KMLLod lod) { // The DrawContext does not meet this region's minLodPixels criteria if minLodPixels is specified and this // region's projected screen pixel count is less than minLodPixels. // The DrawContext does not meet this region's maxLodPixels criteria if maxLodPixels is specified, is not // negative, and this region's projected screen pixel count is greater than or equal to maxLodPixels. If // maxLodPixels is negative, this indicates that maxLodPixels is positive infinity and therefore accepts any // value. Extent extent = this.getCurrentData().getExtent(); if (extent == null) return true; // Assume the criteria is met if we don't know this Region's extent. // Compute the projected screen area of this Region's extent in square pixels in the DrawContext's View. // According to the KML specification, we take the square root of this value to get a value that is comparable // against minLodPixels and maxLodPixels. The projected area is positive infinity if the view's eye point is // inside the extent, or if part of the extent is behind the eye point. In either case we do not take the square // root, and leave the value as positive infinity. double numPixels = extent.getProjectedArea(dc.getView()); if (numPixels != Double.POSITIVE_INFINITY) numPixels = Math.sqrt(numPixels); // This Region's level of detail criteria are met if the number of pixels is greater than or equal to // minLodPixels and less than maxLodPixels. We ignore minLodPixels if it's unspecified, zero, or less than zero. // We ignore maxLodPixels if it's unspecified or less than 0 (infinity). In these cases any distance passes the // test against minLodPixels or maxLodPixels. Double lodMinPixels = lod.getMinLodPixels(); Double lodMaxPixels = lod.getMaxLodPixels(); return (lodMinPixels == null || lodMinPixels <= 0d || lodMinPixels <= numPixels) && (lodMaxPixels == null || lodMaxPixels < 0d || lodMaxPixels > numPixels); } /** * Indicates the detail factor that configures KML scene resolution to screen resolution as the viewing distance * changes. This returns the Region's detailHintOrigin plus the KMLTraversalContext's * detail hint. * * @param tc the KML traversal context that specifies the detail hint. * * @return this Region's detailHintOrigin plus the traversal context's detailHintOrigin. */ protected double getDetailFactor(KMLTraversalContext tc) { return this.detailHintOrigin + tc.getDetailHint(); } @Override public void applyChange(KMLAbstractObject sourceValues) { if (!(sourceValues instanceof KMLRegion)) { String message = Logging.getMessage("nullValue.SourceIsNull"); Logging.logger().warning(message); throw new IllegalArgumentException(message); } this.reset(); super.applyChange(sourceValues); } @Override public void onChange(Message msg) { if (KMLAbstractObject.MSG_BOX_CHANGED.equals(msg.getName())) this.reset(); super.onChange(msg); } protected void reset() { this.regionDataCache.removeAllEntries(); this.currentData = null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy