2017-06-27 16:43:36 +00:00
|
|
|
import ecto, os, json
|
2017-06-27 17:14:09 +00:00
|
|
|
from shutil import copyfile
|
2017-06-23 15:20:46 +00:00
|
|
|
|
|
|
|
from opendm import io
|
|
|
|
from opendm import log
|
|
|
|
from opendm import system
|
|
|
|
from opendm import context
|
|
|
|
from opendm import types
|
|
|
|
|
|
|
|
|
2017-06-23 20:15:13 +00:00
|
|
|
class ODMDEMCell(ecto.Cell):
|
2017-06-23 15:20:46 +00:00
|
|
|
def declare_params(self, params):
|
|
|
|
params.declare("verbose", 'print additional messages to console', False)
|
|
|
|
|
|
|
|
def declare_io(self, params, inputs, outputs):
|
2017-06-23 20:15:13 +00:00
|
|
|
inputs.declare("tree", "Struct with paths", [])
|
2017-06-23 15:20:46 +00:00
|
|
|
inputs.declare("args", "The application arguments.", {})
|
2017-06-23 19:28:46 +00:00
|
|
|
inputs.declare("reconstruction", "list of ODMReconstructions", [])
|
2017-06-23 15:20:46 +00:00
|
|
|
|
|
|
|
def process(self, inputs, outputs):
|
|
|
|
# Benchmarking
|
|
|
|
start_time = system.now_raw()
|
|
|
|
|
|
|
|
log.ODM_INFO('Running ODM DEM Cell')
|
|
|
|
|
|
|
|
# get inputs
|
|
|
|
args = self.inputs.args
|
2017-06-23 20:15:13 +00:00
|
|
|
tree = self.inputs.tree
|
|
|
|
las_model_found = io.file_exists(tree.odm_georeferencing_model_las)
|
2017-06-29 15:00:52 +00:00
|
|
|
env_paths = [context.superbuild_bin_path]
|
2017-06-23 19:28:46 +00:00
|
|
|
|
|
|
|
# Just to make sure
|
|
|
|
l2d_module_installed = True
|
|
|
|
try:
|
2017-06-29 14:55:43 +00:00
|
|
|
system.run('l2d_classify --help > /dev/null', env_paths)
|
2017-06-23 19:28:46 +00:00
|
|
|
except:
|
|
|
|
log.ODM_WARNING('lidar2dems is not installed properly')
|
|
|
|
l2d_module_installed = False
|
|
|
|
|
|
|
|
log.ODM_INFO('Create DSM: ' + str(args.dsm))
|
|
|
|
log.ODM_INFO('Create DTM: ' + str(args.dtm))
|
2017-06-23 20:15:13 +00:00
|
|
|
log.ODM_INFO('DEM input file {0} found: {1}'.format(tree.odm_georeferencing_model_las, str(las_model_found)))
|
2017-06-23 19:28:46 +00:00
|
|
|
|
|
|
|
# Do we need to process anything here?
|
2017-06-23 20:15:13 +00:00
|
|
|
if (args.dsm or args.dtm) and las_model_found and l2d_module_installed:
|
2017-06-23 19:28:46 +00:00
|
|
|
|
|
|
|
# define paths and create working directories
|
|
|
|
odm_dem_root = tree.path('odm_dem')
|
|
|
|
system.mkdir_p(odm_dem_root)
|
|
|
|
|
2017-06-27 16:43:36 +00:00
|
|
|
dsm_output_filename = os.path.join(odm_dem_root, 'dsm.tif')
|
|
|
|
dtm_output_filename = os.path.join(odm_dem_root, 'dtm.tif')
|
2017-06-23 19:28:46 +00:00
|
|
|
|
|
|
|
# check if we rerun cell or not
|
|
|
|
rerun_cell = (args.rerun is not None and
|
|
|
|
args.rerun == 'odm_dem') or \
|
|
|
|
(args.rerun_all) or \
|
|
|
|
(args.rerun_from is not None and
|
|
|
|
'odm_dem' in args.rerun_from)
|
|
|
|
|
|
|
|
if (args.dtm and not io.file_exists(dtm_output_filename)) or \
|
|
|
|
(args.dsm and not io.file_exists(dsm_output_filename)) or \
|
|
|
|
rerun_cell:
|
|
|
|
|
2017-06-27 16:43:36 +00:00
|
|
|
# Extract boundaries and srs of point cloud
|
|
|
|
summary_file_path = os.path.join(odm_dem_root, 'odm_georeferenced_model.summary.json')
|
|
|
|
boundary_file_path = os.path.join(odm_dem_root, 'odm_georeferenced_model.boundary.json')
|
|
|
|
|
2017-06-29 14:55:43 +00:00
|
|
|
system.run('pdal info --summary {0} > {1}'.format(tree.odm_georeferencing_model_las, summary_file_path), env_paths)
|
|
|
|
system.run('pdal info --boundary {0} > {1}'.format(tree.odm_georeferencing_model_las, boundary_file_path), env_paths)
|
2017-06-27 16:43:36 +00:00
|
|
|
|
|
|
|
pc_proj4 = ""
|
|
|
|
pc_geojson_bounds_feature = None
|
|
|
|
|
|
|
|
with open(summary_file_path, 'r') as f:
|
|
|
|
json_f = json.loads(f.read())
|
|
|
|
pc_proj4 = json_f['summary']['srs']['proj4']
|
|
|
|
|
|
|
|
with open(boundary_file_path, 'r') as f:
|
|
|
|
json_f = json.loads(f.read())
|
|
|
|
pc_geojson_boundary_feature = json_f['boundary']['boundary_json']
|
|
|
|
|
|
|
|
# Write bounds to GeoJSON
|
|
|
|
bounds_geojson_path = os.path.join(odm_dem_root, 'odm_georeferenced_model.bounds.geojson')
|
|
|
|
with open(bounds_geojson_path, "w") as f:
|
|
|
|
f.write(json.dumps({
|
|
|
|
"type": "FeatureCollection",
|
|
|
|
"features": [{
|
|
|
|
"type": "Feature",
|
|
|
|
"geometry": pc_geojson_boundary_feature
|
|
|
|
}]
|
|
|
|
}))
|
|
|
|
|
|
|
|
bounds_shapefile_path = os.path.join(odm_dem_root, 'bounds.shp')
|
|
|
|
|
|
|
|
# Convert bounds to Shapefile
|
|
|
|
kwargs = {
|
|
|
|
'input': bounds_geojson_path,
|
|
|
|
'output': bounds_shapefile_path,
|
|
|
|
'proj4': pc_proj4
|
|
|
|
}
|
|
|
|
system.run('ogr2ogr -overwrite -a_srs "{proj4}" {output} {input}'.format(**kwargs))
|
|
|
|
|
|
|
|
# Process with lidar2dems
|
2017-06-23 19:28:46 +00:00
|
|
|
terrain_params_map = {
|
|
|
|
'flatnonforest': (1, 3),
|
|
|
|
'flatforest': (1, 2),
|
|
|
|
'complexnonforest': (5, 2),
|
|
|
|
'complexforest': (10, 2)
|
|
|
|
}
|
|
|
|
terrain_params = terrain_params_map[args.dem_terrain_type.lower()]
|
|
|
|
|
|
|
|
kwargs = {
|
|
|
|
'verbose': '-v' if self.params.verbose else '',
|
|
|
|
'slope': terrain_params[0],
|
|
|
|
'cellsize': terrain_params[1],
|
2017-06-27 16:43:36 +00:00
|
|
|
'outdir': odm_dem_root,
|
|
|
|
'site': bounds_shapefile_path
|
2017-06-23 19:28:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
l2d_params = '--slope {slope} --cellsize {cellsize} ' \
|
2017-06-23 20:15:13 +00:00
|
|
|
'{verbose} ' \
|
2017-06-27 16:43:36 +00:00
|
|
|
'-o -s {site} ' \
|
2017-06-23 19:28:46 +00:00
|
|
|
'--outdir {outdir}'.format(**kwargs)
|
|
|
|
|
2017-06-23 20:15:13 +00:00
|
|
|
approximate = '--approximate' if args.dem_approximate else ''
|
|
|
|
|
2017-06-27 17:14:09 +00:00
|
|
|
# Classify only if we need a DTM
|
|
|
|
run_classification = args.dtm
|
|
|
|
|
|
|
|
if run_classification:
|
|
|
|
system.run('l2d_classify {0} --decimation {1} '
|
2017-07-06 23:40:03 +00:00
|
|
|
'{2} --initialDistance {3} {4}'.format(
|
2017-07-07 00:38:21 +00:00
|
|
|
l2d_params, args.dem_decimation, approximate,
|
|
|
|
args.dem_initial_distance, tree.odm_georeferencing), env_paths)
|
2017-06-27 17:14:09 +00:00
|
|
|
else:
|
|
|
|
log.ODM_INFO("Will skip classification, only DSM is needed")
|
|
|
|
copyfile(tree.odm_georeferencing_model_las, os.path.join(odm_dem_root, 'bounds-0_l2d_s{slope}c{cellsize}.las'.format(**kwargs)))
|
2017-06-23 19:28:46 +00:00
|
|
|
|
|
|
|
products = []
|
|
|
|
if args.dsm: products.append('dsm')
|
|
|
|
if args.dtm: products.append('dtm')
|
|
|
|
|
|
|
|
radius_steps = [args.dem_resolution]
|
|
|
|
for _ in range(args.dem_gapfill_steps - 1):
|
2017-06-23 20:15:13 +00:00
|
|
|
radius_steps.append(radius_steps[-1] * 3) # 3 is arbitrary, maybe there's a better value?
|
2017-06-23 19:28:46 +00:00
|
|
|
|
|
|
|
for product in products:
|
|
|
|
demargs = {
|
|
|
|
'product': product,
|
|
|
|
'indir': odm_dem_root,
|
|
|
|
'l2d_params': l2d_params,
|
|
|
|
'maxsd': args.dem_maxsd,
|
|
|
|
'maxangle': args.dem_maxangle,
|
|
|
|
'resolution': args.dem_resolution,
|
2017-06-23 20:15:13 +00:00
|
|
|
'radius_steps': ' '.join(map(str, radius_steps)),
|
2017-06-27 17:14:09 +00:00
|
|
|
'gapfill': '--gapfill' if args.dem_gapfill_steps > 0 else '',
|
|
|
|
|
|
|
|
# If we didn't run a classification, we should pass the decimate parameter here
|
|
|
|
'decimation': '--decimation {0}'.format(args.dem_decimation) if not run_classification else ''
|
2017-06-23 15:20:46 +00:00
|
|
|
}
|
|
|
|
|
2017-06-23 19:28:46 +00:00
|
|
|
system.run('l2d_dems {product} {indir} {l2d_params} '
|
|
|
|
'--maxsd {maxsd} --maxangle {maxangle} '
|
|
|
|
'--resolution {resolution} --radius {radius_steps} '
|
2017-06-27 17:14:09 +00:00
|
|
|
'{decimation} '
|
2017-06-29 14:55:43 +00:00
|
|
|
'{gapfill} '.format(**demargs), env_paths)
|
2017-06-23 15:20:46 +00:00
|
|
|
|
2017-06-27 16:43:36 +00:00
|
|
|
# Rename final output
|
|
|
|
if product == 'dsm':
|
|
|
|
os.rename(os.path.join(odm_dem_root, 'bounds-0_dsm.idw.tif'), dsm_output_filename)
|
|
|
|
elif product == 'dtm':
|
|
|
|
os.rename(os.path.join(odm_dem_root, 'bounds-0_dtm.idw.tif'), dtm_output_filename)
|
2017-06-23 19:28:46 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
log.ODM_WARNING('Found existing outputs in: %s' % odm_dem_root)
|
2017-06-23 15:20:46 +00:00
|
|
|
else:
|
2017-06-23 19:28:46 +00:00
|
|
|
log.ODM_WARNING('DEM will not be generated')
|
2017-06-23 15:20:46 +00:00
|
|
|
|
|
|
|
if args.time:
|
2017-06-23 19:28:46 +00:00
|
|
|
system.benchmark(start_time, tree.benchmarking, 'Dem')
|
2017-06-23 15:20:46 +00:00
|
|
|
|
2017-06-23 19:28:46 +00:00
|
|
|
log.ODM_INFO('Running ODM DEM Cell - Finished')
|
|
|
|
return ecto.OK if args.end_with != 'odm_dem' else ecto.QUIT
|