VisibilityIndexOperator Class
Estimates, for each terrain raster cell inside an observer area, the percentage of an area of interest that is visible from the cell.
NuGet/Assembly: Carmenta.Engine.5.16.2.nupkg (in the CEOperators assembly)
Syntax
public class VisibilityIndexOperator : VisibilityOperator
Remarks
There is an introductory Visibility Guide, which you should read in parallel with this reference manual, and there is a sample configuration visibility_index_operator.px. The VisibilityIndexOperator is also explored in the Aircraft Routing Tutorial.
Introduction
This operator produces a raster where each cell inside an observer area will contain an estimate of its visibility index: the percentage that an observer in the cell would be able to see of an area of interest. So, the raster cell with the maximal value indicates the best position for a single observer, and other local maxima suggest good alternative positions. But if you want to place several observers optimally so that they together cover the area of interest, that is a much harder problem, since a greedy algorithm will not necessarily be optimal. Automatic algorithms for siting multiple observers are outside the scope of Carmenta Engine, but if you want to implement one yourself, the visibility indices should be useful to guide and improve the search. The related operators LineOfSightOperator and AirspaceCoverageOperator can also be useful for observer siting.
Note that for some purposes, you want to find the raster cell with the minimal visibility index instead. For example, let us say you do not want a good place for a surveillance tower, but for an ugly chimney that must be near a public park. Then, you want to minimize the intervisibility between the chimney and the park, since a line of sight can be used in both directions and the park visitors do not want to see the chimney.
Inputs
Elevation rasters should come from the ElevationInput, and tree height rasters may come from the optional TreeHeightInput.
The observer area can be either explicit or implicit, depending on whether there is an ObserverInput:
If the ObserverInput is Null, the observer area will implicitly be the entire View area.
If the ObserverInput is defined, it should produce explicit polygon features that define observer areas.
Since the operator can be slow, it is often better to use a small explicit observer polygon instead of handling the entire View area.
The area of interest can also be either explicit or implicit. When explicit, we call it the target area, and it should be a polygon feature coming from the TargetInput. A nice visualization of the output raster can be made with a plain RasterVisualizer with semitransparency, combined with a separate layer using an IsolineOperator (the same visualization is used in the visibility_index_operator.px sample):
![]() |
We can confirm the result for one particular observer position by putting a point feature there, which we use as the observer of a LineOfSightOperator. In the screenshot below, we have placed the point observer on a 30-percent isoline. The viewshed is shown as a dark gray area clipped to the target area, and one can confirm, by eye, that about 30 percent of the target is visible from that position. Also, the screenshot has a boldface label with the precise visible percentage, generated by a custom operator from the viewshed raster. (It might have been better if the boldface label were placed next to the point observer.)
![]() |
If the TargetInput is Null, the operator will assume that each observer position has an implicit area of interest that is the circle within the MaxDistance (or a circle sector, if PictureWidth is less than 360°). Our example scenario must be zoomed out a little to illustrate the implicit mode:
![]() |
Since the different positions of the observer will then use differently placed circles, the implicit mode may seem less useful, but an example where it makes sense is given in the Aircraft Routing Tutorial.
Output raster specification
For each observer polygon from ObserverInput, the output raster feature will be a 32-bit Float raster where each cell inside the observer polygon contains an estimate of its visibility index in percent, 0.0 to 100.0, and each cell outside it contains an Undefined value of 255.0. Some special cases occur for areas where the elevation data are missing or Undefined: an observer position that lacks a well-defined ground elevation will get the Undefined visibility index, but as long as the observer position has a well-defined elevation, other raster cells with undefined elevations will just be treated as having an elevation of 500 meters below sea level.
Each output raster feature will have the same ID and the same attribute values (except the geoType attribute) as the corresponding observer polygon feature. The output resolution will be based on the elevation input but can be controlled by operator properties: see the section below, Trading accuracy for speed.
Target height
For each position of an observer with a sensor at SensorHeight above the specified level (ObserverHeightType), the operator must classify each 2D target position as either visible or non-visible. But visibility depends also on the height of the potential target at the 2D target position: it is easier to see aircraft that are higher. To use the operator, you must define a constant TargetHeight that will be assumed for all potential targets. So, the operator classifies each 2D target position as visible if a target object there would be visible at the assumed TargetHeight. The target height can be interpreted as above ground, above treetops, or above sea level: see TargetHeightType. Note that visibility indices computed with different TargetHeight properties or different TargetHeightType properties cannot be compared directly in a meaningful way.
The precise value of the chosen target height can be somewhat arbitrary, so the same is true for the absolute values of the visibility indices. For example, you may think that enemy pilots will try to fly at 50 meters above treetops (and above open terrain), but what if they feel lucky and fly at 40 meters? Well, although visibility indices would be lower for a 40 meters target height, there should still be some correlation with the ones for 50 meters, so the positions of the maximum and the local maxima should not be all that different. Since the positions of the maxima are what is really interesting, the dependency on the choice of target height should not be a serious drawback.
Consistency with LineOfSightOperator
To get consistency between a LineOfSightOperator and a VisibilityIndexOperator, they must of course agree about the observer properties that define the sensor, for example the MaxDistance, the ObserverHeightType and the SensorHeight. And note that the TargetHeightType of the VisibilityIndexOperator must have the same value as the differently named LineOfSightOperator.MinVisibilityHeightType. But there are some properties that are available in only one of the two operators.
For consistency, the more versatile LineOfSightOperator should have specific values for some properties that are not available in VisibilityIndexOperator, namely:
LineOfSightOperator.CarrierType = Airborne
LineOfSightOperator.MaxDistanceType = Horizontal
LineOfSightOperator.OutputMinVisibilityHeights = True
LineOfSightOperator.UndefinedElevationInterpretation = NegativeElevation
With these settings, features produced by LineOfSightOperator.ObserverInput must also have z = 0 to make the total observer height the same as in the VisibilityIndexOperator. (Alternatively, the LineOfSightOperator can be forced to ignore the z coordinate of its observer by setting its carrierType = GroundBased, but that gives consistency only if ObserverHeightType = AboveGround in the VisibilityIndexOperator.)
One property that VisibilityIndexOperator has but LineOfSightOperator does not is a TargetHeight property; instead, in the raster visualizer for the viewshed area, the first raster value that gets the fully transparent color should be the next number after the targetHeight, in other words targetHeight + 1 if the elevation rasters contain integers, and otherwise something like targetHeight + 0.01. See the visibility_index_operator.px sample for an example.
Note also that VisibilityIndexOperator properties that are attributes variables can be controlled via the View.UpdateAttributes or via the attributes of features from ObserverInput, but not via the attributes of features from TargetInput.
Behavior with multiple input polygons
Multiple target polygons
If the TargetInput is defined, the VisibilityIndexOperator expects at least one target polygon feature with a positive area. If the operator gets several target polygon features, their union will be used as a single target area (even if that area is not contiguous). If the TargetInput exists but produces no polygons or only degenerate polygons with zero area, then the operator will produce a raster with only zeros inside the observer polygons.
Multiple observer polygons
If the ObserverInput is defined, the VisibilityIndexOperator expects a number of observer polygon features, each of which will be treated separately and produce its own output raster feature.
Caching
The output from a VisibilityIndexOperator can be cached in two complementary ways: as features or as visualization.
Feature caching is basic: the operator will always cache its results as features in the global cache. Normally, you do not need to think about this caching: if some old input features arrive unmodified in a new update, the operator will recognize them and reuse the cached result features. However, the feature caching assumes that the data coming from ElevationInput and TreeHeightInput are static. So, if your tree heights come from land cover data that are converted to estimated tree heights via a RasterReclassificationOperator, then you may want to make the reclassification table editable in your application user interface. And in that case, your application must flush the cache of the operator whenever the reclassification table has been edited.
Visualization caching is optional but recommended. It can be done in two alternative ways:
If the ObserverInput is defined, you can let an OrdinaryLayer that presents the results of the operator use a dynamic OrdinaryLayer.CacheMode, preferably DynamicAsync, to cache the visualization of the result. But in that case, it is only the ObserverInput that is the ID-giving input, which means that all data coming from any other input are assumed to be static. So, special application logic is needed to handle modifications of the target areas (or the tree height reclassification table); see OrdinaryLayerCacheMode for details. Unfortunately, the general Carmenta Explorer map viewer does not support this special logic, so it is not possible to demonstrate the VisibilityIndexOperator in Carmenta Explorer using both a dynamic cache mode and editable target areas – that is why the sample configuration does not use any dynamic cache mode.
If the ObserverInput is Null, you cannot use a dynamic cache mode, but you can cache the visualization in a TileLayer instead. However, if the target areas or any other input is modified, the application would have to flush the cache of the TileLayer since it is designed for static data.
Trading accuracy for speed
The VisibilityIndexOperator is potentially very slow. When configured to give high accuracy, the computation can take minutes, especially for large input polygons and elevations in high resolution. That can be acceptable if you do need high accuracy – in your application, the operator can be unconnected to the View and instead called on a background thread via GetFeatures, a progress bar can be displayed via EnableEvents, and the eventual result can be stored in a MemoryDataSet or as a GeoTIFF file for further use. However, when the highest accuracy is not needed, you can trade some accuracy for better speed.
First, note that the operator does not really compute the complete viewshed for each potential observer position: instead, it just estimates the viewshed size by tracing a number of sample Rays. So a simple way to trade accuracy for speed is to use fewer sample rays.
There are better opportunities to trade accuracy for speed if the raster data sources for elevations and tree heights have pre-built overviews with coarser resolutions. One way to exploit the overviews is to modify the value of FirstResolutionChange. This property controls how the operator can calculate in a high resolution near each observer position but use a series of overview resolutions farther away. A lower value of FirstResolutionChange means that the operator will switch to overview resolutions earlier, which should give less accuracy, and intuitively, one would expect to get higher speed as well. However, in some cases we have noticed that the speed can actually improve when the FirstResolutionChange is increased. So we can only recommend that you experiment with this property to find the fastest value for your application.
The basic resolution that is used near each observer is normally the finest one available from the elevation data, but you can choose a coarser basic resolution by increasing the DesiredInputResolution, which should give much better speed, but coarser and less accurate output.
Another, more conservative way to get a coarser output raster is to keep the original DesiredInputResolution, but increase the OutputResolutionFactor to 2, 4, or 8, etc. For example, when this factor is 4 and the basic resolution is 10 m, each output raster cell will be 40x40 m and cover 16 basic cells. The operator will then guess which of the 16 cells that has the highest visibility index, namely the one with the highest ground elevation, excluding those cells where the tree height is greater than the sensor height. The visibility index for this most promising basic cell will be computed in the basic 10 m resolution, and the result will be stored in the 40 m output cell. This strategy will limit the error caused by using a coarser output resolution. On the other hand, increasing the value of DesiredInputResolution instead will give a much better speed improvement, so you could afford to use a greater number of sample Rays to compensate.
In summary, it can be hard to find the property settings that will be the most accurate for a desired speed, or will be the fastest for a desired accuracy. However, you can always start by increasing the value of DesiredInputResolution, since this has the largest impact on the speed. See the property descriptions for more details.
Smoothing the isolines. The sample visibility_index_operator.px uses settings that are much faster than the ones used when creating the screenshots above, requiring half a second rather than half a minute, but also less accurate. Not only is the output coarser, but you can see some random noise in the raster values, which is evident especially over water where one would expect a smoother result. The noise can cause isolines to zig-zag in an annoying way. To smooth the isolines in the sample configuration, the visibility index raster is downsampled to lower resolution before the isolines are computed. The result can look like this:
![]() |
Literature
W. R. Franklin and C. K. Ray. Higher isn't necessarily better: Visibility algorithms and experiments.
In T. C. Waugh and R. G. Healey, eds., Advances in GIS Research: Sixth International Symposium on Spatial Data Handling, volume 2, pages 751-770. Taylor & Francis, 1994.W. R. Franklin, C. K. Ray and S. Mehta. Geometric algorithms for siting of air defense missile batteries.
Tech. report, Rensselaer Polytechnic Institute, Troy, New York, 1994.W. R. Franklin. Siting observers on terrain.
In D. Richardson and P. van Oosterom, eds., Advances in Spatial Data Handling: 10th International Symposium on Spatial Data Handling, pages 109–120. Springer-Verlag, 2002.W. R. Franklin, S. V. G. de Magalhães, and Wenli Li. Siting thousands of radio transmitter towers on terrains with billions of points.
Working paper submitted for publication, 2020.
Inheritance Hierarchy
System.Object (not available in C#)
EngineObject
Operator
VisibilityOperator
VisibilityIndexOperator
Platforms
Windows, Linux, Android
VisibilityIndexOperator Members
The VisibilityIndexOperator type has the following members.
Constructors
Name | Description |
---|---|
VisibilityIndexOperator | Initializes a new instance of the VisibilityIndexOperator class. |
Properties
Name | Description |
---|---|
Description | Gets or sets a short description of the operator. Inherited from Operator |
DesiredInputResolution | Gets or sets the desired resolution for the calculation nearest the observers, in meters. Inherited from VisibilityOperator |
DisplayName | Gets or sets a display name for the operator. Inherited from Operator |
DistanceVariation | Gets or sets a value that tells how the range of the observer varies with the direction (Constant or PhasedArray). Inherited from VisibilityOperator |
ElevationInput | Gets or sets the operator that provides elevation data. Inherited from VisibilityOperator |
EnableEvents | Gets or sets a flag that determines whether the operator shall fire events for a progress bar. Inherited from VisibilityOperator |
FirstResolutionChange | Gets or sets a value, telling how far away from the observer that the highest resolution should be used. Inherited from VisibilityOperator |
IsDisposed | Gets a value that tells whether the current VisibilityIndexOperator has been disposed. Inherited from EngineObject |
IsoMetadataDocument | Gets or sets the path to an ISO 19139 metadata document for the operator. Inherited from Operator |
MaxDistance | Gets or sets the maximal range of the observer, in meters. Inherited from VisibilityOperator |
Name | Gets or sets the name of the operator. Inherited from Operator |
NativeHandle | Gets the native Carmenta Engine kernel object the current VisibilityIndexOperator represents. Inherited from EngineObject |
ObserverHeightType | Gets or sets the type of height used for an observer (above sea-level, ground or treetops). Inherited from VisibilityOperator |
ObserverInput | Gets or sets the operator that gives observer polygons. |
OutputResolutionFactor | Gets or sets an integer factor that modifies the output resolution. |
PictureDirection | Gets or sets the main direction of observation. Inherited from VisibilityOperator |
PictureElevation | Gets or sets the elevation angle of the observer. Inherited from VisibilityOperator |
PictureHeight | Gets or sets the height of the picture that the observer sees. Inherited from VisibilityOperator |
PictureWidth | Gets or sets the width of the picture that the observer sees. Inherited from VisibilityOperator |
Rays | Gets or sets the number of sample rays per revolution. |
Refraction | Gets or sets a value that models the effects of refraction in the atmosphere. Inherited from VisibilityOperator |
SecondPictureBackwards | Gets or sets a flag, telling if the observer looks in two opposite directions at once. Inherited from VisibilityOperator |
SensorHeight | Gets or sets the sensor height. Inherited from VisibilityOperator |
TargetHeight | Gets or sets the assumed height of the targets. |
TargetHeightType | Gets or sets the height type of the target: above ground, above treetops, or above sea level. |
TargetInput | Gets or sets an operator that gives a target area represented by one or more polygon features. |
TreeHeightInput | Gets or sets an input operator that provides tree heights above ground. Inherited from VisibilityOperator |
IUserProperties.UserProperties | Gets the AttributeSet that contains the user properties. Inherited from IUserProperties |
VerticalRotationAxis | Gets or sets a flag that determines the exact shape of the rectangular cross-section of the lobe. Use True for a radar system with a vertical rotation axis; use False for a camera with fixed orientation. Inherited from VisibilityOperator |
VerticalUnit | Gets or sets the unit for elevations and vertical distances except maxUpDistance. Inherited from VisibilityOperator |
Methods
Name | Description |
---|---|
Clone | Creates a copy of an object. Inherited from EngineObject |
Dispose | Releases the reference to the native Carmenta Engine kernel instance the EngineObject represents. Inherited from EngineObject |
Equals | Determines whether this instance is equal to another. Inherited from EngineObject |
FindChildObject | Overloaded. Finds the child object with the specified name. Inherited from Operator |
FlushCache | Marks the layer as flushed which will release cached resources during the next update. Inherited from Operator |
GetChildObjects | Overloaded. Gets the child objects of the current object. Inherited from Operator |
GetFeatures | Overloaded. Gets features from the operator chain. Inherited from Operator |
GetLocalizedDescription | Gets a localized version of the operator description in a specific language. Inherited from Operator |
GetLocalizedDisplayName | Gets a localized version of the operator display name in a specific language. Inherited from Operator |
GetLocalizedIsoMetadataDocument | Gets the path to an ISO 19139 metadata document for a specific language. Inherited from Operator |
GetRasterFeature | Overloaded. Gets raster features from the operator chain and merges them into a single raster. Inherited from Operator |
HasLocalizedDescription | Checks if a localized version of the operator description is available in a specific language. Inherited from Operator |
HasLocalizedDisplayName | Checks if a localized version of the operator display name is available in a specific language. Inherited from Operator |
HasLocalizedIsoMetadataDocument | Checks if an ISO 19139 metadata document is available for a specific language. Inherited from Operator |
SetLocalizedDescription | Sets a operator description in a specific language. Inherited from Operator |
SetLocalizedDisplayName | Sets a operator display name in a specific language. Inherited from Operator |
SetLocalizedIsoMetadataDocument | Sets the path to an ISO 19139 metadata document for the operator, for a specific language. Inherited from Operator |