FitToHeightMap
Repository source: FitToHeightMap
Description¶
This example uses vtkFitToHeightMapFilter to cover a height map with vtkPolyData generated by a vtkPlaneSource. The plane's origin, point1 and point2 are calculated from the DEM's bounding box. The z coordinate of each parameter is set to the height of the DEM. With this plane, the example uses vtkProbeFilter to apply the scalar elevation data from the DEM.
vtkWarpScalar is used to "elevate" the DEM mesh. Once the planes are draped over the warped DEM mesh, they are displayed side-by-side using the same vtkLookupTable.
The displayed meshes are original (left), point fit (middle) and cell fit (right).
Note
DEM files for the United States are available here.
Info
This example requires vtk version 8.2 or newer.
Other languages
See (Cxx)
Question
If you have a question about this example, please use the VTK Discourse Forum
Code¶
FitToHeightMap.py
#!/usr/bin/env python3
from dataclasses import dataclass
from pathlib import Path
# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkCommonCore import vtkLookupTable
from vtkmodules.vtkFiltersCore import vtkProbeFilter
from vtkmodules.vtkFiltersGeneral import vtkWarpScalar
from vtkmodules.vtkFiltersGeometry import vtkImageDataGeometryFilter
from vtkmodules.vtkFiltersModeling import vtkFitToHeightMapFilter
from vtkmodules.vtkFiltersSources import vtkPlaneSource
from vtkmodules.vtkIOImage import vtkDEMReader
from vtkmodules.vtkInteractionWidgets import (
vtkTextRepresentation,
vtkTextWidget)
from vtkmodules.vtkRenderingCore import (
vtkActor,
vtkPolyDataMapper,
vtkRenderWindow,
vtkRenderWindowInteractor,
vtkRenderer,
vtkTextActor,
vtkTextProperty
)
def get_program_parameters():
import argparse
description = 'Cover a height map with vtkPolyData generated by a vtkPlaneSource.'
epilogue = '''
'''
parser = argparse.ArgumentParser(description=description, epilog=epilogue,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('filename', help='SainteHelens.dem.')
args = parser.parse_args()
return args.filename
def main():
file_name = get_program_parameters()
fn = Path(file_name)
if not fn.is_file():
print(f'{fn} not found.')
return
# Create the RenderWindow, Renderer
#
colors = vtkNamedColors()
renderers = [
vtkRenderer(viewport=(0, 0, 1.0 / 3.0, 1), background=colors.GetColor3d('Wheat')),
vtkRenderer(viewport=(1.0 / 3.0, 0, 2.0 / 3.0, 1), background=colors.GetColor3d('BurlyWood')),
vtkRenderer(viewport=(2.0 / 3.0, 0, 1, 1), background=colors.GetColor3d('Tan')),
]
render_window = vtkRenderWindow(size=(1200, 400), window_name='FitToHeightMap')
for i in range(0, 3):
render_window.AddRenderer(renderers[i])
interactor = vtkRenderWindowInteractor()
interactor.render_window = render_window
# Create the pipelines. Load terrain data.
lut = vtkLookupTable(hue_range=(0.6, 0), saturation_range=(1.0, 0), value_range=(0.5, 1.0))
# Read the data: a height field results.
dem_reader = vtkDEMReader(file_name=file_name)
dem_reader.update()
surface = vtkImageDataGeometryFilter()
# Warp the surface in the vertical direction.
warp = vtkWarpScalar(scale_factor=1, use_normal=True, normal=(0, 0, 1))
# Show the terrain
scalar_range = dem_reader.output.scalar_range
dem_mapper = vtkPolyDataMapper(scalar_visibility=True, scalar_range=scalar_range, lookup_table=lut)
dem_reader >> surface >> warp >> dem_mapper
dem_actor = vtkActor(mapper=dem_mapper)
# Create polygon(s) to fit. z-values should be height of DEM
# to accommodate ProbeFilter.
z_level = dem_reader.GetOutput().GetBounds()[5]
dem_bounds = dem_reader.output.bounds
plane = vtkPlaneSource(origin=(dem_bounds[0], dem_bounds[2], z_level),
point1=(dem_bounds[1], dem_bounds[2], z_level),
point2=(dem_bounds[0], dem_bounds[3], z_level),
resolution=(128, 128))
# # Get the scalars from the DEM
probe_dem = vtkProbeFilter(source_data=dem_reader.output)
plane >> probe_dem
# Fit polygons to surface using two strategies.
# Both will share the same lookup tables.
point_fit = vtkFitToHeightMapFilter(height_map_connection=dem_reader.output_port, use_height_map_offset=True)
probe_dem >> point_fit
point_fit.SetFittingStrategyToPointProjection()
point_mapper = vtkPolyDataMapper(scalar_visibility=True, scalar_range=scalar_range, lookup_table=lut)
point_fit >> point_mapper
point_actor = vtkActor(mapper=point_mapper)
# Fit polygons to surface (cell strategy).
cell_fit = vtkFitToHeightMapFilter(height_map_connection=dem_reader.output_port, use_height_map_offset=True)
probe_dem >> cell_fit
cell_fit.SetFittingStrategyToCellAverageHeight()
cell_mapper = vtkPolyDataMapper(scalar_visibility=True, scalar_range=scalar_range, lookup_table=lut)
cell_fit >> cell_mapper
cell_actor = vtkActor(mapper=cell_mapper)
# Render the three representations.
renderers[0].AddActor(dem_actor)
renderers[1].AddActor(point_actor)
renderers[2].AddActor(cell_actor)
# Now add some text.
text = {0: 'Original Terrain', 1: 'Point Fit', 2: 'Cell Fit'}
# Create the TextActors.
text_actors = list()
text_representations = list()
text_widgets = list()
text_property = vtkTextProperty(color=colors.GetColor3d('DarkSlateGray'), bold=True, italic=False, shadow=False,
font_size=12, font_family_as_string='Courier',
justification=TextProperty.Justification.VTK_TEXT_CENTERED,
vertical_justification=TextProperty.VerticalJustification.VTK_TEXT_CENTERED)
text_positions = get_text_positions(list(text.values()), justification=TextProperty.Justification.VTK_TEXT_CENTERED,
vertical_justification=TextProperty.VerticalJustification.VTK_TEXT_BOTTOM
)
for k, v in text.items():
text_actors.append(
vtkTextActor(input=v, text_scale_mode=vtkTextActor.TEXT_SCALE_MODE_NONE, text_property=text_property))
# Create the text representation. Used for positioning the text actor.
text_representations.append(vtkTextRepresentation(enforce_normalized_viewport_bounds=True))
text_representations[k].position_coordinate.value = text_positions[v]['p']
text_representations[k].position2_coordinate.value = text_positions[v]['p2']
# Create the TextWidgets.
text_widgets.append(
vtkTextWidget(representation=text_representations[k], text_actor=text_actors[k],
default_renderer=renderers[k], interactor=interactor, selectable=False))
# Look down the x-axis.
renderers[0].active_camera.SetPosition(1, 0, 0)
renderers[0].active_camera.SetFocalPoint(0, 1, 0)
renderers[0].active_camera.SetViewUp(0, 0, 1)
renderers[0].ResetCamera()
# Rotate to an oblique view.
renderers[0].active_camera.Azimuth(30.0)
renderers[0].active_camera.Elevation(60.0)
# Share the cameras.
renderers[1].active_camera = renderers[0].active_camera
renderers[2].active_camera = renderers[0].active_camera
render_window.Render()
for k in text.keys():
text_widgets[k].On()
interactor.Start()
def get_text_positions(names, justification=0, vertical_justification=0, width=0.96, height=0.1):
"""
Get viewport positioning information for a list of names.
:param names: The list of names.
:param justification: Horizontal justification of the text, default is left.
:param vertical_justification: Vertical justification of the text, default is bottom.
:param width: Width of the bounding_box of the text in screen coordinates.
:param height: Height of the bounding_box of the text in screen coordinates.
:return: A list of positioning information.
"""
# The gap between the left or right edge of the screen and the text.
dx = 0.02
width = abs(width)
if width > 0.96:
width = 0.96
y0 = 0.01
height = abs(height)
if height > 0.9:
height = 0.9
dy = height
if vertical_justification == TextProperty.VerticalJustification.VTK_TEXT_TOP:
y0 = 1.0 - (dy + y0)
dy = height
if vertical_justification == TextProperty.VerticalJustification.VTK_TEXT_CENTERED:
y0 = 0.5 - (dy / 2.0 + y0)
dy = height
name_len_min = 0
name_len_max = 0
first = True
for k in names:
sz = len(k)
if first:
name_len_min = name_len_max = sz
first = False
else:
name_len_min = min(name_len_min, sz)
name_len_max = max(name_len_max, sz)
text_positions = dict()
for k in names:
sz = len(k)
delta_sz = width * sz / name_len_max
if delta_sz > width:
delta_sz = width
if justification == TextProperty.Justification.VTK_TEXT_CENTERED:
x0 = 0.5 - delta_sz / 2.0
elif justification == TextProperty.Justification.VTK_TEXT_RIGHT:
x0 = 1.0 - dx - delta_sz
else:
# Default is left justification.
x0 = dx
# For debugging!
# print(
# f'{k:16s}: (x0, y0) = ({x0:3.2f}, {y0:3.2f}), (x1, y1) = ({x0 + delta_sz:3.2f}, {y0 + dy:3.2f})'
# f', width={delta_sz:3.2f}, height={dy:3.2f}')
text_positions[k] = {'p': [x0, y0, 0], 'p2': [delta_sz, dy, 0]}
return text_positions
@dataclass(frozen=True)
class TextProperty:
@dataclass(frozen=True)
class Justification:
VTK_TEXT_LEFT: int = 0
VTK_TEXT_CENTERED: int = 1
VTK_TEXT_RIGHT: int = 2
@dataclass(frozen=True)
class VerticalJustification:
VTK_TEXT_BOTTOM: int = 0
VTK_TEXT_CENTERED: int = 1
VTK_TEXT_TOP: int = 2
if __name__ == '__main__':
main()