Ellipsoid Surface Regions |
The need to define surface regions on a planetary body is common in many domains and applications. In this topic, we'll show you how to create and use EllipsoidSurfaceRegions. There are two classes of techniques with which ellipsoid surface regions can be constructed. The first of which is via builder classes derived from EllipsoidSurfaceRegionBuilders. These builders take various primitives, like surface polygon vertices, collections of surface curves, cartographic extents, etc. and construct a surface region from these primitives in a defined manner. The second class of techniques is via regularized boolean operations on existing surface regions. These operations include: addition (union), subtraction (difference), intersection, and complement. These operations return new regions defined by the application of the relevant binary or unary operation. Surface regions are used in many areas in DME Component Libraries, including:
Defining a surface region for use with an access constraint, which can be a basic membership test, an azimuth-elevation constraint, or a sensor volume constraint.
Visualizing topographical regions or geopolitical regions of interest.
Gridding a surface region for use with Coverage.
Visualizing coverage metrics on the surface of a planet.
Defining a search area for the Route Design Library.
DME Component Libraries includes four methods for creating surface regions on an ellipsoid from other basic types, each provided by a different builder class. Because EllipsoidSurfaceRegions are immutable after construction, the builders provide for the systemic configuration of inputs and options for creating a surface region or surface region hole (a surface region with inverted point membership). After fully configuring a builder, call getEllipsoidSurfaceRegion or getEllipsoidSurfaceRegionHole to obtain the actual region or hole. The builder can then be reconfigured and used to create additional regions or holes. Since each builder has unique features, we will discuss them individually.
First, SpecifiedNodesEllipsoidSurfaceRegionBuilder creates a region based on specified "nodes" or vertices that define the boundary of the surface region. The connection between each vertex of the surface boundary can be either a geodesic curve (minimum distance) or a rhumb line (constant heading).
// First we choose our reference surface, which will be the ellipsoid that defines Earth's surface. EarthCentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); Ellipsoid referenceSurface = earth.getShape(); // We specify the boundary in terms of nodes connected by geodesics. Cartographic[] nodes = { new Cartographic(Trig.degreesToRadians(-72.9947), Trig.degreesToRadians(11.3181), 0.0), new Cartographic(Trig.degreesToRadians(-35.4977), Trig.degreesToRadians(-6.29148), 0.0), new Cartographic(Trig.degreesToRadians(-48.902), Trig.degreesToRadians(-13.6507), 0.0), new Cartographic(Trig.degreesToRadians(-63.9708), Trig.degreesToRadians(-0.859671), 0.0), new Cartographic(Trig.degreesToRadians(-64.5841), Trig.degreesToRadians(-21.9736), 0.0), new Cartographic(Trig.degreesToRadians(-77.9884), Trig.degreesToRadians(-9.44543), 0.0), }; // With the boundary nodes specified and the reference surface chosen, // we now decide on the granularity of the interconnecting curves. double granularity = Trig.degreesToRadians(0.5); SpecifiedNodesEllipsoidSurfaceRegionBuilder builder = new SpecifiedNodesEllipsoidSurfaceRegionBuilder(referenceSurface, granularity, Arrays.asList(nodes)); // The default connection type is geodesic, but rhumb lines could be used instead if desired. builder.setConnectionType(EllipsoidSurfaceRegionCurveConnectionType.GEODESIC); // The region is ready to be built! EllipsoidSurfaceRegion region = builder.getEllipsoidSurfaceRegion();
SpecifiedExtentEllipsoidSurfaceRegionBuilder takes an input CartographicExtent and produces a surface region. This builder is best suited for situations where an approximate boundary for your region is desired, or a quick estimate based upon input north/south and east/west bounds will suffice for your analysis. This builder has a configurable granularity for the output region, as this region discretizes the border to create the final boundary as a series of connected rhumb lines.
// First we choose our reference surface, which will be the ellipsoid that defines Earth's surface. EarthCentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); Ellipsoid referenceSurface = earth.getShape(); // Here we specify a simple boundary for our extent. CartographicExtent extent = new CartographicExtent(Trig.degreesToRadians(-70.0), // west Trig.degreesToRadians(20.0), // south Trig.degreesToRadians(-60.0), // east Trig.degreesToRadians(40.0)); // north // With the extent specified and the reference surface chosen, we can decide on the granularity of the // boundary curve that is created based upon the extent. In this case we choose the default granularity. EllipsoidSurfaceRegionBuilder builder = new SpecifiedExtentEllipsoidSurfaceRegionBuilder(referenceSurface, extent); // The region is ready to be built! EllipsoidSurfaceRegion region = builder.getEllipsoidSurfaceRegion();
SpecifiedCurveEllipsoidSurfaceRegionBuilder creates a surface region based upon an input EllipsoidSurfaceCurve. The boundary will be discretized based upon the input granularity settings for the purpose of computing the centroid and the planar mapping (see below for more information about the mapping and centroid).
// First we choose our reference surface, which will be the ellipsoid that defines Earth's surface. EarthCentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); Ellipsoid referenceSurface = earth.getShape(); // We specify the boundary curve. EllipsoidGeodesic first = new EllipsoidGeodesic(referenceSurface, one, two); EllipsoidGeodesic second = new EllipsoidGeodesic(referenceSurface, two, three); EllipsoidGeodesic third = new EllipsoidGeodesic(referenceSurface, three, one); EllipsoidComplexSurfaceCurve boundary = EllipsoidComplexSurfaceCurve.createCurve(first, second, third); // With the boundary curve specified and the reference surface chosen, we can decide on the granularity of the // boundary curve. In this case we choose the default granularity. EllipsoidSurfaceRegionBuilder builder = new SpecifiedCurveEllipsoidSurfaceRegionBuilder(referenceSurface, boundary); // The region is ready to be built! EllipsoidSurfaceRegion region = builder.getEllipsoidSurfaceRegion();
Finally, ConvexBoundaryEllipsoidSurfaceRegionBuilder creates a surface region by taking the input unorganized points and projecting them on to the tangent plane defined by the centroid of the input points. It then computes the 2D convex hull on this projection and extracts the final set of boundary vertices from this construction. An example of using this builder would be to create a region that bounds a set of input water well locations.
// First we choose our reference surface, which will be the ellipsoid that defines Earth's surface. EarthCentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); Ellipsoid referenceSurface = earth.getShape(); // We specify the boundary in terms of unorganized points. // Note that these are the same points used in the specified nodes builder example. Cartographic[] nodes = { new Cartographic(Trig.degreesToRadians(-72.9947), Trig.degreesToRadians(11.3181), 0.0), new Cartographic(Trig.degreesToRadians(-35.4977), Trig.degreesToRadians(-6.29148), 0.0), new Cartographic(Trig.degreesToRadians(-48.902), Trig.degreesToRadians(-13.6507), 0.0), new Cartographic(Trig.degreesToRadians(-63.9708), Trig.degreesToRadians(-0.859671), 0.0), new Cartographic(Trig.degreesToRadians(-64.5841), Trig.degreesToRadians(-21.9736), 0.0), new Cartographic(Trig.degreesToRadians(-77.9884), Trig.degreesToRadians(-9.44543), 0.0), }; EllipsoidSurfaceRegionBuilder builder = new ConvexBoundaryEllipsoidSurfaceRegionBuilder(referenceSurface, Arrays.asList(nodes)); // The region is ready to be built! EllipsoidSurfaceRegion region = builder.getEllipsoidSurfaceRegion();
Each of these surface region builders allow for the definition of one or more holes. Each hole defines a region with inverted membership relative to the containing region. An example of using ellipsoid surface region holes would be to define a region that bounds the state of Minnesota, then to define a hole for each lake contained within. You could then check whether a sensor volume contains the land of Minnesota, excluding all water.
Suppose we instead wish to check whether the sensor volume contains a specific lake, such as Lake Mille Lacs. A hole defining this lake can be converted to a surface region by calling getEllipsoidSurfaceRegion on the hole to produce a region with the same boundary. Conversion between holes and regions is bidirectional, and getEllipsoidSurfaceRegionHole can be called on a region to convert it to a hole.
When creating holes, be mindful that they are not checked for validity, and it is possible to define holes that overlap or extend beyond the borders of the containing surface region.
In addition to dealing with negative spaces (holes), EllipsoidSurfaceRegions are also designed to handle large areas. For regions built with SpecifiedCurveEllipsoidSurfaceRegionBuilder, SpecifiedNodesEllipsoidSurfaceRegionBuilder, or ConvexBoundaryEllipsoidSurfaceRegionBuilder, you have the option to configure a reference point on the ellipsoid, and indicate if that point should be considered to be inside or outside of the region. This allows creating regions that cover more than half of the ellipsoid.
In addition to the builders mentioned in this help topic, DME Component Libraries also offers regularized boolean operations on ellipsoid surface regions. These operations include: addition (union), subtraction (difference), intersection, and complement. Each of these output a new region (or regions) when executed. Regularized boolean operations on surface regions have numerous use cases, a few of which are:
Adding holes to a region. For example, removing the lakes in the state of Minnesota.
Splitting and joining regions. We could split Pennsylvania into two regions, western and eastern, by cutting the state with a rectangle. We could join New York, Pennsylvania, and New Jersey into a tri-state area.
We can perform queries on regions, such as does a 10 mile exclusion region around state airports cross the state boundary?
Before we demonstrate the use of the boolean operations we need to discuss the requirements placed on the regions in order for the operations to complete successfully. We require that the holes in any operations be well defined and disjoint with the enclosing region's boundary. We also require that these holes have the opposite winding direction relative to their encompassing regions (i.e. the region should be counter clockwise and the holes should be clockwise). The output boundary curve type will be the same as the input boundary curve type (i.e. geodesic boundaries will be output as geodesic, rhumb as rhumb, etc). The results we output will exclude lower dimensional results (i.e. no lines or single points are returned).
Let's demonstrate the available boolean operations. We begin by defining two input surface regions, a triangle and a square.
EarthCentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); double unit = Trig.degreesToRadians(10.0); Cartographic[] squareNodes = { new Cartographic(-1.0 * unit, -1.0 * unit, 0.0), new Cartographic(1.0 * unit, -1.0 * unit, 0.0), new Cartographic(1.0 * unit, 1.0 * unit, 0.0), new Cartographic(-1.0 * unit, 1.0 * unit, 0.0) }; Cartographic[] triangleNodes = { new Cartographic(0.0, 0.0, 0.0), new Cartographic(1.0 * unit, 2.0 * unit, 0.0), new Cartographic(-1.0 * unit, 2.0 * unit, 0.0) }; EllipsoidSurfaceRegionBuilder regionBuilder = new SpecifiedNodesEllipsoidSurfaceRegionBuilder(earth.getShape(), 0.00001, Arrays.asList(squareNodes)); EllipsoidSurfaceRegion square = regionBuilder.getEllipsoidSurfaceRegion(); regionBuilder = new SpecifiedNodesEllipsoidSurfaceRegionBuilder(earth.getShape(), 0.00001, Arrays.asList(triangleNodes)); EllipsoidSurfaceRegion triangle = regionBuilder.getEllipsoidSurfaceRegion();
Which results in the two regions pictured here.
Now let's perform some operations on these input shapes and examine the results.
// addition - union ArrayList<EllipsoidSurfaceRegion> union = square.add(triangle); boolean hasRegion = union.size() == 1; // true boolean hasNoHoles = union.get(0).getHoles().size() == 0; // true // subtraction - difference ArrayList<EllipsoidSurfaceRegion> difference = square.subtract(triangle); hasRegion = difference.size() == 1; // true hasNoHoles = difference.get(0).getHoles().size() == 0; // true // intersection ArrayList<EllipsoidSurfaceRegion> intersection = square.intersect(triangle); hasRegion = intersection.size() == 1; // true hasNoHoles = intersection.get(0).getHoles().size() == 0; // true // complement ArrayList<EllipsoidSurfaceRegion> complement = square.complement(); hasRegion = complement.size() == 1; // true hasNoHoles = complement.get(0).getHoles().size() == 0; // true
Now we will explore another use of regularized boolean operations on surface regions- querying the results. Suppose we want to know if a 10 Km exclusion zone around the airports in our state cross our borders into another state. If we subtract each exclusion zone from our state we can then compare the resulting boundary of our state for equality with the original unmodified boundary. If the boundary is altered then we know our exclusion zone crosses the state boundary.
SpecifiedNodesEllipsoidSurfaceRegionBuilder paBuilder = new SpecifiedNodesEllipsoidSurfaceRegionBuilder(earth.getShape(), boundaryPoints); EllipsoidSurfaceRegion paRegion = paBuilder.getEllipsoidSurfaceRegion(); EllipsoidSurfaceRegionCurveConnectionType connectionType = EllipsoidSurfaceRegionCurveConnectionType.GEODESIC; Cartographic phillyAirportLocation = new Cartographic(Trig.degreesToRadians(-75.2424), Trig.degreesToRadians(39.8744), 0.0); EllipsoidSurfaceRegion phillyExclusionZone = createEllipseRegion(phillyAirportLocation, exclusionRadius, exclusionRadius, 0.0, connectionType); Cartographic harrisburgAirportLocation = new Cartographic(Trig.degreesToRadians(-76.7576), Trig.degreesToRadians(40.1942), 0.0); EllipsoidSurfaceRegion harrisburgExclusionZone = createEllipseRegion(harrisburgAirportLocation, exclusionRadius, exclusionRadius, 0.0, connectionType); Cartographic pittAirportLocation = new Cartographic(Trig.degreesToRadians(-80.2352), Trig.degreesToRadians(40.4919), 0.0); EllipsoidSurfaceRegion pittExclusionZone = createEllipseRegion(pittAirportLocation, exclusionRadius, exclusionRadius, 0.0, connectionType); Cartographic erieAirportLocation = new Cartographic(Trig.degreesToRadians(-80.19077), Trig.degreesToRadians(42.0709), 0.0); EllipsoidSurfaceRegion erieExclusionZone = createEllipseRegion(erieAirportLocation, exclusionRadius, exclusionRadius, 0.0, connectionType);
Now perform some operations...
// Our query operations. Does Philadelphia International Airport modify the state boundary? ArrayList<EllipsoidSurfaceRegion> modifiedState = paRegion.subtract(phillyExclusionZone); // Here we check to see that we do not split the state. boolean isWhole = modifiedState.size() == 1; // true // Here we show that we are not contained entirely within the state since we are not a hole. boolean noHoles = modifiedState.get(0).getHoles().size() == 0; // true // So at this point we are either disjoint or we modify the boundary. Let's check! boolean boundariesEqual = paRegion.getBoundaryCurve().equalsType(modifiedState.get(0).getBoundaryCurve()); // false // This is false, so we know that we modified the boundary. // We could work with this airport further and produce the region covered by the state and the airport exclusion zone. // What about Harrisburg? modifiedState = paRegion.subtract(harrisburgExclusionZone); // The state again is not split. isWhole = modifiedState.size() == 1; // true // We are a hole! noHoles = modifiedState.get(0).getHoles().size() == 0; // false // The boundaries are equal. boundariesEqual = paRegion.getBoundaryCurve().equalsType(modifiedState.get(0).getBoundaryCurve()); // true // Let's add two more airports in the mix... modifiedState = modifiedState.get(0).subtract(pittExclusionZone); isWhole = modifiedState.size() == 1; // true, the same border as before. noHoles = modifiedState.get(0).getHoles().size() == 0; // false, it is 2. a new hole at pitt. modifiedState = modifiedState.get(0).subtract(erieExclusionZone); isWhole = modifiedState.size() == 1; // true noHoles = modifiedState.get(0).getHoles().size() == 0; // false, it is 2. no new holes, we modified the boundary.
Here we show Pennsylvania in an unmodified state and after all four airports have been subtracted.
As we discussed in the introductory text, surface regions have a wide variety of uses in DME Component Libraries. Let's explore some uses beginning with simple point membership. Using isPointInsideRegion, we can check if any point (ignoring height) is within the region. This method will return false for points that are within one of the region's holes. You can also ignore holes by calling isPointInsideRegionIgnoringHoles.
We can obtain a rough boundary for our surface region by using computeCartographicExtent. This method returns the north/south east/west bounds. Similarly, Centroid (get), BoundaryCurve (get), and ReferenceSurface (get) are available properties of EllipsoidSurfaceRegion. BoundaryCurve (get) can be used to compute the total boundary perimeter, Centroid (get) is the location of the point used to form a planar mapping of the surface region in the projected space, and ReferenceSurface (get) indicates which ellipsoid the region is defined on.
Using a surface region with an access constraint, or as a grid in the coverage system, is very straightforward. An EllipsoidSurfaceRegion can be used to create a CentralBodySurfaceRegion, which can then be used to create any of the following built-in constraints:
Constraint | Description |
---|---|
An access constraint that requires that an object be within a surface region. | |
An access constraint that requires that an object be between a span of elevation angles from any or all points within a surface region. | |
An access constraint that requires that part of the input surface region lies within the sensor surface footprint. |
The process for using a surface region as a coverage or volumetric (three-dimensional) grid is similar. Create the region (or regions) using one of the builders, decide on your grid's resolution, and then use SurfaceRegionsCoverageGrid. This grid can be extruded to make a three-dimensional grid relative to the surface region by passing the two-dimensional grid to ExtrudedCentralBodyCoverageGrid. The following example demonstrates this process:
// Use the surface region to define the coverage grid. Note that this type of coverage grid // can take multiple ellipsoid surface regions for use in a single coverage calculation. SurfaceRegionsCoverageGrid surfaceCoverageGrid = new SurfaceRegionsCoverageGrid(Trig.degreesToRadians(1.0), earth, region); // Create the coverage definition using the grid. We will use the default coverage grid platform. ParameterizedSpatiallyPartitionedCoverageDefinition coverage = new ParameterizedSpatiallyPartitionedCoverageDefinition(surfaceCoverageGrid, true); // Create a line-of-sight constraint between a spacecraft and the grid point. LinkInstantaneous link = new LinkInstantaneous(spacecraft, coverage.getGridPointPlaceholder()); CentralBodyObstructionConstraint lineOfSight = new CentralBodyObstructionConstraint(link, earth); coverage.addAsset(new AssetDefinition(spacecraft, lineOfSight, true)); // Compute the results! CoverageResults results = coverage.computeCoverageOverTheGrid(start, stop); // Let's do the same thing, only in 3D. SpecifiedNumberOfPointsCoverageGriddingTechnique heightGriddingTechnique = new SpecifiedNumberOfPointsCoverageGriddingTechnique(10); CentralBodyCoverageGrid grid3D = new ExtrudedCentralBodyCoverageGrid(surfaceCoverageGrid, new Bounds(0.0, 1000.0), heightGriddingTechnique); coverage.setGrid(grid3D); // Compute the 3D results! CoverageResults results3D = coverage.computeCoverageOverTheGrid(start, stop);