ProConcepts Voxel Layers - kataya/arcgis-pro-sdk GitHub Wiki
Voxel layers represent multidimensional spatial and temporal information as a 3D volumetric visualization. Their purpose, and usage relevant to the API are discussed.
Language: C#
Subject: Voxel Layers
Contributor: ArcGIS Pro SDK Team <[email protected]>
Organization: Esri, http://www.esri.com
Date: 11/24/2020
ArcGIS Pro: 2.7
Visual Studio: 2017, 2019
- Background
- Layer Creation via the API
- Voxel Layer Appearance
- Voxel Volume
- Slices
- Variable Profiles
- Isosurfaces
- Sections
Voxel layers were added to ArcGIS Pro at version 2.6 and are now added to the public API at version 2.7. Voxel layers can be created from any volumetric data stored in a netCDF formatted data file. To be compliant as a data source for a voxel layer, the netCDF data must be regularily gridded and follow the Climate and Forecast (CF) convention. These regularly gridded cubes, are "voxels". Each voxel cube represents a location in x,y,z where the z dimension can be either elevation/depth in real-world units, or level which is a derived elevation/depth value like isobaric pressure, or time.
The spacing between voxel cubes, also referred to as "voxels", in a voxel layer is identical but can vary between the different axes. In other words, the length of each voxel in the X dimension will always be the same and the width of each voxel in the Y dimension will always be the same but the lengths and widths can be different. The values associated with each voxel are stored as variables, each with a name, description, and data type. Variable data can be continuous (i.e. "quantitative") or discrete (i.e. "qualitative"). Continuous data is floating point and can represent analog values such as temperatures, gas concentrations, or isobaric pressure readings. Discrete data is categorical, and is any value type other than floating point, such as an integer classification scheme for soil or rock types. There can be many variables stored per voxel of both types, continuous and discrete. Refer to What is a voxel layer? for more information.
Working with voxel layers and the voxel api requires ArcGIS Pro version 2.7 or later. It also requires use of an advanced license level. When exploring 3D volumetric information, it is important that data is not distorted. Consequently, a voxel layer can only be added to a local scene with the same coordinate system as the voxel layer. If a voxel layer is added to a global scene or 2D map, the voxel layer will be unavailable in the Contents pane and will not draw. Likewise, if a local scene, containing a voxel layer, is converted to a global scene or map, the voxel layer will still show in the table of contents but will not draw.
Determining Voxel layer API requirements:
//In the Add-in config.daml - desktopVersion should be set to 2.7 or better
<AddInInfo id="{...}" version="1.0" desktopVersion="2.7">
//Map must be a local scene and license level must be advanced
bool canCreateVoxel = false;
if (MapView.Active.ViewingMode == MapViewingMode.SceneLocal) {
//license level must be advanced
canCreateVoxel = (ArcGIS.Core.Licensing.LicenseInformation.Level ==
ArcGIS.Core.Licensing.LicenseLevels.Advanced);
}
if (canCreateVoxel) {
//use the API
}
Layer creation for voxel layers follows the same general pattern(s) used for other ArcGIS Pro layers. Namely, a local Uri to the layer data source, currently limited to netCDF (.nc) files, is specified as the argument to a VoxelLayerCreationParams
. The VoxelLayerCreationParams is used as the argument to the LayerFactory templatized CreateLayer overload: T CreateLayer<T>(LayerCreationParams layerDef, ...)
. Set the type "T" to be VoxelLayer:
//Must be on the QueuedTask.Run()
var path = @"C:\MyData\AirQuality_Redlands.nc";
var createParams = VoxelLayerCreationParams.Create(path);
//Create the layer on a local scene
LayerFactory.Instance.CreateLayer<VoxelLayer>(createParams, local_scene);
Even though voxels within the voxel layer can contain more than one variable profile, only one variable profile can be rendered at any given time. The initial variable used for rendering, or "visualization", is called the default or the default (variable) profile. When creating the voxel layer, the default variable profile can be specified up-front via the VoxelLayerCreationParams.SetDefault(variable)
method. The default variable will automatically be set as the selected variable profile when the layer is created.
The VoxelLayerCreationParams provides a IReadOnlyList<VoxelVariableCreationParams> Variables
property that returns the list of all variable profiles available within the netCDF data source being used to create the voxel layer (the list of variables corresponds to the same list of variables populated on the Add Multidimensional Voxel Layer dialog box in the Pro UI). Unless otherwise specified, the first variable in the list of variables will be used as the default variable profile when the voxel layer is created.
//Must be on the QueuedTask.Run()
//Create a VoxelLayerCreationParams and set the default variable profile
var path = @"C:\MyData\AirQuality_Redlands.nc";
var createParams = VoxelLayerCreationParams.Create(path);
createParams.IsVisible = true;
//Set the default variable to be the last variable in the list
createParams.SetDefaultVariable(createParams.Variables.Last());
//Create the layer
LayerFactory.Instance.CreateLayer<VoxelLayer>(createParams, local_scene);
Unwanted voxel variables can be removed from the layer by setting the VoxelVariableCreationParams.IsSelected
property for the respective variable(s) false. Once the voxel layer is created, the list of variables it contains cannot be changed.
When a voxel layer is added to a scene, assuming its visibility is set to true, the data for (the selected variable for) each voxel must be sampled to determine how each voxel will be rendered. Voxel data sets can be very large and cover large surface areas. Therefore the creation of a renderer for a voxel layer is not instantaneous (or even quasi-instantaneous) and will vary depending on the size and complexity of the voxel data. If immediate access to the renderer is required after layer creation, add-ins should subscribe to the MapMemberPropertiesChangedEvent
event. The event will fire with a MapMemberEventHint.Renderer
event hint to indicate that the voxel renderer for the current default variable has been initialized and can be safely accessed. Immediately attempting to access the voxel renderer on a newly created voxel layer (or a voxel layer that has not previously been rendered in the current Pro session on the given default variable) can return null or a renderer with values that have not been fully provisioned.
//Must be on the QueuedTask.Run()
//Subscribe to the MapMemberPropertiesChangedEvent, eg in your dockpane or module initialize
ArcGIS.Desktop.Mapping.Events.MapMemberPropertiesChangedEvent.Subscribe((args) => {
//A MapMemberEventHint.Renderer event hint indicates that the voxel layer renderer is ready
if (args.EventHints.Any(hint => hint == MapMemberEventHint.Renderer)) {
//TODO update dockpane UI, etc.
}
});
On the Pro UI, when a voxel layer is selected in the TOC, the "Voxel Layer" tab group is activated which includes an "Appearance" tab. The appearance tab provides a number of different options to configure vertical offset, vertical exaggeration, and various lighting options. These same properties are likewise accessible via the api and are described in this section.
(Refer to Change the appearance of a voxel layer for use of these settings from the Pro UI.)
Elevation
Elevation settings include the cartographic offset and vertical exaggeration. Cartographic offset is an absolute value that raises or lowers the voxel layer relative to the ground surface. A positive value raises the voxel layer, while a negative value lowers the voxel layer. Typically, cartographic offset is set to 0 meters.
Vertical exaggeration is used to scale the height of the voxels. This can be useful if the Z dimension is small or the voxel layer is being compared with other data, such as borehole data, and the exaggeration factors need to match. The default vertical exaggeration value is initially set as a proportion of the x,y extent of the layer and operates in two modes for voxel layers (in the UI, this is configured via the layer properties elevation tab: “From minimum height” and “Z-coordinates”):
-
"Z-coordinates" mode or
ArcGIS.Core.CIM.ExaggerationMode.ScaleZ
. This is the same mode as used by other 3D layers. In "Z-coordinates" mode the entire Z is exaggerated. So if the Z for a given voxel is 10 and the vertical exaggeration is set to 500, the exaggerated Z for the voxel would be 5000. The voxel height and Z position are changed. -
“From minimum height” mode or
ArcGIS.Core.CIM.ExaggerationMode.ScaleVoxelHeight
. In this mode only the voxels themselves are exaggerated and not the space between Z = 0 and the minimum height of the voxel. In other words, only the voxel's height is changed and not its Z position (as in the "Z-coordinates" mode which changes both). This mode is useful when viewing the voxel layer together with a basemap, for example.
//var voxelLayer = ... ;
//Must be on the QueuedTask.Run()
//Offset
var offset = voxelLayer.CartographicOffset;
//Increase the offset by 100 meters
voxelLayer.SetCartographicOffset(offset + 100.0);
var exaggeration = voxelLayer.VerticalExaggeration;
//apply an exaggeration - increase by 100
voxelLayer.SetVerticalExaggeration(exaggeration + 100.0);
//Change the mode - this must be done via the CIM
var def = voxelLayer.GetDefinition() as CIMVoxelLayer;
def.Layer3DProperties.ExaggerationMode = ExaggerationMode.ScaleZ;
//note: vertical exaggeration can also be set via the CIM...
//def.Layer3DProperties.VerticalExaggeration = exaggeration + 100.0;
//apply the change
voxelLayer.SetDefinition(def);
Lighting
Lighting settings include Diffuse and Specular lighting options. Each option can be enabled or disabled, same as on the UI, and is set as a ratio from 0.0 to 1.0 (scaled as a percentage on the UI). Diffuse lighting values increase or decrease the scattering of reflected light from the voxel surface. Higher diffuse lighting values replicate the effect of a rough or convoluted surface with increased scattering resulting in a (more) matte effect. Specular lighting values work exactly the opposite and increase or decrease the concentration of light reflected at the same angle from the surface (i.e. scattering is reduced). Higher specular lighting values replicate the effect of a smooth or regular surface and result in a bright, shiny, or metallic effect. Thus, increasing the Diffuse lighting value is similar to increasing the voxel surface roughness whereas increasing the specular lighting values is similar to increasing the voxel surface smoothness. Although they are not mutually exclusive, diffuse and specular lighting values do have an opposite effect.
//var voxelLayer = ... ;
//Must be on the QueuedTask.Run()
//Diffuse Lighting
if (!voxelLayer.IsDiffuseLightingEnabled)
voxelLayer.SetDiffuseLightingEnabled(true);
var diffuse = voxelLayer.DiffuseLighting;
//set Diffuse lighting to a value between 0 and 1
voxelLayer.SetDiffuseLighting(0.1); //10%
//Specular Lighting
if (!voxelLayer.IsSpecularLightingEnabled)
voxelLayer.SetSpecularLightingEnabled(true);
var specular = voxelLayer.SpecularLighting;
//set Diffuse lighting to a value between 0 and 1
voxelLayer.SetSpecularLighting(0.8); //80%
Variable Profile
Voxels, beyond their dimensions and location, can have both quantitative and/or qualitative data values associated with them, referred to as "variables" or "variable profiles". The active variable is known as the selected variable profile and is shown as the currently selected variable in the variable profile combo box on the appearance tab. When a voxel layer is created, its default variable profile is automatically selected (which can be customized). The selected variable is accessible off the layer via the voxelLayer.SelectedVariableProfile
property. Voxel variables are detailed in the Variable Profile section.
The voxel volume is determined by the extent of its x, y and z dimensions. The voxel volume represents the extent of the voxel layer in "voxel space". Voxel space ranges from (0, 0, 0) to (max X, max Y, max Z) dimension values respectively - as specified by voxelLayer.GetVolumeSize()
. The x and y dimensions always represent coordinate values (in voxel space) however, the Z dimension can be an elevation or time value associated with the voxel.
The voxel volume retrieved via voxelLayer.GetVolumeSize()
returns a tuple<double, double, double>
representing the max x, y, and z dimension value respectively. Voxels are always spaced at regular intervals along their x and y dimensions (albeit the x and y dimensions may differ). The z dimension can often be irregular. When rendering the voxel layer, the z dimension is "regularized" by dividing the total height of the volume by the number of voxels. The number of voxels is determined from the number of z intervals in the data. As mentioned above, the minimum voxel location is always at (0, 0, 0):
//Get the first voxel layer in the TOC
voxelLayer = MapView.Active.Map.GetLayersAsFlattenedList().OfType<VoxelLayer>().FirstOrDefault();
if (voxelLayer == null)
return;
QueuedTask.Run(() => {
//access the volume
(double x, double y, double z) = voxelLayer.GetVolumeSize();
//TODO - use the dimensions
...
Note: The returned voxel z dimension max value is not affected by any current vertical exaggeration setting being applied.
As voxel layers often cover large areas, it can be useful to define an area of interest to view the volume for a specific area rather than the entire dataset. Areas of interest are created on a voxel layer using "slices". Voxels can be sliced vertically to reduce the surface area and horizontally to reduce the voxel layer height. When a slice is applied to a voxel layer, the voxel volume is visually cut, exposing its interior. The volume that is cut will be between the slice and the camera and the volume that is retained will be on the opposite side of the slice (away from the camera). Slices are applied to the voxel volume when its visualization mode is set to VoxelVisualization.Volume
.
To create a slice, an add-in creates a SliceDefinition
with a name, position within, or on, the voxel layer (given in voxel coordinates*) and a unit vector normal representing both the orientation of the slice and the tilt of the slice face. Orientation runs clockwise from North at 0 degrees through West, South, and East at 90, 180, and 270 degrees respectively. Tilt runs from 0 degrees (vertical) to +/- 90 degrees (horizontal - facing up/down respectively). Developers can use voxelLayer.GetNormal(orientation, tilt)
and voxelLayer.GetOrientationAndTilt(unit_vector)
to convert back and forth between orientation and tilt and a unit normal vector. Slices are added to the slice container within the voxel layer legend on the TOC.
//Get the first selected voxel layer in the TOC
var voxelLayer = MapView.Active.GetSelectedLayers().OfType<VoxelLayer>().FirstOrDefault();
if (voxelLayer == null)
return;
await QueuedTask.Run(()=> {
//make sure the visualization is set to volume not surface
if (voxelLayer.Visualization != VoxelVisualization.Volume)
voxelLayer.SetVisualization(VoxelVisualization.Volume);
//expand the slice container in the TOC and set its visibility to true.
voxelLayer.SetSliceContainerExpanded(true);
voxelLayer.SetSliceContainerVisibility(true);
//Create a slice that cuts the volume in two
(double x, double y, double z) = voxelLayer.GetVolumeSize();
var slice_pos = new Coordinate3D(x / 2, y / 2, z / 2);
//Create a normal unit vector w/ orientation and tilt of 0.0 degrees
//The slice will be vertical and oriented along the North/South plane...
var normal = voxelLayer.GetNormal(0.0, 0.0);
//Create the slice definition and apply it to the voxel layer
voxelLayer.CreateSlice(new SliceDefinition() {
Name = "Mid Slice",
VoxelPosition = slice_pos,
Normal = normal,
IsVisible = true
});
});
*Recall: Voxel space ranges from (0, 0, 0) to (max X, max Y, max Z) as specified by voxelLayer.GetVolumeSize()
.
There is no limit to the number of slices that can be created. Slices can be used to visualize areas of interest for all variable profiles.
The collection of slices on a given voxel layer can be retrieved via voxelLayer.GetSlices()
. The slice currently selected in the TOC can be retrieved via mapView.GetSelectedSlices().FirstOrDefault()
. To change the properties of a slice, such as its orientation and/or tilt, retrieve the slice definition via GetSlices()
, apply the relevant updated value(s) to the slice definition, and apply the change(s) by calling voxelLayer.UpdateSlice(slice)
.
In the following code snippet, the selected slice has its existing orientation changed by +10 degrees and the tilt is changed to 45 degrees:
var voxelLayer = ...;
//Must be on the QueuedTask.Run(); Visualization should be Volume
//Retrieve the slice to be updated
var slice = voxelLayer.GetSlices().First(s => s.Name == "My slice");
//or if it is selected in the TOC use: mapView.GetSelectedSlices().FirstOrDefault()
//Compute an updated normal - convert the normal into orientation and tilt first...
//change their values...convert _back_ to a normal
(double orientation, double tilt) = voxelLayer.GetOrientationAndTilt(slice.Normal);
slice.Normal = voxelLayer.GetNormal(orientation + 10.0, 45.0);
//Commit the change
voxelLayer.UpdateSlice(slice);
To delete a slice, retrieve the relevant slice definition and call voxelLayer.DeleteSlice(slice)
. In this snippet, all the slices currently defined on the layer, are deleted:
var voxelLayer = ...;
QueuedTask.Run(()=> {
//Delete all slices
var slices = voxelLayer.GetSlices();
foreach (var slice in slices)
voxelLayer.DeleteSlice(slice);
...
By default, whenever a voxel slice, section, or surface is created - whether via the UI or API - the voxel exploration pane is activated to make the properties of the slice, section, or surface immediately editable. This allows users to fine-tune and adjust the given object's position within the voxel layer, as well as its orientation, and tilt.
If your add-in has a custom dockpane, that is currently the active dockpane, you may not wish to have the voxel exploration pane automatically activate - especially if its activation results in the exploration pane covering your dockpane on the Pro UI. To prevent the voxel exploration pane automatically activating, set the voxelLayer.AutoShowExploreDockPane
property to false. This setting cannot be overridden in the UI. To revert to the default activation behavior, set voxelLayer.AutoShowExploreDockPane
back to true.
var voxelLayer = ...;
//Prevent the exploration pane from auto-activating
voxelLayer.AutoShowExploreDockPane = false;
//TODO - create slice, section, or surface
//When needed, set auto-activation behavior back
voxelLayer.AutoShowExploreDockPane = true;
Data values associated with voxels are stored as variables, or variable profiles, within the voxel layer. Each variable is represented by a VoxelVariableProfile
class instance. VoxelVariableProfiles have a name, description, and data type. The data type can be either VoxelVariableDataType.Continuous
or VoxelVariableDataType.Discrete
. Variables that are continuous contain floating point values; that are analog ("quantitative") in nature; and are rendered using a stretch renderer of type CIMVoxelStretchRenderer
. Variables that are discrete have values other than floating point; the values are qualitative in nature, usually deriving from class categories. Discrete variables are rendered using a unique value renderer of type CIMVoxelUniqueValueRenderer
. The collection of available variables within a given voxel layer is accessed by the voxel layer's GetVariableProfiles()
method.
The currently selected variable profile controls the rendering of the voxel layer to include any associated surfaces and/or (dynamic) sections created on the layer (both of which are described later in this document). The currently selected variable profile is accessed off the voxel layer via the SelectedVariableProfile
property and set via voxelLayer.SetSelectedVariableProfile(variable)
. The currently selected variable profile will be shown on the UI on the voxel layer Appearance tab and its name will be listed in the voxel layer legend on the TOC. Only one variable profile can be "selected" or active at any one time.
//var voxelLayer = ... ;
//Must be on the QueuedTask.Run()
//Get the currently selected variable profile
var variable = voxelLayer.SelectedVariableProfile;
//Set a variable profile "selected"...
voxelLayer.SetSelectedVariableProfile(variable);
In all other layers with renderers (or colorizers), there is only one renderer (or colorizer) and it is accessed off the layer. In the voxel layer, there can be multiple renderers, one per variable, and each renderer is accessed off the variable whose values it is rendering. If the variable profile is using continuous data, identified by variable.DataType == VoxelVariableDataType.Continuous
, the renderer will be a CIMVoxelStretchRenderer
. If the variable profile is using discrete data, identified by variable.DataType == VoxelVariableDataType.Discrete
, the renderer will be a CIMVoxelUniqueValueRenderer
. A renderer is accessed via variable.Renderer
. Changes made to the renderer are applied back to the variable profile (not the layer) by calling variable.SetRenderer(renderer)
.
//var voxelLayer = ... ;
//Must be on the QueuedTask.Run()
var variable = voxelLayer.GetVariableProfiles().First();
//Get the renderer off the _variable_ not the layer...
var renderer = variable.Renderer;
//DataType and Renderer type are related...
if (variable.DataType == VoxelVariableDataType.Continuous) {
//Renderer will be stretch
var stretchRenderer = renderer as CIMVoxelStretchRenderer;
...
}
else if (variable.DataType == VoxelVariableDataType.Discrete) {
//Renderer will be unique value
var uvr = renderer as CIMVoxelUniqueValueRenderer;
...
Similar to unique value renderers on a feature layer, voxel unique value renderers, CIMVoxelUniqueValueRenderer
, assign a unique color (but not symbol) to each class, represented as a CIMVoxelColorUniqueValue
. There is one CIMVoxelColorUniqueValue
defined per discrete data value in the variable data array. Individual unique value classes can be turned on or off to display all unique values or just a subset via the individual CIMVoxelColorUniqueValue.Visible
properties. Default colors are assigned to each unique (or "discrete") value based on the CIMVoxelUniqueValueRenderer.ColorRamp
that is currently assigned. In the UI, the color ramp is assigned via the Color scheme combo on the symbology dockpane. Note: If a transparency is applied to a given CIMVoxelColorUniqueValue.Color
, the transparencies of individual voxels can overlap which can result in a slightly more opaque appearance.
In this snippet, the visibility of the first unique value class is set to false.
//var voxelLayer = ... ;
//Must be on the QueuedTask.Run()
//get the selected variable profile
var variable = voxelLayer.SelectedVariableProfile;
//Must be discrete to have a uvr
if (variable.DataType != VoxelVariableDataType.Discrete)
return;
var renderer = variable.Renderer as CIMVoxelUniqueValueRenderer;
//Arbitrarily, choose the first class
var unique_value_class = renderer.Classes.ToList().First();
//Turn its visibility off
unique_value_class.Visible = false;
//Apply the change to the renderer
renderer.Classes = classes.ToArray();
//apply the changes
variable.SetRenderer(renderer);
Note: When switching to a voxel variable with a unique value renderer for the first time, unique values from the variable profile are dynamically loaded. So, depending on the size of the voxel layer, all the unique value classes may not be available if the renderer is immediately accessed. A ArcGIS.Desktop.Mapping.Events.MapMemberPropertiesChangedEvent
event will fire, with hint type MapMemberEventHint.Renderer
, when the renderer is initialized and all unique value classes are available.
Continuous variable data is rendered using a CIMVoxelStretchRenderer
. A stretch renderer "stretches" a range of colors (selected from the current color scheme) between a maximum and minimum color range value. The color range falls within the minimum and maximum data range on the renderer, computed from the entire range of values present in the variable data array. The data range and color range can both be adjusted on the renderer as needed. By default the data range of the renderer covers +/- 4 standard deviations (change this on the UI by unchecking the More->ShowSignificantDataRange checkbox) but that is not a hard and fast rule and can vary depending on the nature of the data in the given voxel variable. Typically, the color range is set as a subset of the current data range on the renderer otherwise colors would end up being assigned to data values that are not present in the variable data array. However, there may be cases where this is desirable if the add-in wants to filter out specific color values for visual analytic purposes.
In this snippet, the color range of a stretch renderer is increased by 10%:
//var voxelLayer = ... ;
//Must be on the QueuedTask.Run()
//get the selected variable profile
var variable = voxelLayer.SelectedVariableProfile;
//Must be continuous to have a stretch renderer
if (variable.DataType != VoxelVariableDataType.Continuous)
return;
var renderer = variable.Renderer as CIMVoxelStretchRenderer;
var color_min = renderer.ColorRangeMin;
var color_max = renderer.ColorRangeMax;
//increase color range by 10% of the current difference
var dif = (color_max - color_min) * 0.05;
color_min -= dif;
color_max += dif;
//make sure we our color range does not exceed the current
//renderer data range
if (color_min < variable.GetVariableStatistics().MinimumValue)
color_min = variable.GetVariableStatistics().MinimumValue;
if (color_max > variable.GetVariableStatistics().MaximumValue)
color_max = variable.GetVariableStatistics().MaximumValue;
//update the renderer
renderer.ColorRangeMin = color_min;
renderer.ColorRangeMax = color_max;
//apply the changes
variable.SetRenderer(renderer);
As with voxel unique value renderers, a ArcGIS.Desktop.Mapping.Events.MapMemberPropertiesChangedEvent
event will fire, with hint type MapMemberEventHint.Renderer
, when all of the voxel variable data has been sampled and the renderer is initialized with the data and color ranges defined.
Note: the corresponding color assigned to a given value by the stretch renderer can always be retrieved using variable.GetIsosurfaceColor(value)
.
Isosurfaces allow for the preservation of a single data value within the voxel layer which can be visualized, and compared against, the currently selected variable profile. An isosurface can be created for any specific value within a continuous variable data type. All voxels represented in an isosurface have the same value (i.e. the surface is homogenous). Up to variable.MaxNumberOfIsosurfaces
isosurfaces can be created per variable profile (currently, MaxNumberOfIsosurfaces is set at 4). Isosurfaces cannot be created for discrete variable data types though isosurfaces for discrete data are not necessary. Each discrete value can be visualized independently (just like isosurfaces) by manipulating the visibility of the discrete values within the renderer.
To create an isosurface, add-ins call variable.CreateIsosurface(...)
with an IsosurfaceDefinition that has been assigned the specific data value to be visualized. The layer visualization mode must be set to VoxelVisualization.Surface
. By default, a color is assigned to the surface based on the current color theme. Add-ins can also assign their own color to the isosurface as needed (e.g. to differentiate the isosurface from (locked) sections defined on the variable). The isosurface color can be color-locked to prevent it from being changed if the color scheme of the stretch renderer (of the parent variable) is changed.
To check whether an isosurface can be created for a given variable profile, call variable.CanCreateIsosurface
. CanCreateIsoSurface checks that the variable data type is Continuous; that the current layer visualization mode is Surface; and that the variable.MaxNumberofIsoSurfaces limit has not been reached. Attempting to create a surface for a variable where variable.CanCreateIsosurface
returns false will result in an exception.
//var voxelLayer = ... ;
//Must be on the QueuedTask.Run()
//Get the variable profile on which to create the iso surface
var variable = voxelLayer.SelectedVariableProfile;
//Visualization mode must be surface
voxelLayer.SetVisualization(VoxelVisualization.Surface);
double surface_value = ....;
//Check surface can be created
if (variable.CanCreateIsosurface) {
//Do the create
variable.CreateIsosurface(new IsosurfaceDefinition() {
Name = $"Surface for {surface_value}",
Value = surface_value,
IsVisible = true
});
}
In this example, a custom color is assigned and the isosurface definition is color locked to prevent the color from changing:
//var voxelLayer = ... ;
//Must be on the QueuedTask.Run()
var variable = voxelLayer.SelectedVariableProfile;
if (variable.CanCreateIsosurface) {
//Do the create
variable.CreateIsosurface(new IsosurfaceDefinition() {
Name = $"Surface for {surface_value}",
Value = surface_value,
Color = ColorFactory.Instance.CreateColor(System.Windows.Media.Colors.CornflowerBlue),
IsCustomColor = true,//lock the color
IsVisible = true
});
}
Isosurfaces are added to the voxel layer isosurface container within the TOC. Check that the isosurface container is visible and expanded (or collapsed) via voxelLayer.IsIsosurfaceContainerVisible
and voxelLayer.IsIsosurfaceContainerExpanded
respectively. In this example, the visibility and expanded state of the isosurface container is being toggled:
//var voxelLayer = ... ;
//Must be on the QueuedTask.Run()
voxelLayer.SetIsosurfaceContainerExpanded(!voxelLayer.IsIsosurfaceContainerExpanded);
voxelLayer.SetIsosurfaceContainerVisibility(!voxelLayer.IsIsosurfaceContainerVisible);
The collection of surfaces currently defined on a variable can retrieved via variable.GetIsosurfaces()
. To update a surface, change the desired property - name, value, color, and so forth - and apply the surface change via variable.UpdateIsoSurace(...)
.
An isosurface can be deleted via variable.DeleteIsoSurface(...)
.
//var voxelLayer = ... ;
//Must be on the QueuedTask.Run()
//var variable = ....;
var surface = variable.GetIsosurfaces().First();
//Change the iso surface voxel value
surface.Value = surface.Value * 0.9;
//update the surface
variable.UpdateIsosurface(surface);
//Delete an iso surface
variable.DeleteIsosurface(variable.GetIsosurfaces().Last());
A section creates a two-sided vertical or horizontal plane cutting through the voxel layer. Sections are not tied to a variable (unless they are locked), like isosurfaces are, but to their specific location within (or "across") the voxel layer. Sections can reveal valuable information, such as aquifers across a subsurface profile or methane distribution within a riverine water column. Sections can only be visualized and created when the visualization mode is set to VoxelVisualization.Surface
.
The creation process for a section is identical to the creation process of a slice except that the add-in creates a SectionDefinition
rather than a SliceDefinition
and calls voxelLayer.CreateSection(...)
. Same as for slices, the position of a section is specified in voxel-space coordinates and its orientation and tilt are specified as a unit vector normal. As mentioned, developers can use voxelLayer.GetNormal(orientation, tilt)
and voxelLayer.GetOrientationAndTilt(unit_vector)
to convert back and forth between orientation and tilt and a unit normal vector.
The symbology of a section will match whatever is the current symbology of the volume (unless the section is locked). Sections are added to the sections container within the voxel layer TOC and each section is uniquely identified with a "system assigned" id, generated when the section is created. Developers can prevent the voxel exploration dockpane from activating when a section is created by setting voxelLayer.AutoShowExploreDockPane
to false. Refer to Voxel Exploration Dockpane for more information.
//var voxelLayer = ... ;
//Must be on the QueuedTask.Run()
//Set visualization to surface and expand the section container and set
//its visibility true
if (voxelLayer.Visualization != VoxelVisualization.Surface)
voxelLayer.SetVisualization(VoxelVisualization.Surface);
voxelLayer.SetSectionContainerExpanded(true);
voxelLayer.SetSectionContainerVisibility(true);
//To stop the Voxel Exploration Dockpane activating use:
voxelLayer.AutoShowExploreDockPane = false;
//Create a section that cuts the volume in two on the vertical plane
var volume = voxelLayer.GetVolumeSize();
//Orientation 90 degrees (due West), Tilt 0 degrees (vertical)
var normal = voxelLayer.GetNormal(90, 0.0);
voxelLayer.CreateSection(new SectionDefinition() {
Name = "Middle Section",
VoxelPosition = new Coordinate3D(volume.Item1 / 2, volume.Item2 / 2, volume.Item3 / 2),
Normal = normal,
IsVisible = true
});
In the next example, 3 sections are created. Two are vertical and perpendicular to each other in the x,y plane bisecting the voxel layer. The third is a horizontal plane, bisecting the voxel layer horizontally.
//var voxelLayer = ... ;
//Must be on the QueuedTask.Run()
var volume = voxelLayer.GetVolumeSize();
//Make the Normals - each is a Unit Vector (x, y, z)
var north_south = new Coordinate3D(1, 0, 0);
var east_west = new Coordinate3D(0, 1, 0);
var horizontal = new Coordinate3D(0, 0, 1);//or (0,0,-1) - face down
int n = 0;
foreach (var normal in new List<Coordinate3D> { north_south, east_west, horizontal }) {
voxelLayer.CreateSection(new SectionDefinition() {
Name = $"Cross {++n}",
VoxelPosition = new Coordinate3D(volume.Item1 / 2, volume.Item2 / 2, volume.Item3 / 2),
Normal = normal,
IsVisible = true
});
}
The collection of sections for a voxel layer can be retrieved via voxelLayer.GetSections()
. The section currently selected in the TOC can be retrieved via mapView.GetSelectedSections().FirstOrDefault()
.
var voxelLayer = ...;
var section = voxelLayer.GetSections().FirstOrDefault();
var section2 = voxelLayer.GetSections().First(sec => sec.Id == my_section_id);
The voxel layer containing the selected section (in the TOC) can be retrieved via the sectionDefinition.Layer
property. This is useful if there is more than one voxel layer in the TOC and the add-in does not have the context of which voxel layer contains the selected section (slice definitions and isosurface definitions have an identical Layer
property). To change the properties of a section, including its orientation and/or tilt, update the relevant value(s) and apply the change by calling voxelLayer.UpdateSection(section)
. In the following code snippet, the selected section has its existing orientation and tilt updated to 45 degrees:
//Must be on the QueuedTask.Run(); Visualization should be Surface
//Retrieve the selected section in the TOC
var section = mapView.GetSelectedSections().First();
//Compute an updated normal - this example uses the "Layer"
//property to access the section's associated layer
section.Normal = section.Layer.GetNormal(45.0, 45.0);
//Apply the change
section.Layer.UpdateSection(section);
To delete a section, retrieve the section definition and call voxelLayer.DeleteSection(section)
.
//Retrieve the selected section in the TOC
var section = mapView.GetSelectedSections().First();
//Delete it
QueuedTask.Run(()=> {
//Delete the selected section
section.Layer.DeleteSection(section);
...
To create a permanent snapshot of a specific variable, in space and time, a section can be locked. Sections are locked by calling voxelLayer.LockSection(section)
which adds a new "locked" section entry to the voxel layer LockedSection
container. The LockedSection container is a child container within the voxel layer legend on the TOC. When a section is locked, its corresponding "dynamic" section entry is deleted and removed from the collection of sections. Check if a section can be locked by calling voxelLayer.CanLockSection(section)
.
//var voxelLayer = ... ;
//Must be on the QueuedTask.Run()
if (voxelLayer.Visualization != VoxelVisualization.Surface)
voxelLayer.SetVisualization(VoxelVisualization.Surface);
voxelLayer.SetSectionContainerExpanded(true);
voxelLayer.SetLockedSectionContainerExpanded(true);
voxelLayer.SetLockedSectionContainerVisibility(true);
//lock the currently selected section
var section = MapView.Active.GetSelectedSections().First();
//Locking the section creates a locked section, and deletes
//the section entry.
if (voxelLayer.CanLockSection(section))
voxelLayer.LockSection(section);
Once a section is locked, its position and normal cannot be changed. Additionally, A locked section has its symbology “fixed” to the symbology of the selected variable profile when the section was created. The selected variable profile when the section was created is associated with the locked section via its lockedSectionDefinition.VariableName
property and is referred to as the locked section’s parent variable. Changing the currently selected variable, after the section has been locked, will not change the locked section's symbology. Only changing the symbology of the originally selected variable, i.e. the parent variable, will change the symbology of the locked section. As the locked section's symbology does not change when different variables are selected, locked section "snapshots" can be visually compared across different variable profiles.
//Must be on the QueuedTask.Run()
//Get the currently selected locked section
var locked_section = MapView.Active.GetSelectedLockedSections().First();
//Get the variable profile associated with the locked section
var variable = locked_section.Layer.GetVariableProfile(locked_section.VariableName);
//set it selected
locked_section.Layer.SetSelectedVariableProfile(variable);
The collection of locked sections is accessed via voxelLayer.GetLockedSections()
. Locked section name, visibility, and expanded/collapsed state in the TOC can be updated. Set these properties on the locked section definition and apply via voxelLayer.UpdateSection(locked_section)
.
//var voxelLayer = ...;
//Must be on the QueuedTask.Run()
var locked_sections = voxelLayer.GetLockedSections();
//Toggle all locked sections' visibility
foreach (var locked_section in locked_sections) {
locked_section.IsVisible = !locked_section;
//apply the change
voxelLayer.UpdateSection(locked_section);
}
The position and normal of a locked section cannot be changed. To update the position and normal of a locked section, it must be unlocked. When a locked section is unlocked, the "locking" process is reversed. The locked section being unlocked will be removed (from the collection of locked sections) and a (dynamic) section entry for the previously locked section is created (with the same id as before) in the collection of sections for the voxel layer. When, or if, a locked section is unlocked, its symbology changes to the symbology of whichever variable profile is currently selected. Sections are unlocked by calling voxelLayer.UnlockSection(locked_section)
. Check that a section can be unlocked by calling voxelLayer.CanUnlockSection(locked_section)
.
//Must be on the QueuedTask.Run()
//var locked_section = ...;
//Unlock the locked section (Deletes the locked section entry, (re)creates
//a section entry
if (voxelLayer.CanUnlockSection(locked_section))
voxelLayer.UnlockSection(locked_section);
To delete a locked section entry directly, add-ins can use voxelLayer.DeleteSection(locked_section)
.
//var voxelLayer = ...;
//Must be on the QueuedTask.Run()
if (voxelLayer.GetLockedSections().Count() == 0)
return;
//Delete the last locked section from the collection of
//locked sections
voxelLayer.DeleteSection(voxelLayer.GetLockedSections().Last());