From fb1e82d6f3091c97fc205d568ad189aa1425c003 Mon Sep 17 00:00:00 2001 From: John Lindsay Date: Mon, 9 Dec 2019 11:29:43 -0500 Subject: [PATCH] prep for Version 1.0.3 prep for Version 1.0.3 --- .DS_Store | Bin 10244 -> 10244 bytes Cargo.lock | 8 +- Cargo.toml | 4 +- build.py | 70 - lib_test.py | 45 - readme.txt | 30 + src/algorithms/delaunay_triangulation.rs | 55 + src/raster/arcascii_raster.rs | 2 + src/raster/arcbinary_raster.rs | 2 + src/raster/geotiff/geokeys.rs | 18 +- src/raster/geotiff/tiff_consts.rs | 160 +- src/raster/grass_raster.rs | 2 + src/raster/idrisi_raster.rs | 6 +- src/raster/mod.rs | 2 +- src/raster/saga_raster.rs | 31 +- src/raster/surfer7_raster.rs | 2 + src/raster/surfer_ascii_raster.rs | 1 + src/raster/whitebox_raster.rs | 8 +- src/structures/array2d.rs | 22 +- src/structures/mod.rs | 4 +- src/structures/radial_basis_function.rs | 143 + .../add_point_coordinates_to_table.rs | 2 +- src/tools/data_tools/clean_vector.rs | 2 +- .../data_tools/convert_nodata_to_zero.rs | 2 +- src/tools/data_tools/convert_raster_format.rs | 2 +- src/tools/data_tools/csv_points_to_vector.rs | 2 +- src/tools/data_tools/export_table_to_csv.rs | 2 +- src/tools/data_tools/join_tables.rs | 2 +- src/tools/data_tools/lines_to_polygons.rs | 2 +- src/tools/data_tools/merge_table_with_csv.rs | 2 +- src/tools/data_tools/merge_vectors.rs | 2 +- src/tools/data_tools/modify_nodata_value.rs | 2 +- .../data_tools/multipart_to_singlepart.rs | 2 +- src/tools/data_tools/new_raster.rs | 2 +- src/tools/data_tools/polygons_to_lines.rs | 2 +- src/tools/data_tools/print_geotiff_tags.rs | 2 +- .../data_tools/raster_to_vector_lines.rs | 2 +- .../data_tools/raster_to_vector_points.rs | 2 +- .../reinitialize_attribute_table.rs | 2 +- src/tools/data_tools/remove_polygon_holes.rs | 2 +- src/tools/data_tools/set_nodata_value.rs | 2 +- .../data_tools/singlepart_to_multipart.rs | 2 +- .../data_tools/vector_lines_to_raster.rs | 2 +- .../data_tools/vector_points_to_raster.rs | 2 +- .../data_tools/vector_polygons_to_raster.rs | 2 +- src/tools/gis_analysis/aggregate_raster.rs | 2 +- src/tools/gis_analysis/average_overlay.rs | 2 +- src/tools/gis_analysis/block_maximum.rs | 7 +- src/tools/gis_analysis/block_minimum.rs | 7 +- .../gis_analysis/boundary_shape_complexity.rs | 2 +- src/tools/gis_analysis/buffer_raster.rs | 2 +- src/tools/gis_analysis/buffer_vector.rs | 2 +- src/tools/gis_analysis/centroid.rs | 2 +- src/tools/gis_analysis/centroid_vector.rs | 2 +- src/tools/gis_analysis/clip.rs | 7 +- .../gis_analysis/clip_raster_to_polygon.rs | 2 +- src/tools/gis_analysis/clump.rs | 3 +- src/tools/gis_analysis/compactness_ratio.rs | 2 +- .../gis_analysis/construct_vector_tin.rs | 164 +- src/tools/gis_analysis/cost_allocation.rs | 4 +- src/tools/gis_analysis/cost_distance.rs | 4 +- src/tools/gis_analysis/cost_pathway.rs | 2 +- src/tools/gis_analysis/count_if.rs | 2 +- .../create_hexagonal_vector_grid.rs | 2 +- src/tools/gis_analysis/create_plane.rs | 2 +- .../create_rectangular_vector_grid.rs | 2 +- src/tools/gis_analysis/difference.rs | 14 +- src/tools/gis_analysis/dissolve.rs | 6 +- src/tools/gis_analysis/edge_proportion.rs | 2 +- .../eliminate_coincident_points.rs | 2 +- src/tools/gis_analysis/elongation_ratio.rs | 2 +- src/tools/gis_analysis/erase.rs | 7 +- .../gis_analysis/erase_polygon_from_raster.rs | 2 +- .../gis_analysis/euclidean_allocation.rs | 2 +- src/tools/gis_analysis/euclidean_distance.rs | 106 +- src/tools/gis_analysis/extend_vector_lines.rs | 2 +- src/tools/gis_analysis/extract_nodes.rs | 2 +- .../extract_raster_values_at_points.rs | 2 +- .../find_lowest_or_highest_points.rs | 2 +- .../gis_analysis/find_patch_edge_cells.rs | 2 +- src/tools/gis_analysis/highest_pos.rs | 2 +- src/tools/gis_analysis/hole_proportion.rs | 2 +- src/tools/gis_analysis/idw_interpolation.rs | 21 +- src/tools/gis_analysis/intersect.rs | 13 +- src/tools/gis_analysis/layer_footprint.rs | 2 +- src/tools/gis_analysis/line_intersections.rs | 2 +- src/tools/gis_analysis/linearity_index.rs | 2 +- src/tools/gis_analysis/lowest_pos.rs | 2 +- src/tools/gis_analysis/max_abs_overlay.rs | 2 +- src/tools/gis_analysis/max_overlay.rs | 2 +- src/tools/gis_analysis/medoid.rs | 2 +- src/tools/gis_analysis/merge_line_segments.rs | 4 +- src/tools/gis_analysis/min_abs_overlay.rs | 2 +- src/tools/gis_analysis/min_overlay.rs | 2 +- .../gis_analysis/minimum_bounding_box.rs | 2 +- .../gis_analysis/minimum_bounding_circle.rs | 2 +- .../gis_analysis/minimum_bounding_envelope.rs | 2 +- src/tools/gis_analysis/minimum_convex_hull.rs | 2 +- src/tools/gis_analysis/mod.rs | 2 + src/tools/gis_analysis/narrowness_index.rs | 2 +- .../natural_neighbour_interpolation.rs | 727 ++ .../nearest_neighbour_gridding.rs | 13 +- src/tools/gis_analysis/patch_orientation.rs | 2 +- src/tools/gis_analysis/percent_equal_to.rs | 2 +- .../gis_analysis/percent_greater_than.rs | 2 +- src/tools/gis_analysis/percent_less_than.rs | 2 +- .../gis_analysis/perimeter_area_ratio.rs | 2 +- src/tools/gis_analysis/pick_from_list.rs | 2 +- src/tools/gis_analysis/polygon_area.rs | 2 +- src/tools/gis_analysis/polygon_long_axis.rs | 2 +- src/tools/gis_analysis/polygon_perimeter.rs | 2 +- src/tools/gis_analysis/polygon_short_axis.rs | 2 +- src/tools/gis_analysis/polygonize.rs | 4 +- src/tools/gis_analysis/radius_of_gyration.rs | 2 +- src/tools/gis_analysis/raster_area.rs | 14 +- .../gis_analysis/raster_cell_assignment.rs | 2 +- src/tools/gis_analysis/raster_perimeter.rs | 526 ++ src/tools/gis_analysis/reclass.rs | 2 +- .../gis_analysis/reclass_equal_interval.rs | 2 +- src/tools/gis_analysis/reclass_from_file.rs | 2 +- .../related_circumscribing_circle.rs | 2 +- .../gis_analysis/shape_complexity_index.rs | 2 +- .../gis_analysis/shape_complexity_raster.rs | 2 +- .../gis_analysis/sibson_interpolation.rs | 2 +- src/tools/gis_analysis/smooth_vectors.rs | 2 +- src/tools/gis_analysis/snap_endnodes.rs | 2 +- src/tools/gis_analysis/split_with_lines.rs | 4 +- src/tools/gis_analysis/sum_overlay.rs | 2 +- .../gis_analysis/symmetrical_difference.rs | 12 +- src/tools/gis_analysis/tin_gridding.rs | 124 +- src/tools/gis_analysis/union.rs | 18 +- src/tools/gis_analysis/vector_hex_bin.rs | 2 +- src/tools/gis_analysis/voronoi_diagram.rs | 9 +- src/tools/gis_analysis/weighted_overlay.rs | 2 +- src/tools/gis_analysis/weighted_sum.rs | 2 +- .../hydro_analysis/average_flowpath_slope.rs | 2 +- .../average_upslope_flowpath_length.rs | 2 +- src/tools/hydro_analysis/basins.rs | 2 +- .../hydro_analysis/breach_depressions.rs | 25 +- .../breach_depressions_least_cost.rs | 1181 +++ src/tools/hydro_analysis/breach_pits.rs | 2 +- .../hydro_analysis/burn_streams_at_roads.rs | 4 +- src/tools/hydro_analysis/d8_flow_accum.rs | 2 +- src/tools/hydro_analysis/d8_mass_flux.rs | 2 +- src/tools/hydro_analysis/d8_pointer.rs | 2 +- src/tools/hydro_analysis/depth_in_sink.rs | 773 +- src/tools/hydro_analysis/dinf_flow_accum.rs | 2 +- src/tools/hydro_analysis/dinf_mass_flux.rs | 2 +- src/tools/hydro_analysis/dinf_pointer.rs | 2 +- .../downslope_distance_to_stream.rs | 2 +- .../downslope_flowpath_length.rs | 2 +- .../hydro_analysis/elevation_above_stream.rs | 2 +- .../elevation_above_stream_euclidean.rs | 2 +- src/tools/hydro_analysis/fd8_flow_accum.rs | 81 +- src/tools/hydro_analysis/fd8_pointer.rs | 2 +- src/tools/hydro_analysis/fill_burn.rs | 7 +- src/tools/hydro_analysis/fill_depressions.rs | 783 +- .../fill_depressions_wang_and_lui.rs | 653 ++ src/tools/hydro_analysis/fill_pits.rs | 4 +- src/tools/hydro_analysis/find_noflow_cells.rs | 6 +- .../hydro_analysis/find_parallel_flow.rs | 2 +- src/tools/hydro_analysis/flatten_lakes.rs | 2 +- src/tools/hydro_analysis/flood_order.rs | 2 +- .../flow_accum_full_workflow.rs | 10 +- src/tools/hydro_analysis/flow_length_diff.rs | 2 +- src/tools/hydro_analysis/hillslopes.rs | 2 +- src/tools/hydro_analysis/impoundment_index.rs | 2 +- src/tools/hydro_analysis/isobasins.rs | 2 +- .../hydro_analysis/jenson_snap_pour_points.rs | 2 +- src/tools/hydro_analysis/longest_flowpath.rs | 2 +- .../hydro_analysis/max_upslope_flowpath.rs | 2 +- src/tools/hydro_analysis/mod.rs | 6 + .../num_inflowing_neighbours.rs | 2 +- src/tools/hydro_analysis/raise_walls.rs | 2 +- src/tools/hydro_analysis/rho8_pointer.rs | 2 +- src/tools/hydro_analysis/sink.rs | 353 +- src/tools/hydro_analysis/snap_pour_points.rs | 2 +- .../stochastic_depression_analysis.rs | 2 +- src/tools/hydro_analysis/strahler_basins.rs | 2 +- src/tools/hydro_analysis/subbasins.rs | 2 +- .../trace_downslope_flowpaths.rs | 2 +- src/tools/hydro_analysis/unnest_basins.rs | 2 +- .../upslope_depression_storage.rs | 597 ++ src/tools/hydro_analysis/watershed.rs | 2 +- src/tools/image_analysis/adaptive_filter.rs | 2 +- .../balance_contrast_enhancement.rs | 2 +- src/tools/image_analysis/bilateral_filter.rs | 2 +- .../image_analysis/change_vector_analysis.rs | 2 +- src/tools/image_analysis/closing.rs | 2 +- .../conservative_smoothing_filter.rs | 2 +- src/tools/image_analysis/corner_detection.rs | 2 +- .../image_analysis/correct_vignetting.rs | 2 +- .../image_analysis/create_colour_composite.rs | 2 +- .../direct_decorrelation_stretch.rs | 2 +- src/tools/image_analysis/diversity_filter.rs | 2 +- src/tools/image_analysis/dog_filter.rs | 2 +- .../edge_preserving_mean_filter.rs | 2 +- src/tools/image_analysis/emboss_filter.rs | 2 +- .../fast_almost_gaussian_filter.rs | 2 +- src/tools/image_analysis/flip_image.rs | 2 +- src/tools/image_analysis/gamma_correction.rs | 2 +- .../gaussian_contrast_stretch.rs | 2 +- src/tools/image_analysis/gaussian_filter.rs | 2 +- src/tools/image_analysis/highpass_filter.rs | 2 +- .../image_analysis/highpass_median_filter.rs | 2 +- .../image_analysis/histogram_equalization.rs | 2 +- .../image_analysis/histogram_matching.rs | 2 +- .../histogram_matching_two_images.rs | 2 +- src/tools/image_analysis/ihs_to_rgb.rs | 2 +- .../image_analysis/image_stack_profile.rs | 2 +- src/tools/image_analysis/integral_image.rs | 2 +- .../image_analysis/k_means_clustering.rs | 2 +- .../image_analysis/k_nearest_mean_filter.rs | 2 +- src/tools/image_analysis/laplacian_filter.rs | 2 +- src/tools/image_analysis/lee_filter.rs | 2 +- .../image_analysis/line_detection_filter.rs | 2 +- src/tools/image_analysis/line_thin.rs | 2 +- src/tools/image_analysis/log_filter.rs | 2 +- src/tools/image_analysis/majority_filter.rs | 2 +- src/tools/image_analysis/max_filter.rs | 2 +- src/tools/image_analysis/mean_filter.rs | 2 +- src/tools/image_analysis/median_filter.rs | 2 +- src/tools/image_analysis/min_filter.rs | 2 +- .../min_max_contrast_stretch.rs | 2 +- .../modified_k_means_clustering.rs | 2 +- src/tools/image_analysis/mosaic.rs | 6 +- .../image_analysis/mosaic_with_feathering.rs | 10 +- .../normalized_difference_index.rs | 2 +- src/tools/image_analysis/olympic_filter.rs | 2 +- src/tools/image_analysis/opening.rs | 2 +- src/tools/image_analysis/pan_sharpening.rs | 2 +- .../percentage_contrast_stretch.rs | 2 +- src/tools/image_analysis/percentile_filter.rs | 2 +- src/tools/image_analysis/prewitt_filter.rs | 2 +- src/tools/image_analysis/range_filter.rs | 2 +- src/tools/image_analysis/remove_spurs.rs | 2 +- src/tools/image_analysis/resample.rs | 6 +- src/tools/image_analysis/rgb_to_ihs.rs | 2 +- src/tools/image_analysis/roberts_filter.rs | 2 +- src/tools/image_analysis/scharr_filter.rs | 2 +- .../sigmoidal_contrast_stretch.rs | 2 +- src/tools/image_analysis/sobel_filter.rs | 2 +- .../image_analysis/split_colour_composite.rs | 2 +- .../image_analysis/stdev_contrast_stretch.rs | 2 +- src/tools/image_analysis/stdev_filter.rs | 2 +- src/tools/image_analysis/thicken_line.rs | 2 +- src/tools/image_analysis/tophat.rs | 2 +- src/tools/image_analysis/total_filter.rs | 2 +- src/tools/image_analysis/unsharp_masking.rs | 2 +- .../user_defined_weights_filter.rs | 2 +- .../write_func_memory_insertion.rs | 2 +- src/tools/lidar_analysis/ascii_to_las.rs | 2 +- src/tools/lidar_analysis/block_maximum.rs | 2 +- src/tools/lidar_analysis/block_minimum.rs | 2 +- .../lidar_analysis/classify_buildings.rs | 500 ++ .../lidar_analysis/classify_overlap_points.rs | 2 +- .../lidar_analysis/clip_lidar_to_polygon.rs | 2 +- .../erase_polygon_from_lidar.rs | 2 +- .../lidar_analysis/filter_lidar_classes.rs | 2 +- .../filter_lidar_scan_angles.rs | 2 +- .../find_flightline_edge_points.rs | 2 +- .../lidar_analysis/flightline_overlap.rs | 2 +- src/tools/lidar_analysis/las_to_ascii.rs | 2 +- .../las_to_multipoint_shapefile.rs | 2 +- src/tools/lidar_analysis/las_to_shapefile.rs | 2 +- .../lidar_analysis/lidar_classify_subset.rs | 4 +- src/tools/lidar_analysis/lidar_colourize.rs | 13 +- .../lidar_construct_vector_tin.rs | 2 +- .../lidar_analysis/lidar_elevation_slice.rs | 2 +- .../lidar_ground_point_filter.rs | 2 +- src/tools/lidar_analysis/lidar_hex_bin.rs | 2 +- src/tools/lidar_analysis/lidar_hillshade.rs | 2 +- src/tools/lidar_analysis/lidar_histogram.rs | 2 +- .../lidar_analysis/lidar_idw_interpolation.rs | 4 +- src/tools/lidar_analysis/lidar_info.rs | 6 +- src/tools/lidar_analysis/lidar_join.rs | 2 +- src/tools/lidar_analysis/lidar_kappa.rs | 2 +- src/tools/lidar_analysis/lidar_nn_gridding.rs | 2 +- src/tools/lidar_analysis/lidar_outliers.rs | 2 +- .../lidar_analysis/lidar_point_density.rs | 2 +- src/tools/lidar_analysis/lidar_point_stats.rs | 2 +- ...dar_radial_basis_function_interpolation.rs | 1023 +++ .../lidar_analysis/lidar_ransac_planes.rs | 2 +- .../lidar_analysis/lidar_segmentation.rs | 2 +- .../lidar_segmentation_based_filter.rs | 2 +- src/tools/lidar_analysis/lidar_thin.rs | 2 +- .../lidar_analysis/lidar_thin_high_density.rs | 2 +- src/tools/lidar_analysis/lidar_tile.rs | 2 +- .../lidar_analysis/lidar_tile_footprint.rs | 21 +- .../lidar_analysis/lidar_tin_gridding.rs | 3 +- .../lidar_analysis/lidar_tophat_transform.rs | 2 +- src/tools/lidar_analysis/mod.rs | 4 + src/tools/lidar_analysis/normal_vectors.rs | 2 +- src/tools/lidar_analysis/remove_duplicates.rs | 2 +- .../lidar_analysis/select_tiles_by_polygon.rs | 2 +- src/tools/math_stat_analysis/abs.rs | 2 +- src/tools/math_stat_analysis/add.rs | 2 +- src/tools/math_stat_analysis/and.rs | 2 +- src/tools/math_stat_analysis/anova.rs | 2 +- src/tools/math_stat_analysis/arccos.rs | 2 +- src/tools/math_stat_analysis/arcosh.rs | 2 +- src/tools/math_stat_analysis/arcsin.rs | 2 +- src/tools/math_stat_analysis/arctan.rs | 2 +- src/tools/math_stat_analysis/arsinh.rs | 2 +- src/tools/math_stat_analysis/artanh.rs | 2 +- src/tools/math_stat_analysis/atan2.rs | 2 +- .../attribute_correlation.rs | 2 +- .../math_stat_analysis/attribute_histogram.rs | 2 +- .../attribute_scattergram.rs | 2 +- src/tools/math_stat_analysis/ceil.rs | 2 +- src/tools/math_stat_analysis/cos.rs | 2 +- src/tools/math_stat_analysis/cosh.rs | 2 +- .../math_stat_analysis/crispness_index.rs | 2 +- .../math_stat_analysis/cross_tabulation.rs | 2 +- .../math_stat_analysis/cumulative_dist.rs | 2 +- src/tools/math_stat_analysis/decrement.rs | 2 +- src/tools/math_stat_analysis/divide.rs | 2 +- src/tools/math_stat_analysis/equal_to.rs | 2 +- src/tools/math_stat_analysis/exp.rs | 2 +- src/tools/math_stat_analysis/exp2.rs | 2 +- src/tools/math_stat_analysis/floor.rs | 2 +- src/tools/math_stat_analysis/greater_than.rs | 2 +- .../image_autocorrelation.rs | 2 +- .../math_stat_analysis/image_correlation.rs | 2 +- ...mage_correlation_neighbourhood_analysis.rs | 803 ++ .../math_stat_analysis/image_regression.rs | 2 +- src/tools/math_stat_analysis/increment.rs | 2 +- src/tools/math_stat_analysis/inplace_add.rs | 2 +- .../math_stat_analysis/inplace_divide.rs | 2 +- .../math_stat_analysis/inplace_multiply.rs | 2 +- .../math_stat_analysis/inplace_subtract.rs | 2 +- .../math_stat_analysis/integer_division.rs | 2 +- src/tools/math_stat_analysis/isnodata.rs | 2 +- src/tools/math_stat_analysis/kappa_index.rs | 2 +- .../math_stat_analysis/ks_normality_test.rs | 2 +- src/tools/math_stat_analysis/less_than.rs | 2 +- .../math_stat_analysis/list_unique_values.rs | 2 +- src/tools/math_stat_analysis/ln.rs | 2 +- src/tools/math_stat_analysis/log10.rs | 2 +- src/tools/math_stat_analysis/log2.rs | 2 +- src/tools/math_stat_analysis/max.rs | 2 +- src/tools/math_stat_analysis/min.rs | 2 +- src/tools/math_stat_analysis/mod.rs | 2 + src/tools/math_stat_analysis/modulo.rs | 2 +- src/tools/math_stat_analysis/multiply.rs | 2 +- src/tools/math_stat_analysis/negate.rs | 2 +- src/tools/math_stat_analysis/not.rs | 2 +- src/tools/math_stat_analysis/not_equal_to.rs | 2 +- src/tools/math_stat_analysis/or.rs | 2 +- .../paired_sample_t_test.rs | 2 +- src/tools/math_stat_analysis/power.rs | 2 +- .../principal_component_analysis.rs | 2 +- src/tools/math_stat_analysis/quantiles.rs | 2 +- src/tools/math_stat_analysis/random_field.rs | 2 +- src/tools/math_stat_analysis/random_sample.rs | 2 +- .../math_stat_analysis/raster_histogram.rs | 2 +- .../raster_summary_stats.rs | 2 +- src/tools/math_stat_analysis/reciprocal.rs | 2 +- .../math_stat_analysis/rescale_value_range.rs | 2 +- .../root_mean_square_error.rs | 2 +- src/tools/math_stat_analysis/round.rs | 2 +- src/tools/math_stat_analysis/sin.rs | 2 +- src/tools/math_stat_analysis/sinh.rs | 2 +- src/tools/math_stat_analysis/sqrt.rs | 2 +- src/tools/math_stat_analysis/square.rs | 2 +- src/tools/math_stat_analysis/subtract.rs | 2 +- src/tools/math_stat_analysis/tan.rs | 2 +- src/tools/math_stat_analysis/tanh.rs | 2 +- src/tools/math_stat_analysis/to_degrees.rs | 2 +- src/tools/math_stat_analysis/to_radians.rs | 2 +- src/tools/math_stat_analysis/trend_surface.rs | 2 +- .../trend_surface_vector_points.rs | 2 +- src/tools/math_stat_analysis/truncate.rs | 2 +- src/tools/math_stat_analysis/turning_bands.rs | 2 +- .../math_stat_analysis/two_sample_ks_test.rs | 2 +- .../wilcoxon_signed_rank_test.rs | 8 +- src/tools/math_stat_analysis/xor.rs | 2 +- .../math_stat_analysis/zonal_statistics.rs | 2 +- src/tools/math_stat_analysis/zscores.rs | 2 +- src/tools/mod.rs | 14 + .../stream_network_analysis/dist_to_outlet.rs | 2 +- .../extract_streams.rs | 2 +- .../extract_valleys.rs | 2 +- .../farthest_channel_head.rs | 2 +- .../stream_network_analysis/find_main_stem.rs | 2 +- .../stream_network_analysis/hack_order.rs | 2 +- .../stream_network_analysis/horton_order.rs | 2 +- .../stream_network_analysis/long_profile.rs | 2 +- .../long_profile_from_points.rs | 2 +- .../raster_streams_to_vector.rs | 2 +- .../rasterize_streams.rs | 2 +- .../remove_short_streams.rs | 2 +- .../shreve_magnitude.rs | 2 +- .../stream_network_analysis/strahler_order.rs | 2 +- .../stream_link_class.rs | 2 +- .../stream_network_analysis/stream_link_id.rs | 2 +- .../stream_link_length.rs | 2 +- .../stream_link_slope.rs | 2 +- .../stream_slope_continuous.rs | 2 +- .../topological_stream_order.rs | 2 +- .../total_length_channels.rs | 2 +- .../stream_network_analysis/tributary_id.rs | 2 +- .../vector_stream_network_analysis.rs | 2 +- src/tools/terrain_analysis/aspect.rs | 2 +- ...average_normal_vector_angular_deviation.rs | 2 +- .../circular_variance_of_aspect.rs | 2 +- .../terrain_analysis/dev_from_mean_elev.rs | 2 +- .../terrain_analysis/diff_from_mean_elev.rs | 2 +- .../terrain_analysis/directional_relief.rs | 2 +- src/tools/terrain_analysis/downslope_index.rs | 2 +- .../drainage_preserving_smoothing.rs | 2 +- src/tools/terrain_analysis/edge_density.rs | 2 +- src/tools/terrain_analysis/elev_above_pit.rs | 2 +- src/tools/terrain_analysis/elev_percentile.rs | 2 +- .../elev_relative_to_min_max.rs | 2 +- .../elev_relative_to_watershed_min_max.rs | 2 +- .../feature_preserving_smoothing.rs | 2 +- src/tools/terrain_analysis/fetch_analysis.rs | 2 +- .../terrain_analysis/fill_missing_data.rs | 2 +- src/tools/terrain_analysis/find_ridges.rs | 2 +- src/tools/terrain_analysis/geomorphons.rs | 2 +- src/tools/terrain_analysis/hillshade.rs | 2 +- src/tools/terrain_analysis/horizon_angle.rs | 2 +- .../terrain_analysis/hypsometric_analysis.rs | 2 +- .../terrain_analysis/max_anisotropy_dev.rs | 2 +- .../max_anisotropy_dev_signature.rs | 2 +- .../terrain_analysis/max_branch_length.rs | 2 +- .../terrain_analysis/max_diff_from_mean.rs | 2 +- .../max_downslope_elev_change.rs | 2 +- .../max_elev_dev_signature.rs | 2 +- .../terrain_analysis/max_elev_deviation.rs | 2 +- .../min_downslope_elev_change.rs | 2 +- .../terrain_analysis/multiscale_roughness.rs | 2 +- .../multiscale_roughness_signature.rs | 2 +- .../multiscale_std_dev_normals.rs | 2 +- .../multiscale_std_dev_normals_signature.rs | 2 +- .../multiscale_topographic_position_image.rs | 2 +- .../num_downslope_neighbours.rs | 2 +- .../num_upslope_neighbours.rs | 2 +- .../pennock_landform_class.rs | 2 +- .../terrain_analysis/percent_elev_range.rs | 2 +- src/tools/terrain_analysis/plan_curvature.rs | 2 +- src/tools/terrain_analysis/prof_curvature.rs | 2 +- src/tools/terrain_analysis/profile.rs | 2 +- src/tools/terrain_analysis/relative_aspect.rs | 2 +- .../relative_stream_power_index.rs | 2 +- .../relative_topographic_position.rs | 2 +- .../remove_off_terrain_objects.rs | 2 +- .../terrain_analysis/ruggedness_index.rs | 2 +- .../sediment_transport_index.rs | 4 +- src/tools/terrain_analysis/slope.rs | 2 +- .../terrain_analysis/slope_vs_elev_plot.rs | 4 +- .../spherical_std_dev_of_normals.rs | 2 +- .../standard_deviation_of_slope.rs | 2 +- .../terrain_analysis/surface_area_ratio.rs | 2 +- src/tools/terrain_analysis/tan_curvature.rs | 2 +- src/tools/terrain_analysis/total_curvature.rs | 2 +- src/tools/terrain_analysis/viewshed.rs | 2 +- .../terrain_analysis/visibility_index.rs | 2 +- src/tools/terrain_analysis/wetness_index.rs | 2 +- src/vector/shapefile/mod.rs | 8 +- tempCodeRunnerFile.py | 7321 ----------------- wb_runner.py | 639 +- wb_runner_new.py | 1321 --- whitebox_example.py | 143 - whitebox_tools.py | 131 +- 466 files changed, 8983 insertions(+), 10697 deletions(-) delete mode 100644 build.py delete mode 100644 lib_test.py create mode 100644 src/structures/radial_basis_function.rs create mode 100644 src/tools/gis_analysis/natural_neighbour_interpolation.rs create mode 100644 src/tools/gis_analysis/raster_perimeter.rs create mode 100644 src/tools/hydro_analysis/breach_depressions_least_cost.rs create mode 100644 src/tools/hydro_analysis/fill_depressions_wang_and_lui.rs create mode 100644 src/tools/hydro_analysis/upslope_depression_storage.rs create mode 100644 src/tools/lidar_analysis/classify_buildings.rs create mode 100644 src/tools/lidar_analysis/lidar_radial_basis_function_interpolation.rs create mode 100644 src/tools/math_stat_analysis/image_correlation_neighbourhood_analysis.rs delete mode 100644 tempCodeRunnerFile.py delete mode 100644 wb_runner_new.py delete mode 100644 whitebox_example.py diff --git a/.DS_Store b/.DS_Store index 41ea59f38a404bd074e058ed0e10b5efc74fc9aa..9b5f7c955d59016f4a2273ae397a0a2d055ea31e 100644 GIT binary patch delta 24 fcmZn(XbIR5C&X@OtfOFJVKn)isO;t@p(arPV3-Gw delta 24 fcmZn(XbIR5C&X@GqN8ABW;*$tsO;t@p(arPV21~e diff --git a/Cargo.lock b/Cargo.lock index 3194f678f..b35856703 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,7 +154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "kdtree" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -569,11 +569,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "whitebox_tools" -version = "1.0.2" +version = "1.0.3" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "kdtree 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "kdtree 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libflate 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", "lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "nalgebra 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -640,7 +640,7 @@ dependencies = [ "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e65cce4e5084b14874c4e7097f38cab54f47ee554f9194673456ea379dcc4c55" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" -"checksum kdtree 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b63d659081717fe428fbaf9549559083956450194e57e8d28498a0dfa31b3b04" +"checksum kdtree 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "80ee359328fc9087e9e3fc0a4567c4dd27ec69a127d6a70e8d9dd22845b8b1a2" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319" "checksum libflate 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "7346a83e8a2c3958d44d24225d905385dc31fc16e89dffb356c457b278914d20" diff --git a/Cargo.toml b/Cargo.toml index ac03254b4..65701bd41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "whitebox_tools" -version = "1.0.2" +version = "1.0.3" authors = ["John Lindsay "] description = "A library for analyzing geospatial data." keywords = ["geospatial", "GIS", "remote sensing", "geomatics", "image processing", "lidar", "spatial analysis"] @@ -13,7 +13,7 @@ edition = "2018" [dependencies] byteorder = "^1.3.1" chrono = "0.4.6" -kdtree = "0.5.1" +kdtree = "0.6.0" libflate = "0.1.18" lzw = "0.10.0" nalgebra = "0.18.0" diff --git a/build.py b/build.py deleted file mode 100644 index 7d2fadfed..000000000 --- a/build.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -''' this module is used to build whitebox-tools. -''' -import os -import sys -from subprocess import call - - -def main(): - ''' main function - ''' - try: - update_cargo = False - clean_code = False - doc_code = False - build_code = True - mode = 'release' # 'check', 'debug', or 'release' - - # Change the current directory - dir_path = os.path.dirname(os.path.realpath(__file__)) - os.chdir(dir_path) - - if update_cargo: - # Update # - retcode = call(['cargo', 'update'], shell=False) - if retcode < 0: - print("Child was terminated by signal", - - retcode, file=sys.stderr) - else: - print("Update successful", file=sys.stderr) - - if clean_code: - # Clean # - retcode = call(['cargo', 'clean'], shell=False) - if retcode < 0: - print("Child was terminated by signal", - - retcode, file=sys.stderr) - else: - print("Clean successful", file=sys.stderr) - - if doc_code: - # Clean # - retcode = call(['cargo', 'doc'], shell=False) - if retcode < 0: - print("Child was terminated by signal", - - retcode, file=sys.stderr) - else: - print("Clean successful", file=sys.stderr) - - if build_code: - # Build # - if mode == 'release': - retcode = call(['env', 'RUSTFLAGS=-C target-cpu=native', 'CARGO_INCREMENTAL=1', - 'cargo', 'build', '--release'], shell=False) - elif mode == 'check': - retcode = call(['cargo', 'check'], shell=False) - else: - retcode = call(['cargo', 'build'], shell=False) - - if retcode < 0: - print("Child was terminated by signal", - - retcode, file=sys.stderr) - else: - print("Build executed successfully", file=sys.stderr) - - except OSError as err: - print("Execution failed:", err, file=sys.stderr) - - -main() diff --git a/lib_test.py b/lib_test.py deleted file mode 100644 index ab58f38e6..000000000 --- a/lib_test.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -''' This script is intended to experiment with the use of a whitebox_tools shared library (DLL). -It is experimental and is not intended for widespread use. -''' -import os -from sys import platform -from ctypes import cdll, c_int, c_char_p, POINTER, c_size_t - - -def call_tool(name, args): - # Change the current directory - dir_path = os.path.dirname(os.path.realpath(__file__)) - os.chdir(dir_path) - - if platform == 'darwin': - prefix = 'lib' - ext = 'dylib' - elif platform == 'win32': - prefix = '' - ext = 'dll' - else: - prefix = 'lib' - ext = 'so' - - wb_tools = cdll.LoadLibrary( - 'target/release/{}whitebox_tools.{}'.format(prefix, ext)) - - wb_tools.run_tool.restype = c_int - wb_tools.run_tool.argtypes = [ - c_char_p, POINTER(c_char_p), c_size_t] - - wb_tools.print_tool.argtypes = [ - c_char_p] - - args_list = (c_char_p * len(args))(*args) - ret = wb_tools.run_tool(name, args_list, len(args_list)) - print "Return value:", ret - - -TOOL_NAME = "slope" -TOOL_ARGS = ["--wd=\"/Users/johnlindsay/Documents/data/GarveyGlenWatershed/\"", - "--input=\"DEM final.dep\"", - "--output=\"tmp13.dep\"", - "-v"] -call_tool(TOOL_NAME, TOOL_ARGS) diff --git a/readme.txt b/readme.txt index 603633057..d21ecad3a 100644 --- a/readme.txt +++ b/readme.txt @@ -56,6 +56,36 @@ for more details. * Release Notes: * ****************** +Version 1.0.3 (09-12-2019) +- Added the BreachDepressionsLeastCost tool, which performs a modified form of the Lindsay + and Dhun (2015) impact minimizing breaching algorithm. This modified algorithm is very + efficient and can provide an excellent method for creating depressionless DEMs from large + DEMs, including those derived from LiDAR. It is particularly well suited to breaching + through road embankments, approximately the pathway of culverts. +- The FillDepressions tool algorithm has been completely re-developed. The new algorithm is + significantly faster than the previous method, which was based on the Wang and Lui method. + For legacy reasons, the previous tool has been retained and renamed FillDepressonsWangAndLui. + Notice that this new method also incorporates significantly improved flat area correction + that maintains general flowpaths of filled areas. +- The Sink and DepthInSink tools have been updated to use the new depression filling algorithm. +- Added the ClassifyBuildingsInLidar tool to reclassify LiDAR points within a LAS file + to the building class value (6) that are located within one or more building footprint + contained in an input polygon vector file. +- Added the NaturalNeighbourInterpolation tool for performing Sibson's (1981) interpolation + method on input point data. +- Added the UpslopeDepressionStorage tool to estimate the average upslope depression + storage capacity (DSC). +- Added the LidarRfbInterpolation tool for performing a radial basis function interpolation + of LiDAR data sets. +- The WhiteboxTools Runner user interface has been significantly improved (many thanks to + Rachel Broders for these contributions). +- Fixed a bug in which the photometric interpretation was not being set by certain raster + decoders, including the SAGA encoder. This was causing an error when outputting GeoTIFF + files. +- Updated the ConstructVectorTIN and TINGridding tools to include a maximum triangle edge + length to help avoid the creation of spurious long and narrow triangles in convex regions + along the data boundaries. + Version 1.0.2 (01-11-2019) - Added the BurnStreamsAtRoads tool. - Added a two-sample K-S test (TwoSampleKsTest) for comparing the distributions of two rasters. diff --git a/src/algorithms/delaunay_triangulation.rs b/src/algorithms/delaunay_triangulation.rs index 7d0296255..9b27fbee9 100644 --- a/src/algorithms/delaunay_triangulation.rs +++ b/src/algorithms/delaunay_triangulation.rs @@ -45,6 +45,7 @@ println!("{:?}", result.triangles); // [0, 2, 1, 0, 3, 2] use crate::structures::Point2D; use std::f64; +use std::collections::HashSet; /// Represents the area outside of the triangulation. /// Halfedges on the convex hull (which don't have an adjacent halfedge) @@ -165,6 +166,60 @@ impl Triangulation { result } + pub fn natural_neighbours_from_incoming_edge(&self, start: usize) -> Vec { + let mut result = vec![]; + //result.push(self.triangles[self.next_halfedge(start)]); + let mut incoming = start; + let mut outgoing: usize; + loop { + result.push(self.triangles[incoming]); + outgoing = self.next_halfedge(incoming); + incoming = self.halfedges[outgoing]; + if incoming == EMPTY { + break; + } else if incoming == start { + break; + } + } + result + } + + pub fn natural_neighbours_2nd_order(&self, start: usize) -> Vec { + let mut set = HashSet::new(); + let mut edges = vec![]; + // result.push(self.triangles[self.next_halfedge(start)]); + // set.insert(self.triangles[self.next_halfedge(start)]); + let mut incoming = start; + let mut outgoing: usize; + loop { + set.insert(self.triangles[incoming]); + outgoing = self.next_halfedge(incoming); + incoming = self.halfedges[outgoing]; + edges.push(outgoing); + if incoming == EMPTY { + break; + } else if incoming == start { + break; + } + } + + for start in edges { + incoming = start; + loop { + set.insert(self.triangles[incoming]); + outgoing = self.next_halfedge(incoming); + incoming = self.halfedges[outgoing]; + if incoming == EMPTY { + break; + } else if incoming == start { + break; + } + } + } + + set.into_iter().map(|i| i).collect() + } + /// Returns the indices of the adjacent triangles to a triangle. pub fn triangles_adjacent_to_triangle(&self, triangle: usize) -> Vec { let mut adjacent_triangles: Vec = vec![]; diff --git a/src/raster/arcascii_raster.rs b/src/raster/arcascii_raster.rs index 129ea9a67..59beb43a1 100644 --- a/src/raster/arcascii_raster.rs +++ b/src/raster/arcascii_raster.rs @@ -112,6 +112,8 @@ pub fn read_arcascii( yllcenter - (0.5 * configs.resolution_y) + (configs.rows as f64) * configs.resolution_y; } + configs.photometric_interp = PhotometricInterpretation::Continuous; + Ok(()) } diff --git a/src/raster/arcbinary_raster.rs b/src/raster/arcbinary_raster.rs index 156963eea..53262b670 100644 --- a/src/raster/arcbinary_raster.rs +++ b/src/raster/arcbinary_raster.rs @@ -83,6 +83,8 @@ pub fn read_arcbinary( } } + configs.photometric_interp = PhotometricInterpretation::Continuous; + configs.data_type = DataType::F32; // set the North, East, South, and West coodinates diff --git a/src/raster/geotiff/geokeys.rs b/src/raster/geotiff/geokeys.rs index 15913b58c..002ee674d 100644 --- a/src/raster/geotiff/geokeys.rs +++ b/src/raster/geotiff/geokeys.rs @@ -1,5 +1,6 @@ use super::Ifd; use crate::utils::{ByteOrderReader, Endianness}; +use crate::spatial_ref_system; use std::collections::HashMap; use std::fmt; use std::mem::transmute; @@ -205,8 +206,20 @@ impl GeoKeys { if keyword_map.contains_key(&key_code) { match keyword_map.get(&key_code) { Some(hm) => match hm.get(&value_offset) { - Some(v) => value = format!("{} ({})", v.to_string(), value_offset), //v.to_string(), - None => value = format!("Unrecognized value ({})", value_offset), + Some(v) => { + value = if key_code == 3072 || key_code == 2048 { + format!("{} ({})", v.to_string(), spatial_ref_system::esri_wkt_from_epsg(value_offset)) + } else { + format!("{} ({})", v.to_string(), value_offset) + }; + }, + None => { + value = if key_code == 3072 || key_code == 2048 { + spatial_ref_system::esri_wkt_from_epsg(value_offset) + } else { + format!("Unrecognized value ({})", value_offset) + }; + } }, None => value = format!("Unrecognized value ({})", key_code), } @@ -2651,6 +2664,7 @@ pub fn get_keyword_map() -> HashMap> { kw.insert(3076u16, proj_linear_units_map); let vertical_cs_type_map = hashmap![ + 1127=>"Canadian Geodetic Vertical Datum of 2013 (CGVD2013)", 5001=>"VertCS_Airy_1830_ellipsoid", 5002=>"VertCS_Airy_Modified_1849_ellipsoid", 5003=>"VertCS_ANS_ellipsoid", diff --git a/src/raster/geotiff/tiff_consts.rs b/src/raster/geotiff/tiff_consts.rs index cf8be7234..9acdd3381 100644 --- a/src/raster/geotiff/tiff_consts.rs +++ b/src/raster/geotiff/tiff_consts.rs @@ -1,162 +1,3 @@ -// #[repr(u16)] -// pub enum CompressType { -// COMPRESS_NONE = 1, -// COMPRESS_CCITT = 2, -// COMPRESS_G3 = 3, // Group 3 Fax. -// COMPRESS_G4 = 4, // Group 4 Fax. -// COMPRESS_LZW = 5, -// COMPRESS_JPEGOLD = 6, // Superseded by cJPEG. -// COMPRESS_JPEG = 7, -// COMPRESS_DEFLATE = 8, // zlib compression. -// COMPRESS_PACKBITS = 32773, -// COMPRESS_DEFLATEOLD = 32946, // Superseded by cDeflate. -// } - -// #[repr(u16)] -// pub enum TagType { -// DT_BYTE = 1, -// DT_ASCII = 2, -// DT_SHORT = 3, -// DT_LONG = 4, -// DT_RATIONAL = 5, -// DT_SBYTE = 6, -// DT_UNDEFINED = 7, -// DT_SSHORT = 8, -// DT_SLONG = 9, -// DT_SRATIONAL = 10, -// DT_FLOAT = 11, -// DT_DOUBLE = 12, -// } - -// const DT_BYTE: u16 = 1; -// const DT_ASCII: u16 = 2; -// const DT_SHORT: u16 = 3; -// const DT_LONG: u16 = 4; -// const DT_RATIONAL: u16 = 5; -// const DT_SBYTE: u16 = 6; -// const DT_UNDEFINED: u16 = 7; -// const DT_SSHORT: u16 = 8; -// const DT_SLONG: u16 = 9; -// const DT_SRATIONAL: u16 = 10; -// const DT_FLOAT: u16 = 11; -// const DT_DOUBLE: u16 = 12; - -// #[repr(u16)] -// pub enum PhotometricInterpretation { -// PI_WHITEISZERO = 0, -// PI_BLACKISZERO = 1, -// PI_RGB = 2, -// PI_PALETTED = 3, -// // const PI_TRANSMASK: u16 = 4; // transparency mask -// // const PI_CMYK: u16 = 5; -// // const PI_YCBCR: u16 = 6; -// // const PI_CIELAB: u16 = 8; -// } - -// // Tags (see p. 28-41 of the spec). -// #[repr(u16)] -// pub enum TiffTags { -// TAG_NEWSUBFILETYPE = 254u16, -// TAG_IMAGEWIDTH = 256u16, -// TAG_IMAGELENGTH = 257u16, -// TAG_BITSPERSAMPLE = 258u16, -// TAG_COMPRESSION = 259u16, -// TAG_PHOTOMETRICINTERPRETATION = 262u16, -// TAG_FILLORDER = 266u16, -// TAG_DOCUMENTNAME = 269u16, -// TAG_PLANARCONFIGURATION = 284u16, - -// TAG_STRIPOFFSETS = 273u16, -// TAG_ORIENTATION = 274u16, -// TAG_SAMPLESPERPIXEL = 277u16, -// TAG_ROWSPERSTRIP = 278u16, -// TAG_STRIPBYTECOUNTS = 279u16, - -// TAG_TILEWIDTH = 322u16, -// TAG_TILELENGTH = 323u16, -// TAG_TILEOFFSETS = 324u16, -// TAG_TILEBYTECOUNTS = 325u16, - -// TAG_XRESOLUTION = 282u16, -// TAG_YRESOLUTION = 283u16, -// TAG_RESOLUTIONUNIT = 296u16, - -// TAG_SOFTWARE = 305u16, -// TAG_PREDICTOR = 317u16, -// TAG_COLORMAP = 320u16, -// TAG_EXTRASAMPLES = 338u16, -// TAG_SAMPLEFORMAT = 339u16, - -// TAG_GDAL_METADATA = 42112u16, -// TAG_GDAL_NODATA = 42113u16, - -// TAG_MODELPIXELSCALETAG = 33550u16, -// TAG_MODELTRANSFORMATIONTAG = 34264u16, -// TAG_MODELTIEPOINTTAG = 33922u16, -// TAG_GEOKEYDIRECTORYTAG = 34735u16, -// TAG_GEODOUBLEPARAMSTAG = 34736u16, -// TAG_GEOASCIIPARAMSTAG = 34737u16, -// TAG_INTERGRAPHMATRIXTAG = 33920u16, - -// TAG_GTMODELTYPEGEOKEY = 1024u16, -// TAG_GTRASTERTYPEGEOKEY = 1025u16, -// TAG_GTCITATIONGEOKEY = 1026u16, -// TAG_GEOGRAPHICTYPEGEOKEY = 2048u16, -// TAG_GEOGCITATIONGEOKEY = 2049u16, -// TAG_GEOGGEODETICDATUMGEOKEY = 2050u16, -// TAG_GEOGPRIMEMERIDIANGEOKEY = 2051u16, -// TAG_GEOGLINEARUNITSGEOKEY = 2052u16, -// TAG_GEOGLINEARUNITSIZEGEOKEY = 2053u16, -// TAG_GEOGANGULARUNITSGEOKEY = 2054u16, -// TAG_GEOGANGULARUNITSIZEGEOKEY = 2055u16, -// TAG_GEOGELLIPSOIDGEOKEY = 2056u16, -// TAG_GEOGSEMIMAJORAXISGEOKEY = 2057u16, -// TAG_GEOGSEMIMINORAXISGEOKEY = 2058u16, -// TAG_GEOGINVFLATTENINGGEOKEY = 2059u16, -// TAG_GEOGAZIMUTHUNITSGEOKEY = 2060u16, -// TAG_GEOGPRIMEMERIDIANLONGGEOKEY = 2061u16, -// TAG_PROJECTEDCSTYPEGEOKEY = 3072u16, -// TAG_PCSCITATIONGEOKEY = 3073u16, -// TAG_PROJECTIONGEOKEY = 3074u16, -// TAG_PROJCOORDTRANSGEOKEY = 3075u16, -// TAG_PROJLINEARUNITSGEOKEY = 3076u16, -// TAG_PROJLINEARUNITSIZEGEOKEY = 3077u16, -// TAG_PROJSTDPARALLEL1GEOKEY = 3078u16, -// TAG_PROJSTDPARALLEL2GEOKEY = 3079u16, -// TAG_PROJNATORIGINLONGGEOKEY = 3080u16, -// TAG_PROJNATORIGINLATGEOKEY = 3081u16, -// TAG_PROJFALSEEASTINGGEOKEY = 3082u16, -// TAG_PROJFALSENORTHINGGEOKEY = 3083u16, -// TAG_PROJFALSEORIGINLONGGEOKEY = 3084u16, -// TAG_PROJFALSEORIGINLATGEOKEY = 3085u16, -// TAG_PROJFALSEORIGINEASTINGGEOKEY = 3086u16, -// TAG_PROJFALSEORIGINNORTHINGGEOKEY = 3087u16, -// TAG_PROJCENTERLONGGEOKEY = 3088u16, -// TAG_PROJCENTERLATGEOKEY = 3089u16, -// TAG_PROJCENTEREASTINGGEOKEY = 3090u16, -// TAG_PROJCENTERNORTHINGGEOKEY = 3091u16, -// TAG_PROJSCALEATNATORIGINGEOKEY = 3092u16, -// TAG_PROJSCALEATCENTERGEOKEY = 3093u16, -// TAG_PROJAZIMUTHANGLEGEOKEY = 3094u16, -// TAG_PROJSTRAIGHTVERTPOLELONGGEOKEY = 3095u16, -// TAG_VERTICALCSTYPEGEOKEY = 4096u16, -// TAG_VERTICALCITATIONGEOKEY = 4097u16, -// TAG_VERTICALDATUMGEOKEY = 4098u16, -// TAG_VERTICALUNITSGEOKEY = 4099u16, - -// TAG_PHOTOSHOP = 34377u16, -// } - -// pub enum ImageMode { -// Bilevel = 1, -// Paletted = 2, -// Gray = 3, -// GrayInvert = 4, -// RGB = 5, -// RGBA = 6, -// NRGBA = 7, -// } - // Image Modes pub const IM_BILEVEL: u16 = 1u16; pub const IM_PALETTED: u16 = 2u16; @@ -248,6 +89,7 @@ pub const TAG_MODELTIEPOINTTAG: u16 = 33922u16; pub const TAG_GEOKEYDIRECTORYTAG: u16 = 34735u16; pub const TAG_GEODOUBLEPARAMSTAG: u16 = 34736u16; pub const TAG_GEOASCIIPARAMSTAG: u16 = 34737u16; +pub const TAG_INTERGRRAPH_PACKET_DATA: u16 = 33918u16; pub const TAG_INTERGRAPHMATRIXTAG: u16 = 33920u16; pub const TAG_GTMODELTYPEGEOKEY: u16 = 1024u16; diff --git a/src/raster/grass_raster.rs b/src/raster/grass_raster.rs index 88ff2c9f3..679dda58a 100644 --- a/src/raster/grass_raster.rs +++ b/src/raster/grass_raster.rs @@ -98,6 +98,8 @@ pub fn read_grass_raster( } } + configs.photometric_interp = PhotometricInterpretation::Continuous; + Ok(()) } diff --git a/src/raster/idrisi_raster.rs b/src/raster/idrisi_raster.rs index d9c8d621f..221189538 100644 --- a/src/raster/idrisi_raster.rs +++ b/src/raster/idrisi_raster.rs @@ -150,9 +150,9 @@ pub fn read_idrisi( f.read(&mut buffer)?; // read the file's bytes into a buffer - //try!(f.read_to_end(&mut buffer)); + //f.read_to_end(&mut buffer)?; - //try!(br.fill_buf().unwrap()(&mut buffer)); + //br.fill_buf().unwrap()(&mut buffer)?; let mut offset: usize; match configs.data_type { @@ -370,7 +370,7 @@ pub fn write_idrisi<'a>(r: &'a mut Raster) -> Result<(), Error> { )); // for i in 0..num_cells { // u24_bytes = unsafe { mem::transmute(r.data[i] as u32) }; - // try!(writer.write(&u16_bytes)); + // writer.write(&u16_bytes)?; // } } DataType::I16 => { diff --git a/src/raster/mod.rs b/src/raster/mod.rs index a497c87cc..f63a42d0f 100644 --- a/src/raster/mod.rs +++ b/src/raster/mod.rs @@ -1291,7 +1291,7 @@ fn get_raster_type_from_file(file_name: String, file_mode: String) -> RasterType || l.contains("xllcenter") || l.contains("yllcenter") { - return RasterType::ArcAscii;; + return RasterType::ArcAscii; } if line_count > 7 { break; diff --git a/src/raster/saga_raster.rs b/src/raster/saga_raster.rs index cc120ea43..be9fc1abd 100644 --- a/src/raster/saga_raster.rs +++ b/src/raster/saga_raster.rs @@ -68,6 +68,7 @@ pub fn read_saga( )) } } + configs.photometric_interp = PhotometricInterpretation::Continuous; } else if vec[0].to_lowercase().contains("byteorder_big") { if vec[1].replace("=", "").trim().to_lowercase().contains("f") || vec[1] @@ -143,8 +144,7 @@ pub fn read_saga( configs.north = configs.south + configs.resolution_y * configs.rows as f64; configs.east = configs.west + configs.resolution_x * configs.columns as f64; - if z_factor < 0.0 && (configs.data_type == DataType::F32 || configs.data_type == DataType::F64) - { + if z_factor < 0.0 && (configs.data_type == DataType::F32 || configs.data_type == DataType::F64) { configs.data_type = DataType::F32; } @@ -367,6 +367,22 @@ pub fn read_saga( } } + /////////////////////////////////////////// + // Read the projection file if it exists // + /////////////////////////////////////////// + let prj_file = Path::new(&file_name).with_extension("prj").into_os_string().into_string().unwrap(); + match File::open(prj_file) { + Ok(f) => { + configs.projection = String::new(); + let f = BufReader::new(f); + for line in f.lines() { + let line_unwrapped = line.unwrap(); + configs.projection.push_str(&format!("{}\n", line_unwrapped)); + } + } + Err(_) => println!("Warning: Projection file not located."), + } + Ok(()) } @@ -568,5 +584,16 @@ pub fn write_saga<'a>(r: &'a mut Raster) -> Result<(), Error> { let _ = writer.flush(); + /////////////////////////////// + // Write the projection file // + /////////////////////////////// + + if !r.configs.projection.is_empty() { + let prj_file = Path::new(&r.file_name).with_extension("prj").into_os_string().into_string().unwrap(); + let f = File::create(&prj_file)?; + let mut writer = BufWriter::new(f); + writer.write_all(r.configs.projection.as_bytes())?; + } + Ok(()) } diff --git a/src/raster/surfer7_raster.rs b/src/raster/surfer7_raster.rs index b266bc5d0..29c9c0c54 100644 --- a/src/raster/surfer7_raster.rs +++ b/src/raster/surfer7_raster.rs @@ -303,6 +303,8 @@ pub fn read_surfer7( } } + configs.photometric_interp = PhotometricInterpretation::Continuous; + Ok(()) } diff --git a/src/raster/surfer_ascii_raster.rs b/src/raster/surfer_ascii_raster.rs index e3332118f..fde21e0b8 100644 --- a/src/raster/surfer_ascii_raster.rs +++ b/src/raster/surfer_ascii_raster.rs @@ -109,6 +109,7 @@ pub fn read_surfer_ascii_raster( line_num += 1; } + configs.photometric_interp = PhotometricInterpretation::Continuous; configs.resolution_x = (configs.east - configs.west) / configs.columns as f64; configs.resolution_y = (configs.north - configs.south) / configs.rows as f64; diff --git a/src/raster/whitebox_raster.rs b/src/raster/whitebox_raster.rs index 5cd473436..b759de810 100644 --- a/src/raster/whitebox_raster.rs +++ b/src/raster/whitebox_raster.rs @@ -118,7 +118,7 @@ pub fn read_whitebox( let data_file = Path::new(&file_name).with_extension("tas").into_os_string().into_string().unwrap(); let mut f = File::open(data_file.clone())?; //let br = BufReader::new(f); - // let metadata = try!(fs::metadata(data_file.clone())); + // let metadata = fs::metadata(data_file.clone())?; // let file_size: usize = metadata.len() as usize; // let mut buffer = vec![0; file_size]; @@ -426,12 +426,6 @@ pub fn write_whitebox<'a>(r: &'a mut Raster) -> Result<(), Error> { } } DataType::I32 => { - // if verbose { - // println!("Warning: the I32 data type may not be supported on all versions of Whitebox GAT."); - // } - // for i in 0..num_cells { - // writer.write_i32::(r.data[i] as i32)?; - // } for i in 0..num_cells { writer.write_f32::(r.data[i] as f32)?; } diff --git a/src/structures/array2d.rs b/src/structures/array2d.rs index ce9d41507..52e441c98 100644 --- a/src/structures/array2d.rs +++ b/src/structures/array2d.rs @@ -73,17 +73,17 @@ where } pub fn get_value(&self, row: isize, column: isize) -> T { - // if row < 0 || column < 0 { - // return self.nodata; - // } - // if row >= self.rows || column >= self.columns { - // return self.nodata; - // } - // self.data[(row * self.columns + column) as usize] - match self.data.get((row * self.columns + column) as usize) { - Some(v) => return *v, - None => return self.nodata(), - }; + if row < 0 || column < 0 { + return self.nodata; + } + if row >= self.rows || column >= self.columns { + return self.nodata; + } + self.data[(row * self.columns + column) as usize] + // match self.data.get((row * self.columns + column) as usize) { + // Some(v) => return *v, + // None => return self.nodata(), + // }; } pub fn increment(&mut self, row: isize, column: isize, value: T) { diff --git a/src/structures/mod.rs b/src/structures/mod.rs index 57246f8af..5efaed00c 100644 --- a/src/structures/mod.rs +++ b/src/structures/mod.rs @@ -9,6 +9,7 @@ mod n_minimizer; mod point2d; mod polyline; mod polynomial_regression_2d; +mod radial_basis_function; // exports identifiers from private sub-modules in the current module namespace pub use self::array2d::Array2D; @@ -22,4 +23,5 @@ pub use self::point2d::Direction; pub use self::point2d::Point2D; pub use self::polyline::MultiPolyline; pub use self::polyline::Polyline; -pub use self::polynomial_regression_2d::PolynomialRegression2D; \ No newline at end of file +pub use self::polynomial_regression_2d::PolynomialRegression2D; +pub use self::radial_basis_function::{Basis, RadialBasisFunction}; \ No newline at end of file diff --git a/src/structures/radial_basis_function.rs b/src/structures/radial_basis_function.rs new file mode 100644 index 000000000..d3a0ec357 --- /dev/null +++ b/src/structures/radial_basis_function.rs @@ -0,0 +1,143 @@ +//! Based on rbf-interp, a library for multidimensional interpolation. +//! by Raph Levien (raphlinus) +//! https://github.com/linebender/rbf-interp/blob/master/src/lib.rs +use nalgebra::{DMatrix, DVector, SVD}; + +#[derive(Clone, Copy)] +pub enum Basis { + ThinPlateSpine(f64), + PolyHarmonic(i32), + Gaussian(f64), + MultiQuadric(f64), + InverseMultiQuadric(f64), +} + +impl Basis { + fn eval(&self, r: f64) -> f64 { + match self { + Basis::ThinPlateSpine(c) => (c * c + r * r) * (c * c + r * r).ln(), // from Surfer (see Geospatial Analysis 6th Ed., 2018) + Basis::PolyHarmonic(n) if n % 2 == 0 => { + // Somewhat arbitrary but don't expect tiny nonzero values. + if r < 1e-12 { + 0.0 + } else { + r.powi(*n) * r.ln() + } + } + Basis::PolyHarmonic(n) => r.powi(*n), + // Note: it might be slightly more efficient to pre-recip c, but + // let's keep code clean for now. + Basis::Gaussian(c) => (-(r / c).powi(2)).exp(), + Basis::MultiQuadric(c) => r.hypot(*c), + Basis::InverseMultiQuadric(c) => (r * r + c * c).powf(-0.5), + } + } +} + +pub struct RadialBasisFunction { + // Note: could make basis a type-level parameter + basis: Basis, + // TODO(explore): use matrix & slicing instead (fewer allocs). + // An array of n vectors each of size m. + centers: Vec>, + // An m x n' matrix, where n' is the number of basis functions (including polynomial), + // and m is the number of coords. + deltas: DMatrix, +} + +impl RadialBasisFunction { + pub fn eval(&self, coords: DVector) -> DVector { + let n = self.centers.len(); + let basis = DVector::from_fn(self.deltas.ncols(), |row, _c| { + if row < n { + // component from basis functions + self.basis.eval((&coords - &self.centers[row]).norm()) + } else if row == n { + // constant component + 1.0 + } else { + // linear component + coords[row - n - 1] + } + }); + &self.deltas * basis + } + + // The order for the polynomial part, meaning terms up to (order - 1) are included. + // This usage is consistent with Wilna du Toit's masters thesis "Radial Basis + // Function Interpolation" + // Notice, a PolyHarmonic 2 and order of 2 is a thin plate spline. + pub fn create( + centers: Vec>, + vals: Vec>, + basis: Basis, + order: usize, + ) -> RadialBasisFunction { + let n = centers.len(); + // n x m matrix. There's probably a better way to do this, ah well. + let mut vals = DMatrix::from_columns(&vals).transpose(); + let n_aug = match order { + // Pure radial basis functions + 0 => n, + // Constant term + 1 => n + 1, + // Affine terms + 2 => n + 1 + centers[0].len(), + _ => unimplemented!("don't yet support higher order polynomials"), + }; + // Augment to n' x m matrix, where n' is the total number of basis functions. + if n_aug > n { + vals = vals.resize_vertically(n_aug, 0.0); + } + // We translate the system to center the mean at the origin so that when + // the system is degenerate, the pseudoinverse below minimizes the linear + // coefficients. + let means: Vec<_> = if order == 2 { + let n = centers.len(); + let n_recip = (n as f64).recip(); + (0..centers[0].len()) + .map(|i| centers.iter().map(|c| c[i]).sum::() * n_recip) + .collect() + } else { + Vec::new() + }; + let mat = DMatrix::from_fn(n_aug, n_aug, |r, c| { + if r < n && c < n { + basis.eval((¢ers[r] - ¢ers[c]).norm()) + } else if r < n { + if c == n { + 1.0 + } else { + centers[r][c - n - 1] - means[c - n - 1] + } + } else if c < n { + if r == n { + 1.0 + } else { + centers[c][r - n - 1] - means[r - n - 1] + } + } else { + 0.0 + } + }); + // inv is an n' x n' matrix. + let svd = SVD::new(mat, true, true); + // Use pseudo-inverse here to get "least squares fit" when there's + // no unique result (for example, when dimensionality is too small). + let inv = svd.pseudo_inverse(1e-6).expect("error inverting matrix"); + // Again, this transpose feels like I don't know what I'm doing. + let mut deltas = (inv * vals).transpose(); + if order == 2 { + let m = centers[0].len(); + for i in 0..deltas.nrows() { + let offset: f64 = (0..m).map(|j| means[j] * deltas[(i, n + 1 + j)]).sum(); + deltas[(i, n)] -= offset; + } + } + RadialBasisFunction { + basis, + centers, + deltas, + } + } +} diff --git a/src/tools/data_tools/add_point_coordinates_to_table.rs b/src/tools/data_tools/add_point_coordinates_to_table.rs index 89836aabc..20ca2e446 100644 --- a/src/tools/data_tools/add_point_coordinates_to_table.rs +++ b/src/tools/data_tools/add_point_coordinates_to_table.rs @@ -116,7 +116,7 @@ impl WhiteboxTool for AddPointCoordinatesToTable { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/clean_vector.rs b/src/tools/data_tools/clean_vector.rs index dea55148e..193768d22 100644 --- a/src/tools/data_tools/clean_vector.rs +++ b/src/tools/data_tools/clean_vector.rs @@ -127,7 +127,7 @@ impl WhiteboxTool for CleanVector { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/convert_nodata_to_zero.rs b/src/tools/data_tools/convert_nodata_to_zero.rs index 3812fb41c..4b14d5838 100644 --- a/src/tools/data_tools/convert_nodata_to_zero.rs +++ b/src/tools/data_tools/convert_nodata_to_zero.rs @@ -126,7 +126,7 @@ impl WhiteboxTool for ConvertNodataToZero { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/convert_raster_format.rs b/src/tools/data_tools/convert_raster_format.rs index 09e592e07..1bba1386d 100644 --- a/src/tools/data_tools/convert_raster_format.rs +++ b/src/tools/data_tools/convert_raster_format.rs @@ -122,7 +122,7 @@ impl WhiteboxTool for ConvertRasterFormat { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/csv_points_to_vector.rs b/src/tools/data_tools/csv_points_to_vector.rs index 3dbbf5d25..e3a303cbf 100644 --- a/src/tools/data_tools/csv_points_to_vector.rs +++ b/src/tools/data_tools/csv_points_to_vector.rs @@ -161,7 +161,7 @@ impl WhiteboxTool for CsvPointsToVector { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/export_table_to_csv.rs b/src/tools/data_tools/export_table_to_csv.rs index 2695b24dd..e42afda0c 100644 --- a/src/tools/data_tools/export_table_to_csv.rs +++ b/src/tools/data_tools/export_table_to_csv.rs @@ -137,7 +137,7 @@ impl WhiteboxTool for ExportTableToCsv { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/join_tables.rs b/src/tools/data_tools/join_tables.rs index 1103c60f6..fbafe1f7e 100644 --- a/src/tools/data_tools/join_tables.rs +++ b/src/tools/data_tools/join_tables.rs @@ -191,7 +191,7 @@ impl WhiteboxTool for JoinTables { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/lines_to_polygons.rs b/src/tools/data_tools/lines_to_polygons.rs index 8f37f8f9a..bb4a35c42 100644 --- a/src/tools/data_tools/lines_to_polygons.rs +++ b/src/tools/data_tools/lines_to_polygons.rs @@ -134,7 +134,7 @@ impl WhiteboxTool for LinesToPolygons { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/merge_table_with_csv.rs b/src/tools/data_tools/merge_table_with_csv.rs index f1873fc39..f9f0db594 100644 --- a/src/tools/data_tools/merge_table_with_csv.rs +++ b/src/tools/data_tools/merge_table_with_csv.rs @@ -195,7 +195,7 @@ impl WhiteboxTool for MergeTableWithCsv { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/merge_vectors.rs b/src/tools/data_tools/merge_vectors.rs index 4d45a8236..fa06ae5cc 100644 --- a/src/tools/data_tools/merge_vectors.rs +++ b/src/tools/data_tools/merge_vectors.rs @@ -144,7 +144,7 @@ impl WhiteboxTool for MergeVectors { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/modify_nodata_value.rs b/src/tools/data_tools/modify_nodata_value.rs index ade0e5502..0f55d8d7f 100644 --- a/src/tools/data_tools/modify_nodata_value.rs +++ b/src/tools/data_tools/modify_nodata_value.rs @@ -121,7 +121,7 @@ impl WhiteboxTool for ModifyNoDataValue { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/multipart_to_singlepart.rs b/src/tools/data_tools/multipart_to_singlepart.rs index 73f5f7da4..8f17a4e9f 100644 --- a/src/tools/data_tools/multipart_to_singlepart.rs +++ b/src/tools/data_tools/multipart_to_singlepart.rs @@ -146,7 +146,7 @@ impl WhiteboxTool for MultiPartToSinglePart { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/new_raster.rs b/src/tools/data_tools/new_raster.rs index 17054d2cd..1b6e02f52 100644 --- a/src/tools/data_tools/new_raster.rs +++ b/src/tools/data_tools/new_raster.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for NewRasterFromBase { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/polygons_to_lines.rs b/src/tools/data_tools/polygons_to_lines.rs index 0e99b8668..293bf8100 100644 --- a/src/tools/data_tools/polygons_to_lines.rs +++ b/src/tools/data_tools/polygons_to_lines.rs @@ -129,7 +129,7 @@ impl WhiteboxTool for PolygonsToLines { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/print_geotiff_tags.rs b/src/tools/data_tools/print_geotiff_tags.rs index bc5e09f82..a6ec35299 100644 --- a/src/tools/data_tools/print_geotiff_tags.rs +++ b/src/tools/data_tools/print_geotiff_tags.rs @@ -122,7 +122,7 @@ impl WhiteboxTool for PrintGeoTiffTags { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/raster_to_vector_lines.rs b/src/tools/data_tools/raster_to_vector_lines.rs index bc8b67a30..6f9caeedb 100644 --- a/src/tools/data_tools/raster_to_vector_lines.rs +++ b/src/tools/data_tools/raster_to_vector_lines.rs @@ -142,7 +142,7 @@ impl WhiteboxTool for RasterToVectorLines { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/raster_to_vector_points.rs b/src/tools/data_tools/raster_to_vector_points.rs index f5ef97f0b..3b413f842 100644 --- a/src/tools/data_tools/raster_to_vector_points.rs +++ b/src/tools/data_tools/raster_to_vector_points.rs @@ -130,7 +130,7 @@ impl WhiteboxTool for RasterToVectorPoints { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/reinitialize_attribute_table.rs b/src/tools/data_tools/reinitialize_attribute_table.rs index b3a802606..9e6b8184e 100644 --- a/src/tools/data_tools/reinitialize_attribute_table.rs +++ b/src/tools/data_tools/reinitialize_attribute_table.rs @@ -115,7 +115,7 @@ impl WhiteboxTool for ReinitializeAttributeTable { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/remove_polygon_holes.rs b/src/tools/data_tools/remove_polygon_holes.rs index d15e8ef67..2ca8ccf64 100644 --- a/src/tools/data_tools/remove_polygon_holes.rs +++ b/src/tools/data_tools/remove_polygon_holes.rs @@ -129,7 +129,7 @@ impl WhiteboxTool for RemovePolygonHoles { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/set_nodata_value.rs b/src/tools/data_tools/set_nodata_value.rs index e42784dd5..e50a000d0 100644 --- a/src/tools/data_tools/set_nodata_value.rs +++ b/src/tools/data_tools/set_nodata_value.rs @@ -147,7 +147,7 @@ impl WhiteboxTool for SetNodataValue { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/singlepart_to_multipart.rs b/src/tools/data_tools/singlepart_to_multipart.rs index db7503eaf..2ad30d43c 100644 --- a/src/tools/data_tools/singlepart_to_multipart.rs +++ b/src/tools/data_tools/singlepart_to_multipart.rs @@ -157,7 +157,7 @@ impl WhiteboxTool for SinglePartToMultiPart { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/vector_lines_to_raster.rs b/src/tools/data_tools/vector_lines_to_raster.rs index 270ebe04a..6cf8a8e38 100644 --- a/src/tools/data_tools/vector_lines_to_raster.rs +++ b/src/tools/data_tools/vector_lines_to_raster.rs @@ -185,7 +185,7 @@ impl WhiteboxTool for VectorLinesToRaster { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/vector_points_to_raster.rs b/src/tools/data_tools/vector_points_to_raster.rs index 7181dc5e3..e96ef9e6e 100644 --- a/src/tools/data_tools/vector_points_to_raster.rs +++ b/src/tools/data_tools/vector_points_to_raster.rs @@ -190,7 +190,7 @@ impl WhiteboxTool for VectorPointsToRaster { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/data_tools/vector_polygons_to_raster.rs b/src/tools/data_tools/vector_polygons_to_raster.rs index 5d9b5ef17..1061416c0 100644 --- a/src/tools/data_tools/vector_polygons_to_raster.rs +++ b/src/tools/data_tools/vector_polygons_to_raster.rs @@ -163,7 +163,7 @@ impl WhiteboxTool for VectorPolygonsToRaster { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/aggregate_raster.rs b/src/tools/gis_analysis/aggregate_raster.rs index 4e68ce3ca..11001a8e6 100644 --- a/src/tools/gis_analysis/aggregate_raster.rs +++ b/src/tools/gis_analysis/aggregate_raster.rs @@ -151,7 +151,7 @@ impl WhiteboxTool for AggregateRaster { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/average_overlay.rs b/src/tools/gis_analysis/average_overlay.rs index d8f7ff4ed..b2a70a8e7 100644 --- a/src/tools/gis_analysis/average_overlay.rs +++ b/src/tools/gis_analysis/average_overlay.rs @@ -125,7 +125,7 @@ impl WhiteboxTool for AverageOverlay { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/block_maximum.rs b/src/tools/gis_analysis/block_maximum.rs index e82e60392..498669f10 100644 --- a/src/tools/gis_analysis/block_maximum.rs +++ b/src/tools/gis_analysis/block_maximum.rs @@ -2,7 +2,7 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay Created: 09/10/2018 -Last Modified: 18/10/2019 +Last Modified: 09/12/2019 License: MIT */ @@ -158,7 +158,7 @@ impl WhiteboxTool for BlockMaximumGridding { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -251,7 +251,8 @@ impl WhiteboxTool for BlockMaximumGridding { if !base_file.contains(&sep) && !base_file.contains("/") { base_file = format!("{}{}", working_directory, base_file); } - let base = Raster::new(&base_file, "r")?; + let mut base = Raster::new(&base_file, "r")?; + base.configs.nodata = nodata; Raster::initialize_using_file(&output_file, &base) } else { // base the output raster on the grid_res and the diff --git a/src/tools/gis_analysis/block_minimum.rs b/src/tools/gis_analysis/block_minimum.rs index aa1a9b6f7..e8c517d9e 100644 --- a/src/tools/gis_analysis/block_minimum.rs +++ b/src/tools/gis_analysis/block_minimum.rs @@ -2,7 +2,7 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay Created: 09/10/2018 -Last Modified: 18/10/2019 +Last Modified: 09/12/2019 License: MIT */ @@ -158,7 +158,7 @@ impl WhiteboxTool for BlockMinimumGridding { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -251,7 +251,8 @@ impl WhiteboxTool for BlockMinimumGridding { if !base_file.contains(&sep) && !base_file.contains("/") { base_file = format!("{}{}", working_directory, base_file); } - let base = Raster::new(&base_file, "r")?; + let mut base = Raster::new(&base_file, "r")?; + base.configs.nodata = nodata; Raster::initialize_using_file(&output_file, &base) } else { // base the output raster on the grid_res and the diff --git a/src/tools/gis_analysis/boundary_shape_complexity.rs b/src/tools/gis_analysis/boundary_shape_complexity.rs index 2d8d3c9f1..316fff0e8 100644 --- a/src/tools/gis_analysis/boundary_shape_complexity.rs +++ b/src/tools/gis_analysis/boundary_shape_complexity.rs @@ -135,7 +135,7 @@ impl WhiteboxTool for BoundaryShapeComplexity { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/buffer_raster.rs b/src/tools/gis_analysis/buffer_raster.rs index 153ecdf18..ac9832c19 100644 --- a/src/tools/gis_analysis/buffer_raster.rs +++ b/src/tools/gis_analysis/buffer_raster.rs @@ -160,7 +160,7 @@ impl WhiteboxTool for BufferRaster { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/buffer_vector.rs b/src/tools/gis_analysis/buffer_vector.rs index d5a43f027..cc8641286 100644 --- a/src/tools/gis_analysis/buffer_vector.rs +++ b/src/tools/gis_analysis/buffer_vector.rs @@ -173,7 +173,7 @@ // if args.len() == 0 { // return Err(Error::new( // ErrorKind::InvalidInput, -// "Tool run with no paramters.", +// "Tool run with no parameters.", // )); // } // for i in 0..args.len() { diff --git a/src/tools/gis_analysis/centroid.rs b/src/tools/gis_analysis/centroid.rs index 102652afe..82e410e40 100644 --- a/src/tools/gis_analysis/centroid.rs +++ b/src/tools/gis_analysis/centroid.rs @@ -133,7 +133,7 @@ impl WhiteboxTool for Centroid { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/centroid_vector.rs b/src/tools/gis_analysis/centroid_vector.rs index aec6c1b77..151c1fb0b 100644 --- a/src/tools/gis_analysis/centroid_vector.rs +++ b/src/tools/gis_analysis/centroid_vector.rs @@ -137,7 +137,7 @@ impl WhiteboxTool for CentroidVector { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/clip.rs b/src/tools/gis_analysis/clip.rs index 00f969a68..e9ea64a8c 100644 --- a/src/tools/gis_analysis/clip.rs +++ b/src/tools/gis_analysis/clip.rs @@ -164,7 +164,7 @@ impl WhiteboxTool for Clip { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -637,8 +637,7 @@ impl WhiteboxTool for Clip { // Break the polygons up into lines at junction points. let dimensions = 2; let capacity_per_node = 64; - let mut snap_tree = - KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut snap_tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; for i in 0..polygons.len() { for j in 0..polygons[i].len() { @@ -780,7 +779,7 @@ impl WhiteboxTool for Clip { // now add the endpoints of each polyline into a kd tree let dimensions = 2; let capacity_per_node = 64; - let mut kdtree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut kdtree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p1: Point2D; let mut p2: Point2D; let mut p3: Point2D; diff --git a/src/tools/gis_analysis/clip_raster_to_polygon.rs b/src/tools/gis_analysis/clip_raster_to_polygon.rs index 575e57e46..5bf77dcfb 100644 --- a/src/tools/gis_analysis/clip_raster_to_polygon.rs +++ b/src/tools/gis_analysis/clip_raster_to_polygon.rs @@ -148,7 +148,7 @@ impl WhiteboxTool for ClipRasterToPolygon { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/clump.rs b/src/tools/gis_analysis/clump.rs index d14d17cd4..30a641e68 100644 --- a/src/tools/gis_analysis/clump.rs +++ b/src/tools/gis_analysis/clump.rs @@ -152,7 +152,7 @@ impl WhiteboxTool for Clump { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -224,7 +224,6 @@ impl WhiteboxTool for Clump { output.configs.nodata = out_nodata; output.configs.photometric_interp = PhotometricInterpretation::Categorical; output.configs.data_type = DataType::I32; - // output.configs.data_type = DataType::F32; let mut dx = [1, 1, 1, 0, -1, -1, -1, 0]; let mut dy = [-1, 0, 1, 1, 1, 0, -1, -1]; diff --git a/src/tools/gis_analysis/compactness_ratio.rs b/src/tools/gis_analysis/compactness_ratio.rs index 559c4ca28..0624d3740 100644 --- a/src/tools/gis_analysis/compactness_ratio.rs +++ b/src/tools/gis_analysis/compactness_ratio.rs @@ -127,7 +127,7 @@ impl WhiteboxTool for CompactnessRatio { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/construct_vector_tin.rs b/src/tools/gis_analysis/construct_vector_tin.rs index 317f381be..ed1db7a1f 100644 --- a/src/tools/gis_analysis/construct_vector_tin.rs +++ b/src/tools/gis_analysis/construct_vector_tin.rs @@ -2,7 +2,7 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay Created: 21/09/2018 -Last Modified: 18/10/2019 +Last Modified: 07/12/2019 License: MIT */ @@ -24,6 +24,10 @@ use std::path; /// or alternatively, if the vector is of a z-dimension *ShapeTypeDimension*, the point z-values may be /// used for vertex heights (`--use_z`). For LiDAR points, use the `LidarConstructVectorTIN` tool instead. /// +/// Triangulation often creates very long, narrow triangles near the edges of the data coverage, particularly +/// in convex regions along the data boundary. To avoid these spurious triangles, the user may optionally +/// specify the maximum allowable edge length of a triangular facet (`--max_triangle_edge_length`). +/// /// # See Also /// `LidarConstructVectorTIN` pub struct ConstructVectorTIN { @@ -89,6 +93,15 @@ impl ConstructVectorTIN { optional: false, }); + parameters.push(ToolParameter { + name: "Maximum Triangle Edge Length (optional)".to_owned(), + flags: vec!["--max_triangle_edge_length".to_owned()], + description: "Optional maximum triangle edge length; triangles larger than this size will not be gridded.".to_owned(), + parameter_type: ParameterType::Float, + default_value: None, + optional: true, + }); + let sep: String = path::MAIN_SEPARATOR.to_string(); let p = format!("{}", env::current_dir().unwrap().display()); let e = format!("{}", env::current_exe().unwrap().display()); @@ -163,12 +176,13 @@ impl WhiteboxTool for ConstructVectorTIN { let mut use_z = false; let mut use_field = false; let mut output_file: String = "".to_string(); + let mut max_triangle_edge_length = f64::INFINITY; // read the arguments if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -204,6 +218,14 @@ impl WhiteboxTool for ConstructVectorTIN { } else { args[i + 1].to_string() }; + } else if flag_val == "-max_triangle_edge_length" { + max_triangle_edge_length = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; + + max_triangle_edge_length *= max_triangle_edge_length; // actually squared distance } } @@ -350,64 +372,68 @@ impl WhiteboxTool for ConstructVectorTIN { p2 = result.triangles[i + 1]; p3 = result.triangles[i]; - let mut tri_points: Vec = Vec::with_capacity(4); - tri_points.push(points[p1].clone()); - tri_points.push(points[p2].clone()); - tri_points.push(points[p3].clone()); - tri_points.push(points[p1].clone()); - - let mut sfg = ShapefileGeometry::new(ShapeType::Polygon); - sfg.add_part(&tri_points); - output.add_record(sfg); - - if use_field || use_z { - // calculate the hillshade value - let a = Vector3::new(tri_points[0].x, tri_points[0].y, z_values[p1]); - let b = Vector3::new(tri_points[1].x, tri_points[1].y, z_values[p2]); - let c = Vector3::new(tri_points[2].x, tri_points[2].y, z_values[p3]); - let norm = (b - a).cross(&(c - a)); //).normalize(); - let centroid = (a + b + c) / 3f64; - // k = -(tri_points[0].x * norm.x + tri_points[0].y * norm.y + norm.z * z_values[p1]); - // centroid_z = -(norm.x * centroid.x + norm.y * centroid.y + k) / norm.z; - - hillshade = 0f64; - if norm.z != 0f64 { - fx = -norm.x / norm.z; - fy = -norm.y / norm.z; - if fx != 0f64 { - tan_slope = (fx * fx + fy * fy).sqrt(); - aspect = (180f64 - ((fy / fx).atan()).to_degrees() - + 90f64 * (fx / (fx).abs())) - .to_radians(); - term1 = tan_slope / (1f64 + tan_slope * tan_slope).sqrt(); - term2 = sin_theta / tan_slope; - term3 = cos_theta * (azimuth - aspect).sin(); - hillshade = term1 * (term2 - term3); - } else { - hillshade = 0.5; - } - hillshade = hillshade * 1024f64; - if hillshade < 0f64 { - hillshade = 0f64; + if max_distance_squared(points[p1], points[p2], points[p3], z_values[p1], + z_values[p2], z_values[p3]) < max_triangle_edge_length { + + let mut tri_points: Vec = Vec::with_capacity(4); + tri_points.push(points[p1].clone()); + tri_points.push(points[p2].clone()); + tri_points.push(points[p3].clone()); + tri_points.push(points[p1].clone()); + + let mut sfg = ShapefileGeometry::new(ShapeType::Polygon); + sfg.add_part(&tri_points); + output.add_record(sfg); + + if use_field || use_z { + // calculate the hillshade value + let a = Vector3::new(tri_points[0].x, tri_points[0].y, z_values[p1]); + let b = Vector3::new(tri_points[1].x, tri_points[1].y, z_values[p2]); + let c = Vector3::new(tri_points[2].x, tri_points[2].y, z_values[p3]); + let norm = (b - a).cross(&(c - a)); //).normalize(); + let centroid = (a + b + c) / 3f64; + // k = -(tri_points[0].x * norm.x + tri_points[0].y * norm.y + norm.z * z_values[p1]); + // centroid_z = -(norm.x * centroid.x + norm.y * centroid.y + k) / norm.z; + + hillshade = 0f64; + if norm.z != 0f64 { + fx = -norm.x / norm.z; + fy = -norm.y / norm.z; + if fx != 0f64 { + tan_slope = (fx * fx + fy * fy).sqrt(); + aspect = (180f64 - ((fy / fx).atan()).to_degrees() + + 90f64 * (fx / (fx).abs())) + .to_radians(); + term1 = tan_slope / (1f64 + tan_slope * tan_slope).sqrt(); + term2 = sin_theta / tan_slope; + term3 = cos_theta * (azimuth - aspect).sin(); + hillshade = term1 * (term2 - term3); + } else { + hillshade = 0.5; + } + hillshade = hillshade * 1024f64; + if hillshade < 0f64 { + hillshade = 0f64; + } } + + output.attributes.add_record( + vec![ + FieldData::Int(rec_num), + FieldData::Real(centroid.z), + FieldData::Int(hillshade as i32), + ], + false, + ); + } else { + output + .attributes + .add_record(vec![FieldData::Int(rec_num)], false); } - output.attributes.add_record( - vec![ - FieldData::Int(rec_num), - FieldData::Real(centroid.z), - FieldData::Int(hillshade as i32), - ], - false, - ); - } else { - output - .attributes - .add_record(vec![FieldData::Int(rec_num)], false); + rec_num += 1i32; } - rec_num += 1i32; - if verbose { progress = (100.0_f64 * i as f64 / (result.triangles.len() - 1) as f64) as usize; if progress != old_progress { @@ -438,3 +464,31 @@ impl WhiteboxTool for ConstructVectorTIN { Ok(()) } } + +/// Calculate squared Euclidean distance between the point and another. +pub fn max_distance_squared(p1: Point2D, p2: Point2D, p3: Point2D, z1: f64, z2: f64, z3: f64) -> f64 { + let mut dx = p1.x - p2.x; + let mut dy = p1.y - p2.y; + let mut dz = z1 - z2; + let mut max_dist = dx * dx + dy * dy + dz * dz; + + dx = p1.x - p3.x; + dy = p1.y - p3.y; + dz = z1 - z3; + let mut dist = dx * dx + dy * dy + dz * dz; + + if dist > max_dist { + max_dist = dist + } + + dx = p2.x - p3.x; + dy = p2.y - p3.y; + dz = z2 - z3; + dist = dx * dx + dy * dy + dz * dz; + + if dist > max_dist { + max_dist = dist + } + + max_dist +} \ No newline at end of file diff --git a/src/tools/gis_analysis/cost_allocation.rs b/src/tools/gis_analysis/cost_allocation.rs index 6dd6d6f4e..88158a2aa 100644 --- a/src/tools/gis_analysis/cost_allocation.rs +++ b/src/tools/gis_analysis/cost_allocation.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 4, 2017 +Created: 04/072017 Last Modified: 13/10/2018 License: MIT @@ -137,7 +137,7 @@ impl WhiteboxTool for CostAllocation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/cost_distance.rs b/src/tools/gis_analysis/cost_distance.rs index 75805836e..266e48fd9 100644 --- a/src/tools/gis_analysis/cost_distance.rs +++ b/src/tools/gis_analysis/cost_distance.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 4, 2017 +Created: 04/07/2017 Last Modified: 15/11/2018 License: MIT @@ -167,7 +167,7 @@ impl WhiteboxTool for CostDistance { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/cost_pathway.rs b/src/tools/gis_analysis/cost_pathway.rs index b1983363e..2d6e85e1e 100644 --- a/src/tools/gis_analysis/cost_pathway.rs +++ b/src/tools/gis_analysis/cost_pathway.rs @@ -147,7 +147,7 @@ impl WhiteboxTool for CostPathway { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/count_if.rs b/src/tools/gis_analysis/count_if.rs index 54ce857cc..96b00a407 100644 --- a/src/tools/gis_analysis/count_if.rs +++ b/src/tools/gis_analysis/count_if.rs @@ -130,7 +130,7 @@ impl WhiteboxTool for CountIf { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/create_hexagonal_vector_grid.rs b/src/tools/gis_analysis/create_hexagonal_vector_grid.rs index 2732ec31c..9f5309593 100644 --- a/src/tools/gis_analysis/create_hexagonal_vector_grid.rs +++ b/src/tools/gis_analysis/create_hexagonal_vector_grid.rs @@ -159,7 +159,7 @@ impl WhiteboxTool for CreateHexagonalVectorGrid { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/create_plane.rs b/src/tools/gis_analysis/create_plane.rs index dc56e4727..cd092aca9 100644 --- a/src/tools/gis_analysis/create_plane.rs +++ b/src/tools/gis_analysis/create_plane.rs @@ -155,7 +155,7 @@ impl WhiteboxTool for CreatePlane { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/create_rectangular_vector_grid.rs b/src/tools/gis_analysis/create_rectangular_vector_grid.rs index 7df54e236..7a7d7557e 100644 --- a/src/tools/gis_analysis/create_rectangular_vector_grid.rs +++ b/src/tools/gis_analysis/create_rectangular_vector_grid.rs @@ -176,7 +176,7 @@ impl WhiteboxTool for CreateRectangularVectorGrid { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/difference.rs b/src/tools/gis_analysis/difference.rs index 9aac6117b..272803250 100644 --- a/src/tools/gis_analysis/difference.rs +++ b/src/tools/gis_analysis/difference.rs @@ -175,7 +175,7 @@ impl WhiteboxTool for Difference { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -294,7 +294,7 @@ impl WhiteboxTool for Difference { // place the points from both files into a KD-tree let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; for record_num in 0..input.num_records { let record = input.get_record(record_num); @@ -347,7 +347,7 @@ impl WhiteboxTool for Difference { // place the points from both files into a KD-tree let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; let mut total_points = 0; for record_num in 0..input.num_records { @@ -510,7 +510,7 @@ impl WhiteboxTool for Difference { // Break the polylines up into shorter lines at junction points. let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; for i in 0..polylines.len() { for j in 0..polylines[i].len() { @@ -742,8 +742,7 @@ impl WhiteboxTool for Difference { // Break the polygons up into lines at junction points. let dimensions = 2; let capacity_per_node = 64; - let mut snap_tree = - KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut snap_tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; for i in 0..polygons.len() { for j in 0..polygons[i].len() { @@ -902,8 +901,7 @@ impl WhiteboxTool for Difference { // now add the endpoints of each polyline into a kd tree let dimensions = 2; let capacity_per_node = 64; - let mut kdtree = - KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut kdtree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p1: Point2D; let mut p2: Point2D; let mut p3: Point2D; diff --git a/src/tools/gis_analysis/dissolve.rs b/src/tools/gis_analysis/dissolve.rs index 94c1435da..15cb0237c 100644 --- a/src/tools/gis_analysis/dissolve.rs +++ b/src/tools/gis_analysis/dissolve.rs @@ -169,7 +169,7 @@ impl WhiteboxTool for Dissolve { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -338,7 +338,7 @@ impl WhiteboxTool for Dissolve { }; let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); for i in 0..num_polygons { for j in 0..polygons[i].len() { p = polygons[i][j]; @@ -535,7 +535,7 @@ impl WhiteboxTool for Dissolve { // now add the endpoints of each polyline into a kd tree let dimensions = 2; let capacity_per_node = 64; - let mut kdtree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut kdtree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p1: Point2D; let mut p2: Point2D; let mut p3: Point2D; diff --git a/src/tools/gis_analysis/edge_proportion.rs b/src/tools/gis_analysis/edge_proportion.rs index 836a87ac8..4287b82f3 100644 --- a/src/tools/gis_analysis/edge_proportion.rs +++ b/src/tools/gis_analysis/edge_proportion.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for EdgeProportion { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/eliminate_coincident_points.rs b/src/tools/gis_analysis/eliminate_coincident_points.rs index e067e89a0..cd02d3130 100644 --- a/src/tools/gis_analysis/eliminate_coincident_points.rs +++ b/src/tools/gis_analysis/eliminate_coincident_points.rs @@ -147,7 +147,7 @@ impl WhiteboxTool for EliminateCoincidentPoints { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/elongation_ratio.rs b/src/tools/gis_analysis/elongation_ratio.rs index f0c7842cb..6f229ee55 100644 --- a/src/tools/gis_analysis/elongation_ratio.rs +++ b/src/tools/gis_analysis/elongation_ratio.rs @@ -129,7 +129,7 @@ impl WhiteboxTool for ElongationRatio { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/erase.rs b/src/tools/gis_analysis/erase.rs index 4d5c1db4d..31dd66188 100644 --- a/src/tools/gis_analysis/erase.rs +++ b/src/tools/gis_analysis/erase.rs @@ -161,7 +161,7 @@ impl WhiteboxTool for Erase { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -636,8 +636,7 @@ impl WhiteboxTool for Erase { // Break the polygons up into lines at junction points. let dimensions = 2; let capacity_per_node = 64; - let mut snap_tree = - KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut snap_tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; for i in 0..polygons.len() { for j in 0..polygons[i].len() { @@ -779,7 +778,7 @@ impl WhiteboxTool for Erase { // now add the endpoints of each polyline into a kd tree let dimensions = 2; let capacity_per_node = 64; - let mut kdtree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut kdtree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p1: Point2D; let mut p2: Point2D; let mut p3: Point2D; diff --git a/src/tools/gis_analysis/erase_polygon_from_raster.rs b/src/tools/gis_analysis/erase_polygon_from_raster.rs index 78ff9cdad..d5d8f444c 100644 --- a/src/tools/gis_analysis/erase_polygon_from_raster.rs +++ b/src/tools/gis_analysis/erase_polygon_from_raster.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for ErasePolygonFromRaster { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/euclidean_allocation.rs b/src/tools/gis_analysis/euclidean_allocation.rs index 245b92f95..6ea17d390 100644 --- a/src/tools/gis_analysis/euclidean_allocation.rs +++ b/src/tools/gis_analysis/euclidean_allocation.rs @@ -128,7 +128,7 @@ impl WhiteboxTool for EuclideanAllocation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/euclidean_distance.rs b/src/tools/gis_analysis/euclidean_distance.rs index 96a708dfe..d47c19c78 100644 --- a/src/tools/gis_analysis/euclidean_distance.rs +++ b/src/tools/gis_analysis/euclidean_distance.rs @@ -1,8 +1,8 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 22 2017 -Last Modified: 25/11/2018 +Created: 22/06/2017 +Last Modified: 05/12/2019 License: MIT */ @@ -137,7 +137,7 @@ impl WhiteboxTool for EuclideanDistance { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -149,18 +149,19 @@ impl WhiteboxTool for EuclideanDistance { if vec.len() > 1 { keyval = true; } - if vec[0].to_lowercase() == "-i" || vec[0].to_lowercase() == "--input" { - if keyval { - input_file = vec[1].to_string(); + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-i" || flag_val == "-input" { + input_file = if keyval { + vec[1].to_string() } else { - input_file = args[i + 1].to_string(); - } - } else if vec[0].to_lowercase() == "-o" || vec[0].to_lowercase() == "--output" { - if keyval { - output_file = vec[1].to_string(); + args[i + 1].to_string() + }; + } else if flag_val == "-o" || flag_val == "-output" { + output_file = if keyval { + vec[1].to_string() } else { - output_file = args[i + 1].to_string(); - } + args[i + 1].to_string() + }; } } @@ -191,31 +192,31 @@ impl WhiteboxTool for EuclideanDistance { let nodata = input.configs.nodata; let rows = input.configs.rows as isize; let columns = input.configs.columns as isize; - let mut r_x: Array2D = Array2D::new(rows, columns, 0f64, nodata)?; - let mut r_y: Array2D = Array2D::new(rows, columns, 0f64, nodata)?; - + let start = Instant::now(); + let mut rx: Array2D = Array2D::new(rows, columns, 0f64, nodata)?; + let mut ry: Array2D = Array2D::new(rows, columns, 0f64, nodata)?; + let mut output = Raster::initialize_using_file(&output_file, &input); output.configs.data_type = DataType::F32; let mut h: f64; let mut which_cell: usize; let inf_val = f64::INFINITY; - let d_x = [-1, -1, 0, 1, 1, 1, 0, -1]; - let d_y = [0, -1, -1, -1, 0, 1, 1, 1]; - let g_x = [1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0]; - let g_y = [0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0]; + let dx = [-1, -1, 0, 1, 1, 1, 0, -1]; + let dy = [0, -1, -1, -1, 0, 1, 1, 1]; + let gx = [1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0]; + let gy = [0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0]; let (mut x, mut y): (isize, isize); let (mut z, mut z2, mut z_min): (f64, f64, f64); for row in 0..rows { for col in 0..columns { - z = input[(row, col)]; - if z != 0.0 { - output[(row, col)] = 0.0; + if input.get_value(row, col) != 0.0 { + output.set_value(row, col, 0.0); } else { - output[(row, col)] = inf_val; + output.set_value(row, col, inf_val); } } if verbose { @@ -229,20 +230,20 @@ impl WhiteboxTool for EuclideanDistance { for row in 0..rows { for col in 0..columns { - z = output[(row, col)]; + z = output.get_value(row, col); if z != 0.0 { z_min = inf_val; which_cell = 0; for i in 0..4 { - x = col + d_x[i]; - y = row + d_y[i]; - z2 = output[(y, x)]; + x = col + dx[i]; + y = row + dy[i]; + z2 = output.get_value(y, x); if z2 != nodata { h = match i { - 0 => 2.0 * r_x[(y, x)] + 1.0, - 1 => 2.0 * (r_x[(y, x)] + r_y[(y, x)] + 1.0), - 2 => 2.0 * r_y[(y, x)] + 1.0, - _ => 2.0 * (r_x[(y, x)] + r_y[(y, x)] + 1.0), // 3 + 0 => 2.0 * rx.get_value(y, x) + 1.0, + 1 => 2.0 * (rx.get_value(y, x) + ry.get_value(y, x) + 1.0), + 2 => 2.0 * ry.get_value(y, x) + 1.0, + _ => 2.0 * (rx.get_value(y, x) + ry.get_value(y, x) + 1.0), // 3 }; z2 += h; if z2 < z_min { @@ -252,11 +253,11 @@ impl WhiteboxTool for EuclideanDistance { } } if z_min < z { - output[(row, col)] = z_min; - x = col + d_x[which_cell]; - y = row + d_y[which_cell]; - r_x[(row, col)] = r_x[(y, x)] + g_x[which_cell]; - r_y[(row, col)] = r_y[(y, x)] + g_y[which_cell]; + output.set_value(row, col, z_min); + x = col + dx[which_cell]; + y = row + dy[which_cell]; + rx.set_value(row, col, rx.get_value(y, x) + gx[which_cell]); + ry.set_value(row, col, ry.get_value(y, x) + gy[which_cell]); } } } @@ -271,20 +272,20 @@ impl WhiteboxTool for EuclideanDistance { for row in (0..rows).rev() { for col in (0..columns).rev() { - z = output[(row, col)]; + z = output.get_value(row, col); if z != 0.0 { z_min = inf_val; which_cell = 0; for i in 4..8 { - x = col + d_x[i]; - y = row + d_y[i]; - z2 = output[(y, x)]; + x = col + dx[i]; + y = row + dy[i]; + z2 = output.get_value(y, x); if z2 != nodata { h = match i { - 5 => 2.0 * (r_x[(y, x)] + r_y[(y, x)] + 1.0), - 4 => 2.0 * r_x[(y, x)] + 1.0, - 6 => 2.0 * r_y[(y, x)] + 1.0, - _ => 2.0 * (r_x[(y, x)] + r_y[(y, x)] + 1.0), // 7 + 5 => 2.0 * (rx.get_value(y, x) + ry.get_value(y, x) + 1.0), + 4 => 2.0 * rx.get_value(y, x) + 1.0, + 6 => 2.0 * ry.get_value(y, x) + 1.0, + _ => 2.0 * (rx.get_value(y, x) + ry.get_value(y, x) + 1.0), // 7 }; z2 += h; if z2 < z_min { @@ -295,10 +296,10 @@ impl WhiteboxTool for EuclideanDistance { } if z_min < z { output[(row, col)] = z_min; - x = col + d_x[which_cell]; - y = row + d_y[which_cell]; - r_x[(row, col)] = r_x[(y, x)] + g_x[which_cell]; - r_y[(row, col)] = r_y[(y, x)] + g_y[which_cell]; + x = col + dx[which_cell]; + y = row + dy[which_cell]; + rx.set_value(row, col, rx.get_value(y, x) + gx[which_cell]); + ry.set_value(row, col, ry.get_value(y, x) + gy[which_cell]); } } } @@ -314,11 +315,10 @@ impl WhiteboxTool for EuclideanDistance { let cell_size = (input.configs.resolution_x + input.configs.resolution_y) / 2.0; for row in 0..rows { for col in 0..columns { - z = input[(row, col)]; - if z != nodata { - output[(row, col)] = output[(row, col)].sqrt() * cell_size; + if input.get_value(row, col) != nodata { + output.set_value(row, col, output.get_value(row, col).sqrt() * cell_size); } else { - output[(row, col)] = nodata; + output.set_value(row, col, nodata); } } if verbose { diff --git a/src/tools/gis_analysis/extend_vector_lines.rs b/src/tools/gis_analysis/extend_vector_lines.rs index c1db8e8d3..0268996fa 100644 --- a/src/tools/gis_analysis/extend_vector_lines.rs +++ b/src/tools/gis_analysis/extend_vector_lines.rs @@ -153,7 +153,7 @@ impl WhiteboxTool for ExtendVectorLines { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/extract_nodes.rs b/src/tools/gis_analysis/extract_nodes.rs index b9766e738..2944f589b 100644 --- a/src/tools/gis_analysis/extract_nodes.rs +++ b/src/tools/gis_analysis/extract_nodes.rs @@ -128,7 +128,7 @@ impl WhiteboxTool for ExtractNodes { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/extract_raster_values_at_points.rs b/src/tools/gis_analysis/extract_raster_values_at_points.rs index 10071f72e..7afc77a06 100644 --- a/src/tools/gis_analysis/extract_raster_values_at_points.rs +++ b/src/tools/gis_analysis/extract_raster_values_at_points.rs @@ -143,7 +143,7 @@ impl WhiteboxTool for ExtractRasterValuesAtPoints { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/find_lowest_or_highest_points.rs b/src/tools/gis_analysis/find_lowest_or_highest_points.rs index aa2550969..721436dad 100644 --- a/src/tools/gis_analysis/find_lowest_or_highest_points.rs +++ b/src/tools/gis_analysis/find_lowest_or_highest_points.rs @@ -146,7 +146,7 @@ impl WhiteboxTool for FindLowestOrHighestPoints { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/find_patch_edge_cells.rs b/src/tools/gis_analysis/find_patch_edge_cells.rs index 218307d1f..937e3ac64 100644 --- a/src/tools/gis_analysis/find_patch_edge_cells.rs +++ b/src/tools/gis_analysis/find_patch_edge_cells.rs @@ -127,7 +127,7 @@ impl WhiteboxTool for FindPatchOrClassEdgeCells { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/highest_pos.rs b/src/tools/gis_analysis/highest_pos.rs index 28936ea3a..070d12324 100644 --- a/src/tools/gis_analysis/highest_pos.rs +++ b/src/tools/gis_analysis/highest_pos.rs @@ -125,7 +125,7 @@ impl WhiteboxTool for HighestPosition { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/hole_proportion.rs b/src/tools/gis_analysis/hole_proportion.rs index 8abfb840a..92cf15fc2 100644 --- a/src/tools/gis_analysis/hole_proportion.rs +++ b/src/tools/gis_analysis/hole_proportion.rs @@ -122,7 +122,7 @@ impl WhiteboxTool for HoleProportion { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/idw_interpolation.rs b/src/tools/gis_analysis/idw_interpolation.rs index b5ddade03..161247fe7 100644 --- a/src/tools/gis_analysis/idw_interpolation.rs +++ b/src/tools/gis_analysis/idw_interpolation.rs @@ -2,7 +2,7 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay Created: 10/05/2018 -Last Modified: 18/10/2019 +Last Modified: 9/12/2019 License: MIT Most IDW tool have the option to work either based on a fixed number of neighbouring @@ -205,7 +205,7 @@ impl WhiteboxTool for IdwInterpolation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -240,7 +240,7 @@ impl WhiteboxTool for IdwInterpolation { } else { args[i + 1].to_string() }; - } else if flag_val == "-cell_size" { + } else if flag_val == "-resolution" || flag_val == "-cell_size" { grid_res = if keyval { vec[1].to_string().parse::().unwrap() } else { @@ -518,9 +518,16 @@ impl WhiteboxTool for IdwInterpolation { if !base_file.contains(&sep) && !base_file.contains("/") { base_file = format!("{}{}", working_directory, base_file); } - let base = Raster::new(&base_file, "r")?; + let mut base = Raster::new(&base_file, "r")?; + base.configs.nodata = nodata; Raster::initialize_using_file(&output_file, &base) } else { + if grid_res == 0f64 { + return Err(Error::new( + ErrorKind::InvalidInput, + "The specified grid resolution is incorrect. Either a non-zero grid resolution \nor an input existing base file name must be used.", + )); + } // base the output raster on the grid_res and the // extent of the input vector. let west: f64 = vector_data.header.x_min; @@ -553,6 +560,8 @@ impl WhiteboxTool for IdwInterpolation { let west = output.configs.west; let north = output.configs.north; output.configs.nodata = nodata; // in case a base image is used with a different nodata value. + let res_x = output.configs.resolution_x; + let res_y = output.configs.resolution_y; // let kdtree = Arc::new(kdtree); // wrap FRS in an Arc let frs = Arc::new(frs); @@ -572,8 +581,8 @@ impl WhiteboxTool for IdwInterpolation { for row in (0..rows).filter(|r| r % num_procs == tid) { let mut data = vec![nodata; columns as usize]; for col in 0..columns { - x = west + (col as f64 + 0.5) * grid_res; - y = north - (row as f64 + 0.5) * grid_res; + x = west + (col as f64 + 0.5) * res_x; + y = north - (row as f64 + 0.5) * res_y; let mut ret = frs.search(x, y); if ret.len() < min_points { ret = frs.knn_search(x, y, min_points); diff --git a/src/tools/gis_analysis/intersect.rs b/src/tools/gis_analysis/intersect.rs index 7a63e3b48..ced7130ce 100644 --- a/src/tools/gis_analysis/intersect.rs +++ b/src/tools/gis_analysis/intersect.rs @@ -183,7 +183,7 @@ impl WhiteboxTool for Intersect { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -308,7 +308,7 @@ impl WhiteboxTool for Intersect { // place the points from both files into a KD-tree let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; for record_num in 0..input.num_records { let record = input.get_record(record_num); @@ -374,7 +374,7 @@ impl WhiteboxTool for Intersect { // place the points from both files into a KD-tree let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; let mut total_points = 0; for record_num in 0..input.num_records { @@ -534,7 +534,7 @@ impl WhiteboxTool for Intersect { // Break the polylines up into shorter lines at junction points. let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; for i in 0..polylines.len() { for j in 0..polylines[i].len() { @@ -774,7 +774,7 @@ impl WhiteboxTool for Intersect { let mut p: Point2D; let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); for i in 0..polygons.len() { for j in 0..polygons[i].len() { p = polygons[i][j]; @@ -979,8 +979,7 @@ impl WhiteboxTool for Intersect { // now add the endpoints of each polyline into a kd tree let dimensions = 2; let capacity_per_node = 64; - let mut kdtree = - KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut kdtree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p1: Point2D; let mut p2: Point2D; let mut p3: Point2D; diff --git a/src/tools/gis_analysis/layer_footprint.rs b/src/tools/gis_analysis/layer_footprint.rs index a46903648..f325e0136 100644 --- a/src/tools/gis_analysis/layer_footprint.rs +++ b/src/tools/gis_analysis/layer_footprint.rs @@ -144,7 +144,7 @@ impl WhiteboxTool for LayerFootprint { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/line_intersections.rs b/src/tools/gis_analysis/line_intersections.rs index 53572f956..5797757a9 100644 --- a/src/tools/gis_analysis/line_intersections.rs +++ b/src/tools/gis_analysis/line_intersections.rs @@ -155,7 +155,7 @@ impl WhiteboxTool for LineIntersections { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/linearity_index.rs b/src/tools/gis_analysis/linearity_index.rs index 05c03e0e1..d3b3560e9 100644 --- a/src/tools/gis_analysis/linearity_index.rs +++ b/src/tools/gis_analysis/linearity_index.rs @@ -129,7 +129,7 @@ impl WhiteboxTool for LinearityIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/lowest_pos.rs b/src/tools/gis_analysis/lowest_pos.rs index 58b53c9db..e486c04ea 100644 --- a/src/tools/gis_analysis/lowest_pos.rs +++ b/src/tools/gis_analysis/lowest_pos.rs @@ -125,7 +125,7 @@ impl WhiteboxTool for LowestPosition { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/max_abs_overlay.rs b/src/tools/gis_analysis/max_abs_overlay.rs index 31edd0410..4a8c40fe6 100644 --- a/src/tools/gis_analysis/max_abs_overlay.rs +++ b/src/tools/gis_analysis/max_abs_overlay.rs @@ -120,7 +120,7 @@ impl WhiteboxTool for MaxAbsoluteOverlay { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/max_overlay.rs b/src/tools/gis_analysis/max_overlay.rs index e46848751..7cdcdef8b 100644 --- a/src/tools/gis_analysis/max_overlay.rs +++ b/src/tools/gis_analysis/max_overlay.rs @@ -121,7 +121,7 @@ impl WhiteboxTool for MaxOverlay { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/medoid.rs b/src/tools/gis_analysis/medoid.rs index 039c72fe0..2f9f4753c 100644 --- a/src/tools/gis_analysis/medoid.rs +++ b/src/tools/gis_analysis/medoid.rs @@ -144,7 +144,7 @@ impl WhiteboxTool for Medoid { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/merge_line_segments.rs b/src/tools/gis_analysis/merge_line_segments.rs index e5a30cb21..716fd8f39 100644 --- a/src/tools/gis_analysis/merge_line_segments.rs +++ b/src/tools/gis_analysis/merge_line_segments.rs @@ -149,7 +149,7 @@ impl WhiteboxTool for MergeLineSegments { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -269,7 +269,7 @@ impl WhiteboxTool for MergeLineSegments { // Break the polylines up into shorter lines at junction points. let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p1: Point2D; let mut p2: Point2D; for i in 0..polylines.len() { diff --git a/src/tools/gis_analysis/min_abs_overlay.rs b/src/tools/gis_analysis/min_abs_overlay.rs index 874bcc97c..7d34ba086 100644 --- a/src/tools/gis_analysis/min_abs_overlay.rs +++ b/src/tools/gis_analysis/min_abs_overlay.rs @@ -120,7 +120,7 @@ impl WhiteboxTool for MinAbsoluteOverlay { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/min_overlay.rs b/src/tools/gis_analysis/min_overlay.rs index 7030ab90a..f99dd7cef 100644 --- a/src/tools/gis_analysis/min_overlay.rs +++ b/src/tools/gis_analysis/min_overlay.rs @@ -121,7 +121,7 @@ impl WhiteboxTool for MinOverlay { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/minimum_bounding_box.rs b/src/tools/gis_analysis/minimum_bounding_box.rs index bd9ce3a80..9fa090fd2 100644 --- a/src/tools/gis_analysis/minimum_bounding_box.rs +++ b/src/tools/gis_analysis/minimum_bounding_box.rs @@ -159,7 +159,7 @@ impl WhiteboxTool for MinimumBoundingBox { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/minimum_bounding_circle.rs b/src/tools/gis_analysis/minimum_bounding_circle.rs index b41a8d511..d9afd5443 100644 --- a/src/tools/gis_analysis/minimum_bounding_circle.rs +++ b/src/tools/gis_analysis/minimum_bounding_circle.rs @@ -146,7 +146,7 @@ impl WhiteboxTool for MinimumBoundingCircle { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/minimum_bounding_envelope.rs b/src/tools/gis_analysis/minimum_bounding_envelope.rs index da679e165..84f9b737f 100644 --- a/src/tools/gis_analysis/minimum_bounding_envelope.rs +++ b/src/tools/gis_analysis/minimum_bounding_envelope.rs @@ -148,7 +148,7 @@ impl WhiteboxTool for MinimumBoundingEnvelope { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/minimum_convex_hull.rs b/src/tools/gis_analysis/minimum_convex_hull.rs index 03371949f..33b930a84 100644 --- a/src/tools/gis_analysis/minimum_convex_hull.rs +++ b/src/tools/gis_analysis/minimum_convex_hull.rs @@ -147,7 +147,7 @@ impl WhiteboxTool for MinimumConvexHull { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/mod.rs b/src/tools/gis_analysis/mod.rs index f556343ac..291f42780 100644 --- a/src/tools/gis_analysis/mod.rs +++ b/src/tools/gis_analysis/mod.rs @@ -52,6 +52,7 @@ mod minimum_bounding_circle; mod minimum_bounding_envelope; mod minimum_convex_hull; mod narrowness_index; +mod natural_neighbour_interpolation; mod nearest_neighbour_gridding; mod patch_orientation; mod percent_equal_to; @@ -140,6 +141,7 @@ pub use self::minimum_bounding_circle::MinimumBoundingCircle; pub use self::minimum_bounding_envelope::MinimumBoundingEnvelope; pub use self::minimum_convex_hull::MinimumConvexHull; pub use self::narrowness_index::NarrownessIndex; +pub use self::natural_neighbour_interpolation::NaturalNeighbourInterpolation; pub use self::nearest_neighbour_gridding::NearestNeighbourGridding; pub use self::patch_orientation::PatchOrientation; pub use self::percent_equal_to::PercentEqualTo; diff --git a/src/tools/gis_analysis/narrowness_index.rs b/src/tools/gis_analysis/narrowness_index.rs index c3a068902..1906180ab 100644 --- a/src/tools/gis_analysis/narrowness_index.rs +++ b/src/tools/gis_analysis/narrowness_index.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for NarrownessIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/natural_neighbour_interpolation.rs b/src/tools/gis_analysis/natural_neighbour_interpolation.rs new file mode 100644 index 000000000..41a6c9c47 --- /dev/null +++ b/src/tools/gis_analysis/natural_neighbour_interpolation.rs @@ -0,0 +1,727 @@ +/* +This tool is part of the WhiteboxTools geospatial analysis library. +Authors: Dr. John Lindsay +Created: 08/12/2019 +Last Modified: 08/12/2019 +License: MIT +*/ + +use crate::algorithms::{point_in_poly, polygon_area, triangulate, Triangulation}; +use crate::raster::*; +use crate::structures::{BoundingBox, Point2D}; +use crate::tools::*; +use crate::vector::{FieldData, ShapeTypeDimension, ShapeType, Shapefile}; +use kdtree::distance::squared_euclidean; +use kdtree::KdTree; +use num_cpus; +use std::collections::HashMap; +use std::env; +use std::f64; +use std::io::{Error, ErrorKind}; +use std::path; +use std::sync::mpsc; +use std::sync::Arc; +use std::thread; + +/// This tool can be used to interpolate a set of input vector points (`--input`) onto a raster grid using +/// Sibson's (1981) natural neighbour method. Similar to inverse-distance-weight interpolation (`IdwInterpolation`), +/// the natural neighbour method performs a weighted averaging of nearby point values to estimate the attribute +/// (`--field`) value at grid cell intersections in the output raster (`--output`). However, the two methods differ +/// quite significantly in the way that neighbours are identified and in the weighting scheme. First, natural neigbhour +/// identifies neighbours to be used in the interpolation of a point by finding the points connected to the +/// estimated value location in a [Delaunay triangulation](https://en.wikipedia.org/wiki/Delaunay_triangulation), that +/// is, the so-called *natural neighbours*. This approach has the main advantage of not having to specify an arbitrary +/// search distance or minimum number of nearest neighbours like many other interpolators do. Weights in the natural +/// neighbour scheme are determined using an area-stealing approach, whereby the weight assigned to a neighbour's value +/// is determined by the proportion of its [Voronoi polygon](https://en.wikipedia.org/wiki/Voronoi_diagram) that would +/// be lost by inserting the interpolation point into the Voronoi diagram. That is, inserting the interpolation point into +/// the Voronoi diagram results in the creation of a new polygon and shrinking the sizes of the Voronoi polygons associated +/// with each of the natural neighbours. The larger the area by which a neighbours polygon is reduced through the +/// insertion, relative to the polygon of the interpolation point, the greater the weight given to the neighbour point's +/// value in the interpolation. Interpolation weights sum to one because the sum of the reduced polygon areas must +/// account for the entire area of the interpolation points polygon. +/// +/// The user must specify the attribute field containing point values (`--field`). Alternatively, if the input Shapefile +/// contains z-values, the interpolation may be based on these values (`--use_z`). Either an output grid resolution +/// (`--cell_size`) must be specified or alternatively an existing base file (`--base`) can be used to determine the +/// output raster's (`--output`) resolution and spatial extent. Natural neighbour interpolation generally produces a +/// satisfactorily smooth surface within the region of data points but can produce spurious breaks in the surface +/// outside of this region. Thus, it is recommended that the output surface be clipped to the convex hull of the input +/// points (`--clip`). +/// +/// # Reference +/// Sibson, R. (1981). "A brief description of natural neighbor interpolation (Chapter 2)". In V. Barnett (ed.). +/// Interpolating Multivariate Data. Chichester: John Wiley. pp. 21–36. +/// +/// # See Also +/// `IdwInterpolation`, `NearestNeighbourGridding` +pub struct NaturalNeighbourInterpolation { + name: String, + description: String, + toolbox: String, + parameters: Vec, + example_usage: String, +} + +impl NaturalNeighbourInterpolation { + pub fn new() -> NaturalNeighbourInterpolation { + // public constructor + let name = "NaturalNeighbourInterpolation".to_string(); + let toolbox = "GIS Analysis".to_string(); + let description = "Creates a raster grid based on Sibson's natural neighbour method.".to_string(); + + let mut parameters = vec![]; + parameters.push(ToolParameter { + name: "Input Vector Points File".to_owned(), + flags: vec!["-i".to_owned(), "--input".to_owned()], + description: "Input vector points file.".to_owned(), + parameter_type: ParameterType::ExistingFile(ParameterFileType::Vector( + VectorGeometryType::Point, + )), + default_value: None, + optional: false, + }); + + parameters.push(ToolParameter { + name: "Field Name".to_owned(), + flags: vec!["--field".to_owned()], + description: "Input field name in attribute table.".to_owned(), + parameter_type: ParameterType::VectorAttributeField( + AttributeType::Number, + "--input".to_string(), + ), + default_value: None, + optional: true, + }); + + parameters.push(ToolParameter { + name: "Use Shapefile 'z' values?".to_owned(), + flags: vec!["--use_z".to_owned()], + description: + "Use the 'z' dimension of the Shapefile's geometry instead of an attribute field?" + .to_owned(), + parameter_type: ParameterType::Boolean, + default_value: Some("false".to_string()), + optional: true, + }); + + parameters.push(ToolParameter { + name: "Output Raster File".to_owned(), + flags: vec!["-o".to_owned(), "--output".to_owned()], + description: "Output raster file.".to_owned(), + parameter_type: ParameterType::NewFile(ParameterFileType::Raster), + default_value: None, + optional: false, + }); + + parameters.push(ToolParameter{ + name: "Cell Size (optional)".to_owned(), + flags: vec!["--cell_size".to_owned()], + description: "Optionally specified cell size of output raster. Not used when base raster is specified.".to_owned(), + parameter_type: ParameterType::Float, + default_value: None, + optional: true + }); + + parameters.push(ToolParameter{ + name: "Base Raster File (optional)".to_owned(), + flags: vec!["--base".to_owned()], + description: "Optionally specified input base raster file. Not used when a cell size is specified.".to_owned(), + parameter_type: ParameterType::ExistingFile(ParameterFileType::Raster), + default_value: None, + optional: true + }); + + parameters.push(ToolParameter { + name: "Clip to convex hull?".to_owned(), + flags: vec!["--clip".to_owned()], + description: "Clip the data to the convex hull of the points?".to_owned(), + parameter_type: ParameterType::Boolean, + default_value: Some("true".to_string()), + optional: true, + }); + + let sep: String = path::MAIN_SEPARATOR.to_string(); + let p = format!("{}", env::current_dir().unwrap().display()); + let e = format!("{}", env::current_exe().unwrap().display()); + let mut short_exe = e + .replace(&p, "") + .replace(".exe", "") + .replace(".", "") + .replace(&sep, ""); + if e.contains(".exe") { + short_exe += ".exe"; + } + let usage = format!( + ">>.*{0} -r={1} -v --wd=\"*path*to*data*\" -i=points.shp --field=HEIGHT -o=surface.tif --resolution=10.0 --clip", + short_exe, name + ).replace("*", &sep); + + NaturalNeighbourInterpolation { + name: name, + description: description, + toolbox: toolbox, + parameters: parameters, + example_usage: usage, + } + } +} + +impl WhiteboxTool for NaturalNeighbourInterpolation { + fn get_source_file(&self) -> String { + String::from(file!()) + } + + fn get_tool_name(&self) -> String { + self.name.clone() + } + + fn get_tool_description(&self) -> String { + self.description.clone() + } + + fn get_tool_parameters(&self) -> String { + let mut s = String::from("{\"parameters\": ["); + for i in 0..self.parameters.len() { + if i < self.parameters.len() - 1 { + s.push_str(&(self.parameters[i].to_string())); + s.push_str(","); + } else { + s.push_str(&(self.parameters[i].to_string())); + } + } + s.push_str("]}"); + s + } + + fn get_example_usage(&self) -> String { + self.example_usage.clone() + } + + fn get_toolbox(&self) -> String { + self.toolbox.clone() + } + + fn run<'a>( + &self, + args: Vec, + working_directory: &'a str, + verbose: bool, + ) -> Result<(), Error> { + let mut input_file: String = "".to_string(); + let mut field_name = String::new(); + let mut use_z = false; + let mut use_field = false; + let mut output_file: String = "".to_string(); + let mut grid_res: f64 = 0.0; + let mut base_file = String::new(); + let mut clip_to_hull = false; + + // read the arguments + if args.len() == 0 { + return Err(Error::new( + ErrorKind::InvalidInput, + "Tool run with no parameters.", + )); + } + for i in 0..args.len() { + let mut arg = args[i].replace("\"", ""); + arg = arg.replace("\'", ""); + let cmd = arg.split("="); // in case an equals sign was used + let vec = cmd.collect::>(); + let mut keyval = false; + if vec.len() > 1 { + keyval = true; + } + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-i" || flag_val == "-input" { + input_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-field" { + field_name = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + use_field = true; + } else if flag_val.contains("use_z") { + if vec.len() == 1 || !vec[1].to_string().to_lowercase().contains("false") { + use_z = true; + } + } else if flag_val == "-o" || flag_val == "-output" { + output_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-resolution" || flag_val == "-cell_size" { + grid_res = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; + } else if flag_val.contains("clip") { + if vec.len() == 1 || !vec[1].to_string().to_lowercase().contains("false") { + clip_to_hull = true; + } + } else if flag_val == "-base" { + base_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } + } + + let sep: String = path::MAIN_SEPARATOR.to_string(); + let mut progress: usize; + let mut old_progress: usize = 1; + + let start = Instant::now(); + + if verbose { + println!("***************{}", "*".repeat(self.get_tool_name().len())); + println!("* Welcome to {} *", self.get_tool_name()); + println!("***************{}", "*".repeat(self.get_tool_name().len())); + } + + if !input_file.contains(path::MAIN_SEPARATOR) && !input_file.contains("/") { + input_file = format!("{}{}", working_directory, input_file); + } + + if !output_file.contains(&sep) && !output_file.contains("/") { + output_file = format!("{}{}", working_directory, output_file); + } + + let input = Shapefile::read(&input_file)?; + + // make sure the input vector file is of points type + if input.header.shape_type.base_shape_type() != ShapeType::Point + && input.header.shape_type.base_shape_type() != ShapeType::MultiPoint + { + return Err(Error::new( + ErrorKind::InvalidInput, + "The input vector data must be of POINT base shape type.", + )); + } + + if !use_z && !use_field { + return Err(Error::new( + ErrorKind::InvalidInput, + "If vector data 'Z' data are unavailable (--use_z), an attribute field must be specified (--field=).", + )); + } + + if use_z && input.header.shape_type.dimension() != ShapeTypeDimension::Z { + return Err(Error::new( + ErrorKind::InvalidInput, + "The input vector data must be of 'POINTZ' or 'MULTIPOINTZ' ShapeType to use the --use_z flag.", + )); + } else if use_field { + // What is the index of the field to be analyzed? + let field_index = match input.attributes.get_field_num(&field_name) { + Some(i) => i, + None => { + return Err(Error::new( + ErrorKind::InvalidInput, + "The specified field name does not exist in input shapefile.", + )) + } + }; + + // Is the field numeric? + if !input.attributes.is_field_numeric(field_index) { + return Err(Error::new( + ErrorKind::InvalidInput, + "The specified attribute field is non-numeric.", + )); + } + } + + let nodata = -32768.0f64; + let mut output = if !base_file.trim().is_empty() || grid_res == 0f64 { + if !base_file.contains(&sep) && !base_file.contains("/") { + base_file = format!("{}{}", working_directory, base_file); + } + let mut base = Raster::new(&base_file, "r")?; + base.configs.nodata = nodata; + Raster::initialize_using_file(&output_file, &base) + } else { + if grid_res == 0f64 { + return Err(Error::new( + ErrorKind::InvalidInput, + "The specified grid resolution is incorrect. Either a non-zero grid resolution \nor an input existing base file name must be used.", + )); + } + // base the output raster on the grid_res and the + // extent of the input vector. + let west: f64 = input.header.x_min; + let north: f64 = input.header.y_max; + let rows: isize = (((north - input.header.y_min) / grid_res).ceil()) as isize; + let columns: isize = (((input.header.x_max - west) / grid_res).ceil()) as isize; + let south: f64 = north - rows as f64 * grid_res; + let east = west + columns as f64 * grid_res; + + let mut configs = RasterConfigs { + ..Default::default() + }; + configs.rows = rows as usize; + configs.columns = columns as usize; + configs.north = north; + configs.south = south; + configs.east = east; + configs.west = west; + configs.resolution_x = grid_res; + configs.resolution_y = grid_res; + configs.nodata = nodata; + configs.data_type = DataType::F32; + configs.photometric_interp = PhotometricInterpretation::Continuous; + + Raster::initialize_using_config(&output_file, &configs) + }; + + let rows = output.configs.rows as isize; + let columns = output.configs.columns as isize; + let west = output.configs.west; + let north = output.configs.north; + output.configs.nodata = nodata; // in case a base image is used with a different nodata value. + output.configs.palette = "spectrum.pal".to_string(); + output.configs.data_type = DataType::F32; + output.configs.photometric_interp = PhotometricInterpretation::Continuous; + + let mut points: Vec = Vec::with_capacity(input.get_total_num_points()); + let mut z_values: Vec = Vec::with_capacity(input.get_total_num_points()); + + const DIMENSIONS: usize = 2; + const CAPACITY_PER_NODE: usize = 64; + let mut tree = KdTree::with_capacity(DIMENSIONS, CAPACITY_PER_NODE); + let mut p = 0; + for record_num in 0..input.num_records { + let record = input.get_record(record_num); + for i in 0..record.num_points as usize { + points.push(Point2D::new(record.points[i].x, record.points[i].y)); + if use_z { + z_values.push(record.z_array[i]); + } else if use_field { + match input.attributes.get_value(record_num, &field_name) { + FieldData::Int(val) => { + z_values.push(val as f64); + } + FieldData::Real(val) => { + z_values.push(val); + } + _ => { + // likely a null field + z_values.push(0f64); + } + } + } + tree.add([points[p].x, points[p].y], p).unwrap(); + p += 1; + } + + if verbose { + progress = + (100.0_f64 * (record_num + 1) as f64 / input.num_records as f64) as usize; + if progress != old_progress { + println!("Reading points: {}%", progress); + old_progress = progress; + } + } + } + + if verbose { + println!("Performing triangulation..."); + } + // this is where the heavy-lifting is + let delaunay = triangulate(&points).expect("No triangulation exists."); + + // get the hull + let dont_clip_to_hull = !clip_to_hull; + let mut hull_vertices: Vec = vec![points[delaunay.hull[0]].clone()]; + for a in (0..delaunay.hull.len()).rev() { + hull_vertices.push(points[delaunay.hull[a]].clone()); + } + + if verbose { + println!("Creating point-halfedge mapping..."); + } + const EMPTY: usize = usize::max_value(); + let mut point_edge_map = HashMap::new(); // point id to half-edge id + for edge in 0..delaunay.triangles.len() { + let endpoint = delaunay.triangles[delaunay.next_halfedge(edge)]; + if !point_edge_map.contains_key(&endpoint) || delaunay.halfedges[edge] == EMPTY { + point_edge_map.insert(endpoint, edge); + } + if verbose { + progress = + (100.0_f64 * edge as f64 / (delaunay.triangles.len() - 1) as f64) as usize; + if progress != old_progress { + println!("Progress: {}%", progress); + old_progress = progress; + } + } + } + + let res_x = output.configs.resolution_x; + let res_y = output.configs.resolution_y; + + if verbose { + println!("Interpolating..."); + } + let points = Arc::new(points); + let z_values = Arc::new(z_values); + let delaunay = Arc::new(delaunay); + let tree = Arc::new(tree); + let hull_vertices = Arc::new(hull_vertices); + let point_edge_map = Arc::new(point_edge_map); + let num_procs = num_cpus::get() as isize; + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let points = points.clone(); + let z_values = z_values.clone(); + let delaunay = delaunay.clone(); + let tree = tree.clone(); + let hull_vertices = hull_vertices.clone(); + let point_edge_map = point_edge_map.clone(); + let tx = tx.clone(); + thread::spawn(move || { + + let (mut px, mut py): (f64, f64); + let mut previous_nn = EMPTY; + let mut delaunay2: Triangulation; + let mut natural_neighbours: Vec = vec![]; + let mut nn_points: Vec = vec![]; + let mut num_neighbours = 0; + let mut areas1: Vec = vec![]; + let mut areas2: Vec; + let mut edge: usize; + let mut edges: Vec; + let mut point_num: usize; + let mut vertices: Vec; + let mut triangles: Vec; + let mut ghost_box: BoundingBox; + let mut expansion: f64; + let mut gap: f64; + let mut num_edge_points: usize; + let mut endpoint: usize; + let mut sum_diff: f64; + let mut z: f64; + for row in (0..rows).filter(|r| r % num_procs == tid) { + let mut data = vec![nodata; columns as usize]; + for col in 0..columns { + px = west + (col as f64 + 0.5) * res_x; + py = north - (row as f64 + 0.5) * res_y; + if dont_clip_to_hull || point_in_poly(&Point2D::new(px, py), &hull_vertices) { + // find the nearest point + match tree.nearest(&[px, py], 1, &squared_euclidean) { + Ok(ret) => { + point_num = *ret[0].1; + + if point_num != previous_nn { + + // get the edge that is incoming to 'point_num' + edge = match point_edge_map.get(&point_num) { + Some(e) => *e, + None => EMPTY, + }; + if edge != EMPTY { + + // find all the neighbours of point_num and their neighbours too + natural_neighbours = delaunay.natural_neighbours_2nd_order(edge); + num_neighbours = natural_neighbours.len(); + + nn_points = natural_neighbours + .clone() + .into_iter() + .map(|p| points[p].clone()) + .collect(); + + ///////////////////////////////////////////// + // Create the Voronoi diagram of the points + ///////////////////////////////////////////// + + // Add a frame of hidden points surrounding the data, to serve as an artificial hull. + ghost_box = BoundingBox::from_points(&nn_points); + + // expand the box by a factor of the average point spacing. + expansion = ((ghost_box.max_x - ghost_box.min_x) + * (ghost_box.max_y - ghost_box.min_y) + / num_neighbours as f64) + .sqrt(); + ghost_box.expand_by(2.0 * expansion); + + gap = expansion / 2f64; // One-half the average point spacing + num_edge_points = ((ghost_box.max_x - ghost_box.min_x) / gap) as usize; + for x in 0..num_edge_points { + nn_points.push(Point2D::new( + ghost_box.min_x + x as f64 * gap, + ghost_box.min_y, + )); + nn_points.push(Point2D::new( + ghost_box.min_x + x as f64 * gap, + ghost_box.max_y, + )); + } + + num_edge_points = ((ghost_box.max_y - ghost_box.min_y) / gap) as usize; + for y in 0..num_edge_points { + nn_points.push(Point2D::new( + ghost_box.min_x, + ghost_box.min_y + y as f64 * gap, + )); + nn_points.push(Point2D::new( + ghost_box.max_x, + ghost_box.min_y + y as f64 * gap, + )); + } + + delaunay2 = triangulate(&nn_points).expect("No triangulation exists."); + + + // measure their areas + areas1 = vec![0f64; num_neighbours]; + let mut point_edge_map2 = HashMap::new(); // point id to half-edge id + for edge in 0..delaunay2.triangles.len() { + endpoint = delaunay2.triangles[delaunay2.next_halfedge(edge)]; + if !point_edge_map2.contains_key(&endpoint) || delaunay2.halfedges[edge] == EMPTY { + point_edge_map2.insert(endpoint, edge); + } + } + for a in 0..num_neighbours { + edge = match point_edge_map2.get(&a) { + Some(e) => *e, + None => EMPTY, + }; + if edge != EMPTY { + edges = delaunay2.edges_around_point(edge); + triangles = edges + .into_iter() + .map(|e| delaunay2.triangle_of_edge(e)) + .collect(); + + vertices = triangles + .into_iter() + .map(|t| delaunay2.triangle_center(&nn_points, t)) + .collect(); + + areas1[a] = polygon_area(&vertices); + } + } + + previous_nn = point_num; + } + } + + if areas1.len() > 0 { + // now add the grid cell centre point in and re-triangulate. + nn_points.pop(); + nn_points.push(Point2D::new(px, py)); + let delaunay3 = triangulate(&nn_points).expect("No triangulation exists."); + let mut point_edge_map2 = HashMap::new(); // point id to half-edge id + for edge in 0..delaunay3.triangles.len() { + endpoint = delaunay3.triangles[delaunay3.next_halfedge(edge)]; + if !point_edge_map2.contains_key(&endpoint) || delaunay3.halfedges[edge] == EMPTY { + point_edge_map2.insert(endpoint, edge); + } + } + areas2 = vec![0f64; num_neighbours]; + for a in 0..num_neighbours { + edge = match point_edge_map2.get(&a) { + Some(e) => *e, + None => EMPTY, + }; + if edge != EMPTY { + edges = delaunay3.edges_around_point(edge); + triangles = edges + .into_iter() + .map(|e| delaunay3.triangle_of_edge(e)) + .collect(); + + vertices= triangles + .into_iter() + .map(|t| delaunay3.triangle_center(&nn_points, t)) + .collect(); + + areas2[a] = polygon_area(&vertices); + } + } + + sum_diff = 0f64; + for a in 0..num_neighbours { + sum_diff += areas1[a] - areas2[a]; + } + if sum_diff > 0f64 { + z = 0f64; + for a in 0..num_neighbours { + z += (areas1[a] - areas2[a]) / sum_diff * z_values[natural_neighbours[a]]; + } + data[col as usize] = z; + } + } + + }, + Err(_) => { + // no point found; output nodata + } + }; + } + } + tx.send((row, data)).unwrap(); + } + }); + } + + for row in 0..rows { + let data = rx.recv().unwrap(); + output.set_row_data(data.0, data.1); + if verbose { + progress = (100.0_f64 * row as f64 / (rows - 1) as f64) as usize; + if progress != old_progress { + println!("Interpolating: {}%", progress); + old_progress = progress; + } + } + } + + let elapsed_time = get_formatted_elapsed_time(start); + + output.add_metadata_entry(format!( + "Created by whitebox_tools\' {} tool", + self.get_tool_name() + )); + output.add_metadata_entry(format!("Input file: {}", input_file)); + output.add_metadata_entry(format!("Field name: {}", field_name)); + output.add_metadata_entry(format!("Use z-field: {}", use_z)); + if grid_res > 0f64 { + output.add_metadata_entry(format!("Grid resolution: {}", grid_res)); + } else { + output.add_metadata_entry(format!("Base file: {}", base_file)); + } + output.add_metadata_entry(format!("Clip to hull: {}", clip_to_hull)); + output.add_metadata_entry(format!("Elapsed Time (including I/O): {}", elapsed_time)); + + if verbose { + println!("Saving data...") + }; + let _ = match output.write() { + Ok(_) => { + if verbose { + println!("Output file written") + } + } + Err(e) => return Err(e), + }; + + let elapsed_time = get_formatted_elapsed_time(start); + + if verbose { + println!("{}", &format!("Elapsed Time: {}", elapsed_time)); + } + + Ok(()) + } +} diff --git a/src/tools/gis_analysis/nearest_neighbour_gridding.rs b/src/tools/gis_analysis/nearest_neighbour_gridding.rs index bbf419dde..41883bb98 100644 --- a/src/tools/gis_analysis/nearest_neighbour_gridding.rs +++ b/src/tools/gis_analysis/nearest_neighbour_gridding.rs @@ -2,7 +2,7 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay Created: 09/10/2018 -Last Modified: 18/10/2019 +Last Modified: 09/12/2019 License: MIT */ @@ -173,7 +173,7 @@ impl WhiteboxTool for NearestNeighbourGridding { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -373,7 +373,8 @@ impl WhiteboxTool for NearestNeighbourGridding { if !base_file.contains(&sep) && !base_file.contains("/") { base_file = format!("{}{}", working_directory, base_file); } - let base = Raster::new(&base_file, "r")?; + let mut base = Raster::new(&base_file, "r")?; + base.configs.nodata = nodata; Raster::initialize_using_file(&output_file, &base) } else { // base the output raster on the grid_res and the @@ -408,6 +409,8 @@ impl WhiteboxTool for NearestNeighbourGridding { let west = output.configs.west; let north = output.configs.north; output.configs.nodata = nodata; // in case a base image is used with a different nodata value. + let res_x = output.configs.resolution_x; + let res_y = output.configs.resolution_y; let frs = Arc::new(frs); let num_procs = num_cpus::get() as isize; @@ -420,8 +423,8 @@ impl WhiteboxTool for NearestNeighbourGridding { for row in (0..rows).filter(|r| r % num_procs == tid) { let mut data = vec![nodata; columns as usize]; for col in 0..columns { - x = west + (col as f64 + 0.5) * grid_res; - y = north - (row as f64 + 0.5) * grid_res; + x = west + (col as f64 + 0.5) * res_x; + y = north - (row as f64 + 0.5) * res_y; let ret = frs.knn_search(x, y, 1); if ret.len() == 1 { if ret[0].1 <= max_dist { diff --git a/src/tools/gis_analysis/patch_orientation.rs b/src/tools/gis_analysis/patch_orientation.rs index 35c4f7bd8..44a84dadd 100644 --- a/src/tools/gis_analysis/patch_orientation.rs +++ b/src/tools/gis_analysis/patch_orientation.rs @@ -127,7 +127,7 @@ impl WhiteboxTool for PatchOrientation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/percent_equal_to.rs b/src/tools/gis_analysis/percent_equal_to.rs index 5b2a48389..04596972e 100644 --- a/src/tools/gis_analysis/percent_equal_to.rs +++ b/src/tools/gis_analysis/percent_equal_to.rs @@ -134,7 +134,7 @@ impl WhiteboxTool for PercentEqualTo { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/percent_greater_than.rs b/src/tools/gis_analysis/percent_greater_than.rs index cb5234bfc..e1441c26e 100644 --- a/src/tools/gis_analysis/percent_greater_than.rs +++ b/src/tools/gis_analysis/percent_greater_than.rs @@ -134,7 +134,7 @@ impl WhiteboxTool for PercentGreaterThan { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/percent_less_than.rs b/src/tools/gis_analysis/percent_less_than.rs index e932e8116..e93248135 100644 --- a/src/tools/gis_analysis/percent_less_than.rs +++ b/src/tools/gis_analysis/percent_less_than.rs @@ -134,7 +134,7 @@ impl WhiteboxTool for PercentLessThan { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/perimeter_area_ratio.rs b/src/tools/gis_analysis/perimeter_area_ratio.rs index 4482e648e..241f0990f 100644 --- a/src/tools/gis_analysis/perimeter_area_ratio.rs +++ b/src/tools/gis_analysis/perimeter_area_ratio.rs @@ -124,7 +124,7 @@ impl WhiteboxTool for PerimeterAreaRatio { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/pick_from_list.rs b/src/tools/gis_analysis/pick_from_list.rs index 5846df88d..e26cea43f 100644 --- a/src/tools/gis_analysis/pick_from_list.rs +++ b/src/tools/gis_analysis/pick_from_list.rs @@ -133,7 +133,7 @@ impl WhiteboxTool for PickFromList { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/polygon_area.rs b/src/tools/gis_analysis/polygon_area.rs index 29a44ea8e..19bf708bf 100644 --- a/src/tools/gis_analysis/polygon_area.rs +++ b/src/tools/gis_analysis/polygon_area.rs @@ -123,7 +123,7 @@ impl WhiteboxTool for PolygonArea { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/polygon_long_axis.rs b/src/tools/gis_analysis/polygon_long_axis.rs index 0b0ab7e50..fe628f357 100644 --- a/src/tools/gis_analysis/polygon_long_axis.rs +++ b/src/tools/gis_analysis/polygon_long_axis.rs @@ -135,7 +135,7 @@ impl WhiteboxTool for PolygonLongAxis { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/polygon_perimeter.rs b/src/tools/gis_analysis/polygon_perimeter.rs index 46942414d..e2aa2f739 100644 --- a/src/tools/gis_analysis/polygon_perimeter.rs +++ b/src/tools/gis_analysis/polygon_perimeter.rs @@ -118,7 +118,7 @@ impl WhiteboxTool for PolygonPerimeter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/polygon_short_axis.rs b/src/tools/gis_analysis/polygon_short_axis.rs index 69496dc69..fce8934d9 100644 --- a/src/tools/gis_analysis/polygon_short_axis.rs +++ b/src/tools/gis_analysis/polygon_short_axis.rs @@ -135,7 +135,7 @@ impl WhiteboxTool for PolygonShortAxis { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/polygonize.rs b/src/tools/gis_analysis/polygonize.rs index 91ca2f02d..bdb73d362 100644 --- a/src/tools/gis_analysis/polygonize.rs +++ b/src/tools/gis_analysis/polygonize.rs @@ -146,7 +146,7 @@ impl WhiteboxTool for Polygonize { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -331,7 +331,7 @@ impl WhiteboxTool for Polygonize { // now add the endpoints of each polyline into a kd tree let dimensions = 2; let capacity_per_node = 64; - let mut kdtree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut kdtree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p1: Point2D; let mut p2: Point2D; let mut p3: Point2D; diff --git a/src/tools/gis_analysis/radius_of_gyration.rs b/src/tools/gis_analysis/radius_of_gyration.rs index a28584db9..dcb9fee90 100644 --- a/src/tools/gis_analysis/radius_of_gyration.rs +++ b/src/tools/gis_analysis/radius_of_gyration.rs @@ -139,7 +139,7 @@ impl WhiteboxTool for RadiusOfGyration { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/raster_area.rs b/src/tools/gis_analysis/raster_area.rs index f146e9745..db5a10de1 100644 --- a/src/tools/gis_analysis/raster_area.rs +++ b/src/tools/gis_analysis/raster_area.rs @@ -2,7 +2,7 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay Created: 10/02/2019 -Last Modified: 18/10/2019 +Last Modified: 04/12/2019 License: MIT */ @@ -169,7 +169,7 @@ impl WhiteboxTool for RasterArea { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -354,10 +354,10 @@ impl WhiteboxTool for RasterArea { } } } - } else { + } else { // map units let is_geographic = input.is_in_geographic_coordinates(); if is_geographic && verbose { - println!("Warning: the input file does not appear to be in a projected coodinate system. Area values will only be estimates."); + println!("Warning: the input file does not appear to be in a projected coordinate system. Area values will only be estimates."); } let num_procs = num_cpus::get() as isize; @@ -368,6 +368,7 @@ impl WhiteboxTool for RasterArea { thread::spawn(move || { let mut resx = input.configs.resolution_x; let mut resy = input.configs.resolution_y; + let mut cell_area = resx * resy; let mut area_data = vec![0f64; num_bins]; let mut val: f64; let mut bin: usize; @@ -377,12 +378,13 @@ impl WhiteboxTool for RasterArea { mid_lat = input.get_y_from_row(row).to_radians(); resx = resx * 111_111.0 * mid_lat.cos(); resy = resy * 111_111.0; + cell_area = resx * resy; } for col in 0..columns { val = input.get_value(row, col); if val != nodata && val != back_val && val >= min_val && val <= max_val { bin = (val - min_val).floor() as usize; - area_data[bin] += resx * resy; + area_data[bin] += cell_area; } } } @@ -417,7 +419,7 @@ impl WhiteboxTool for RasterArea { output.reinitialize_values(out_nodata); output.configs.nodata = out_nodata; output.configs.photometric_interp = PhotometricInterpretation::Continuous; - output.configs.data_type = DataType::I32; + output.configs.data_type = DataType::F32; for row in 0..rows { for col in 0..columns { val = input.get_value(row, col); diff --git a/src/tools/gis_analysis/raster_cell_assignment.rs b/src/tools/gis_analysis/raster_cell_assignment.rs index 884ea69f2..24cecdb4a 100644 --- a/src/tools/gis_analysis/raster_cell_assignment.rs +++ b/src/tools/gis_analysis/raster_cell_assignment.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for RasterCellAssignment { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } diff --git a/src/tools/gis_analysis/raster_perimeter.rs b/src/tools/gis_analysis/raster_perimeter.rs new file mode 100644 index 000000000..e191ce7d3 --- /dev/null +++ b/src/tools/gis_analysis/raster_perimeter.rs @@ -0,0 +1,526 @@ +/* +This tool is part of the WhiteboxTools geospatial analysis library. +Authors: Dr. John Lindsay +Created: 10/02/2019 +Last Modified: 04/12/2019 +License: MIT +*/ + +use crate::raster::*; +use crate::tools::*; +use num_cpus; +use std::env; +use std::f64; +use std::io::{Error, ErrorKind}; +use std::path; +use std::sync::mpsc; +use std::sync::Arc; +use std::thread; + +/// This tools estimates the area of each category, polygon, or patch in an input raster. The input raster must be categorical +/// in data scale. Rasters with floating-point cell values are not good candidates for an area analysis. The user must specify +/// whether the output is given in `grid cells` or `map units` (`--units`). Map Units are physical units, e.g. if the rasters's +/// scale is in metres, areas will report in square-metres. Notice that square-metres can be converted into hectares by dividing +/// by 10,000 and into square-kilometres by dividing by 1,000,000. If the input raster is in geographic coordinates (i.e. +/// latitude and longitude) a warning will be issued and areas will be estimated based on per-row calculated degree lengths. +/// +/// The tool can be run with a raster output (`--output`), a text output (`--out_text`), or both. If niether outputs are specified, +/// the tool will automatically output a raster named `area.tif`. +/// +/// Zero values in the input raster may be excluded from the area analysis if the `--zero_back` flag is used. +/// +/// To calculate the area of vector polygons, use the `PolygonArea` tool instead. +/// +/// # See Also +/// `RasterArea` +pub struct RasterPerimeter { + name: String, + description: String, + toolbox: String, + parameters: Vec, + example_usage: String, +} + +impl RasterPerimeter { + pub fn new() -> RasterPerimeter { + // public constructor + let name = "RasterPerimeter".to_string(); + let toolbox = "GIS Analysis".to_string(); + let description = + "Calculates the perimeters of polygons or classes within a raster image." + .to_string(); + + let mut parameters = vec![]; + parameters.push(ToolParameter { + name: "Input File".to_owned(), + flags: vec!["-i".to_owned(), "--input".to_owned()], + description: "Input raster file.".to_owned(), + parameter_type: ParameterType::ExistingFile(ParameterFileType::Raster), + default_value: None, + optional: false, + }); + + parameters.push(ToolParameter { + name: "Output File".to_owned(), + flags: vec!["-o".to_owned(), "--output".to_owned()], + description: "Output raster file.".to_owned(), + parameter_type: ParameterType::NewFile(ParameterFileType::Raster), + default_value: None, + optional: true, + }); + + parameters.push(ToolParameter { + name: "Output text?".to_owned(), + flags: vec!["--out_text".to_owned()], + description: "Would you like to output polygon areas to text?" + .to_owned(), + parameter_type: ParameterType::Boolean, + default_value: None, + optional: false, + }); + + parameters.push(ToolParameter{ + name: "Units".to_owned(), + flags: vec!["--units".to_owned()], + description: "Area units; options include 'grid cells' and 'map units'.".to_owned(), + parameter_type: ParameterType::OptionList(vec!["grid cells".to_owned(), "map units".to_owned()]), + default_value: Some("grid cells".to_owned()), + optional: true + }); + + parameters.push(ToolParameter { + name: "Treat zero values as background?".to_owned(), + flags: vec!["--zero_back".to_owned()], + description: "Flag indicating whether zero values should be treated as a background." + .to_owned(), + parameter_type: ParameterType::Boolean, + default_value: None, + optional: false, + }); + + let sep: String = path::MAIN_SEPARATOR.to_string(); + let p = format!("{}", env::current_dir().unwrap().display()); + let e = format!("{}", env::current_exe().unwrap().display()); + let mut short_exe = e + .replace(&p, "") + .replace(".exe", "") + .replace(".", "") + .replace(&sep, ""); + if e.contains(".exe") { + short_exe += ".exe"; + } + let usage = format!( + ">>.*{} -r={} -v --wd=\"*path*to*data*\" -i=input.tif -o=output.tif --out_text --units='grid cells' --zero_back", + short_exe, name + ) + .replace("*", &sep); + + RasterPerimeter { + name: name, + description: description, + toolbox: toolbox, + parameters: parameters, + example_usage: usage, + } + } +} + +impl WhiteboxTool for RasterPerimeter { + fn get_source_file(&self) -> String { + String::from(file!()) + } + + fn get_tool_name(&self) -> String { + self.name.clone() + } + + fn get_tool_description(&self) -> String { + self.description.clone() + } + + fn get_tool_parameters(&self) -> String { + match serde_json::to_string(&self.parameters) { + Ok(json_str) => return format!("{{\"parameters\":{}}}", json_str), + Err(err) => return format!("{:?}", err), + } + } + + fn get_example_usage(&self) -> String { + self.example_usage.clone() + } + + fn get_toolbox(&self) -> String { + self.toolbox.clone() + } + + fn run<'a>( + &self, + args: Vec, + working_directory: &'a str, + verbose: bool, + ) -> Result<(), Error> { + let mut input_file = String::new(); + let mut output_file = String::new(); + let mut output_raster = false; + let mut zero_back = false; + let mut is_grid_cell_units = false; + let mut output_text = false; + + if args.len() == 0 { + return Err(Error::new( + ErrorKind::InvalidInput, + "Tool run with no parameters.", + )); + } + for i in 0..args.len() { + let mut arg = args[i].replace("\"", ""); + arg = arg.replace("\'", ""); + let cmd = arg.split("="); // in case an equals sign was used + let vec = cmd.collect::>(); + let mut keyval = false; + if vec.len() > 1 { + keyval = true; + } + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-i" || flag_val == "-input" { + input_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-o" || flag_val == "-output" { + output_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + output_raster = true; + } else if flag_val == "-units" { + is_grid_cell_units = if keyval { + vec[1].to_string().to_lowercase().contains("cells") + } else { + args[i + 1].to_string().to_lowercase().contains("cells") + }; + } else if flag_val == "-zero_back" { + if vec.len() == 1 || !vec[1].to_string().to_lowercase().contains("false") { + zero_back = true; + } + } else if flag_val == "-out_text" { + if vec.len() == 1 || !vec[1].to_string().to_lowercase().contains("false") { + output_text = true; + } + } + } + + if verbose { + println!("***************{}", "*".repeat(self.get_tool_name().len())); + println!("* Welcome to {} *", self.get_tool_name()); + println!("***************{}", "*".repeat(self.get_tool_name().len())); + } + + let sep: String = path::MAIN_SEPARATOR.to_string(); + + let mut progress: usize; + let mut old_progress: usize = 1; + + if !input_file.contains(&sep) && !input_file.contains("/") { + input_file = format!("{}{}", working_directory, input_file); + } + + if !output_raster && !output_text { + println!("Warning: Niether a raster nor text outputs were selected. An area raster will be generated."); + output_file = String::from("area.tif"); + output_raster = true; + } + + if output_raster { + if !output_file.contains(&sep) && !output_file.contains("/") { + output_file = format!("{}{}", working_directory, output_file); + } + } + + if verbose { + println!("Reading data...") + }; + + let input = Arc::new(Raster::new(&input_file, "r")?); + + let start = Instant::now(); + + let lut = [4.000000000f64, 2.828427125, 2.236067977, 2.414213562, 2.828427125, 3.000000000, + 2.414213562, 2.236067977, 2.236067977, 2.414213562, 2.000000000, + 2.000000000, 2.828427125, 1.414213562, 1.414213562, 1.414213562, + 2.236067977, 2.828427125, 2.000000000, 1.414213562, 2.414213562, + 1.414213562, 2.000000000, 1.414213562, 2.000000000, 2.000000000, + 1.000000000, 2.000000000, 2.000000000, 2.000000000, 2.000000000, + 1.000000000, 2.828427125, 3.000000000, 2.828427125, 1.414213562, + 2.000000000, 4.000000000, 2.236067977, 2.236067977, 2.414213562, + 2.236067977, 1.414213562, 1.414213562, 2.236067977, 2.236067977, + 1.414213562, 1.414213562, 2.828427125, 2.236067977, 1.414213562, + 1.414213562, 2.236067977, 2.414213562, 2.000000000, 1.414213562, 2.000000000, 2.000000000, 1.000000000, + 1.414213562, 2.000000000, 2.000000000, 1.000000000, 1.000000000, 2.236067977, 2.828427125, 2.000000000, + 2.000000000, 2.828427125, 2.236067977, 2.000000000, 2.000000000, 2.000000000, 1.414213562, 1.000000000, + 2.000000000, 1.414213562, 1.414213562, 1.000000000, 1.414213562, 2.000000000, 1.414213562, + 1.000000000, 1.000000000, 1.414213562, 1.414213562, 2.000000000, 1.414213562, 1.000000000, 1.000000000, + 0.000000000, 0.000000000, 1.000000000, 1.000000000, 0.000000000, 0.000000000, 2.414213562, 1.414213562, + 2.000000000, 2.000000000, 2.236067977, 2.414213562, 2.000000000, 2.000000000, 2.000000000, 1.414213562, + 2.000000000, 1.000000000, 2.000000000, 1.414213562, 1.000000000, 1.000000000, 1.414213562, 1.414213562, + 1.000000000, 1.000000000, 1.414213562, 1.414213562, 1.000000000, 1.000000000, 2.000000000, 1.414213562, + 0.000000000, 0.000000000, 1.000000000, 1.000000000, 0.000000000, 0.000000000, 2.828427125, 2.000000000, + 2.828427125, 2.236067977, 3.000000000, 4.000000000, 1.414213562, 2.236067977, + 2.828427125, 2.236067977, 1.414213562, 2.000000000, 2.236067977, 2.414213562, 1.414213562, 1.414213562, + 2.414213562, 2.236067977, 1.414213562, 1.414213562, 2.236067977, 2.236067977, 1.414213562, 1.414213562, + 2.000000000, 2.000000000, 1.000000000, 1.000000000, 2.000000000, 2.000000000, 1.414213562, 1.000000000, + 3.000000000, 4.000000000, 2.236067977, 2.414213562, 4.000000000, 4.000000000, 2.414213562, 2.236067977, + 1.414213562, 2.236067977, 1.414213562, 1.414213562, 2.414213562, 2.236067977, 1.414213562, 1.414213562, + 1.414213562, 2.414213562, 1.414213562, 1.414213562, 2.236067977, 2.236067977, + 1.414213562, 1.414213562, 2.000000000, 2.000000000, 1.000000000, 1.000000000, 2.000000000, 2.000000000, + 1.000000000, 1.000000000, 2.414213562, 2.000000000, 2.236067977, 2.000000000, 1.414213562, 2.414213562, + 2.000000000, 2.000000000, 1.414213562, 1.414213562, 1.000000000, 1.000000000, 1.414213562, 1.414213562, + 1.000000000, 1.000000000, 2.000000000, 2.000000000, 2.000000000, 1.000000000, 1.414213562, 1.414213562, + 1.000000000, 1.000000000, 2.000000000, 1.000000000, 0.000000000, 0.000000000, 1.414213562, 1.000000000, + 0.000000000, 0.000000000, 2.236067977, 2.236067977, 2.000000000, 2.000000000, 2.236067977, 2.236067977, + 2.000000000, 2.000000000, 1.414213562, 1.414213562, 1.414213562, 1.000000000, 1.414213562, 1.414213562, + 1.000000000, 1.000000000, 1.414213562, 1.414213562, 1.414213562, 1.000000000, 1.414213562, 1.414213562, + 1.000000000, 1.000000000, 1.000000000, 1.000000000, 0.000000000, 0.000000000, 1.000000000, 1.000000000, + 0.000000000, 0.000000000]; + + let dx = [1, 1, 1, 0, -1, -1, -1, 0]; + let dy = [-1, 0, 1, 1, 1, 0, -1, -1]; + let v = [1usize, 2, 4, 8, 16, 32, 64, 128]; + let (mut i, mut j): (isize, isize); + let nodata = input.configs.nodata; + let rows = input.configs.rows as isize; + let columns = input.configs.columns as isize; + let resx = input.configs.resolution_x; + let resy = input.configs.resolution_y; + let avg_res = (resx + resy) / 2f64; + let min_val = input.configs.display_min; + let max_val = input.configs.display_max; + let range = max_val - min_val + 0.00001f64; // otherwise the max value is outside the range + let num_bins = range.ceil() as usize; + let back_val = if zero_back { + 0f64 + } else { + nodata + }; + + if is_grid_cell_units { + let num_procs = num_cpus::get() as isize; + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let input = input.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let mut data = vec![0f64; num_bins]; + let mut val: f64; + let mut val2: usize; + let mut bin: usize; + for row in (0..rows).filter(|r| r % num_procs == tid) { + for col in 0..columns { + val = input.get_value(row, col); + if val != nodata && val != back_val && val >= min_val && val <= max_val { + bin = (val - min_val).floor() as usize; + for n in 0..8 { + i = col + dx[n]; + j = row + dy[n]; + if input.get_value(j, i) == val { + val2 += v[a]; + } + } + data[bin] += lut[val2]; + } + } + } + tx.send(data).unwrap(); + }); + } + + let mut data = vec![0usize; num_bins]; + for tid in 0..num_procs { + let data_rx = rx.recv().unwrap(); + for a in 0..num_bins { + data[a] += data_rx[a] * avg_res; + } + + if verbose { + progress = (100.0_f64 * (tid + 1) as f64 / num_procs as f64) as usize; + if progress != old_progress { + println!("Progress: {}%", progress); + old_progress = progress; + } + } + } + + let mut val: f64; + let mut bin: usize; + if output_raster { + let mut output = Raster::initialize_using_file(&output_file, &input); + let out_nodata = -999f64; + output.reinitialize_values(out_nodata); + output.configs.nodata = out_nodata; + output.configs.photometric_interp = PhotometricInterpretation::Continuous; + output.configs.data_type = DataType::I32; + for row in 0..rows { + for col in 0..columns { + val = input.get_value(row, col); + if val != nodata && val != back_val && val >= min_val && val <= max_val { + bin = (val - min_val).floor() as usize; + output.set_value(row, col, freq_data[bin] as f64); + } + } + if verbose { + progress = (100.0_f64 * row as f64 / (rows - 1) as f64) as usize; + if progress != old_progress { + println!("Outputting raster: {}%", progress); + old_progress = progress; + } + } + } + + let elapsed_time = get_formatted_elapsed_time(start); + output.add_metadata_entry(format!( + "Created by whitebox_tools\' {} tool", + self.get_tool_name() + )); + output.add_metadata_entry(format!("Input file: {}", input_file)); + output.add_metadata_entry(format!("Elapsed Time (excluding I/O): {}", elapsed_time)); + + if verbose { + println!("Saving data...") + }; + let _ = match output.write() { + Ok(_) => { + if verbose { + println!("Output file written") + } + } + Err(e) => return Err(e), + }; + } + if output_text { + println!("Class,Cells"); + for a in 0..num_bins { + if freq_data[a] > 0 { + val = (a as f64 + min_val).floor(); + println!("{},{}", val, freq_data[a]); + } + } + } + } else { // map units + let is_geographic = input.is_in_geographic_coordinates(); + if is_geographic && verbose { + println!("Warning: the input file does not appear to be in a projected coordinate system. Area values will only be estimates."); + } + + let num_procs = num_cpus::get() as isize; + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let input = input.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let mut resx = input.configs.resolution_x; + let mut resy = input.configs.resolution_y; + let mut cell_area = resx * resy; + let mut area_data = vec![0f64; num_bins]; + let mut val: f64; + let mut bin: usize; + let mut mid_lat: f64; + for row in (0..rows).filter(|r| r % num_procs == tid) { + if is_geographic { + mid_lat = input.get_y_from_row(row).to_radians(); + resx = resx * 111_111.0 * mid_lat.cos(); + resy = resy * 111_111.0; + cell_area = resx * resy; + } + for col in 0..columns { + val = input.get_value(row, col); + if val != nodata && val != back_val && val >= min_val && val <= max_val { + bin = (val - min_val).floor() as usize; + area_data[bin] += cell_area; + } + } + } + tx.send(area_data).unwrap(); + }); + } + + // we could just multiply the num cells by the cell area to get the area, + // but for the possibility of an input in geographic coordinates where the + // cell size is not constant for the data. + let mut area_data = vec![0f64; num_bins]; + for tid in 0..num_procs { + let data = rx.recv().unwrap(); + for a in 0..num_bins { + area_data[a] += data[a]; + } + + if verbose { + progress = (100.0_f64 * (tid + 1) as f64 / num_procs as f64) as usize; + if progress != old_progress { + println!("Progress: {}%", progress); + old_progress = progress; + } + } + } + + let mut val: f64; + let mut bin: usize; + if output_raster { + let mut output = Raster::initialize_using_file(&output_file, &input); + let out_nodata = -999f64; + output.reinitialize_values(out_nodata); + output.configs.nodata = out_nodata; + output.configs.photometric_interp = PhotometricInterpretation::Continuous; + output.configs.data_type = DataType::F32; + for row in 0..rows { + for col in 0..columns { + val = input.get_value(row, col); + if val != nodata && val != back_val && val >= min_val && val <= max_val { + bin = (val - min_val).floor() as usize; + output.set_value(row, col, area_data[bin]); + } + } + if verbose { + progress = (100.0_f64 * row as f64 / (rows - 1) as f64) as usize; + if progress != old_progress { + println!("Outputting raster: {}%", progress); + old_progress = progress; + } + } + } + + let elapsed_time = get_formatted_elapsed_time(start); + output.add_metadata_entry(format!( + "Created by whitebox_tools\' {} tool", + self.get_tool_name() + )); + output.add_metadata_entry(format!("Input file: {}", input_file)); + output.add_metadata_entry(format!("Elapsed Time (excluding I/O): {}", elapsed_time)); + + if verbose { + println!("Saving data...") + }; + let _ = match output.write() { + Ok(_) => { + if verbose { + println!("Output file written") + } + } + Err(e) => return Err(e), + }; + } + if output_text { + println!("Class,Area"); + for a in 0..num_bins { + if area_data[a] > 0f64 { + val = (a as f64 + min_val).floor(); + println!("{},{}", val, area_data[a]); + } + } + } + } + + Ok(()) + } +} diff --git a/src/tools/gis_analysis/reclass.rs b/src/tools/gis_analysis/reclass.rs index fd169e439..e60107b14 100644 --- a/src/tools/gis_analysis/reclass.rs +++ b/src/tools/gis_analysis/reclass.rs @@ -156,7 +156,7 @@ impl WhiteboxTool for Reclass { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/reclass_equal_interval.rs b/src/tools/gis_analysis/reclass_equal_interval.rs index 4e761e07a..a3f05fc97 100644 --- a/src/tools/gis_analysis/reclass_equal_interval.rs +++ b/src/tools/gis_analysis/reclass_equal_interval.rs @@ -153,7 +153,7 @@ impl WhiteboxTool for ReclassEqualInterval { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/reclass_from_file.rs b/src/tools/gis_analysis/reclass_from_file.rs index c1e3e329f..37b90d68c 100644 --- a/src/tools/gis_analysis/reclass_from_file.rs +++ b/src/tools/gis_analysis/reclass_from_file.rs @@ -147,7 +147,7 @@ impl WhiteboxTool for ReclassFromFile { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/related_circumscribing_circle.rs b/src/tools/gis_analysis/related_circumscribing_circle.rs index 7c6f00f42..ba7e3a340 100644 --- a/src/tools/gis_analysis/related_circumscribing_circle.rs +++ b/src/tools/gis_analysis/related_circumscribing_circle.rs @@ -136,7 +136,7 @@ impl WhiteboxTool for RelatedCircumscribingCircle { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/shape_complexity_index.rs b/src/tools/gis_analysis/shape_complexity_index.rs index 291968146..2c5bd2287 100644 --- a/src/tools/gis_analysis/shape_complexity_index.rs +++ b/src/tools/gis_analysis/shape_complexity_index.rs @@ -140,7 +140,7 @@ impl WhiteboxTool for ShapeComplexityIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/shape_complexity_raster.rs b/src/tools/gis_analysis/shape_complexity_raster.rs index 9cb8e56a8..d6eaa31e3 100644 --- a/src/tools/gis_analysis/shape_complexity_raster.rs +++ b/src/tools/gis_analysis/shape_complexity_raster.rs @@ -128,7 +128,7 @@ impl WhiteboxTool for ShapeComplexityIndexRaster { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/sibson_interpolation.rs b/src/tools/gis_analysis/sibson_interpolation.rs index d85641bb6..576d0e67f 100644 --- a/src/tools/gis_analysis/sibson_interpolation.rs +++ b/src/tools/gis_analysis/sibson_interpolation.rs @@ -186,7 +186,7 @@ impl WhiteboxTool for SibsonInterpolation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/smooth_vectors.rs b/src/tools/gis_analysis/smooth_vectors.rs index 5480e830d..bd271fedc 100644 --- a/src/tools/gis_analysis/smooth_vectors.rs +++ b/src/tools/gis_analysis/smooth_vectors.rs @@ -142,7 +142,7 @@ impl WhiteboxTool for SmoothVectors { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/snap_endnodes.rs b/src/tools/gis_analysis/snap_endnodes.rs index 346d40315..43d8e01f8 100644 --- a/src/tools/gis_analysis/snap_endnodes.rs +++ b/src/tools/gis_analysis/snap_endnodes.rs @@ -146,7 +146,7 @@ impl WhiteboxTool for SnapEndnodes { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/split_with_lines.rs b/src/tools/gis_analysis/split_with_lines.rs index 6c674227b..f7f5dde4d 100644 --- a/src/tools/gis_analysis/split_with_lines.rs +++ b/src/tools/gis_analysis/split_with_lines.rs @@ -160,7 +160,7 @@ impl WhiteboxTool for SplitWithLines { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -688,7 +688,7 @@ impl WhiteboxTool for SplitWithLines { // now add the endpoints of each polyline into a kd tree let dimensions = 2; let capacity_per_node = 64; - let mut kdtree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut kdtree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; for i in 0..polylines.len() { p = polylines[i].first_vertex(); diff --git a/src/tools/gis_analysis/sum_overlay.rs b/src/tools/gis_analysis/sum_overlay.rs index f77ad4fbc..6a9905cea 100644 --- a/src/tools/gis_analysis/sum_overlay.rs +++ b/src/tools/gis_analysis/sum_overlay.rs @@ -119,7 +119,7 @@ impl WhiteboxTool for SumOverlay { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/symmetrical_difference.rs b/src/tools/gis_analysis/symmetrical_difference.rs index 6ca77f60b..a7067e921 100644 --- a/src/tools/gis_analysis/symmetrical_difference.rs +++ b/src/tools/gis_analysis/symmetrical_difference.rs @@ -185,7 +185,7 @@ impl WhiteboxTool for SymmetricalDifference { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -310,7 +310,7 @@ impl WhiteboxTool for SymmetricalDifference { // place the points from both files into a KD-tree let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; for record_num in 0..input.num_records { let record = input.get_record(record_num); @@ -406,7 +406,7 @@ impl WhiteboxTool for SymmetricalDifference { // place the points from both files into a KD-tree let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; let mut total_points = 0; for record_num in 0..input.num_records { @@ -632,7 +632,7 @@ impl WhiteboxTool for SymmetricalDifference { // Break the polylines up into shorter lines at junction points. let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; for i in 0..polylines.len() { for j in 0..polylines[i].len() { @@ -870,7 +870,7 @@ impl WhiteboxTool for SymmetricalDifference { let mut p: Point2D; let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); for i in 0..polygons.len() { for j in 0..polygons[i].len() { p = polygons[i][j]; @@ -1086,7 +1086,7 @@ impl WhiteboxTool for SymmetricalDifference { // now add the endpoints of each polyline into a kd tree let dimensions = 2; let capacity_per_node = 64; - let mut kdtree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut kdtree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p1: Point2D; let mut p2: Point2D; let mut p3: Point2D; diff --git a/src/tools/gis_analysis/tin_gridding.rs b/src/tools/gis_analysis/tin_gridding.rs index a16ff115f..974712be7 100644 --- a/src/tools/gis_analysis/tin_gridding.rs +++ b/src/tools/gis_analysis/tin_gridding.rs @@ -93,6 +93,15 @@ impl TINGridding { optional: false, }); + parameters.push(ToolParameter { + name: "Maximum Triangle Edge Length (optional)".to_owned(), + flags: vec!["--max_triangle_edge_length".to_owned()], + description: "Optional maximum triangle edge length; triangles larger than this size will not be gridded.".to_owned(), + parameter_type: ParameterType::Float, + default_value: None, + optional: true, + }); + let sep: String = path::MAIN_SEPARATOR.to_string(); let p = format!("{}", env::current_dir().unwrap().display()); let e = format!("{}", env::current_exe().unwrap().display()); @@ -167,12 +176,13 @@ impl WhiteboxTool for TINGridding { let mut use_field = false; let mut output_file: String = "".to_string(); let mut grid_res: f64 = 1.0; + let mut max_triangle_edge_length = f64::INFINITY; // read the arguments if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -214,6 +224,14 @@ impl WhiteboxTool for TINGridding { } else { args[i + 1].to_string().parse::().unwrap() }; + } else if flag_val == "-max_triangle_edge_length" { + max_triangle_edge_length = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; + + max_triangle_edge_length *= max_triangle_edge_length; // actually squared distance } } @@ -370,42 +388,46 @@ impl WhiteboxTool for TINGridding { p1 = delaunay.triangles[i]; p2 = delaunay.triangles[i + 1]; p3 = delaunay.triangles[i + 2]; - tri_points[0] = points[p1].clone(); - tri_points[1] = points[p2].clone(); - tri_points[2] = points[p3].clone(); - tri_points[3] = points[p1].clone(); - // if is_clockwise_order(&tri_points) { - // tri_points.reverse(); - // } - - // get the equation of the plane - a = Vector3::new(tri_points[0].x, tri_points[0].y, z_values[p1]); - b = Vector3::new(tri_points[1].x, tri_points[1].y, z_values[p2]); - c = Vector3::new(tri_points[2].x, tri_points[2].y, z_values[p3]); - norm = (b - a).cross(&(c - a)); - - if norm.z != 0f64 { - k = -(tri_points[0].x * norm.x + tri_points[0].y * norm.y + norm.z * z_values[p1]); - - // find grid intersections with this triangle - bottom = points[p1].y.min(points[p2].y.min(points[p3].y)); - top = points[p1].y.max(points[p2].y.max(points[p3].y)); - left = points[p1].x.min(points[p2].x.min(points[p3].x)); - right = points[p1].x.max(points[p2].x.max(points[p3].x)); - - bottom_row = ((north - bottom) / grid_res).ceil() as isize; - top_row = ((north - top) / grid_res).floor() as isize; - left_col = ((left - west) / grid_res).floor() as isize; - right_col = ((right - west) / grid_res).ceil() as isize; - - for row in top_row..=bottom_row { - for col in left_col..=right_col { - x = west + (col as f64 + 0.5) * grid_res; - y = north - (row as f64 + 0.5) * grid_res; - if point_in_poly(&Point2D::new(x, y), &tri_points) { - // calculate the z values - z = -(norm.x * x + norm.y * y + k) / norm.z; - output.set_value(row, col, z); + if max_distance_squared(points[p1], points[p2], points[p3], z_values[p1], + z_values[p2], z_values[p3]) < max_triangle_edge_length { + + tri_points[0] = points[p1].clone(); + tri_points[1] = points[p2].clone(); + tri_points[2] = points[p3].clone(); + tri_points[3] = points[p1].clone(); + // if is_clockwise_order(&tri_points) { + // tri_points.reverse(); + // } + + // get the equation of the plane + a = Vector3::new(tri_points[0].x, tri_points[0].y, z_values[p1]); + b = Vector3::new(tri_points[1].x, tri_points[1].y, z_values[p2]); + c = Vector3::new(tri_points[2].x, tri_points[2].y, z_values[p3]); + norm = (b - a).cross(&(c - a)); + + if norm.z != 0f64 { + k = -(tri_points[0].x * norm.x + tri_points[0].y * norm.y + norm.z * z_values[p1]); + + // find grid intersections with this triangle + bottom = points[p1].y.min(points[p2].y.min(points[p3].y)); + top = points[p1].y.max(points[p2].y.max(points[p3].y)); + left = points[p1].x.min(points[p2].x.min(points[p3].x)); + right = points[p1].x.max(points[p2].x.max(points[p3].x)); + + bottom_row = ((north - bottom) / grid_res).ceil() as isize; + top_row = ((north - top) / grid_res).floor() as isize; + left_col = ((left - west) / grid_res).floor() as isize; + right_col = ((right - west) / grid_res).ceil() as isize; + + for row in top_row..=bottom_row { + for col in left_col..=right_col { + x = west + (col as f64 + 0.5) * grid_res; + y = north - (row as f64 + 0.5) * grid_res; + if point_in_poly(&Point2D::new(x, y), &tri_points) { + // calculate the z values + z = -(norm.x * x + norm.y * y + k) / norm.z; + output.set_value(row, col, z); + } } } } @@ -451,3 +473,31 @@ impl WhiteboxTool for TINGridding { Ok(()) } } + +/// Calculate squared Euclidean distance between the point and another. +pub fn max_distance_squared(p1: Point2D, p2: Point2D, p3: Point2D, z1: f64, z2: f64, z3: f64) -> f64 { + let mut dx = p1.x - p2.x; + let mut dy = p1.y - p2.y; + let mut dz = z1 - z2; + let mut max_dist = dx * dx + dy * dy + dz * dz; + + dx = p1.x - p3.x; + dy = p1.y - p3.y; + dz = z1 - z3; + let mut dist = dx * dx + dy * dy + dz * dz; + + if dist > max_dist { + max_dist = dist + } + + dx = p2.x - p3.x; + dy = p2.y - p3.y; + dz = z2 - z3; + dist = dx * dx + dy * dy + dz * dz; + + if dist > max_dist { + max_dist = dist + } + + max_dist +} \ No newline at end of file diff --git a/src/tools/gis_analysis/union.rs b/src/tools/gis_analysis/union.rs index c41c3ae9f..47a46dfc2 100644 --- a/src/tools/gis_analysis/union.rs +++ b/src/tools/gis_analysis/union.rs @@ -178,7 +178,7 @@ impl WhiteboxTool for Union { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -303,7 +303,7 @@ impl WhiteboxTool for Union { // place the points from both files into a KD-tree let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; for record_num in 0..input.num_records { let record = input.get_record(record_num); @@ -420,7 +420,7 @@ impl WhiteboxTool for Union { // place the points from both files into a KD-tree let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; let mut total_points = 0; for record_num in 0..input.num_records { @@ -600,7 +600,7 @@ impl WhiteboxTool for Union { // Break the polylines up into shorter lines at junction points. let dimensions = 2; let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p: Point2D; for i in 0..polylines.len() { for j in 0..polylines[i].len() { @@ -732,7 +732,7 @@ impl WhiteboxTool for Union { // } // } - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut tree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p1: Point2D; let mut p2: Point2D; for i in 0..features_polylines.len() { @@ -950,9 +950,9 @@ impl WhiteboxTool for Union { // Break the polygons up into line segments at junction points and endnodes. let mut p: Point2D; - let dimensions = 2; - let capacity_per_node = 64; - let mut tree = KdTree::new_with_capacity(dimensions, capacity_per_node); + const DIMENSIONS: usize = 2; + const CAPACITY_PER_NODE: usize = 64; + let mut tree = KdTree::with_capacity(DIMENSIONS, CAPACITY_PER_NODE); for i in 0..polygons.len() { for j in 0..polygons[i].len() { p = polygons[i][j]; @@ -1162,7 +1162,7 @@ impl WhiteboxTool for Union { // now add the endpoints of each polyline into a kd tree let dimensions = 2; let capacity_per_node = 64; - let mut kdtree = KdTree::new_with_capacity(dimensions, capacity_per_node); + let mut kdtree = KdTree::with_capacity(dimensions, capacity_per_node); let mut p1: Point2D; let mut p2: Point2D; let mut p3: Point2D; diff --git a/src/tools/gis_analysis/vector_hex_bin.rs b/src/tools/gis_analysis/vector_hex_bin.rs index 2ec74231c..9acbcd493 100644 --- a/src/tools/gis_analysis/vector_hex_bin.rs +++ b/src/tools/gis_analysis/vector_hex_bin.rs @@ -171,7 +171,7 @@ impl WhiteboxTool for VectorHexBinning { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/voronoi_diagram.rs b/src/tools/gis_analysis/voronoi_diagram.rs index a03fa235d..b5212cbdc 100644 --- a/src/tools/gis_analysis/voronoi_diagram.rs +++ b/src/tools/gis_analysis/voronoi_diagram.rs @@ -7,8 +7,7 @@ License: MIT */ use crate::algorithms::triangulate; -use crate::structures::BoundingBox; -use crate::structures::Point2D; +use crate::structures::{BoundingBox, Point2D}; use crate::tools::*; use crate::vector::*; use std::collections::HashMap; @@ -154,7 +153,7 @@ impl WhiteboxTool for VoronoiDiagram { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -220,9 +219,7 @@ impl WhiteboxTool for VoronoiDiagram { // set the projection information output.projection = input.projection.clone(); - output - .attributes - .add_fields(&input.attributes.get_fields().clone()); + output.attributes.add_fields(&input.attributes.get_fields().clone()); // Read the points in let mut points: Vec = vec![]; diff --git a/src/tools/gis_analysis/weighted_overlay.rs b/src/tools/gis_analysis/weighted_overlay.rs index 120cefbde..2c244ae90 100644 --- a/src/tools/gis_analysis/weighted_overlay.rs +++ b/src/tools/gis_analysis/weighted_overlay.rs @@ -174,7 +174,7 @@ impl WhiteboxTool for WeightedOverlay { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/gis_analysis/weighted_sum.rs b/src/tools/gis_analysis/weighted_sum.rs index 3acbdf4af..e85a6df75 100644 --- a/src/tools/gis_analysis/weighted_sum.rs +++ b/src/tools/gis_analysis/weighted_sum.rs @@ -133,7 +133,7 @@ impl WhiteboxTool for WeightedSum { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/average_flowpath_slope.rs b/src/tools/hydro_analysis/average_flowpath_slope.rs index c2e4686e7..0fdecabb1 100644 --- a/src/tools/hydro_analysis/average_flowpath_slope.rs +++ b/src/tools/hydro_analysis/average_flowpath_slope.rs @@ -130,7 +130,7 @@ impl WhiteboxTool for AverageFlowpathSlope { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/average_upslope_flowpath_length.rs b/src/tools/hydro_analysis/average_upslope_flowpath_length.rs index 79c4719f0..b7bff9bd7 100644 --- a/src/tools/hydro_analysis/average_upslope_flowpath_length.rs +++ b/src/tools/hydro_analysis/average_upslope_flowpath_length.rs @@ -130,7 +130,7 @@ impl WhiteboxTool for AverageUpslopeFlowpathLength { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/basins.rs b/src/tools/hydro_analysis/basins.rs index 79cb07851..e7a27c298 100644 --- a/src/tools/hydro_analysis/basins.rs +++ b/src/tools/hydro_analysis/basins.rs @@ -149,7 +149,7 @@ impl WhiteboxTool for Basins { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/breach_depressions.rs b/src/tools/hydro_analysis/breach_depressions.rs index f2b5e9bab..9aa85b521 100644 --- a/src/tools/hydro_analysis/breach_depressions.rs +++ b/src/tools/hydro_analysis/breach_depressions.rs @@ -2,7 +2,7 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay Created: 28/06/2017 -Last Modified: 18/10/2019 +Last Modified: 24/11/2019 License: MIT */ @@ -41,6 +41,13 @@ use std::path; /// the need to precisely represent small elevation differences along flats. Therefore, if the input DEM is stored /// at a lower level of precision (e.g. 32-bit floating point elevations), this may result in a doubling of /// the size of the DEM. +/// +/// In comparison with the `BreachDepressionsLeastCost` tool, this breaching method often provides a less +/// satisfactory, higher impact, breaching solution and is often less efficient. **It has been provided to users for +/// legacy reasons and it is advisable that users try the `BreachDepressionsLeastCost` tool to remove depressions from +/// their DEMs first**. The `BreachDepressionsLeastCost` tool is particularly +/// well suited to breaching through road embankments. Nonetheless, there are applications for which full depression filling +/// using the `FillDepressions` tool may be preferred. /// /// # Reference /// Lindsay JB. 2016. *Efficient hybrid breaching-filling sink removal methods for @@ -48,7 +55,7 @@ use std::path; /// 30(6): 846–857. DOI: 10.1002/hyp.10648 /// /// # See Also -/// `FillDepressions`, `FillSingleCellPits` +/// `BreachDepressionsLeastCost`, `FillDepressions`, `FillSingleCellPits` pub struct BreachDepressions { name: String, description: String, @@ -194,7 +201,7 @@ impl WhiteboxTool for BreachDepressions { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -279,14 +286,16 @@ impl WhiteboxTool for BreachDepressions { let columns = input.configs.columns as isize; let num_cells = rows * columns; let nodata = input.configs.nodata; + let resx = input.configs.resolution_x; + let resy = input.configs.resolution_y; + let diagres = (resx * resx + resy * resy).sqrt(); - let small_num = if !flat_increment.is_nan() { + let small_num = if !flat_increment.is_nan() || flat_increment == 0f64 { flat_increment } else { - let min_val = input.configs.minimum; - let elev_digits = ((input.configs.maximum - min_val) as i64).to_string().len(); + let elev_digits = (input.configs.maximum as i64).to_string().len(); let elev_multiplier = 10.0_f64.powi((6 - elev_digits) as i32); - 1.0_f64 / elev_multiplier as f64 + 1.0_f64 / elev_multiplier as f64 * diagres.ceil() }; let mut z: f64; @@ -315,7 +324,7 @@ impl WhiteboxTool for BreachDepressions { } } if flag { - input.set_value(row, col, min_zn + small_num); + input.set_value(row, col, min_zn - small_num); } } } diff --git a/src/tools/hydro_analysis/breach_depressions_least_cost.rs b/src/tools/hydro_analysis/breach_depressions_least_cost.rs new file mode 100644 index 000000000..056f41371 --- /dev/null +++ b/src/tools/hydro_analysis/breach_depressions_least_cost.rs @@ -0,0 +1,1181 @@ +/* +This tool is part of the WhiteboxTools geospatial analysis library. +Authors: Dr. John Lindsay +Created: 01/11/2019 +Last Modified: 24/11/2019 +License: MIT +*/ + +use crate::raster::*; +use crate::structures::Array2D; +use crate::tools::*; +use std::cmp::Ordering::Equal; +use std::cmp::Ordering; +use std::collections::{BinaryHeap, VecDeque}; +use std::env; +use std::f64; +use std::i32; +use std::io::{Error, ErrorKind}; +use std::path; +use std::sync::mpsc; +use std::sync::Arc; +use std::thread; + +/// This tool can be used to perform a type of optimal depression breaching to prepare a +/// digital elevation model (DEM) for hydrological analysis. Depression breaching is a common +/// alternative to depression filling (`FillDepression`) and often offers a lower-impact +/// solution to the removal of topographic depressions. This tool implements a method that is +/// loosely based on the algorithm described by Lindsay and Dhun (2015), furthering the earlier +/// algorithm with efficiency optimizations and other significant enhancements. The approach uses a least-cost +/// path analysis to identify the breach channel that connects pit cells (i.e. grid cells for +/// which there is no lower neighbour) to some distant lower cell. Here, the cost of a breach +/// path is determined by the amount of elevation lowering needed to cut the breach channel +/// through the surrounding topography. +/// +/// The user must specify the name of the input DEM file (`--dem`), the output breached DEM +/// file (`--output`), the maximum search window radius (`--radius`), the optional maximum breach +/// cost (`--max_cost`), and an optional flat height increment value (`--flat_increment`). Notice that if the +/// `--flat_increment` parameter is not specified, the small number used to ensure flow across flats will be +/// calculated automatically, which should be preferred in most applications of the tool. +/// The tool operates by performing a least-cost path analysis within a neighbourhood surround +/// each pit cell, where the neighbourhood size begins small (9 × 9; radius = 4) and iteratively increases (by doubling) +/// until it reaches the maximum breach length parameter. If a value is specified for the optional +/// `--max_cost` parameter, then least-cost breach paths that would require digging a channel that is more costly +/// than this value will be left to see if the pit cell can be resolved with a longer but shallower (less costly) breach +/// channel in an additional iteration (i.e. a larger search window). The flat increment value is used +/// to ensure that there is a monotonically descending path along breach channels to satisfy the necessary +/// condition of a downslope gradient for flowpath modelling. It is best for this value to be a small +/// value. If left unspecified, the tool with determine an appropriate value based on the range of +/// elevation values in the input DEM. Notice that the need to specify these very small elevation +/// increment values is one of the reasons why the output DEM will always be of a 64-bit floating-point +/// data type, which will often double the storage requirements of a DEM (DEMs are often store with 32-bit +/// precision). Lastly, the user may optionally choose to apply depression filling (`--fill`) on any depressions +/// that remain unresolved by the earlier depression breaching operation. This filling step uses an efficient +/// filling method based on flooding depressions from their pit cells until outlets are identified and then +/// raising the elevations of flooded cells back and away from the outlets. +/// +/// The tool can be run in two modes, based on whether the `--min_dist` is specified. If the `--min_dist` flag +/// is specified, the accumulated cost (accum2) of breaching from *cell1* to *cell2* along a channel +/// issuing from *pit* is calculated using the traditional cost-distance function: +/// +/// > cost1 = z1 - (zpit + *l* × *s*) +/// > +/// > cost2 = z2 - [zpit + (*l* + 1)*s*] +/// > +/// > accum2 = accum1 + *g*(cost1 + cost2) / 2.0 +/// +/// where cost1 and cost2 are the costs associated with moving through *cell1* and *cell2* +/// respectively, z1 and z2 are the elevations of the two cells, zpit is the elevation +/// of the pit cell, *l* is the length of the breach channel to *cell1*, *g* is the grid cell distance between +/// cells (accounting for diagonal distances), and *s* is the small number used to ensure flow +/// across flats. If the `--min_dist` flag is not present, the accumulated cost is calculated as: +/// +/// > accum2 = accum1 + cost2 +/// +/// That is, without the `--min_dist` flag, the tool works to minimize elevation changes to the DEM caused by +/// breaching, without considering the distance of breach channels. Notice that the value `--max_cost`, if +/// specified, should account for this difference in the way cost/cost-distances are calculated. The first cell +/// in the least-cost accumulation operation that is identified for which cost2 <= 0.0 is the target +/// cell to which the breach channel will connect the pit along the least-cost path. +/// +/// In comparison with the `BreachDepressions` tool, this breaching method often provides a more +/// satisfactory, lower impact, breaching solution and is often more efficient. It is therefore advisable that users +/// try the `BreachDepressionsLeastCost` tool to remove depressions from their DEMs first. This tool is particularly +/// well suited to breaching through road embankments. There are instances when a breaching solution is inappropriate, e.g. +/// when a very deep depression such as an open-pit mine occurs in the DEM and long, deep breach paths are created. Often +/// restricting breaching with the `--max_cost` parameter, combined with subsequent depression filling (`--fill`) can +/// provide an adequate solution in these cases. Nonetheless, there are applications for which full depression filling +/// using the `FillDepressions` tool may be preferred. +/// +/// # Reference +/// Lindsay J, Dhun K. 2015. Modelling surface drainage patterns in altered landscapes using LiDAR. +/// *International Journal of Geographical Information Science*, 29: 1-15. DOI: 10.1080/13658816.2014.975715 +/// +/// # See Also +/// `BreachDepressions`, `FillDepressions`, `CostPathway` +pub struct BreachDepressionsLeastCost { + name: String, + description: String, + toolbox: String, + parameters: Vec, + example_usage: String, +} + +impl BreachDepressionsLeastCost { + pub fn new() -> BreachDepressionsLeastCost { + // public constructor + let name = "BreachDepressionsLeastCost".to_string(); + let toolbox = "Hydrological Analysis".to_string(); + let description = "Breaches the depressions in a DEM using a least-cost pathway method.".to_string(); + + let mut parameters = vec![]; + parameters.push(ToolParameter { + name: "Input DEM File".to_owned(), + flags: vec!["-i".to_owned(), "--dem".to_owned()], + description: "Input raster DEM file.".to_owned(), + parameter_type: ParameterType::ExistingFile(ParameterFileType::Raster), + default_value: None, + optional: false, + }); + + parameters.push(ToolParameter { + name: "Output File".to_owned(), + flags: vec!["-o".to_owned(), "--output".to_owned()], + description: "Output raster file.".to_owned(), + parameter_type: ParameterType::NewFile(ParameterFileType::Raster), + default_value: None, + optional: false, + }); + + parameters.push(ToolParameter { + name: "Maximum Search Window Radius (cells)".to_owned(), + flags: vec!["--radius".to_owned()], + description: "" + .to_owned(), + parameter_type: ParameterType::Integer, + default_value: None, + optional: false, + }); + + parameters.push(ToolParameter { + name: "Maximum Breach Cost (z units)".to_owned(), + flags: vec!["--max_cost".to_owned()], + description: "Optional maximum breach cost (default is Inf).".to_owned(), + parameter_type: ParameterType::Float, + default_value: None, + optional: true, + }); + + parameters.push(ToolParameter { + name: "Minimize breach distances?".to_owned(), + flags: vec!["--min_dist".to_owned()], + description: "Optional flag indicating whether to minimize breach distances.".to_owned(), + parameter_type: ParameterType::Boolean, + default_value: Some("true".to_string()), + optional: true, + }); + + parameters.push(ToolParameter { + name: "Flat increment value (z units)".to_owned(), + flags: vec!["--flat_increment".to_owned()], + description: "Optional elevation increment applied to flat areas.".to_owned(), + parameter_type: ParameterType::Float, + default_value: None, + optional: true, + }); + + parameters.push(ToolParameter { + name: "Fill unbreached depressions?".to_owned(), + flags: vec!["--fill".to_owned()], + description: "Optional flag indicating whether to fill any remaining unbreached depressions.".to_owned(), + parameter_type: ParameterType::Boolean, + default_value: Some("true".to_string()), + optional: true, + }); + + let sep: String = path::MAIN_SEPARATOR.to_string(); + let p = format!("{}", env::current_dir().unwrap().display()); + let e = format!("{}", env::current_exe().unwrap().display()); + let mut short_exe = e + .replace(&p, "") + .replace(".exe", "") + .replace(".", "") + .replace(&sep, ""); + if e.contains(".exe") { + short_exe += ".exe"; + } + let usage = format!( + ">>.*{0} -r={1} -v --wd=\"*path*to*data*\" --dem=DEM.tif -o=output.tif", + short_exe, name + ) + .replace("*", &sep); + + BreachDepressionsLeastCost { + name: name, + description: description, + toolbox: toolbox, + parameters: parameters, + example_usage: usage, + } + } +} + +impl WhiteboxTool for BreachDepressionsLeastCost { + fn get_source_file(&self) -> String { + String::from(file!()) + } + + fn get_tool_name(&self) -> String { + self.name.clone() + } + + fn get_tool_description(&self) -> String { + self.description.clone() + } + + fn get_tool_parameters(&self) -> String { + match serde_json::to_string(&self.parameters) { + Ok(json_str) => return format!("{{\"parameters\":{}}}", json_str), + Err(err) => return format!("{:?}", err), + } + } + + fn get_example_usage(&self) -> String { + self.example_usage.clone() + } + + fn get_toolbox(&self) -> String { + self.toolbox.clone() + } + + fn run<'a>( + &self, + args: Vec, + working_directory: &'a str, + verbose: bool, + ) -> Result<(), Error> { + let mut input_file = String::new(); + let mut output_file = String::new(); + let mut max_cost = f64::INFINITY; + let mut end_radius = 20isize; + let mut flat_increment = f64::NAN; + let mut fill_deps = false; + let mut minimize_dist = false; + + if args.len() == 0 { + return Err(Error::new( + ErrorKind::InvalidInput, + "Tool run with no parameters.", + )); + } + for i in 0..args.len() { + let mut arg = args[i].replace("\"", ""); + arg = arg.replace("\'", ""); + let cmd = arg.split("="); // in case an equals sign was used + let vec = cmd.collect::>(); + let mut keyval = false; + if vec.len() > 1 { + keyval = true; + } + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-i" || flag_val == "-input" || flag_val == "-dem" { + input_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-o" || flag_val == "-output" { + output_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-radius" { + end_radius = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; + } else if flag_val == "-max_cost" { + max_cost = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; + } else if flag_val == "-flat_increment" { + flat_increment = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; + } else if flag_val == "-min_dist" { + if vec.len() == 1 || !vec[1].to_string().to_lowercase().contains("false") { + minimize_dist = true; + } + } else if flag_val == "-fill" { + if vec.len() == 1 || !vec[1].to_string().to_lowercase().contains("false") { + fill_deps = true; + } + } + } + + if verbose { + println!("***************{}", "*".repeat(self.get_tool_name().len())); + println!("* Welcome to {} *", self.get_tool_name()); + println!("***************{}", "*".repeat(self.get_tool_name().len())); + } + + let sep: String = path::MAIN_SEPARATOR.to_string(); + + let mut progress: usize; + let mut old_progress: usize = 1; + + if !input_file.contains(&sep) && !input_file.contains("/") { + input_file = format!("{}{}", working_directory, input_file); + } + if !output_file.contains(&sep) && !output_file.contains("/") { + output_file = format!("{}{}", working_directory, output_file); + } + + if verbose { + println!("Reading data...") + }; + + let input = Arc::new(Raster::new(&input_file, "r")?); + + let start = Instant::now(); + + let rows = input.configs.rows as isize; + let columns = input.configs.columns as isize; + let nodata = input.configs.nodata; + let (mut col, mut row): (isize, isize); + let (mut rn, mut cn): (isize, isize); + let mut accum: f64; + let (mut z, mut zn, mut zout): (f64, f64, f64); + let dx = [1, 1, 1, 0, -1, -1, -1, 0]; + let dy = [-1, 0, 1, 1, 1, 0, -1, -1]; + let mut flag: bool; + let mut num_solved: usize; + let mut overall_num_solved = 0; + let mut num_unsolved = 0; + let resx = input.configs.resolution_x; + let resy = input.configs.resolution_y; + let diagres = (resx * resx + resy * resy).sqrt(); + let cost_dist = [diagres, resx, diagres, resy, diagres, resx, diagres, resy]; + let mut cost1: f64; + let mut cost2: f64; + let mut new_cost: f64; + let mut length: i16; + let mut b: usize; + let num_procs = num_cpus::get() as isize; + + let small_num = if !flat_increment.is_nan() || flat_increment == 0f64 { + flat_increment + } else { + let elev_digits = (input.configs.maximum as i32).to_string().len(); + let elev_multiplier = 10.0_f64.powi((15 - elev_digits) as i32); + 1.0_f64 / elev_multiplier as f64 * diagres.ceil() + }; + + let mut output = Raster::initialize_using_file(&output_file, &input); + // Even if the input is f32, the output will need to be 64-bit to represent the small elevation differences + output.configs.data_type = DataType::F64; + let display_min = input.configs.display_min; + let display_max = input.configs.display_max; + + // Raise pit cells to minimize the depth of breach channels. + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let input = input.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let (mut z, mut zn, mut min_zn): (f64, f64, f64); + let mut flag: bool; + let dx = [1, 1, 1, 0, -1, -1, -1, 0]; + let dy = [-1, 0, 1, 1, 1, 0, -1, -1]; + for row in (0..rows).filter(|r| r % num_procs == tid) { + let mut data = input.get_row_data(row); + for col in 0..columns { + z = input.get_value(row, col); + if z != nodata { + flag = true; + min_zn = f64::INFINITY; + for n in 0..8 { + zn = input.get_value(row + dy[n], col + dx[n]); + if zn < min_zn { + min_zn = zn; + } + if zn == nodata { // It's an edge cell. + flag = false; + break; + } + if zn < z { // There's a lower neighbour + flag = false; + break; + } + } + if flag { + data[col as usize] = min_zn - small_num; + } + } + } + tx.send((row, data)).unwrap(); + } + }); + } + + for r in 0..rows { + let (row, data) = rx.recv().unwrap(); + output.set_row_data(row, data); + + if verbose { + progress = (100.0_f64 * r as f64 / (rows - 1) as f64) as usize; + if progress != old_progress { + println!("Filling pits: {}%", progress); + old_progress = progress; + } + } + } + + // drop(input); // input is no longer needed. + + + ////////////////////////////////////////////////////////////////////////////////////// + // We will process the pits iteratively with increasing neighbourhood size. This is // + // for greater efficiency. Most of the depressions are likely to be solved with // + // neighbourhoods much smaller than the maximum. // + ////////////////////////////////////////////////////////////////////////////////////// + let mut radius = 2; // the actual starting radius will be 4, which translates to a 9x9 filter for the first iteration. + if end_radius < radius { + end_radius = radius; + } + let mut iterations = vec![]; + flag = true; + while flag { + radius *= 2; + if radius >= end_radius { + radius = end_radius; + flag = false; + } + iterations.push(radius); + } + let mut i = 1; + let num_iterations = iterations.len(); + let mut undefined_flow_cells2 = vec![]; + for neighbourhood_radius in iterations { + if verbose { + println!("Iteration {} of {} (search radius={} cells)", i, num_iterations, neighbourhood_radius); + } + i += 1; + + /////////////////////////////////////////////////////////////////////////////////// + // Breach paths need to have a downward slope of at least min_val between cells. // + /////////////////////////////////////////////////////////////////////////////////// + let neighbourhood_size = 2 * neighbourhood_radius + 1; + let filter_size = (neighbourhood_size * neighbourhood_size) as usize; + + ////////////////////////////////////////////////// + // Find all cells with no downslope neighbours. // + ////////////////////////////////////////////////// + if neighbourhood_radius == 4 { + let output2 = Arc::new(output); + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let output2 = output2.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let mut z: f64; + let mut zn: f64; + let mut flag: bool; + for row in (1..rows-1).filter(|r| r % num_procs == tid) { + let mut pits = vec![]; + for col in 1..columns-1 { + z = output2.get_value(row, col); + if z != nodata { + flag = true; + for n in 0..8 { + zn = output2.get_value(row + dy[n], col + dx[n]); + if zn < z || zn == nodata { // It either has a lower neighbour or is an edge cell. + flag = false; + break; + } + } + if flag { // it's a cell with undefined flow + pits.push((row, col, z)); + } + } + } + tx.send(pits).unwrap(); + } + }); + } + + for r in 0..rows-2 { + let mut pits = rx.recv().unwrap(); + undefined_flow_cells2.append(&mut pits); + + if verbose { + progress = (100.0_f64 * r as f64 / (rows - 3) as f64) as usize; + if progress != old_progress { + println!("Finding pits: {}%", progress); + old_progress = progress; + } + } + } + + output = match Arc::try_unwrap(output2) { + Ok(val) => val, + Err(_) => panic!("Error unwrapping 'output'"), + }; + } + + //////////////////////////////////////////////////////////////////////////////////////////// + // We need to visit and (potentially) solve each undefined-flow cell in order from lowest // + // to highest. This is because some higher pits can be solved, or partially solved using // + // the breach paths of lower pits. // + //////////////////////////////////////////////////////////////////////////////////////////// + let mut undefined_flow_cells = undefined_flow_cells2.clone(); + undefined_flow_cells2.clear(); + /* Vec is a stack and so if we want to pop the values from lowest to highest, we need to sort + them from highest to lowest. */ + undefined_flow_cells.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(Equal)); + let num_deps = undefined_flow_cells.len(); + if num_deps == 0 { + if verbose { + println!("All depressions solved. Process ending..."); + break; + } + } + if verbose && neighbourhood_radius == 4 { + println!("Num. pits: {}", num_deps); + } + num_solved = 0; + num_unsolved = 0; + let backlink_dir = [4i8, 5, 6, 7, 0, 1, 2, 3]; + while let Some(cell) = undefined_flow_cells.pop() { + row = cell.0; + col = cell.1; + z = output.get_value(row, col); + + // Is it still a pit cell? It may have been solved during a previous depression solution. + flag = true; + for n in 0..8 { + zn = output.get_value(row + dy[n], col + dx[n]); + if zn < z && zn != nodata { // It has a lower non-nodata cell + // Resolving some other pit cell resulted in a solution for this one. + num_solved += 1; + overall_num_solved += 1; + flag = false; + break; + } + } + if flag { + // Perform the cost-accumulation operation. + let mut minheap = BinaryHeap::with_capacity(filter_size); + let mut backlink: Array2D = Array2D::new(neighbourhood_size, neighbourhood_size, -1, -2)?; + let mut encountered: Array2D = Array2D::new(neighbourhood_size, neighbourhood_size, 0, -1)?; + let mut path_length: Array2D = Array2D::new(neighbourhood_size, neighbourhood_size, 0, -1)?; + encountered.set_value(neighbourhood_radius, neighbourhood_radius, 1i8); + minheap.push(GridCell { + row: neighbourhood_radius, + column: neighbourhood_radius, + priority: 0f64, + }); + flag = true; + while !minheap.is_empty() && flag { + let cell = minheap.pop().unwrap(); + accum = cell.priority; + if accum > max_cost { // There isn't a breach channel cheap enough + undefined_flow_cells2.push((row, col, z)); // Add it to the list for the next iteration + num_unsolved += 1; + flag = false; + break; + } + length = path_length.get_value(cell.row, cell.column); + zn = output.get_value(row + (cell.row - neighbourhood_radius), col + (cell.column - neighbourhood_radius)); + cost1 = zn - z + length as f64 * small_num; + for n in 0..8 { + cn = cell.column + dx[n]; + rn = cell.row + dy[n]; + if encountered.get_value(rn, cn) == 0i8 { // not yet encountered + length += 1; + path_length.set_value(rn, cn, length); + backlink.set_value(rn, cn, backlink_dir[n]); + zn = output.get_value(row + (rn - neighbourhood_radius), col + (cn - neighbourhood_radius)); + zout = z - (length as f64 * small_num); + if zn > zout { + cost2 = zn - zout; + new_cost = if minimize_dist { + accum + (cost1 + cost2) / 2f64 * cost_dist[n] + } else { + accum + cost2 + }; + encountered.set_value(rn, cn, 1i8); + minheap.push(GridCell { + row: rn, + column: cn, + priority: new_cost, + }); + } else { + // We're at a cell that we can breach to + col += cn - neighbourhood_radius; + row += rn - neighbourhood_radius; + while flag { + // Find which cell to go to from here + if backlink.get_value(rn, cn) > -1i8 { + b = backlink.get_value(rn, cn) as usize; + cn += dx[b]; + rn += dy[b]; + col += dx[b]; + row += dy[b]; + zn = output.get_value(row, col); + length = path_length.get_value(rn, cn); + zout = z - (length as f64 * small_num); + if zn > zout { + output.set_value(row, col, zout); + } + } else { + flag = false; + } + } + num_solved += 1; + overall_num_solved += 1; + + break; // don't check any more neighbours. + } + } + } + } + + if flag { // Didn't find any lower cells. + undefined_flow_cells2.push((row, col, z)); // Add it to the list for the next iteration + num_unsolved += 1; + } + } + + if verbose { + progress = (100.0_f64 * (1f64 - (undefined_flow_cells.len()) as f64 / (num_deps - 1) as f64)) as usize; + if progress != old_progress { + println!("Breaching: {}%", progress); + old_progress = progress; + } + } + } + if verbose { + println!("Num. solved pits: {}", num_solved); + println!("Num. unsolved pits: {}", num_unsolved); + } + } + + if overall_num_solved > 0 && verbose { + println!("Overall num. solved pits: {}", overall_num_solved); + println!("Overall num. unsolved pits: {}", num_unsolved); + } else if verbose{ + println!("No depressions found."); + } + + if fill_deps && num_unsolved > 0 { + if verbose { + println!("Filling remaining depressions..."); + } + // Find pit cells. This step is parallelized. + let output2 = Arc::new(output); + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let output2 = output2.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let mut z: f64; + let mut zn: f64; + let mut flag: bool; + let mut pits = vec![]; + for row in (1..rows-1).filter(|r| r % num_procs == tid) { + for col in 1..columns-1 { + z = output2.get_value(row, col); + if z != nodata { + flag = true; + for n in 0..8 { + zn = output2.get_value(row + dy[n], col + dx[n]); + if zn < z || zn == nodata { // It either has a lower neighbour or is an edge cell. + flag = false; + break; + } + } + if flag { // it's a cell with undefined flow + pits.push((row, col, z)); + } + } + } + } + tx.send(pits).unwrap(); + }); + } + + let mut undefined_flow_cells = vec![]; + for p in 0..num_procs { + let mut pits = rx.recv().unwrap(); + undefined_flow_cells.append(&mut pits); + + if verbose { + progress = (100.0_f64 * (p + 1) as f64 / num_procs as f64) as usize; + if progress != old_progress { + println!("Finding pit cells: {}%", progress); + old_progress = progress; + } + } + } + + output = match Arc::try_unwrap(output2) { + Ok(val) => val, + Err(_) => panic!("Error unwrapping 'output'"), + }; + + let num_deps = undefined_flow_cells.len(); + + // Now we need to perform an in-place depression filling + let mut minheap = BinaryHeap::new(); + let mut visited: Array2D = Array2D::new(rows, columns, 0, -1)?; + let mut flats: Array2D = Array2D::new(rows, columns, 0, -1)?; + let mut possible_outlets = vec![]; + // solve from highest to lowest + undefined_flow_cells.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(Equal)); + let mut pit_id = 1; + let mut flag: bool; + while let Some(cell) = undefined_flow_cells.pop() { + row = cell.0; + col = cell.1; + if flats.get_value(row, col) != 1 { // if it's already in a solved site, don't do it a second time. + // First there is a priority region-growing operation to find the outlets. + z = output.get_value(row, col); + minheap.clear(); + minheap.push(GridCell { + row: row, + column: col, + priority: z, + }); + visited.set_value(row, col, 1); + let mut outlet_found = false; + let mut outlet_z = f64::INFINITY; + let mut queue = VecDeque::new(); + while let Some(cell2) = minheap.pop() { + z = cell2.priority; + if outlet_found && z > outlet_z { + break; + } + if !outlet_found { + for n in 0..8 { + cn = cell2.column + dx[n]; + rn = cell2.row + dy[n]; + if visited.get_value(rn, cn) == 0 { + zn = output.get_value(rn, cn); + if !outlet_found { + if zn >= z && zn != nodata { + minheap.push(GridCell { + row: rn, + column: cn, + priority: zn, + }); + visited.set_value(rn, cn, 1); + } else if zn != nodata { // zn < z + // 'cell' has a lower neighbour that hasn't already passed through minheap. + // Therefore, 'cell' is a pour point cell. + outlet_found = true; + outlet_z = z; + queue.push_back((cell2.row, cell2.column)); + possible_outlets.push((cell2.row, cell2.column)); + } + } else if zn == outlet_z { // We've found the outlet but are still looking for additional outlets. + minheap.push(GridCell { + row: rn, + column: cn, + priority: zn, + }); + visited.set_value(rn, cn, 1); + } + } + } + } else { + if z == outlet_z { + flag = false; + for n in 0..8 { + cn = cell2.column + dx[n]; + rn = cell2.row + dy[n]; + if visited.get_value(rn, cn) == 0 { + zn = output.get_value(rn, cn); + if zn < z { + flag = true; + } else if zn == outlet_z { + minheap.push(GridCell { + row: rn, + column: cn, + priority: zn, + }); + visited.set_value(rn, cn, 1); + } + } + } + if flag { // it's an outlet + queue.push_back((cell2.row, cell2.column)); + possible_outlets.push((cell2.row, cell2.column)); + } else { + visited.set_value(cell2.row, cell2.column, 1); + } + } + } + } + + // Now that we have the outlets, raise the interior of the depression + if outlet_found { + while let Some(cell2) = queue.pop_front() { + for n in 0..8 { + rn = cell2.0 + dy[n]; + cn = cell2.1 + dx[n]; + if visited.get_value(rn, cn) == 1 { + visited.set_value(rn, cn, 0); + queue.push_back((rn, cn)); + z = output.get_value(rn, cn); + if z < outlet_z { + output.set_value(rn, cn, outlet_z); + flats.set_value(rn, cn, 1); + } else if z == outlet_z { + flats.set_value(rn, cn, 1); + } + } + } + } + } + } + + if verbose { + progress = (100.0_f64 * pit_id as f64 / num_deps as f64) as usize; + if progress != old_progress { + println!("Filling depressions: {}%", progress); + old_progress = progress; + } + } + pit_id += 1; + } + + drop(visited); + + if small_num > 0f64 { // fix the flats + if verbose { + println!("Fixing flow on flats..."); + println!("Flats increment value: {}", small_num); + } + // Some of the potential outlets really will have lower cells. + // let mut queue = VecDeque::new(); + minheap.clear(); + while let Some(cell) = possible_outlets.pop() { + z = output.get_value(cell.0, cell.1); + flag = false; + for n in 0..8 { + rn = cell.0 + dy[n]; + cn = cell.1 + dx[n]; + zn = output.get_value(rn, cn); + if zn < z && zn != nodata { + flag = true; + break; + } + } + if flag { // it's confirmed as an outlet + minheap.push(GridCell { + row: cell.0, + column: cell.1, + priority: z, + }); + } + } + + let num_outlets = minheap.len(); + + while let Some(cell) = minheap.pop() { + if flats.get_value(cell.row, cell.column) != 3 { + z = output.get_value(cell.row, cell.column); + flats.set_value(cell.row, cell.column, 3); + let mut outlets = vec![]; + outlets.push(cell); + // Are there any other outlet cells at the same elevation (likely for the same feature) + flag = true; + while flag { + match minheap.peek() { + Some(cell2) => { + if cell2.priority == z { + flats.set_value(cell2.row, cell2.column, 3); + outlets.push(minheap.pop().unwrap()); + } else { + flag = false; + } + }, + None => { + flag = false; + } + } + + } + // let mut queue = VecDeque::new(); + let mut minheap2 = BinaryHeap::new(); + for cell2 in &outlets { + z = output.get_value(cell2.row, cell2.column); + for n in 0..8 { + rn = cell2.row + dy[n]; + cn = cell2.column + dx[n]; + if flats.get_value(rn, cn) != 3 { + zn = output.get_value(rn, cn); + if zn == z && zn != nodata { + // queue.push_back((rn, cn, z)); + minheap2.push(GridCell2 { + row: rn, + column: cn, + z: z, + priority: input.get_value(rn, cn), + }); + output.set_value(rn, cn, z + small_num); + flats.set_value(rn, cn, 3); + } + } + } + } + // Now fix the flats + while let Some(cell2) = minheap2.pop() { + z = output.get_value(cell2.row, cell2.column); + for n in 0..8 { + rn = cell2.row + dy[n]; + cn = cell2.column + dx[n]; + if flats.get_value(rn, cn) != 3 { + zn = output.get_value(rn, cn); + if zn < z + small_num && zn >= cell2.z && zn != nodata { + // queue.push_back((rn, cn, cell2.2)); + minheap2.push(GridCell2 { + row: rn, + column: cn, + z: cell2.z, + priority: input.get_value(rn, cn), + }); + output.set_value(rn, cn, z + small_num); + flats.set_value(rn, cn, 3); + } + } + } + } + } + + if verbose { + progress = (100.0_f64 * (1f64 - minheap.len() as f64 / num_outlets as f64)) as usize; + if progress != old_progress { + println!("Fixing flats: {}%", progress); + old_progress = progress; + } + } + } + } + } + + + // // Now we need to perform an in-place depression filling + // let mut minheap = BinaryHeap::new(); + // let mut visited: Array2D = Array2D::new(rows, columns, 0, -1)?; + // let mut outlet_visited: Array2D = Array2D::new(rows, columns, 0, -1)?; + // undefined_flow_cells2.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(Equal)); + // let num_deps = undefined_flow_cells2.len(); + // let mut pit_id = 1; + // let mut num_dep_cells: usize; + // while !undefined_flow_cells2.is_empty() { + // let cell = undefined_flow_cells2.pop().unwrap(); + // row = cell.0; + // col = cell.1; + // if outlet_visited.get_value(row, col) != 1 { // if it's already in a solved site, don't do it a second time. + // z = output.get_value(row, col); + // minheap.clear(); + // minheap.push(GridCell { + // row: row, + // column: col, + // priority: z, + // }); + // visited.set_value(row, col, 1); + // num_dep_cells = 1; + // flag = true; + // while !minheap.is_empty() && flag { + // let cell = minheap.pop().unwrap(); + // z = cell.priority; + // for n in 0..8 { + // cn = cell.column + dx[n]; + // rn = cell.row + dy[n]; + // if visited.get_value(rn, cn) == 0 { + // zn = output.get_value(rn, cn); + // if zn >= z { + // minheap.push(GridCell { + // row: rn, + // column: cn, + // priority: zn, + // }); + // visited.set_value(rn, cn, 1); + // num_dep_cells += 1; + // } else { + // // 'cell' has a lower neighbour that hasn't already passed through minheap. + // // 'cell' therefore is a pour point cell. + // visited.set_value(cell.row, cell.column, 0); + // outlet_visited.set_value(cell.row, cell.column, 1); + // let mut queue = VecDeque::with_capacity(num_dep_cells); + // queue.push_back((cell.row, cell.column)); + // while let Some(cell2) = queue.pop_front() { + // z = output.get_value(cell2.0, cell2.1); + // for n in 0..8 { + // rn = cell2.0 + dy[n]; + // cn = cell2.1 + dx[n]; + // if visited.get_value(rn, cn) == 1 { + // visited.set_value(rn, cn, 0); + // zn = output.get_value(rn, cn); + // if zn < z + small_num { + // queue.push_back((rn, cn)); + // output.set_value(rn, cn, z + small_num); + // outlet_visited.set_value(rn, cn, 1); + // } + // } + // } + // } + // flag = false; + // break; + // } + // } + // } + // } + // } + + // if verbose { + // progress = (100.0_f64 * pit_id as f64 / num_deps as f64) as usize; + // if progress != old_progress { + // println!("Filling remaining depressions: {}%", progress); + // old_progress = progress; + // } + // } + // pit_id += 1; + // } + + + + // // Start by finding all cells that neighbour NoData cells. + // let mut visited: Array2D = Array2D::new(rows, columns, 0, -1)?; + // let mut minheap = BinaryHeap::with_capacity((rows * columns) as usize); + // let mut num_cells_visited = 0; + // for row in 0..rows { + // for col in 0..columns { + // z = output.get_value(row, col); + // if z != nodata { + // for n in 0..8 { + // if output.get_value(row + dy[n], col + dx[n]) == nodata { + // minheap.push(GridCell { + // row: row, + // column: col, + // priority: z, + // }); + // visited.set_value(row, col, 1); + // num_cells_visited += 1; + // break; + // } + // } + // } else { + // visited.set_value(row, col, 1); + // num_cells_visited += 1; + // } + // } + // if verbose { + // progress = (100.0_f64 * row as f64 / (rows - 1) as f64) as usize; + // if progress != old_progress { + // println!("Finding edges: {}%", progress); + // old_progress = progress; + // } + // } + // } + + // while !minheap.is_empty() { + // let cell = minheap.pop().unwrap(); + // row = cell.row; + // col = cell.column; + // z = output.get_value(row, col); + // for n in 0..8 { + // rn = row + dy[n]; + // cn = col + dx[n]; + // if visited.get_value(rn, cn) == 0i8 { + // zn = output.get_value(rn, cn); + // if zn < z + small_num { + // zn = z + small_num; + // output.set_value(rn, cn, zn); + // } + // minheap.push(GridCell { + // row: rn, + // column: cn, + // priority: zn, + // }); + // visited.set_value(rn, cn, 1); + // num_cells_visited += 1; + // } + // } + // if verbose { + // progress = (100.0_f64 * num_cells_visited as f64 / (rows*columns - 1) as f64) as usize; + // if progress != old_progress { + // println!("Filling: {}%", progress); + // old_progress = progress; + // } + // } + // } + + + let elapsed_time = get_formatted_elapsed_time(start); + output.configs.display_min = display_min; + output.configs.display_max = display_max; + output.add_metadata_entry(format!( + "Created by whitebox_tools\' {} tool", + self.get_tool_name() + )); + output.add_metadata_entry(format!("Input file: {}", input_file)); + output.add_metadata_entry(format!("Maximum search window radius: {}", end_radius)); + output.add_metadata_entry(format!("Maximum breach cost: {}", max_cost)); + output.add_metadata_entry(format!("Flat elevation increment: {}", small_num)); + output.add_metadata_entry(format!("Remaining depressions filled: {}", fill_deps)); + output.add_metadata_entry(format!("Elapsed Time (excluding I/O): {}", elapsed_time)); + + if verbose { + println!("Saving data...") + }; + let _ = match output.write() { + Ok(_) => { + if verbose { + println!("Output file written") + } + } + Err(e) => return Err(e), + }; + if verbose { + println!( + "{}", + &format!("Elapsed Time (excluding I/O): {}", elapsed_time) + ); + } + + Ok(()) + } +} + +#[derive(PartialEq, Debug)] +struct GridCell { + row: isize, + column: isize, + priority: f64, +} + +impl Eq for GridCell {} + +impl PartialOrd for GridCell { + fn partial_cmp(&self, other: &Self) -> Option { + other.priority.partial_cmp(&self.priority) + } +} + +impl Ord for GridCell { + fn cmp(&self, other: &GridCell) -> Ordering { + self.partial_cmp(other).unwrap() + } +} + +#[derive(PartialEq, Debug)] +struct GridCell2 { + row: isize, + column: isize, + z: f64, + priority: f64, +} + +impl Eq for GridCell2 {} + +impl PartialOrd for GridCell2 { + fn partial_cmp(&self, other: &Self) -> Option { + other.priority.partial_cmp(&self.priority) + } +} + +impl Ord for GridCell2 { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() + } +} diff --git a/src/tools/hydro_analysis/breach_pits.rs b/src/tools/hydro_analysis/breach_pits.rs index 96fae2d39..ac7754340 100644 --- a/src/tools/hydro_analysis/breach_pits.rs +++ b/src/tools/hydro_analysis/breach_pits.rs @@ -124,7 +124,7 @@ impl WhiteboxTool for BreachSingleCellPits { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/burn_streams_at_roads.rs b/src/tools/hydro_analysis/burn_streams_at_roads.rs index a049b7ffc..ce30e9447 100644 --- a/src/tools/hydro_analysis/burn_streams_at_roads.rs +++ b/src/tools/hydro_analysis/burn_streams_at_roads.rs @@ -44,7 +44,7 @@ impl BurnStreamsAtRoads { // public constructor let name = "BurnStreamsAtRoads".to_string(); let toolbox = "Hydrological Analysis".to_string(); - let description = "Rasterizes vector streams based on Lindsay (2016) method.".to_string(); + let description = "Burns-in streams at the sites of road embankments.".to_string(); let mut parameters = vec![]; parameters.push(ToolParameter { @@ -169,7 +169,7 @@ impl WhiteboxTool for BurnStreamsAtRoads { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/d8_flow_accum.rs b/src/tools/hydro_analysis/d8_flow_accum.rs index 29ddb90a9..38cd52125 100644 --- a/src/tools/hydro_analysis/d8_flow_accum.rs +++ b/src/tools/hydro_analysis/d8_flow_accum.rs @@ -172,7 +172,7 @@ impl WhiteboxTool for D8FlowAccumulation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/d8_mass_flux.rs b/src/tools/hydro_analysis/d8_mass_flux.rs index a56d6b62d..a9abaf142 100644 --- a/src/tools/hydro_analysis/d8_mass_flux.rs +++ b/src/tools/hydro_analysis/d8_mass_flux.rs @@ -168,7 +168,7 @@ impl WhiteboxTool for D8MassFlux { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/d8_pointer.rs b/src/tools/hydro_analysis/d8_pointer.rs index 72d5086e4..d6ede4fe4 100644 --- a/src/tools/hydro_analysis/d8_pointer.rs +++ b/src/tools/hydro_analysis/d8_pointer.rs @@ -155,7 +155,7 @@ impl WhiteboxTool for D8Pointer { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/depth_in_sink.rs b/src/tools/hydro_analysis/depth_in_sink.rs index 0f1f9b050..44fc08b0a 100644 --- a/src/tools/hydro_analysis/depth_in_sink.rs +++ b/src/tools/hydro_analysis/depth_in_sink.rs @@ -2,20 +2,23 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay Created: 11/07/2017 -Last Modified: 18/10/2019 +Last Modified: 05/12/2019 License: MIT */ use crate::raster::*; use crate::tools::*; +use crate::structures::Array2D; use std::cmp::Ordering; -use std::collections::BinaryHeap; -use std::collections::VecDeque; +use std::cmp::Ordering::Equal; +use std::collections::{BinaryHeap, VecDeque}; use std::env; use std::f64; -use std::i32; use std::io::{Error, ErrorKind}; use std::path; +use std::sync::mpsc; +use std::sync::Arc; +use std::thread; /// This tool measures the depth that each grid cell in an input (`--dem`) raster digital elevation model (DEM) /// lies within a sink feature, i.e. a closed topographic depression. A sink, or depression, is a bowl-like @@ -142,7 +145,7 @@ impl WhiteboxTool for DepthInSink { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -154,31 +157,25 @@ impl WhiteboxTool for DepthInSink { if vec.len() > 1 { keyval = true; } - if vec[0].to_lowercase() == "-i" - || vec[0].to_lowercase() == "--input" - || vec[0].to_lowercase() == "--dem" - { - if keyval { - input_file = vec[1].to_string(); + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-i" || flag_val == "-input" || flag_val == "-dem" { + input_file = if keyval { + vec[1].to_string() } else { - input_file = args[i + 1].to_string(); - } - } else if vec[0].to_lowercase() == "-o" || vec[0].to_lowercase() == "--output" { - if keyval { - output_file = vec[1].to_string(); + args[i + 1].to_string() + }; + } else if flag_val == "-o" || flag_val == "-output" { + output_file = if keyval { + vec[1].to_string() } else { - output_file = args[i + 1].to_string(); - } - } else if vec[0].to_lowercase() == "-zero_background" - || vec[0].to_lowercase() == "--zero_background" - || vec[0].to_lowercase() == "--esri_style" - { + args[i + 1].to_string() + }; + } else if flag_val == "-zero_background" { if vec.len() == 1 || !vec[1].to_string().to_lowercase().contains("false") { zero_background = true; } } } - if verbose { println!("***************{}", "*".repeat(self.get_tool_name().len())); println!("* Welcome to {} *", self.get_tool_name()); @@ -206,158 +203,239 @@ impl WhiteboxTool for DepthInSink { let start = Instant::now(); let rows = input.configs.rows as isize; let columns = input.configs.columns as isize; - let num_cells = rows * columns; let nodata = input.configs.nodata; - let mut output = Raster::initialize_using_file(&output_file, &input); - let mut background_val = (i32::min_value() + 1) as f64; - output.reinitialize_values(background_val); - - /* - Find the data edges. This is complicated by the fact that DEMs frequently - have nodata edges, whereby the DEM does not occupy the full extent of - the raster. One approach to doing this would be simply to scan the - raster, looking for cells that neighbour nodata values. However, this - assumes that there are no interior nodata holes in the dataset. Instead, - the approach used here is to perform a region-growing operation, looking - for nodata values along the raster's edges. - */ - - let mut queue: VecDeque<(isize, isize)> = - VecDeque::with_capacity((rows * columns) as usize); - for row in 0..rows { - /* - Note that this is only possible because Whitebox rasters - allow you to address cells beyond the raster extent but - return the nodata value for these regions. - */ - queue.push_back((row, -1)); - queue.push_back((row, columns)); - } - - for col in 0..columns { - queue.push_back((-1, col)); - queue.push_back((rows, col)); - } + let mut filled_dem = input.get_data_as_array2d(); - /* - minheap is the priority queue. Note that I've tested using integer-based - priority values, by multiplying the elevations, but this didn't result - in a significant performance gain over the use of f64s. - */ - let mut minheap = BinaryHeap::with_capacity((rows * columns) as usize); - let mut num_solved_cells = 0; - let mut zin_n: f64; // value of neighbour of row, col in input raster - let mut zout: f64; // value of row, col in output raster - let mut zout_n: f64; // value of neighbour of row, col in output raster + let (mut col, mut row): (isize, isize); + let (mut rn, mut cn): (isize, isize); + let (mut z, mut zn): (f64, f64); let dx = [1, 1, 1, 0, -1, -1, -1, 0]; let dy = [-1, 0, 1, 1, 1, 0, -1, -1]; - let (mut row, mut col): (isize, isize); - let (mut row_n, mut col_n): (isize, isize); - while !queue.is_empty() { - let cell = queue.pop_front().unwrap(); - row = cell.0; - col = cell.1; - for n in 0..8 { - row_n = row + dy[n]; - col_n = col + dx[n]; - zin_n = input[(row_n, col_n)]; - zout_n = output[(row_n, col_n)]; - if zout_n == background_val { - if zin_n == nodata { - output[(row_n, col_n)] = nodata; - queue.push_back((row_n, col_n)); - } else { - output[(row_n, col_n)] = zin_n; - // Push it onto the priority queue for the priority flood operation - minheap.push(GridCell { - row: row_n, - column: col_n, - priority: zin_n, - }); + + // Find pit cells. This step is parallelized. + let num_procs = num_cpus::get() as isize; + let filled_dem2 = Arc::new(filled_dem); + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let filled_dem2 = filled_dem2.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let mut z: f64; + let mut zn: f64; + let mut flag: bool; + let mut pits = vec![]; + for row in (1..rows-1).filter(|r| r % num_procs == tid) { + for col in 1..columns-1 { + z = filled_dem2.get_value(row, col); + if z != nodata { + flag = true; + for n in 0..8 { + zn = filled_dem2.get_value(row + dy[n], col + dx[n]); + if zn < z || zn == nodata { // It either has a lower neighbour or is an edge cell. + flag = false; + break; + } + } + if flag { // it's a cell with undefined flow + pits.push((row, col, z)); + } + } } - num_solved_cells += 1; } - } + tx.send(pits).unwrap(); + }); + } + let mut undefined_flow_cells = vec![]; + for p in 0..num_procs { + let mut pits = rx.recv().unwrap(); + undefined_flow_cells.append(&mut pits); + if verbose { - progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; + progress = (100.0_f64 * (p + 1) as f64 / num_procs as f64) as usize; if progress != old_progress { - println!("progress: {}%", progress); + println!("Finding pit cells: {}%", progress); old_progress = progress; } } } - // Perform the priority flood operation. - while !minheap.is_empty() { - let cell = minheap.pop().unwrap(); - row = cell.row; - col = cell.column; - zout = output[(row, col)]; - for n in 0..8 { - row_n = row + dy[n]; - col_n = col + dx[n]; - zout_n = output[(row_n, col_n)]; - if zout_n == background_val { - zin_n = input[(row_n, col_n)]; - if zin_n != nodata { - if zin_n < zout { - zin_n = zout; - } // We're in a depression. Raise the elevation. - output[(row_n, col_n)] = zin_n; - minheap.push(GridCell { - row: row_n, - column: col_n, - priority: zin_n, - }); + let mut input_configs = input.configs.clone(); + + filled_dem = match Arc::try_unwrap(filled_dem2) { + Ok(val) => val, + Err(_) => panic!("Error unwrapping 'filled_dem'"), + }; + + let num_deps = undefined_flow_cells.len(); + + // Now we need to perform an in-place depression filling + let mut minheap = BinaryHeap::new(); + let mut visited: Array2D = Array2D::new(rows, columns, 0, -1)?; + let mut flats: Array2D = Array2D::new(rows, columns, 0, -1)?; + let mut possible_outlets = vec![]; + // solve from highest to lowest + undefined_flow_cells.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(Equal)); + let mut pit_id = 1; + let mut flag: bool; + while let Some(cell) = undefined_flow_cells.pop() { + row = cell.0; + col = cell.1; + if flats.get_value(row, col) != 1 { // if it's already in a solved site, don't do it a second time. + // First there is a priority region-growing operation to find the outlets. + z = filled_dem.get_value(row, col); + minheap.clear(); + minheap.push(GridCell { + row: row, + column: col, + priority: z, + }); + visited.set_value(row, col, 1); + let mut outlet_found = false; + let mut outlet_z = f64::INFINITY; + let mut queue = VecDeque::new(); + while let Some(cell2) = minheap.pop() { + z = cell2.priority; + if outlet_found && z > outlet_z { + break; + } + if !outlet_found { + for n in 0..8 { + cn = cell2.column + dx[n]; + rn = cell2.row + dy[n]; + if visited.get_value(rn, cn) == 0 { + zn = filled_dem.get_value(rn, cn); + if !outlet_found { + if zn >= z && zn != nodata { + minheap.push(GridCell { + row: rn, + column: cn, + priority: zn, + }); + visited.set_value(rn, cn, 1); + } else if zn != nodata { // zn < z + // 'cell' has a lower neighbour that hasn't already passed through minheap. + // Therefore, 'cell' is a pour point cell. + outlet_found = true; + outlet_z = z; + queue.push_back((cell2.row, cell2.column)); + possible_outlets.push((cell2.row, cell2.column)); + } + } else if zn == outlet_z { // We've found the outlet but are still looking for additional outlets. + minheap.push(GridCell { + row: rn, + column: cn, + priority: zn, + }); + visited.set_value(rn, cn, 1); + } + } + } } else { - // Interior nodata cells are still treated as nodata and are not filled. - output[(row_n, col_n)] = nodata; - num_solved_cells += 1; + if z == outlet_z { + flag = false; + for n in 0..8 { + cn = cell2.column + dx[n]; + rn = cell2.row + dy[n]; + if visited.get_value(rn, cn) == 0 { + zn = filled_dem.get_value(rn, cn); + if zn < z { + flag = true; + } else if zn == outlet_z { + minheap.push(GridCell { + row: rn, + column: cn, + priority: zn, + }); + visited.set_value(rn, cn, 1); + } + } + } + if flag { // it's an outlet + queue.push_back((cell2.row, cell2.column)); + possible_outlets.push((cell2.row, cell2.column)); + } else { + visited.set_value(cell2.row, cell2.column, 1); + } + } + } + } + + // Now that we have the outlets, raise the interior of the depression + if outlet_found { + while let Some(cell2) = queue.pop_front() { + for n in 0..8 { + rn = cell2.0 + dy[n]; + cn = cell2.1 + dx[n]; + if visited.get_value(rn, cn) == 1 { + visited.set_value(rn, cn, 0); + queue.push_back((rn, cn)); + z = filled_dem.get_value(rn, cn); + if z < outlet_z { + filled_dem.set_value(rn, cn, outlet_z); + flats.set_value(rn, cn, 1); + } else if z == outlet_z { + flats.set_value(rn, cn, 1); + } + } + } } } } if verbose { - num_solved_cells += 1; - progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; + progress = (100.0_f64 * pit_id as f64 / num_deps as f64) as usize; if progress != old_progress { - println!("Progress (Loop 1 of 2): {}%", progress); + println!("Finding depressions: {}%", progress); old_progress = progress; } } + pit_id += 1; } - background_val = nodata; + drop(visited); + + input_configs.nodata = -32768f64; + let mut output = Raster::initialize_using_config(&output_file, &input_configs); if zero_background { - background_val = 0f64; + output.reinitialize_values(0f64); } - for row in 0..rows { - for col in 0..columns { - if output[(row, col)] > input[(row, col)] { - output[(row, col)] = output[(row, col)] - input[(row, col)]; - } else { - if input[(row, col)] != nodata { - output[(row, col)] = background_val; - } else { - output[(row, col)] = nodata; + output.configs.data_type = DataType::F32; + let num_outlets = possible_outlets.len(); + let mut diff: f64; + while let Some(cell) = possible_outlets.pop() { + if flats.get_value(cell.0, cell.1) == 1 { + z = filled_dem.get_value(cell.0, cell.1); + output.set_value(cell.0, cell.1, 0f64); + let mut queue = VecDeque::new(); + flats.set_value(cell.0, cell.1, 0); + queue.push_back((cell.0, cell.1)); + while let Some(cell2) = queue.pop_front() { + for n in 0..8 { + rn = cell2.0 + dy[n]; + cn = cell2.1 + dx[n]; + if flats.get_value(rn, cn) == 1 { + if filled_dem.get_value(rn, cn) == z { + flats.set_value(rn, cn, 0); + diff = z - input.get_value(rn, cn); + output.set_value(rn, cn, diff); + queue.push_back((rn, cn)); + } + } } } } if verbose { - progress = (100.0_f64 * row as f64 / (rows - 1) as f64) as usize; + progress = (100.0_f64 * (1.0 - possible_outlets.len() as f64 / num_outlets as f64)) as usize; if progress != old_progress { - println!("Progress (Loop 2 of 2): {}%", progress); + println!("Estimating depths: {}%", progress); old_progress = progress; } } } let elapsed_time = get_formatted_elapsed_time(start); - output.configs.data_type = DataType::F32; - output.configs.palette = "qual.plt".to_string(); - output.configs.photometric_interp = PhotometricInterpretation::Categorical; output.add_metadata_entry(format!( "Created by whitebox_tools\' {} tool", self.get_tool_name() @@ -403,12 +481,423 @@ impl PartialOrd for GridCell { } impl Ord for GridCell { - fn cmp(&self, other: &GridCell) -> Ordering { - let ord = self.partial_cmp(other).unwrap(); - match ord { - Ordering::Greater => Ordering::Less, - Ordering::Less => Ordering::Greater, - Ordering::Equal => ord, - } + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() } } + + +// /* +// This tool is part of the WhiteboxTools geospatial analysis library. +// Authors: Dr. John Lindsay +// Created: 11/07/2017 +// Last Modified: 18/10/2019 +// License: MIT +// */ + +// use crate::raster::*; +// use crate::tools::*; +// use std::cmp::Ordering; +// use std::collections::BinaryHeap; +// use std::collections::VecDeque; +// use std::env; +// use std::f64; +// use std::i32; +// use std::io::{Error, ErrorKind}; +// use std::path; + +// /// This tool measures the depth that each grid cell in an input (`--dem`) raster digital elevation model (DEM) +// /// lies within a sink feature, i.e. a closed topographic depression. A sink, or depression, is a bowl-like +// /// landscape feature, which is characterized by interior drainage and groundwater recharge. The `DepthInSink` tool +// /// operates by differencing a filled DEM, using the same depression filling method as `FillDepressions`, and the +// /// original surface model. +// /// +// /// In addition to the names of the input DEM (`--dem`) and the output raster (`--output`), the user must specify +// /// whether the background value (i.e. the value assigned to grid cells that are not contained within sinks) should be +// /// set to 0.0 (`--zero_background`) Without this optional parameter specified, the tool will use the NoData value +// /// as the background value. +// /// +// /// # Reference +// /// Antonić, O., Hatic, D., & Pernar, R. (2001). DEM-based depth in sink as an environmental estimator. Ecological +// /// Modelling, 138(1-3), 247-254. +// /// +// /// # See Also +// /// `FillDepressions` +// pub struct DepthInSink { +// name: String, +// description: String, +// toolbox: String, +// parameters: Vec, +// example_usage: String, +// } + +// impl DepthInSink { +// pub fn new() -> DepthInSink { +// // public constructor +// let name = "DepthInSink".to_string(); +// let toolbox = "Hydrological Analysis".to_string(); +// let description = "Measures the depth of sinks (depressions) in a DEM.".to_string(); + +// let mut parameters = vec![]; +// parameters.push(ToolParameter { +// name: "Input DEM File".to_owned(), +// flags: vec!["-i".to_owned(), "--dem".to_owned()], +// description: "Input raster DEM file.".to_owned(), +// parameter_type: ParameterType::ExistingFile(ParameterFileType::Raster), +// default_value: None, +// optional: false, +// }); + +// parameters.push(ToolParameter { +// name: "Output File".to_owned(), +// flags: vec!["-o".to_owned(), "--output".to_owned()], +// description: "Output raster file.".to_owned(), +// parameter_type: ParameterType::NewFile(ParameterFileType::Raster), +// default_value: None, +// optional: false, +// }); + +// parameters.push(ToolParameter { +// name: "Should a background value of zero be used?".to_owned(), +// flags: vec!["--zero_background".to_owned()], +// description: "Flag indicating whether the background value of zero should be used." +// .to_owned(), +// parameter_type: ParameterType::Boolean, +// default_value: None, +// optional: true, +// }); + +// let sep: String = path::MAIN_SEPARATOR.to_string(); +// let p = format!("{}", env::current_dir().unwrap().display()); +// let e = format!("{}", env::current_exe().unwrap().display()); +// let mut short_exe = e +// .replace(&p, "") +// .replace(".exe", "") +// .replace(".", "") +// .replace(&sep, ""); +// if e.contains(".exe") { +// short_exe += ".exe"; +// } +// let usage = format!(">>.*{0} -r={1} -v --wd=\"*path*to*data*\" --dem=DEM.tif -o=output.tif --zero_background", short_exe, name).replace("*", &sep); + +// DepthInSink { +// name: name, +// description: description, +// toolbox: toolbox, +// parameters: parameters, +// example_usage: usage, +// } +// } +// } + +// impl WhiteboxTool for DepthInSink { +// fn get_source_file(&self) -> String { +// String::from(file!()) +// } + +// fn get_tool_name(&self) -> String { +// self.name.clone() +// } + +// fn get_tool_description(&self) -> String { +// self.description.clone() +// } + +// fn get_tool_parameters(&self) -> String { +// match serde_json::to_string(&self.parameters) { +// Ok(json_str) => return format!("{{\"parameters\":{}}}", json_str), +// Err(err) => return format!("{:?}", err), +// } +// } + +// fn get_example_usage(&self) -> String { +// self.example_usage.clone() +// } + +// fn get_toolbox(&self) -> String { +// self.toolbox.clone() +// } + +// fn run<'a>( +// &self, +// args: Vec, +// working_directory: &'a str, +// verbose: bool, +// ) -> Result<(), Error> { +// let mut input_file = String::new(); +// let mut output_file = String::new(); +// let mut zero_background = false; + +// if args.len() == 0 { +// return Err(Error::new( +// ErrorKind::InvalidInput, +// "Tool run with no parameters.", +// )); +// } +// for i in 0..args.len() { +// let mut arg = args[i].replace("\"", ""); +// arg = arg.replace("\'", ""); +// let cmd = arg.split("="); // in case an equals sign was used +// let vec = cmd.collect::>(); +// let mut keyval = false; +// if vec.len() > 1 { +// keyval = true; +// } +// if vec[0].to_lowercase() == "-i" +// || vec[0].to_lowercase() == "--input" +// || vec[0].to_lowercase() == "--dem" +// { +// if keyval { +// input_file = vec[1].to_string(); +// } else { +// input_file = args[i + 1].to_string(); +// } +// } else if vec[0].to_lowercase() == "-o" || vec[0].to_lowercase() == "--output" { +// if keyval { +// output_file = vec[1].to_string(); +// } else { +// output_file = args[i + 1].to_string(); +// } +// } else if vec[0].to_lowercase() == "-zero_background" +// || vec[0].to_lowercase() == "--zero_background" +// || vec[0].to_lowercase() == "--esri_style" +// { +// if vec.len() == 1 || !vec[1].to_string().to_lowercase().contains("false") { +// zero_background = true; +// } +// } +// } + +// if verbose { +// println!("***************{}", "*".repeat(self.get_tool_name().len())); +// println!("* Welcome to {} *", self.get_tool_name()); +// println!("***************{}", "*".repeat(self.get_tool_name().len())); +// } + +// let sep: String = path::MAIN_SEPARATOR.to_string(); + +// let mut progress: usize; +// let mut old_progress: usize = 1; + +// if !input_file.contains(&sep) && !input_file.contains("/") { +// input_file = format!("{}{}", working_directory, input_file); +// } +// if !output_file.contains(&sep) && !output_file.contains("/") { +// output_file = format!("{}{}", working_directory, output_file); +// } + +// if verbose { +// println!("Reading data...") +// }; + +// let input = Raster::new(&input_file, "r")?; + +// let start = Instant::now(); +// let rows = input.configs.rows as isize; +// let columns = input.configs.columns as isize; +// let num_cells = rows * columns; +// let nodata = input.configs.nodata; + +// let mut output = Raster::initialize_using_file(&output_file, &input); +// let mut background_val = (i32::min_value() + 1) as f64; +// output.reinitialize_values(background_val); + +// /* +// Find the data edges. This is complicated by the fact that DEMs frequently +// have nodata edges, whereby the DEM does not occupy the full extent of +// the raster. One approach to doing this would be simply to scan the +// raster, looking for cells that neighbour nodata values. However, this +// assumes that there are no interior nodata holes in the dataset. Instead, +// the approach used here is to perform a region-growing operation, looking +// for nodata values along the raster's edges. +// */ + +// let mut queue: VecDeque<(isize, isize)> = +// VecDeque::with_capacity((rows * columns) as usize); +// for row in 0..rows { +// /* +// Note that this is only possible because Whitebox rasters +// allow you to address cells beyond the raster extent but +// return the nodata value for these regions. +// */ +// queue.push_back((row, -1)); +// queue.push_back((row, columns)); +// } + +// for col in 0..columns { +// queue.push_back((-1, col)); +// queue.push_back((rows, col)); +// } + +// /* +// minheap is the priority queue. Note that I've tested using integer-based +// priority values, by multiplying the elevations, but this didn't result +// in a significant performance gain over the use of f64s. +// */ +// let mut minheap = BinaryHeap::with_capacity((rows * columns) as usize); +// let mut num_solved_cells = 0; +// let mut zin_n: f64; // value of neighbour of row, col in input raster +// let mut zout: f64; // value of row, col in output raster +// let mut zout_n: f64; // value of neighbour of row, col in output raster +// let dx = [1, 1, 1, 0, -1, -1, -1, 0]; +// let dy = [-1, 0, 1, 1, 1, 0, -1, -1]; +// let (mut row, mut col): (isize, isize); +// let (mut row_n, mut col_n): (isize, isize); +// while !queue.is_empty() { +// let cell = queue.pop_front().unwrap(); +// row = cell.0; +// col = cell.1; +// for n in 0..8 { +// row_n = row + dy[n]; +// col_n = col + dx[n]; +// zin_n = input[(row_n, col_n)]; +// zout_n = output[(row_n, col_n)]; +// if zout_n == background_val { +// if zin_n == nodata { +// output[(row_n, col_n)] = nodata; +// queue.push_back((row_n, col_n)); +// } else { +// output[(row_n, col_n)] = zin_n; +// // Push it onto the priority queue for the priority flood operation +// minheap.push(GridCell { +// row: row_n, +// column: col_n, +// priority: zin_n, +// }); +// } +// num_solved_cells += 1; +// } +// } + +// if verbose { +// progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; +// if progress != old_progress { +// println!("progress: {}%", progress); +// old_progress = progress; +// } +// } +// } + +// // Perform the priority flood operation. +// while !minheap.is_empty() { +// let cell = minheap.pop().unwrap(); +// row = cell.row; +// col = cell.column; +// zout = output[(row, col)]; +// for n in 0..8 { +// row_n = row + dy[n]; +// col_n = col + dx[n]; +// zout_n = output[(row_n, col_n)]; +// if zout_n == background_val { +// zin_n = input[(row_n, col_n)]; +// if zin_n != nodata { +// if zin_n < zout { +// zin_n = zout; +// } // We're in a depression. Raise the elevation. +// output[(row_n, col_n)] = zin_n; +// minheap.push(GridCell { +// row: row_n, +// column: col_n, +// priority: zin_n, +// }); +// } else { +// // Interior nodata cells are still treated as nodata and are not filled. +// output[(row_n, col_n)] = nodata; +// num_solved_cells += 1; +// } +// } +// } + +// if verbose { +// num_solved_cells += 1; +// progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; +// if progress != old_progress { +// println!("Progress (Loop 1 of 2): {}%", progress); +// old_progress = progress; +// } +// } +// } + +// background_val = nodata; +// if zero_background { +// background_val = 0f64; +// } +// for row in 0..rows { +// for col in 0..columns { +// if output[(row, col)] > input[(row, col)] { +// output[(row, col)] = output[(row, col)] - input[(row, col)]; +// } else { +// if input[(row, col)] != nodata { +// output[(row, col)] = background_val; +// } else { +// output[(row, col)] = nodata; +// } +// } +// } +// if verbose { +// progress = (100.0_f64 * row as f64 / (rows - 1) as f64) as usize; +// if progress != old_progress { +// println!("Progress (Loop 2 of 2): {}%", progress); +// old_progress = progress; +// } +// } +// } + +// let elapsed_time = get_formatted_elapsed_time(start); +// output.configs.data_type = DataType::F32; +// output.configs.palette = "qual.plt".to_string(); +// output.configs.photometric_interp = PhotometricInterpretation::Categorical; +// output.add_metadata_entry(format!( +// "Created by whitebox_tools\' {} tool", +// self.get_tool_name() +// )); +// output.add_metadata_entry(format!("Input file: {}", input_file)); +// output.add_metadata_entry(format!("Elapsed Time (excluding I/O): {}", elapsed_time)); + +// if verbose { +// println!("Saving data...") +// }; +// let _ = match output.write() { +// Ok(_) => { +// if verbose { +// println!("Output file written") +// } +// } +// Err(e) => return Err(e), +// }; +// if verbose { +// println!( +// "{}", +// &format!("Elapsed Time (excluding I/O): {}", elapsed_time) +// ); +// } + +// Ok(()) +// } +// } + +// #[derive(PartialEq, Debug)] +// struct GridCell { +// row: isize, +// column: isize, +// priority: f64, +// } + +// impl Eq for GridCell {} + +// impl PartialOrd for GridCell { +// fn partial_cmp(&self, other: &Self) -> Option { +// other.priority.partial_cmp(&self.priority) +// } +// } + +// impl Ord for GridCell { +// fn cmp(&self, other: &GridCell) -> Ordering { +// let ord = self.partial_cmp(other).unwrap(); +// match ord { +// Ordering::Greater => Ordering::Less, +// Ordering::Less => Ordering::Greater, +// Ordering::Equal => ord, +// } +// } +// } diff --git a/src/tools/hydro_analysis/dinf_flow_accum.rs b/src/tools/hydro_analysis/dinf_flow_accum.rs index 6352c1e43..1f498b58f 100644 --- a/src/tools/hydro_analysis/dinf_flow_accum.rs +++ b/src/tools/hydro_analysis/dinf_flow_accum.rs @@ -194,7 +194,7 @@ impl WhiteboxTool for DInfFlowAccumulation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/dinf_mass_flux.rs b/src/tools/hydro_analysis/dinf_mass_flux.rs index c46a80868..107d21a22 100644 --- a/src/tools/hydro_analysis/dinf_mass_flux.rs +++ b/src/tools/hydro_analysis/dinf_mass_flux.rs @@ -169,7 +169,7 @@ impl WhiteboxTool for DInfMassFlux { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/dinf_pointer.rs b/src/tools/hydro_analysis/dinf_pointer.rs index b354cb3e3..24f9ca443 100644 --- a/src/tools/hydro_analysis/dinf_pointer.rs +++ b/src/tools/hydro_analysis/dinf_pointer.rs @@ -142,7 +142,7 @@ impl WhiteboxTool for DInfPointer { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/downslope_distance_to_stream.rs b/src/tools/hydro_analysis/downslope_distance_to_stream.rs index 92b39c18e..53aa939f0 100644 --- a/src/tools/hydro_analysis/downslope_distance_to_stream.rs +++ b/src/tools/hydro_analysis/downslope_distance_to_stream.rs @@ -135,7 +135,7 @@ impl WhiteboxTool for DownslopeDistanceToStream { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/downslope_flowpath_length.rs b/src/tools/hydro_analysis/downslope_flowpath_length.rs index d698fb510..297bd49d8 100644 --- a/src/tools/hydro_analysis/downslope_flowpath_length.rs +++ b/src/tools/hydro_analysis/downslope_flowpath_length.rs @@ -166,7 +166,7 @@ impl WhiteboxTool for DownslopeFlowpathLength { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/elevation_above_stream.rs b/src/tools/hydro_analysis/elevation_above_stream.rs index 0ec2d03fa..1a3b1bc68 100644 --- a/src/tools/hydro_analysis/elevation_above_stream.rs +++ b/src/tools/hydro_analysis/elevation_above_stream.rs @@ -147,7 +147,7 @@ impl WhiteboxTool for ElevationAboveStream { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/elevation_above_stream_euclidean.rs b/src/tools/hydro_analysis/elevation_above_stream_euclidean.rs index 919e33996..6960e9e20 100644 --- a/src/tools/hydro_analysis/elevation_above_stream_euclidean.rs +++ b/src/tools/hydro_analysis/elevation_above_stream_euclidean.rs @@ -142,7 +142,7 @@ impl WhiteboxTool for ElevationAboveStreamEuclidean { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/fd8_flow_accum.rs b/src/tools/hydro_analysis/fd8_flow_accum.rs index f08de5d27..e54466d53 100644 --- a/src/tools/hydro_analysis/fd8_flow_accum.rs +++ b/src/tools/hydro_analysis/fd8_flow_accum.rs @@ -2,7 +2,7 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay Created: 26/06/2017 -Last Modified: 18/10/2019 +Last Modified: 21/11/2019 License: MIT */ @@ -208,7 +208,7 @@ impl WhiteboxTool for FD8FlowAccumulation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -220,55 +220,52 @@ impl WhiteboxTool for FD8FlowAccumulation { if vec.len() > 1 { keyval = true; } - if vec[0].to_lowercase() == "-i" - || vec[0].to_lowercase() == "--input" - || vec[0].to_lowercase() == "--dem" - { - if keyval { - input_file = vec[1].to_string(); + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-i" || flag_val == "-input" || flag_val == "-dem" { + input_file = if keyval { + vec[1].to_string() } else { - input_file = args[i + 1].to_string(); - } - } else if vec[0].to_lowercase() == "-o" || vec[0].to_lowercase() == "--output" { - if keyval { - output_file = vec[1].to_string(); + args[i + 1].to_string() + }; + } else if flag_val == "-o" || flag_val == "-output" { + output_file = if keyval { + vec[1].to_string() } else { - output_file = args[i + 1].to_string(); - } - } else if vec[0].to_lowercase() == "-out_type" || vec[0].to_lowercase() == "--out_type" - { - if keyval { - out_type = vec[1].to_lowercase(); + args[i + 1].to_string() + }; + } else if flag_val == "-out_type" { + out_type = if keyval { + vec[1].to_lowercase() } else { - out_type = args[i + 1].to_lowercase(); - } - if out_type.contains("specific") || out_type.contains("sca") { - out_type = String::from("sca"); + args[i + 1].to_lowercase() + }; + out_type = if out_type.contains("specific") || out_type.contains("sca") { + String::from("sca") } else if out_type.contains("cells") { - out_type = String::from("cells"); + String::from("cells") } else { - out_type = String::from("ca"); - } - } else if vec[0].to_lowercase() == "-exponent" || vec[0].to_lowercase() == "--exponent" - { - if keyval { - exponent = vec[1].to_string().parse::().unwrap(); + String::from("ca") + }; + } else if flag_val == "-exponent" { + exponent = if keyval { + vec[1].to_string().parse::().unwrap() } else { - exponent = args[i + 1].to_string().parse::().unwrap(); - } - } else if vec[0].to_lowercase() == "-threshold" - || vec[0].to_lowercase() == "--threshold" - { - if keyval { - convergence_threshold = vec[1].to_string().parse::().unwrap(); + args[i + 1].to_string().parse::().unwrap() + }; + } else if flag_val == "-threshold" { + convergence_threshold = if keyval { + vec[1].to_string().parse::().unwrap() } else { - convergence_threshold = args[i + 1].to_string().parse::().unwrap(); + args[i + 1].to_string().parse::().unwrap() + }; + if convergence_threshold == 0f64 { + convergence_threshold = f64::INFINITY; } - } else if vec[0].to_lowercase() == "-log" || vec[0].to_lowercase() == "--log" { + } else if flag_val == "-log" { if vec.len() == 1 || !vec[1].to_string().to_lowercase().contains("false") { log_transform = true; } - } else if vec[0].to_lowercase() == "-clip" || vec[0].to_lowercase() == "--clip" { + } else if flag_val == "-clip" { if vec.len() == 1 || !vec[1].to_string().to_lowercase().contains("false") { clip_max = true; } @@ -389,7 +386,7 @@ impl WhiteboxTool for FD8FlowAccumulation { ]; let (mut max_slope, mut slope): (f64, f64); let mut dir: i8; - + let mut total_weights: f64; while !stack.is_empty() { let cell = stack.pop().unwrap(); row = cell.0; @@ -398,7 +395,7 @@ impl WhiteboxTool for FD8FlowAccumulation { fa = output[(row, col)]; num_inflowing[(row, col)] = -1i8; - let mut total_weights = 0.0; + total_weights = 0.0; let mut weights: [f64; 8] = [0.0; 8]; let mut downslope: [bool; 8] = [false; 8]; if fa < convergence_threshold { diff --git a/src/tools/hydro_analysis/fd8_pointer.rs b/src/tools/hydro_analysis/fd8_pointer.rs index da64c8a2e..78da9f8d0 100644 --- a/src/tools/hydro_analysis/fd8_pointer.rs +++ b/src/tools/hydro_analysis/fd8_pointer.rs @@ -146,7 +146,7 @@ impl WhiteboxTool for FD8Pointer { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/fill_burn.rs b/src/tools/hydro_analysis/fill_burn.rs index e4e14bb08..ad9eaaaed 100644 --- a/src/tools/hydro_analysis/fill_burn.rs +++ b/src/tools/hydro_analysis/fill_burn.rs @@ -149,7 +149,7 @@ impl WhiteboxTool for FillBurn { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -534,9 +534,8 @@ impl WhiteboxTool for FillBurn { // Perform the priority flood operation. - let min_val = dem.configs.minimum; - let elev_digits = ((dem.configs.maximum - min_val) as i64).to_string().len(); - let elev_multiplier = 10.0_f64.powi((7 - elev_digits) as i32); + let elev_digits = (dem.configs.maximum as i64).to_string().len(); + let elev_multiplier = 10.0_f64.powi((12 - elev_digits) as i32); let small_num = 1.0 / elev_multiplier as f64; while !minheap.is_empty() { diff --git a/src/tools/hydro_analysis/fill_depressions.rs b/src/tools/hydro_analysis/fill_depressions.rs index 8f66f68ed..10c9e8620 100644 --- a/src/tools/hydro_analysis/fill_depressions.rs +++ b/src/tools/hydro_analysis/fill_depressions.rs @@ -2,20 +2,24 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay Created: 28/06/2017 -Last Modified: 18/10/2019 +Last Modified: 24/11/2019 License: MIT */ use crate::raster::*; use crate::tools::*; +use crate::structures::Array2D; use std::cmp::Ordering; -use std::collections::BinaryHeap; -use std::collections::VecDeque; +use std::cmp::Ordering::Equal; +use std::collections::{BinaryHeap, VecDeque}; use std::env; use std::f64; use std::i32; use std::io::{Error, ErrorKind}; use std::path; +use std::sync::mpsc; +use std::sync::Arc; +use std::thread; /// This tool can be used to fill all of the depressions in a digital elevation model (DEM) and to remove the f /// lat areas. This is a common pre-processing step required by many flow-path analysis tools to ensure continuous @@ -24,7 +28,9 @@ use std::path; /// edge cells, and visiting cells from lowest order using a priority queue. As such, it is based on the algorithm /// first proposed by Wang and Liu (2006). It is currently the most efficient depression-removal algorithm available /// in WhiteboxTools, although it is not significantly more efficient than the `BreachDepressions` tool, which is -/// known to provide a solution to depression removal with less impact of the DEM. +/// known to provide a solution to depression removal with less impact of the DEM. Furthermore, the `BreachDepressionsLeastCost`, +/// while less efficient than either other hydrological preprocessing methods, often provides a lower impact solution +/// to topographic depression. /// /// If the input DEM has gaps, or missing-data holes, that contain NoData values, it is better to use the /// `FillMissingData` tool to repair these gaps. This tool will interpolate values across the gaps and produce @@ -33,12 +39,18 @@ use std::path; /// of valid data. Any NoData areas along the edge of the grid will simply be ignored and will remain NoData areas in /// the output image. /// +/// In comparison with the `BreachDepressionsLeastCost` tool, the depression filling method often provides a less +/// satisfactory, higher impact, depression removal solution and is often less efficient. **It is advisable that users +/// try the `BreachDepressionsLeastCost` tool to remove depressions from their DEMs first**. The `BreachDepressionsLeastCost` +/// tool is particularly well suited to breaching through road embankments in fine-resolution DEMs. Nonetheless, there are +/// applications for which full depression filling using the `FillDepressions` tool may be preferred. +/// /// # Reference /// Wang, L. and Lui, H. 2006. An efficient method for identifying and filling surface depressions in digital elevation /// models for hydrologic analysis and modelling. International Journal of Geographical Information Science, 20(2): 193-213. /// /// # See Also -/// `BreachDepressions`, `FillMissingData` +/// `BreachDepressionsLeastCost`, `BreachDepressions`, `FillMissingData` pub struct FillDepressions { name: String, description: String, @@ -93,6 +105,15 @@ impl FillDepressions { optional: true, }); + parameters.push(ToolParameter { + name: "Maximum depth (z units)".to_owned(), + flags: vec!["--max_depth".to_owned()], + description: "Optional maximum depression depth to fill.".to_owned(), + parameter_type: ParameterType::Float, + default_value: None, + optional: true, + }); + let sep: String = path::MAIN_SEPARATOR.to_string(); let p = format!("{}", env::current_dir().unwrap().display()); let e = format!("{}", env::current_exe().unwrap().display()); @@ -158,11 +179,12 @@ impl WhiteboxTool for FillDepressions { let mut output_file = String::new(); let mut fix_flats = false; let mut flat_increment = f64::NAN; + let mut max_depth = f64::INFINITY; if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -197,6 +219,12 @@ impl WhiteboxTool for FillDepressions { } else { args[i + 1].to_string().parse::().unwrap() }; + } else if flag_val == "-max_depth" { + max_depth = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; } } @@ -227,372 +255,425 @@ impl WhiteboxTool for FillDepressions { let start = Instant::now(); let rows = input.configs.rows as isize; let columns = input.configs.columns as isize; - let num_cells = rows * columns; let nodata = input.configs.nodata; - - // let min_val = input.configs.minimum; - // let elev_digits = ((input.configs.maximum - min_val) as i64).to_string().len(); - // let elev_multiplier = 10.0_f64.powi((7 - elev_digits) as i32); - // let mut small_num = 0.0; - // if fix_flats { - // small_num = 1.0 / elev_multiplier as f64; - // } + let resx = input.configs.resolution_x; + let resy = input.configs.resolution_y; + let diagres = (resx * resx + resy * resy).sqrt(); let small_num = if fix_flats && !flat_increment.is_nan() { flat_increment } else if fix_flats { - let min_val = input.configs.minimum; - let elev_digits = ((input.configs.maximum - min_val) as i64).to_string().len(); - let elev_multiplier = 10.0_f64.powi((6 - elev_digits) as i32); - 1.0_f64 / elev_multiplier as f64 + let elev_digits = (input.configs.maximum as i64).to_string().len(); + let elev_multiplier = 10.0_f64.powi((15 - elev_digits) as i32); + 1.0_f64 / elev_multiplier as f64 * diagres.ceil() } else { 0f64 }; let mut output = Raster::initialize_using_file(&output_file, &input); + output.set_data_from_raster(&input)?; output.configs.data_type = DataType::F64; - let background_val = (i32::min_value() + 1) as f64; - output.reinitialize_values(background_val); - - /* - Find the data edges. This is complicated by the fact that DEMs frequently - have nodata edges, whereby the DEM does not occupy the full extent of - the raster. One approach to doing this would be simply to scan the - raster, looking for cells that neighbour nodata values. However, this - assumes that there are no interior nodata holes in the dataset. Instead, - the approach used here is to perform a region-growing operation, looking - for nodata values along the raster's edges. - */ - - let mut queue: VecDeque<(isize, isize)> = - VecDeque::with_capacity((rows * columns) as usize); - for row in 0..rows { - /* - Note that this is only possible because Whitebox rasters - allow you to address cells beyond the raster extent but - return the nodata value for these regions. - */ - queue.push_back((row, -1)); - queue.push_back((row, columns)); - } + output.configs.display_min = input.configs.display_min; + output.configs.display_max = input.configs.display_max; - for col in 0..columns { - queue.push_back((-1, col)); - queue.push_back((rows, col)); - } + // drop(input); // input is no longer needed. - /* - minheap is the priority queue. Note that I've tested using integer-based - priority values, by multiplying the elevations, but this didn't result - in a significant performance gain over the use of f64s. - */ - let mut minheap = BinaryHeap::with_capacity((rows * columns) as usize); - let mut num_solved_cells = 0; - let mut zin_n: f64; // value of neighbour of row, col in input raster - let mut zout: f64; // value of row, col in output raster - let mut zout_n: f64; // value of neighbour of row, col in output raster + + let (mut col, mut row): (isize, isize); + let (mut rn, mut cn): (isize, isize); + let (mut z, mut zn): (f64, f64); let dx = [1, 1, 1, 0, -1, -1, -1, 0]; let dy = [-1, 0, 1, 1, 1, 0, -1, -1]; - let (mut row, mut col): (isize, isize); - let (mut row_n, mut col_n): (isize, isize); - while !queue.is_empty() { - let cell = queue.pop_front().unwrap(); - row = cell.0; - col = cell.1; - for n in 0..8 { - row_n = row + dy[n]; - col_n = col + dx[n]; - zin_n = input.get_value(row_n, col_n); - zout_n = output[(row_n, col_n)]; - if zout_n == background_val { - if zin_n == nodata { - output.set_value(row_n, col_n, nodata); - queue.push_back((row_n, col_n)); - } else { - output[(row_n, col_n)] = zin_n; - // Push it onto the priority queue for the priority flood operation - minheap.push(GridCell { - row: row_n, - column: col_n, - priority: zin_n, - }); + + // Find pit cells. This step is parallelized. + let num_procs = num_cpus::get() as isize; + let output2 = Arc::new(output); + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let output2 = output2.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let mut z: f64; + let mut zn: f64; + let mut flag: bool; + let mut pits = vec![]; + for row in (1..rows-1).filter(|r| r % num_procs == tid) { + for col in 1..columns-1 { + z = output2.get_value(row, col); + if z != nodata { + flag = true; + for n in 0..8 { + zn = output2.get_value(row + dy[n], col + dx[n]); + if zn < z || zn == nodata { // It either has a lower neighbour or is an edge cell. + flag = false; + break; + } + } + if flag { // it's a cell with undefined flow + pits.push((row, col, z)); + } + } } - num_solved_cells += 1; } - } + tx.send(pits).unwrap(); + }); + } + let mut undefined_flow_cells = vec![]; + for p in 0..num_procs { + let mut pits = rx.recv().unwrap(); + undefined_flow_cells.append(&mut pits); + if verbose { - progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; + progress = (100.0_f64 * (p + 1) as f64 / num_procs as f64) as usize; if progress != old_progress { - println!("progress: {}%", progress); + println!("Finding pit cells: {}%", progress); old_progress = progress; } } } - /* - The following code follows the scenario of a priority-flood method without the extra - complication of an embedded region-growing operation for in-depression sites. - */ - - // Perform the priority flood operation. - while !minheap.is_empty() { - let cell = minheap.pop().unwrap(); - row = cell.row; - col = cell.column; - zout = output[(row, col)]; - for n in 0..8 { - row_n = row + dy[n]; - col_n = col + dx[n]; - zout_n = output[(row_n, col_n)]; - if zout_n == background_val { - zin_n = input[(row_n, col_n)]; - if zin_n != nodata { - if zin_n < (zout + small_num) { - zin_n = zout + small_num; - } // We're in a depression. Raise the elevation. - output[(row_n, col_n)] = zin_n; - minheap.push(GridCell { - row: row_n, - column: col_n, - priority: zin_n, - }); + output = match Arc::try_unwrap(output2) { + Ok(val) => val, + Err(_) => panic!("Error unwrapping 'output'"), + }; + + let num_deps = undefined_flow_cells.len(); + + + // Now we need to perform an in-place depression filling + let mut minheap = BinaryHeap::new(); + let mut visited: Array2D = Array2D::new(rows, columns, 0, -1)?; + let mut flats: Array2D = Array2D::new(rows, columns, 0, -1)?; + let mut possible_outlets = vec![]; + // solve from highest to lowest + undefined_flow_cells.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(Equal)); + let mut pit_id = 1; + let mut flag: bool; + let mut z_pit: f64; + while let Some(cell) = undefined_flow_cells.pop() { + row = cell.0; + col = cell.1; + if flats.get_value(row, col) != 1 { // if it's already in a solved site, don't do it a second time. + // First there is a priority region-growing operation to find the outlets. + z_pit = output.get_value(row, col); + minheap.clear(); + minheap.push(GridCell { + row: row, + column: col, + priority: z_pit, + }); + visited.set_value(row, col, 1); + let mut outlet_found = false; + let mut outlet_z = f64::INFINITY; + let mut queue = VecDeque::new(); + while let Some(cell2) = minheap.pop() { + z = cell2.priority; + if outlet_found && z > outlet_z { + break; + } + if z - z_pit > max_depth { + // no outlet could be found that was low enough. + break; + } + if !outlet_found { + for n in 0..8 { + cn = cell2.column + dx[n]; + rn = cell2.row + dy[n]; + if visited.get_value(rn, cn) == 0 { + zn = output.get_value(rn, cn); + if !outlet_found { + if zn >= z && zn != nodata { + minheap.push(GridCell { + row: rn, + column: cn, + priority: zn, + }); + visited.set_value(rn, cn, 1); + } else if zn != nodata { // zn < z + // 'cell' has a lower neighbour that hasn't already passed through minheap. + // Therefore, 'cell' is a pour point cell. + outlet_found = true; + outlet_z = z; + queue.push_back((cell2.row, cell2.column)); + possible_outlets.push((cell2.row, cell2.column)); + } + } else if zn == outlet_z { // We've found the outlet but are still looking for additional outlets. + minheap.push(GridCell { + row: rn, + column: cn, + priority: zn, + }); + visited.set_value(rn, cn, 1); + } + } + } } else { - // Interior nodata cells are still treated as nodata and are not filled. - output[(row_n, col_n)] = nodata; - num_solved_cells += 1; + if z == outlet_z { + flag = false; + for n in 0..8 { + cn = cell2.column + dx[n]; + rn = cell2.row + dy[n]; + if visited.get_value(rn, cn) == 0 { + zn = output.get_value(rn, cn); + if zn < z { + flag = true; + } else if zn == outlet_z { + minheap.push(GridCell { + row: rn, + column: cn, + priority: zn, + }); + visited.set_value(rn, cn, 1); + } + } + } + if flag { // it's an outlet + queue.push_back((cell2.row, cell2.column)); + possible_outlets.push((cell2.row, cell2.column)); + } else { + visited.set_value(cell2.row, cell2.column, 1); + } + } + } + } + + // Now that we have the outlets, raise the interior of the depression + if outlet_found { + while let Some(cell2) = queue.pop_front() { + for n in 0..8 { + rn = cell2.0 + dy[n]; + cn = cell2.1 + dx[n]; + if visited.get_value(rn, cn) == 1 { + visited.set_value(rn, cn, 0); + queue.push_back((rn, cn)); + z = output.get_value(rn, cn); + if z < outlet_z { + output.set_value(rn, cn, outlet_z); + flats.set_value(rn, cn, 1); + } else if z == outlet_z { + flats.set_value(rn, cn, 1); + } + } + } + } + } else { + queue.push_back((row, col)); // start at the pit cell and clean up visited + while let Some(cell2) = queue.pop_front() { + for n in 0..8 { + rn = cell2.0 + dy[n]; + cn = cell2.1 + dx[n]; + if visited.get_value(rn, cn) == 1 { + visited.set_value(rn, cn, 0); + queue.push_back((rn, cn)); + } + } } } } if verbose { - num_solved_cells += 1; - progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; + progress = (100.0_f64 * pit_id as f64 / num_deps as f64) as usize; if progress != old_progress { - println!("Progress: {}%", progress); + println!("Filling depressions: {}%", progress); old_progress = progress; } } + pit_id += 1; + } + + drop(visited); + + if small_num > 0f64 && fix_flats { // fix the flats + if verbose { + println!("Fixing flow on flats..."); + println!("Flats increment value: {}", small_num); + } + // Some of the potential outlets really will have lower cells. + // let mut queue = VecDeque::new(); + minheap.clear(); + while let Some(cell) = possible_outlets.pop() { + z = output.get_value(cell.0, cell.1); + flag = false; + for n in 0..8 { + rn = cell.0 + dy[n]; + cn = cell.1 + dx[n]; + zn = output.get_value(rn, cn); + if zn < z && zn != nodata { + flag = true; + break; + } + } + if flag { // it's confirmed as an outlet + minheap.push(GridCell { + row: cell.0, + column: cell.1, + priority: z, + }); + } + } + + let num_outlets = minheap.len(); + + while let Some(cell) = minheap.pop() { + if flats.get_value(cell.row, cell.column) != 3 { + z = output.get_value(cell.row, cell.column); + flats.set_value(cell.row, cell.column, 3); + let mut outlets = vec![]; + outlets.push(cell); + // Are there any other outlet cells at the same elevation (likely for the same feature) + flag = true; + while flag { + match minheap.peek() { + Some(cell2) => { + if cell2.priority == z { + flats.set_value(cell2.row, cell2.column, 3); + outlets.push(minheap.pop().unwrap()); + } else { + flag = false; + } + }, + None => { + flag = false; + } + } + + } + // let mut queue = VecDeque::new(); + let mut minheap2 = BinaryHeap::new(); + for cell2 in &outlets { + z = output.get_value(cell2.row, cell2.column); + for n in 0..8 { + rn = cell2.row + dy[n]; + cn = cell2.column + dx[n]; + if flats.get_value(rn, cn) != 3 { + zn = output.get_value(rn, cn); + if zn == z && zn != nodata { + // queue.push_back((rn, cn, z)); + minheap2.push(GridCell2 { + row: rn, + column: cn, + z: z, + priority: input.get_value(rn, cn), + }); + output.set_value(rn, cn, z + small_num); + flats.set_value(rn, cn, 3); + } + } + } + } + // Now fix the flats + while let Some(cell2) = minheap2.pop() { + z = output.get_value(cell2.row, cell2.column); + for n in 0..8 { + rn = cell2.row + dy[n]; + cn = cell2.column + dx[n]; + if flats.get_value(rn, cn) != 3 { + zn = output.get_value(rn, cn); + if zn < z + small_num && zn >= cell2.z && zn != nodata { + // queue.push_back((rn, cn, cell2.2)); + minheap2.push(GridCell2 { + row: rn, + column: cn, + z: cell2.z, + priority: input.get_value(rn, cn), + }); + output.set_value(rn, cn, z + small_num); + flats.set_value(rn, cn, 3); + } + } + } + } + // while let Some(cell2) = queue.pop_front() { + // z = output.get_value(cell2.0, cell2.1); + // for n in 0..8 { + // rn = cell2.0 + dy[n]; + // cn = cell2.1 + dx[n]; + // if flats.get_value(rn, cn) != 3 { + // zn = output.get_value(rn, cn); + // if zn < z + small_num && zn >= cell2.2 && zn != nodata { + // queue.push_back((rn, cn, cell2.2)); + // output.set_value(rn, cn, z + small_num); + // flats.set_value(rn, cn, 3); + // } + // } + // } + // } + } + + if verbose { + progress = (100.0_f64 * (1f64 - minheap.len() as f64 / num_outlets as f64)) as usize; + if progress != old_progress { + println!("Fixing flats: {}%", progress); + old_progress = progress; + } + } + } + + + // let mut queue = VecDeque::new(); + // minheap.clear(); + // while let Some(cell) = possible_outlets.pop() { + // z = output.get_value(cell.0, cell.1); + // flag = false; + // for n in 0..8 { + // rn = cell.0 + dy[n]; + // cn = cell.1 + dx[n]; + // zn = output.get_value(rn, cn); + // if zn < z && zn != nodata { + // flag = true; + // break; + // } + // } + // if flag { + // queue.push_back((cell.0, cell.1, z)); + // flats.set_value(cell.0, cell.1, 2); + // } else { + // flats.set_value(cell.0, cell.1, 1); + // } + // } + + // let mut flats_value: i8; + + // while let Some(cell) = queue.pop_front() { + // z = output.get_value(cell.0, cell.1); + // flats_value = flats.get_value(cell.0, cell.1); + // if flats_value == 2 { // outlet cell + // for n in 0..8 { + // rn = cell.0 + dy[n]; + // cn = cell.1 + dx[n]; + // if flats.get_value(rn, cn) == 1 { + // zn = output.get_value(rn, cn); + // if zn == z { + // queue.push_back((rn, cn, z)); + // output.set_value(rn, cn, z + small_num); + // flats.set_value(rn, cn, 3); + // } + // } + // } + // flats.set_value(cell.0, cell.1, 3); + // } else { // non-outlet cell + // for n in 0..8 { + // rn = cell.0 + dy[n]; + // cn = cell.1 + dx[n]; + // flats_value = flats.get_value(rn, cn); + // if flats_value == 0 || flats_value == 1 { + // zn = output.get_value(rn, cn); + // if zn < z + small_num && zn >= cell.2 && zn != nodata { + // queue.push_back((rn, cn, cell.2)); + // output.set_value(rn, cn, z + small_num); + // flats.set_value(rn, cn, 3); + // } else if flats_value == 1 { + // flats.set_value(rn, cn, 3); + // } + // } + // } + // } + // } } - /* - This code uses a slightly more complex priority flood approach that uses an embedded - region-growing operation for cells within depressions. It offers a slight speed up - over the traditional approach, but I have noticed that sometimes it doesn't work - as expected. I'm not sure why and it would require some effort to track down the bug. - Given that most DEMs have relatively few cells within depressions, the speed up of - this approach is perhaps not worthwhile and it is certainly more complex. - */ - // // Perform the priority flood operation. - // while !minheap.is_empty() { - // let cell = minheap.pop().unwrap(); - // row = cell.row; - // col = cell.column; - // zout = output[(row, col)]; - // for n in 0..8 { - // row_n = row + dy[n]; - // col_n = col + dx[n]; - // zout_n = output[(row_n, col_n)]; - // if zout_n == background_val { - // zin_n = input[(row_n, col_n)]; - // if zin_n != nodata { - // if zin_n < (zout + small_num) { - // // We're in a depression. Raise the elevation. - // zout_n = zout + small_num; - // output[(row_n, col_n)] = zout_n; - // /* - // Cells that are in the depression don't need to be discovered by - // the more expensive priority-flood operation. Instead, perform - // an efficient region-growing operation to find cells connected - // to this cell that have elevations in the input DEM that are - // less than the adjusted zout_n. - // */ - // queue.push_back((row_n, col_n)); - // while !queue.is_empty() { - // let cell = queue.pop_front().unwrap(); - // row = cell.0; - // col = cell.1; - // zout = output[(row, col)]; - // for n2 in 0..8 { - // row_n = row + dy[n2]; - // col_n = col + dx[n2]; - // zout_n = output[(row_n, col_n)]; - // if zout_n == background_val { - // zin_n = input[(row_n, col_n)]; - // if zin_n != nodata { - // if zin_n < (zout + small_num) { - // zout_n = zout + small_num; - // output[(row_n, col_n)] = zout_n; - // queue.push_back((row_n, col_n)); - // } else { - // minheap.push(GridCell{ row: row_n, column: col_n, priority: zin_n }); - // output[(row_n, col_n)] = zin_n; - // } - // } else { - // // Interior nodata cells are still treated as nodata and are not filled. - // output[(row_n, col_n)] = nodata; - // num_solved_cells += 1; - // } - // } - // } - // if verbose { - // num_solved_cells += 1; - // progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; - // if progress != old_progress { - // println!("Progress: {}%", progress); - // old_progress = progress; - // } - // } - // } - // } else { - // minheap.push(GridCell{ row: row_n, column: col_n, priority: zin_n }); - // output[(row_n, col_n)] = zin_n; - // } - // } else { - // // Interior nodata cells are still treated as nodata and are not filled. - // output[(row_n, col_n)] = nodata; - // num_solved_cells += 1; - // } - // } - // } - - // if verbose { - // num_solved_cells += 1; - // progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; - // if progress != old_progress { - // println!("Progress: {}%", progress); - // old_progress = progress; - // } - // } - // } - - /* This was an experiement with an approach that reduced the reliance on the priority queue. - It works well but is slightly less efficient that the traditional approach. */ - - // let mut output = Raster::initialize_using_file(&output_file, &input); - // let background_val = (i32::max_value() - 1) as f64; - // output.reinitialize_values(background_val); - - // /* - // Find the data edges. This is complicated by the fact that DEMs frequently - // have nodata edges, whereby the DEM does not occupy the full extent of - // the raster. One approach to doing this would be simply to scan the - // raster, looking for cells that neighbour nodata values. However, this - // assumes that there are no interior nodata holes in the dataset. Instead, - // the approach used here is to perform a region-growing operation, looking - // for nodata values along the raster's edges. - // */ - // //let mut stack = Vec::with_capacity((rows * columns) as usize); - // let mut queue: VecDeque<(isize, isize)> = VecDeque::with_capacity((rows * columns) as usize); - // for row in 0..rows { - // /* - // Note that this is only possible because Whitebox rasters - // allow you to address cells beyond the raster extent but - // return the nodata value for these regions. - // */ - // queue.push_back((row, -1)); - // queue.push_back((row, columns)); - // } - - // for col in 0..columns { - // queue.push_back((-1, col)); - // queue.push_back((rows, col)); - // } - - // /* - // minheap is the priority queue. Note that I've tested using integer-based - // priority values, by multiplying the elevations, but this didn't result - // in a significant performance gain over the use of f64s. - // */ - // let mut minheap = BinaryHeap::with_capacity((rows * columns) as usize); - // let mut num_solved_cells = 0; - // let mut zin_n: f64; // value of neighbour of row, col in input raster - // let mut zout: f64; // value of row, col in output raster - // let mut zout_n: f64; // value of neighbour of row, col in output raster - // let dx = [ 1, 1, 1, 0, -1, -1, -1, 0 ]; - // let dy = [ -1, 0, 1, 1, 1, 0, -1, -1 ]; - // let (mut row, mut col): (isize, isize); - // let (mut row_n, mut col_n): (isize, isize); - // while !queue.is_empty() { - // let cell = queue.pop_front().unwrap(); - // row = cell.0; - // col = cell.1; - // for n in 0..8 { - // row_n = row + dy[n]; - // col_n = col + dx[n]; - // zin_n = input[(row_n, col_n)]; - // zout_n = output[(row_n, col_n)]; - // if zout_n == background_val { - // if zin_n == nodata { - // output[(row_n, col_n)] = nodata; - // queue.push_back((row_n, col_n)); - // } else { - // output[(row_n, col_n)] = zin_n; - // // Push it onto the priority queue for the priority flood operation - // minheap.push(GridCell{ row: row_n, column: col_n, priority: zin_n }); - // } - // num_solved_cells += 1; - // } - // } - - // if verbose { - // progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; - // if progress != old_progress { - // println!("progress: {}%", progress); - // old_progress = progress; - // } - // } - // } - - // // Perform the priority flood operation. - // // let initial_heap_size = minheap.len(); - // // num_solved_cells = 0; - // while !minheap.is_empty() { - // let cell = minheap.pop().unwrap(); - // queue.push_back((cell.row, cell.column)); - // while !queue.is_empty() { - // let cell = queue.pop_front().unwrap(); - // row = cell.0; - // col = cell.1; - // zout = output[(row, col)]; - // for n in 0..8 { - // row_n = row + dy[n]; - // col_n = col + dx[n]; - // zout_n = output[(row_n, col_n)]; - // zin_n = input[(row_n, col_n)]; - // if zout_n > zin_n { - // if zin_n != nodata { - // if zin_n > zout + small_num { - // output[(row_n, col_n)] = zin_n; - // queue.push_back((row_n, col_n)); - // num_solved_cells += 1; - // } else if zout + small_num < zout_n { - // output[(row_n, col_n)] = zout + small_num; - // queue.push_back((row_n, col_n)); - // } - // } else { - // output[(row_n, col_n)] = nodata; - // queue.push_back((row_n, col_n)); - // num_solved_cells += 1; - // } - // } - // } - - // if verbose { - // progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; - // if progress != old_progress { - // println!("Progress: {}%", progress); - // old_progress = progress; - // } - // } - // } - // } - - // if verbose { println!("Progress: 100%"); } let elapsed_time = get_formatted_elapsed_time(start); - output.configs.display_min = input.configs.display_min; - output.configs.display_max = input.configs.display_max; output.add_metadata_entry(format!( "Created by whitebox_tools\' {} tool", self.get_tool_name() @@ -630,27 +711,41 @@ impl WhiteboxTool for FillDepressions { struct GridCell { row: isize, column: isize, - // priority: usize, priority: f64, } impl Eq for GridCell {} impl PartialOrd for GridCell { - fn partial_cmp(&self, other: &GridCell) -> Option { - // Some(other.priority.cmp(&self.priority)) + fn partial_cmp(&self, other: &Self) -> Option { other.priority.partial_cmp(&self.priority) } } impl Ord for GridCell { - fn cmp(&self, other: &GridCell) -> Ordering { - // other.priority.cmp(&self.priority) - let ord = self.partial_cmp(other).unwrap(); - match ord { - Ordering::Greater => Ordering::Less, - Ordering::Less => Ordering::Greater, - Ordering::Equal => ord, - } + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() + } +} + +#[derive(PartialEq, Debug)] +struct GridCell2 { + row: isize, + column: isize, + z: f64, + priority: f64, +} + +impl Eq for GridCell2 {} + +impl PartialOrd for GridCell2 { + fn partial_cmp(&self, other: &Self) -> Option { + other.priority.partial_cmp(&self.priority) + } +} + +impl Ord for GridCell2 { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() } } diff --git a/src/tools/hydro_analysis/fill_depressions_wang_and_lui.rs b/src/tools/hydro_analysis/fill_depressions_wang_and_lui.rs new file mode 100644 index 000000000..a078a6f3f --- /dev/null +++ b/src/tools/hydro_analysis/fill_depressions_wang_and_lui.rs @@ -0,0 +1,653 @@ +/* +This tool is part of the WhiteboxTools geospatial analysis library. +Authors: Dr. John Lindsay +Created: 28/06/2017 +Last Modified: 05/12/2019 +License: MIT + +NOTE: This tool was originally named FillDepressions. However, I have updated the algorithm used by the +FillDepressions tool to something that is often more efficient than the Wang and Lui method. As such, +I have created this tool to house the original Wang and Lui based depression filling method for +legacy reasons. +*/ + +use crate::raster::*; +use crate::tools::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; +use std::collections::VecDeque; +use std::env; +use std::f64; +use std::i32; +use std::io::{Error, ErrorKind}; +use std::path; + +/// This tool can be used to fill all of the depressions in a digital elevation model (DEM) and to remove the f +/// lat areas. This is a common pre-processing step required by many flow-path analysis tools to ensure continuous +/// flow from each grid cell to an outlet located along the grid edge. The `FillDepressions` algorithm is based on +/// the computationally efficient approach of examining each cell based on its spill elevation, starting from the +/// edge cells, and visiting cells from lowest order using a priority queue. As such, it is based on the algorithm +/// first proposed by Wang and Liu (2006). It is currently the most efficient depression-removal algorithm available +/// in WhiteboxTools, although it is not significantly more efficient than the `BreachDepressions` tool, which is +/// known to provide a solution to depression removal with less impact of the DEM. +/// +/// If the input DEM has gaps, or missing-data holes, that contain NoData values, it is better to use the +/// `FillMissingData` tool to repair these gaps. This tool will interpolate values across the gaps and produce +/// a more natural-looking surface than the flat areas that are produced by depression filling. Importantly, the +/// `FillDepressions` tool algorithm implementation assumes that there are no 'donut hole' NoData gaps within the area +/// of valid data. Any NoData areas along the edge of the grid will simply be ignored and will remain NoData areas in +/// the output image. +/// +/// # Reference +/// Wang, L. and Lui, H. 2006. An efficient method for identifying and filling surface depressions in digital elevation +/// models for hydrologic analysis and modelling. International Journal of Geographical Information Science, 20(2): 193-213. +/// +/// # See Also +/// `BreachDepressions`, `FillMissingData` +pub struct FillDepressionsWangAndLui { + name: String, + description: String, + toolbox: String, + parameters: Vec, + example_usage: String, +} + +impl FillDepressionsWangAndLui { + pub fn new() -> FillDepressionsWangAndLui { + // public constructor + let name = "FillDepressionsWangAndLui".to_string(); + let toolbox = "Hydrological Analysis".to_string(); + let description = "Fills all of the depressions in a DEM. Depression breaching should be preferred in most cases.".to_string(); + + let mut parameters = vec![]; + parameters.push(ToolParameter { + name: "Input DEM File".to_owned(), + flags: vec!["-i".to_owned(), "--dem".to_owned()], + description: "Input raster DEM file.".to_owned(), + parameter_type: ParameterType::ExistingFile(ParameterFileType::Raster), + default_value: None, + optional: false, + }); + + parameters.push(ToolParameter { + name: "Output File".to_owned(), + flags: vec!["-o".to_owned(), "--output".to_owned()], + description: "Output raster file.".to_owned(), + parameter_type: ParameterType::NewFile(ParameterFileType::Raster), + default_value: None, + optional: false, + }); + + parameters.push(ToolParameter { + name: "Fix flat areas?".to_owned(), + flags: vec!["--fix_flats".to_owned()], + description: + "Optional flag indicating whether flat areas should have a small gradient applied." + .to_owned(), + parameter_type: ParameterType::Boolean, + default_value: Some("true".to_string()), + optional: true, + }); + + parameters.push(ToolParameter { + name: "Flat increment value (z units)".to_owned(), + flags: vec!["--flat_increment".to_owned()], + description: "Optional elevation increment applied to flat areas.".to_owned(), + parameter_type: ParameterType::Float, + default_value: None, + optional: true, + }); + + let sep: String = path::MAIN_SEPARATOR.to_string(); + let p = format!("{}", env::current_dir().unwrap().display()); + let e = format!("{}", env::current_exe().unwrap().display()); + let mut short_exe = e + .replace(&p, "") + .replace(".exe", "") + .replace(".", "") + .replace(&sep, ""); + if e.contains(".exe") { + short_exe += ".exe"; + } + let usage = format!( + ">>.*{0} -r={1} -v --wd=\"*path*to*data*\" --dem=DEM.tif -o=output.tif --fix_flats", + short_exe, name + ) + .replace("*", &sep); + + FillDepressionsWangAndLui { + name: name, + description: description, + toolbox: toolbox, + parameters: parameters, + example_usage: usage, + } + } +} + +impl WhiteboxTool for FillDepressionsWangAndLui { + fn get_source_file(&self) -> String { + String::from(file!()) + } + + fn get_tool_name(&self) -> String { + self.name.clone() + } + + fn get_tool_description(&self) -> String { + self.description.clone() + } + + fn get_tool_parameters(&self) -> String { + match serde_json::to_string(&self.parameters) { + Ok(json_str) => return format!("{{\"parameters\":{}}}", json_str), + Err(err) => return format!("{:?}", err), + } + } + + fn get_example_usage(&self) -> String { + self.example_usage.clone() + } + + fn get_toolbox(&self) -> String { + self.toolbox.clone() + } + + fn run<'a>( + &self, + args: Vec, + working_directory: &'a str, + verbose: bool, + ) -> Result<(), Error> { + let mut input_file = String::new(); + let mut output_file = String::new(); + let mut fix_flats = false; + let mut flat_increment = f64::NAN; + + if args.len() == 0 { + return Err(Error::new( + ErrorKind::InvalidInput, + "Tool run with no paramters.", + )); + } + for i in 0..args.len() { + let mut arg = args[i].replace("\"", ""); + arg = arg.replace("\'", ""); + let cmd = arg.split("="); // in case an equals sign was used + let vec = cmd.collect::>(); + let mut keyval = false; + if vec.len() > 1 { + keyval = true; + } + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-i" || flag_val == "-input" || flag_val == "-dem"{ + input_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-o" || flag_val == "-output" { + output_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-fix_flats" { + if vec.len() == 1 || !vec[1].to_string().to_lowercase().contains("false") { + fix_flats = true; + } + } else if flag_val == "-flat_increment" { + flat_increment = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; + } + } + + if verbose { + println!("***************{}", "*".repeat(self.get_tool_name().len())); + println!("* Welcome to {} *", self.get_tool_name()); + println!("***************{}", "*".repeat(self.get_tool_name().len())); + } + + let sep: String = path::MAIN_SEPARATOR.to_string(); + + let mut progress: usize; + let mut old_progress: usize = 1; + + if !input_file.contains(&sep) && !input_file.contains("/") { + input_file = format!("{}{}", working_directory, input_file); + } + if !output_file.contains(&sep) && !output_file.contains("/") { + output_file = format!("{}{}", working_directory, output_file); + } + + if verbose { + println!("Reading data...") + }; + + let input = Raster::new(&input_file, "r")?; + + let start = Instant::now(); + let rows = input.configs.rows as isize; + let columns = input.configs.columns as isize; + let num_cells = rows * columns; + let nodata = input.configs.nodata; + + // let min_val = input.configs.minimum; + // let elev_digits = ((input.configs.maximum - min_val) as i64).to_string().len(); + // let elev_multiplier = 10.0_f64.powi((7 - elev_digits) as i32); + // let mut small_num = 0.0; + // if fix_flats { + // small_num = 1.0 / elev_multiplier as f64; + // } + + let small_num = if fix_flats && !flat_increment.is_nan() { + flat_increment + } else if fix_flats { + let min_val = input.configs.minimum; + let elev_digits = ((input.configs.maximum - min_val) as i64).to_string().len(); + let elev_multiplier = 10.0_f64.powi((6 - elev_digits) as i32); + 1.0_f64 / elev_multiplier as f64 + } else { + 0f64 + }; + + let mut output = Raster::initialize_using_file(&output_file, &input); + output.configs.data_type = DataType::F64; + let background_val = (i32::min_value() + 1) as f64; + output.reinitialize_values(background_val); + + /* + Find the data edges. This is complicated by the fact that DEMs frequently + have nodata edges, whereby the DEM does not occupy the full extent of + the raster. One approach to doing this would be simply to scan the + raster, looking for cells that neighbour nodata values. However, this + assumes that there are no interior nodata holes in the dataset. Instead, + the approach used here is to perform a region-growing operation, looking + for nodata values along the raster's edges. + */ + + let mut queue: VecDeque<(isize, isize)> = + VecDeque::with_capacity((rows * columns) as usize); + for row in 0..rows { + /* + Note that this is only possible because Whitebox rasters + allow you to address cells beyond the raster extent but + return the nodata value for these regions. + */ + queue.push_back((row, -1)); + queue.push_back((row, columns)); + } + + for col in 0..columns { + queue.push_back((-1, col)); + queue.push_back((rows, col)); + } + + /* + minheap is the priority queue. Note that I've tested using integer-based + priority values, by multiplying the elevations, but this didn't result + in a significant performance gain over the use of f64s. + */ + let mut minheap = BinaryHeap::with_capacity((rows * columns) as usize); + let mut num_solved_cells = 0; + let mut zin_n: f64; // value of neighbour of row, col in input raster + let mut zout: f64; // value of row, col in output raster + let mut zout_n: f64; // value of neighbour of row, col in output raster + let dx = [1, 1, 1, 0, -1, -1, -1, 0]; + let dy = [-1, 0, 1, 1, 1, 0, -1, -1]; + let (mut row, mut col): (isize, isize); + let (mut row_n, mut col_n): (isize, isize); + while !queue.is_empty() { + let cell = queue.pop_front().unwrap(); + row = cell.0; + col = cell.1; + for n in 0..8 { + row_n = row + dy[n]; + col_n = col + dx[n]; + zin_n = input.get_value(row_n, col_n); + zout_n = output[(row_n, col_n)]; + if zout_n == background_val { + if zin_n == nodata { + output.set_value(row_n, col_n, nodata); + queue.push_back((row_n, col_n)); + } else { + output[(row_n, col_n)] = zin_n; + // Push it onto the priority queue for the priority flood operation + minheap.push(GridCell { + row: row_n, + column: col_n, + priority: zin_n, + }); + } + num_solved_cells += 1; + } + } + + if verbose { + progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; + if progress != old_progress { + println!("progress: {}%", progress); + old_progress = progress; + } + } + } + + /* + The following code follows the scenario of a priority-flood method without the extra + complication of an embedded region-growing operation for in-depression sites. + */ + + // Perform the priority flood operation. + while !minheap.is_empty() { + let cell = minheap.pop().unwrap(); + row = cell.row; + col = cell.column; + zout = output[(row, col)]; + for n in 0..8 { + row_n = row + dy[n]; + col_n = col + dx[n]; + zout_n = output[(row_n, col_n)]; + if zout_n == background_val { + zin_n = input[(row_n, col_n)]; + if zin_n != nodata { + if zin_n < (zout + small_num) { + zin_n = zout + small_num; + } // We're in a depression. Raise the elevation. + output[(row_n, col_n)] = zin_n; + minheap.push(GridCell { + row: row_n, + column: col_n, + priority: zin_n, + }); + } else { + // Interior nodata cells are still treated as nodata and are not filled. + output[(row_n, col_n)] = nodata; + num_solved_cells += 1; + } + } + } + + if verbose { + num_solved_cells += 1; + progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; + if progress != old_progress { + println!("Progress: {}%", progress); + old_progress = progress; + } + } + } + + /* + This code uses a slightly more complex priority flood approach that uses an embedded + region-growing operation for cells within depressions. It offers a slight speed up + over the traditional approach, but I have noticed that sometimes it doesn't work + as expected. I'm not sure why and it would require some effort to track down the bug. + Given that most DEMs have relatively few cells within depressions, the speed up of + this approach is perhaps not worthwhile and it is certainly more complex. + */ + // // Perform the priority flood operation. + // while !minheap.is_empty() { + // let cell = minheap.pop().unwrap(); + // row = cell.row; + // col = cell.column; + // zout = output[(row, col)]; + // for n in 0..8 { + // row_n = row + dy[n]; + // col_n = col + dx[n]; + // zout_n = output[(row_n, col_n)]; + // if zout_n == background_val { + // zin_n = input[(row_n, col_n)]; + // if zin_n != nodata { + // if zin_n < (zout + small_num) { + // // We're in a depression. Raise the elevation. + // zout_n = zout + small_num; + // output[(row_n, col_n)] = zout_n; + // /* + // Cells that are in the depression don't need to be discovered by + // the more expensive priority-flood operation. Instead, perform + // an efficient region-growing operation to find cells connected + // to this cell that have elevations in the input DEM that are + // less than the adjusted zout_n. + // */ + // queue.push_back((row_n, col_n)); + // while !queue.is_empty() { + // let cell = queue.pop_front().unwrap(); + // row = cell.0; + // col = cell.1; + // zout = output[(row, col)]; + // for n2 in 0..8 { + // row_n = row + dy[n2]; + // col_n = col + dx[n2]; + // zout_n = output[(row_n, col_n)]; + // if zout_n == background_val { + // zin_n = input[(row_n, col_n)]; + // if zin_n != nodata { + // if zin_n < (zout + small_num) { + // zout_n = zout + small_num; + // output[(row_n, col_n)] = zout_n; + // queue.push_back((row_n, col_n)); + // } else { + // minheap.push(GridCell{ row: row_n, column: col_n, priority: zin_n }); + // output[(row_n, col_n)] = zin_n; + // } + // } else { + // // Interior nodata cells are still treated as nodata and are not filled. + // output[(row_n, col_n)] = nodata; + // num_solved_cells += 1; + // } + // } + // } + // if verbose { + // num_solved_cells += 1; + // progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; + // if progress != old_progress { + // println!("Progress: {}%", progress); + // old_progress = progress; + // } + // } + // } + // } else { + // minheap.push(GridCell{ row: row_n, column: col_n, priority: zin_n }); + // output[(row_n, col_n)] = zin_n; + // } + // } else { + // // Interior nodata cells are still treated as nodata and are not filled. + // output[(row_n, col_n)] = nodata; + // num_solved_cells += 1; + // } + // } + // } + + // if verbose { + // num_solved_cells += 1; + // progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; + // if progress != old_progress { + // println!("Progress: {}%", progress); + // old_progress = progress; + // } + // } + // } + + /* This was an experiement with an approach that reduced the reliance on the priority queue. + It works well but is slightly less efficient that the traditional approach. */ + + // let mut output = Raster::initialize_using_file(&output_file, &input); + // let background_val = (i32::max_value() - 1) as f64; + // output.reinitialize_values(background_val); + + // /* + // Find the data edges. This is complicated by the fact that DEMs frequently + // have nodata edges, whereby the DEM does not occupy the full extent of + // the raster. One approach to doing this would be simply to scan the + // raster, looking for cells that neighbour nodata values. However, this + // assumes that there are no interior nodata holes in the dataset. Instead, + // the approach used here is to perform a region-growing operation, looking + // for nodata values along the raster's edges. + // */ + // //let mut stack = Vec::with_capacity((rows * columns) as usize); + // let mut queue: VecDeque<(isize, isize)> = VecDeque::with_capacity((rows * columns) as usize); + // for row in 0..rows { + // /* + // Note that this is only possible because Whitebox rasters + // allow you to address cells beyond the raster extent but + // return the nodata value for these regions. + // */ + // queue.push_back((row, -1)); + // queue.push_back((row, columns)); + // } + + // for col in 0..columns { + // queue.push_back((-1, col)); + // queue.push_back((rows, col)); + // } + + // /* + // minheap is the priority queue. Note that I've tested using integer-based + // priority values, by multiplying the elevations, but this didn't result + // in a significant performance gain over the use of f64s. + // */ + // let mut minheap = BinaryHeap::with_capacity((rows * columns) as usize); + // let mut num_solved_cells = 0; + // let mut zin_n: f64; // value of neighbour of row, col in input raster + // let mut zout: f64; // value of row, col in output raster + // let mut zout_n: f64; // value of neighbour of row, col in output raster + // let dx = [ 1, 1, 1, 0, -1, -1, -1, 0 ]; + // let dy = [ -1, 0, 1, 1, 1, 0, -1, -1 ]; + // let (mut row, mut col): (isize, isize); + // let (mut row_n, mut col_n): (isize, isize); + // while !queue.is_empty() { + // let cell = queue.pop_front().unwrap(); + // row = cell.0; + // col = cell.1; + // for n in 0..8 { + // row_n = row + dy[n]; + // col_n = col + dx[n]; + // zin_n = input[(row_n, col_n)]; + // zout_n = output[(row_n, col_n)]; + // if zout_n == background_val { + // if zin_n == nodata { + // output[(row_n, col_n)] = nodata; + // queue.push_back((row_n, col_n)); + // } else { + // output[(row_n, col_n)] = zin_n; + // // Push it onto the priority queue for the priority flood operation + // minheap.push(GridCell{ row: row_n, column: col_n, priority: zin_n }); + // } + // num_solved_cells += 1; + // } + // } + + // if verbose { + // progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; + // if progress != old_progress { + // println!("progress: {}%", progress); + // old_progress = progress; + // } + // } + // } + + // // Perform the priority flood operation. + // // let initial_heap_size = minheap.len(); + // // num_solved_cells = 0; + // while !minheap.is_empty() { + // let cell = minheap.pop().unwrap(); + // queue.push_back((cell.row, cell.column)); + // while !queue.is_empty() { + // let cell = queue.pop_front().unwrap(); + // row = cell.0; + // col = cell.1; + // zout = output[(row, col)]; + // for n in 0..8 { + // row_n = row + dy[n]; + // col_n = col + dx[n]; + // zout_n = output[(row_n, col_n)]; + // zin_n = input[(row_n, col_n)]; + // if zout_n > zin_n { + // if zin_n != nodata { + // if zin_n > zout + small_num { + // output[(row_n, col_n)] = zin_n; + // queue.push_back((row_n, col_n)); + // num_solved_cells += 1; + // } else if zout + small_num < zout_n { + // output[(row_n, col_n)] = zout + small_num; + // queue.push_back((row_n, col_n)); + // } + // } else { + // output[(row_n, col_n)] = nodata; + // queue.push_back((row_n, col_n)); + // num_solved_cells += 1; + // } + // } + // } + + // if verbose { + // progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; + // if progress != old_progress { + // println!("Progress: {}%", progress); + // old_progress = progress; + // } + // } + // } + // } + + // if verbose { println!("Progress: 100%"); } + + let elapsed_time = get_formatted_elapsed_time(start); + output.configs.display_min = input.configs.display_min; + output.configs.display_max = input.configs.display_max; + output.add_metadata_entry(format!( + "Created by whitebox_tools\' {} tool", + self.get_tool_name() + )); + output.add_metadata_entry(format!("Input file: {}", input_file)); + output.add_metadata_entry(format!("Fix flats: {}", fix_flats)); + if fix_flats { + output.add_metadata_entry(format!("Flat increment value: {}", small_num)); + } + output.add_metadata_entry(format!("Elapsed Time (excluding I/O): {}", elapsed_time)); + + if verbose { + println!("Saving data...") + }; + let _ = match output.write() { + Ok(_) => { + if verbose { + println!("Output file written") + } + } + Err(e) => return Err(e), + }; + if verbose { + println!( + "{}", + &format!("Elapsed Time (excluding I/O): {}", elapsed_time) + ); + } + + Ok(()) + } +} + +#[derive(PartialEq, Debug)] +struct GridCell { + row: isize, + column: isize, + priority: f64, +} + +impl Eq for GridCell {} + +impl PartialOrd for GridCell { + fn partial_cmp(&self, other: &Self) -> Option { + other.priority.partial_cmp(&self.priority) + } +} + +impl Ord for GridCell { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() + } +} diff --git a/src/tools/hydro_analysis/fill_pits.rs b/src/tools/hydro_analysis/fill_pits.rs index 334d439a0..ca23fb79a 100644 --- a/src/tools/hydro_analysis/fill_pits.rs +++ b/src/tools/hydro_analysis/fill_pits.rs @@ -128,7 +128,7 @@ impl WhiteboxTool for FillSingleCellPits { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -206,7 +206,7 @@ impl WhiteboxTool for FillSingleCellPits { flag = true; min_zn = f64::INFINITY; for n in 0..8 { - zn = input[(row + dy[n], col + dx[n])]; + zn = input.get_value(row + dy[n], col + dx[n]); if zn < min_zn { min_zn = zn; } diff --git a/src/tools/hydro_analysis/find_noflow_cells.rs b/src/tools/hydro_analysis/find_noflow_cells.rs index 6c9cba633..cac87177d 100644 --- a/src/tools/hydro_analysis/find_noflow_cells.rs +++ b/src/tools/hydro_analysis/find_noflow_cells.rs @@ -127,7 +127,7 @@ impl WhiteboxTool for FindNoFlowCells { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -198,11 +198,11 @@ impl WhiteboxTool for FindNoFlowCells { for row in (0..rows).filter(|r| r % num_procs == tid) { let mut data = vec![nodata; columns as usize]; for col in 0..columns { - z = input[(row, col)]; + z = input.get_value(row, col); if z != nodata { has_no_lower_neighbour = 1.0; for n in 0..8 { - zn = input[(row + dy[n], col + dx[n])]; + zn = input.get_value(row + dy[n], col + dx[n]); if zn < z && zn != nodata { has_no_lower_neighbour = nodata; break; diff --git a/src/tools/hydro_analysis/find_parallel_flow.rs b/src/tools/hydro_analysis/find_parallel_flow.rs index 5728f2f1a..925990a34 100644 --- a/src/tools/hydro_analysis/find_parallel_flow.rs +++ b/src/tools/hydro_analysis/find_parallel_flow.rs @@ -138,7 +138,7 @@ impl WhiteboxTool for FindParallelFlow { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/flatten_lakes.rs b/src/tools/hydro_analysis/flatten_lakes.rs index 0d2285e01..0d2d283b1 100644 --- a/src/tools/hydro_analysis/flatten_lakes.rs +++ b/src/tools/hydro_analysis/flatten_lakes.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for FlattenLakes { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/flood_order.rs b/src/tools/hydro_analysis/flood_order.rs index 7cb0682b5..4d76b7cc9 100644 --- a/src/tools/hydro_analysis/flood_order.rs +++ b/src/tools/hydro_analysis/flood_order.rs @@ -137,7 +137,7 @@ impl WhiteboxTool for FloodOrder { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/flow_accum_full_workflow.rs b/src/tools/hydro_analysis/flow_accum_full_workflow.rs index f465b4961..70d3e017d 100644 --- a/src/tools/hydro_analysis/flow_accum_full_workflow.rs +++ b/src/tools/hydro_analysis/flow_accum_full_workflow.rs @@ -25,7 +25,8 @@ use std::sync::mpsc; use std::sync::Arc; use std::thread; -/// Resolves all of the depressions in a DEM, outputting a breached DEM, an aspect-aligned non-divergent flow pointer, and a flow accumulation raster. +/// Resolves all of the depressions in a DEM, outputting a breached DEM, an aspect-aligned non-divergent flow +/// pointer, and a flow accumulation raster. pub struct FlowAccumulationFullWorkflow { name: String, description: String, @@ -187,7 +188,7 @@ impl WhiteboxTool for FlowAccumulationFullWorkflow { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -367,9 +368,8 @@ impl WhiteboxTool for FlowAccumulationFullWorkflow { } } - let min_val = input.configs.minimum; - let elev_digits = ((input.configs.maximum - min_val) as i64).to_string().len(); - let elev_multiplier = 10.0_f64.powi((7 - elev_digits) as i32); + let elev_digits = (input.configs.maximum as i64).to_string().len(); + let elev_multiplier = 10.0_f64.powi((12 - elev_digits) as i32); let small_num = 1.0 / elev_multiplier as f64; let mut output = Raster::initialize_using_file(&outdem_file, &input); diff --git a/src/tools/hydro_analysis/flow_length_diff.rs b/src/tools/hydro_analysis/flow_length_diff.rs index 02bf9fd1e..788801e33 100644 --- a/src/tools/hydro_analysis/flow_length_diff.rs +++ b/src/tools/hydro_analysis/flow_length_diff.rs @@ -130,7 +130,7 @@ impl WhiteboxTool for FlowLengthDiff { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/hillslopes.rs b/src/tools/hydro_analysis/hillslopes.rs index 932ffaab4..aca4720f5 100644 --- a/src/tools/hydro_analysis/hillslopes.rs +++ b/src/tools/hydro_analysis/hillslopes.rs @@ -153,7 +153,7 @@ impl WhiteboxTool for Hillslopes { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/impoundment_index.rs b/src/tools/hydro_analysis/impoundment_index.rs index a2ee32167..ce3d6c58c 100644 --- a/src/tools/hydro_analysis/impoundment_index.rs +++ b/src/tools/hydro_analysis/impoundment_index.rs @@ -163,7 +163,7 @@ impl WhiteboxTool for ImpoundmentSizeIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/isobasins.rs b/src/tools/hydro_analysis/isobasins.rs index fbe13f34b..c09b566bb 100644 --- a/src/tools/hydro_analysis/isobasins.rs +++ b/src/tools/hydro_analysis/isobasins.rs @@ -140,7 +140,7 @@ impl WhiteboxTool for Isobasins { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/jenson_snap_pour_points.rs b/src/tools/hydro_analysis/jenson_snap_pour_points.rs index 66f657985..00a66e09d 100644 --- a/src/tools/hydro_analysis/jenson_snap_pour_points.rs +++ b/src/tools/hydro_analysis/jenson_snap_pour_points.rs @@ -163,7 +163,7 @@ impl WhiteboxTool for JensonSnapPourPoints { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/longest_flowpath.rs b/src/tools/hydro_analysis/longest_flowpath.rs index c04e40d11..1934f44f5 100644 --- a/src/tools/hydro_analysis/longest_flowpath.rs +++ b/src/tools/hydro_analysis/longest_flowpath.rs @@ -160,7 +160,7 @@ impl WhiteboxTool for LongestFlowpath { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/max_upslope_flowpath.rs b/src/tools/hydro_analysis/max_upslope_flowpath.rs index 7a669480f..a26cf18e3 100644 --- a/src/tools/hydro_analysis/max_upslope_flowpath.rs +++ b/src/tools/hydro_analysis/max_upslope_flowpath.rs @@ -128,7 +128,7 @@ impl WhiteboxTool for MaxUpslopeFlowpathLength { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/mod.rs b/src/tools/hydro_analysis/mod.rs index 60b7489a2..09c7bddb5 100644 --- a/src/tools/hydro_analysis/mod.rs +++ b/src/tools/hydro_analysis/mod.rs @@ -3,6 +3,7 @@ mod average_flowpath_slope; mod average_upslope_flowpath_length; mod basins; mod breach_depressions; +mod breach_depressions_least_cost; mod breach_pits; mod burn_streams_at_roads; mod d8_flow_accum; @@ -20,6 +21,7 @@ mod fd8_flow_accum; mod fd8_pointer; mod fill_burn; mod fill_depressions; +mod fill_depressions_wang_and_lui; mod fill_pits; mod find_noflow_cells; mod find_parallel_flow; @@ -43,6 +45,7 @@ mod strahler_basins; mod subbasins; mod trace_downslope_flowpaths; mod unnest_basins; +mod upslope_depression_storage; mod watershed; // exports identifiers from private sub-modules in the current module namespace @@ -67,6 +70,7 @@ pub use self::fd8_flow_accum::FD8FlowAccumulation; pub use self::fd8_pointer::FD8Pointer; pub use self::fill_burn::FillBurn; pub use self::fill_depressions::FillDepressions; +pub use self::fill_depressions_wang_and_lui::FillDepressionsWangAndLui; pub use self::fill_pits::FillSingleCellPits; pub use self::find_noflow_cells::FindNoFlowCells; pub use self::find_parallel_flow::FindParallelFlow; @@ -78,6 +82,7 @@ pub use self::hillslopes::Hillslopes; pub use self::impoundment_index::ImpoundmentSizeIndex; pub use self::isobasins::Isobasins; pub use self::jenson_snap_pour_points::JensonSnapPourPoints; +pub use self::breach_depressions_least_cost::BreachDepressionsLeastCost; pub use self::longest_flowpath::LongestFlowpath; pub use self::max_upslope_flowpath::MaxUpslopeFlowpathLength; pub use self::num_inflowing_neighbours::NumInflowingNeighbours; @@ -90,4 +95,5 @@ pub use self::strahler_basins::StrahlerOrderBasins; pub use self::subbasins::Subbasins; pub use self::trace_downslope_flowpaths::TraceDownslopeFlowpaths; pub use self::unnest_basins::UnnestBasins; +pub use self::upslope_depression_storage::UpslopeDepressionStorage; pub use self::watershed::Watershed; diff --git a/src/tools/hydro_analysis/num_inflowing_neighbours.rs b/src/tools/hydro_analysis/num_inflowing_neighbours.rs index 66d50f71c..4e77467cd 100644 --- a/src/tools/hydro_analysis/num_inflowing_neighbours.rs +++ b/src/tools/hydro_analysis/num_inflowing_neighbours.rs @@ -129,7 +129,7 @@ impl WhiteboxTool for NumInflowingNeighbours { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/raise_walls.rs b/src/tools/hydro_analysis/raise_walls.rs index d9c797bda..6fb600603 100644 --- a/src/tools/hydro_analysis/raise_walls.rs +++ b/src/tools/hydro_analysis/raise_walls.rs @@ -154,7 +154,7 @@ impl WhiteboxTool for RaiseWalls { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/rho8_pointer.rs b/src/tools/hydro_analysis/rho8_pointer.rs index 5522b71ab..6a95ced0c 100644 --- a/src/tools/hydro_analysis/rho8_pointer.rs +++ b/src/tools/hydro_analysis/rho8_pointer.rs @@ -157,7 +157,7 @@ impl WhiteboxTool for Rho8Pointer { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/sink.rs b/src/tools/hydro_analysis/sink.rs index bf2528ac2..c17c9df38 100644 --- a/src/tools/hydro_analysis/sink.rs +++ b/src/tools/hydro_analysis/sink.rs @@ -2,21 +2,27 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay Created: 01/07/2017 -Last Modified: 18/10/2019 +Last Modified: 05/12/2019 License: MIT + +Note: the previous iteration of this tool did not include the outlet cell itself as part of the depression. +This iteration of the tool does include outlets. */ use crate::raster::*; -use crate::structures::Array2D; use crate::tools::*; +use crate::structures::Array2D; use std::cmp::Ordering; -use std::collections::BinaryHeap; -use std::collections::VecDeque; +use std::cmp::Ordering::Equal; +use std::collections::{BinaryHeap, VecDeque}; use std::env; use std::f64; use std::i32; use std::io::{Error, ErrorKind}; use std::path; +use std::sync::mpsc; +use std::sync::Arc; +use std::thread; /// This tool identifies each sink (i.e. topographic depression) in a raster digital elevation model (DEM). A /// sink, or depression, is a bowl-like landscape feature, which is characterized by interior drainage. Each @@ -46,7 +52,7 @@ impl Sink { let mut parameters = vec![]; parameters.push(ToolParameter { name: "Input DEM File".to_owned(), - flags: vec!["-i".to_owned(), "--dem".to_owned()], + flags: vec!["-i".to_owned(), "--dem".to_owned(), "--input".to_owned()], description: "Input raster DEM file.".to_owned(), parameter_type: ParameterType::ExistingFile(ParameterFileType::Raster), default_value: None, @@ -83,7 +89,7 @@ impl Sink { if e.contains(".exe") { short_exe += ".exe"; } - let usage = format!(">>.*{0} -r={1} -v --wd=\"*path*to*data*\" --dem=DEM.tif -o=output.tif --zero_background", short_exe, name).replace("*", &sep); + let usage = format!(">>.*{0} -r={1} -v --wd=\"*path*to*data*\" --dem=DEM.tif -o=filled_dem.tif --zero_background", short_exe, name).replace("*", &sep); Sink { name: name, @@ -136,7 +142,7 @@ impl WhiteboxTool for Sink { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -195,179 +201,241 @@ impl WhiteboxTool for Sink { let start = Instant::now(); let rows = input.configs.rows as isize; let columns = input.configs.columns as isize; - let num_cells = rows * columns; let nodata = input.configs.nodata; - let mut output = Raster::initialize_using_file(&output_file, &input); - let mut background_val = (i32::min_value() + 1) as f64; - output.reinitialize_values(background_val); - - /* - Find the data edges. This is complicated by the fact that DEMs frequently - have nodata edges, whereby the DEM does not occupy the full extent of - the raster. One approach to doing this would be simply to scan the - raster, looking for cells that neighbour nodata values. However, this - assumes that there are no interior nodata holes in the dataset. Instead, - the approach used here is to perform a region-growing operation, looking - for nodata values along the raster's edges. - */ - - let mut queue: VecDeque<(isize, isize)> = - VecDeque::with_capacity((rows * columns) as usize); - for row in 0..rows { - /* - Note that this is only possible because Whitebox rasters - allow you to address cells beyond the raster extent but - return the nodata value for these regions. - */ - queue.push_back((row, -1)); - queue.push_back((row, columns)); - } - - for col in 0..columns { - queue.push_back((-1, col)); - queue.push_back((rows, col)); - } + let mut filled_dem = input.get_data_as_array2d(); - /* - minheap is the priority queue. Note that I've tested using integer-based - priority values, by multiplying the elevations, but this didn't result - in a significant performance gain over the use of f64s. - */ - let mut minheap = BinaryHeap::with_capacity((rows * columns) as usize); - let mut num_solved_cells = 0; - let mut zin_n: f64; // value of neighbour of row, col in input raster - let mut zout: f64; // value of row, col in output raster - let mut zout_n: f64; // value of neighbour of row, col in output raster + let (mut col, mut row): (isize, isize); + let (mut rn, mut cn): (isize, isize); + let (mut z, mut zn): (f64, f64); let dx = [1, 1, 1, 0, -1, -1, -1, 0]; let dy = [-1, 0, 1, 1, 1, 0, -1, -1]; - let (mut row, mut col): (isize, isize); - let (mut row_n, mut col_n): (isize, isize); - while !queue.is_empty() { - let cell = queue.pop_front().unwrap(); - row = cell.0; - col = cell.1; - for n in 0..8 { - row_n = row + dy[n]; - col_n = col + dx[n]; - zin_n = input[(row_n, col_n)]; - zout_n = output[(row_n, col_n)]; - if zout_n == background_val { - if zin_n == nodata { - output[(row_n, col_n)] = nodata; - queue.push_back((row_n, col_n)); - } else { - output[(row_n, col_n)] = zin_n; - // Push it onto the priority queue for the priority flood operation - minheap.push(GridCell { - row: row_n, - column: col_n, - priority: zin_n, - }); + + // Find pit cells. This step is parallelized. + let num_procs = num_cpus::get() as isize; + let filled_dem2 = Arc::new(filled_dem); + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let filled_dem2 = filled_dem2.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let mut z: f64; + let mut zn: f64; + let mut flag: bool; + let mut pits = vec![]; + for row in (1..rows-1).filter(|r| r % num_procs == tid) { + for col in 1..columns-1 { + z = filled_dem2.get_value(row, col); + if z != nodata { + flag = true; + for n in 0..8 { + zn = filled_dem2.get_value(row + dy[n], col + dx[n]); + if zn < z || zn == nodata { // It either has a lower neighbour or is an edge cell. + flag = false; + break; + } + } + if flag { // it's a cell with undefined flow + pits.push((row, col, z)); + } + } } - num_solved_cells += 1; } - } + tx.send(pits).unwrap(); + }); + } + let mut undefined_flow_cells = vec![]; + for p in 0..num_procs { + let mut pits = rx.recv().unwrap(); + undefined_flow_cells.append(&mut pits); + if verbose { - progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; + progress = (100.0_f64 * (p + 1) as f64 / num_procs as f64) as usize; if progress != old_progress { - println!("progress: {}%", progress); + println!("Finding pit cells: {}%", progress); old_progress = progress; } } } - // Perform the priority flood operation. - while !minheap.is_empty() { - let cell = minheap.pop().unwrap(); - row = cell.row; - col = cell.column; - zout = output[(row, col)]; - for n in 0..8 { - row_n = row + dy[n]; - col_n = col + dx[n]; - zout_n = output[(row_n, col_n)]; - if zout_n == background_val { - zin_n = input[(row_n, col_n)]; - if zin_n != nodata { - if zin_n < zout { - zin_n = zout; - } // We're in a depression. Raise the elevation. - output[(row_n, col_n)] = zin_n; - minheap.push(GridCell { - row: row_n, - column: col_n, - priority: zin_n, - }); + let mut input_configs = input.configs.clone(); + drop(input); + + filled_dem = match Arc::try_unwrap(filled_dem2) { + Ok(val) => val, + Err(_) => panic!("Error unwrapping 'filled_dem'"), + }; + + let num_deps = undefined_flow_cells.len(); + + // Now we need to perform an in-place depression filling + let mut minheap = BinaryHeap::new(); + let mut visited: Array2D = Array2D::new(rows, columns, 0, -1)?; + let mut flats: Array2D = Array2D::new(rows, columns, 0, -1)?; + let mut possible_outlets = vec![]; + // solve from highest to lowest + undefined_flow_cells.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(Equal)); + let mut pit_id = 1; + let mut flag: bool; + while let Some(cell) = undefined_flow_cells.pop() { + row = cell.0; + col = cell.1; + if flats.get_value(row, col) != 1 { // if it's already in a solved site, don't do it a second time. + // First there is a priority region-growing operation to find the outlets. + z = filled_dem.get_value(row, col); + minheap.clear(); + minheap.push(GridCell { + row: row, + column: col, + priority: z, + }); + visited.set_value(row, col, 1); + let mut outlet_found = false; + let mut outlet_z = f64::INFINITY; + let mut queue = VecDeque::new(); + while let Some(cell2) = minheap.pop() { + z = cell2.priority; + if outlet_found && z > outlet_z { + break; + } + if !outlet_found { + for n in 0..8 { + cn = cell2.column + dx[n]; + rn = cell2.row + dy[n]; + if visited.get_value(rn, cn) == 0 { + zn = filled_dem.get_value(rn, cn); + if !outlet_found { + if zn >= z && zn != nodata { + minheap.push(GridCell { + row: rn, + column: cn, + priority: zn, + }); + visited.set_value(rn, cn, 1); + } else if zn != nodata { // zn < z + // 'cell' has a lower neighbour that hasn't already passed through minheap. + // Therefore, 'cell' is a pour point cell. + outlet_found = true; + outlet_z = z; + queue.push_back((cell2.row, cell2.column)); + possible_outlets.push((cell2.row, cell2.column)); + } + } else if zn == outlet_z { // We've found the outlet but are still looking for additional outlets. + minheap.push(GridCell { + row: rn, + column: cn, + priority: zn, + }); + visited.set_value(rn, cn, 1); + } + } + } } else { - // Interior nodata cells are still treated as nodata and are not filled. - output[(row_n, col_n)] = nodata; - num_solved_cells += 1; + if z == outlet_z { + flag = false; + for n in 0..8 { + cn = cell2.column + dx[n]; + rn = cell2.row + dy[n]; + if visited.get_value(rn, cn) == 0 { + zn = filled_dem.get_value(rn, cn); + if zn < z { + flag = true; + } else if zn == outlet_z { + minheap.push(GridCell { + row: rn, + column: cn, + priority: zn, + }); + visited.set_value(rn, cn, 1); + } + } + } + if flag { // it's an outlet + queue.push_back((cell2.row, cell2.column)); + possible_outlets.push((cell2.row, cell2.column)); + } else { + visited.set_value(cell2.row, cell2.column, 1); + } + } + } + } + + // Now that we have the outlets, raise the interior of the depression + if outlet_found { + while let Some(cell2) = queue.pop_front() { + for n in 0..8 { + rn = cell2.0 + dy[n]; + cn = cell2.1 + dx[n]; + if visited.get_value(rn, cn) == 1 { + visited.set_value(rn, cn, 0); + queue.push_back((rn, cn)); + z = filled_dem.get_value(rn, cn); + if z < outlet_z { + filled_dem.set_value(rn, cn, outlet_z); + flats.set_value(rn, cn, 1); + } else if z == outlet_z { + flats.set_value(rn, cn, 1); + } + } + } } } } if verbose { - num_solved_cells += 1; - progress = (100.0_f64 * num_solved_cells as f64 / (num_cells - 1) as f64) as usize; + progress = (100.0_f64 * pit_id as f64 / num_deps as f64) as usize; if progress != old_progress { - println!("Progress: {}%", progress); + println!("Finding depressions: {}%", progress); old_progress = progress; } } + pit_id += 1; } - // Reclassify the output such that all cells that are higher than the input are identified. - let mut fid = 0f64; - background_val = nodata; + drop(visited); + + input_configs.nodata = i32::MIN as f64; + let mut output = Raster::initialize_using_config(&output_file, &input_configs); if zero_background { - background_val = 0f64; + output.reinitialize_values(0f64); } - let mut visited: Array2D = Array2D::new(rows, columns, 0, -1)?; - for row in 0..rows { - for col in 0..columns { - if output[(row, col)] > input[(row, col)] && visited[(row, col)] != 1 { - fid += 1f64; - output[(row, col)] = fid; - visited[(row, col)] = 1; - queue.push_back((row, col)); - while !queue.is_empty() { - let cell = queue.pop_front().unwrap(); - for n in 0..8 { - row_n = cell.0 + dy[n]; - col_n = cell.1 + dx[n]; - zout_n = output[(row_n, col_n)]; - zin_n = input[(row_n, col_n)]; - if zout_n > zin_n && visited[(row_n, col_n)] != 1 { - output[(row_n, col_n)] = fid; - visited[(row_n, col_n)] = 1; - queue.push_back((row_n, col_n)); + output.configs.data_type = DataType::I32; + output.configs.photometric_interp = PhotometricInterpretation::Categorical; + let mut dep_id = 1f64; + let num_outlets = possible_outlets.len(); + while let Some(cell) = possible_outlets.pop() { + if flats.get_value(cell.0, cell.1) == 1 { + z = filled_dem.get_value(cell.0, cell.1); + output.set_value(cell.0, cell.1, dep_id); + let mut queue = VecDeque::new(); + flats.set_value(cell.0, cell.1, 0); + queue.push_back((cell.0, cell.1, dep_id)); + while let Some(cell2) = queue.pop_front() { + for n in 0..8 { + rn = cell2.0 + dy[n]; + cn = cell2.1 + dx[n]; + if flats.get_value(rn, cn) == 1 { + if filled_dem.get_value(rn, cn) == z { + flats.set_value(rn, cn, 0); + output.set_value(rn, cn, dep_id); + queue.push_back((rn, cn, dep_id)); } } } - } else if output[(row, col)] == input[(row, col)] { - visited[(row, col)] = 1; - if input[(row, col)] != nodata { - output[(row, col)] = background_val; - } else { - output[(row, col)] = nodata; - } } + dep_id += 1f64; } if verbose { - progress = (100.0_f64 * row as f64 / (rows - 1) as f64) as usize; + progress = (100.0_f64 * (1.0 - possible_outlets.len() as f64 / num_outlets as f64)) as usize; if progress != old_progress { - println!("Clumping: {}%", progress); + println!("Labelling depressions: {}%", progress); old_progress = progress; } } } let elapsed_time = get_formatted_elapsed_time(start); - output.configs.data_type = DataType::F32; - output.configs.palette = "qual.plt".to_string(); - output.configs.photometric_interp = PhotometricInterpretation::Categorical; output.add_metadata_entry(format!( "Created by whitebox_tools\' {} tool", self.get_tool_name() @@ -413,12 +481,7 @@ impl PartialOrd for GridCell { } impl Ord for GridCell { - fn cmp(&self, other: &GridCell) -> Ordering { - let ord = self.partial_cmp(other).unwrap(); - match ord { - Ordering::Greater => Ordering::Less, - Ordering::Less => Ordering::Greater, - Ordering::Equal => ord, - } + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() } } diff --git a/src/tools/hydro_analysis/snap_pour_points.rs b/src/tools/hydro_analysis/snap_pour_points.rs index 023f8e770..144f7b441 100644 --- a/src/tools/hydro_analysis/snap_pour_points.rs +++ b/src/tools/hydro_analysis/snap_pour_points.rs @@ -160,7 +160,7 @@ impl WhiteboxTool for SnapPourPoints { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/stochastic_depression_analysis.rs b/src/tools/hydro_analysis/stochastic_depression_analysis.rs index 633e5acb6..38bffbfea 100644 --- a/src/tools/hydro_analysis/stochastic_depression_analysis.rs +++ b/src/tools/hydro_analysis/stochastic_depression_analysis.rs @@ -202,7 +202,7 @@ impl WhiteboxTool for StochasticDepressionAnalysis { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/strahler_basins.rs b/src/tools/hydro_analysis/strahler_basins.rs index 699c75fea..26508d949 100644 --- a/src/tools/hydro_analysis/strahler_basins.rs +++ b/src/tools/hydro_analysis/strahler_basins.rs @@ -146,7 +146,7 @@ impl WhiteboxTool for StrahlerOrderBasins { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/subbasins.rs b/src/tools/hydro_analysis/subbasins.rs index e38cf6d2b..e97816c05 100644 --- a/src/tools/hydro_analysis/subbasins.rs +++ b/src/tools/hydro_analysis/subbasins.rs @@ -151,7 +151,7 @@ impl WhiteboxTool for Subbasins { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/trace_downslope_flowpaths.rs b/src/tools/hydro_analysis/trace_downslope_flowpaths.rs index 824b7c818..47895522b 100644 --- a/src/tools/hydro_analysis/trace_downslope_flowpaths.rs +++ b/src/tools/hydro_analysis/trace_downslope_flowpaths.rs @@ -167,7 +167,7 @@ impl WhiteboxTool for TraceDownslopeFlowpaths { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/unnest_basins.rs b/src/tools/hydro_analysis/unnest_basins.rs index afe0f5ea7..00b7c9689 100644 --- a/src/tools/hydro_analysis/unnest_basins.rs +++ b/src/tools/hydro_analysis/unnest_basins.rs @@ -151,7 +151,7 @@ impl WhiteboxTool for UnnestBasins { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/hydro_analysis/upslope_depression_storage.rs b/src/tools/hydro_analysis/upslope_depression_storage.rs new file mode 100644 index 000000000..178cd4b2a --- /dev/null +++ b/src/tools/hydro_analysis/upslope_depression_storage.rs @@ -0,0 +1,597 @@ +/* +This tool is part of the WhiteboxTools geospatial analysis library. +Authors: Dr. John Lindsay +Created: 21/11/2019 +Last Modified: 21/11/2019 +License: MIT +*/ + +use crate::raster::*; +use crate::structures::Array2D; +use crate::tools::*; +use num_cpus; +use std::cmp::Ordering; +use std::collections::BinaryHeap; +use std::env; +use std::f64; +use std::f32; +use std::io::{Error, ErrorKind}; +use std::path; +use std::sync::mpsc; +use std::sync::Arc; +use std::thread; + +/// This tool estimates the average upslope depression storage depth using the FD8 flow algorithm. +/// The input DEM (`--dem`) need not be hydrologically corrected; the tool will internally map depression +/// storage and resolve flowpaths using depression filling. This input elevation model should be of a +/// fine resolution (< 2 m), and is ideally derived using LiDAR. The tool calculates the total upslope +/// depth of depression storage, which is divided by the number of upslope cells in the final step +/// of the process, yielding the average upslope depression depth. Roughened surfaces tend to have higher +/// values compared with smoothed surfaces. Values, particularly on hillslopes, may be very small (< 0.01 m). +/// +/// # See Also +/// `FD8FlowAccumulation`, `FillDepressions`, `DepthInSink` +pub struct UpslopeDepressionStorage { + name: String, + description: String, + toolbox: String, + parameters: Vec, + example_usage: String, +} + +impl UpslopeDepressionStorage { + pub fn new() -> UpslopeDepressionStorage { + // public constructor + let name = "UpslopeDepressionStorage".to_string(); + let toolbox = "Hydrological Analysis".to_string(); + let description = "Estimates the average upslope depression storage depth.".to_string(); + + let mut parameters = vec![]; + parameters.push(ToolParameter { + name: "Input DEM File".to_owned(), + flags: vec!["-i".to_owned(), "--dem".to_owned()], + description: "Input raster DEM file.".to_owned(), + parameter_type: ParameterType::ExistingFile(ParameterFileType::Raster), + default_value: None, + optional: false, + }); + + parameters.push(ToolParameter { + name: "Output File".to_owned(), + flags: vec!["-o".to_owned(), "--output".to_owned()], + description: "Output raster file.".to_owned(), + parameter_type: ParameterType::NewFile(ParameterFileType::Raster), + default_value: None, + optional: false, + }); + + let sep: String = path::MAIN_SEPARATOR.to_string(); + let p = format!("{}", env::current_dir().unwrap().display()); + let e = format!("{}", env::current_exe().unwrap().display()); + let mut short_exe = e + .replace(&p, "") + .replace(".exe", "") + .replace(".", "") + .replace(&sep, ""); + if e.contains(".exe") { + short_exe += ".exe"; + } + let usage = format!( + ">>.*{0} -r={1} -v --wd=\"*path*to*data*\" --dem=DEM.tif -o=output.tif", + short_exe, name + ) + .replace("*", &sep); + + UpslopeDepressionStorage { + name: name, + description: description, + toolbox: toolbox, + parameters: parameters, + example_usage: usage, + } + } +} + +impl WhiteboxTool for UpslopeDepressionStorage { + fn get_source_file(&self) -> String { + String::from(file!()) + } + + fn get_tool_name(&self) -> String { + self.name.clone() + } + + fn get_tool_description(&self) -> String { + self.description.clone() + } + + fn get_tool_parameters(&self) -> String { + match serde_json::to_string(&self.parameters) { + Ok(json_str) => return format!("{{\"parameters\":{}}}", json_str), + Err(err) => return format!("{:?}", err), + } + } + + fn get_example_usage(&self) -> String { + self.example_usage.clone() + } + + fn get_toolbox(&self) -> String { + self.toolbox.clone() + } + + fn run<'a>( + &self, + args: Vec, + working_directory: &'a str, + verbose: bool, + ) -> Result<(), Error> { + let mut input_file = String::new(); + let mut output_file = String::new(); + + if args.len() == 0 { + return Err(Error::new( + ErrorKind::InvalidInput, + "Tool run with no parameters.", + )); + } + for i in 0..args.len() { + let mut arg = args[i].replace("\"", ""); + arg = arg.replace("\'", ""); + let cmd = arg.split("="); // in case an equals sign was used + let vec = cmd.collect::>(); + let mut keyval = false; + if vec.len() > 1 { + keyval = true; + } + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-i" || flag_val == "-input" || flag_val == "-dem" { + input_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-o" || flag_val == "-output" { + output_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } + } + + if verbose { + println!("***************{}", "*".repeat(self.get_tool_name().len())); + println!("* Welcome to {} *", self.get_tool_name()); + println!("***************{}", "*".repeat(self.get_tool_name().len())); + } + + let sep: String = path::MAIN_SEPARATOR.to_string(); + + let mut progress: usize; + let mut old_progress: usize = 1; + + if !input_file.contains(&sep) && !input_file.contains("/") { + input_file = format!("{}{}", working_directory, input_file); + } + if !output_file.contains(&sep) && !output_file.contains("/") { + output_file = format!("{}{}", working_directory, output_file); + } + + if verbose { + println!("Reading data...") + }; + + let input = Raster::new(&input_file, "r")?; + + let start = Instant::now(); + + let rows = input.configs.rows as isize; + let columns = input.configs.columns as isize; + // let cell_area = input.configs.resolution_x * input.configs.resolution_y; + let (mut col, mut row): (isize, isize); + let (mut rn, mut cn): (isize, isize); + let mut z: f32; + let mut zn: f32; + let dx = [1, 1, 1, 0, -1, -1, -1, 0]; + let dy = [-1, 0, 1, 1, 1, 0, -1, -1]; + // let back_link = [4i8, 5i8, 6i8, 7i8, 0i8, 1i8, 2i8, 3i8]; + let mut num_solved: usize; + + // let cell_size_x = input.configs.resolution_x as f32; + // let cell_size_y = input.configs.resolution_y as f32; + // let diag_cell_size = (cell_size_x * cell_size_x + cell_size_y * cell_size_y).sqrt(); + // let grid_lengths = [ + // diag_cell_size, + // cell_size_x, + // diag_cell_size, + // cell_size_y, + // diag_cell_size, + // cell_size_x, + // diag_cell_size, + // cell_size_y, + // ]; + + let mut filled = input.get_data_as_f32_array2d(); + let nodata = filled.nodata(); + + let elev_digits = (input.configs.maximum as i64).to_string().len(); + let elev_multiplier = 10.0_f64.powi((6 - elev_digits) as i32); + let small_num = 1.0_f32 / elev_multiplier as f32; + + let mut output = Raster::initialize_using_file(&output_file, &input); + + // drop(input); // input is no longer needed. + + // Now we need to perform an in-place depression filling + + // Start by finding all cells that neighbour NoData cells. + let mut visited: Array2D = Array2D::new(rows, columns, 0, -1)?; + // let mut flow_dir: Array2D = Array2D::new(rows, columns, -1, -1)?; + let mut minheap = BinaryHeap::with_capacity((rows * columns) as usize); + let mut num_cells_visited = 0; + for row in 0..rows { + for col in 0..columns { + z = filled.get_value(row, col); + if z != nodata { + for n in 0..8 { + if filled.get_value(row + dy[n], col + dx[n]) == nodata { + minheap.push(GridCell { + row: row, + column: col, + priority: z, + }); + visited.set_value(row, col, 1); + output.set_value(row, col, 0f64); + num_cells_visited += 1; + break; + } + } + } else { + visited.set_value(row, col, 1); + num_cells_visited += 1; + } + } + if verbose { + progress = (100.0_f64 * row as f64 / (rows - 1) as f64) as usize; + if progress != old_progress { + println!("Finding edges: {}%", progress); + old_progress = progress; + } + } + } + + while !minheap.is_empty() { + let cell = minheap.pop().unwrap(); + row = cell.row; + col = cell.column; + z = filled.get_value(row, col); + for n in 0..8 { + rn = row + dy[n]; + cn = col + dx[n]; + if visited.get_value(rn, cn) == 0i8 { + zn = filled.get_value(rn, cn); + if zn < (z + small_num) { + // output.set_value(rn, cn, (z - zn) as f64); // * cell_area); + if (zn as f64) < (input.get_value(row, col) + output.get_value(row, col)) { + output.set_value(rn, cn, (input.get_value(row, col) + output.get_value(row, col)) - zn as f64); + } else { + output.set_value(rn, cn, 0f64); + } + filled.set_value(rn, cn, z + small_num); + } else { + output.set_value(rn, cn, 0f64); + } + minheap.push(GridCell { + row: rn, + column: cn, + priority: zn, + }); + visited.set_value(rn, cn, 1); + // flow_dir.set_value(rn, cn, back_link[n]); + num_cells_visited += 1; + } + } + if verbose { + progress = (100.0_f64 * num_cells_visited as f64 / (rows*columns - 1) as f64) as usize; + if progress != old_progress { + println!("Filling: {}%", progress); + old_progress = progress; + } + } + } + drop(input); + drop(visited); + + // let mut num_inflowing: Array2D = Array2D::new(rows, columns, -1, -1)?; + // let filled = Arc::new(filled); + // let flow_dir = Arc::new(flow_dir); + // let num_procs = num_cpus::get() as isize; + // let (tx, rx) = mpsc::channel(); + // for tid in 0..num_procs { + // let filled = filled.clone(); + // let flow_dir = flow_dir.clone(); + // let tx = tx.clone(); + // thread::spawn(move || { + // let dx = [1, 1, 1, 0, -1, -1, -1, 0]; + // let dy = [-1, 0, 1, 1, 1, 0, -1, -1]; + // let inflowing_vals: [i8; 8] = [4, 5, 6, 7, 0, 1, 2, 3]; + // let mut count: i8; + // for row in (0..rows).filter(|r| r % num_procs == tid) { + // let mut data: Vec = vec![-1i8; columns as usize]; + // for col in 0..columns { + // if filled.get_value(row, col) != nodata { + // count = 0i8; + // for i in 0..8 { + // if flow_dir.get_value(row + dy[i], col + dx[i]) == inflowing_vals[i] { + // count += 1; + // } + // } + // data[col as usize] = count; + // } else { + // data[col as usize] = -1i8; + // } + // } + // tx.send((row, data)).unwrap(); + // } + // }); + // } + + // let mut stack = Vec::with_capacity((rows * columns) as usize); + // num_solved = 0; + // for r in 0..rows { + // let (row, data) = rx.recv().unwrap(); + // num_inflowing.set_row_data(row, data); + // for col in 0..columns { + // if num_inflowing.get_value(row, col) == 0i8 { + // stack.push((row, col)); + // } else if num_inflowing[(row, col)] == -1i8 { + // num_solved += 1; + // } + // } + + // if verbose { + // progress = (100.0_f64 * r as f64 / (rows - 1) as f64) as usize; + // if progress != old_progress { + // println!("Num. inflowing neighbours: {}%", progress); + // old_progress = progress; + // } + // } + // } + + // let mut area: Array2D = Array2D::new(rows, columns, 1, -1)?; + // let mut fa: f64; + // let mut fa2: i32; + // let mut dir: i8; + // while !stack.is_empty() { + // let cell = stack.pop().unwrap(); + // row = cell.0; + // col = cell.1; + // fa = output.get_value(row, col); + // fa2 = area.get_value(row, col); + // num_inflowing.decrement(row, col, 1i8); + // dir = flow_dir.get_value(row, col); + // if dir >= 0 { + // rn = row + dy[dir as usize]; + // cn = col + dx[dir as usize]; + // output.increment(rn, cn, fa); + // area.increment(rn, cn, fa2); + // num_inflowing.decrement(rn, cn, 1i8); + // if num_inflowing.get_value(rn, cn) == 0i8 { + // stack.push((rn, cn)); + // } + // } + + // if verbose { + // num_solved += 1; + // progress = (100.0_f64 * num_solved as f64 / (rows*columns - 1) as f64) as usize; + // if progress != old_progress { + // println!("Flow accumulation: {}%", progress); + // old_progress = progress; + // } + // } + // } + + // for row in 0..rows { + // for col in 0..columns { + // z = filled.get_value(row, col); + // if z != nodata { + // output.set_value(row, col, output.get_value(row, col) / area.get_value(row, col) as f64); + // } + // } + // if verbose { + // progress = (100.0_f64 * num_cells_visited as f64 / (rows*columns - 1) as f64) as usize; + // if progress != old_progress { + // println!("Final calculation: {}%", progress); + // old_progress = progress; + // } + // } + // } + + + // calculate the number of inflowing cells + let filled = Arc::new(filled); + let mut num_inflowing: Array2D = Array2D::new(rows, columns, -1, -1)?; + let num_procs = num_cpus::get() as isize; + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let filled = filled.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let dx = [1, 1, 1, 0, -1, -1, -1, 0]; + let dy = [-1, 0, 1, 1, 1, 0, -1, -1]; + let mut z: f32; + let mut zn: f32; + let mut count: i8; + for row in (0..rows).filter(|r| r % num_procs == tid) { + let mut data: Vec = vec![-1i8; columns as usize]; + for col in 0..columns { + z = filled.get_value(row, col); + if z != nodata { + count = 0i8; + for n in 0..8 { + zn = filled.get_value(row + dy[n], col + dx[n]); + if zn > z && zn != nodata { + count += 1; + } + } + data[col as usize] = count; + } + } + tx.send((row, data)).unwrap(); + } + }); + } + + let mut stack = Vec::with_capacity((rows * columns) as usize); + num_solved = 0; + for r in 0..rows { + let (row, data) = rx.recv().unwrap(); + num_inflowing.set_row_data(row, data); + for col in 0..columns { + if num_inflowing.get_value(row, col) == 0i8 { + stack.push((row, col)); + } else if num_inflowing.get_value(row, col) == -1i8 { + num_solved += 1; + } + } + + if verbose { + progress = (100.0_f64 * r as f64 / (rows - 1) as f64) as usize; + if progress != old_progress { + println!("Num. inflowing neighbours: {}%", progress); + old_progress = progress; + } + } + } + + let mut fa: f64; + let mut fa2: f64; + let mut area: Array2D = Array2D::new(rows, columns, 1f64, -1f64)?; + // let (mut max_slope, mut slope): (f32, f32); + // let mut dir: i8; + // let convergence_threshold = f64::INFINITY; + let exponent = 1.1f32; + let mut total_weights: f32; + let mut weights: [f32; 8] = [0.0; 8]; + let mut downslope: [bool; 8] = [false; 8]; + while !stack.is_empty() { + let cell = stack.pop().unwrap(); + row = cell.0; + col = cell.1; + z = filled.get_value(row, col); + fa = output.get_value(row, col); + fa2 = area.get_value(row, col); + num_inflowing.set_value(row, col, -1i8); + total_weights = 0.0f32; + for n in 0..8 { + rn = row + dy[n]; + cn = col + dx[n]; + zn = filled.get_value(rn, cn); + if zn < z && zn != nodata { + weights[n] = (z - zn).powf(exponent); + total_weights += weights[n]; + downslope[n] = true; + } else { + weights[n] = 0f32; + downslope[n] = false; + } + } + + if total_weights > 0.0 { + for n in 0..8 { + if downslope[n] { + rn = row + dy[n]; + cn = col + dx[n]; + output.increment(rn, cn, fa * (weights[n] / total_weights) as f64); + area.increment(rn, cn, fa2 * (weights[n] / total_weights) as f64); + num_inflowing.decrement(rn, cn, 1i8); + if num_inflowing.get_value(rn, cn) == 0i8 { + stack.push((rn, cn)); + } + } + } + } + + if verbose { + num_solved += 1; + progress = (100.0_f64 * num_solved as f64 / (rows*columns - 1) as f64) as usize; + if progress != old_progress { + println!("Flow accumulation: {}%", progress); + old_progress = progress; + } + } + } + + for row in 0..rows { + for col in 0..columns { + z = filled.get_value(row, col); + if z != nodata { + output.set_value(row, col, output.get_value(row, col) / area.get_value(row, col)); + } + } + if verbose { + progress = (100.0_f64 * num_cells_visited as f64 / (rows*columns - 1) as f64) as usize; + if progress != old_progress { + println!("Final calculation: {}%", progress); + old_progress = progress; + } + } + } + + let elapsed_time = get_formatted_elapsed_time(start); + output.add_metadata_entry(format!( + "Created by whitebox_tools\' {} tool", + self.get_tool_name() + )); + output.add_metadata_entry(format!("Input file: {}", input_file)); + output.add_metadata_entry(format!("Elapsed Time (excluding I/O): {}", elapsed_time)); + + if verbose { + println!("Saving data...") + }; + let _ = match output.write() { + Ok(_) => { + if verbose { + println!("Output file written") + } + } + Err(e) => return Err(e), + }; + if verbose { + println!( + "{}", + &format!("Elapsed Time (excluding I/O): {}", elapsed_time) + ); + } + + Ok(()) + } +} + +#[derive(PartialEq, Debug)] +struct GridCell { + row: isize, + column: isize, + priority: f32, +} + +impl Eq for GridCell {} + +impl PartialOrd for GridCell { + fn partial_cmp(&self, other: &Self) -> Option { + other.priority.partial_cmp(&self.priority) + } +} + +impl Ord for GridCell { + fn cmp(&self, other: &GridCell) -> Ordering { + // other.priority.cmp(&self.priority) + let ord = self.partial_cmp(other).unwrap(); + match ord { + Ordering::Greater => Ordering::Less, + Ordering::Less => Ordering::Greater, + Ordering::Equal => ord, + } + } +} diff --git a/src/tools/hydro_analysis/watershed.rs b/src/tools/hydro_analysis/watershed.rs index 5d1eb36c2..bf21effb6 100644 --- a/src/tools/hydro_analysis/watershed.rs +++ b/src/tools/hydro_analysis/watershed.rs @@ -173,7 +173,7 @@ impl WhiteboxTool for Watershed { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/adaptive_filter.rs b/src/tools/image_analysis/adaptive_filter.rs index f90ea333b..1a3d30dac 100644 --- a/src/tools/image_analysis/adaptive_filter.rs +++ b/src/tools/image_analysis/adaptive_filter.rs @@ -159,7 +159,7 @@ impl WhiteboxTool for AdaptiveFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/balance_contrast_enhancement.rs b/src/tools/image_analysis/balance_contrast_enhancement.rs index 6d7f3b909..df3a62421 100644 --- a/src/tools/image_analysis/balance_contrast_enhancement.rs +++ b/src/tools/image_analysis/balance_contrast_enhancement.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for BalanceContrastEnhancement { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/bilateral_filter.rs b/src/tools/image_analysis/bilateral_filter.rs index 35eb03a18..95243d8fb 100644 --- a/src/tools/image_analysis/bilateral_filter.rs +++ b/src/tools/image_analysis/bilateral_filter.rs @@ -159,7 +159,7 @@ impl WhiteboxTool for BilateralFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/change_vector_analysis.rs b/src/tools/image_analysis/change_vector_analysis.rs index 1b7d3954b..8453a3ebe 100644 --- a/src/tools/image_analysis/change_vector_analysis.rs +++ b/src/tools/image_analysis/change_vector_analysis.rs @@ -162,7 +162,7 @@ impl WhiteboxTool for ChangeVectorAnalysis { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/closing.rs b/src/tools/image_analysis/closing.rs index 2924a53ac..56181fcc7 100644 --- a/src/tools/image_analysis/closing.rs +++ b/src/tools/image_analysis/closing.rs @@ -148,7 +148,7 @@ impl WhiteboxTool for Closing { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/conservative_smoothing_filter.rs b/src/tools/image_analysis/conservative_smoothing_filter.rs index 7b697008f..8afb1f8bb 100644 --- a/src/tools/image_analysis/conservative_smoothing_filter.rs +++ b/src/tools/image_analysis/conservative_smoothing_filter.rs @@ -154,7 +154,7 @@ impl WhiteboxTool for ConservativeSmoothingFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/corner_detection.rs b/src/tools/image_analysis/corner_detection.rs index 7b8fb7b44..fc4cc9216 100644 --- a/src/tools/image_analysis/corner_detection.rs +++ b/src/tools/image_analysis/corner_detection.rs @@ -128,7 +128,7 @@ impl WhiteboxTool for CornerDetection { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/correct_vignetting.rs b/src/tools/image_analysis/correct_vignetting.rs index 86982f9d4..e5b4ea5c2 100644 --- a/src/tools/image_analysis/correct_vignetting.rs +++ b/src/tools/image_analysis/correct_vignetting.rs @@ -176,7 +176,7 @@ impl WhiteboxTool for CorrectVignetting { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/create_colour_composite.rs b/src/tools/image_analysis/create_colour_composite.rs index 9b0e42b67..ce239ddb3 100644 --- a/src/tools/image_analysis/create_colour_composite.rs +++ b/src/tools/image_analysis/create_colour_composite.rs @@ -193,7 +193,7 @@ impl WhiteboxTool for CreateColourComposite { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/direct_decorrelation_stretch.rs b/src/tools/image_analysis/direct_decorrelation_stretch.rs index f0bd89dab..4a464f3e6 100644 --- a/src/tools/image_analysis/direct_decorrelation_stretch.rs +++ b/src/tools/image_analysis/direct_decorrelation_stretch.rs @@ -162,7 +162,7 @@ impl WhiteboxTool for DirectDecorrelationStretch { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/diversity_filter.rs b/src/tools/image_analysis/diversity_filter.rs index 4d31cd170..32ce0a62d 100644 --- a/src/tools/image_analysis/diversity_filter.rs +++ b/src/tools/image_analysis/diversity_filter.rs @@ -146,7 +146,7 @@ impl WhiteboxTool for DiversityFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/dog_filter.rs b/src/tools/image_analysis/dog_filter.rs index 09d9c4712..015638427 100644 --- a/src/tools/image_analysis/dog_filter.rs +++ b/src/tools/image_analysis/dog_filter.rs @@ -162,7 +162,7 @@ impl WhiteboxTool for DiffOfGaussianFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/edge_preserving_mean_filter.rs b/src/tools/image_analysis/edge_preserving_mean_filter.rs index 0d09b1571..d6437fa66 100644 --- a/src/tools/image_analysis/edge_preserving_mean_filter.rs +++ b/src/tools/image_analysis/edge_preserving_mean_filter.rs @@ -159,7 +159,7 @@ impl WhiteboxTool for EdgePreservingMeanFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/emboss_filter.rs b/src/tools/image_analysis/emboss_filter.rs index a791a6a4c..5dca821c3 100644 --- a/src/tools/image_analysis/emboss_filter.rs +++ b/src/tools/image_analysis/emboss_filter.rs @@ -207,7 +207,7 @@ impl WhiteboxTool for EmbossFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } diff --git a/src/tools/image_analysis/fast_almost_gaussian_filter.rs b/src/tools/image_analysis/fast_almost_gaussian_filter.rs index 7ba89bcab..0f9de7ccc 100644 --- a/src/tools/image_analysis/fast_almost_gaussian_filter.rs +++ b/src/tools/image_analysis/fast_almost_gaussian_filter.rs @@ -137,7 +137,7 @@ impl WhiteboxTool for FastAlmostGaussianFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/flip_image.rs b/src/tools/image_analysis/flip_image.rs index 59a9ab326..8550877be 100644 --- a/src/tools/image_analysis/flip_image.rs +++ b/src/tools/image_analysis/flip_image.rs @@ -131,7 +131,7 @@ impl WhiteboxTool for FlipImage { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/gamma_correction.rs b/src/tools/image_analysis/gamma_correction.rs index 6a4246df9..fb8e35258 100644 --- a/src/tools/image_analysis/gamma_correction.rs +++ b/src/tools/image_analysis/gamma_correction.rs @@ -136,7 +136,7 @@ impl WhiteboxTool for GammaCorrection { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/gaussian_contrast_stretch.rs b/src/tools/image_analysis/gaussian_contrast_stretch.rs index 51a593523..b11c9335f 100644 --- a/src/tools/image_analysis/gaussian_contrast_stretch.rs +++ b/src/tools/image_analysis/gaussian_contrast_stretch.rs @@ -152,7 +152,7 @@ impl WhiteboxTool for GaussianContrastStretch { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/gaussian_filter.rs b/src/tools/image_analysis/gaussian_filter.rs index d1d23174c..e00a97b30 100644 --- a/src/tools/image_analysis/gaussian_filter.rs +++ b/src/tools/image_analysis/gaussian_filter.rs @@ -153,7 +153,7 @@ impl WhiteboxTool for GaussianFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/highpass_filter.rs b/src/tools/image_analysis/highpass_filter.rs index a3795faca..ac8cb368a 100644 --- a/src/tools/image_analysis/highpass_filter.rs +++ b/src/tools/image_analysis/highpass_filter.rs @@ -148,7 +148,7 @@ impl WhiteboxTool for HighPassFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/highpass_median_filter.rs b/src/tools/image_analysis/highpass_median_filter.rs index f6faf7ff4..9c5ee1d52 100644 --- a/src/tools/image_analysis/highpass_median_filter.rs +++ b/src/tools/image_analysis/highpass_median_filter.rs @@ -168,7 +168,7 @@ impl WhiteboxTool for HighPassMedianFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/histogram_equalization.rs b/src/tools/image_analysis/histogram_equalization.rs index 4dc612921..4ba5dc49f 100644 --- a/src/tools/image_analysis/histogram_equalization.rs +++ b/src/tools/image_analysis/histogram_equalization.rs @@ -160,7 +160,7 @@ impl WhiteboxTool for HistogramEqualization { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/histogram_matching.rs b/src/tools/image_analysis/histogram_matching.rs index 86c855589..a2c7712a6 100644 --- a/src/tools/image_analysis/histogram_matching.rs +++ b/src/tools/image_analysis/histogram_matching.rs @@ -164,7 +164,7 @@ impl WhiteboxTool for HistogramMatching { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/histogram_matching_two_images.rs b/src/tools/image_analysis/histogram_matching_two_images.rs index 30689f892..a948adc93 100644 --- a/src/tools/image_analysis/histogram_matching_two_images.rs +++ b/src/tools/image_analysis/histogram_matching_two_images.rs @@ -148,7 +148,7 @@ impl WhiteboxTool for HistogramMatchingTwoImages { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/ihs_to_rgb.rs b/src/tools/image_analysis/ihs_to_rgb.rs index 69732b3f3..282ec2a15 100644 --- a/src/tools/image_analysis/ihs_to_rgb.rs +++ b/src/tools/image_analysis/ihs_to_rgb.rs @@ -208,7 +208,7 @@ impl WhiteboxTool for IhsToRgb { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/image_stack_profile.rs b/src/tools/image_analysis/image_stack_profile.rs index c906011d9..ac1ab74c3 100644 --- a/src/tools/image_analysis/image_stack_profile.rs +++ b/src/tools/image_analysis/image_stack_profile.rs @@ -150,7 +150,7 @@ impl WhiteboxTool for ImageStackProfile { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/integral_image.rs b/src/tools/image_analysis/integral_image.rs index c4fabbdee..904c9c32a 100644 --- a/src/tools/image_analysis/integral_image.rs +++ b/src/tools/image_analysis/integral_image.rs @@ -126,7 +126,7 @@ impl WhiteboxTool for IntegralImage { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/k_means_clustering.rs b/src/tools/image_analysis/k_means_clustering.rs index 4da8c5345..7c7dced18 100644 --- a/src/tools/image_analysis/k_means_clustering.rs +++ b/src/tools/image_analysis/k_means_clustering.rs @@ -205,7 +205,7 @@ impl WhiteboxTool for KMeansClustering { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/k_nearest_mean_filter.rs b/src/tools/image_analysis/k_nearest_mean_filter.rs index 6b47df60c..0475aaad9 100644 --- a/src/tools/image_analysis/k_nearest_mean_filter.rs +++ b/src/tools/image_analysis/k_nearest_mean_filter.rs @@ -168,7 +168,7 @@ impl WhiteboxTool for KNearestMeanFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/laplacian_filter.rs b/src/tools/image_analysis/laplacian_filter.rs index 442fda145..cde223d9b 100644 --- a/src/tools/image_analysis/laplacian_filter.rs +++ b/src/tools/image_analysis/laplacian_filter.rs @@ -193,7 +193,7 @@ impl WhiteboxTool for LaplacianFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } diff --git a/src/tools/image_analysis/lee_filter.rs b/src/tools/image_analysis/lee_filter.rs index cbfa0eb6d..c19662e07 100644 --- a/src/tools/image_analysis/lee_filter.rs +++ b/src/tools/image_analysis/lee_filter.rs @@ -165,7 +165,7 @@ impl WhiteboxTool for LeeSigmaFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/line_detection_filter.rs b/src/tools/image_analysis/line_detection_filter.rs index d1cbbbb70..735b3517c 100644 --- a/src/tools/image_analysis/line_detection_filter.rs +++ b/src/tools/image_analysis/line_detection_filter.rs @@ -184,7 +184,7 @@ impl WhiteboxTool for LineDetectionFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } diff --git a/src/tools/image_analysis/line_thin.rs b/src/tools/image_analysis/line_thin.rs index d082e0ecb..10e53761d 100644 --- a/src/tools/image_analysis/line_thin.rs +++ b/src/tools/image_analysis/line_thin.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for LineThinning { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/log_filter.rs b/src/tools/image_analysis/log_filter.rs index 58fe89494..344ff6f04 100644 --- a/src/tools/image_analysis/log_filter.rs +++ b/src/tools/image_analysis/log_filter.rs @@ -152,7 +152,7 @@ impl WhiteboxTool for LaplacianOfGaussianFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/majority_filter.rs b/src/tools/image_analysis/majority_filter.rs index 982bc4069..79a89aaf6 100644 --- a/src/tools/image_analysis/majority_filter.rs +++ b/src/tools/image_analysis/majority_filter.rs @@ -155,7 +155,7 @@ impl WhiteboxTool for MajorityFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/max_filter.rs b/src/tools/image_analysis/max_filter.rs index bc776f31a..368a094d7 100644 --- a/src/tools/image_analysis/max_filter.rs +++ b/src/tools/image_analysis/max_filter.rs @@ -152,7 +152,7 @@ impl WhiteboxTool for MaximumFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/mean_filter.rs b/src/tools/image_analysis/mean_filter.rs index dba36e7df..61e45c48f 100644 --- a/src/tools/image_analysis/mean_filter.rs +++ b/src/tools/image_analysis/mean_filter.rs @@ -164,7 +164,7 @@ impl WhiteboxTool for MeanFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/median_filter.rs b/src/tools/image_analysis/median_filter.rs index cb427d879..17de606be 100644 --- a/src/tools/image_analysis/median_filter.rs +++ b/src/tools/image_analysis/median_filter.rs @@ -173,7 +173,7 @@ impl WhiteboxTool for MedianFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/min_filter.rs b/src/tools/image_analysis/min_filter.rs index 9fb5f7a47..2b4173843 100644 --- a/src/tools/image_analysis/min_filter.rs +++ b/src/tools/image_analysis/min_filter.rs @@ -159,7 +159,7 @@ impl WhiteboxTool for MinimumFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/min_max_contrast_stretch.rs b/src/tools/image_analysis/min_max_contrast_stretch.rs index 19fa2fe18..0f60efe49 100644 --- a/src/tools/image_analysis/min_max_contrast_stretch.rs +++ b/src/tools/image_analysis/min_max_contrast_stretch.rs @@ -176,7 +176,7 @@ impl WhiteboxTool for MinMaxContrastStretch { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/modified_k_means_clustering.rs b/src/tools/image_analysis/modified_k_means_clustering.rs index 5304dbfbb..0da027943 100644 --- a/src/tools/image_analysis/modified_k_means_clustering.rs +++ b/src/tools/image_analysis/modified_k_means_clustering.rs @@ -208,7 +208,7 @@ impl WhiteboxTool for ModifiedKMeansClustering { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/mosaic.rs b/src/tools/image_analysis/mosaic.rs index 13b0b7e26..6cebe80cf 100644 --- a/src/tools/image_analysis/mosaic.rs +++ b/src/tools/image_analysis/mosaic.rs @@ -148,7 +148,7 @@ impl WhiteboxTool for Mosaic { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -433,7 +433,7 @@ impl WhiteboxTool for Mosaic { for n in 0..num_neighbours { row_n = origin_row + shift_y[n]; col_n = origin_col + shift_x[n]; - neighbour[n][0] = inputs[i].get_value(row_n, col_n);; + neighbour[n][0] = inputs[i].get_value(row_n, col_n); dy = row_n as f64 - row_src; dx = col_n as f64 - col_src; @@ -518,7 +518,7 @@ impl WhiteboxTool for Mosaic { for n in 0..num_neighbours { row_n = origin_row + shift_y[n]; col_n = origin_col + shift_x[n]; - neighbour[n][0] = inputs[i].get_value(row_n, col_n);; + neighbour[n][0] = inputs[i].get_value(row_n, col_n); dy = row_n as f64 - row_src; dx = col_n as f64 - col_src; diff --git a/src/tools/image_analysis/mosaic_with_feathering.rs b/src/tools/image_analysis/mosaic_with_feathering.rs index 9776da0a1..5911ab2f3 100644 --- a/src/tools/image_analysis/mosaic_with_feathering.rs +++ b/src/tools/image_analysis/mosaic_with_feathering.rs @@ -177,7 +177,7 @@ impl WhiteboxTool for MosaicWithFeathering { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -543,7 +543,7 @@ impl WhiteboxTool for MosaicWithFeathering { for n in 0..num_neighbours { row_n = origin_row1 + shift_y[n]; col_n = origin_col1 + shift_x[n]; - neighbour[n][0] = input1.get_value(row_n, col_n);; + neighbour[n][0] = input1.get_value(row_n, col_n); dy = row_n as f64 - row_src1; dx = col_n as f64 - col_src1; @@ -568,7 +568,7 @@ impl WhiteboxTool for MosaicWithFeathering { for n in 0..num_neighbours { row_n = origin_row2 + shift_y[n]; col_n = origin_col2 + shift_x[n]; - neighbour[n][0] = input2.get_value(row_n, col_n);; + neighbour[n][0] = input2.get_value(row_n, col_n); dy = row_n as f64 - row_src2; dx = col_n as f64 - col_src2; @@ -609,7 +609,7 @@ impl WhiteboxTool for MosaicWithFeathering { for n in 0..num_neighbours { row_n = origin_row1 + shift_y[n]; col_n = origin_col1 + shift_x[n]; - neighbour[n][0] = input1.get_value(row_n, col_n);; + neighbour[n][0] = input1.get_value(row_n, col_n); dy = row_n as f64 - row_src1; dx = col_n as f64 - col_src1; @@ -642,7 +642,7 @@ impl WhiteboxTool for MosaicWithFeathering { for n in 0..num_neighbours { row_n = origin_row2 + shift_y[n]; col_n = origin_col2 + shift_x[n]; - neighbour[n][0] = input2.get_value(row_n, col_n);; + neighbour[n][0] = input2.get_value(row_n, col_n); dy = row_n as f64 - row_src2; dx = col_n as f64 - col_src2; diff --git a/src/tools/image_analysis/normalized_difference_index.rs b/src/tools/image_analysis/normalized_difference_index.rs index e696f90de..3d644a4a1 100644 --- a/src/tools/image_analysis/normalized_difference_index.rs +++ b/src/tools/image_analysis/normalized_difference_index.rs @@ -204,7 +204,7 @@ impl WhiteboxTool for NormalizedDifferenceIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/olympic_filter.rs b/src/tools/image_analysis/olympic_filter.rs index 2675d0b86..3918ceb5a 100644 --- a/src/tools/image_analysis/olympic_filter.rs +++ b/src/tools/image_analysis/olympic_filter.rs @@ -156,7 +156,7 @@ impl WhiteboxTool for OlympicFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/opening.rs b/src/tools/image_analysis/opening.rs index 667781491..cbc60e29b 100644 --- a/src/tools/image_analysis/opening.rs +++ b/src/tools/image_analysis/opening.rs @@ -155,7 +155,7 @@ impl WhiteboxTool for Opening { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/pan_sharpening.rs b/src/tools/image_analysis/pan_sharpening.rs index 33a9376a4..ed8deef92 100644 --- a/src/tools/image_analysis/pan_sharpening.rs +++ b/src/tools/image_analysis/pan_sharpening.rs @@ -195,7 +195,7 @@ impl WhiteboxTool for PanchromaticSharpening { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/percentage_contrast_stretch.rs b/src/tools/image_analysis/percentage_contrast_stretch.rs index 7b171038e..34aa15049 100644 --- a/src/tools/image_analysis/percentage_contrast_stretch.rs +++ b/src/tools/image_analysis/percentage_contrast_stretch.rs @@ -178,7 +178,7 @@ impl WhiteboxTool for PercentageContrastStretch { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/percentile_filter.rs b/src/tools/image_analysis/percentile_filter.rs index 78380320c..7e08481ee 100644 --- a/src/tools/image_analysis/percentile_filter.rs +++ b/src/tools/image_analysis/percentile_filter.rs @@ -178,7 +178,7 @@ impl WhiteboxTool for PercentileFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/prewitt_filter.rs b/src/tools/image_analysis/prewitt_filter.rs index 54484a9ce..84f0c9be1 100644 --- a/src/tools/image_analysis/prewitt_filter.rs +++ b/src/tools/image_analysis/prewitt_filter.rs @@ -160,7 +160,7 @@ impl WhiteboxTool for PrewittFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } diff --git a/src/tools/image_analysis/range_filter.rs b/src/tools/image_analysis/range_filter.rs index 6c2fe5028..9d7a7e14c 100644 --- a/src/tools/image_analysis/range_filter.rs +++ b/src/tools/image_analysis/range_filter.rs @@ -154,7 +154,7 @@ impl WhiteboxTool for RangeFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/remove_spurs.rs b/src/tools/image_analysis/remove_spurs.rs index 5c7dd238f..314a0c393 100644 --- a/src/tools/image_analysis/remove_spurs.rs +++ b/src/tools/image_analysis/remove_spurs.rs @@ -149,7 +149,7 @@ impl WhiteboxTool for RemoveSpurs { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/resample.rs b/src/tools/image_analysis/resample.rs index 64109f1bf..7316233c8 100644 --- a/src/tools/image_analysis/resample.rs +++ b/src/tools/image_analysis/resample.rs @@ -136,7 +136,7 @@ impl WhiteboxTool for Resample { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -346,7 +346,7 @@ impl WhiteboxTool for Resample { for n in 0..num_neighbours { row_n = origin_row + shift_y[n]; col_n = origin_col + shift_x[n]; - neighbour[n][0] = inputs[i].get_value(row_n, col_n);; + neighbour[n][0] = inputs[i].get_value(row_n, col_n); dy = row_n as f64 - row_src; dx = col_n as f64 - col_src; @@ -429,7 +429,7 @@ impl WhiteboxTool for Resample { for n in 0..num_neighbours { row_n = origin_row + shift_y[n]; col_n = origin_col + shift_x[n]; - neighbour[n][0] = inputs[i].get_value(row_n, col_n);; + neighbour[n][0] = inputs[i].get_value(row_n, col_n); dy = row_n as f64 - row_src; dx = col_n as f64 - col_src; diff --git a/src/tools/image_analysis/rgb_to_ihs.rs b/src/tools/image_analysis/rgb_to_ihs.rs index 2cd431cb6..c4e5dc935 100644 --- a/src/tools/image_analysis/rgb_to_ihs.rs +++ b/src/tools/image_analysis/rgb_to_ihs.rs @@ -206,7 +206,7 @@ impl WhiteboxTool for RgbToIhs { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/roberts_filter.rs b/src/tools/image_analysis/roberts_filter.rs index f70a86502..29879eb48 100644 --- a/src/tools/image_analysis/roberts_filter.rs +++ b/src/tools/image_analysis/roberts_filter.rs @@ -160,7 +160,7 @@ impl WhiteboxTool for RobertsCrossFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } diff --git a/src/tools/image_analysis/scharr_filter.rs b/src/tools/image_analysis/scharr_filter.rs index 589b289ae..70704bbdb 100644 --- a/src/tools/image_analysis/scharr_filter.rs +++ b/src/tools/image_analysis/scharr_filter.rs @@ -161,7 +161,7 @@ impl WhiteboxTool for ScharrFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } diff --git a/src/tools/image_analysis/sigmoidal_contrast_stretch.rs b/src/tools/image_analysis/sigmoidal_contrast_stretch.rs index adad34678..3c75f2675 100644 --- a/src/tools/image_analysis/sigmoidal_contrast_stretch.rs +++ b/src/tools/image_analysis/sigmoidal_contrast_stretch.rs @@ -178,7 +178,7 @@ impl WhiteboxTool for SigmoidalContrastStretch { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/sobel_filter.rs b/src/tools/image_analysis/sobel_filter.rs index 483fa4da2..b59b2441e 100644 --- a/src/tools/image_analysis/sobel_filter.rs +++ b/src/tools/image_analysis/sobel_filter.rs @@ -168,7 +168,7 @@ impl WhiteboxTool for SobelFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } diff --git a/src/tools/image_analysis/split_colour_composite.rs b/src/tools/image_analysis/split_colour_composite.rs index 782fcced8..bfe011cf6 100644 --- a/src/tools/image_analysis/split_colour_composite.rs +++ b/src/tools/image_analysis/split_colour_composite.rs @@ -157,7 +157,7 @@ impl WhiteboxTool for SplitColourComposite { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/stdev_contrast_stretch.rs b/src/tools/image_analysis/stdev_contrast_stretch.rs index 3fbecedd1..a1b77f023 100644 --- a/src/tools/image_analysis/stdev_contrast_stretch.rs +++ b/src/tools/image_analysis/stdev_contrast_stretch.rs @@ -163,7 +163,7 @@ impl WhiteboxTool for StandardDeviationContrastStretch { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/stdev_filter.rs b/src/tools/image_analysis/stdev_filter.rs index ce7e55fd3..676d8b0b4 100644 --- a/src/tools/image_analysis/stdev_filter.rs +++ b/src/tools/image_analysis/stdev_filter.rs @@ -154,7 +154,7 @@ impl WhiteboxTool for StandardDeviationFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/thicken_line.rs b/src/tools/image_analysis/thicken_line.rs index e08f67f34..b81a1062f 100644 --- a/src/tools/image_analysis/thicken_line.rs +++ b/src/tools/image_analysis/thicken_line.rs @@ -140,7 +140,7 @@ impl WhiteboxTool for ThickenRasterLine { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/tophat.rs b/src/tools/image_analysis/tophat.rs index 119e4c486..caddc7b46 100644 --- a/src/tools/image_analysis/tophat.rs +++ b/src/tools/image_analysis/tophat.rs @@ -172,7 +172,7 @@ impl WhiteboxTool for TophatTransform { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/total_filter.rs b/src/tools/image_analysis/total_filter.rs index d89824200..1881674cb 100644 --- a/src/tools/image_analysis/total_filter.rs +++ b/src/tools/image_analysis/total_filter.rs @@ -153,7 +153,7 @@ impl WhiteboxTool for TotalFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/unsharp_masking.rs b/src/tools/image_analysis/unsharp_masking.rs index 8faf74d9c..af276ce59 100644 --- a/src/tools/image_analysis/unsharp_masking.rs +++ b/src/tools/image_analysis/unsharp_masking.rs @@ -168,7 +168,7 @@ impl WhiteboxTool for UnsharpMasking { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/user_defined_weights_filter.rs b/src/tools/image_analysis/user_defined_weights_filter.rs index cef2a4bb8..30a40b3ad 100644 --- a/src/tools/image_analysis/user_defined_weights_filter.rs +++ b/src/tools/image_analysis/user_defined_weights_filter.rs @@ -161,7 +161,7 @@ impl WhiteboxTool for UserDefinedWeightsFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/image_analysis/write_func_memory_insertion.rs b/src/tools/image_analysis/write_func_memory_insertion.rs index aa9b12ef3..24d03c8d4 100644 --- a/src/tools/image_analysis/write_func_memory_insertion.rs +++ b/src/tools/image_analysis/write_func_memory_insertion.rs @@ -165,7 +165,7 @@ impl WhiteboxTool for WriteFunctionMemoryInsertion { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/ascii_to_las.rs b/src/tools/lidar_analysis/ascii_to_las.rs index 07e510473..b597157b3 100644 --- a/src/tools/lidar_analysis/ascii_to_las.rs +++ b/src/tools/lidar_analysis/ascii_to_las.rs @@ -172,7 +172,7 @@ impl WhiteboxTool for AsciiToLas { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/block_maximum.rs b/src/tools/lidar_analysis/block_maximum.rs index a231104e1..796cd8719 100644 --- a/src/tools/lidar_analysis/block_maximum.rs +++ b/src/tools/lidar_analysis/block_maximum.rs @@ -145,7 +145,7 @@ impl WhiteboxTool for LidarBlockMaximum { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/block_minimum.rs b/src/tools/lidar_analysis/block_minimum.rs index 4bd49dec3..b3cd7af72 100644 --- a/src/tools/lidar_analysis/block_minimum.rs +++ b/src/tools/lidar_analysis/block_minimum.rs @@ -145,7 +145,7 @@ impl WhiteboxTool for LidarBlockMinimum { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/classify_buildings.rs b/src/tools/lidar_analysis/classify_buildings.rs new file mode 100644 index 000000000..e832964e7 --- /dev/null +++ b/src/tools/lidar_analysis/classify_buildings.rs @@ -0,0 +1,500 @@ +/* +This tool is part of the WhiteboxTools geospatial analysis library. +Authors: Dr. John Lindsay +Created: 17/11/2019 +Last Modified: 17/11/2019 +License: MIT +*/ + +use crate::algorithms; +use crate::lidar::*; +use crate::structures::{BoundingBox, Point2D}; +use crate::tools::*; +use crate::vector::{ShapeType, Shapefile}; +use std::env; +use std::io::{Error, ErrorKind}; +use std::path; +use num_cpus; +use std::sync::{Arc, mpsc}; +use std::thread; + +/// This tool can be used to assign the building class (classification value 6) to all points within an +/// input LiDAR point cloud (`--input`) that are contained within the polygons of an input buildings +/// footprint vector (`--buildings`). The tool performs a simple point-in-polygon operation to determine +/// membership. The two inputs (i.e. the LAS file and vector) must share the same map projection. Furthermore, +/// any error in the definition of the building footprints will result in misclassified points in the output +/// LAS file (`--output`). In particular, if the footprints extend slightly beyond the actual building, +/// ground points situated adjacent to the building will be incorrectly classified. Thus, care must be +/// taken in digitizing building footprint polygons. Furthermore, where there are tall trees that overlap +/// significantly with the building footprint, these vegetation points will also be incorrectly assigned the +/// building class value. +/// +/// # See Also +/// `FilterLidarClasses`, `LidarGroundPointFilter`, `ClipLidarToPolygon` +pub struct ClassifyBuildingsInLidar { + name: String, + description: String, + toolbox: String, + parameters: Vec, + example_usage: String, +} + +impl ClassifyBuildingsInLidar { + /// public constructor + pub fn new() -> ClassifyBuildingsInLidar { + let name = "ClassifyBuildingsInLidar".to_string(); + let toolbox = "LiDAR Tools".to_string(); + let description = "Reclassifies a LiDAR points that lie within vector building footprints.".to_string(); + + let mut parameters = vec![]; + parameters.push(ToolParameter { + name: "Input File".to_owned(), + flags: vec!["-i".to_owned(), "--input".to_owned()], + description: "Input LiDAR file.".to_owned(), + parameter_type: ParameterType::ExistingFile(ParameterFileType::Lidar), + default_value: None, + optional: false, + }); + + parameters.push(ToolParameter { + name: "Input Building Polygon File".to_owned(), + flags: vec!["--buildings".to_owned()], + description: "Input vector polygons file.".to_owned(), + parameter_type: ParameterType::ExistingFile(ParameterFileType::Vector( + VectorGeometryType::Polygon, + )), + default_value: None, + optional: false, + }); + + parameters.push(ToolParameter { + name: "Output File".to_owned(), + flags: vec!["-o".to_owned(), "--output".to_owned()], + description: "Output LiDAR file.".to_owned(), + parameter_type: ParameterType::NewFile(ParameterFileType::Lidar), + default_value: None, + optional: false, + }); + + let sep: String = path::MAIN_SEPARATOR.to_string(); + let p = format!("{}", env::current_dir().unwrap().display()); + let e = format!("{}", env::current_exe().unwrap().display()); + let mut short_exe = e + .replace(&p, "") + .replace(".exe", "") + .replace(".", "") + .replace(&sep, ""); + if e.contains(".exe") { + short_exe += ".exe"; + } + let usage = format!(">>.*{0} -r={1} -v --wd=\"*path*to*data*\" -i='data.las' --polygons='lakes.shp' -o='output.las'", short_exe, name).replace("*", &sep); + + ClassifyBuildingsInLidar { + name: name, + description: description, + toolbox: toolbox, + parameters: parameters, + example_usage: usage, + } + } +} + +impl WhiteboxTool for ClassifyBuildingsInLidar { + fn get_source_file(&self) -> String { + String::from(file!()) + } + + fn get_tool_name(&self) -> String { + self.name.clone() + } + + fn get_tool_description(&self) -> String { + self.description.clone() + } + + fn get_tool_parameters(&self) -> String { + match serde_json::to_string(&self.parameters) { + Ok(json_str) => return format!("{{\"parameters\":{}}}", json_str), + Err(err) => return format!("{:?}", err), + } + } + + fn get_example_usage(&self) -> String { + self.example_usage.clone() + } + + fn get_toolbox(&self) -> String { + self.toolbox.clone() + } + + fn run<'a>( + &self, + args: Vec, + working_directory: &'a str, + verbose: bool, + ) -> Result<(), Error> { + let mut input_file = String::new(); + let mut polygons_file = String::new(); + let mut output_file = String::new(); + + if args.len() == 0 { + return Err(Error::new( + ErrorKind::InvalidInput, + "Tool run with no parameters.", + )); + } + for i in 0..args.len() { + let mut arg = args[i].replace("\"", ""); + arg = arg.replace("\'", ""); + let cmd = arg.split("="); // in case an equals sign was used + let vec = cmd.collect::>(); + let mut keyval = false; + if vec.len() > 1 { + keyval = true; + } + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-i" || flag_val == "-input" { + input_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-building" || flag_val == "-buildings" { + polygons_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-o" || flag_val == "-output" { + output_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } + } + + if verbose { + println!("***************{}", "*".repeat(self.get_tool_name().len())); + println!("* Welcome to {} *", self.get_tool_name()); + println!("***************{}", "*".repeat(self.get_tool_name().len())); + } + + let sep: String = path::MAIN_SEPARATOR.to_string(); + + let mut progress: usize; + let mut old_progress: usize = 1; + + if !input_file.contains(&sep) && !input_file.contains("/") { + input_file = format!("{}{}", working_directory, input_file); + } + if !polygons_file.contains(&sep) && !polygons_file.contains("/") { + polygons_file = format!("{}{}", working_directory, polygons_file); + } + if !output_file.contains(&sep) && !output_file.contains("/") { + output_file = format!("{}{}", working_directory, output_file); + } + + if verbose { + println!("Reading data...") + }; + let input = match LasFile::new(&input_file, "r") { + Ok(lf) => lf, + Err(err) => panic!(format!("Error reading file {}: {}", input_file, err)), + }; + + let lidar_bb = BoundingBox::new( + input.header.min_x, + input.header.max_x, + input.header.min_y, + input.header.max_y, + ); + + let polygons = Shapefile::read(&polygons_file)?; + let num_records = polygons.num_records; + + let start = Instant::now(); + + // make sure the input vector file is of polygon type + if polygons.header.shape_type.base_shape_type() != ShapeType::Polygon { + return Err(Error::new( + ErrorKind::InvalidInput, + "The input vector data must be of polygon base shape type.", + )); + } + + // place the bounding boxes of each of the polygons into a vector + let mut bb: Vec = Vec::with_capacity(num_records); + let mut feature_bb; + let mut record_nums = Vec::with_capacity(num_records); + for record_num in 0..polygons.num_records { + let record = polygons.get_record(record_num); + feature_bb = BoundingBox::new( + record.x_min, + record.x_max, + record.y_min, + record.y_max, + ); + if feature_bb.overlaps(lidar_bb) { + bb.push(feature_bb); + record_nums.push(record_num); + } + } + + if verbose { + println!("Performing reclassification...") + }; + + let n_points = input.header.number_of_points as usize; + let num_points: f64 = (input.header.number_of_points - 1) as f64; // used for progress calculation only + + let num_procs = num_cpus::get(); + let input = Arc::new(input); + let polygons = Arc::new(polygons); + let record_nums = Arc::new(record_nums); + let bb = Arc::new(bb); + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let input = input.clone(); + let polygons = polygons.clone(); + let record_nums = record_nums.clone(); + let bb = bb.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let mut p: PointData; + let mut record_num: usize; + let mut point_in_poly: bool; + let mut start_point_in_part: usize; + let mut end_point_in_part: usize; + for point_num in (0..n_points).filter(|point_num| point_num % num_procs == tid) { + p = input.get_point_info(point_num); + point_in_poly = false; + for r in 0..record_nums.len() { + record_num = record_nums[r]; + if bb[r].is_point_in_box(p.x, p.y) { + // it's in the bounding box and worth seeing if it's in the enclosed polygon + let record = polygons.get_record(record_num); + for part in 0..record.num_parts as usize { + if !record.is_hole(part as i32) { + // not holes + start_point_in_part = record.parts[part] as usize; + end_point_in_part = if part < record.num_parts as usize - 1 { + record.parts[part + 1] as usize - 1 + } else { + record.num_points as usize - 1 + }; + + if algorithms::point_in_poly( + &Point2D { x: p.x, y: p.y }, + &record.points[start_point_in_part..end_point_in_part + 1], + ) { + point_in_poly = true; + break; + } + } + } + + for part in 0..record.num_parts as usize { + if record.is_hole(part as i32) { + // holes + start_point_in_part = record.parts[part] as usize; + end_point_in_part = if part < record.num_parts as usize - 1 { + record.parts[part + 1] as usize - 1 + } else { + record.num_points as usize - 1 + }; + + if algorithms::point_in_poly( + &Point2D { x: p.x, y: p.y }, + &record.points[start_point_in_part..end_point_in_part + 1], + ) { + point_in_poly = false; + break; + } + } + } + } + } + match tx.send((point_in_poly, point_num)) { + Ok(_) => {}, // do nothing + Err(_) => panic!("Error performing clipping operation on point num. {}", point_num), + }; + } + }); + } + + let mut output = LasFile::initialize_using_file(&output_file, &input); + output.header.system_id = "EXTRACTION".to_string(); + let mut num_building_points = 0; + for i in 0..n_points { + let data = rx.recv().unwrap(); + if !data.0 { + output.add_point_record(input.get_record(data.1)); + } else { + num_building_points += 1; + let pr = input.get_record(data.1); + let pr2: LidarPointRecord; + match pr { + LidarPointRecord::PointRecord0 { mut point_data } => { + point_data.set_classification(6); + pr2 = LidarPointRecord::PointRecord0 { + point_data: point_data, + }; + } + LidarPointRecord::PointRecord1 { + mut point_data, + gps_data, + } => { + point_data.set_classification(6); + pr2 = LidarPointRecord::PointRecord1 { + point_data: point_data, + gps_data: gps_data, + }; + } + LidarPointRecord::PointRecord2 { + mut point_data, + colour_data, + } => { + point_data.set_classification(6); + pr2 = LidarPointRecord::PointRecord2 { + point_data: point_data, + colour_data: colour_data, + }; + } + LidarPointRecord::PointRecord3 { + mut point_data, + gps_data, + colour_data, + } => { + point_data.set_classification(6); + pr2 = LidarPointRecord::PointRecord3 { + point_data: point_data, + gps_data: gps_data, + colour_data: colour_data, + }; + } + LidarPointRecord::PointRecord4 { + mut point_data, + gps_data, + wave_packet, + } => { + point_data.set_classification(6); + pr2 = LidarPointRecord::PointRecord4 { + point_data: point_data, + gps_data: gps_data, + wave_packet: wave_packet, + }; + } + LidarPointRecord::PointRecord5 { + mut point_data, + gps_data, + colour_data, + wave_packet, + } => { + point_data.set_classification(6); + pr2 = LidarPointRecord::PointRecord5 { + point_data: point_data, + gps_data: gps_data, + colour_data: colour_data, + wave_packet: wave_packet, + }; + } + LidarPointRecord::PointRecord6 { + mut point_data, + gps_data, + } => { + point_data.set_classification(6); + pr2 = LidarPointRecord::PointRecord6 { + point_data: point_data, + gps_data: gps_data, + }; + } + LidarPointRecord::PointRecord7 { + mut point_data, + gps_data, + colour_data, + } => { + point_data.set_classification(6); + pr2 = LidarPointRecord::PointRecord7 { + point_data: point_data, + gps_data: gps_data, + colour_data: colour_data, + }; + } + LidarPointRecord::PointRecord8 { + mut point_data, + gps_data, + colour_data, + } => { + point_data.set_classification(6); + pr2 = LidarPointRecord::PointRecord8 { + point_data: point_data, + gps_data: gps_data, + colour_data: colour_data, + }; + } + LidarPointRecord::PointRecord9 { + mut point_data, + gps_data, + wave_packet, + } => { + point_data.set_classification(6); + pr2 = LidarPointRecord::PointRecord9 { + point_data: point_data, + gps_data: gps_data, + wave_packet: wave_packet, + }; + } + LidarPointRecord::PointRecord10 { + mut point_data, + gps_data, + colour_data, + wave_packet, + } => { + point_data.set_classification(6); + pr2 = LidarPointRecord::PointRecord10 { + point_data: point_data, + gps_data: gps_data, + colour_data: colour_data, + wave_packet: wave_packet, + }; + } + } + output.add_point_record(pr2); + } + if verbose { + progress = (100.0_f64 * i as f64 / num_points) as usize; + if progress != old_progress { + println!("Progress: {}%", progress); + old_progress = progress; + } + } + } + + println!("Number of building points classified: {}", num_building_points); + + let elapsed_time = get_formatted_elapsed_time(start); + + if verbose { + println!("Writing output LAS file..."); + } + if output.header.number_of_points > 0 { + let _ = match output.write() { + Ok(_) => if verbose { println!("Complete!") }, + Err(e) => println!("error while writing: {:?}", e), + }; + } else { + if verbose { + println!("Warning: the file {} does not appear to contain any points within the clip polygon. No output file has been created.", output.get_short_filename()); + } + } + if verbose { + println!( + "{}", + &format!("Elapsed Time (excluding I/O): {}", elapsed_time) + ); + } + + Ok(()) + } +} diff --git a/src/tools/lidar_analysis/classify_overlap_points.rs b/src/tools/lidar_analysis/classify_overlap_points.rs index b6347925f..cc767a59b 100644 --- a/src/tools/lidar_analysis/classify_overlap_points.rs +++ b/src/tools/lidar_analysis/classify_overlap_points.rs @@ -158,7 +158,7 @@ impl WhiteboxTool for ClassifyOverlapPoints { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/clip_lidar_to_polygon.rs b/src/tools/lidar_analysis/clip_lidar_to_polygon.rs index c49c36413..6c222759d 100644 --- a/src/tools/lidar_analysis/clip_lidar_to_polygon.rs +++ b/src/tools/lidar_analysis/clip_lidar_to_polygon.rs @@ -138,7 +138,7 @@ impl WhiteboxTool for ClipLidarToPolygon { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/erase_polygon_from_lidar.rs b/src/tools/lidar_analysis/erase_polygon_from_lidar.rs index 8679485cb..267653477 100644 --- a/src/tools/lidar_analysis/erase_polygon_from_lidar.rs +++ b/src/tools/lidar_analysis/erase_polygon_from_lidar.rs @@ -136,7 +136,7 @@ impl WhiteboxTool for ErasePolygonFromLidar { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/filter_lidar_classes.rs b/src/tools/lidar_analysis/filter_lidar_classes.rs index 68e1da6f9..596dd36f8 100644 --- a/src/tools/lidar_analysis/filter_lidar_classes.rs +++ b/src/tools/lidar_analysis/filter_lidar_classes.rs @@ -164,7 +164,7 @@ impl WhiteboxTool for FilterLidarClasses { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/filter_lidar_scan_angles.rs b/src/tools/lidar_analysis/filter_lidar_scan_angles.rs index 52737e421..f9c225587 100644 --- a/src/tools/lidar_analysis/filter_lidar_scan_angles.rs +++ b/src/tools/lidar_analysis/filter_lidar_scan_angles.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for FilterLidarScanAngles { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/find_flightline_edge_points.rs b/src/tools/lidar_analysis/find_flightline_edge_points.rs index c8765871c..b1c17d070 100644 --- a/src/tools/lidar_analysis/find_flightline_edge_points.rs +++ b/src/tools/lidar_analysis/find_flightline_edge_points.rs @@ -122,7 +122,7 @@ impl WhiteboxTool for FindFlightlineEdgePoints { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/flightline_overlap.rs b/src/tools/lidar_analysis/flightline_overlap.rs index 2422f063f..e7a7ca604 100644 --- a/src/tools/lidar_analysis/flightline_overlap.rs +++ b/src/tools/lidar_analysis/flightline_overlap.rs @@ -135,7 +135,7 @@ impl WhiteboxTool for FlightlineOverlap { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/las_to_ascii.rs b/src/tools/lidar_analysis/las_to_ascii.rs index 522d94292..762a3cea9 100644 --- a/src/tools/lidar_analysis/las_to_ascii.rs +++ b/src/tools/lidar_analysis/las_to_ascii.rs @@ -127,7 +127,7 @@ impl WhiteboxTool for LasToAscii { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/las_to_multipoint_shapefile.rs b/src/tools/lidar_analysis/las_to_multipoint_shapefile.rs index 95efb876d..6e8361fb6 100644 --- a/src/tools/lidar_analysis/las_to_multipoint_shapefile.rs +++ b/src/tools/lidar_analysis/las_to_multipoint_shapefile.rs @@ -131,7 +131,7 @@ impl WhiteboxTool for LasToMultipointShapefile { if args.len() == 0 && working_directory.is_empty() { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/las_to_shapefile.rs b/src/tools/lidar_analysis/las_to_shapefile.rs index a2571221d..2affb0e60 100644 --- a/src/tools/lidar_analysis/las_to_shapefile.rs +++ b/src/tools/lidar_analysis/las_to_shapefile.rs @@ -136,7 +136,7 @@ impl WhiteboxTool for LasToShapefile { if args.len() == 0 && working_directory.is_empty() { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_classify_subset.rs b/src/tools/lidar_analysis/lidar_classify_subset.rs index 453c043e2..fb4cd24e3 100644 --- a/src/tools/lidar_analysis/lidar_classify_subset.rs +++ b/src/tools/lidar_analysis/lidar_classify_subset.rs @@ -189,7 +189,7 @@ impl WhiteboxTool for LidarClassifySubset { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -283,7 +283,7 @@ impl WhiteboxTool for LidarClassifySubset { let mut old_progress = 1usize; let capacity_per_node = 128; - let mut kdtree = KdTree::new_with_capacity(3, capacity_per_node); + let mut kdtree = KdTree::with_capacity(3, capacity_per_node); println!("Creating tree..."); for i in 0..subset_lidar.header.number_of_points as usize { let p: PointData = subset_lidar.get_point_info(i); diff --git a/src/tools/lidar_analysis/lidar_colourize.rs b/src/tools/lidar_analysis/lidar_colourize.rs index f587fa1b2..087933acc 100644 --- a/src/tools/lidar_analysis/lidar_colourize.rs +++ b/src/tools/lidar_analysis/lidar_colourize.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: February 18, 2018 +Created: 18/02/2018 Last Modified: 12/10/2018 License: MIT */ @@ -18,6 +18,15 @@ use std::sync::mpsc; use std::sync::Arc; use std::thread; +/// This tool can be used to add red-green-blue (RGB) colour values to the points contained within an +/// input LAS file (`--in_lidar`), based on the pixel values of an input colour image (`--in_image`). Ideally, +/// the image has been acquired at the same time as the LiDAR point cloud. If this is not the case, one may +/// expect that transient objects (e.g. cars) in both input data sets will be incorrectly coloured. The +/// input image should overlap in extent with the LiDAR data set. You may use the `LidarTileFootprint` tool +/// to determine the spatial extent of the LAS file. +/// +/// # See Also +/// `LidarTileFootprint` pub struct LidarColourize { name: String, description: String, @@ -135,7 +144,7 @@ impl WhiteboxTool for LidarColourize { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_construct_vector_tin.rs b/src/tools/lidar_analysis/lidar_construct_vector_tin.rs index 8bdc65a3d..322f2dd6a 100644 --- a/src/tools/lidar_analysis/lidar_construct_vector_tin.rs +++ b/src/tools/lidar_analysis/lidar_construct_vector_tin.rs @@ -184,7 +184,7 @@ impl WhiteboxTool for LidarConstructVectorTIN { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_elevation_slice.rs b/src/tools/lidar_analysis/lidar_elevation_slice.rs index 67c796339..2e6809418 100644 --- a/src/tools/lidar_analysis/lidar_elevation_slice.rs +++ b/src/tools/lidar_analysis/lidar_elevation_slice.rs @@ -184,7 +184,7 @@ impl WhiteboxTool for LidarElevationSlice { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_ground_point_filter.rs b/src/tools/lidar_analysis/lidar_ground_point_filter.rs index ef1256e79..fadab5046 100644 --- a/src/tools/lidar_analysis/lidar_ground_point_filter.rs +++ b/src/tools/lidar_analysis/lidar_ground_point_filter.rs @@ -226,7 +226,7 @@ impl WhiteboxTool for LidarGroundPointFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_hex_bin.rs b/src/tools/lidar_analysis/lidar_hex_bin.rs index e12934526..eb7e2057f 100644 --- a/src/tools/lidar_analysis/lidar_hex_bin.rs +++ b/src/tools/lidar_analysis/lidar_hex_bin.rs @@ -172,7 +172,7 @@ impl WhiteboxTool for LidarHexBinning { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_hillshade.rs b/src/tools/lidar_analysis/lidar_hillshade.rs index 21e98ef84..fc9acc01e 100644 --- a/src/tools/lidar_analysis/lidar_hillshade.rs +++ b/src/tools/lidar_analysis/lidar_hillshade.rs @@ -156,7 +156,7 @@ impl WhiteboxTool for LidarHillshade { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_histogram.rs b/src/tools/lidar_analysis/lidar_histogram.rs index 732f539bb..4e158c23c 100644 --- a/src/tools/lidar_analysis/lidar_histogram.rs +++ b/src/tools/lidar_analysis/lidar_histogram.rs @@ -163,7 +163,7 @@ impl WhiteboxTool for LidarHistogram { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_idw_interpolation.rs b/src/tools/lidar_analysis/lidar_idw_interpolation.rs index c3268b4b5..99ba7739d 100644 --- a/src/tools/lidar_analysis/lidar_idw_interpolation.rs +++ b/src/tools/lidar_analysis/lidar_idw_interpolation.rs @@ -8,7 +8,7 @@ License: MIT NOTES: 1. This tool is designed to work either by specifying a single input and output file or a working directory containing multiple input LAS files. -2. Need to add the ability to exclude points based on max scan angle divation. +2. Need to add the ability to exclude points based on max scan angle deviation. */ use crate::lidar::*; @@ -232,7 +232,7 @@ impl WhiteboxTool for LidarIdwInterpolation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_info.rs b/src/tools/lidar_analysis/lidar_info.rs index cd1f04951..2adae3845 100644 --- a/src/tools/lidar_analysis/lidar_info.rs +++ b/src/tools/lidar_analysis/lidar_info.rs @@ -62,7 +62,7 @@ impl LidarInfo { "Flag indicating whether or not to print the variable length records (VLRs)." .to_owned(), parameter_type: ParameterType::Boolean, - default_value: None, + default_value: Some("true".to_string()), optional: true, }); @@ -71,7 +71,7 @@ impl LidarInfo { flags: vec!["--geokeys".to_owned()], description: "Flag indicating whether or not to print the geokeys.".to_owned(), parameter_type: ParameterType::Boolean, - default_value: None, + default_value: Some("true".to_string()), optional: true, }); @@ -152,7 +152,7 @@ impl WhiteboxTool for LidarInfo { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_join.rs b/src/tools/lidar_analysis/lidar_join.rs index f9d7276a7..e0fb9869a 100644 --- a/src/tools/lidar_analysis/lidar_join.rs +++ b/src/tools/lidar_analysis/lidar_join.rs @@ -124,7 +124,7 @@ impl WhiteboxTool for LidarJoin { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_kappa.rs b/src/tools/lidar_analysis/lidar_kappa.rs index 711083da5..f2278cf4b 100644 --- a/src/tools/lidar_analysis/lidar_kappa.rs +++ b/src/tools/lidar_analysis/lidar_kappa.rs @@ -167,7 +167,7 @@ impl WhiteboxTool for LidarKappaIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_nn_gridding.rs b/src/tools/lidar_analysis/lidar_nn_gridding.rs index 15a3dd5fe..9ecb78982 100644 --- a/src/tools/lidar_analysis/lidar_nn_gridding.rs +++ b/src/tools/lidar_analysis/lidar_nn_gridding.rs @@ -221,7 +221,7 @@ impl WhiteboxTool for LidarNearestNeighbourGridding { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_outliers.rs b/src/tools/lidar_analysis/lidar_outliers.rs index 2e311effd..e96172b2c 100644 --- a/src/tools/lidar_analysis/lidar_outliers.rs +++ b/src/tools/lidar_analysis/lidar_outliers.rs @@ -169,7 +169,7 @@ impl WhiteboxTool for LidarRemoveOutliers { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_point_density.rs b/src/tools/lidar_analysis/lidar_point_density.rs index 7fa20744a..c1ecd537c 100644 --- a/src/tools/lidar_analysis/lidar_point_density.rs +++ b/src/tools/lidar_analysis/lidar_point_density.rs @@ -209,7 +209,7 @@ impl WhiteboxTool for LidarPointDensity { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_point_stats.rs b/src/tools/lidar_analysis/lidar_point_stats.rs index 22d5703e2..6c384e13b 100644 --- a/src/tools/lidar_analysis/lidar_point_stats.rs +++ b/src/tools/lidar_analysis/lidar_point_stats.rs @@ -229,7 +229,7 @@ impl WhiteboxTool for LidarPointStats { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_radial_basis_function_interpolation.rs b/src/tools/lidar_analysis/lidar_radial_basis_function_interpolation.rs new file mode 100644 index 000000000..dce4d1352 --- /dev/null +++ b/src/tools/lidar_analysis/lidar_radial_basis_function_interpolation.rs @@ -0,0 +1,1023 @@ +/* +This tool is part of the WhiteboxTools geospatial analysis library. +Authors: Dr. John Lindsay +Created: 08/11/2019 +Last Modified: 08/11/2019 +License: MIT + +NOTES: +1. This tool is designed to work either by specifying a single input and output file or + a working directory containing multiple input LAS files. +2. Need to add the ability to exclude points based on max scan angle deviation. +*/ + +use crate::lidar::*; +use crate::raster::*; +use crate::structures::{Basis, BoundingBox, DistanceMetric, FixedRadiusSearch2D, RadialBasisFunction}; +use crate::tools::*; +use num_cpus; +use nalgebra::DVector; +use std::env; +use std::f64; +use std::fs; +use std::io::{Error, ErrorKind}; +use std::path; +use std::sync::mpsc; +use std::sync::{Arc, Mutex}; +use std::thread; + +pub struct LidarRfbInterpolation { + name: String, + description: String, + toolbox: String, + parameters: Vec, + example_usage: String, +} + +impl LidarRfbInterpolation { + pub fn new() -> LidarRfbInterpolation { + // public constructor + let name = "LidarRfbInterpolation".to_string(); + let toolbox = "LiDAR Tools".to_string(); + let description = "Interpolates LAS files using a radial basis function (RFB) scheme. When the input/output parameters are not specified, the tool interpolates all LAS files contained within the working directory." + .to_string(); + + let mut parameters = vec![]; + parameters.push(ToolParameter { + name: "Input File".to_owned(), + flags: vec!["-i".to_owned(), "--input".to_owned()], + description: "Input LiDAR file (including extension).".to_owned(), + parameter_type: ParameterType::ExistingFile(ParameterFileType::Lidar), + default_value: None, + optional: true, + }); + + parameters.push(ToolParameter { + name: "Output File".to_owned(), + flags: vec!["-o".to_owned(), "--output".to_owned()], + description: "Output raster file (including extension).".to_owned(), + parameter_type: ParameterType::NewFile(ParameterFileType::Raster), + default_value: None, + optional: true, + }); + + parameters.push(ToolParameter{ + name: "Interpolation Parameter".to_owned(), + flags: vec!["--parameter".to_owned()], + description: "Interpolation parameter; options are 'elevation' (default), 'intensity', 'class', 'return_number', 'number_of_returns', 'scan angle', 'rgb', 'user data'.".to_owned(), + parameter_type: ParameterType::OptionList( + vec![ + "elevation".to_owned(), + "intensity".to_owned(), + "class".to_owned(), + "return_number".to_owned(), + "number_of_returns".to_owned(), + "scan angle".to_owned(), + "rgb".to_owned(), + "user data".to_owned() + ] + ), + default_value: Some("elevation".to_owned()), + optional: true + }); + + parameters.push(ToolParameter { + name: "Point Returns Included".to_owned(), + flags: vec!["--returns".to_owned()], + description: + "Point return types to include; options are 'all' (default), 'last', 'first'." + .to_owned(), + parameter_type: ParameterType::OptionList(vec![ + "all".to_owned(), + "last".to_owned(), + "first".to_owned(), + ]), + default_value: Some("all".to_owned()), + optional: true, + }); + + parameters.push(ToolParameter { + name: "Grid Resolution".to_owned(), + flags: vec!["--resolution".to_owned()], + description: "Output raster's grid resolution.".to_owned(), + parameter_type: ParameterType::Float, + default_value: Some("1.0".to_owned()), + optional: true, + }); + + parameters.push(ToolParameter { + name: "Search Radius".to_owned(), + flags: vec!["--radius".to_owned()], + description: "Search Radius.".to_owned(), + parameter_type: ParameterType::Float, + default_value: Some("2.5".to_owned()), + optional: true, + }); + + parameters.push(ToolParameter{ + name: "Exclusion Classes (0-18, based on LAS spec; e.g. 3,4,5,6,7)".to_owned(), + flags: vec!["--exclude_cls".to_owned()], + description: "Optional exclude classes from interpolation; Valid class values range from 0 to 18, based on LAS specifications. Example, --exclude_cls='3,4,5,6,7,18'.".to_owned(), + parameter_type: ParameterType::String, + default_value: None, + optional: true + }); + + parameters.push(ToolParameter { + name: "Minimum Elevation Value (optional)".to_owned(), + flags: vec!["--minz".to_owned()], + description: "Optional minimum elevation for inclusion in interpolation.".to_owned(), + parameter_type: ParameterType::Float, + default_value: None, + optional: true, + }); + + parameters.push(ToolParameter { + name: "Maximum Elevation Value (optional)".to_owned(), + flags: vec!["--maxz".to_owned()], + description: "Optional maximum elevation for inclusion in interpolation.".to_owned(), + parameter_type: ParameterType::Float, + default_value: None, + optional: true, + }); + + parameters.push(ToolParameter{ + name: "Radial Basis Function Type".to_owned(), + flags: vec!["--func_type".to_owned()], + description: "Radial basis function type; options are 'ThinPlateSpline' (default), 'PolyHarmonic', 'Gaussian', 'MultiQuadric', 'InverseMultiQuadric'.".to_owned(), + parameter_type: ParameterType::OptionList( + vec![ + "ThinPlateSpline".to_owned(), + "PolyHarmonic".to_owned(), + "Gaussian".to_owned(), + "MultiQuadric".to_owned(), + "InverseMultiQuadric".to_owned() + ] + ), + default_value: Some("ThinPlateSpline".to_owned()), + optional: true + }); + + parameters.push(ToolParameter{ + name: "Polynomial Order".to_owned(), + flags: vec!["--poly_order".to_owned()], + description: "Polynomial order; options are 'none' (default), 'constant', 'affine'.".to_owned(), + parameter_type: ParameterType::OptionList( + vec![ + "none".to_owned(), + "constant".to_owned(), + "affine".to_owned() + ] + ), + default_value: Some("none".to_owned()), + optional: true + }); + + parameters.push(ToolParameter { + name: "Weight".to_owned(), + flags: vec!["--weight".to_owned()], + description: "Weight parameter used in basis function.".to_owned(), + parameter_type: ParameterType::Float, + default_value: Some("0.1".to_owned()), + optional: false, + }); + + let sep: String = path::MAIN_SEPARATOR.to_string(); + let p = format!("{}", env::current_dir().unwrap().display()); + let e = format!("{}", env::current_exe().unwrap().display()); + let mut short_exe = e + .replace(&p, "") + .replace(".exe", "") + .replace(".", "") + .replace(&sep, ""); + if e.contains(".exe") { + short_exe += ".exe"; + } + let usage = format!(">>.*{0} -r={1} -v --wd=\"*path*to*data*\" -i=file.las -o=outfile.tif --resolution=2.0 --radius=5.0\" +.*{0} -r={1} --wd=\"*path*to*data*\" -i=file.las -o=outfile.tif --resolution=5.0 --weight=2.0 --radius=2.0 --exclude_cls='3,4,5,6,7,18' --palette=light_quant.plt", short_exe, name).replace("*", &sep); + + LidarRfbInterpolation { + name: name, + description: description, + toolbox: toolbox, + parameters: parameters, + example_usage: usage, + } + } +} + +impl WhiteboxTool for LidarRfbInterpolation { + fn get_source_file(&self) -> String { + String::from(file!()) + } + + fn get_tool_name(&self) -> String { + self.name.clone() + } + + fn get_tool_description(&self) -> String { + self.description.clone() + } + + fn get_tool_parameters(&self) -> String { + let mut s = String::from("{\"parameters\": ["); + for i in 0..self.parameters.len() { + if i < self.parameters.len() - 1 { + s.push_str(&(self.parameters[i].to_string())); + s.push_str(","); + } else { + s.push_str(&(self.parameters[i].to_string())); + } + } + s.push_str("]}"); + s + } + + fn get_example_usage(&self) -> String { + self.example_usage.clone() + } + + fn get_toolbox(&self) -> String { + self.toolbox.clone() + } + + fn run<'a>( + &self, + args: Vec, + working_directory: &'a str, + verbose: bool, + ) -> Result<(), Error> { + let mut input_file: String = "".to_string(); + let mut output_file: String = "".to_string(); + let mut interp_parameter = "elevation".to_string(); + // let mut interp_parameter_is_rgb = false; + let mut return_type = "all".to_string(); + let mut grid_res: f64 = 1.0; + let mut search_radius = 2.5; + let mut include_class_vals = vec![true; 256]; + let mut palette = "default".to_string(); + let mut exclude_cls_str = String::new(); + let mut max_z = f64::INFINITY; + let mut min_z = f64::NEG_INFINITY; + let mut func_type = String::from("ThinPlateSpline"); + let mut poly_order = 0usize; + let mut weight = 0.1f64; + + // read the arguments + if args.len() == 0 { + return Err(Error::new( + ErrorKind::InvalidInput, + "Tool run with no parameters.", + )); + } + for i in 0..args.len() { + let mut arg = args[i].replace("\"", ""); + arg = arg.replace("\'", ""); + let cmd = arg.split("="); // in case an equals sign was used + let vec = cmd.collect::>(); + let mut keyval = false; + if vec.len() > 1 { + keyval = true; + } + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-i" || flag_val == "-input" { + input_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-o" || flag_val == "-output" { + output_file = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-parameter" { + interp_parameter = if keyval { + vec[1].to_string().to_lowercase() + } else { + args[i + 1].to_string().to_lowercase() + }; + // if interp_parameter == "rgb" { + // interp_parameter_is_rgb = true; + // } + } else if flag_val == "-returns" { + return_type = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-resolution" { + grid_res = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; + } else if flag_val == "-weight" { + weight = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; + } else if flag_val == "-radius" { + search_radius = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; + } else if flag_val == "-palette" { + palette = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-exclude_cls" { + exclude_cls_str = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + let mut cmd = exclude_cls_str.split(","); + let mut vec = cmd.collect::>(); + if vec.len() == 1 { + cmd = exclude_cls_str.split(";"); + vec = cmd.collect::>(); + } + for value in vec { + if !value.trim().is_empty() { + let c = value.trim().parse::().unwrap(); + include_class_vals[c] = false; + } + } + } else if flag_val == "-minz" { + min_z = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; + } else if flag_val == "-maxz" { + max_z = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; + } else if flag_val == "-func_type" { + func_type = if keyval { + vec[1].to_string().to_lowercase() + } else { + args[i + 1].to_string().to_lowercase() + }; + } else if flag_val == "-poly_order" { + let s = if keyval { + vec[1].to_string().to_lowercase() + } else { + args[i + 1].to_string().to_lowercase() + }; + poly_order = if s.contains("none") { + 0usize + } else if s.contains("const") { + 1usize + } else { + 2usize + }; + } else if flag_val == "-weight" { + weight = if keyval { + vec[1].to_string().parse::().unwrap() + } else { + args[i + 1].to_string().parse::().unwrap() + }; + } + } + + let basis_func = if func_type.contains("thin") { + Basis::ThinPlateSpine(weight) + } else if func_type.contains("PolyHarmonic") { + Basis::PolyHarmonic(weight as i32) + } else if func_type.contains("Gaussian") { + Basis::Gaussian(weight) + } else if func_type.contains("MultiQuadric") { + Basis::MultiQuadric(weight) + } else { //if func_type.contains("InverseMultiQuadric") { + Basis::InverseMultiQuadric(weight) + }; + + let (all_returns, late_returns, early_returns): (bool, bool, bool); + if return_type.contains("last") { + all_returns = false; + late_returns = true; + early_returns = false; + } else if return_type.contains("first") { + all_returns = false; + late_returns = false; + early_returns = true; + } else { + // all + all_returns = true; + late_returns = false; + early_returns = false; + } + + if verbose { + println!("***************{}", "*".repeat(self.get_tool_name().len())); + println!("* Welcome to {} *", self.get_tool_name()); + println!("***************{}", "*".repeat(self.get_tool_name().len())); + } + + let start = Instant::now(); + + let mut inputs = vec![]; + let mut outputs = vec![]; + if input_file.is_empty() { + if working_directory.is_empty() { + return Err(Error::new(ErrorKind::InvalidInput, + "This tool must be run by specifying either an individual input file or a working directory.")); + } + if std::path::Path::new(&working_directory).is_dir() { + for entry in fs::read_dir(working_directory.clone())? { + let s = entry? + .path() + .into_os_string() + .to_str() + .expect("Error reading path string") + .to_string(); + if s.to_lowercase().ends_with(".las") { + inputs.push(s); + outputs.push( + inputs[inputs.len() - 1] + .replace(".las", ".tif") + .replace(".LAS", ".tif"), + ) + } else if s.to_lowercase().ends_with(".zip") { + inputs.push(s); + outputs.push( + inputs[inputs.len() - 1] + .replace(".zip", ".tif") + .replace(".ZIP", ".tif"), + ) + } + } + } else { + return Err(Error::new( + ErrorKind::InvalidInput, + format!("The input directory ({}) is incorrect.", working_directory), + )); + } + } else { + if !input_file.contains(path::MAIN_SEPARATOR) && !input_file.contains("/") { + input_file = format!("{}{}", working_directory, input_file); + } + inputs.push(input_file.clone()); + if output_file.is_empty() { + output_file = input_file + .clone() + .replace(".las", ".tif") + .replace(".LAS", ".tif"); + } + if !output_file.contains(path::MAIN_SEPARATOR) && !output_file.contains("/") { + output_file = format!("{}{}", working_directory, output_file); + } + outputs.push(output_file); + } + + /* + If multiple files are being interpolated, we will need to know their bounding boxes, + in order to retrieve points from adjacent tiles. This is so that there are no edge + effects. + */ + let mut bounding_boxes = vec![]; + for in_file in &inputs { + let header = LasHeader::read_las_header(&in_file.replace("\"", ""))?; + bounding_boxes.push(BoundingBox { + min_x: header.min_x, + max_x: header.max_x, + min_y: header.min_y, + max_y: header.max_y, + }); + } + + if verbose { + println!("Performing interpolation..."); + } + + let num_tiles = inputs.len(); + let tile_list = Arc::new(Mutex::new(0..num_tiles)); + let inputs = Arc::new(inputs); + let outputs = Arc::new(outputs); + let bounding_boxes = Arc::new(bounding_boxes); + let num_procs2 = num_cpus::get() as isize; + let (tx2, rx2) = mpsc::channel(); + for _ in 0..num_procs2 { + let inputs = inputs.clone(); + let outputs = outputs.clone(); + let bounding_boxes = bounding_boxes.clone(); + let tile_list = tile_list.clone(); + // copy over the string parameters + let interp_parameter = interp_parameter.clone(); + let palette = palette.clone(); + let return_type = return_type.clone(); + let tool_name = self.get_tool_name(); + let exclude_cls_str = exclude_cls_str.clone(); + let include_class_vals = include_class_vals.clone(); + let tx2 = tx2.clone(); + thread::spawn(move || { + let mut tile = 0; + while tile < num_tiles { + // Get the next tile up for interpolation + tile = match tile_list.lock().unwrap().next() { + Some(val) => val, + None => break, // There are no more tiles to interpolate + }; + // for tile in (0..inputs.len()).filter(|t| t % num_procs2 as usize == tid as usize) { + let start_run = Instant::now(); + + let input_file = inputs[tile].replace("\"", "").clone(); + let output_file = outputs[tile].replace("\"", "").clone(); + + // Expand the bounding box to include the areas of overlap + let bb = BoundingBox { + min_x: bounding_boxes[tile].min_x - search_radius, + max_x: bounding_boxes[tile].max_x + search_radius, + min_y: bounding_boxes[tile].min_y - search_radius, + max_y: bounding_boxes[tile].max_y + search_radius, + }; + let mut frs: FixedRadiusSearch2D = FixedRadiusSearch2D::new(search_radius, DistanceMetric::Euclidean); + + let mut points = vec![]; + let mut z_values = vec![]; + + if verbose && inputs.len() == 1 { + println!("Reading input LAS file..."); + } + + let mut progress: i32; + let mut old_progress: i32 = -1; + + for m in 0..inputs.len() { + if bounding_boxes[m].overlaps(bb) { + let input = + match LasFile::new(&inputs[m].replace("\"", "").clone(), "r") { + Ok(lf) => lf, + Err(err) => panic!( + "Error reading file {}: {}", + inputs[m].replace("\"", ""), + err + ), + }; + + let n_points = input.header.number_of_points as usize; + let num_points: f64 = (input.header.number_of_points - 1) as f64; // used for progress calculation only + + let mut pt = 0; + match &interp_parameter as &str { + "elevation" | "z" => { + for i in 0..n_points { + let p: PointData = input[i]; + if !p.withheld() { + if all_returns + || (p.is_late_return() & late_returns) + || (p.is_early_return() & early_returns) + { + if include_class_vals[p.classification() as usize] { + if bb.is_point_in_box(p.x, p.y) + && p.z >= min_z + && p.z <= max_z + { + frs.insert(p.x, p.y, pt); + pt += 1; + points.push(DVector::from_vec(vec![p.x, p.y])); + z_values.push(DVector::from_vec(vec![p.z])); + } + } + } + } + if verbose && inputs.len() == 1 { + progress = (100.0_f64 * i as f64 / num_points) as i32; + if progress != old_progress { + println!("Binning points: {}%", progress); + old_progress = progress; + } + } + } + } + "intensity" => { + for i in 0..n_points { + let p: PointData = input[i]; + if !p.withheld() { + if all_returns + || (p.is_late_return() & late_returns) + || (p.is_early_return() & early_returns) + { + if include_class_vals[p.classification() as usize] { + if bb.is_point_in_box(p.x, p.y) + && p.z >= min_z + && p.z <= max_z + { + frs.insert(p.x, p.y, pt); + pt += 1; + points.push(DVector::from_vec(vec![p.x, p.y])); + z_values.push(DVector::from_vec(vec![p.intensity as f64])); + } + } + } + } + if verbose && inputs.len() == 1 { + progress = (100.0_f64 * i as f64 / num_points) as i32; + if progress != old_progress { + println!("Binning points: {}%", progress); + old_progress = progress; + } + } + } + } + "scan angle" | "scan_angle" => { + for i in 0..n_points { + let p: PointData = input[i]; + if !p.withheld() { + if all_returns + || (p.is_late_return() & late_returns) + || (p.is_early_return() & early_returns) + { + if include_class_vals[p.classification() as usize] { + if bb.is_point_in_box(p.x, p.y) + && p.z >= min_z + && p.z <= max_z + { + frs.insert(p.x, p.y, pt); + pt += 1; + points.push(DVector::from_vec(vec![p.x, p.y])); + z_values.push(DVector::from_vec(vec![p.scan_angle as f64])); + } + } + } + } + if verbose && inputs.len() == 1 { + progress = (100.0_f64 * i as f64 / num_points) as i32; + if progress != old_progress { + println!("Binning points: {}%", progress); + old_progress = progress; + } + } + } + } + "class" => { + for i in 0..n_points { + let p: PointData = input[i]; + if !p.withheld() { + if all_returns + || (p.is_late_return() & late_returns) + || (p.is_early_return() & early_returns) + { + if include_class_vals[p.classification() as usize] { + if bb.is_point_in_box(p.x, p.y) + && p.z >= min_z + && p.z <= max_z + { + frs.insert(p.x, p.y, pt); + pt += 1; + points.push(DVector::from_vec(vec![p.x, p.y])); + z_values.push(DVector::from_vec(vec![p.classification() as f64])); + } + } + } + } + if verbose && inputs.len() == 1 { + progress = (100.0_f64 * i as f64 / num_points) as i32; + if progress != old_progress { + println!("Binning points: {}%", progress); + old_progress = progress; + } + } + } + } + "return_number" => { + for i in 0..n_points { + let p: PointData = input[i]; + if !p.withheld() { + if all_returns + || (p.is_late_return() & late_returns) + || (p.is_early_return() & early_returns) + { + if include_class_vals[p.classification() as usize] { + if bb.is_point_in_box(p.x, p.y) + && p.z >= min_z + && p.z <= max_z + { + frs.insert(p.x, p.y, pt); + pt += 1; + points.push(DVector::from_vec(vec![p.x, p.y])); + z_values.push(DVector::from_vec(vec![p.return_number() as f64])); + } + } + } + } + if verbose && inputs.len() == 1 { + progress = (100.0_f64 * i as f64 / num_points) as i32; + if progress != old_progress { + println!("Reading points: {}%", progress); + old_progress = progress; + } + } + } + } + "number_of_returns" => { + for i in 0..n_points { + let p: PointData = input[i]; + if !p.withheld() { + if all_returns + || (p.is_late_return() & late_returns) + || (p.is_early_return() & early_returns) + { + if include_class_vals[p.classification() as usize] { + if bb.is_point_in_box(p.x, p.y) + && p.z >= min_z + && p.z <= max_z + { + frs.insert(p.x, p.y, pt); + pt += 1; + points.push(DVector::from_vec(vec![p.x, p.y])); + z_values.push(DVector::from_vec(vec![p.number_of_returns() as f64])); + } + } + } + } + if verbose && inputs.len() == 1 { + progress = (100.0_f64 * i as f64 / num_points) as i32; + if progress != old_progress { + println!("Reading points: {}%", progress); + old_progress = progress; + } + } + } + } + "rgb" => { + if !input.has_rgb() { + println!("Error: The input LAS file does not contain RGB colour data. The interpolation will not proceed."); + break; + } + // let mut clr: ColourData; + for i in 0..n_points { + let p: PointData = input[i]; + if !p.withheld() { + if all_returns + || (p.is_late_return() & late_returns) + || (p.is_early_return() & early_returns) + { + if include_class_vals[p.classification() as usize] { + if bb.is_point_in_box(p.x, p.y) + && p.z >= min_z + && p.z <= max_z + { + // clr = match input.get_rgb(i) { + // Ok(value) => { value }, + // Err(_) => break, + // }; + // *************************** + // This needs to be fixed + // *************************** + // frs.insert(p.x, p.y, ((255u32 << 24) | ((clr.blue as u32) << 16) | ((clr.green as u32) << 8) | (clr.red as u32)) as f64); + frs.insert(p.x, p.y, pt); + pt += 1; + points.push(DVector::from_vec(vec![p.x, p.y])); + z_values.push(DVector::from_vec(vec![p.classification() as f64])); + } + } + } + } + if verbose && inputs.len() == 1 { + progress = (100.0_f64 * i as f64 / num_points) as i32; + if progress != old_progress { + println!("Reading points: {}%", progress); + old_progress = progress; + } + } + } + } + _ => { + // user data + for i in 0..n_points { + let p: PointData = input[i]; + if !p.withheld() { + if all_returns + || (p.is_late_return() & late_returns) + || (p.is_early_return() & early_returns) + { + if include_class_vals[p.classification() as usize] { + if bb.is_point_in_box(p.x, p.y) + && p.z >= min_z + && p.z <= max_z + { + frs.insert(p.x, p.y, pt); + pt += 1; + points.push(DVector::from_vec(vec![p.x, p.y])); + z_values.push(DVector::from_vec(vec![p.user_data as f64])); + } + } + } + } + if verbose && inputs.len() == 1 { + progress = (100.0_f64 * i as f64 / num_points) as i32; + if progress != old_progress { + println!("Binning points: {}%", progress); + old_progress = progress; + } + } + } + } + } + } + } + + let west: f64 = bounding_boxes[tile].min_x; + let north: f64 = bounding_boxes[tile].max_y; + let rows: isize = + (((north - bounding_boxes[tile].min_y) / grid_res).ceil()) as isize; + let columns: isize = + (((bounding_boxes[tile].max_x - west) / grid_res).ceil()) as isize; + let south: f64 = north - rows as f64 * grid_res; + let east = west + columns as f64 * grid_res; + let nodata = -32768.0f64; + + let mut configs = RasterConfigs { + ..Default::default() + }; + configs.rows = rows as usize; + configs.columns = columns as usize; + configs.north = north; + configs.south = south; + configs.east = east; + configs.west = west; + configs.resolution_x = grid_res; + configs.resolution_y = grid_res; + configs.nodata = nodata; + configs.data_type = DataType::F32; + configs.photometric_interp = PhotometricInterpretation::Continuous; + configs.palette = palette.clone(); + + let mut output = Raster::initialize_using_config(&output_file, &configs); + if interp_parameter == "rgb" { + output.configs.photometric_interp = PhotometricInterpretation::RGB; + output.configs.data_type = DataType::RGBA32; + } + + if num_tiles > 1 { + let (mut x, mut y): (f64, f64); + let mut zn: f64; + // let (mut val_red, mut val_green, mut val_blue): (f64, f64, f64); + // let (mut red, mut green, mut blue): (f64, f64, f64); + for row in 0..rows { + for col in 0..columns { + x = west + (col as f64 + 0.5) * grid_res; + y = north - (row as f64 + 0.5) * grid_res; + + let mut ret = frs.search(x, y); + if ret.len() < 6 { + ret = frs.knn_search(x, y, 6); + } + if ret.len() > 0 { + let mut centers: Vec> = Vec::with_capacity(ret.len()); + let mut vals: Vec> = Vec::with_capacity(ret.len()); + for j in 0..ret.len() { + centers.push(points[ret[j].0].clone()); + vals.push(z_values[ret[j].0].clone()); + } + let rfb = RadialBasisFunction::create(centers, vals, basis_func, poly_order); + zn = rfb.eval(DVector::from_vec(vec![x, y]))[0]; + output.set_value(row, col, zn); + } else { + + } + } + if verbose && inputs.len() == 1 { + progress = (100.0_f64 * row as f64 / (rows - 1) as f64) as i32; + if progress != old_progress { + println!("Progress: {}%", progress); + old_progress = progress; + } + } + } + } else { + // there's only one tile, so use all cores to interpolate this one tile. + let points = Arc::new(points); + let z_values = Arc::new(z_values); + let frs = Arc::new(frs); // wrap FRS in an Arc + let num_procs = num_cpus::get() as isize; + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let frs = frs.clone(); + let tx1 = tx.clone(); + let points = points.clone(); + let z_values = z_values.clone(); + thread::spawn(move || { + let (mut x, mut y): (f64, f64); + let mut zn: f64; + + // let (mut val_red, mut val_green, mut val_blue): (f64, f64, f64); + // let (mut red, mut green, mut blue): (f64, f64, f64); + for row in (0..rows).filter(|r| r % num_procs == tid) { + // for row in 0..rows { + let mut data = vec![nodata; columns as usize]; + for col in 0..columns { + x = west + (col as f64 + 0.5) * grid_res; + y = north - (row as f64 + 0.5) * grid_res; + let mut ret = frs.search(x, y); + if ret.len() < 6 { + ret = frs.knn_search(x, y, 6); + } + if ret.len() > 0 { + let mut centers: Vec> = Vec::with_capacity(ret.len()); + let mut vals: Vec> = Vec::with_capacity(ret.len()); + for j in 0..ret.len() { + centers.push(points[ret[j].0].clone()); + vals.push(z_values[ret[j].0].clone()); + } + let rfb = RadialBasisFunction::create(centers, vals, basis_func, poly_order); + zn = rfb.eval(DVector::from_vec(vec![x, y]))[0]; + data[col as usize] = zn; + } + } + tx1.send((row, data)).unwrap(); + } + }); + } + + for row in 0..rows { + let data = rx.recv().unwrap(); + output.set_row_data(data.0, data.1); + if verbose { + progress = (100.0_f64 * row as f64 / (rows - 1) as f64) as i32; + if progress != old_progress { + println!("Progress: {}%", progress); + old_progress = progress; + } + } + } + } + + let elapsed_time_run = get_formatted_elapsed_time(start_run); + + output.add_metadata_entry(format!( + "Created by whitebox_tools\' {} tool", + tool_name + )); + output.add_metadata_entry(format!("Input file: {}", input_file)); + output.add_metadata_entry(format!("Grid resolution: {}", grid_res)); + output.add_metadata_entry(format!("Search radius: {}", search_radius)); + output.add_metadata_entry(format!("Weight: {}", weight)); + output.add_metadata_entry(format!( + "Interpolation parameter: {}", + interp_parameter + )); + output.add_metadata_entry(format!("Returns: {}", return_type)); + output.add_metadata_entry(format!("Excluded classes: {}", exclude_cls_str)); + output.add_metadata_entry(format!( + "Elapsed Time (including I/O): {}", + elapsed_time_run + )); + + if verbose && inputs.len() == 1 { + println!("Saving data...") + }; + + let _ = output.write().unwrap(); + + tx2.send(tile).unwrap(); + } + }); + } + + let mut progress: i32; + let mut old_progress: i32 = -1; + for tile in 0..inputs.len() { + let tile_completed = rx2.recv().unwrap(); + if verbose { + println!( + "Finished interpolating {} ({} of {})", + inputs[tile_completed] + .replace("\"", "") + .replace(working_directory, "") + .replace(".las", ""), + tile + 1, + inputs.len() + ); + } + if verbose { + progress = (100.0_f64 * tile as f64 / (inputs.len() - 1) as f64) as i32; + if progress != old_progress { + println!("Progress: {}%", progress); + old_progress = progress; + } + } + } + + let elapsed_time = get_formatted_elapsed_time(start); + + if verbose { + println!( + "{}", + &format!("Elapsed Time (including I/O): {}", elapsed_time) + ); + } + + Ok(()) + } +} diff --git a/src/tools/lidar_analysis/lidar_ransac_planes.rs b/src/tools/lidar_analysis/lidar_ransac_planes.rs index c0ff81d00..9bdf674f4 100644 --- a/src/tools/lidar_analysis/lidar_ransac_planes.rs +++ b/src/tools/lidar_analysis/lidar_ransac_planes.rs @@ -201,7 +201,7 @@ impl WhiteboxTool for LidarRansacPlanes { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_segmentation.rs b/src/tools/lidar_analysis/lidar_segmentation.rs index 2550a3078..7b0a47e8b 100644 --- a/src/tools/lidar_analysis/lidar_segmentation.rs +++ b/src/tools/lidar_analysis/lidar_segmentation.rs @@ -189,7 +189,7 @@ impl WhiteboxTool for LidarSegmentation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_segmentation_based_filter.rs b/src/tools/lidar_analysis/lidar_segmentation_based_filter.rs index 5f445fba0..00c5b64ee 100644 --- a/src/tools/lidar_analysis/lidar_segmentation_based_filter.rs +++ b/src/tools/lidar_analysis/lidar_segmentation_based_filter.rs @@ -168,7 +168,7 @@ impl WhiteboxTool for LidarSegmentationBasedFilter { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_thin.rs b/src/tools/lidar_analysis/lidar_thin.rs index 27cc53dd8..d2da659a1 100644 --- a/src/tools/lidar_analysis/lidar_thin.rs +++ b/src/tools/lidar_analysis/lidar_thin.rs @@ -158,7 +158,7 @@ impl WhiteboxTool for LidarThin { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_thin_high_density.rs b/src/tools/lidar_analysis/lidar_thin_high_density.rs index 294628907..7ebf9c843 100644 --- a/src/tools/lidar_analysis/lidar_thin_high_density.rs +++ b/src/tools/lidar_analysis/lidar_thin_high_density.rs @@ -153,7 +153,7 @@ impl WhiteboxTool for LidarThinHighDensity { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_tile.rs b/src/tools/lidar_analysis/lidar_tile.rs index 063d31f4d..31e1bae18 100644 --- a/src/tools/lidar_analysis/lidar_tile.rs +++ b/src/tools/lidar_analysis/lidar_tile.rs @@ -169,7 +169,7 @@ impl WhiteboxTool for LidarTile { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_tile_footprint.rs b/src/tools/lidar_analysis/lidar_tile_footprint.rs index 406d6a424..53858a026 100644 --- a/src/tools/lidar_analysis/lidar_tile_footprint.rs +++ b/src/tools/lidar_analysis/lidar_tile_footprint.rs @@ -163,7 +163,7 @@ impl WhiteboxTool for LidarTileFootprint { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -333,14 +333,15 @@ impl WhiteboxTool for LidarTileFootprint { short_filename, n_points, input.header.min_z, - input.header.max_z + input.header.max_z, + input.get_wkt() )).unwrap(); } Err(err) => { tx.send(( vec![], format!("Error reading file {}:\n{}", input_file, err), - 0, 0f64, 0f64 + 0, 0f64, 0f64, "".to_string() )) .unwrap(); } @@ -364,14 +365,15 @@ impl WhiteboxTool for LidarTileFootprint { short_filename, header.get_number_of_points() as usize, header.min_z, - header.max_z + header.max_z, + "".to_string() )).unwrap(); } Err(err) => { tx.send(( vec![], format!("Error reading file {}:\n{}", input_file, err), - 0, 0f64, 0f64 + 0, 0f64, 0f64, "".to_string() )) .unwrap(); } @@ -410,6 +412,9 @@ impl WhiteboxTool for LidarTileFootprint { ], false, ); + if !data.5.is_empty() && output.projection.is_empty() { + output.projection = data.5.clone(); + } } else { // there was an error, likely reading a LAS file. println!("{}", data.1); @@ -426,6 +431,12 @@ impl WhiteboxTool for LidarTileFootprint { } } + if output.projection.is_empty() { + let input_file = inputs[0].replace("\"", "").clone(); + let mut input = LasFile::new(&input_file, "r")?; + output.projection = input.get_wkt().clone(); + } + let data = wkt.lock().unwrap(); if *data != "" && *data != "Unknown EPSG Code" && output.projection.is_empty() { output.projection = data.to_string(); diff --git a/src/tools/lidar_analysis/lidar_tin_gridding.rs b/src/tools/lidar_analysis/lidar_tin_gridding.rs index 3edb74d70..7a5817951 100644 --- a/src/tools/lidar_analysis/lidar_tin_gridding.rs +++ b/src/tools/lidar_analysis/lidar_tin_gridding.rs @@ -19,7 +19,6 @@ use std::sync::mpsc; use std::sync::{Arc, Mutex}; use std::{env, f64, fs, path, thread}; -/// Creates a raster grid based on a Delaunay triangular irregular network (TIN) fitted to LiDAR points. pub struct LidarTINGridding { name: String, description: String, @@ -214,7 +213,7 @@ impl WhiteboxTool for LidarTINGridding { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/lidar_tophat_transform.rs b/src/tools/lidar_analysis/lidar_tophat_transform.rs index 70b909ec0..bf2e67442 100644 --- a/src/tools/lidar_analysis/lidar_tophat_transform.rs +++ b/src/tools/lidar_analysis/lidar_tophat_transform.rs @@ -145,7 +145,7 @@ impl WhiteboxTool for LidarTophatTransform { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/mod.rs b/src/tools/lidar_analysis/mod.rs index 6f3ddc8d0..05ed0e227 100644 --- a/src/tools/lidar_analysis/mod.rs +++ b/src/tools/lidar_analysis/mod.rs @@ -2,6 +2,7 @@ // mod ascii_to_las; mod block_maximum; mod block_minimum; +mod classify_buildings; mod classify_overlap_points; mod clip_lidar_to_polygon; mod erase_polygon_from_lidar; @@ -28,6 +29,7 @@ mod lidar_nn_gridding; mod lidar_outliers; mod lidar_point_density; mod lidar_point_stats; +mod lidar_radial_basis_function_interpolation; mod lidar_ransac_planes; mod lidar_segmentation; mod lidar_segmentation_based_filter; @@ -45,6 +47,7 @@ mod select_tiles_by_polygon; // pub use self::ascii_to_las::AsciiToLas; pub use self::block_maximum::LidarBlockMaximum; pub use self::block_minimum::LidarBlockMinimum; +pub use self::classify_buildings::ClassifyBuildingsInLidar; pub use self::classify_overlap_points::ClassifyOverlapPoints; pub use self::clip_lidar_to_polygon::ClipLidarToPolygon; pub use self::erase_polygon_from_lidar::ErasePolygonFromLidar; @@ -71,6 +74,7 @@ pub use self::lidar_nn_gridding::LidarNearestNeighbourGridding; pub use self::lidar_outliers::LidarRemoveOutliers; pub use self::lidar_point_density::LidarPointDensity; pub use self::lidar_point_stats::LidarPointStats; +pub use self::lidar_radial_basis_function_interpolation::LidarRfbInterpolation; pub use self::lidar_ransac_planes::LidarRansacPlanes; pub use self::lidar_segmentation::LidarSegmentation; pub use self::lidar_segmentation_based_filter::LidarSegmentationBasedFilter; diff --git a/src/tools/lidar_analysis/normal_vectors.rs b/src/tools/lidar_analysis/normal_vectors.rs index 61570d0d8..1da763f1e 100644 --- a/src/tools/lidar_analysis/normal_vectors.rs +++ b/src/tools/lidar_analysis/normal_vectors.rs @@ -136,7 +136,7 @@ impl WhiteboxTool for NormalVectors { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/remove_duplicates.rs b/src/tools/lidar_analysis/remove_duplicates.rs index c6000c3d0..7711d7c42 100644 --- a/src/tools/lidar_analysis/remove_duplicates.rs +++ b/src/tools/lidar_analysis/remove_duplicates.rs @@ -143,7 +143,7 @@ impl WhiteboxTool for LidarRemoveDuplicates { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/lidar_analysis/select_tiles_by_polygon.rs b/src/tools/lidar_analysis/select_tiles_by_polygon.rs index f23dd7ee2..8a49d79ed 100644 --- a/src/tools/lidar_analysis/select_tiles_by_polygon.rs +++ b/src/tools/lidar_analysis/select_tiles_by_polygon.rs @@ -148,7 +148,7 @@ impl WhiteboxTool for SelectTilesByPolygon { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/abs.rs b/src/tools/math_stat_analysis/abs.rs index ffa0a6779..2624a72d8 100644 --- a/src/tools/math_stat_analysis/abs.rs +++ b/src/tools/math_stat_analysis/abs.rs @@ -131,7 +131,7 @@ impl WhiteboxTool for AbsoluteValue { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/add.rs b/src/tools/math_stat_analysis/add.rs index a4ebe749c..2aaee279d 100644 --- a/src/tools/math_stat_analysis/add.rs +++ b/src/tools/math_stat_analysis/add.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for Add { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/and.rs b/src/tools/math_stat_analysis/and.rs index 20e5d493d..01625d04c 100644 --- a/src/tools/math_stat_analysis/and.rs +++ b/src/tools/math_stat_analysis/and.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for And { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/anova.rs b/src/tools/math_stat_analysis/anova.rs index 6b88eaee5..17ca53eae 100644 --- a/src/tools/math_stat_analysis/anova.rs +++ b/src/tools/math_stat_analysis/anova.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for Anova { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/arccos.rs b/src/tools/math_stat_analysis/arccos.rs index 5745c1c6b..3cd548a97 100644 --- a/src/tools/math_stat_analysis/arccos.rs +++ b/src/tools/math_stat_analysis/arccos.rs @@ -134,7 +134,7 @@ impl WhiteboxTool for ArcCos { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/arcosh.rs b/src/tools/math_stat_analysis/arcosh.rs index 2a324f7e8..37d00d56e 100644 --- a/src/tools/math_stat_analysis/arcosh.rs +++ b/src/tools/math_stat_analysis/arcosh.rs @@ -135,7 +135,7 @@ impl WhiteboxTool for Arcosh { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/arcsin.rs b/src/tools/math_stat_analysis/arcsin.rs index 4b999f176..eaa66624a 100644 --- a/src/tools/math_stat_analysis/arcsin.rs +++ b/src/tools/math_stat_analysis/arcsin.rs @@ -134,7 +134,7 @@ impl WhiteboxTool for ArcSin { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/arctan.rs b/src/tools/math_stat_analysis/arctan.rs index 2f68359f7..f66cbc0fa 100644 --- a/src/tools/math_stat_analysis/arctan.rs +++ b/src/tools/math_stat_analysis/arctan.rs @@ -134,7 +134,7 @@ impl WhiteboxTool for ArcTan { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/arsinh.rs b/src/tools/math_stat_analysis/arsinh.rs index 0199a5a89..b6568aa6d 100644 --- a/src/tools/math_stat_analysis/arsinh.rs +++ b/src/tools/math_stat_analysis/arsinh.rs @@ -135,7 +135,7 @@ impl WhiteboxTool for Arsinh { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/artanh.rs b/src/tools/math_stat_analysis/artanh.rs index 66bdafd8e..f99ff8c69 100644 --- a/src/tools/math_stat_analysis/artanh.rs +++ b/src/tools/math_stat_analysis/artanh.rs @@ -135,7 +135,7 @@ impl WhiteboxTool for Artanh { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/atan2.rs b/src/tools/math_stat_analysis/atan2.rs index d7c08d6ea..19dffe270 100644 --- a/src/tools/math_stat_analysis/atan2.rs +++ b/src/tools/math_stat_analysis/atan2.rs @@ -145,7 +145,7 @@ impl WhiteboxTool for Atan2 { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/attribute_correlation.rs b/src/tools/math_stat_analysis/attribute_correlation.rs index 97eb07bd2..0de61dc72 100644 --- a/src/tools/math_stat_analysis/attribute_correlation.rs +++ b/src/tools/math_stat_analysis/attribute_correlation.rs @@ -152,7 +152,7 @@ impl WhiteboxTool for AttributeCorrelation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/attribute_histogram.rs b/src/tools/math_stat_analysis/attribute_histogram.rs index d33ee3410..b5949fc85 100644 --- a/src/tools/math_stat_analysis/attribute_histogram.rs +++ b/src/tools/math_stat_analysis/attribute_histogram.rs @@ -155,7 +155,7 @@ impl WhiteboxTool for AttributeHistogram { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/attribute_scattergram.rs b/src/tools/math_stat_analysis/attribute_scattergram.rs index d48bdc446..6048f2408 100644 --- a/src/tools/math_stat_analysis/attribute_scattergram.rs +++ b/src/tools/math_stat_analysis/attribute_scattergram.rs @@ -173,7 +173,7 @@ impl WhiteboxTool for AttributeScattergram { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/ceil.rs b/src/tools/math_stat_analysis/ceil.rs index 15d37132f..ab3e557e3 100644 --- a/src/tools/math_stat_analysis/ceil.rs +++ b/src/tools/math_stat_analysis/ceil.rs @@ -131,7 +131,7 @@ impl WhiteboxTool for Ceil { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/cos.rs b/src/tools/math_stat_analysis/cos.rs index 8000470be..3e70d8320 100644 --- a/src/tools/math_stat_analysis/cos.rs +++ b/src/tools/math_stat_analysis/cos.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for Cos { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/cosh.rs b/src/tools/math_stat_analysis/cosh.rs index 0dc6e40cf..9f264172e 100644 --- a/src/tools/math_stat_analysis/cosh.rs +++ b/src/tools/math_stat_analysis/cosh.rs @@ -134,7 +134,7 @@ impl WhiteboxTool for Cosh { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/crispness_index.rs b/src/tools/math_stat_analysis/crispness_index.rs index 0c9ff2995..080648498 100644 --- a/src/tools/math_stat_analysis/crispness_index.rs +++ b/src/tools/math_stat_analysis/crispness_index.rs @@ -161,7 +161,7 @@ impl WhiteboxTool for CrispnessIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/cross_tabulation.rs b/src/tools/math_stat_analysis/cross_tabulation.rs index b3b070d51..8d8ae79c5 100644 --- a/src/tools/math_stat_analysis/cross_tabulation.rs +++ b/src/tools/math_stat_analysis/cross_tabulation.rs @@ -147,7 +147,7 @@ impl WhiteboxTool for CrossTabulation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/cumulative_dist.rs b/src/tools/math_stat_analysis/cumulative_dist.rs index fdc9dcefc..71dc90888 100644 --- a/src/tools/math_stat_analysis/cumulative_dist.rs +++ b/src/tools/math_stat_analysis/cumulative_dist.rs @@ -131,7 +131,7 @@ impl WhiteboxTool for CumulativeDistribution { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/decrement.rs b/src/tools/math_stat_analysis/decrement.rs index aa91de3c9..3dba08eb0 100644 --- a/src/tools/math_stat_analysis/decrement.rs +++ b/src/tools/math_stat_analysis/decrement.rs @@ -131,7 +131,7 @@ impl WhiteboxTool for Decrement { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/divide.rs b/src/tools/math_stat_analysis/divide.rs index adafb3c1e..6a00a8823 100644 --- a/src/tools/math_stat_analysis/divide.rs +++ b/src/tools/math_stat_analysis/divide.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for Divide { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/equal_to.rs b/src/tools/math_stat_analysis/equal_to.rs index d56be3349..be4f5a2e6 100644 --- a/src/tools/math_stat_analysis/equal_to.rs +++ b/src/tools/math_stat_analysis/equal_to.rs @@ -138,7 +138,7 @@ impl WhiteboxTool for EqualTo { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/exp.rs b/src/tools/math_stat_analysis/exp.rs index 0c18cc633..6edd47b61 100644 --- a/src/tools/math_stat_analysis/exp.rs +++ b/src/tools/math_stat_analysis/exp.rs @@ -134,7 +134,7 @@ impl WhiteboxTool for Exp { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/exp2.rs b/src/tools/math_stat_analysis/exp2.rs index 35e3d9982..5c87d364a 100644 --- a/src/tools/math_stat_analysis/exp2.rs +++ b/src/tools/math_stat_analysis/exp2.rs @@ -133,7 +133,7 @@ impl WhiteboxTool for Exp2 { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/floor.rs b/src/tools/math_stat_analysis/floor.rs index 965b6cff5..9570c8826 100644 --- a/src/tools/math_stat_analysis/floor.rs +++ b/src/tools/math_stat_analysis/floor.rs @@ -131,7 +131,7 @@ impl WhiteboxTool for Floor { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/greater_than.rs b/src/tools/math_stat_analysis/greater_than.rs index 337cbd32a..d3f984641 100644 --- a/src/tools/math_stat_analysis/greater_than.rs +++ b/src/tools/math_stat_analysis/greater_than.rs @@ -151,7 +151,7 @@ impl WhiteboxTool for GreaterThan { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/image_autocorrelation.rs b/src/tools/math_stat_analysis/image_autocorrelation.rs index d6b758c5b..395e629b2 100644 --- a/src/tools/math_stat_analysis/image_autocorrelation.rs +++ b/src/tools/math_stat_analysis/image_autocorrelation.rs @@ -192,7 +192,7 @@ impl WhiteboxTool for ImageAutocorrelation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/image_correlation.rs b/src/tools/math_stat_analysis/image_correlation.rs index 85724e04e..53396e53c 100644 --- a/src/tools/math_stat_analysis/image_correlation.rs +++ b/src/tools/math_stat_analysis/image_correlation.rs @@ -157,7 +157,7 @@ impl WhiteboxTool for ImageCorrelation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/image_correlation_neighbourhood_analysis.rs b/src/tools/math_stat_analysis/image_correlation_neighbourhood_analysis.rs new file mode 100644 index 000000000..355192732 --- /dev/null +++ b/src/tools/math_stat_analysis/image_correlation_neighbourhood_analysis.rs @@ -0,0 +1,803 @@ +/* +This tool is part of the WhiteboxTools geospatial analysis library. +Authors: Simon Gudim and Dr. John Lindsay +Created: 06/12/2019 +Last Modified: 06/12/2019 +License: MIT +*/ + +use std::cmp::Ordering::Equal; +use std::env; +use std::path; +use std::f64; +use std::sync::Arc; +use std::sync::mpsc; +use std::thread; +use crate::raster::*; +use std::io::{Error, ErrorKind}; +use crate::tools::*; +use statrs::distribution::{StudentsT, Univariate}; + + +pub struct ImageCorrelationNeighbourhoodAnalysis { + name: String, + description: String, + toolbox: String, + parameters: Vec, + example_usage: String, +} + +impl ImageCorrelationNeighbourhoodAnalysis { + pub fn new() -> ImageCorrelationNeighbourhoodAnalysis { + // public constructor + let name = "ImageCorrelationNeighbourhoodAnalysis".to_string(); + let toolbox = "Math and Stats Tools".to_string(); + let description = "Performs image correlation on two input images neighbourhood search windows.".to_string(); + + let mut parameters = vec![]; + parameters.push(ToolParameter{ + name: "Image 1".to_owned(), + flags: vec!["--i1".to_owned(), "--input1".to_owned()], + description: "Input raster file.".to_owned(), + parameter_type: ParameterType::ExistingFile(ParameterFileType::Raster), + default_value: None, + optional: false + }); + + parameters.push(ToolParameter{ + name: "Image 2".to_owned(), + flags: vec!["--i2".to_owned(), "--input2".to_owned()], + description: "Input raster file.".to_owned(), + parameter_type: ParameterType::ExistingFile(ParameterFileType::Raster), + default_value: None, + optional: false + }); + + parameters.push(ToolParameter{ + name: "Output Correlation File".to_owned(), + flags: vec!["--o1".to_owned(), "--output1".to_owned()], + description: "Output correlation (r-value or rho) raster file.".to_owned(), + parameter_type: ParameterType::NewFile(ParameterFileType::Raster), + default_value: None, + optional: false + }); + + parameters.push(ToolParameter{ + name: "Output Significance File".to_owned(), + flags: vec!["--o2".to_owned(), "--output2".to_owned()], + description: "Output significance (p-value) raster file.".to_owned(), + parameter_type: ParameterType::NewFile(ParameterFileType::Raster), + default_value: None, + optional: false + }); + + parameters.push(ToolParameter{ + name: "Filter Size".to_owned(), + flags: vec!["--filter".to_owned()], + description: "Size of the filter kernel.".to_owned(), + parameter_type: ParameterType::Integer, + default_value: Some("11".to_owned()), + optional: true + }); + + parameters.push(ToolParameter { + name: "Correlation Statistic Type".to_owned(), + flags: vec!["--stat".to_owned()], + description: "Correlation type; one of 'pearson' (default) and 'spearman'.".to_owned(), + parameter_type: ParameterType::OptionList(vec!["pearson".to_owned(), "kendall".to_owned(), "spearman".to_owned()]), + default_value: Some("pearson".to_owned()), + optional: true, + }); + + let sep: String = path::MAIN_SEPARATOR.to_string(); + let p = format!("{}", env::current_dir().unwrap().display()); + let e = format!("{}", env::current_exe().unwrap().display()); + let mut short_exe = e.replace(&p, "") + .replace(".exe", "") + .replace(".", "") + .replace(&sep, ""); + if e.contains(".exe") { + short_exe += ".exe"; + } + let usage = format!(">>.*{0} -r={1} -v --wd=\"*path*to*data*\" --i1=file1.tif --i2=file2.tif --o1=corr.tif --o2=sig.tif --stat=\"spearman\"", + short_exe, + name) + .replace("*", &sep); + + ImageCorrelationNeighbourhoodAnalysis { + name: name, + description: description, + toolbox: toolbox, + parameters: parameters, + example_usage: usage, + } + } +} + +impl WhiteboxTool for ImageCorrelationNeighbourhoodAnalysis { + fn get_source_file(&self) -> String { + String::from(file!()) + } + + fn get_tool_name(&self) -> String { + self.name.clone() + } + + fn get_tool_description(&self) -> String { + self.description.clone() + } + + fn get_tool_parameters(&self) -> String { + let mut s = String::from("{\"parameters\": ["); + for i in 0..self.parameters.len() { + if i < self.parameters.len() - 1 { + s.push_str(&(self.parameters[i].to_string())); + s.push_str(","); + } else { + s.push_str(&(self.parameters[i].to_string())); + } + } + s.push_str("]}"); + s + } + + fn get_example_usage(&self) -> String { + self.example_usage.clone() + } + + fn get_toolbox(&self) -> String { + self.toolbox.clone() + } + + fn run<'a>(&self, + args: Vec, + working_directory: &'a str, + verbose: bool) + -> Result<(), Error> { + let mut input_file1 = String::new(); + let mut input_file2 = String::new(); + let mut output_file1 = String::new(); + let mut output_file2 = String::new(); + let mut filter_size = 11usize; + let mut stat_type = String::from("pearson"); + + if args.len() == 0 { + return Err(Error::new(ErrorKind::InvalidInput, "Tool run with no parameters.")); + } + + for i in 0..args.len() { + let mut arg = args[i].replace("\"", ""); + arg = arg.replace("\'", ""); + let cmd = arg.split("="); // in case an equals sign was used + let vec = cmd.collect::>(); + let mut keyval = false; + if vec.len() > 1 { + keyval = true; + } + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-i1" || flag_val == "-input1" { + input_file1 = if keyval { + vec[1].to_string() + } else { + args[i+1].to_string() + }; + } else if flag_val == "-i2" || flag_val == "-input2" { + input_file2 = if keyval { + vec[1].to_string() + } else { + args[i+1].to_string() + }; + } else if flag_val == "-o1" || flag_val == "-output1" { + output_file1 = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-o2" || flag_val == "-output2" { + output_file2 = if keyval { + vec[1].to_string() + } else { + args[i + 1].to_string() + }; + } else if flag_val == "-stat" { + let val = if keyval { + vec[1].to_lowercase() + } else { + args[i + 1].to_lowercase() + }; + stat_type = if val.contains("son") { + "pearson".to_string() + } else if val.contains("kendall") { + "kendall".to_string() + } else { + "spearman".to_string() + }; + } else if vec[0].to_lowercase() == "-filter" || vec[0].to_lowercase() == "--filter" { + if keyval { + filter_size = vec[1].to_string().parse::().unwrap() as usize; + } else { + filter_size = args[i+1].to_string().parse::().unwrap() as usize; + } + } + } + + + if verbose { + println!("***************{}", "*".repeat(self.get_tool_name().len())); + println!("* Welcome to {} *", self.get_tool_name()); + println!("***************{}", "*".repeat(self.get_tool_name().len())); + } + + if filter_size < 3 { filter_size = 3; } + let sep: String = path::MAIN_SEPARATOR.to_string(); + + let mut progress: usize; + let mut old_progress: usize = 1; + + //let start = time::now(); + let start = Instant::now(); // had to change to this from old time::now + + if !input_file1.contains(&sep) && !input_file1.contains("/") { + input_file1 = format!("{}{}", working_directory, input_file1); + } + if !input_file2.contains(&sep) && !input_file2.contains("/") { + input_file2 = format!("{}{}", working_directory, input_file2); + } + + + if !output_file1.contains(&sep) && !output_file1.contains("/") { + output_file1 = format!("{}{}", working_directory, output_file1); + } + + if !output_file2.contains(&sep) && !output_file2.contains("/") { + output_file2 = format!("{}{}", working_directory, output_file2); + } + + let num_procs = num_cpus::get() as isize; + + if verbose { println!("Reading data...") }; + + let image1 = Arc::new(Raster::new(&input_file1, "r")?); + let rows = image1.configs.rows as isize; + let columns = image1.configs.columns as isize; + let nodata1 = image1.configs.nodata; + + let image2 = Arc::new(Raster::new(&input_file2, "r")?); + if image2.configs.rows as isize != rows || image2.configs.columns as isize != columns { + panic!("Error: The input files do not contain the same raster extent."); + } + let nodata2 = image2.configs.nodata; + + // The r-value output + let mut output_val = Raster::initialize_using_file(&output_file1, &image1); + // The significance (p-value) output + let mut output_sig = Raster::initialize_using_file(&output_file2, &image1); + + // Pearson's Correlation // + if stat_type == "pearson" { + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let image1 = image1.clone(); + let image2 = image2.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let mut num_cells: usize; + let mut sum1: f64; + let mut sum2: f64; + let mut total_deviation1: f64; + let mut total_deviation2: f64; + let mut product_deviations: f64; + let (mut mean1, mut mean2): (f64, f64); + let mut r: f64; + let mut df: usize; + let mut tvalue: f64; + let mut pvalue: f64; + let (mut z1, mut z2): (f64, f64); + let (mut z_n1, mut z_n2): (f64, f64); + let num_pixels_in_filter = filter_size * filter_size; + let mut dx = vec![0isize; num_pixels_in_filter]; + let mut dy = vec![0isize; num_pixels_in_filter]; + + // fill the filter d_x and d_y values + let midpoint: isize = (filter_size as f64 / 2f64).floor() as isize; // + 1; + let mut a = 0; + for row in 0..filter_size { + for col in 0..filter_size { + dx[a] = col as isize - midpoint; + dy[a] = row as isize - midpoint; + a += 1; + } + } + + for row in (0..rows).filter(|r| r % num_procs == tid) { + let mut data1 = vec![nodata1; columns as usize]; + let mut data2 = vec![nodata1; columns as usize]; + for col in 0..columns { + z1 = image1.get_value(row, col); + z2 = image2.get_value(row, col); + if z1 != nodata1 && z2 != nodata2 { + // First, calculate the mean + num_cells = 0; + sum1 = 0f64; + sum2 = 0f64; + for i in 0..num_pixels_in_filter { + z_n1 = image1.get_value(row + dy[i], col + dx[i]); + z_n2 = image2.get_value(row + dy[i], col + dx[i]); + if z_n1 != nodata1 && z_n2 != nodata2 { + sum1 += z_n1; + sum2 += z_n2; + num_cells += 1; + } + } + mean1 = sum1 / num_cells as f64; + mean2 = sum2 / num_cells as f64; + + // Now calculate the total deviations and total cross-deviation. + total_deviation1 = 0f64; + total_deviation2 = 0f64; + product_deviations = 0f64; + if num_cells > 2 { + for i in 0..num_pixels_in_filter { + z_n1 = image1.get_value(row + dy[i], col + dx[i]); + z_n2 = image2.get_value(row + dy[i], col + dx[i]); + if z_n1 != nodata1 && z_n2 != nodata2 { + total_deviation1 += (z_n1 - mean1) * (z_n1 - mean1); + total_deviation2 += (z_n2 - mean2) * (z_n2 - mean2); + product_deviations += (z_n1 - mean1) * (z_n2 - mean2); + } + } + } + + // Finally, calculate r for the neighbourhood. + r = if total_deviation1 != 0f64 && total_deviation2 != 0f64 && num_cells > 2 { + product_deviations / (total_deviation1 * total_deviation2).sqrt() + } else { + // You can't divide by zero + 0f64 + }; + + data1[col as usize] = r; + + df = num_cells - 2; + if df > 2 { + tvalue = r * (df as f64 / (1f64 - r * r)).sqrt(); + let t = StudentsT::new(0.0, 1.0, df as f64).unwrap(); + pvalue = 2f64 * (1f64 - t.cdf(tvalue.abs())); + data2[col as usize] = pvalue; + } else { + data2[col as usize] = 0f64; + } + } + } + tx.send((row, data1, data2)).unwrap(); + } + }); + } + + for r in 0..rows { + let (row, data1, data2) = rx.recv().unwrap(); + output_val.set_row_data(row, data1); + output_sig.set_row_data(row, data2); + + if verbose { + progress = (100.0_f64 * r as f64 / (rows - 1) as f64) as usize; + if progress != old_progress { + println!("Performing Correlation: {}%", progress); + old_progress = progress; + } + } + } + } else if stat_type == "kendall" { // Perform Kendall's Tau-b correlation + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let image1 = image1.clone(); + let image2 = image2.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let mut num_cells: usize; + let mut num_cells_f64: f64; + let mut tau: f64; + let mut df: f64; + let mut zvalue: f64; + let mut pvalue: f64; + let (mut z1, mut z2): (f64, f64); + let (mut z_n1, mut z_n2): (f64, f64); + let num_pixels_in_filter = filter_size * filter_size; + let mut dx = vec![0isize; num_pixels_in_filter]; + let mut dy = vec![0isize; num_pixels_in_filter]; + let (mut rank, mut rank2): (f64, f64); + let mut upper_range: usize; + + let mut num_tied_vals: f64; + let mut nt1: f64; + let mut nt2: f64; + let mut n0: f64; + let mut numer: f64; + + let midpoint: isize = (filter_size as f64 / 2f64).floor() as isize; // + 1; + let mut a = 0; + for row in 0..filter_size { + for col in 0..filter_size { + dx[a] = col as isize - midpoint; + dy[a] = row as isize - midpoint; + a += 1; + } + } + + for row in (0..rows).filter(|r| r % num_procs == tid) { + let mut data1 = vec![nodata1; columns as usize]; + let mut data2 = vec![nodata1; columns as usize]; + for col in 0..columns { + z1 = image1.get_value(row, col); + z2 = image2.get_value(row, col); + if z1 != nodata1 && z2 != nodata2 { + let mut v1 = Vec::with_capacity(num_pixels_in_filter); + let mut v2 = Vec::with_capacity(num_pixels_in_filter); + num_cells = 0; + for i in 0..num_pixels_in_filter { + z_n1 = image1.get_value(row + dy[i], col + dx[i]); + z_n2 = image2.get_value(row + dy[i], col + dx[i]); + if z_n1 != nodata1 && z_n2 != nodata2 { + num_cells += 1; + // tuple = (value, index, rank) + v1.push((z_n1, num_cells, 0f64)); + v2.push((z_n2, num_cells, 0f64)); + } + } + num_cells_f64 = num_cells as f64; + + // Sort both lists based on value + v1.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Equal)); + v2.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Equal)); + + // Now provide the rank data + rank = 0f64; + nt1 = 0f64; + for i in 0..num_cells { + if v1[i].2 == 0f64 { + rank += 1f64; + if i < num_cells - 1 { + // are there any ties above this one? + upper_range = i; + for j in i+1..num_cells { + if v1[i].0 == v1[j].0 { + upper_range = j; + } else { + break; + } + } + if upper_range != i { + num_tied_vals = (upper_range - i + 1) as f64; + nt1 += num_tied_vals * (num_tied_vals - 1f64) / 2f64; + rank2 = rank + (upper_range - i) as f64; + rank = (rank + rank2) / 2f64; // average rank + for k in i..=upper_range { + v1[k].2 = rank; + } + rank = rank2; + } else { + v1[i].2 = rank; + } + } else { + v1[i].2 = rank; + } + } + } + + nt2 = 0f64; + rank = 0f64; + for i in 0..num_cells { + if v2[i].2 == 0f64 { + rank += 1f64; + if i < num_cells - 1 { + // are there any ties above this one? + upper_range = i; + for j in i+1..num_cells { + if v2[i].0 == v2[j].0 { + upper_range = j; + } else { + break; + } + } + if upper_range != i { + num_tied_vals = (upper_range - i + 1) as f64; + nt2 += num_tied_vals * (num_tied_vals - 1f64) / 2f64; + rank2 = rank + (upper_range - i) as f64; + rank = (rank + rank2) / 2f64; // average rank + for k in i..=upper_range { + v2[k].2 = rank; + } + rank = rank2; + } else { + v2[i].2 = rank; + } + } else { + v2[i].2 = rank; + } + } + } + + // Sort both lists based on index + v1.sort_by(|a, b| a.1.cmp(&b.1)); + v2.sort_by(|a, b| a.1.cmp(&b.1)); + + //////////////////////////////////////////////////////////////////////////// + // This block of code is O(n^2) and is a serious performance killer. There + // is a O(nlogn) solution based on swaps in a merge-sort but I have yet to + // figure it out. As it stands, this solution is unacceptable for search + // windows larger than about 25, depending the number of cores in the + // system processor. + //////////////////////////////////////////////////////////////////////////// + numer = 0f64; + for i in 0..num_cells { + for j in i+1..num_cells { + if v1[i].2 != v1[j].2 && v2[i].2 != v2[j].2 { + numer += (v1[i].2 - v1[j].2).signum() * (v2[i].2 - v2[j].2).signum(); + } + } + } + + n0 = num_cells as f64 * (num_cells as f64 - 1f64) / 2f64; + tau = numer / ((n0 - nt1)*(n0 - nt2)).sqrt(); + data1[col as usize] = tau; + df = num_cells_f64 - 2f64; + + if df > 2f64 { + zvalue = 3f64 * numer / (num_cells_f64*(num_cells_f64-1f64)*(2f64*num_cells_f64+5f64) / 2f64).sqrt(); + let t = StudentsT::new(0.0, 1.0, df as f64).unwrap(); // create a student's t distribution + pvalue = 2f64 * (1f64 - t.cdf(zvalue.abs())); // calculate the p-value (significance) + data2[col as usize] = pvalue; + } else { + data2[col as usize] = 0f64; + } + } + } + tx.send((row, data1, data2)).unwrap(); + } + }); + } + + for r in 0..rows { + let (row, data1, data2) = rx.recv().unwrap(); + output_val.set_row_data(row, data1); + output_sig.set_row_data(row, data2); + + if verbose { + progress = (100.0_f64 * r as f64 / (rows - 1) as f64) as usize; + if progress != old_progress { + println!("Performing Correlation: {}%", progress); + old_progress = progress; + } + } + } + } else { // Calculate Spearman's Rho correlation + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let image1 = image1.clone(); + let image2 = image2.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let mut num_cells: usize; + let mut num_cells_f64: f64; + let mut rho: f64; + let mut df: f64; + let mut tvalue: f64; + let mut pvalue: f64; + let (mut z1, mut z2): (f64, f64); + let (mut z_n1, mut z_n2): (f64, f64); + let num_pixels_in_filter = filter_size * filter_size; + let mut dx = vec![0isize; num_pixels_in_filter]; + let mut dy = vec![0isize; num_pixels_in_filter]; + let (mut rank, mut rank2): (f64, f64); + let mut upper_range: usize; + let mut num_ties = 0; + let midpoint: isize = (filter_size as f64 / 2f64).floor() as isize; // + 1; + let mut a = 0; + for row in 0..filter_size { + for col in 0..filter_size { + dx[a] = col as isize - midpoint; + dy[a] = row as isize - midpoint; + a += 1; + } + } + + for row in (0..rows).filter(|r| r % num_procs == tid) { + let mut data1 = vec![nodata1; columns as usize]; + let mut data2 = vec![nodata1; columns as usize]; + for col in 0..columns { + z1 = image1.get_value(row, col); + z2 = image2.get_value(row, col); + if z1 != nodata1 && z2 != nodata2 { + let mut v1 = Vec::with_capacity(num_pixels_in_filter); + let mut v2 = Vec::with_capacity(num_pixels_in_filter); + num_cells = 0; + for i in 0..num_pixels_in_filter { + z_n1 = image1.get_value(row + dy[i], col + dx[i]); + z_n2 = image2.get_value(row + dy[i], col + dx[i]); + if z_n1 != nodata1 && z_n2 != nodata2 { + num_cells += 1; + // tuple = (value, index, rank) + v1.push((z_n1, num_cells, 0f64)); + v2.push((z_n2, num_cells, 0f64)); + } + } + num_cells_f64 = num_cells as f64; + + // Sort both lists based on value + v1.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Equal)); + v2.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Equal)); + + rank = 0f64; + for i in 0..num_cells { + if v1[i].2 == 0f64 { + rank += 1f64; + if i < num_cells - 1 { + // are there any ties above this one? + upper_range = i; + for j in i+1..num_cells { + if v1[i].0 == v1[j].0 { + upper_range = j; + num_ties += 1; + } else { + break; + } + } + if upper_range != i { + rank2 = rank + (upper_range - i) as f64; + rank = (rank + rank2) / 2f64; // average rank + for k in i..=upper_range { + v1[k].2 = rank; + } + rank = rank2; + } else { + v1[i].2 = rank; + } + } else { + v1[i].2 = rank; + } + } + } + + rank = 0f64; + for i in 0..num_cells { + if v2[i].2 == 0f64 { + rank += 1f64; + if i < num_cells - 1 { + // are there any ties above this one? + upper_range = i; + for j in i+1..num_cells { + if v2[i].0 == v2[j].0 { + upper_range = j; + num_ties += 1; + } else { + break; + } + } + if upper_range != i { + rank2 = rank + (upper_range - i) as f64; + rank = (rank + rank2) / 2f64; // average rank + for k in i..=upper_range { + v2[k].2 = rank; + } + rank = rank2; + } else { + v2[i].2 = rank; + } + } else { + v2[i].2 = rank; + } + } + } + + // Sort both lists based on index + v1.sort_by(|a, b| a.1.cmp(&b.1)); + v2.sort_by(|a, b| a.1.cmp(&b.1)); + + let mut rank_diff_sqrd = 0f64; + for i in 0..num_cells { + rank_diff_sqrd += (v1[i].2 - v2[i].2) * (v1[i].2 - v2[i].2); + } + + rho = 1f64 - (6f64 * rank_diff_sqrd / (num_cells_f64 * num_cells_f64 * num_cells_f64 - num_cells_f64)); + data1[col as usize] = rho; + df = num_cells_f64 - 2f64; // calculate degrees of freedom (Anthony Comment) + + if df > 2f64 { + tvalue = rho * (df / (1f64 - rho * rho)).sqrt(); + let t = StudentsT::new(0.0, 1.0, df as f64).unwrap(); // create a student's t distribution + pvalue = 2f64 * (1f64 - t.cdf(tvalue.abs())); // calculate the p-value (significance) + data2[col as usize] = pvalue; + } else { + data2[col as usize] = 0f64; + } + } + } + tx.send((row, data1, data2, num_ties)).unwrap(); + } + }); + } + + let mut num_ties = 0; + for r in 0..rows { + let (row, data1, data2, ties) = rx.recv().unwrap(); + output_val.set_row_data(row, data1); + output_sig.set_row_data(row, data2); + num_ties += ties; + + if verbose { + progress = (100.0_f64 * r as f64 / (rows - 1) as f64) as usize; + if progress != old_progress { + println!("Performing Correlation: {}%", progress); + old_progress = progress; + } + } + } + + if num_ties > 0 { + println!("Warning: There were ties in the tests and as a result p-values \nmay be misleading. Use Kendall's Tau instead."); + } + } + + let elapsed_time = get_formatted_elapsed_time(start); + + output_val.add_metadata_entry(format!("Created by whitebox_tools\' {} tool", self.get_tool_name())); + output_val.add_metadata_entry(format!("Filter size: {}", filter_size)); + output_val.add_metadata_entry(format!("Input file 1: {}", input_file1)); + output_val.add_metadata_entry(format!("Input file 2: {}", input_file2)); + output_val.add_metadata_entry(format!("Statistic: {}", stat_type)); + output_val.add_metadata_entry(format!("Elapsed Time (excluding I/O): {}", elapsed_time).replace("PT", "")); + + if verbose { println!("Saving data...") }; + let _ = match output_val.write() { + Ok(_) => if verbose { println!("Output file written") }, + Err(e) => return Err(e), + }; + + output_sig.add_metadata_entry(format!("Created by whitebox_tools\' {} tool", self.get_tool_name())); + output_sig.add_metadata_entry(format!("Filter size: {}", filter_size)); + output_sig.add_metadata_entry(format!("Input file 1: {}", input_file1)); + output_sig.add_metadata_entry(format!("Input file 2: {}", input_file2)); + output_val.add_metadata_entry(format!("Statistic: {}", stat_type)); + output_sig.add_metadata_entry(format!("Elapsed Time (excluding I/O): {}", elapsed_time).replace("PT", "")); + + let _ = match output_sig.write() { + Ok(_) => if verbose { println!(" ") }, + Err(e) => return Err(e), + }; + + if verbose { + println!( + "{}", + &format!("Elapsed Time (excluding I/O): {}", elapsed_time) + ); + } + + // println!("Number of total cells: {}", valid); + // println!("Numbed of significant cells: {}", sig); + // println!("Numbed of significant negative cells: {}", sig_neg); + // println!("Numbed of significant postive cells: {}", sig_pos); + + Ok(()) + } + +} +// #[derive(PartialEq, Debug)] +// struct GridCell { +// pub z: f64, +// pub index: usize, +// pub rank: f64, +// } + +// impl Eq for GridCell {} + +// impl PartialOrd for GridCell { +// fn partial_cmp(&self, other: &Self) -> Option { +// self.z.partial_cmp(&other.z) +// } +// } + +// impl Ord for GridCell { +// fn cmp(&self, other: &GridCell) -> Ordering { +// self.partial_cmp(other).unwrap() +// } +// } diff --git a/src/tools/math_stat_analysis/image_regression.rs b/src/tools/math_stat_analysis/image_regression.rs index 5d0cbff1c..959a58e34 100644 --- a/src/tools/math_stat_analysis/image_regression.rs +++ b/src/tools/math_stat_analysis/image_regression.rs @@ -203,7 +203,7 @@ impl WhiteboxTool for ImageRegression { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/increment.rs b/src/tools/math_stat_analysis/increment.rs index 28cd42cd0..62eae7be5 100644 --- a/src/tools/math_stat_analysis/increment.rs +++ b/src/tools/math_stat_analysis/increment.rs @@ -131,7 +131,7 @@ impl WhiteboxTool for Increment { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/inplace_add.rs b/src/tools/math_stat_analysis/inplace_add.rs index 451ea90f0..6b027b062 100644 --- a/src/tools/math_stat_analysis/inplace_add.rs +++ b/src/tools/math_stat_analysis/inplace_add.rs @@ -136,7 +136,7 @@ impl WhiteboxTool for InPlaceAdd { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/inplace_divide.rs b/src/tools/math_stat_analysis/inplace_divide.rs index b0d5616e5..e5ef371b8 100644 --- a/src/tools/math_stat_analysis/inplace_divide.rs +++ b/src/tools/math_stat_analysis/inplace_divide.rs @@ -135,7 +135,7 @@ impl WhiteboxTool for InPlaceDivide { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/inplace_multiply.rs b/src/tools/math_stat_analysis/inplace_multiply.rs index 77bfcb029..bcf6c3253 100644 --- a/src/tools/math_stat_analysis/inplace_multiply.rs +++ b/src/tools/math_stat_analysis/inplace_multiply.rs @@ -135,7 +135,7 @@ impl WhiteboxTool for InPlaceMultiply { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/inplace_subtract.rs b/src/tools/math_stat_analysis/inplace_subtract.rs index fcfd13ca2..e582ad123 100644 --- a/src/tools/math_stat_analysis/inplace_subtract.rs +++ b/src/tools/math_stat_analysis/inplace_subtract.rs @@ -137,7 +137,7 @@ impl WhiteboxTool for InPlaceSubtract { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/integer_division.rs b/src/tools/math_stat_analysis/integer_division.rs index 717b2155a..aea97fb95 100644 --- a/src/tools/math_stat_analysis/integer_division.rs +++ b/src/tools/math_stat_analysis/integer_division.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for IntegerDivision { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/isnodata.rs b/src/tools/math_stat_analysis/isnodata.rs index 07f91110d..107586854 100644 --- a/src/tools/math_stat_analysis/isnodata.rs +++ b/src/tools/math_stat_analysis/isnodata.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for IsNoData { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/kappa_index.rs b/src/tools/math_stat_analysis/kappa_index.rs index 26454ef17..11a03529f 100644 --- a/src/tools/math_stat_analysis/kappa_index.rs +++ b/src/tools/math_stat_analysis/kappa_index.rs @@ -149,7 +149,7 @@ impl WhiteboxTool for KappaIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/ks_normality_test.rs b/src/tools/math_stat_analysis/ks_normality_test.rs index 0135b2cca..784788d12 100644 --- a/src/tools/math_stat_analysis/ks_normality_test.rs +++ b/src/tools/math_stat_analysis/ks_normality_test.rs @@ -147,7 +147,7 @@ impl WhiteboxTool for KsTestForNormality { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/less_than.rs b/src/tools/math_stat_analysis/less_than.rs index 017f776ad..2f3a6f11f 100644 --- a/src/tools/math_stat_analysis/less_than.rs +++ b/src/tools/math_stat_analysis/less_than.rs @@ -151,7 +151,7 @@ impl WhiteboxTool for LessThan { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/list_unique_values.rs b/src/tools/math_stat_analysis/list_unique_values.rs index 91a268a19..a0e4e43e6 100644 --- a/src/tools/math_stat_analysis/list_unique_values.rs +++ b/src/tools/math_stat_analysis/list_unique_values.rs @@ -157,7 +157,7 @@ impl WhiteboxTool for ListUniqueValues { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/ln.rs b/src/tools/math_stat_analysis/ln.rs index 62def02f1..dcc112d9f 100644 --- a/src/tools/math_stat_analysis/ln.rs +++ b/src/tools/math_stat_analysis/ln.rs @@ -134,7 +134,7 @@ impl WhiteboxTool for Ln { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/log10.rs b/src/tools/math_stat_analysis/log10.rs index 92d242c9c..45981e6d3 100644 --- a/src/tools/math_stat_analysis/log10.rs +++ b/src/tools/math_stat_analysis/log10.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for Log10 { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/log2.rs b/src/tools/math_stat_analysis/log2.rs index e012c8bea..2f938ddca 100644 --- a/src/tools/math_stat_analysis/log2.rs +++ b/src/tools/math_stat_analysis/log2.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for Log2 { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/max.rs b/src/tools/math_stat_analysis/max.rs index 99df42e1e..bf3006cdb 100644 --- a/src/tools/math_stat_analysis/max.rs +++ b/src/tools/math_stat_analysis/max.rs @@ -139,7 +139,7 @@ impl WhiteboxTool for Max { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/min.rs b/src/tools/math_stat_analysis/min.rs index cd215167d..539e5e5c6 100644 --- a/src/tools/math_stat_analysis/min.rs +++ b/src/tools/math_stat_analysis/min.rs @@ -139,7 +139,7 @@ impl WhiteboxTool for Min { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/mod.rs b/src/tools/math_stat_analysis/mod.rs index e9b65ef98..9c6333c58 100644 --- a/src/tools/math_stat_analysis/mod.rs +++ b/src/tools/math_stat_analysis/mod.rs @@ -29,6 +29,7 @@ mod floor; mod greater_than; mod image_autocorrelation; mod image_correlation; +mod image_correlation_neighbourhood_analysis; mod image_regression; mod increment; mod inplace_add; @@ -113,6 +114,7 @@ pub use self::floor::Floor; pub use self::greater_than::GreaterThan; pub use self::image_autocorrelation::ImageAutocorrelation; pub use self::image_correlation::ImageCorrelation; +pub use self::image_correlation_neighbourhood_analysis::ImageCorrelationNeighbourhoodAnalysis; pub use self::image_regression::ImageRegression; pub use self::increment::Increment; pub use self::inplace_add::InPlaceAdd; diff --git a/src/tools/math_stat_analysis/modulo.rs b/src/tools/math_stat_analysis/modulo.rs index b778149ee..65d5196cd 100644 --- a/src/tools/math_stat_analysis/modulo.rs +++ b/src/tools/math_stat_analysis/modulo.rs @@ -146,7 +146,7 @@ impl WhiteboxTool for Modulo { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/multiply.rs b/src/tools/math_stat_analysis/multiply.rs index 21f2e955e..f07b2fb22 100644 --- a/src/tools/math_stat_analysis/multiply.rs +++ b/src/tools/math_stat_analysis/multiply.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for Multiply { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/negate.rs b/src/tools/math_stat_analysis/negate.rs index dd7a843a2..54d2ea816 100644 --- a/src/tools/math_stat_analysis/negate.rs +++ b/src/tools/math_stat_analysis/negate.rs @@ -136,7 +136,7 @@ impl WhiteboxTool for Negate { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/not.rs b/src/tools/math_stat_analysis/not.rs index e84cdcc33..7f529b02d 100644 --- a/src/tools/math_stat_analysis/not.rs +++ b/src/tools/math_stat_analysis/not.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for Not { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/not_equal_to.rs b/src/tools/math_stat_analysis/not_equal_to.rs index d5ec335c8..6ba53ca0b 100644 --- a/src/tools/math_stat_analysis/not_equal_to.rs +++ b/src/tools/math_stat_analysis/not_equal_to.rs @@ -138,7 +138,7 @@ impl WhiteboxTool for NotEqualTo { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/or.rs b/src/tools/math_stat_analysis/or.rs index a42ea61d7..54e97cfeb 100644 --- a/src/tools/math_stat_analysis/or.rs +++ b/src/tools/math_stat_analysis/or.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for Or { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/paired_sample_t_test.rs b/src/tools/math_stat_analysis/paired_sample_t_test.rs index 4a9e0835f..2a6aa4c08 100644 --- a/src/tools/math_stat_analysis/paired_sample_t_test.rs +++ b/src/tools/math_stat_analysis/paired_sample_t_test.rs @@ -167,7 +167,7 @@ impl WhiteboxTool for PairedSampleTTest { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/power.rs b/src/tools/math_stat_analysis/power.rs index 07304839f..2716f382d 100644 --- a/src/tools/math_stat_analysis/power.rs +++ b/src/tools/math_stat_analysis/power.rs @@ -139,7 +139,7 @@ impl WhiteboxTool for Power { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/principal_component_analysis.rs b/src/tools/math_stat_analysis/principal_component_analysis.rs index 0b81ae9ee..70efe8a34 100644 --- a/src/tools/math_stat_analysis/principal_component_analysis.rs +++ b/src/tools/math_stat_analysis/principal_component_analysis.rs @@ -188,7 +188,7 @@ impl WhiteboxTool for PrincipalComponentAnalysis { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/quantiles.rs b/src/tools/math_stat_analysis/quantiles.rs index 66206fffe..c09cf657c 100644 --- a/src/tools/math_stat_analysis/quantiles.rs +++ b/src/tools/math_stat_analysis/quantiles.rs @@ -144,7 +144,7 @@ impl WhiteboxTool for Quantiles { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/random_field.rs b/src/tools/math_stat_analysis/random_field.rs index f4f693cc3..fc552481a 100644 --- a/src/tools/math_stat_analysis/random_field.rs +++ b/src/tools/math_stat_analysis/random_field.rs @@ -137,7 +137,7 @@ impl WhiteboxTool for RandomField { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/random_sample.rs b/src/tools/math_stat_analysis/random_sample.rs index 1a942f6a6..3e068fc5c 100644 --- a/src/tools/math_stat_analysis/random_sample.rs +++ b/src/tools/math_stat_analysis/random_sample.rs @@ -142,7 +142,7 @@ impl WhiteboxTool for RandomSample { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/raster_histogram.rs b/src/tools/math_stat_analysis/raster_histogram.rs index 23d8eae60..fe4611779 100644 --- a/src/tools/math_stat_analysis/raster_histogram.rs +++ b/src/tools/math_stat_analysis/raster_histogram.rs @@ -135,7 +135,7 @@ impl WhiteboxTool for RasterHistogram { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/raster_summary_stats.rs b/src/tools/math_stat_analysis/raster_summary_stats.rs index 921517c07..d9c372d81 100644 --- a/src/tools/math_stat_analysis/raster_summary_stats.rs +++ b/src/tools/math_stat_analysis/raster_summary_stats.rs @@ -140,7 +140,7 @@ impl WhiteboxTool for RasterSummaryStats { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/reciprocal.rs b/src/tools/math_stat_analysis/reciprocal.rs index 0c7c70496..fa2367d02 100644 --- a/src/tools/math_stat_analysis/reciprocal.rs +++ b/src/tools/math_stat_analysis/reciprocal.rs @@ -128,7 +128,7 @@ impl WhiteboxTool for Reciprocal { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/rescale_value_range.rs b/src/tools/math_stat_analysis/rescale_value_range.rs index 63a730e09..3f246f495 100644 --- a/src/tools/math_stat_analysis/rescale_value_range.rs +++ b/src/tools/math_stat_analysis/rescale_value_range.rs @@ -163,7 +163,7 @@ impl WhiteboxTool for RescaleValueRange { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/root_mean_square_error.rs b/src/tools/math_stat_analysis/root_mean_square_error.rs index 1dd6513ab..be2a0f81d 100644 --- a/src/tools/math_stat_analysis/root_mean_square_error.rs +++ b/src/tools/math_stat_analysis/root_mean_square_error.rs @@ -137,7 +137,7 @@ impl WhiteboxTool for RootMeanSquareError { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/round.rs b/src/tools/math_stat_analysis/round.rs index d53e01815..37629ad82 100644 --- a/src/tools/math_stat_analysis/round.rs +++ b/src/tools/math_stat_analysis/round.rs @@ -131,7 +131,7 @@ impl WhiteboxTool for Round { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/sin.rs b/src/tools/math_stat_analysis/sin.rs index 13368f491..af3609357 100644 --- a/src/tools/math_stat_analysis/sin.rs +++ b/src/tools/math_stat_analysis/sin.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for Sin { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/sinh.rs b/src/tools/math_stat_analysis/sinh.rs index 40910cce5..f485b64e6 100644 --- a/src/tools/math_stat_analysis/sinh.rs +++ b/src/tools/math_stat_analysis/sinh.rs @@ -134,7 +134,7 @@ impl WhiteboxTool for Sinh { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/sqrt.rs b/src/tools/math_stat_analysis/sqrt.rs index 997c80f4a..6f6518a2a 100644 --- a/src/tools/math_stat_analysis/sqrt.rs +++ b/src/tools/math_stat_analysis/sqrt.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for SquareRoot { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/square.rs b/src/tools/math_stat_analysis/square.rs index 1db4df970..a09eabd91 100644 --- a/src/tools/math_stat_analysis/square.rs +++ b/src/tools/math_stat_analysis/square.rs @@ -131,7 +131,7 @@ impl WhiteboxTool for Square { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/subtract.rs b/src/tools/math_stat_analysis/subtract.rs index 401e707a7..bbd36422c 100644 --- a/src/tools/math_stat_analysis/subtract.rs +++ b/src/tools/math_stat_analysis/subtract.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for Subtract { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/tan.rs b/src/tools/math_stat_analysis/tan.rs index 2d13fb226..cbf9825a3 100644 --- a/src/tools/math_stat_analysis/tan.rs +++ b/src/tools/math_stat_analysis/tan.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for Tan { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/tanh.rs b/src/tools/math_stat_analysis/tanh.rs index bb7eea19a..f53760209 100644 --- a/src/tools/math_stat_analysis/tanh.rs +++ b/src/tools/math_stat_analysis/tanh.rs @@ -134,7 +134,7 @@ impl WhiteboxTool for Tanh { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/to_degrees.rs b/src/tools/math_stat_analysis/to_degrees.rs index 9741cbba7..48dc1d3b2 100644 --- a/src/tools/math_stat_analysis/to_degrees.rs +++ b/src/tools/math_stat_analysis/to_degrees.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for ToDegrees { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/to_radians.rs b/src/tools/math_stat_analysis/to_radians.rs index 10f3b9634..c29077c1e 100644 --- a/src/tools/math_stat_analysis/to_radians.rs +++ b/src/tools/math_stat_analysis/to_radians.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for ToRadians { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/trend_surface.rs b/src/tools/math_stat_analysis/trend_surface.rs index 67abc0978..778ba5228 100644 --- a/src/tools/math_stat_analysis/trend_surface.rs +++ b/src/tools/math_stat_analysis/trend_surface.rs @@ -146,7 +146,7 @@ impl WhiteboxTool for TrendSurface { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/trend_surface_vector_points.rs b/src/tools/math_stat_analysis/trend_surface_vector_points.rs index ef1ff6db9..15fd11d3f 100644 --- a/src/tools/math_stat_analysis/trend_surface_vector_points.rs +++ b/src/tools/math_stat_analysis/trend_surface_vector_points.rs @@ -169,7 +169,7 @@ impl WhiteboxTool for TrendSurfaceVectorPoints { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/truncate.rs b/src/tools/math_stat_analysis/truncate.rs index 16f17f3a3..29fed2eef 100644 --- a/src/tools/math_stat_analysis/truncate.rs +++ b/src/tools/math_stat_analysis/truncate.rs @@ -137,7 +137,7 @@ impl WhiteboxTool for Truncate { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/turning_bands.rs b/src/tools/math_stat_analysis/turning_bands.rs index cff6ad1ab..ec9f8e4c9 100644 --- a/src/tools/math_stat_analysis/turning_bands.rs +++ b/src/tools/math_stat_analysis/turning_bands.rs @@ -173,7 +173,7 @@ impl WhiteboxTool for TurningBandsSimulation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/two_sample_ks_test.rs b/src/tools/math_stat_analysis/two_sample_ks_test.rs index f92dd4fa7..b5ecb1449 100644 --- a/src/tools/math_stat_analysis/two_sample_ks_test.rs +++ b/src/tools/math_stat_analysis/two_sample_ks_test.rs @@ -163,7 +163,7 @@ impl WhiteboxTool for TwoSampleKsTest { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/wilcoxon_signed_rank_test.rs b/src/tools/math_stat_analysis/wilcoxon_signed_rank_test.rs index 911047078..5d835984b 100644 --- a/src/tools/math_stat_analysis/wilcoxon_signed_rank_test.rs +++ b/src/tools/math_stat_analysis/wilcoxon_signed_rank_test.rs @@ -169,7 +169,7 @@ impl WhiteboxTool for WilcoxonSignedRankTest { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -361,6 +361,8 @@ impl WhiteboxTool for WilcoxonSignedRankTest { let mut ranks = vec![-1f64; diffs.len()]; let mut nr = 0u128; // number of non-zero ranks let mut r = 0f64; + let mut r2: f64; + let mut upper_range: usize; for i in 0..diffs.len() { if diffs[i] != 0f64 { nr += 1; @@ -368,7 +370,7 @@ impl WhiteboxTool for WilcoxonSignedRankTest { r += 1f64; if i < diffs.len() - 1 { // are there any ties above this one? - let mut upper_range = i; + upper_range = i; for j in i+1..diffs.len() { if diffs[j].abs() == diffs[i].abs() { upper_range = j @@ -377,7 +379,7 @@ impl WhiteboxTool for WilcoxonSignedRankTest { } } if upper_range != i { - let r2 = r + (upper_range - i) as f64; + r2 = r + (upper_range - i) as f64; r = (r + r2) / 2f64; // average rank for k in i..=upper_range { ranks[k] = r * diffs[k].signum(); diff --git a/src/tools/math_stat_analysis/xor.rs b/src/tools/math_stat_analysis/xor.rs index 687c03c95..d0232ea69 100644 --- a/src/tools/math_stat_analysis/xor.rs +++ b/src/tools/math_stat_analysis/xor.rs @@ -141,7 +141,7 @@ impl WhiteboxTool for Xor { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/zonal_statistics.rs b/src/tools/math_stat_analysis/zonal_statistics.rs index 0fd4a819f..a4bbbe5d0 100644 --- a/src/tools/math_stat_analysis/zonal_statistics.rs +++ b/src/tools/math_stat_analysis/zonal_statistics.rs @@ -194,7 +194,7 @@ impl WhiteboxTool for ZonalStatistics { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/math_stat_analysis/zscores.rs b/src/tools/math_stat_analysis/zscores.rs index 9710ddf1b..a0515c225 100644 --- a/src/tools/math_stat_analysis/zscores.rs +++ b/src/tools/math_stat_analysis/zscores.rs @@ -137,7 +137,7 @@ impl WhiteboxTool for ZScores { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/mod.rs b/src/tools/mod.rs index 01e10c104..29ff719a8 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -106,6 +106,7 @@ impl ToolManager { tool_names.push("MinimumBoundingEnvelope".to_string()); tool_names.push("MinimumConvexHull".to_string()); tool_names.push("NarrownessIndex".to_string()); + tool_names.push("NaturalNeighbourInterpolation".to_string()); tool_names.push("NearestNeighbourGridding".to_string()); tool_names.push("MinOverlay".to_string()); tool_names.push("PatchOrientation".to_string()); @@ -145,6 +146,7 @@ impl ToolManager { tool_names.push("AverageUpslopeFlowpathLength".to_string()); tool_names.push("Basins".to_string()); tool_names.push("BreachDepressions".to_string()); + tool_names.push("BreachDepressionsLeastCost".to_string()); tool_names.push("BreachSingleCellPits".to_string()); tool_names.push("BurnStreamsAtRoads".to_string()); tool_names.push("D8FlowAccumulation".to_string()); @@ -162,6 +164,7 @@ impl ToolManager { tool_names.push("FD8Pointer".to_string()); tool_names.push("FillBurn".to_string()); tool_names.push("FillDepressions".to_string()); + tool_names.push("FillDepressionsWangAndLui".to_string()); tool_names.push("FillSingleCellPits".to_string()); tool_names.push("FindNoFlowCells".to_string()); tool_names.push("FindParallelFlow".to_string()); @@ -185,6 +188,7 @@ impl ToolManager { tool_names.push("Subbasins".to_string()); tool_names.push("TraceDownslopeFlowpaths".to_string()); tool_names.push("UnnestBasins".to_string()); + tool_names.push("UpslopeDepressionStorage".to_string()); tool_names.push("Watershed".to_string()); // image_analysis @@ -260,6 +264,7 @@ impl ToolManager { // tool_names.push("AsciiToLas".to_string()); tool_names.push("LidarBlockMaximum".to_string()); tool_names.push("LidarBlockMinimum".to_string()); + tool_names.push("ClassifyBuildingsInLidar".to_string()); tool_names.push("ClassifyOverlapPoints".to_string()); tool_names.push("ClipLidarToPolygon".to_string()); tool_names.push("ErasePolygonFromLidar".to_string()); @@ -285,6 +290,7 @@ impl ToolManager { tool_names.push("LidarNearestNeighbourGridding".to_string()); tool_names.push("LidarPointDensity".to_string()); tool_names.push("LidarPointStats".to_string()); + tool_names.push("LidarRfbInterpolation".to_string()); tool_names.push("LidarRansacPlanes".to_string()); tool_names.push("LidarRemoveDuplicates".to_string()); tool_names.push("LidarRemoveOutliers".to_string()); @@ -330,6 +336,7 @@ impl ToolManager { tool_names.push("GreaterThan".to_string()); tool_names.push("ImageAutocorrelation".to_string()); tool_names.push("ImageCorrelation".to_string()); + tool_names.push("ImageCorrelationNeighbourhoodAnalysis".to_string()); tool_names.push("ImageRegression".to_string()); tool_names.push("Increment".to_string()); tool_names.push("InPlaceAdd".to_string()); @@ -576,6 +583,7 @@ impl ToolManager { } "minimumconvexhull" => Some(Box::new(gis_analysis::MinimumConvexHull::new())), "minoverlay" => Some(Box::new(gis_analysis::MinOverlay::new())), + "naturalneighbourinterpolation" => Some(Box::new(gis_analysis::NaturalNeighbourInterpolation::new())), "nearestneighbourgridding" => { Some(Box::new(gis_analysis::NearestNeighbourGridding::new())) } @@ -623,6 +631,7 @@ impl ToolManager { } "basins" => Some(Box::new(hydro_analysis::Basins::new())), "breachdepressions" => Some(Box::new(hydro_analysis::BreachDepressions::new())), + "breachdepressionsleastcost" => Some(Box::new(hydro_analysis::BreachDepressionsLeastCost::new())), "breachsinglecellpits" => Some(Box::new(hydro_analysis::BreachSingleCellPits::new())), "burnstreamsatroads" => Some(Box::new(hydro_analysis::BurnStreamsAtRoads::new())), "d8flowaccumulation" => Some(Box::new(hydro_analysis::D8FlowAccumulation::new())), @@ -646,6 +655,7 @@ impl ToolManager { "fd8pointer" => Some(Box::new(hydro_analysis::FD8Pointer::new())), "fillburn" => Some(Box::new(hydro_analysis::FillBurn::new())), "filldepressions" => Some(Box::new(hydro_analysis::FillDepressions::new())), + "filldepressionswangandlui" => Some(Box::new(hydro_analysis::FillDepressionsWangAndLui::new())), "fillsinglecellpits" => Some(Box::new(hydro_analysis::FillSingleCellPits::new())), "findnoflowcells" => Some(Box::new(hydro_analysis::FindNoFlowCells::new())), "findparallelflow" => Some(Box::new(hydro_analysis::FindParallelFlow::new())), @@ -679,6 +689,7 @@ impl ToolManager { Some(Box::new(hydro_analysis::TraceDownslopeFlowpaths::new())) } "unnestbasins" => Some(Box::new(hydro_analysis::UnnestBasins::new())), + "upslopedepressionstorage" => Some(Box::new(hydro_analysis::UpslopeDepressionStorage::new())), "watershed" => Some(Box::new(hydro_analysis::Watershed::new())), // image_analysis @@ -788,6 +799,7 @@ impl ToolManager { // "asciitolas" => Some(Box::new(lidar_analysis::AsciiToLas::new())), "lidarblockmaximum" => Some(Box::new(lidar_analysis::LidarBlockMaximum::new())), "lidarblockminimum" => Some(Box::new(lidar_analysis::LidarBlockMinimum::new())), + "classifybuildingsinlidar" => Some(Box::new(lidar_analysis::ClassifyBuildingsInLidar::new())), "classifyoverlappoints" => Some(Box::new(lidar_analysis::ClassifyOverlapPoints::new())), "cliplidartopolygon" => Some(Box::new(lidar_analysis::ClipLidarToPolygon::new())), "erasepolygonfromlidar" => Some(Box::new(lidar_analysis::ErasePolygonFromLidar::new())), @@ -823,6 +835,7 @@ impl ToolManager { )), "lidarpointdensity" => Some(Box::new(lidar_analysis::LidarPointDensity::new())), "lidarpointstats" => Some(Box::new(lidar_analysis::LidarPointStats::new())), + "lidarrfbinterpolation" => Some(Box::new(lidar_analysis::LidarRfbInterpolation::new())), "lidarransacplanes" => Some(Box::new(lidar_analysis::LidarRansacPlanes::new())), "lidarremoveduplicates" => Some(Box::new(lidar_analysis::LidarRemoveDuplicates::new())), "lidarremoveoutliers" => Some(Box::new(lidar_analysis::LidarRemoveOutliers::new())), @@ -880,6 +893,7 @@ impl ToolManager { Some(Box::new(math_stat_analysis::ImageAutocorrelation::new())) } "imagecorrelation" => Some(Box::new(math_stat_analysis::ImageCorrelation::new())), + "imagecorrelationneighbourhoodanalysis" => Some(Box::new(math_stat_analysis::ImageCorrelationNeighbourhoodAnalysis::new())), "imageregression" => Some(Box::new(math_stat_analysis::ImageRegression::new())), "increment" => Some(Box::new(math_stat_analysis::Increment::new())), "inplaceadd" => Some(Box::new(math_stat_analysis::InPlaceAdd::new())), diff --git a/src/tools/stream_network_analysis/dist_to_outlet.rs b/src/tools/stream_network_analysis/dist_to_outlet.rs index 4c88f124a..44c09e68d 100644 --- a/src/tools/stream_network_analysis/dist_to_outlet.rs +++ b/src/tools/stream_network_analysis/dist_to_outlet.rs @@ -165,7 +165,7 @@ impl WhiteboxTool for DistanceToOutlet { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/extract_streams.rs b/src/tools/stream_network_analysis/extract_streams.rs index f9ab7a36f..1c505037d 100644 --- a/src/tools/stream_network_analysis/extract_streams.rs +++ b/src/tools/stream_network_analysis/extract_streams.rs @@ -164,7 +164,7 @@ impl WhiteboxTool for ExtractStreams { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/extract_valleys.rs b/src/tools/stream_network_analysis/extract_valleys.rs index a196b8f0f..4f92e30d8 100644 --- a/src/tools/stream_network_analysis/extract_valleys.rs +++ b/src/tools/stream_network_analysis/extract_valleys.rs @@ -207,7 +207,7 @@ impl WhiteboxTool for ExtractValleys { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/farthest_channel_head.rs b/src/tools/stream_network_analysis/farthest_channel_head.rs index 95b699b1a..a14f8836c 100644 --- a/src/tools/stream_network_analysis/farthest_channel_head.rs +++ b/src/tools/stream_network_analysis/farthest_channel_head.rs @@ -166,7 +166,7 @@ impl WhiteboxTool for FarthestChannelHead { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/find_main_stem.rs b/src/tools/stream_network_analysis/find_main_stem.rs index 29172ca1a..776d5e6fd 100644 --- a/src/tools/stream_network_analysis/find_main_stem.rs +++ b/src/tools/stream_network_analysis/find_main_stem.rs @@ -174,7 +174,7 @@ impl WhiteboxTool for FindMainStem { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/hack_order.rs b/src/tools/stream_network_analysis/hack_order.rs index 70f4f4fba..9bb9c8794 100644 --- a/src/tools/stream_network_analysis/hack_order.rs +++ b/src/tools/stream_network_analysis/hack_order.rs @@ -178,7 +178,7 @@ impl WhiteboxTool for HackStreamOrder { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/horton_order.rs b/src/tools/stream_network_analysis/horton_order.rs index c4b023307..35f11399c 100644 --- a/src/tools/stream_network_analysis/horton_order.rs +++ b/src/tools/stream_network_analysis/horton_order.rs @@ -177,7 +177,7 @@ impl WhiteboxTool for HortonStreamOrder { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/long_profile.rs b/src/tools/stream_network_analysis/long_profile.rs index 366caacec..57d553b90 100644 --- a/src/tools/stream_network_analysis/long_profile.rs +++ b/src/tools/stream_network_analysis/long_profile.rs @@ -182,7 +182,7 @@ impl WhiteboxTool for LongProfile { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/long_profile_from_points.rs b/src/tools/stream_network_analysis/long_profile_from_points.rs index b5f340089..3d495971d 100644 --- a/src/tools/stream_network_analysis/long_profile_from_points.rs +++ b/src/tools/stream_network_analysis/long_profile_from_points.rs @@ -172,7 +172,7 @@ impl WhiteboxTool for LongProfileFromPoints { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/raster_streams_to_vector.rs b/src/tools/stream_network_analysis/raster_streams_to_vector.rs index 7d1cbe2a0..71b8ce20f 100644 --- a/src/tools/stream_network_analysis/raster_streams_to_vector.rs +++ b/src/tools/stream_network_analysis/raster_streams_to_vector.rs @@ -155,7 +155,7 @@ impl WhiteboxTool for RasterStreamsToVector { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/rasterize_streams.rs b/src/tools/stream_network_analysis/rasterize_streams.rs index c129d1fef..dc49ed04a 100644 --- a/src/tools/stream_network_analysis/rasterize_streams.rs +++ b/src/tools/stream_network_analysis/rasterize_streams.rs @@ -162,7 +162,7 @@ impl WhiteboxTool for RasterizeStreams { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/remove_short_streams.rs b/src/tools/stream_network_analysis/remove_short_streams.rs index 04ab78ac2..9bfb65a2a 100644 --- a/src/tools/stream_network_analysis/remove_short_streams.rs +++ b/src/tools/stream_network_analysis/remove_short_streams.rs @@ -161,7 +161,7 @@ impl WhiteboxTool for RemoveShortStreams { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/shreve_magnitude.rs b/src/tools/stream_network_analysis/shreve_magnitude.rs index d33c5fe1f..4ec740f2e 100644 --- a/src/tools/stream_network_analysis/shreve_magnitude.rs +++ b/src/tools/stream_network_analysis/shreve_magnitude.rs @@ -170,7 +170,7 @@ impl WhiteboxTool for ShreveStreamMagnitude { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/strahler_order.rs b/src/tools/stream_network_analysis/strahler_order.rs index b51795b7b..4c2d609e1 100644 --- a/src/tools/stream_network_analysis/strahler_order.rs +++ b/src/tools/stream_network_analysis/strahler_order.rs @@ -176,7 +176,7 @@ impl WhiteboxTool for StrahlerStreamOrder { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/stream_link_class.rs b/src/tools/stream_network_analysis/stream_link_class.rs index 98775ffad..54c8cb14d 100644 --- a/src/tools/stream_network_analysis/stream_link_class.rs +++ b/src/tools/stream_network_analysis/stream_link_class.rs @@ -178,7 +178,7 @@ impl WhiteboxTool for StreamLinkClass { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/stream_link_id.rs b/src/tools/stream_network_analysis/stream_link_id.rs index 6be8a8e32..d4250e32b 100644 --- a/src/tools/stream_network_analysis/stream_link_id.rs +++ b/src/tools/stream_network_analysis/stream_link_id.rs @@ -170,7 +170,7 @@ impl WhiteboxTool for StreamLinkIdentifier { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/stream_link_length.rs b/src/tools/stream_network_analysis/stream_link_length.rs index 941a6ec2a..5fb6a8899 100644 --- a/src/tools/stream_network_analysis/stream_link_length.rs +++ b/src/tools/stream_network_analysis/stream_link_length.rs @@ -160,7 +160,7 @@ impl WhiteboxTool for StreamLinkLength { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/stream_link_slope.rs b/src/tools/stream_network_analysis/stream_link_slope.rs index e66abaa12..2b925fa0d 100644 --- a/src/tools/stream_network_analysis/stream_link_slope.rs +++ b/src/tools/stream_network_analysis/stream_link_slope.rs @@ -177,7 +177,7 @@ impl WhiteboxTool for StreamLinkSlope { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/stream_slope_continuous.rs b/src/tools/stream_network_analysis/stream_slope_continuous.rs index 4ef009958..ec897957e 100644 --- a/src/tools/stream_network_analysis/stream_slope_continuous.rs +++ b/src/tools/stream_network_analysis/stream_slope_continuous.rs @@ -178,7 +178,7 @@ impl WhiteboxTool for StreamSlopeContinuous { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/topological_stream_order.rs b/src/tools/stream_network_analysis/topological_stream_order.rs index 0e86ce9a5..b8d28a681 100644 --- a/src/tools/stream_network_analysis/topological_stream_order.rs +++ b/src/tools/stream_network_analysis/topological_stream_order.rs @@ -167,7 +167,7 @@ impl WhiteboxTool for TopologicalStreamOrder { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/total_length_channels.rs b/src/tools/stream_network_analysis/total_length_channels.rs index 0d3478fb7..832c92e68 100644 --- a/src/tools/stream_network_analysis/total_length_channels.rs +++ b/src/tools/stream_network_analysis/total_length_channels.rs @@ -164,7 +164,7 @@ impl WhiteboxTool for LengthOfUpstreamChannels { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/tributary_id.rs b/src/tools/stream_network_analysis/tributary_id.rs index 703597ebf..f339cd928 100644 --- a/src/tools/stream_network_analysis/tributary_id.rs +++ b/src/tools/stream_network_analysis/tributary_id.rs @@ -173,7 +173,7 @@ impl WhiteboxTool for TributaryIdentifier { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/stream_network_analysis/vector_stream_network_analysis.rs b/src/tools/stream_network_analysis/vector_stream_network_analysis.rs index 3f93e5dc9..721d0a432 100644 --- a/src/tools/stream_network_analysis/vector_stream_network_analysis.rs +++ b/src/tools/stream_network_analysis/vector_stream_network_analysis.rs @@ -155,7 +155,7 @@ impl WhiteboxTool for VectorStreamNetworkAnalysis { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/aspect.rs b/src/tools/terrain_analysis/aspect.rs index c88eadbc1..5bef3f035 100644 --- a/src/tools/terrain_analysis/aspect.rs +++ b/src/tools/terrain_analysis/aspect.rs @@ -167,7 +167,7 @@ impl WhiteboxTool for Aspect { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/average_normal_vector_angular_deviation.rs b/src/tools/terrain_analysis/average_normal_vector_angular_deviation.rs index 9e609ffbb..8e2e8db79 100644 --- a/src/tools/terrain_analysis/average_normal_vector_angular_deviation.rs +++ b/src/tools/terrain_analysis/average_normal_vector_angular_deviation.rs @@ -152,7 +152,7 @@ impl WhiteboxTool for AverageNormalVectorAngularDeviation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/circular_variance_of_aspect.rs b/src/tools/terrain_analysis/circular_variance_of_aspect.rs index cd5a23f40..b34545fc5 100644 --- a/src/tools/terrain_analysis/circular_variance_of_aspect.rs +++ b/src/tools/terrain_analysis/circular_variance_of_aspect.rs @@ -159,7 +159,7 @@ impl WhiteboxTool for CircularVarianceOfAspect { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/dev_from_mean_elev.rs b/src/tools/terrain_analysis/dev_from_mean_elev.rs index 289fd67dc..90ab560ab 100644 --- a/src/tools/terrain_analysis/dev_from_mean_elev.rs +++ b/src/tools/terrain_analysis/dev_from_mean_elev.rs @@ -164,7 +164,7 @@ impl WhiteboxTool for DevFromMeanElev { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/diff_from_mean_elev.rs b/src/tools/terrain_analysis/diff_from_mean_elev.rs index a5a3ba171..fcd487b06 100644 --- a/src/tools/terrain_analysis/diff_from_mean_elev.rs +++ b/src/tools/terrain_analysis/diff_from_mean_elev.rs @@ -165,7 +165,7 @@ impl WhiteboxTool for DiffFromMeanElev { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/directional_relief.rs b/src/tools/terrain_analysis/directional_relief.rs index 68dee1959..4a10f89a6 100644 --- a/src/tools/terrain_analysis/directional_relief.rs +++ b/src/tools/terrain_analysis/directional_relief.rs @@ -171,7 +171,7 @@ impl WhiteboxTool for DirectionalRelief { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/downslope_index.rs b/src/tools/terrain_analysis/downslope_index.rs index e94f3fd80..6555547e9 100644 --- a/src/tools/terrain_analysis/downslope_index.rs +++ b/src/tools/terrain_analysis/downslope_index.rs @@ -172,7 +172,7 @@ impl WhiteboxTool for DownslopeIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/drainage_preserving_smoothing.rs b/src/tools/terrain_analysis/drainage_preserving_smoothing.rs index fd622a6b4..5fdcdcd70 100644 --- a/src/tools/terrain_analysis/drainage_preserving_smoothing.rs +++ b/src/tools/terrain_analysis/drainage_preserving_smoothing.rs @@ -219,7 +219,7 @@ impl WhiteboxTool for DrainagePreservingSmoothing { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/edge_density.rs b/src/tools/terrain_analysis/edge_density.rs index 24cdd97c4..168ec736d 100644 --- a/src/tools/terrain_analysis/edge_density.rs +++ b/src/tools/terrain_analysis/edge_density.rs @@ -174,7 +174,7 @@ impl WhiteboxTool for EdgeDensity { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/elev_above_pit.rs b/src/tools/terrain_analysis/elev_above_pit.rs index 6f6af2da8..b15aee906 100644 --- a/src/tools/terrain_analysis/elev_above_pit.rs +++ b/src/tools/terrain_analysis/elev_above_pit.rs @@ -134,7 +134,7 @@ impl WhiteboxTool for ElevAbovePit { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/elev_percentile.rs b/src/tools/terrain_analysis/elev_percentile.rs index fb1b022ed..45a92e18b 100644 --- a/src/tools/terrain_analysis/elev_percentile.rs +++ b/src/tools/terrain_analysis/elev_percentile.rs @@ -195,7 +195,7 @@ impl WhiteboxTool for ElevPercentile { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/elev_relative_to_min_max.rs b/src/tools/terrain_analysis/elev_relative_to_min_max.rs index c88564ed3..04d6eaad1 100644 --- a/src/tools/terrain_analysis/elev_relative_to_min_max.rs +++ b/src/tools/terrain_analysis/elev_relative_to_min_max.rs @@ -131,7 +131,7 @@ impl WhiteboxTool for ElevRelativeToMinMax { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/elev_relative_to_watershed_min_max.rs b/src/tools/terrain_analysis/elev_relative_to_watershed_min_max.rs index be4af186b..a340969a1 100644 --- a/src/tools/terrain_analysis/elev_relative_to_watershed_min_max.rs +++ b/src/tools/terrain_analysis/elev_relative_to_watershed_min_max.rs @@ -138,7 +138,7 @@ impl WhiteboxTool for ElevRelativeToWatershedMinMax { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/feature_preserving_smoothing.rs b/src/tools/terrain_analysis/feature_preserving_smoothing.rs index b21737c57..b75ec2166 100644 --- a/src/tools/terrain_analysis/feature_preserving_smoothing.rs +++ b/src/tools/terrain_analysis/feature_preserving_smoothing.rs @@ -206,7 +206,7 @@ impl WhiteboxTool for FeaturePreservingSmoothing { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/fetch_analysis.rs b/src/tools/terrain_analysis/fetch_analysis.rs index 7af4d806a..3379cc06d 100644 --- a/src/tools/terrain_analysis/fetch_analysis.rs +++ b/src/tools/terrain_analysis/fetch_analysis.rs @@ -173,7 +173,7 @@ impl WhiteboxTool for FetchAnalysis { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/fill_missing_data.rs b/src/tools/terrain_analysis/fill_missing_data.rs index 44b8b92c5..746d99dcc 100644 --- a/src/tools/terrain_analysis/fill_missing_data.rs +++ b/src/tools/terrain_analysis/fill_missing_data.rs @@ -159,7 +159,7 @@ impl WhiteboxTool for FillMissingData { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/find_ridges.rs b/src/tools/terrain_analysis/find_ridges.rs index ebece8c78..82645be06 100644 --- a/src/tools/terrain_analysis/find_ridges.rs +++ b/src/tools/terrain_analysis/find_ridges.rs @@ -138,7 +138,7 @@ impl WhiteboxTool for FindRidges { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/geomorphons.rs b/src/tools/terrain_analysis/geomorphons.rs index 74e650a6f..2ea304614 100644 --- a/src/tools/terrain_analysis/geomorphons.rs +++ b/src/tools/terrain_analysis/geomorphons.rs @@ -153,7 +153,7 @@ impl WhiteboxTool for Geomorphons { if args.len() == 0 { return Err(Error::new(ErrorKind::InvalidInput, - "Tool run with no paramters.")); + "Tool run with no parameters.")); } for i in 0..args.len() { let mut arg = args[i].replace("\"", ""); diff --git a/src/tools/terrain_analysis/hillshade.rs b/src/tools/terrain_analysis/hillshade.rs index f044fe8dc..2f44539d8 100644 --- a/src/tools/terrain_analysis/hillshade.rs +++ b/src/tools/terrain_analysis/hillshade.rs @@ -182,7 +182,7 @@ impl WhiteboxTool for Hillshade { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/horizon_angle.rs b/src/tools/terrain_analysis/horizon_angle.rs index 62312d956..5a1152bcd 100644 --- a/src/tools/terrain_analysis/horizon_angle.rs +++ b/src/tools/terrain_analysis/horizon_angle.rs @@ -168,7 +168,7 @@ impl WhiteboxTool for HorizonAngle { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/hypsometric_analysis.rs b/src/tools/terrain_analysis/hypsometric_analysis.rs index 2afadd1d6..c31621d29 100644 --- a/src/tools/terrain_analysis/hypsometric_analysis.rs +++ b/src/tools/terrain_analysis/hypsometric_analysis.rs @@ -142,7 +142,7 @@ impl WhiteboxTool for HypsometricAnalysis { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/max_anisotropy_dev.rs b/src/tools/terrain_analysis/max_anisotropy_dev.rs index a81b0eba1..fd51bc219 100644 --- a/src/tools/terrain_analysis/max_anisotropy_dev.rs +++ b/src/tools/terrain_analysis/max_anisotropy_dev.rs @@ -162,7 +162,7 @@ impl WhiteboxTool for MaxAnisotropyDev { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/max_anisotropy_dev_signature.rs b/src/tools/terrain_analysis/max_anisotropy_dev_signature.rs index dcd4a51f9..178e63165 100644 --- a/src/tools/terrain_analysis/max_anisotropy_dev_signature.rs +++ b/src/tools/terrain_analysis/max_anisotropy_dev_signature.rs @@ -166,7 +166,7 @@ impl WhiteboxTool for MaxAnisotropyDevSignature { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/max_branch_length.rs b/src/tools/terrain_analysis/max_branch_length.rs index d9ebe2163..17d41e6b5 100644 --- a/src/tools/terrain_analysis/max_branch_length.rs +++ b/src/tools/terrain_analysis/max_branch_length.rs @@ -167,7 +167,7 @@ impl WhiteboxTool for MaxBranchLength { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/max_diff_from_mean.rs b/src/tools/terrain_analysis/max_diff_from_mean.rs index f862d83d4..d34d8626c 100644 --- a/src/tools/terrain_analysis/max_diff_from_mean.rs +++ b/src/tools/terrain_analysis/max_diff_from_mean.rs @@ -164,7 +164,7 @@ impl WhiteboxTool for MaxDifferenceFromMean { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/max_downslope_elev_change.rs b/src/tools/terrain_analysis/max_downslope_elev_change.rs index eed470aea..375ebd54d 100644 --- a/src/tools/terrain_analysis/max_downslope_elev_change.rs +++ b/src/tools/terrain_analysis/max_downslope_elev_change.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for MaxDownslopeElevChange { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/max_elev_dev_signature.rs b/src/tools/terrain_analysis/max_elev_dev_signature.rs index ddee10890..c3e9bb9a7 100644 --- a/src/tools/terrain_analysis/max_elev_dev_signature.rs +++ b/src/tools/terrain_analysis/max_elev_dev_signature.rs @@ -166,7 +166,7 @@ impl WhiteboxTool for MaxElevDevSignature { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/max_elev_deviation.rs b/src/tools/terrain_analysis/max_elev_deviation.rs index d0c90f1ce..e6fb50741 100644 --- a/src/tools/terrain_analysis/max_elev_deviation.rs +++ b/src/tools/terrain_analysis/max_elev_deviation.rs @@ -198,7 +198,7 @@ impl WhiteboxTool for MaxElevationDeviation { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/min_downslope_elev_change.rs b/src/tools/terrain_analysis/min_downslope_elev_change.rs index 19806c4fc..960fb81d5 100644 --- a/src/tools/terrain_analysis/min_downslope_elev_change.rs +++ b/src/tools/terrain_analysis/min_downslope_elev_change.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for MinDownslopeElevChange { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/multiscale_roughness.rs b/src/tools/terrain_analysis/multiscale_roughness.rs index d8646d864..4c0ff42bc 100644 --- a/src/tools/terrain_analysis/multiscale_roughness.rs +++ b/src/tools/terrain_analysis/multiscale_roughness.rs @@ -164,7 +164,7 @@ impl WhiteboxTool for MultiscaleRoughness { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/multiscale_roughness_signature.rs b/src/tools/terrain_analysis/multiscale_roughness_signature.rs index 65becc39d..4bcb21135 100644 --- a/src/tools/terrain_analysis/multiscale_roughness_signature.rs +++ b/src/tools/terrain_analysis/multiscale_roughness_signature.rs @@ -174,7 +174,7 @@ impl WhiteboxTool for MultiscaleRoughnessSignature { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/multiscale_std_dev_normals.rs b/src/tools/terrain_analysis/multiscale_std_dev_normals.rs index f16664ea5..a3beb5b27 100644 --- a/src/tools/terrain_analysis/multiscale_std_dev_normals.rs +++ b/src/tools/terrain_analysis/multiscale_std_dev_normals.rs @@ -212,7 +212,7 @@ impl WhiteboxTool for MultiscaleStdDevNormals { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/multiscale_std_dev_normals_signature.rs b/src/tools/terrain_analysis/multiscale_std_dev_normals_signature.rs index ac2057038..92bbf817e 100644 --- a/src/tools/terrain_analysis/multiscale_std_dev_normals_signature.rs +++ b/src/tools/terrain_analysis/multiscale_std_dev_normals_signature.rs @@ -182,7 +182,7 @@ impl WhiteboxTool for MultiscaleStdDevNormalsSignature { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/multiscale_topographic_position_image.rs b/src/tools/terrain_analysis/multiscale_topographic_position_image.rs index d741a413d..6a905438b 100644 --- a/src/tools/terrain_analysis/multiscale_topographic_position_image.rs +++ b/src/tools/terrain_analysis/multiscale_topographic_position_image.rs @@ -173,7 +173,7 @@ impl WhiteboxTool for MultiscaleTopographicPositionImage { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/num_downslope_neighbours.rs b/src/tools/terrain_analysis/num_downslope_neighbours.rs index 8cfd22088..9c52e19b3 100644 --- a/src/tools/terrain_analysis/num_downslope_neighbours.rs +++ b/src/tools/terrain_analysis/num_downslope_neighbours.rs @@ -135,7 +135,7 @@ impl WhiteboxTool for NumDownslopeNeighbours { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/num_upslope_neighbours.rs b/src/tools/terrain_analysis/num_upslope_neighbours.rs index d6073be78..1ce05d4f9 100644 --- a/src/tools/terrain_analysis/num_upslope_neighbours.rs +++ b/src/tools/terrain_analysis/num_upslope_neighbours.rs @@ -136,7 +136,7 @@ impl WhiteboxTool for NumUpslopeNeighbours { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/pennock_landform_class.rs b/src/tools/terrain_analysis/pennock_landform_class.rs index f214a1ad0..a94e94250 100644 --- a/src/tools/terrain_analysis/pennock_landform_class.rs +++ b/src/tools/terrain_analysis/pennock_landform_class.rs @@ -213,7 +213,7 @@ impl WhiteboxTool for PennockLandformClass { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/percent_elev_range.rs b/src/tools/terrain_analysis/percent_elev_range.rs index 1446d9c05..cba3dd662 100644 --- a/src/tools/terrain_analysis/percent_elev_range.rs +++ b/src/tools/terrain_analysis/percent_elev_range.rs @@ -166,7 +166,7 @@ impl WhiteboxTool for PercentElevRange { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/plan_curvature.rs b/src/tools/terrain_analysis/plan_curvature.rs index 2d59148de..01ba62302 100644 --- a/src/tools/terrain_analysis/plan_curvature.rs +++ b/src/tools/terrain_analysis/plan_curvature.rs @@ -163,7 +163,7 @@ impl WhiteboxTool for PlanCurvature { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/prof_curvature.rs b/src/tools/terrain_analysis/prof_curvature.rs index 1a2498ec5..d1d5eaebb 100644 --- a/src/tools/terrain_analysis/prof_curvature.rs +++ b/src/tools/terrain_analysis/prof_curvature.rs @@ -163,7 +163,7 @@ impl WhiteboxTool for ProfileCurvature { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/profile.rs b/src/tools/terrain_analysis/profile.rs index b5761a492..9ea0b4a30 100644 --- a/src/tools/terrain_analysis/profile.rs +++ b/src/tools/terrain_analysis/profile.rs @@ -147,7 +147,7 @@ impl WhiteboxTool for Profile { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/relative_aspect.rs b/src/tools/terrain_analysis/relative_aspect.rs index d306e91f6..d8e0b0f58 100644 --- a/src/tools/terrain_analysis/relative_aspect.rs +++ b/src/tools/terrain_analysis/relative_aspect.rs @@ -164,7 +164,7 @@ impl WhiteboxTool for RelativeAspect { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/relative_stream_power_index.rs b/src/tools/terrain_analysis/relative_stream_power_index.rs index 257762c0b..500c3fb77 100644 --- a/src/tools/terrain_analysis/relative_stream_power_index.rs +++ b/src/tools/terrain_analysis/relative_stream_power_index.rs @@ -162,7 +162,7 @@ impl WhiteboxTool for StreamPowerIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/relative_topographic_position.rs b/src/tools/terrain_analysis/relative_topographic_position.rs index d37f50891..d7cc705a6 100644 --- a/src/tools/terrain_analysis/relative_topographic_position.rs +++ b/src/tools/terrain_analysis/relative_topographic_position.rs @@ -175,7 +175,7 @@ impl WhiteboxTool for RelativeTopographicPosition { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/remove_off_terrain_objects.rs b/src/tools/terrain_analysis/remove_off_terrain_objects.rs index 2a6ec67f4..a5c92ae29 100644 --- a/src/tools/terrain_analysis/remove_off_terrain_objects.rs +++ b/src/tools/terrain_analysis/remove_off_terrain_objects.rs @@ -161,7 +161,7 @@ impl WhiteboxTool for RemoveOffTerrainObjects { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/ruggedness_index.rs b/src/tools/terrain_analysis/ruggedness_index.rs index 92646bc63..c3d7ab100 100644 --- a/src/tools/terrain_analysis/ruggedness_index.rs +++ b/src/tools/terrain_analysis/ruggedness_index.rs @@ -155,7 +155,7 @@ impl WhiteboxTool for RuggednessIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/sediment_transport_index.rs b/src/tools/terrain_analysis/sediment_transport_index.rs index 97e63bf61..7b61e22ee 100644 --- a/src/tools/terrain_analysis/sediment_transport_index.rs +++ b/src/tools/terrain_analysis/sediment_transport_index.rs @@ -183,7 +183,7 @@ impl WhiteboxTool for SedimentTransportIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -313,7 +313,7 @@ impl WhiteboxTool for SedimentTransportIndex { } } - let elapsed_time = get_formatted_elapsed_time(start);; + let elapsed_time = get_formatted_elapsed_time(start); output.configs.data_type = DataType::F32; output.configs.palette = "grey.plt".to_string(); output.configs.photometric_interp = PhotometricInterpretation::Continuous; diff --git a/src/tools/terrain_analysis/slope.rs b/src/tools/terrain_analysis/slope.rs index 4c1a0a730..0d627792a 100644 --- a/src/tools/terrain_analysis/slope.rs +++ b/src/tools/terrain_analysis/slope.rs @@ -174,7 +174,7 @@ impl WhiteboxTool for Slope { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/slope_vs_elev_plot.rs b/src/tools/terrain_analysis/slope_vs_elev_plot.rs index bd4c9b547..108fd8cd6 100644 --- a/src/tools/terrain_analysis/slope_vs_elev_plot.rs +++ b/src/tools/terrain_analysis/slope_vs_elev_plot.rs @@ -154,7 +154,7 @@ impl WhiteboxTool for SlopeVsElevationPlot { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { @@ -601,7 +601,7 @@ impl WhiteboxTool for SlopeVsElevationPlot { } } writer.write_all(("

").as_bytes())?; - let elapsed_time = get_formatted_elapsed_time(start);; + let elapsed_time = get_formatted_elapsed_time(start); let multiples = num_files > 1; diff --git a/src/tools/terrain_analysis/spherical_std_dev_of_normals.rs b/src/tools/terrain_analysis/spherical_std_dev_of_normals.rs index ece0b7981..32d7a2eaf 100644 --- a/src/tools/terrain_analysis/spherical_std_dev_of_normals.rs +++ b/src/tools/terrain_analysis/spherical_std_dev_of_normals.rs @@ -169,7 +169,7 @@ impl WhiteboxTool for SphericalStdDevOfNormals { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/standard_deviation_of_slope.rs b/src/tools/terrain_analysis/standard_deviation_of_slope.rs index 3b90140f3..f539cce2f 100644 --- a/src/tools/terrain_analysis/standard_deviation_of_slope.rs +++ b/src/tools/terrain_analysis/standard_deviation_of_slope.rs @@ -162,7 +162,7 @@ impl WhiteboxTool for StandardDeviationOfSlope { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } //checks arguments diff --git a/src/tools/terrain_analysis/surface_area_ratio.rs b/src/tools/terrain_analysis/surface_area_ratio.rs index 63ee33e62..48873ab94 100644 --- a/src/tools/terrain_analysis/surface_area_ratio.rs +++ b/src/tools/terrain_analysis/surface_area_ratio.rs @@ -137,7 +137,7 @@ impl WhiteboxTool for SurfaceAreaRatio { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/tan_curvature.rs b/src/tools/terrain_analysis/tan_curvature.rs index 0147ae713..c14bb671f 100644 --- a/src/tools/terrain_analysis/tan_curvature.rs +++ b/src/tools/terrain_analysis/tan_curvature.rs @@ -153,7 +153,7 @@ impl WhiteboxTool for TangentialCurvature { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/total_curvature.rs b/src/tools/terrain_analysis/total_curvature.rs index 61edf5e5a..9213746b0 100644 --- a/src/tools/terrain_analysis/total_curvature.rs +++ b/src/tools/terrain_analysis/total_curvature.rs @@ -155,7 +155,7 @@ impl WhiteboxTool for TotalCurvature { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/viewshed.rs b/src/tools/terrain_analysis/viewshed.rs index cb2e4436e..2ce7b24ff 100644 --- a/src/tools/terrain_analysis/viewshed.rs +++ b/src/tools/terrain_analysis/viewshed.rs @@ -169,7 +169,7 @@ impl WhiteboxTool for Viewshed { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/visibility_index.rs b/src/tools/terrain_analysis/visibility_index.rs index 338a5ab2e..b3c8fe5fa 100644 --- a/src/tools/terrain_analysis/visibility_index.rs +++ b/src/tools/terrain_analysis/visibility_index.rs @@ -166,7 +166,7 @@ impl WhiteboxTool for VisibilityIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/tools/terrain_analysis/wetness_index.rs b/src/tools/terrain_analysis/wetness_index.rs index 5a68322f6..192e1baf8 100644 --- a/src/tools/terrain_analysis/wetness_index.rs +++ b/src/tools/terrain_analysis/wetness_index.rs @@ -132,7 +132,7 @@ impl WhiteboxTool for WetnessIndex { if args.len() == 0 { return Err(Error::new( ErrorKind::InvalidInput, - "Tool run with no paramters.", + "Tool run with no parameters.", )); } for i in 0..args.len() { diff --git a/src/vector/shapefile/mod.rs b/src/vector/shapefile/mod.rs index b6e611675..f6da15c62 100644 --- a/src/vector/shapefile/mod.rs +++ b/src/vector/shapefile/mod.rs @@ -1,7 +1,7 @@ /* This code is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 21, 2017 +Created: 21/06/2017 Last Modified: 17/10/2018 License: MIT @@ -13,15 +13,10 @@ pub mod geometry; use self::attributes::*; use self::geometry::*; -// use attributes::{ -// AttributeField, AttributeHeader, DateData, FieldData, FieldDataType, Intersector, -// ShapefileAttributes, -// }; use crate::structures::Point2D; use crate::utils::{ByteOrderReader, Endianness}; use byteorder::{BigEndian, LittleEndian, WriteBytesExt}; use chrono::prelude::*; -// use geometry::{ShapeType, ShapeTypeDimension, ShapefileGeometry}; use std::f64; use std::fmt; use std::fs; @@ -31,7 +26,6 @@ use std::io::{BufReader, BufWriter, Cursor, Error, ErrorKind}; use std::path::Path; use std::str; -/// `ShapefileHeader` stores the header variables of a ShapeFile header. #[derive(Debug, Default, Clone)] pub struct ShapefileHeader { file_code: i32, // BigEndian; value is 9994 diff --git a/tempCodeRunnerFile.py b/tempCodeRunnerFile.py deleted file mode 100644 index 414732016..000000000 --- a/tempCodeRunnerFile.py +++ /dev/null @@ -1,7321 +0,0 @@ -#!/usr/bin/env python -''' This file is intended to be a helper for running whitebox-tools plugins from a Python script. -See whitebox_example.py for an example of how to use it. -''' - -# This script is part of the WhiteboxTools geospatial library. -# Authors: Dr. John Lindsay -# Created: 28/11/2017 -# Last Modified: 22/04/2018 -# License: MIT - -from __future__ import print_function -import os -from os import path -import sys -import platform -import re -# import shutil -from subprocess import CalledProcessError, Popen, PIPE, STDOUT - - -def default_callback(value): - ''' - A simple default callback that outputs using the print function. When - tools are called without providing a custom callback, this function - will be used to print to standard output. - ''' - print(value) - - -def to_camelcase(name): - ''' - Convert snake_case name to CamelCase name - ''' - return ''.join(x.title() for x in name.split('_')) - - -def to_snakecase(name): - ''' - Convert CamelCase name to snake_case name - ''' - s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) - return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() - - -class WhiteboxTools(object): - ''' - An object for interfacing with the WhiteboxTools executable. - ''' - - def __init__(self): - if platform.system() == 'Windows': - self.ext = '.exe' - else: - self.ext = '' - self.exe_name = "whitebox_tools{}".format(self.ext) - # self.exe_path = os.path.dirname(shutil.which( - # self.exe_name) or path.dirname(path.abspath(__file__))) - # self.exe_path = os.path.dirname(os.path.join(os.path.realpath(__file__))) - self.exe_path = path.dirname(path.abspath(__file__)) - self.work_dir = "" - self.verbose = True - self.cancel_op = False - self.default_callback = default_callback - - def set_whitebox_dir(self, path_str): - ''' - Sets the directory to the WhiteboxTools executable file. - ''' - self.exe_path = path_str - - def set_working_dir(self, path_str): - ''' - Sets the working directory, i.e. the directory in which - the data files are located. By setting the working - directory, tool input parameters that are files need only - specify the file name rather than the complete file path. - ''' - self.work_dir = path.normpath(path_str) - - def set_verbose_mode(self, val=True): - ''' - Sets verbose mode. If verbose mode is False, tools will not - print output messages. Tools will frequently provide substantial - feedback while they are operating, e.g. updating progress for - various sub-routines. When the user has scripted a workflow - that ties many tools in sequence, this level of tool output - can be problematic. By setting verbose mode to False, these - messages are suppressed and tools run as background processes. - ''' - self.verbose = val - - def run_tool(self, tool_name, args, callback=None): - ''' - Runs a tool and specifies tool arguments. - Returns 0 if completes without error. - Returns 1 if error encountered (details are sent to callback). - Returns 2 if process is cancelled by user. - ''' - try: - if callback is None: - callback = self.default_callback - - os.chdir(self.exe_path) - args2 = [] - args2.append("." + path.sep + self.exe_name) - args2.append("--run=\"{}\"".format(to_camelcase(tool_name))) - - if self.work_dir.strip() != "": - args2.append("--wd=\"{}\"".format(self.work_dir)) - - for arg in args: - args2.append(arg) - - # args_str = args_str[:-1] - # a.append("--args=\"{}\"".format(args_str)) - - if self.verbose: - args2.append("-v") - - if self.verbose: - cl = "" - for v in args2: - cl += v + " " - callback(cl.strip() + "\n") - - proc = Popen(args2, shell=False, stdout=PIPE, - stderr=STDOUT, bufsize=1, universal_newlines=True) - - while True: - line = proc.stdout.readline() - sys.stdout.flush() - if line != '': - if not self.cancel_op: - callback(line.strip()) - else: - self.cancel_op = False - proc.terminate() - return 2 - - else: - break - - return 0 - except (OSError, ValueError, CalledProcessError) as err: - callback(str(err)) - return 1 - - def help(self): - ''' - Retrieves the help description for WhiteboxTools. - ''' - try: - os.chdir(self.exe_path) - args = [] - args.append("." + os.path.sep + self.exe_name) - args.append("-h") - - proc = Popen(args, shell=False, stdout=PIPE, - stderr=STDOUT, bufsize=1, universal_newlines=True) - ret = "" - while True: - line = proc.stdout.readline() - if line != '': - ret += line - else: - break - - return ret - except (OSError, ValueError, CalledProcessError) as err: - return err - - def license(self): - ''' - Retrieves the license information for WhiteboxTools. - ''' - try: - os.chdir(self.exe_path) - args = [] - args.append("." + os.path.sep + self.exe_name) - args.append("--license") - - proc = Popen(args, shell=False, stdout=PIPE, - stderr=STDOUT, bufsize=1, universal_newlines=True) - ret = "" - while True: - line = proc.stdout.readline() - if line != '': - ret += line - else: - break - - return ret - except (OSError, ValueError, CalledProcessError) as err: - return err - - def version(self): - ''' - Retrieves the version information for WhiteboxTools. - ''' - try: - os.chdir(self.exe_path) - args = [] - args.append("." + os.path.sep + self.exe_name) - args.append("--version") - - proc = Popen(args, shell=False, stdout=PIPE, - stderr=STDOUT, bufsize=1, universal_newlines=True) - ret = "" - while True: - line = proc.stdout.readline() - if line != '': - ret += line - else: - break - - return ret - except (OSError, ValueError, CalledProcessError) as err: - return err - - def tool_help(self, tool_name=''): - ''' - Retrieves the help description for a specific tool. - ''' - try: - os.chdir(self.exe_path) - args = [] - args.append("." + os.path.sep + self.exe_name) - args.append("--toolhelp={}".format(to_camelcase(tool_name))) - - proc = Popen(args, shell=False, stdout=PIPE, - stderr=STDOUT, bufsize=1, universal_newlines=True) - ret = "" - while True: - line = proc.stdout.readline() - if line != '': - ret += line - else: - break - - return ret - except (OSError, ValueError, CalledProcessError) as err: - return err - - def tool_parameters(self, tool_name): - ''' - Retrieves the tool parameter descriptions for a specific tool. - ''' - try: - os.chdir(self.exe_path) - args = [] - args.append("." + os.path.sep + self.exe_name) - args.append("--toolparameters={}".format(to_camelcase(tool_name))) - - proc = Popen(args, shell=False, stdout=PIPE, - stderr=STDOUT, bufsize=1, universal_newlines=True) - ret = "" - while True: - line = proc.stdout.readline() - if line != '': - ret += line - else: - break - - return ret - except (OSError, ValueError, CalledProcessError) as err: - return err - - def toolbox(self, tool_name=''): - ''' - Retrieve the toolbox for a specific tool. - ''' - try: - os.chdir(self.exe_path) - args = [] - args.append("." + os.path.sep + self.exe_name) - args.append("--toolbox={}".format(to_camelcase(tool_name))) - - proc = Popen(args, shell=False, stdout=PIPE, - stderr=STDOUT, bufsize=1, universal_newlines=True) - ret = "" - while True: - line = proc.stdout.readline() - if line != '': - ret += line - else: - break - - return ret - except (OSError, ValueError, CalledProcessError) as err: - return err - - def view_code(self, tool_name): - ''' - Opens a web browser to view the source code for a specific tool - on the projects source code repository. - ''' - try: - os.chdir(self.exe_path) - args = [] - args.append("." + os.path.sep + self.exe_name) - args.append("--viewcode={}".format(to_camelcase(tool_name))) - - proc = Popen(args, shell=False, stdout=PIPE, - stderr=STDOUT, bufsize=1, universal_newlines=True) - ret = "" - while True: - line = proc.stdout.readline() - if line != '': - ret += line - else: - break - - return ret - except (OSError, ValueError, CalledProcessError) as err: - return err - - def list_tools(self, keywords=[]): - ''' - Lists all available tools in WhiteboxTools. - ''' - try: - os.chdir(self.exe_path) - args = [] - args.append("." + os.path.sep + self.exe_name) - args.append("--listtools") - if len(keywords) > 0: - for kw in keywords: - args.append(kw) - - proc = Popen(args, shell=False, stdout=PIPE, - stderr=STDOUT, bufsize=1, universal_newlines=True) - ret = {} - line = proc.stdout.readline() # skip number of available tools header - while True: - line = proc.stdout.readline() - if line != '': - if line.strip() != '': - name, descr = line.split(':') - ret[to_snakecase(name.strip())] = descr.strip() - else: - break - - return ret - except (OSError, ValueError, CalledProcessError) as err: - return err - - ######################################################################## - # The following methods are convenience methods for each available tool. - # This needs updating whenever new tools are added to the WhiteboxTools - # library. They can be generated automatically using the - # whitebox_plugin_generator.py script. It would also be possible to - # discover plugins at runtime and monkey-patch their methods using - # MethodType. However, this would not be as useful since it would - # restrict the ability for text editors and IDEs to use autocomplete. - ######################################################################## - - - - - - - - - - - - - - - - - ############## - # Data Tools # - ############## - - def add_point_coordinates_to_table(self, i, callback=None): - """Modifies the attribute table of a point vector by adding fields containing each point's X and Y coordinates. - - Keyword arguments: - - i -- Input vector Points file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('add_point_coordinates_to_table', args, callback) # returns 1 if error - - def convert_nodata_to_zero(self, i, output, callback=None): - """Converts nodata values in a raster to zero. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('convert_nodata_to_zero', args, callback) # returns 1 if error - - def convert_raster_format(self, i, output, callback=None): - """Converts raster data from one format to another. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('convert_raster_format', args, callback) # returns 1 if error - - def export_table_to_csv(self, i, output, headers=True, callback=None): - """Exports an attribute table to a CSV text file. - - Keyword arguments: - - i -- Input vector file. - output -- Output raster file. - headers -- Export field names as file header?. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - if headers: args.append("--headers") - return self.run_tool('export_table_to_csv', args, callback) # returns 1 if error - - def join_tables(self, input1, pkey, input2, fkey, import_field, callback=None): - """Merge a vector's attribute table with another table based on a common field. - - Keyword arguments: - - input1 -- Input primary vector file (i.e. the table to be modified). - pkey -- Primary key field. - input2 -- Input foreign vector file (i.e. source of data to be imported). - fkey -- Foreign key field. - import_field -- Imported field (all fields will be imported if not specified). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--pkey='{}'".format(pkey)) - args.append("--input2='{}'".format(input2)) - args.append("--fkey='{}'".format(fkey)) - args.append("--import_field='{}'".format(import_field)) - return self.run_tool('join_tables', args, callback) # returns 1 if error - - def lines_to_polygons(self, i, output, callback=None): - """Converts vector polylines to polygons. - - Keyword arguments: - - i -- Input vector line file. - output -- Output vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('lines_to_polygons', args, callback) # returns 1 if error - - def merge_table_with_csv(self, i, pkey, csv, fkey, import_field=None, callback=None): - """Merge a vector's attribute table with a table contained within a CSV text file. - - Keyword arguments: - - i -- Input primary vector file (i.e. the table to be modified). - pkey -- Primary key field. - csv -- Input CSV file (i.e. source of data to be imported). - fkey -- Foreign key field. - import_field -- Imported field (all fields will be imported if not specified). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--pkey='{}'".format(pkey)) - args.append("--csv='{}'".format(csv)) - args.append("--fkey='{}'".format(fkey)) - if import_field is not None: args.append("--import_field='{}'".format(import_field)) - return self.run_tool('merge_table_with_csv', args, callback) # returns 1 if error - - def merge_vectors(self, inputs, output, callback=None): - """Combines two or more input vectors of the same ShapeType creating a single, new output vector. - - Keyword arguments: - - inputs -- Input vector files. - output -- Output vector file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - return self.run_tool('merge_vectors', args, callback) # returns 1 if error - - def multi_part_to_single_part(self, i, output, exclude_holes=True, callback=None): - """Converts a vector file containing multi-part features into a vector containing only single-part features. - - Keyword arguments: - - i -- Input vector line or polygon file. - output -- Output vector line or polygon file. - exclude_holes -- Exclude hole parts from the feature splitting? (holes will continue to belong to their features in output.). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - if exclude_holes: args.append("--exclude_holes") - return self.run_tool('multi_part_to_single_part', args, callback) # returns 1 if error - - def new_raster_from_base(self, base, output, value="nodata", data_type="float", callback=None): - """Creates a new raster using a base image. - - Keyword arguments: - - base -- Input base raster file. - output -- Output raster file. - value -- Constant value to fill raster with; either 'nodata' or numeric value. - data_type -- Output raster data type; options include 'double' (64-bit), 'float' (32-bit), and 'integer' (signed 16-bit) (default is 'float'). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--base='{}'".format(base)) - args.append("--output='{}'".format(output)) - args.append("--value={}".format(value)) - args.append("--data_type={}".format(data_type)) - return self.run_tool('new_raster_from_base', args, callback) # returns 1 if error - - def polygons_to_lines(self, i, output, callback=None): - """Converts vector polygons to polylines. - - Keyword arguments: - - i -- Input vector polygon file. - output -- Output vector lines file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('polygons_to_lines', args, callback) # returns 1 if error - - def print_geo_tiff_tags(self, i, callback=None): - """Prints the tags within a GeoTIFF. - - Keyword arguments: - - i -- Input GeoTIFF file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('print_geo_tiff_tags', args, callback) # returns 1 if error - - def raster_to_vector_lines(self, i, output, callback=None): - """Converts a raster lines features into a vector of the POLYLINE shapetype. - - Keyword arguments: - - i -- Input raster lines file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('raster_to_vector_lines', args, callback) # returns 1 if error - - def raster_to_vector_points(self, i, output, callback=None): - """Converts a raster dataset to a vector of the POINT shapetype. - - Keyword arguments: - - i -- Input raster file. - output -- Output vector points file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('raster_to_vector_points', args, callback) # returns 1 if error - - def reinitialize_attribute_table(self, i, callback=None): - """Reinitializes a vector's attribute table deleting all fields but the feature ID (FID). - - Keyword arguments: - - i -- Input vector file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('reinitialize_attribute_table', args, callback) # returns 1 if error - - def remove_polygon_holes(self, i, output, callback=None): - """Removes holes within the features of a vector polygon file. - - Keyword arguments: - - i -- Input vector polygon file. - output -- Output vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('remove_polygon_holes', args, callback) # returns 1 if error - - def set_nodata_value(self, i, output, back_value=0.0, callback=None): - """Assign a specified value in an input image to the NoData value. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - back_value -- Background value to set to nodata. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--back_value={}".format(back_value)) - return self.run_tool('set_nodata_value', args, callback) # returns 1 if error - - def single_part_to_multi_part(self, i, output, field=None, callback=None): - """Converts a vector file containing multi-part features into a vector containing only single-part features. - - Keyword arguments: - - i -- Input vector line or polygon file. - field -- Grouping ID field name in attribute table. - output -- Output vector line or polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - if field is not None: args.append("--field='{}'".format(field)) - args.append("--output='{}'".format(output)) - return self.run_tool('single_part_to_multi_part', args, callback) # returns 1 if error - - def vector_lines_to_raster(self, i, output, field="FID", nodata=True, cell_size=None, base=None, callback=None): - """Converts a vector containing polylines into a raster. - - Keyword arguments: - - i -- Input vector lines file. - field -- Input field name in attribute table. - output -- Output raster file. - nodata -- Background value to set to NoData. Without this flag, it will be set to 0.0. - cell_size -- Optionally specified cell size of output raster. Not used when base raster is specified. - base -- Optionally specified input base raster file. Not used when a cell size is specified. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--field={}".format(field)) - args.append("--output='{}'".format(output)) - if nodata: args.append("--nodata") - if cell_size is not None: args.append("--cell_size='{}'".format(cell_size)) - if base is not None: args.append("--base='{}'".format(base)) - return self.run_tool('vector_lines_to_raster', args, callback) # returns 1 if error - - def vector_points_to_raster(self, i, output, field="FID", assign="last", nodata=True, cell_size=None, base=None, callback=None): - """Converts a vector containing points into a raster. - - Keyword arguments: - - i -- Input vector Points file. - field -- Input field name in attribute table. - output -- Output raster file. - assign -- Assignment operation, where multiple points are in the same grid cell; options include 'first', 'last' (default), 'min', 'max', 'sum'. - nodata -- Background value to set to NoData. Without this flag, it will be set to 0.0. - cell_size -- Optionally specified cell size of output raster. Not used when base raster is specified. - base -- Optionally specified input base raster file. Not used when a cell size is specified. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--field={}".format(field)) - args.append("--output='{}'".format(output)) - args.append("--assign={}".format(assign)) - if nodata: args.append("--nodata") - if cell_size is not None: args.append("--cell_size='{}'".format(cell_size)) - if base is not None: args.append("--base='{}'".format(base)) - return self.run_tool('vector_points_to_raster', args, callback) # returns 1 if error - - def vector_polygons_to_raster(self, i, output, field="FID", nodata=True, cell_size=None, base=None, callback=None): - """Converts a vector containing polygons into a raster. - - Keyword arguments: - - i -- Input vector polygons file. - field -- Input field name in attribute table. - output -- Output raster file. - nodata -- Background value to set to NoData. Without this flag, it will be set to 0.0. - cell_size -- Optionally specified cell size of output raster. Not used when base raster is specified. - base -- Optionally specified input base raster file. Not used when a cell size is specified. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--field={}".format(field)) - args.append("--output='{}'".format(output)) - if nodata: args.append("--nodata") - if cell_size is not None: args.append("--cell_size='{}'".format(cell_size)) - if base is not None: args.append("--base='{}'".format(base)) - return self.run_tool('vector_polygons_to_raster', args, callback) # returns 1 if error - - ################ - # GIS Analysis # - ################ - - def aggregate_raster(self, i, output, agg_factor=2, type="mean", callback=None): - """Aggregates a raster to a lower resolution. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - agg_factor -- Aggregation factor, in pixels. - type -- Statistic used to fill output pixels. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--agg_factor={}".format(agg_factor)) - args.append("--type={}".format(type)) - return self.run_tool('aggregate_raster', args, callback) # returns 1 if error - - def block_maximum_gridding(self, i, field, output, use_z=False, cell_size=None, base=None, callback=None): - """Creates a raster grid based on a set of vector points and assigns grid values using a block maximum scheme. - - Keyword arguments: - - i -- Input vector Points file. - field -- Input field name in attribute table. - use_z -- Use z-coordinate instead of field?. - output -- Output raster file. - cell_size -- Optionally specified cell size of output raster. Not used when base raster is specified. - base -- Optionally specified input base raster file. Not used when a cell size is specified. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--field='{}'".format(field)) - if use_z: args.append("--use_z") - args.append("--output='{}'".format(output)) - if cell_size is not None: args.append("--cell_size='{}'".format(cell_size)) - if base is not None: args.append("--base='{}'".format(base)) - return self.run_tool('block_maximum_gridding', args, callback) # returns 1 if error - - def block_minimum_gridding(self, i, field, output, use_z=False, cell_size=None, base=None, callback=None): - """Creates a raster grid based on a set of vector points and assigns grid values using a block minimum scheme. - - Keyword arguments: - - i -- Input vector Points file. - field -- Input field name in attribute table. - use_z -- Use z-coordinate instead of field?. - output -- Output raster file. - cell_size -- Optionally specified cell size of output raster. Not used when base raster is specified. - base -- Optionally specified input base raster file. Not used when a cell size is specified. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--field='{}'".format(field)) - if use_z: args.append("--use_z") - args.append("--output='{}'".format(output)) - if cell_size is not None: args.append("--cell_size='{}'".format(cell_size)) - if base is not None: args.append("--base='{}'".format(base)) - return self.run_tool('block_minimum_gridding', args, callback) # returns 1 if error - - def centroid(self, i, output, text_output=False, callback=None): - """Calculates the centroid, or average location, of raster polygon objects. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - text_output -- Optional text output. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - if text_output: args.append("--text_output") - return self.run_tool('centroid', args, callback) # returns 1 if error - - def centroid_vector(self, i, output, callback=None): - """Identifes the centroid point of a vector polyline or polygon feature or a group of vector points. - - Keyword arguments: - - i -- Input vector file. - output -- Output vector file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('centroid_vector', args, callback) # returns 1 if error - - def clump(self, i, output, diag=True, zero_back=False, callback=None): - """Groups cells that form discrete areas, assigning them unique identifiers. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - diag -- Flag indicating whether diagonal connections should be considered. - zero_back -- Flag indicating whether zero values should be treated as a background. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - if diag: args.append("--diag") - if zero_back: args.append("--zero_back") - return self.run_tool('clump', args, callback) # returns 1 if error - - def construct_vector_tin(self, i, output, field=None, use_z=False, callback=None): - """Creates a vector triangular irregular network (TIN) for a set of vector points. - - Keyword arguments: - - i -- Input vector points file. - field -- Input field name in attribute table. - use_z -- Use the 'z' dimension of the Shapefile's geometry instead of an attribute field?. - output -- Output vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - if field is not None: args.append("--field='{}'".format(field)) - if use_z: args.append("--use_z") - args.append("--output='{}'".format(output)) - return self.run_tool('construct_vector_tin', args, callback) # returns 1 if error - - def create_hexagonal_vector_grid(self, i, output, width, orientation="horizontal", callback=None): - """Creates a hexagonal vector grid. - - Keyword arguments: - - i -- Input base file. - output -- Output vector polygon file. - width -- The grid cell width. - orientation -- Grid Orientation, 'horizontal' or 'vertical'. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--width='{}'".format(width)) - args.append("--orientation={}".format(orientation)) - return self.run_tool('create_hexagonal_vector_grid', args, callback) # returns 1 if error - - def create_plane(self, base, output, gradient=15.0, aspect=90.0, constant=0.0, callback=None): - """Creates a raster image based on the equation for a simple plane. - - Keyword arguments: - - base -- Input base raster file. - output -- Output raster file. - gradient -- Slope gradient in degrees (-85.0 to 85.0). - aspect -- Aspect (direction) in degrees clockwise from north (0.0-360.0). - constant -- Constant value. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--base='{}'".format(base)) - args.append("--output='{}'".format(output)) - args.append("--gradient={}".format(gradient)) - args.append("--aspect={}".format(aspect)) - args.append("--constant={}".format(constant)) - return self.run_tool('create_plane', args, callback) # returns 1 if error - - def create_rectangular_vector_grid(self, i, output, width, height, xorig=0, yorig=0, callback=None): - """Creates a rectangular vector grid. - - Keyword arguments: - - i -- Input base file. - output -- Output vector polygon file. - width -- The grid cell width. - height -- The grid cell height. - xorig -- The grid origin x-coordinate. - yorig -- The grid origin y-coordinate. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--width='{}'".format(width)) - args.append("--height='{}'".format(height)) - args.append("--xorig={}".format(xorig)) - args.append("--yorig={}".format(yorig)) - return self.run_tool('create_rectangular_vector_grid', args, callback) # returns 1 if error - - def dissolve(self, i, output, field=None, snap=0.0, callback=None): - """Removes the interior, or shared, boundaries within a vector polygon coverage. - - Keyword arguments: - - i -- Input vector file. - field -- Dissolve field attribute (optional). - output -- Output vector file. - snap -- Snap tolerance. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - if field is not None: args.append("--field='{}'".format(field)) - args.append("--output='{}'".format(output)) - args.append("--snap={}".format(snap)) - return self.run_tool('dissolve', args, callback) # returns 1 if error - - def eliminate_coincident_points(self, i, output, tolerance, callback=None): - """Removes any coincident, or nearly coincident, points from a vector points file. - - Keyword arguments: - - i -- Input vector file. - output -- Output vector polygon file. - tolerance -- The distance tolerance for points. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--tolerance='{}'".format(tolerance)) - return self.run_tool('eliminate_coincident_points', args, callback) # returns 1 if error - - def extend_vector_lines(self, i, output, dist, extend="both ends", callback=None): - """Extends vector lines by a specified distance. - - Keyword arguments: - - i -- Input vector polyline file. - output -- Output vector polyline file. - dist -- The distance to extend. - extend -- Extend direction, 'both ends' (default), 'line start', 'line end'. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--dist='{}'".format(dist)) - args.append("--extend={}".format(extend)) - return self.run_tool('extend_vector_lines', args, callback) # returns 1 if error - - def extract_nodes(self, i, output, callback=None): - """Converts vector lines or polygons into vertex points. - - Keyword arguments: - - i -- Input vector lines or polygon file. - output -- Output vector points file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('extract_nodes', args, callback) # returns 1 if error - - def extract_raster_values_at_points(self, inputs, points, out_text=False, callback=None): - """Extracts the values of raster(s) at vector point locations. - - Keyword arguments: - - inputs -- Input raster files. - points -- Input vector points file. - out_text -- Output point values as text? Otherwise, the only output is to to the points file's attribute table. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--points='{}'".format(points)) - if out_text: args.append("--out_text") - return self.run_tool('extract_raster_values_at_points', args, callback) # returns 1 if error - - def find_lowest_or_highest_points(self, i, output, out_type="lowest", callback=None): - """Locates the lowest and/or highest valued cells in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output vector points file. - out_type -- Output type; one of 'area' (default) and 'volume'. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--out_type={}".format(out_type)) - return self.run_tool('find_lowest_or_highest_points', args, callback) # returns 1 if error - - def idw_interpolation(self, i, field, output, use_z=False, weight=2.0, radius=None, min_points=None, cell_size=None, base=None, callback=None): - """Interpolates vector points into a raster surface using an inverse-distance weighted scheme. - - Keyword arguments: - - i -- Input vector Points file. - field -- Input field name in attribute table. - use_z -- Use z-coordinate instead of field?. - output -- Output raster file. - weight -- IDW weight value. - radius -- Search Radius. - min_points -- Minimum number of points. - cell_size -- Optionally specified cell size of output raster. Not used when base raster is specified. - base -- Optionally specified input base raster file. Not used when a cell size is specified. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--field='{}'".format(field)) - if use_z: args.append("--use_z") - args.append("--output='{}'".format(output)) - args.append("--weight={}".format(weight)) - if radius is not None: args.append("--radius='{}'".format(radius)) - if min_points is not None: args.append("--min_points='{}'".format(min_points)) - if cell_size is not None: args.append("--cell_size='{}'".format(cell_size)) - if base is not None: args.append("--base='{}'".format(base)) - return self.run_tool('idw_interpolation', args, callback) # returns 1 if error - - def layer_footprint(self, i, output, callback=None): - """Creates a vector polygon footprint of the area covered by a raster grid or vector layer. - - Keyword arguments: - - i -- Input raster or vector file. - output -- Output vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('layer_footprint', args, callback) # returns 1 if error - - def medoid(self, i, output, callback=None): - """Calculates the medoid for a series of vector features contained in a shapefile. - - Keyword arguments: - - i -- Input vector file. - output -- Output vector file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('medoid', args, callback) # returns 1 if error - - def minimum_bounding_box(self, i, output, criterion="area", features=True, callback=None): - """Creates a vector minimum bounding rectangle around vector features. - - Keyword arguments: - - i -- Input vector file. - output -- Output vector polygon file. - criterion -- Minimization criterion; options include 'area' (default), 'length', 'width', and 'perimeter'. - features -- Find the minimum bounding rectangles around each individual vector feature. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--criterion={}".format(criterion)) - if features: args.append("--features") - return self.run_tool('minimum_bounding_box', args, callback) # returns 1 if error - - def minimum_bounding_circle(self, i, output, features=True, callback=None): - """Delineates the minimum bounding circle (i.e. smallest enclosing circle) for a group of vectors. - - Keyword arguments: - - i -- Input vector file. - output -- Output vector polygon file. - features -- Find the minimum bounding circle around each individual vector feature. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - if features: args.append("--features") - return self.run_tool('minimum_bounding_circle', args, callback) # returns 1 if error - - def minimum_bounding_envelope(self, i, output, features=True, callback=None): - """Creates a vector axis-aligned minimum bounding rectangle (envelope) around vector features. - - Keyword arguments: - - i -- Input vector file. - output -- Output vector polygon file. - features -- Find the minimum bounding envelop around each individual vector feature. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - if features: args.append("--features") - return self.run_tool('minimum_bounding_envelope', args, callback) # returns 1 if error - - def minimum_convex_hull(self, i, output, features=True, callback=None): - """Creates a vector convex polygon around vector features. - - Keyword arguments: - - i -- Input vector file. - output -- Output vector polygon file. - features -- Find the hulls around each vector feature. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - if features: args.append("--features") - return self.run_tool('minimum_convex_hull', args, callback) # returns 1 if error - - def nearest_neighbour_gridding(self, i, field, output, use_z=False, cell_size=None, base=None, max_dist=None, callback=None): - """Creates a raster grid based on a set of vector points and assigns grid values using the nearest neighbour. - - Keyword arguments: - - i -- Input vector Points file. - field -- Input field name in attribute table. - use_z -- Use z-coordinate instead of field?. - output -- Output raster file. - cell_size -- Optionally specified cell size of output raster. Not used when base raster is specified. - base -- Optionally specified input base raster file. Not used when a cell size is specified. - max_dist -- Maximum search distance (optional). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--field='{}'".format(field)) - if use_z: args.append("--use_z") - args.append("--output='{}'".format(output)) - if cell_size is not None: args.append("--cell_size='{}'".format(cell_size)) - if base is not None: args.append("--base='{}'".format(base)) - if max_dist is not None: args.append("--max_dist='{}'".format(max_dist)) - return self.run_tool('nearest_neighbour_gridding', args, callback) # returns 1 if error - - def polygon_area(self, i, callback=None): - """Calculates the area of vector polygons. - - Keyword arguments: - - i -- Input vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('polygon_area', args, callback) # returns 1 if error - - def polygon_long_axis(self, i, output, callback=None): - """This tool can be used to map the long axis of polygon features. - - Keyword arguments: - - i -- Input vector polygons file. - output -- Output vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('polygon_long_axis', args, callback) # returns 1 if error - - def polygon_perimeter(self, i, callback=None): - """Calculates the perimeter of vector polygons. - - Keyword arguments: - - i -- Input vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('polygon_perimeter', args, callback) # returns 1 if error - - def polygon_short_axis(self, i, output, callback=None): - """This tool can be used to map the short axis of polygon features. - - Keyword arguments: - - i -- Input vector polygons file. - output -- Output vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('polygon_short_axis', args, callback) # returns 1 if error - - def raster_area(self, i, output=None, out_text=False, units="grid cells", zero_back=False, callback=None): - """Calculates the area of polygons or classes within a raster image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - out_text -- Would you like to output polygon areas to text?. - units -- Area units; options include 'grid cells' and 'map units'. - zero_back -- Flag indicating whether zero values should be treated as a background. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - if output is not None: args.append("--output='{}'".format(output)) - if out_text: args.append("--out_text") - args.append("--units={}".format(units)) - if zero_back: args.append("--zero_back") - return self.run_tool('raster_area', args, callback) # returns 1 if error - - def raster_cell_assignment(self, i, output, assign="column", callback=None): - """Assign row or column number to cells. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - assign -- Which variable would you like to assign to grid cells? Options include 'column', 'row', 'x', and 'y'. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--assign={}".format(assign)) - return self.run_tool('raster_cell_assignment', args, callback) # returns 1 if error - - def reclass(self, i, output, reclass_vals, assign_mode=False, callback=None): - """Reclassifies the values in a raster image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - reclass_vals -- Reclassification triplet values (new value; from value; to less than), e.g. '0.0;0.0;1.0;1.0;1.0;2.0'. - assign_mode -- Optional Boolean flag indicating whether to operate in assign mode, reclass_vals values are interpreted as new value; old value pairs. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--reclass_vals='{}'".format(reclass_vals)) - if assign_mode: args.append("--assign_mode") - return self.run_tool('reclass', args, callback) # returns 1 if error - - def reclass_equal_interval(self, i, output, interval=10.0, start_val=None, end_val=None, callback=None): - """Reclassifies the values in a raster image based on equal-ranges. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - interval -- Class interval size. - start_val -- Optional starting value (default is input minimum value). - end_val -- Optional ending value (default is input maximum value). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--interval={}".format(interval)) - if start_val is not None: args.append("--start_val='{}'".format(start_val)) - if end_val is not None: args.append("--end_val='{}'".format(end_val)) - return self.run_tool('reclass_equal_interval', args, callback) # returns 1 if error - - def reclass_from_file(self, i, reclass_file, output, callback=None): - """Reclassifies the values in a raster image using reclass ranges in a text file. - - Keyword arguments: - - i -- Input raster file. - reclass_file -- Input text file containing reclass ranges. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--reclass_file='{}'".format(reclass_file)) - args.append("--output='{}'".format(output)) - return self.run_tool('reclass_from_file', args, callback) # returns 1 if error - - def smooth_vectors(self, i, output, filter=3, callback=None): - """Smooths a vector coverage of either a POLYLINE or POLYGON base ShapeType. - - Keyword arguments: - - i -- Input vector POLYLINE or POLYGON file. - output -- Output vector file. - filter -- The filter size, any odd integer greater than or equal to 3; default is 3. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filter={}".format(filter)) - return self.run_tool('smooth_vectors', args, callback) # returns 1 if error - - def tin_gridding(self, i, output, resolution, field=None, use_z=False, callback=None): - """Creates a raster grid based on a triangular irregular network (TIN) fitted to vector points. - - Keyword arguments: - - i -- Input vector points file. - field -- Input field name in attribute table. - use_z -- Use the 'z' dimension of the Shapefile's geometry instead of an attribute field?. - output -- Output raster file. - resolution -- Output raster's grid resolution. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - if field is not None: args.append("--field='{}'".format(field)) - if use_z: args.append("--use_z") - args.append("--output='{}'".format(output)) - args.append("--resolution='{}'".format(resolution)) - return self.run_tool('tin_gridding', args, callback) # returns 1 if error - - def vector_hex_binning(self, i, output, width, orientation="horizontal", callback=None): - """Hex-bins a set of vector points. - - Keyword arguments: - - i -- Input base file. - output -- Output vector polygon file. - width -- The grid cell width. - orientation -- Grid Orientation, 'horizontal' or 'vertical'. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--width='{}'".format(width)) - args.append("--orientation={}".format(orientation)) - return self.run_tool('vector_hex_binning', args, callback) # returns 1 if error - - def voronoi_diagram(self, i, output, callback=None): - """Creates a vector Voronoi diagram for a set of vector points. - - Keyword arguments: - - i -- Input vector points file. - output -- Output vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('voronoi_diagram', args, callback) # returns 1 if error - - ############################### - # GIS Analysis/Distance Tools # - ############################### - - def buffer_raster(self, i, output, size, gridcells=False, callback=None): - """Maps a distance-based buffer around each non-background (non-zero/non-nodata) grid cell in an input image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - size -- Buffer size. - gridcells -- Optional flag to indicate that the 'size' threshold should be measured in grid cells instead of the default map units. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--size='{}'".format(size)) - if gridcells: args.append("--gridcells") - return self.run_tool('buffer_raster', args, callback) # returns 1 if error - - def cost_allocation(self, source, backlink, output, callback=None): - """Identifies the source cell to which each grid cell is connected by a least-cost pathway in a cost-distance analysis. - - Keyword arguments: - - source -- Input source raster file. - backlink -- Input backlink raster file generated by the cost-distance tool. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--source='{}'".format(source)) - args.append("--backlink='{}'".format(backlink)) - args.append("--output='{}'".format(output)) - return self.run_tool('cost_allocation', args, callback) # returns 1 if error - - def cost_distance(self, source, cost, out_accum, out_backlink, callback=None): - """Performs cost-distance accumulation on a cost surface and a group of source cells. - - Keyword arguments: - - source -- Input source raster file. - cost -- Input cost (friction) raster file. - out_accum -- Output cost accumulation raster file. - out_backlink -- Output backlink raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--source='{}'".format(source)) - args.append("--cost='{}'".format(cost)) - args.append("--out_accum='{}'".format(out_accum)) - args.append("--out_backlink='{}'".format(out_backlink)) - return self.run_tool('cost_distance', args, callback) # returns 1 if error - - def cost_pathway(self, destination, backlink, output, zero_background=False, callback=None): - """Performs cost-distance pathway analysis using a series of destination grid cells. - - Keyword arguments: - - destination -- Input destination raster file. - backlink -- Input backlink raster file generated by the cost-distance tool. - output -- Output cost pathway raster file. - zero_background -- Flag indicating whether zero values should be treated as a background. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--destination='{}'".format(destination)) - args.append("--backlink='{}'".format(backlink)) - args.append("--output='{}'".format(output)) - if zero_background: args.append("--zero_background") - return self.run_tool('cost_pathway', args, callback) # returns 1 if error - - def euclidean_allocation(self, i, output, callback=None): - """Assigns grid cells in the output raster the value of the nearest target cell in the input image, measured by the Shih and Wu (2004) Euclidean distance transform. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('euclidean_allocation', args, callback) # returns 1 if error - - def euclidean_distance(self, i, output, callback=None): - """Calculates the Shih and Wu (2004) Euclidean distance transform. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('euclidean_distance', args, callback) # returns 1 if error - - ############################## - # GIS Analysis/Overlay Tools # - ############################## - - def average_overlay(self, inputs, output, callback=None): - """Calculates the average for each grid cell from a group of raster images. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - return self.run_tool('average_overlay', args, callback) # returns 1 if error - - def clip(self, i, clip, output, callback=None): - """Extract all the features, or parts of features, that overlap with the features of the clip vector. - - Keyword arguments: - - i -- Input vector file. - clip -- Input clip polygon vector file. - output -- Output vector file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--clip='{}'".format(clip)) - args.append("--output='{}'".format(output)) - return self.run_tool('clip', args, callback) # returns 1 if error - - def clip_raster_to_polygon(self, i, polygons, output, maintain_dimensions=False, callback=None): - """Clips a raster to a vector polygon. - - Keyword arguments: - - i -- Input raster file. - polygons -- Input vector polygons file. - output -- Output raster file. - maintain_dimensions -- Maintain input raster dimensions?. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--polygons='{}'".format(polygons)) - args.append("--output='{}'".format(output)) - if maintain_dimensions: args.append("--maintain_dimensions") - return self.run_tool('clip_raster_to_polygon', args, callback) # returns 1 if error - - def count_if(self, inputs, output, value, callback=None): - """Counts the number of occurrences of a specified value in a cell-stack of rasters. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output raster file. - value -- Search value (e.g. countif value = 5.0). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - args.append("--value='{}'".format(value)) - return self.run_tool('count_if', args, callback) # returns 1 if error - - def difference(self, i, overlay, output, callback=None): - """Outputs the features that occur in one of the two vector inputs but not both, i.e. no overlapping features. - - Keyword arguments: - - i -- Input vector file. - overlay -- Input overlay vector file. - output -- Output vector file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--overlay='{}'".format(overlay)) - args.append("--output='{}'".format(output)) - return self.run_tool('difference', args, callback) # returns 1 if error - - def erase(self, i, erase, output, callback=None): - """Removes all the features, or parts of features, that overlap with the features of the erase vector polygon. - - Keyword arguments: - - i -- Input vector file. - erase -- Input erase polygon vector file. - output -- Output vector file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--erase='{}'".format(erase)) - args.append("--output='{}'".format(output)) - return self.run_tool('erase', args, callback) # returns 1 if error - - def erase_polygon_from_raster(self, i, polygons, output, callback=None): - """Erases (cuts out) a vector polygon from a raster. - - Keyword arguments: - - i -- Input raster file. - polygons -- Input vector polygons file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--polygons='{}'".format(polygons)) - args.append("--output='{}'".format(output)) - return self.run_tool('erase_polygon_from_raster', args, callback) # returns 1 if error - - def highest_position(self, inputs, output, callback=None): - """Identifies the stack position of the maximum value within a raster stack on a cell-by-cell basis. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - return self.run_tool('highest_position', args, callback) # returns 1 if error - - def intersect(self, i, overlay, output, snap=0.0, callback=None): - """Identifies the parts of features in common between two input vector layers. - - Keyword arguments: - - i -- Input vector file. - overlay -- Input overlay vector file. - output -- Output vector file. - snap -- Snap tolerance. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--overlay='{}'".format(overlay)) - args.append("--output='{}'".format(output)) - args.append("--snap={}".format(snap)) - return self.run_tool('intersect', args, callback) # returns 1 if error - - def line_intersections(self, input1, input2, output, callback=None): - """Identifies points where the features of two vector line layers intersect. - - Keyword arguments: - - input1 -- Input vector polyline file. - input2 -- Input vector polyline file. - output -- Output vector point file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('line_intersections', args, callback) # returns 1 if error - - def lowest_position(self, inputs, output, callback=None): - """Identifies the stack position of the minimum value within a raster stack on a cell-by-cell basis. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - return self.run_tool('lowest_position', args, callback) # returns 1 if error - - def max_absolute_overlay(self, inputs, output, callback=None): - """Evaluates the maximum absolute value for each grid cell from a stack of input rasters. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - return self.run_tool('max_absolute_overlay', args, callback) # returns 1 if error - - def max_overlay(self, inputs, output, callback=None): - """Evaluates the maximum value for each grid cell from a stack of input rasters. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - return self.run_tool('max_overlay', args, callback) # returns 1 if error - - def min_absolute_overlay(self, inputs, output, callback=None): - """Evaluates the minimum absolute value for each grid cell from a stack of input rasters. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - return self.run_tool('min_absolute_overlay', args, callback) # returns 1 if error - - def min_overlay(self, inputs, output, callback=None): - """Evaluates the minimum value for each grid cell from a stack of input rasters. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - return self.run_tool('min_overlay', args, callback) # returns 1 if error - - def percent_equal_to(self, inputs, comparison, output, callback=None): - """Calculates the percentage of a raster stack that have cell values equal to an input on a cell-by-cell basis. - - Keyword arguments: - - inputs -- Input raster files. - comparison -- Input comparison raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--comparison='{}'".format(comparison)) - args.append("--output='{}'".format(output)) - return self.run_tool('percent_equal_to', args, callback) # returns 1 if error - - def percent_greater_than(self, inputs, comparison, output, callback=None): - """Calculates the percentage of a raster stack that have cell values greather than an input on a cell-by-cell basis. - - Keyword arguments: - - inputs -- Input raster files. - comparison -- Input comparison raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--comparison='{}'".format(comparison)) - args.append("--output='{}'".format(output)) - return self.run_tool('percent_greater_than', args, callback) # returns 1 if error - - def percent_less_than(self, inputs, comparison, output, callback=None): - """Calculates the percentage of a raster stack that have cell values less than an input on a cell-by-cell basis. - - Keyword arguments: - - inputs -- Input raster files. - comparison -- Input comparison raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--comparison='{}'".format(comparison)) - args.append("--output='{}'".format(output)) - return self.run_tool('percent_less_than', args, callback) # returns 1 if error - - def pick_from_list(self, inputs, pos_input, output, callback=None): - """Outputs the value from a raster stack specified by a position raster. - - Keyword arguments: - - inputs -- Input raster files. - pos_input -- Input position raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--pos_input='{}'".format(pos_input)) - args.append("--output='{}'".format(output)) - return self.run_tool('pick_from_list', args, callback) # returns 1 if error - - def polygonize(self, inputs, output, callback=None): - """Creates a polygon layer from two or more intersecting line features contained in one or more input vector line files. - - Keyword arguments: - - inputs -- Input vector polyline file. - output -- Output vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - return self.run_tool('polygonize', args, callback) # returns 1 if error - - def split_with_lines(self, i, split, output, callback=None): - """Splits the lines or polygons in one layer using the lines in another layer. - - Keyword arguments: - - i -- Input vector line or polygon file. - split -- Input vector polyline file. - output -- Output vector file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--split='{}'".format(split)) - args.append("--output='{}'".format(output)) - return self.run_tool('split_with_lines', args, callback) # returns 1 if error - - def sum_overlay(self, inputs, output, callback=None): - """Calculates the sum for each grid cell from a group of raster images. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - return self.run_tool('sum_overlay', args, callback) # returns 1 if error - - def symmetrical_difference(self, i, overlay, output, snap=0.0, callback=None): - """Outputs the features that occur in one of the two vector inputs but not both, i.e. no overlapping features. - - Keyword arguments: - - i -- Input vector file. - overlay -- Input overlay vector file. - output -- Output vector file. - snap -- Snap tolerance. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--overlay='{}'".format(overlay)) - args.append("--output='{}'".format(output)) - args.append("--snap={}".format(snap)) - return self.run_tool('symmetrical_difference', args, callback) # returns 1 if error - - def union(self, i, overlay, output, snap=0.0, callback=None): - """Splits vector layers at their overlaps, creating a layer containing all the portions from both input and overlay layers. - - Keyword arguments: - - i -- Input vector file. - overlay -- Input overlay vector file. - output -- Output vector file. - snap -- Snap tolerance. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--overlay='{}'".format(overlay)) - args.append("--output='{}'".format(output)) - args.append("--snap={}".format(snap)) - return self.run_tool('union', args, callback) # returns 1 if error - - def weighted_overlay(self, factors, weights, output, cost=None, constraints=None, scale_max=1.0, callback=None): - """Performs a weighted sum on multiple input rasters after converting each image to a common scale. The tool performs a multi-criteria evaluation (MCE). - - Keyword arguments: - - factors -- Input factor raster files. - weights -- Weight values, contained in quotes and separated by commas or semicolons. Must have the same number as factors. - cost -- Weight values, contained in quotes and separated by commas or semicolons. Must have the same number as factors. - constraints -- Input constraints raster files. - output -- Output raster file. - scale_max -- Suitability scale maximum value (common values are 1.0, 100.0, and 255.0). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--factors='{}'".format(factors)) - args.append("--weights='{}'".format(weights)) - if cost is not None: args.append("--cost='{}'".format(cost)) - if constraints is not None: args.append("--constraints='{}'".format(constraints)) - args.append("--output='{}'".format(output)) - args.append("--scale_max={}".format(scale_max)) - return self.run_tool('weighted_overlay', args, callback) # returns 1 if error - - def weighted_sum(self, inputs, weights, output, callback=None): - """Performs a weighted-sum overlay on multiple input raster images. - - Keyword arguments: - - inputs -- Input raster files. - weights -- Weight values, contained in quotes and separated by commas or semicolons. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--weights='{}'".format(weights)) - args.append("--output='{}'".format(output)) - return self.run_tool('weighted_sum', args, callback) # returns 1 if error - - ################################## - # GIS Analysis/Patch Shape Tools # - ################################## - - def compactness_ratio(self, i, callback=None): - """Calculates the compactness ratio (A/P), a measure of shape complexity, for vector polygons. - - Keyword arguments: - - i -- Input vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('compactness_ratio', args, callback) # returns 1 if error - - def edge_proportion(self, i, output, output_text=False, callback=None): - """Calculate the proportion of cells in a raster polygon that are edge cells. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - output_text -- flag indicating whether a text report should also be output. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - if output_text: args.append("--output_text") - return self.run_tool('edge_proportion', args, callback) # returns 1 if error - - def elongation_ratio(self, i, callback=None): - """Calculates the elongation ratio for vector polygons. - - Keyword arguments: - - i -- Input vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('elongation_ratio', args, callback) # returns 1 if error - - def find_patch_or_class_edge_cells(self, i, output, callback=None): - """Finds all cells located on the edge of patch or class features. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('find_patch_or_class_edge_cells', args, callback) # returns 1 if error - - def hole_proportion(self, i, callback=None): - """Calculates the proportion of the total area of a polygon's holes relative to the area of the polygon's hull. - - Keyword arguments: - - i -- Input vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('hole_proportion', args, callback) # returns 1 if error - - def linearity_index(self, i, callback=None): - """Calculates the linearity index for vector polygons. - - Keyword arguments: - - i -- Input vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('linearity_index', args, callback) # returns 1 if error - - def narrowness_index(self, i, output, callback=None): - """Calculates the area of polygons or classes within a raster image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('narrowness_index', args, callback) # returns 1 if error - - def patch_orientation(self, i, callback=None): - """Calculates the orientation of vector polygons. - - Keyword arguments: - - i -- Input vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('patch_orientation', args, callback) # returns 1 if error - - def perimeter_area_ratio(self, i, callback=None): - """Calculates the perimeter-area ratio of vector polygons. - - Keyword arguments: - - i -- Input vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('perimeter_area_ratio', args, callback) # returns 1 if error - - def radius_of_gyration(self, i, output, text_output=False, callback=None): - """Calculates the distance of cells from their polygon's centroid. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - text_output -- Optional text output. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - if text_output: args.append("--text_output") - return self.run_tool('radius_of_gyration', args, callback) # returns 1 if error - - def related_circumscribing_circle(self, i, callback=None): - """Calculates the related circumscribing circle of vector polygons. - - Keyword arguments: - - i -- Input vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('related_circumscribing_circle', args, callback) # returns 1 if error - - def shape_complexity_index(self, i, callback=None): - """Calculates overall polygon shape complexity or irregularity. - - Keyword arguments: - - i -- Input vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('shape_complexity_index', args, callback) # returns 1 if error - - def shape_complexity_index_raster(self, i, output, callback=None): - """Calculates the area of polygons or classes within a raster image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('shape_complexity_index_raster', args, callback) # returns 1 if error - - ############################ - # Geomorphometric Analysis # - ############################ - - def aspect(self, dem, output, zfactor=1.0, callback=None): - """Calculates an aspect raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--zfactor={}".format(zfactor)) - return self.run_tool('aspect', args, callback) # returns 1 if error - - def circular_variance_of_aspect(self, dem, output, filter=11, callback=None): - """Calculates the circular variance of aspect at a scale for a DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster roughness scale file. - filter -- Size of the filter kernel. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--filter={}".format(filter)) - return self.run_tool('circular_variance_of_aspect', args, callback) # returns 1 if error - - def dev_from_mean_elev(self, dem, output, filterx=11, filtery=11, callback=None): - """Calculates deviation from mean elevation. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('dev_from_mean_elev', args, callback) # returns 1 if error - - def diff_from_mean_elev(self, dem, output, filterx=11, filtery=11, callback=None): - """Calculates difference from mean elevation (equivalent to a high-pass filter). - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('diff_from_mean_elev', args, callback) # returns 1 if error - - def directional_relief(self, dem, output, azimuth=0.0, max_dist=None, callback=None): - """Calculates relief for cells in an input DEM for a specified direction. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - azimuth -- Wind azimuth in degrees. - max_dist -- Optional maximum search distance (unspecified if none; in xy units). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--azimuth={}".format(azimuth)) - if max_dist is not None: args.append("--max_dist='{}'".format(max_dist)) - return self.run_tool('directional_relief', args, callback) # returns 1 if error - - def downslope_index(self, dem, output, drop=2.0, out_type="tangent", callback=None): - """Calculates the Hjerdt et al. (2004) downslope index. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - drop -- Vertical drop value (default is 2.0). - out_type -- Output type, options include 'tangent', 'degrees', 'radians', 'distance' (default is 'tangent'). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--drop={}".format(drop)) - args.append("--out_type={}".format(out_type)) - return self.run_tool('downslope_index', args, callback) # returns 1 if error - - def drainage_preserving_smoothing(self, dem, output, filter=11, norm_diff=15.0, num_iter=3, max_diff=0.5, reduction=80.0, dfm=0.15, zfactor=1.0, callback=None): - """Reduces short-scale variation in an input DEM while preserving breaks-in-slope and small drainage features using a modified Sun et al. (2007) algorithm. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - filter -- Size of the filter kernel. - norm_diff -- Maximum difference in normal vectors, in degrees. - num_iter -- Number of iterations. - max_diff -- Maximum allowable absolute elevation change (optional). - reduction -- Maximum Amount to reduce the threshold angle by (0 = full smoothing; 100 = no smoothing). - dfm -- Difference from median threshold (in z-units), determines when a location is low-lying. - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--filter={}".format(filter)) - args.append("--norm_diff={}".format(norm_diff)) - args.append("--num_iter={}".format(num_iter)) - args.append("--max_diff={}".format(max_diff)) - args.append("--reduction={}".format(reduction)) - args.append("--dfm={}".format(dfm)) - args.append("--zfactor={}".format(zfactor)) - return self.run_tool('drainage_preserving_smoothing', args, callback) # returns 1 if error - - def edge_density(self, dem, output, filter=11, norm_diff=5.0, zfactor=1.0, callback=None): - """Calculates the density of edges, or breaks-in-slope within DEMs. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - filter -- Size of the filter kernel. - norm_diff -- Maximum difference in normal vectors, in degrees. - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--filter={}".format(filter)) - args.append("--norm_diff={}".format(norm_diff)) - args.append("--zfactor={}".format(zfactor)) - return self.run_tool('edge_density', args, callback) # returns 1 if error - - def elev_above_pit(self, dem, output, callback=None): - """Calculate the elevation of each grid cell above the nearest downstream pit cell or grid edge cell. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('elev_above_pit', args, callback) # returns 1 if error - - def elev_percentile(self, dem, output, filterx=11, filtery=11, sig_digits=2, callback=None): - """Calculates the elevation percentile raster from a DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - sig_digits -- Number of significant digits. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - args.append("--sig_digits={}".format(sig_digits)) - return self.run_tool('elev_percentile', args, callback) # returns 1 if error - - def elev_relative_to_min_max(self, dem, output, callback=None): - """Calculates the elevation of a location relative to the minimum and maximum elevations in a DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('elev_relative_to_min_max', args, callback) # returns 1 if error - - def elev_relative_to_watershed_min_max(self, dem, watersheds, output, callback=None): - """Calculates the elevation of a location relative to the minimum and maximum elevations in a watershed. - - Keyword arguments: - - dem -- Input raster DEM file. - watersheds -- Input raster watersheds file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--watersheds='{}'".format(watersheds)) - args.append("--output='{}'".format(output)) - return self.run_tool('elev_relative_to_watershed_min_max', args, callback) # returns 1 if error - - def feature_preserving_denoise(self, dem, output, filter=11, norm_diff=15.0, num_iter=3, max_diff=0.5, zfactor=1.0, callback=None): - """Reduces short-scale variation in an input DEM using a modified Sun et al. (2007) algorithm. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - filter -- Size of the filter kernel. - norm_diff -- Maximum difference in normal vectors, in degrees. - num_iter -- Number of iterations. - max_diff -- Maximum allowable absolute elevation change (optional). - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--filter={}".format(filter)) - args.append("--norm_diff={}".format(norm_diff)) - args.append("--num_iter={}".format(num_iter)) - args.append("--max_diff={}".format(max_diff)) - args.append("--zfactor={}".format(zfactor)) - return self.run_tool('feature_preserving_denoise', args, callback) # returns 1 if error - - def fetch_analysis(self, dem, output, azimuth=0.0, hgt_inc=0.05, callback=None): - """Performs an analysis of fetch or upwind distance to an obstacle. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - azimuth -- Wind azimuth in degrees in degrees. - hgt_inc -- Height increment value. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--azimuth={}".format(azimuth)) - args.append("--hgt_inc={}".format(hgt_inc)) - return self.run_tool('fetch_analysis', args, callback) # returns 1 if error - - def fill_missing_data(self, i, output, filter=11, weight=2.0, callback=None): - """Fills NoData holes in a DEM. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filter -- Filter size (cells). - weight -- IDW weight value. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filter={}".format(filter)) - args.append("--weight={}".format(weight)) - return self.run_tool('fill_missing_data', args, callback) # returns 1 if error - - def find_ridges(self, dem, output, line_thin=True, callback=None): - """Identifies potential ridge and peak grid cells. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - line_thin -- Optional flag indicating whether post-processing line-thinning should be performed. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - if line_thin: args.append("--line_thin") - return self.run_tool('find_ridges', args, callback) # returns 1 if error - - def hillshade(self, dem, output, azimuth=315.0, altitude=30.0, zfactor=1.0, callback=None): - """Calculates a hillshade raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - azimuth -- Illumination source azimuth in degrees. - altitude -- Illumination source altitude in degrees. - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--azimuth={}".format(azimuth)) - args.append("--altitude={}".format(altitude)) - args.append("--zfactor={}".format(zfactor)) - return self.run_tool('hillshade', args, callback) # returns 1 if error - - def horizon_angle(self, dem, output, azimuth=0.0, max_dist=None, callback=None): - """Calculates horizon angle (maximum upwind slope) for each grid cell in an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - azimuth -- Wind azimuth in degrees. - max_dist -- Optional maximum search distance (unspecified if none; in xy units). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--azimuth={}".format(azimuth)) - if max_dist is not None: args.append("--max_dist='{}'".format(max_dist)) - return self.run_tool('horizon_angle', args, callback) # returns 1 if error - - def hypsometric_analysis(self, inputs, output, watershed=None, callback=None): - """Calculates a hypsometric curve for one or more DEMs. - - Keyword arguments: - - inputs -- Input DEM files. - watershed -- Input watershed files (optional). - output -- Output HTML file (default name will be based on input file if unspecified). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - if watershed is not None: args.append("--watershed='{}'".format(watershed)) - args.append("--output='{}'".format(output)) - return self.run_tool('hypsometric_analysis', args, callback) # returns 1 if error - - def max_anisotropy_dev(self, dem, out_mag, out_scale, max_scale, min_scale=3, step=2, callback=None): - """Calculates the maximum anisotropy (directionality) in elevation deviation over a range of spatial scales. - - Keyword arguments: - - dem -- Input raster DEM file. - out_mag -- Output raster DEVmax magnitude file. - out_scale -- Output raster DEVmax scale file. - min_scale -- Minimum search neighbourhood radius in grid cells. - max_scale -- Maximum search neighbourhood radius in grid cells. - step -- Step size as any positive non-zero integer. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--out_mag='{}'".format(out_mag)) - args.append("--out_scale='{}'".format(out_scale)) - args.append("--min_scale={}".format(min_scale)) - args.append("--max_scale='{}'".format(max_scale)) - args.append("--step={}".format(step)) - return self.run_tool('max_anisotropy_dev', args, callback) # returns 1 if error - - def max_anisotropy_dev_signature(self, dem, points, output, max_scale, min_scale=1, step=1, callback=None): - """Calculates the anisotropy in deviation from mean for points over a range of spatial scales. - - Keyword arguments: - - dem -- Input raster DEM file. - points -- Input vector points file. - output -- Output HTML file. - min_scale -- Minimum search neighbourhood radius in grid cells. - max_scale -- Maximum search neighbourhood radius in grid cells. - step -- Step size as any positive non-zero integer. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--points='{}'".format(points)) - args.append("--output='{}'".format(output)) - args.append("--min_scale={}".format(min_scale)) - args.append("--max_scale='{}'".format(max_scale)) - args.append("--step={}".format(step)) - return self.run_tool('max_anisotropy_dev_signature', args, callback) # returns 1 if error - - def max_branch_length(self, dem, output, log=False, callback=None): - """Lindsay and Seibert's (2013) branch length index is used to map drainage divides or ridge lines. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - log -- Optional flag to request the output be log-transformed. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - if log: args.append("--log") - return self.run_tool('max_branch_length', args, callback) # returns 1 if error - - def max_difference_from_mean(self, dem, out_mag, out_scale, min_scale, max_scale, step=1, callback=None): - """Calculates the maximum difference from mean elevation over a range of spatial scales. - - Keyword arguments: - - dem -- Input raster DEM file. - out_mag -- Output raster DIFFmax magnitude file. - out_scale -- Output raster DIFFmax scale file. - min_scale -- Minimum search neighbourhood radius in grid cells. - max_scale -- Maximum search neighbourhood radius in grid cells. - step -- Step size as any positive non-zero integer. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--out_mag='{}'".format(out_mag)) - args.append("--out_scale='{}'".format(out_scale)) - args.append("--min_scale='{}'".format(min_scale)) - args.append("--max_scale='{}'".format(max_scale)) - args.append("--step={}".format(step)) - return self.run_tool('max_difference_from_mean', args, callback) # returns 1 if error - - def max_downslope_elev_change(self, dem, output, callback=None): - """Calculates the maximum downslope change in elevation between a grid cell and its eight downslope neighbors. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('max_downslope_elev_change', args, callback) # returns 1 if error - - def max_elev_dev_signature(self, dem, points, output, min_scale, max_scale, step=10, callback=None): - """Calculates the maximum elevation deviation over a range of spatial scales and for a set of points. - - Keyword arguments: - - dem -- Input raster DEM file. - points -- Input vector points file. - output -- Output HTML file. - min_scale -- Minimum search neighbourhood radius in grid cells. - max_scale -- Maximum search neighbourhood radius in grid cells. - step -- Step size as any positive non-zero integer. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--points='{}'".format(points)) - args.append("--output='{}'".format(output)) - args.append("--min_scale='{}'".format(min_scale)) - args.append("--max_scale='{}'".format(max_scale)) - args.append("--step={}".format(step)) - return self.run_tool('max_elev_dev_signature', args, callback) # returns 1 if error - - def max_elevation_deviation(self, dem, out_mag, out_scale, min_scale, max_scale, step=1, callback=None): - """Calculates the maximum elevation deviation over a range of spatial scales. - - Keyword arguments: - - dem -- Input raster DEM file. - out_mag -- Output raster DEVmax magnitude file. - out_scale -- Output raster DEVmax scale file. - min_scale -- Minimum search neighbourhood radius in grid cells. - max_scale -- Maximum search neighbourhood radius in grid cells. - step -- Step size as any positive non-zero integer. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--out_mag='{}'".format(out_mag)) - args.append("--out_scale='{}'".format(out_scale)) - args.append("--min_scale='{}'".format(min_scale)) - args.append("--max_scale='{}'".format(max_scale)) - args.append("--step={}".format(step)) - return self.run_tool('max_elevation_deviation', args, callback) # returns 1 if error - - def min_downslope_elev_change(self, dem, output, callback=None): - """Calculates the minimum downslope change in elevation between a grid cell and its eight downslope neighbors. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('min_downslope_elev_change', args, callback) # returns 1 if error - - def multiscale_roughness(self, dem, out_mag, out_scale, max_scale, min_scale=1, step=1, callback=None): - """Calculates surface roughness over a range of spatial scales. - - Keyword arguments: - - dem -- Input raster DEM file. - out_mag -- Output raster roughness magnitude file. - out_scale -- Output raster roughness scale file. - min_scale -- Minimum search neighbourhood radius in grid cells. - max_scale -- Maximum search neighbourhood radius in grid cells. - step -- Step size as any positive non-zero integer. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--out_mag='{}'".format(out_mag)) - args.append("--out_scale='{}'".format(out_scale)) - args.append("--min_scale={}".format(min_scale)) - args.append("--max_scale='{}'".format(max_scale)) - args.append("--step={}".format(step)) - return self.run_tool('multiscale_roughness', args, callback) # returns 1 if error - - def multiscale_roughness_signature(self, dem, points, output, max_scale, min_scale=1, step=1, callback=None): - """Calculates the surface roughness for points over a range of spatial scales. - - Keyword arguments: - - dem -- Input raster DEM file. - points -- Input vector points file. - output -- Output HTML file. - min_scale -- Minimum search neighbourhood radius in grid cells. - max_scale -- Maximum search neighbourhood radius in grid cells. - step -- Step size as any positive non-zero integer. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--points='{}'".format(points)) - args.append("--output='{}'".format(output)) - args.append("--min_scale={}".format(min_scale)) - args.append("--max_scale='{}'".format(max_scale)) - args.append("--step={}".format(step)) - return self.run_tool('multiscale_roughness_signature', args, callback) # returns 1 if error - - def multiscale_topographic_position_image(self, local, meso, broad, output, lightness=1.2, callback=None): - """Creates a multiscale topographic position image from three DEVmax rasters of differing spatial scale ranges. - - Keyword arguments: - - local -- Input local-scale topographic position (DEVmax) raster file. - meso -- Input meso-scale topographic position (DEVmax) raster file. - broad -- Input broad-scale topographic position (DEVmax) raster file. - output -- Output raster file. - lightness -- Image lightness value (default is 1.2). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--local='{}'".format(local)) - args.append("--meso='{}'".format(meso)) - args.append("--broad='{}'".format(broad)) - args.append("--output='{}'".format(output)) - args.append("--lightness={}".format(lightness)) - return self.run_tool('multiscale_topographic_position_image', args, callback) # returns 1 if error - - def num_downslope_neighbours(self, dem, output, callback=None): - """Calculates the number of downslope neighbours to each grid cell in a DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('num_downslope_neighbours', args, callback) # returns 1 if error - - def num_upslope_neighbours(self, dem, output, callback=None): - """Calculates the number of upslope neighbours to each grid cell in a DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('num_upslope_neighbours', args, callback) # returns 1 if error - - def pennock_landform_class(self, dem, output, slope=3.0, prof=0.1, plan=0.0, zfactor=1.0, callback=None): - """Classifies hillslope zones based on slope, profile curvature, and plan curvature. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - slope -- Slope threshold value, in degrees (default is 3.0). - prof -- Profile curvature threshold value (default is 0.1). - plan -- Plan curvature threshold value (default is 0.0). - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--slope={}".format(slope)) - args.append("--prof={}".format(prof)) - args.append("--plan={}".format(plan)) - args.append("--zfactor={}".format(zfactor)) - return self.run_tool('pennock_landform_class', args, callback) # returns 1 if error - - def percent_elev_range(self, dem, output, filterx=3, filtery=3, callback=None): - """Calculates percent of elevation range from a DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('percent_elev_range', args, callback) # returns 1 if error - - def plan_curvature(self, dem, output, zfactor=1.0, callback=None): - """Calculates a plan (contour) curvature raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--zfactor={}".format(zfactor)) - return self.run_tool('plan_curvature', args, callback) # returns 1 if error - - def profile(self, lines, surface, output, callback=None): - """Plots profiles from digital surface models. - - Keyword arguments: - - lines -- Input vector line file. - surface -- Input raster surface file. - output -- Output HTML file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--lines='{}'".format(lines)) - args.append("--surface='{}'".format(surface)) - args.append("--output='{}'".format(output)) - return self.run_tool('profile', args, callback) # returns 1 if error - - def profile_curvature(self, dem, output, zfactor=1.0, callback=None): - """Calculates a profile curvature raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--zfactor={}".format(zfactor)) - return self.run_tool('profile_curvature', args, callback) # returns 1 if error - - def relative_aspect(self, dem, output, azimuth=0.0, zfactor=1.0, callback=None): - """Calculates relative aspect (relative to a user-specified direction) from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - azimuth -- Illumination source azimuth. - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--azimuth={}".format(azimuth)) - args.append("--zfactor={}".format(zfactor)) - return self.run_tool('relative_aspect', args, callback) # returns 1 if error - - def relative_stream_power_index(self, sca, slope, output, exponent=1.0, callback=None): - """Calculates the relative stream power index. - - Keyword arguments: - - sca -- Input raster specific contributing area (SCA) file. - slope -- Input raster slope file. - output -- Output raster file. - exponent -- SCA exponent value. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--sca='{}'".format(sca)) - args.append("--slope='{}'".format(slope)) - args.append("--output='{}'".format(output)) - args.append("--exponent={}".format(exponent)) - return self.run_tool('relative_stream_power_index', args, callback) # returns 1 if error - - def relative_topographic_position(self, dem, output, filterx=11, filtery=11, callback=None): - """Calculates the relative topographic position index from a DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('relative_topographic_position', args, callback) # returns 1 if error - - def remove_off_terrain_objects(self, dem, output, filter=11, slope=15.0, callback=None): - """Removes off-terrain objects from a raster digital elevation model (DEM). - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - filter -- Filter size (cells). - slope -- Slope threshold value. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--filter={}".format(filter)) - args.append("--slope={}".format(slope)) - return self.run_tool('remove_off_terrain_objects', args, callback) # returns 1 if error - - def ruggedness_index(self, dem, output, zfactor=1.0, callback=None): - """Calculates the Riley et al.'s (1999) terrain ruggedness index from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--zfactor={}".format(zfactor)) - return self.run_tool('ruggedness_index', args, callback) # returns 1 if error - - def sediment_transport_index(self, sca, slope, output, sca_exponent=0.4, slope_exponent=1.3, callback=None): - """Calculates the sediment transport index. - - Keyword arguments: - - sca -- Input raster specific contributing area (SCA) file. - slope -- Input raster slope file. - output -- Output raster file. - sca_exponent -- SCA exponent value. - slope_exponent -- Slope exponent value. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--sca='{}'".format(sca)) - args.append("--slope='{}'".format(slope)) - args.append("--output='{}'".format(output)) - args.append("--sca_exponent={}".format(sca_exponent)) - args.append("--slope_exponent={}".format(slope_exponent)) - return self.run_tool('sediment_transport_index', args, callback) # returns 1 if error - - def slope(self, dem, output, zfactor=1.0, callback=None): - """Calculates a slope raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--zfactor={}".format(zfactor)) - return self.run_tool('slope', args, callback) # returns 1 if error - - def slope_vs_elevation_plot(self, inputs, output, watershed=None, callback=None): - """Creates a slope vs. elevation plot for one or more DEMs. - - Keyword arguments: - - inputs -- Input DEM files. - watershed -- Input watershed files (optional). - output -- Output HTML file (default name will be based on input file if unspecified). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - if watershed is not None: args.append("--watershed='{}'".format(watershed)) - args.append("--output='{}'".format(output)) - return self.run_tool('slope_vs_elevation_plot', args, callback) # returns 1 if error - - def standard_deviation_of_slope(self, i, output, zfactor=1.0, filterx=11, filtery=11, callback=None): - """Calculates the standard deviation of slope from an input DEM. - - Keyword arguments: - - i -- Input raster DEM file. - output -- Output raster DEM file. - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--zfactor={}".format(zfactor)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('standard_deviation_of_slope', args, callback) # returns 1 if error - - def surface_area_ratio(self, dem, output, callback=None): - """Calculates a the surface area ratio of each grid cell in an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('surface_area_ratio', args, callback) # returns 1 if error - - def tangential_curvature(self, dem, output, zfactor=1.0, callback=None): - """Calculates a tangential curvature raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--zfactor={}".format(zfactor)) - return self.run_tool('tangential_curvature', args, callback) # returns 1 if error - - def total_curvature(self, dem, output, zfactor=1.0, callback=None): - """Calculates a total curvature raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - zfactor -- Optional multiplier for when the vertical and horizontal units are not the same. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--zfactor={}".format(zfactor)) - return self.run_tool('total_curvature', args, callback) # returns 1 if error - - def viewshed(self, dem, stations, output, height=2.0, callback=None): - """Identifies the viewshed for a point or set of points. - - Keyword arguments: - - dem -- Input raster DEM file. - stations -- Input viewing station vector file. - output -- Output raster file. - height -- Viewing station height, in z units. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--stations='{}'".format(stations)) - args.append("--output='{}'".format(output)) - args.append("--height={}".format(height)) - return self.run_tool('viewshed', args, callback) # returns 1 if error - - def visibility_index(self, dem, output, height=2.0, res_factor=2, callback=None): - """Estimates the relative visibility of sites in a DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - height -- Viewing station height, in z units. - res_factor -- The resolution factor determines the density of measured viewsheds. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--height={}".format(height)) - args.append("--res_factor={}".format(res_factor)) - return self.run_tool('visibility_index', args, callback) # returns 1 if error - - def wetness_index(self, sca, slope, output, callback=None): - """Calculates the topographic wetness index, Ln(A / tan(slope)). - - Keyword arguments: - - sca -- Input raster specific contributing area (SCA) file. - slope -- Input raster slope file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--sca='{}'".format(sca)) - args.append("--slope='{}'".format(slope)) - args.append("--output='{}'".format(output)) - return self.run_tool('wetness_index', args, callback) # returns 1 if error - - ######################### - # Hydrological Analysis # - ######################### - - def average_flowpath_slope(self, dem, output, callback=None): - """Measures the average slope gradient from each grid cell to all upslope divide cells. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('average_flowpath_slope', args, callback) # returns 1 if error - - def average_upslope_flowpath_length(self, dem, output, callback=None): - """Measures the average length of all upslope flowpaths draining each grid cell. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('average_upslope_flowpath_length', args, callback) # returns 1 if error - - def basins(self, d8_pntr, output, esri_pntr=False, callback=None): - """Identifies drainage basins that drain to the DEM edge. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('basins', args, callback) # returns 1 if error - - def breach_depressions(self, dem, output, max_depth=None, max_length=None, callback=None): - """Breaches all of the depressions in a DEM using Lindsay's (2016) algorithm. This should be preferred over depression filling in most cases. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - max_depth -- Optional maximum breach depth (default is Inf). - max_length -- Optional maximum breach channel length (in grid cells; default is Inf). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - if max_depth is not None: args.append("--max_depth='{}'".format(max_depth)) - if max_length is not None: args.append("--max_length='{}'".format(max_length)) - return self.run_tool('breach_depressions', args, callback) # returns 1 if error - - def breach_single_cell_pits(self, dem, output, callback=None): - """Removes single-cell pits from an input DEM by breaching. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('breach_single_cell_pits', args, callback) # returns 1 if error - - def d8_flow_accumulation(self, dem, output, out_type="cells", log=False, clip=False, callback=None): - """Calculates a D8 flow accumulation raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - out_type -- Output type; one of 'cells' (default), 'catchment area', and 'specific contributing area'. - log -- Optional flag to request the output be log-transformed. - clip -- Optional flag to request clipping the display max by 1%. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--out_type={}".format(out_type)) - if log: args.append("--log") - if clip: args.append("--clip") - return self.run_tool('d8_flow_accumulation', args, callback) # returns 1 if error - - def d8_mass_flux(self, dem, loading, efficiency, absorption, output, callback=None): - """Performs a D8 mass flux calculation. - - Keyword arguments: - - dem -- Input raster DEM file. - loading -- Input loading raster file. - efficiency -- Input efficiency raster file. - absorption -- Input absorption raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--loading='{}'".format(loading)) - args.append("--efficiency='{}'".format(efficiency)) - args.append("--absorption='{}'".format(absorption)) - args.append("--output='{}'".format(output)) - return self.run_tool('d8_mass_flux', args, callback) # returns 1 if error - - def d8_pointer(self, dem, output, esri_pntr=False, callback=None): - """Calculates a D8 flow pointer raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('d8_pointer', args, callback) # returns 1 if error - - def d_inf_flow_accumulation(self, dem, output, out_type="Specific Contributing Area", threshold=None, log=False, clip=False, callback=None): - """Calculates a D-infinity flow accumulation raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - out_type -- Output type; one of 'cells', 'sca' (default), and 'ca'. - threshold -- Optional convergence threshold parameter, in grid cells; default is inifinity. - log -- Optional flag to request the output be log-transformed. - clip -- Optional flag to request clipping the display max by 1%. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--out_type={}".format(out_type)) - if threshold is not None: args.append("--threshold='{}'".format(threshold)) - if log: args.append("--log") - if clip: args.append("--clip") - return self.run_tool('d_inf_flow_accumulation', args, callback) # returns 1 if error - - def d_inf_mass_flux(self, dem, loading, efficiency, absorption, output, callback=None): - """Performs a D-infinity mass flux calculation. - - Keyword arguments: - - dem -- Input raster DEM file. - loading -- Input loading raster file. - efficiency -- Input efficiency raster file. - absorption -- Input absorption raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--loading='{}'".format(loading)) - args.append("--efficiency='{}'".format(efficiency)) - args.append("--absorption='{}'".format(absorption)) - args.append("--output='{}'".format(output)) - return self.run_tool('d_inf_mass_flux', args, callback) # returns 1 if error - - def d_inf_pointer(self, dem, output, callback=None): - """Calculates a D-infinity flow pointer (flow direction) raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('d_inf_pointer', args, callback) # returns 1 if error - - def depth_in_sink(self, dem, output, zero_background=False, callback=None): - """Measures the depth of sinks (depressions) in a DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - zero_background -- Flag indicating whether the background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - if zero_background: args.append("--zero_background") - return self.run_tool('depth_in_sink', args, callback) # returns 1 if error - - def downslope_distance_to_stream(self, dem, streams, output, callback=None): - """Measures distance to the nearest downslope stream cell. - - Keyword arguments: - - dem -- Input raster DEM file. - streams -- Input raster streams file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - return self.run_tool('downslope_distance_to_stream', args, callback) # returns 1 if error - - def downslope_flowpath_length(self, d8_pntr, output, watersheds=None, weights=None, esri_pntr=False, callback=None): - """Calculates the downslope flowpath length from each cell to basin outlet. - - Keyword arguments: - - d8_pntr -- Input D8 pointer raster file. - watersheds -- Optional input watershed raster file. - weights -- Optional input weights raster file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - if watersheds is not None: args.append("--watersheds='{}'".format(watersheds)) - if weights is not None: args.append("--weights='{}'".format(weights)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('downslope_flowpath_length', args, callback) # returns 1 if error - - def elevation_above_stream(self, dem, streams, output, callback=None): - """Calculates the elevation of cells above the nearest downslope stream cell. - - Keyword arguments: - - dem -- Input raster DEM file. - streams -- Input raster streams file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - return self.run_tool('elevation_above_stream', args, callback) # returns 1 if error - - def elevation_above_stream_euclidean(self, dem, streams, output, callback=None): - """Calculates the elevation of cells above the nearest (Euclidean distance) stream cell. - - Keyword arguments: - - dem -- Input raster DEM file. - streams -- Input raster streams file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - return self.run_tool('elevation_above_stream_euclidean', args, callback) # returns 1 if error - - def fd8_flow_accumulation(self, dem, output, out_type="specific contributing area", exponent=1.1, threshold=None, log=False, clip=False, callback=None): - """Calculates an FD8 flow accumulation raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - out_type -- Output type; one of 'cells', 'specific contributing area' (default), and 'catchment area'. - exponent -- Optional exponent parameter; default is 1.1. - threshold -- Optional convergence threshold parameter, in grid cells; default is inifinity. - log -- Optional flag to request the output be log-transformed. - clip -- Optional flag to request clipping the display max by 1%. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--out_type={}".format(out_type)) - args.append("--exponent={}".format(exponent)) - if threshold is not None: args.append("--threshold='{}'".format(threshold)) - if log: args.append("--log") - if clip: args.append("--clip") - return self.run_tool('fd8_flow_accumulation', args, callback) # returns 1 if error - - def fd8_pointer(self, dem, output, callback=None): - """Calculates an FD8 flow pointer raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('fd8_pointer', args, callback) # returns 1 if error - - def fill_burn(self, dem, streams, output, callback=None): - """Burns streams into a DEM using the FillBurn (Saunders, 1999) method. - - Keyword arguments: - - dem -- Input raster DEM file. - streams -- Input vector streams file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - return self.run_tool('fill_burn', args, callback) # returns 1 if error - - def fill_depressions(self, dem, output, fix_flats=True, callback=None): - """Fills all of the depressions in a DEM. Depression breaching should be preferred in most cases. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - fix_flats -- Optional flag indicating whether flat areas should have a small gradient applied. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - if fix_flats: args.append("--fix_flats") - return self.run_tool('fill_depressions', args, callback) # returns 1 if error - - def fill_single_cell_pits(self, dem, output, callback=None): - """Raises pit cells to the elevation of their lowest neighbour. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('fill_single_cell_pits', args, callback) # returns 1 if error - - def find_no_flow_cells(self, dem, output, callback=None): - """Finds grid cells with no downslope neighbours. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('find_no_flow_cells', args, callback) # returns 1 if error - - def find_parallel_flow(self, d8_pntr, streams, output, callback=None): - """Finds areas of parallel flow in D8 flow direction rasters. - - Keyword arguments: - - d8_pntr -- Input D8 pointer raster file. - streams -- Input raster streams file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - return self.run_tool('find_parallel_flow', args, callback) # returns 1 if error - - def flatten_lakes(self, dem, lakes, output, callback=None): - """Flattens lake polygons in a raster DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - lakes -- Input lakes vector polygons file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--lakes='{}'".format(lakes)) - args.append("--output='{}'".format(output)) - return self.run_tool('flatten_lakes', args, callback) # returns 1 if error - - def flood_order(self, dem, output, callback=None): - """Assigns each DEM grid cell its order in the sequence of inundations that are encountered during a search starting from the edges, moving inward at increasing elevations. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('flood_order', args, callback) # returns 1 if error - - def flow_accumulation_full_workflow(self, dem, out_dem, out_pntr, out_accum, out_type="Specific Contributing Area", log=False, clip=False, esri_pntr=False, callback=None): - """Resolves all of the depressions in a DEM, outputting a breached DEM, an aspect-aligned non-divergent flow pointer, and a flow accumulation raster. - - Keyword arguments: - - dem -- Input raster DEM file. - out_dem -- Output raster DEM file. - out_pntr -- Output raster flow pointer file. - out_accum -- Output raster flow accumulation file. - out_type -- Output type; one of 'cells', 'sca' (default), and 'ca'. - log -- Optional flag to request the output be log-transformed. - clip -- Optional flag to request clipping the display max by 1%. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--out_dem='{}'".format(out_dem)) - args.append("--out_pntr='{}'".format(out_pntr)) - args.append("--out_accum='{}'".format(out_accum)) - args.append("--out_type={}".format(out_type)) - if log: args.append("--log") - if clip: args.append("--clip") - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('flow_accumulation_full_workflow', args, callback) # returns 1 if error - - def flow_length_diff(self, d8_pntr, output, esri_pntr=False, callback=None): - """Calculates the local maximum absolute difference in downslope flowpath length, useful in mapping drainage divides and ridges. - - Keyword arguments: - - d8_pntr -- Input D8 pointer raster file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('flow_length_diff', args, callback) # returns 1 if error - - def hillslopes(self, d8_pntr, streams, output, esri_pntr=False, callback=None): - """Identifies the individual hillslopes draining to each link in a stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('hillslopes', args, callback) # returns 1 if error - - def impoundment_size_index(self, dem, output, damlength, out_type="depth", callback=None): - """Calculates the impoundment size resulting from damming a DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output file. - out_type -- Output type; one of 'depth' (default), 'volume', and 'area'. - damlength -- Maximum length of thr dam. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--out_type={}".format(out_type)) - args.append("--damlength='{}'".format(damlength)) - return self.run_tool('impoundment_size_index', args, callback) # returns 1 if error - - def isobasins(self, dem, output, size, callback=None): - """Divides a landscape into nearly equal sized drainage basins (i.e. watersheds). - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - size -- Target basin size, in grid cells. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--size='{}'".format(size)) - return self.run_tool('isobasins', args, callback) # returns 1 if error - - def jenson_snap_pour_points(self, pour_pts, streams, output, snap_dist, callback=None): - """Moves outlet points used to specify points of interest in a watershedding operation to the nearest stream cell. - - Keyword arguments: - - pour_pts -- Input vector pour points (outlet) file. - streams -- Input raster streams file. - output -- Output vector file. - snap_dist -- Maximum snap distance in map units. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--pour_pts='{}'".format(pour_pts)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - args.append("--snap_dist='{}'".format(snap_dist)) - return self.run_tool('jenson_snap_pour_points', args, callback) # returns 1 if error - - def longest_flowpath(self, dem, basins, output, callback=None): - """Delineates the longest flowpaths for a group of subbasins or watersheds. - - Keyword arguments: - - dem -- Input raster DEM file. - basins -- Input raster basins file. - output -- Output vector file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--basins='{}'".format(basins)) - args.append("--output='{}'".format(output)) - return self.run_tool('longest_flowpath', args, callback) # returns 1 if error - - def max_upslope_flowpath_length(self, dem, output, callback=None): - """Measures the maximum length of all upslope flowpaths draining each grid cell. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('max_upslope_flowpath_length', args, callback) # returns 1 if error - - def num_inflowing_neighbours(self, dem, output, callback=None): - """Computes the number of inflowing neighbours to each cell in an input DEM based on the D8 algorithm. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - return self.run_tool('num_inflowing_neighbours', args, callback) # returns 1 if error - - def raise_walls(self, i, dem, output, breach=None, height=100.0, callback=None): - """Raises walls in a DEM along a line or around a polygon, e.g. a watershed. - - Keyword arguments: - - i -- Input vector lines or polygons file. - breach -- Optional input vector breach lines. - dem -- Input raster DEM file. - output -- Output raster file. - height -- Wall height. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - if breach is not None: args.append("--breach='{}'".format(breach)) - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--height={}".format(height)) - return self.run_tool('raise_walls', args, callback) # returns 1 if error - - def rho8_pointer(self, dem, output, esri_pntr=False, callback=None): - """Calculates a stochastic Rho8 flow pointer raster from an input DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('rho8_pointer', args, callback) # returns 1 if error - - def sink(self, dem, output, zero_background=False, callback=None): - """Identifies the depressions in a DEM, giving each feature a unique identifier. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - if zero_background: args.append("--zero_background") - return self.run_tool('sink', args, callback) # returns 1 if error - - def snap_pour_points(self, pour_pts, flow_accum, output, snap_dist, callback=None): - """Moves outlet points used to specify points of interest in a watershedding operation to the cell with the highest flow accumulation in its neighbourhood. - - Keyword arguments: - - pour_pts -- Input vector pour points (outlet) file. - flow_accum -- Input raster D8 flow accumulation file. - output -- Output vector file. - snap_dist -- Maximum snap distance in map units. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--pour_pts='{}'".format(pour_pts)) - args.append("--flow_accum='{}'".format(flow_accum)) - args.append("--output='{}'".format(output)) - args.append("--snap_dist='{}'".format(snap_dist)) - return self.run_tool('snap_pour_points', args, callback) # returns 1 if error - - def stochastic_depression_analysis(self, dem, output, rmse, range, iterations=1000, callback=None): - """Preforms a stochastic analysis of depressions within a DEM. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output file. - rmse -- The DEM's root-mean-square-error (RMSE), in z units. This determines error magnitude. - range -- The error field's correlation length, in xy-units. - iterations -- The number of iterations. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--rmse='{}'".format(rmse)) - args.append("--range='{}'".format(range)) - args.append("--iterations={}".format(iterations)) - return self.run_tool('stochastic_depression_analysis', args, callback) # returns 1 if error - - def strahler_order_basins(self, d8_pntr, streams, output, esri_pntr=False, callback=None): - """Identifies Strahler-order basins from an input stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('strahler_order_basins', args, callback) # returns 1 if error - - def subbasins(self, d8_pntr, streams, output, esri_pntr=False, callback=None): - """Identifies the catchments, or sub-basin, draining to each link in a stream network. - - Keyword arguments: - - d8_pntr -- Input D8 pointer raster file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('subbasins', args, callback) # returns 1 if error - - def trace_downslope_flowpaths(self, seed_pts, d8_pntr, output, esri_pntr=False, zero_background=False, callback=None): - """Traces downslope flowpaths from one or more target sites (i.e. seed points). - - Keyword arguments: - - seed_pts -- Input vector seed points file. - d8_pntr -- Input D8 pointer raster file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--seed_pts='{}'".format(seed_pts)) - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('trace_downslope_flowpaths', args, callback) # returns 1 if error - - def unnest_basins(self, d8_pntr, pour_pts, output, esri_pntr=False, callback=None): - """Extract whole watersheds for a set of outlet points. - - Keyword arguments: - - d8_pntr -- Input D8 pointer raster file. - pour_pts -- Input vector pour points (outlet) file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--pour_pts='{}'".format(pour_pts)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('unnest_basins', args, callback) # returns 1 if error - - def watershed(self, d8_pntr, pour_pts, output, esri_pntr=False, callback=None): - """Identifies the watershed, or drainage basin, draining to a set of target cells. - - Keyword arguments: - - d8_pntr -- Input D8 pointer raster file. - pour_pts -- Input vector pour points (outlet) file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--pour_pts='{}'".format(pour_pts)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('watershed', args, callback) # returns 1 if error - - ########################## - # Image Processing Tools # - ########################## - - def change_vector_analysis(self, date1, date2, magnitude, direction, callback=None): - """Performs a change vector analysis on a two-date multi-spectral dataset. - - Keyword arguments: - - date1 -- Input raster files for the earlier date. - date2 -- Input raster files for the later date. - magnitude -- Output vector magnitude raster file. - direction -- Output vector Direction raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--date1='{}'".format(date1)) - args.append("--date2='{}'".format(date2)) - args.append("--magnitude='{}'".format(magnitude)) - args.append("--direction='{}'".format(direction)) - return self.run_tool('change_vector_analysis', args, callback) # returns 1 if error - - def closing(self, i, output, filterx=11, filtery=11, callback=None): - """A closing is a mathematical morphology operation involving an erosion (min filter) of a dilation (max filter) set. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('closing', args, callback) # returns 1 if error - - def create_colour_composite(self, red, green, blue, output, opacity=None, enhance=True, zeros=False, callback=None): - """Creates a colour-composite image from three bands of multispectral imagery. - - Keyword arguments: - - red -- Input red band image file. - green -- Input green band image file. - blue -- Input blue band image file. - opacity -- Input opacity band image file (optional). - output -- Output colour composite file. - enhance -- Optional flag indicating whether a balance contrast enhancement is performed. - zeros -- Optional flag to indicate if zeros are nodata values. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--red='{}'".format(red)) - args.append("--green='{}'".format(green)) - args.append("--blue='{}'".format(blue)) - if opacity is not None: args.append("--opacity='{}'".format(opacity)) - args.append("--output='{}'".format(output)) - if enhance: args.append("--enhance") - if zeros: args.append("--zeros") - return self.run_tool('create_colour_composite', args, callback) # returns 1 if error - - def flip_image(self, i, output, direction="vertical", callback=None): - """Reflects an image in the vertical or horizontal axis. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - direction -- Direction of reflection; options include 'v' (vertical), 'h' (horizontal), and 'b' (both). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--direction={}".format(direction)) - return self.run_tool('flip_image', args, callback) # returns 1 if error - - def ihs_to_rgb(self, intensity, hue, saturation, red=None, green=None, blue=None, output=None, callback=None): - """Converts intensity, hue, and saturation (IHS) images into red, green, and blue (RGB) images. - - Keyword arguments: - - intensity -- Input intensity file. - hue -- Input hue file. - saturation -- Input saturation file. - red -- Output red band file. Optionally specified if colour-composite not specified. - green -- Output green band file. Optionally specified if colour-composite not specified. - blue -- Output blue band file. Optionally specified if colour-composite not specified. - output -- Output colour-composite file. Only used if individual bands are not specified. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--intensity='{}'".format(intensity)) - args.append("--hue='{}'".format(hue)) - args.append("--saturation='{}'".format(saturation)) - if red is not None: args.append("--red='{}'".format(red)) - if green is not None: args.append("--green='{}'".format(green)) - if blue is not None: args.append("--blue='{}'".format(blue)) - if output is not None: args.append("--output='{}'".format(output)) - return self.run_tool('ihs_to_rgb', args, callback) # returns 1 if error - - def image_stack_profile(self, inputs, points, output, callback=None): - """Plots an image stack profile (i.e. signature) for a set of points and multispectral images. - - Keyword arguments: - - inputs -- Input multispectral image files. - points -- Input vector points file. - output -- Output HTML file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--points='{}'".format(points)) - args.append("--output='{}'".format(output)) - return self.run_tool('image_stack_profile', args, callback) # returns 1 if error - - def integral_image(self, i, output, callback=None): - """Transforms an input image (summed area table) into its integral image equivalent. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('integral_image', args, callback) # returns 1 if error - - def k_means_clustering(self, inputs, output, classes, out_html=None, max_iterations=10, class_change=2.0, initialize="diagonal", min_class_size=10, callback=None): - """Performs a k-means clustering operation on a multi-spectral dataset. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output raster file. - out_html -- Output HTML report file. - classes -- Number of classes. - max_iterations -- Maximum number of iterations. - class_change -- Minimum percent of cells changed between iterations before completion. - initialize -- How to initialize cluster centres?. - min_class_size -- Minimum class size, in pixels. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - if out_html is not None: args.append("--out_html='{}'".format(out_html)) - args.append("--classes='{}'".format(classes)) - args.append("--max_iterations={}".format(max_iterations)) - args.append("--class_change={}".format(class_change)) - args.append("--initialize={}".format(initialize)) - args.append("--min_class_size={}".format(min_class_size)) - return self.run_tool('k_means_clustering', args, callback) # returns 1 if error - - def line_thinning(self, i, output, callback=None): - """Performs line thinning a on Boolean raster image; intended to be used with the RemoveSpurs tool. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('line_thinning', args, callback) # returns 1 if error - - def modified_k_means_clustering(self, inputs, output, out_html=None, start_clusters=1000, merger_dist=None, max_iterations=10, class_change=2.0, callback=None): - """Performs a modified k-means clustering operation on a multi-spectral dataset. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output raster file. - out_html -- Output HTML report file. - start_clusters -- Initial number of clusters. - merger_dist -- Cluster merger distance. - max_iterations -- Maximum number of iterations. - class_change -- Minimum percent of cells changed between iterations before completion. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - if out_html is not None: args.append("--out_html='{}'".format(out_html)) - args.append("--start_clusters={}".format(start_clusters)) - if merger_dist is not None: args.append("--merger_dist='{}'".format(merger_dist)) - args.append("--max_iterations={}".format(max_iterations)) - args.append("--class_change={}".format(class_change)) - return self.run_tool('modified_k_means_clustering', args, callback) # returns 1 if error - - def mosaic(self, inputs, output, method="cc", callback=None): - """Mosaics two or more images together. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output raster file. - method -- Resampling method; options include 'nn' (nearest neighbour), 'bilinear', and 'cc' (cubic convolution). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - args.append("--method={}".format(method)) - return self.run_tool('mosaic', args, callback) # returns 1 if error - - def mosaic_with_feathering(self, input1, input2, output, method="cc", weight=4.0, callback=None): - """Mosaics two images together using a feathering technique in overlapping areas to reduce edge-effects. - - Keyword arguments: - - input1 -- Input raster file to modify. - input2 -- Input reference raster file. - output -- Output raster file. - method -- Resampling method; options include 'nn' (nearest neighbour), 'bilinear', and 'cc' (cubic convolution). - weight -- . - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - args.append("--method={}".format(method)) - args.append("--weight={}".format(weight)) - return self.run_tool('mosaic_with_feathering', args, callback) # returns 1 if error - - def normalized_difference_vegetation_index(self, nir, red, output, clip=0.0, osavi=False, callback=None): - """Calculates the normalized difference vegetation index (NDVI) from near-infrared and red imagery. - - Keyword arguments: - - nir -- Input near-infrared band image. - red -- Input red band image. - output -- Output raster file. - clip -- Optional amount to clip the distribution tails by, in percent. - osavi -- Optional flag indicating whether the optimized soil-adjusted veg index (OSAVI) should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--nir='{}'".format(nir)) - args.append("--red='{}'".format(red)) - args.append("--output='{}'".format(output)) - args.append("--clip={}".format(clip)) - if osavi: args.append("--osavi") - return self.run_tool('normalized_difference_vegetation_index', args, callback) # returns 1 if error - - def opening(self, i, output, filterx=11, filtery=11, callback=None): - """An opening is a mathematical morphology operation involving a dilation (max filter) of an erosion (min filter) set. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('opening', args, callback) # returns 1 if error - - def remove_spurs(self, i, output, iterations=10, callback=None): - """Removes the spurs (pruning operation) from a Boolean line image; intended to be used on the output of the LineThinning tool. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - iterations -- Maximum number of iterations. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--iterations={}".format(iterations)) - return self.run_tool('remove_spurs', args, callback) # returns 1 if error - - def resample(self, inputs, destination, method="cc", callback=None): - """Resamples one or more input images into a destination image. - - Keyword arguments: - - inputs -- Input raster files. - destination -- Destination raster file. - method -- Resampling method; options include 'nn' (nearest neighbour), 'bilinear', and 'cc' (cubic convolution). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--destination='{}'".format(destination)) - args.append("--method={}".format(method)) - return self.run_tool('resample', args, callback) # returns 1 if error - - def rgb_to_ihs(self, intensity, hue, saturation, red=None, green=None, blue=None, composite=None, callback=None): - """Converts red, green, and blue (RGB) images into intensity, hue, and saturation (IHS) images. - - Keyword arguments: - - red -- Input red band image file. Optionally specified if colour-composite not specified. - green -- Input green band image file. Optionally specified if colour-composite not specified. - blue -- Input blue band image file. Optionally specified if colour-composite not specified. - composite -- Input colour-composite image file. Only used if individual bands are not specified. - intensity -- Output intensity raster file. - hue -- Output hue raster file. - saturation -- Output saturation raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if red is not None: args.append("--red='{}'".format(red)) - if green is not None: args.append("--green='{}'".format(green)) - if blue is not None: args.append("--blue='{}'".format(blue)) - if composite is not None: args.append("--composite='{}'".format(composite)) - args.append("--intensity='{}'".format(intensity)) - args.append("--hue='{}'".format(hue)) - args.append("--saturation='{}'".format(saturation)) - return self.run_tool('rgb_to_ihs', args, callback) # returns 1 if error - - def split_colour_composite(self, i, output, callback=None): - """This tool splits an RGB colour composite image into seperate multispectral images. - - Keyword arguments: - - i -- Input colour composite image file. - output -- Output raster file (suffixes of _r, _g, and _b will be appended). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('split_colour_composite', args, callback) # returns 1 if error - - def thicken_raster_line(self, i, output, callback=None): - """Thickens single-cell wide lines within a raster image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('thicken_raster_line', args, callback) # returns 1 if error - - def tophat_transform(self, i, output, filterx=11, filtery=11, variant="white", callback=None): - """Performs either a white or black top-hat transform on an input image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - variant -- Optional variant value. Options include 'white' and 'black'. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - args.append("--variant={}".format(variant)) - return self.run_tool('tophat_transform', args, callback) # returns 1 if error - - def write_function_memory_insertion(self, input1, input2, output, input3=None, callback=None): - """Performs a write function memory insertion for single-band multi-date change detection. - - Keyword arguments: - - input1 -- Input raster file associated with the first date. - input2 -- Input raster file associated with the second date. - input3 -- Optional input raster file associated with the third date. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - if input3 is not None: args.append("--input3='{}'".format(input3)) - args.append("--output='{}'".format(output)) - return self.run_tool('write_function_memory_insertion', args, callback) # returns 1 if error - - ################################## - # Image Processing Tools/Filters # - ################################## - - def adaptive_filter(self, i, output, filterx=11, filtery=11, threshold=2.0, callback=None): - """Performs an adaptive filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - threshold -- Difference from mean threshold, in standard deviations. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - args.append("--threshold={}".format(threshold)) - return self.run_tool('adaptive_filter', args, callback) # returns 1 if error - - def bilateral_filter(self, i, output, sigma_dist=0.75, sigma_int=1.0, callback=None): - """A bilateral filter is an edge-preserving smoothing filter introduced by Tomasi and Manduchi (1998). - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - sigma_dist -- Standard deviation in distance in pixels. - sigma_int -- Standard deviation in intensity in pixels. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--sigma_dist={}".format(sigma_dist)) - args.append("--sigma_int={}".format(sigma_int)) - return self.run_tool('bilateral_filter', args, callback) # returns 1 if error - - def conservative_smoothing_filter(self, i, output, filterx=3, filtery=3, callback=None): - """Performs a conservative-smoothing filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('conservative_smoothing_filter', args, callback) # returns 1 if error - - def corner_detection(self, i, output, callback=None): - """Identifies corner patterns in boolean images using hit-and-miss pattern mattching. - - Keyword arguments: - - i -- Input boolean image. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('corner_detection', args, callback) # returns 1 if error - - def diff_of_gaussian_filter(self, i, output, sigma1=2.0, sigma2=4.0, callback=None): - """Performs a Difference of Gaussian (DoG) filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - sigma1 -- Standard deviation distance in pixels. - sigma2 -- Standard deviation distance in pixels. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--sigma1={}".format(sigma1)) - args.append("--sigma2={}".format(sigma2)) - return self.run_tool('diff_of_gaussian_filter', args, callback) # returns 1 if error - - def diversity_filter(self, i, output, filterx=11, filtery=11, callback=None): - """Assigns each cell in the output grid the number of different values in a moving window centred on each grid cell in the input raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('diversity_filter', args, callback) # returns 1 if error - - def edge_preserving_mean_filter(self, i, output, threshold, filter=11, callback=None): - """Performs a simple edge-preserving mean filter on an input image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filter -- Size of the filter kernel. - threshold -- Maximum difference in values. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filter={}".format(filter)) - args.append("--threshold='{}'".format(threshold)) - return self.run_tool('edge_preserving_mean_filter', args, callback) # returns 1 if error - - def emboss_filter(self, i, output, direction="n", clip=0.0, callback=None): - """Performs an emboss filter on an image, similar to a hillshade operation. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - direction -- Direction of reflection; options include 'n', 's', 'e', 'w', 'ne', 'se', 'nw', 'sw'. - clip -- Optional amount to clip the distribution tails by, in percent. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--direction={}".format(direction)) - args.append("--clip={}".format(clip)) - return self.run_tool('emboss_filter', args, callback) # returns 1 if error - - def fast_almost_gaussian_filter(self, i, output, sigma=1.8, callback=None): - """Performs a fast approximate Gaussian filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - sigma -- Standard deviation distance in pixels. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--sigma={}".format(sigma)) - return self.run_tool('fast_almost_gaussian_filter', args, callback) # returns 1 if error - - def gaussian_filter(self, i, output, sigma=0.75, callback=None): - """Performs a Gaussian filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - sigma -- Standard deviation distance in pixels. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--sigma={}".format(sigma)) - return self.run_tool('gaussian_filter', args, callback) # returns 1 if error - - def high_pass_filter(self, i, output, filterx=11, filtery=11, callback=None): - """Performs a high-pass filter on an input image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('high_pass_filter', args, callback) # returns 1 if error - - def high_pass_median_filter(self, i, output, filterx=11, filtery=11, sig_digits=2, callback=None): - """Performs a high pass median filter on an input image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - sig_digits -- Number of significant digits. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - args.append("--sig_digits={}".format(sig_digits)) - return self.run_tool('high_pass_median_filter', args, callback) # returns 1 if error - - def k_nearest_mean_filter(self, i, output, filterx=11, filtery=11, k=5, callback=None): - """A k-nearest mean filter is a type of edge-preserving smoothing filter. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - k -- k-value in pixels; this is the number of nearest-valued neighbours to use. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - args.append("-k={}".format(k)) - return self.run_tool('k_nearest_mean_filter', args, callback) # returns 1 if error - - def laplacian_filter(self, i, output, variant="3x3(1)", clip=0.0, callback=None): - """Performs a Laplacian filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - variant -- Optional variant value. Options include 3x3(1), 3x3(2), 3x3(3), 3x3(4), 5x5(1), and 5x5(2) (default is 3x3(1)). - clip -- Optional amount to clip the distribution tails by, in percent. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--variant={}".format(variant)) - args.append("--clip={}".format(clip)) - return self.run_tool('laplacian_filter', args, callback) # returns 1 if error - - def laplacian_of_gaussian_filter(self, i, output, sigma=0.75, callback=None): - """Performs a Laplacian-of-Gaussian (LoG) filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - sigma -- Standard deviation in pixels. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--sigma={}".format(sigma)) - return self.run_tool('laplacian_of_gaussian_filter', args, callback) # returns 1 if error - - def lee_filter(self, i, output, filterx=11, filtery=11, sigma=10.0, m=5.0, callback=None): - """Performs a Lee (Sigma) smoothing filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - sigma -- Sigma value should be related to the standarad deviation of the distribution of image speckle noise. - m -- M-threshold value the minimum allowable number of pixels within the intensity range. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - args.append("--sigma={}".format(sigma)) - args.append("-m={}".format(m)) - return self.run_tool('lee_filter', args, callback) # returns 1 if error - - def line_detection_filter(self, i, output, variant="vertical", absvals=False, clip=0.0, callback=None): - """Performs a line-detection filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - variant -- Optional variant value. Options include 'v' (vertical), 'h' (horizontal), '45', and '135' (default is 'v'). - absvals -- Optional flag indicating whether outputs should be absolute values. - clip -- Optional amount to clip the distribution tails by, in percent. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--variant={}".format(variant)) - if absvals: args.append("--absvals") - args.append("--clip={}".format(clip)) - return self.run_tool('line_detection_filter', args, callback) # returns 1 if error - - def majority_filter(self, i, output, filterx=11, filtery=11, callback=None): - """Assigns each cell in the output grid the most frequently occurring value (mode) in a moving window centred on each grid cell in the input raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('majority_filter', args, callback) # returns 1 if error - - def maximum_filter(self, i, output, filterx=11, filtery=11, callback=None): - """Assigns each cell in the output grid the maximum value in a moving window centred on each grid cell in the input raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('maximum_filter', args, callback) # returns 1 if error - - def mean_filter(self, i, output, filterx=3, filtery=3, callback=None): - """Performs a mean filter (low-pass filter) on an input image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('mean_filter', args, callback) # returns 1 if error - - def median_filter(self, i, output, filterx=11, filtery=11, sig_digits=2, callback=None): - """Performs a median filter on an input image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - sig_digits -- Number of significant digits. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - args.append("--sig_digits={}".format(sig_digits)) - return self.run_tool('median_filter', args, callback) # returns 1 if error - - def minimum_filter(self, i, output, filterx=11, filtery=11, callback=None): - """Assigns each cell in the output grid the minimum value in a moving window centred on each grid cell in the input raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('minimum_filter', args, callback) # returns 1 if error - - def olympic_filter(self, i, output, filterx=11, filtery=11, callback=None): - """Performs an olympic smoothing filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('olympic_filter', args, callback) # returns 1 if error - - def percentile_filter(self, i, output, filterx=11, filtery=11, sig_digits=2, callback=None): - """Performs a percentile filter on an input image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - sig_digits -- Number of significant digits. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - args.append("--sig_digits={}".format(sig_digits)) - return self.run_tool('percentile_filter', args, callback) # returns 1 if error - - def prewitt_filter(self, i, output, clip=0.0, callback=None): - """Performs a Prewitt edge-detection filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - clip -- Optional amount to clip the distribution tails by, in percent. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--clip={}".format(clip)) - return self.run_tool('prewitt_filter', args, callback) # returns 1 if error - - def range_filter(self, i, output, filterx=11, filtery=11, callback=None): - """Assigns each cell in the output grid the range of values in a moving window centred on each grid cell in the input raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('range_filter', args, callback) # returns 1 if error - - def roberts_cross_filter(self, i, output, clip=0.0, callback=None): - """Performs a Robert's cross edge-detection filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - clip -- Optional amount to clip the distribution tails by, in percent. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--clip={}".format(clip)) - return self.run_tool('roberts_cross_filter', args, callback) # returns 1 if error - - def scharr_filter(self, i, output, clip=0.0, callback=None): - """Performs a Scharr edge-detection filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - clip -- Optional amount to clip the distribution tails by, in percent. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--clip={}".format(clip)) - return self.run_tool('scharr_filter', args, callback) # returns 1 if error - - def sobel_filter(self, i, output, variant="3x3", clip=0.0, callback=None): - """Performs a Sobel edge-detection filter on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - variant -- Optional variant value. Options include 3x3 and 5x5 (default is 3x3). - clip -- Optional amount to clip the distribution tails by, in percent (default is 0.0). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--variant={}".format(variant)) - args.append("--clip={}".format(clip)) - return self.run_tool('sobel_filter', args, callback) # returns 1 if error - - def standard_deviation_filter(self, i, output, filterx=11, filtery=11, callback=None): - """Assigns each cell in the output grid the standard deviation of values in a moving window centred on each grid cell in the input raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('standard_deviation_filter', args, callback) # returns 1 if error - - def total_filter(self, i, output, filterx=11, filtery=11, callback=None): - """Performs a total filter on an input image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - filterx -- Size of the filter kernel in the x-direction. - filtery -- Size of the filter kernel in the y-direction. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--filterx={}".format(filterx)) - args.append("--filtery={}".format(filtery)) - return self.run_tool('total_filter', args, callback) # returns 1 if error - - def unsharp_masking(self, i, output, sigma=0.75, amount=100.0, threshold=0.0, callback=None): - """An image sharpening technique that enhances edges. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - sigma -- Standard deviation distance in pixels. - amount -- A percentage and controls the magnitude of each overshoot. - threshold -- Controls the minimal brightness change that will be sharpened. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--sigma={}".format(sigma)) - args.append("--amount={}".format(amount)) - args.append("--threshold={}".format(threshold)) - return self.run_tool('unsharp_masking', args, callback) # returns 1 if error - - def user_defined_weights_filter(self, i, weights, output, center="center", normalize=False, callback=None): - """Performs a user-defined weights filter on an image. - - Keyword arguments: - - i -- Input raster file. - weights -- Input weights file. - output -- Output raster file. - center -- Kernel center cell; options include 'center', 'upper-left', 'upper-right', 'lower-left', 'lower-right'. - normalize -- Normalize kernel weights? This can reduce edge effects and lessen the impact of data gaps (nodata) but is not suited when the kernel weights sum to zero. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--weights='{}'".format(weights)) - args.append("--output='{}'".format(output)) - args.append("--center={}".format(center)) - if normalize: args.append("--normalize") - return self.run_tool('user_defined_weights_filter', args, callback) # returns 1 if error - - ############################################ - # Image Processing Tools/Image Enhancement # - ############################################ - - def balance_contrast_enhancement(self, i, output, band_mean=100.0, callback=None): - """Performs a balance contrast enhancement on a colour-composite image of multispectral data. - - Keyword arguments: - - i -- Input colour composite image file. - output -- Output raster file. - band_mean -- Band mean value. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--band_mean={}".format(band_mean)) - return self.run_tool('balance_contrast_enhancement', args, callback) # returns 1 if error - - def correct_vignetting(self, i, pp, output, focal_length=304.8, image_width=228.6, n=4.0, callback=None): - """Corrects the darkening of images towards corners. - - Keyword arguments: - - i -- Input raster file. - pp -- Input principal point file. - output -- Output raster file. - focal_length -- Camera focal length, in millimeters. - image_width -- Distance between photograph edges, in millimeters. - n -- The 'n' parameter. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--pp='{}'".format(pp)) - args.append("--output='{}'".format(output)) - args.append("--focal_length={}".format(focal_length)) - args.append("--image_width={}".format(image_width)) - args.append("-n={}".format(n)) - return self.run_tool('correct_vignetting', args, callback) # returns 1 if error - - def direct_decorrelation_stretch(self, i, output, k=0.5, clip=1.0, callback=None): - """Performs a direct decorrelation stretch enhancement on a colour-composite image of multispectral data. - - Keyword arguments: - - i -- Input colour composite image file. - output -- Output raster file. - k -- Achromatic factor (k) ranges between 0 (no effect) and 1 (full saturation stretch), although typical values range from 0.3 to 0.7. - clip -- Optional percent to clip the upper tail by during the stretch. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("-k={}".format(k)) - args.append("--clip={}".format(clip)) - return self.run_tool('direct_decorrelation_stretch', args, callback) # returns 1 if error - - def gamma_correction(self, i, output, gamma=0.5, callback=None): - """Performs a gamma correction on an input images. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - gamma -- Gamma value. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--gamma={}".format(gamma)) - return self.run_tool('gamma_correction', args, callback) # returns 1 if error - - def gaussian_contrast_stretch(self, i, output, num_tones=256, callback=None): - """Performs a Gaussian contrast stretch on input images. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - num_tones -- Number of tones in the output image. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--num_tones={}".format(num_tones)) - return self.run_tool('gaussian_contrast_stretch', args, callback) # returns 1 if error - - def histogram_equalization(self, i, output, num_tones=256, callback=None): - """Performs a histogram equalization contrast enhancment on an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - num_tones -- Number of tones in the output image. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--num_tones={}".format(num_tones)) - return self.run_tool('histogram_equalization', args, callback) # returns 1 if error - - def histogram_matching(self, i, histo_file, output, callback=None): - """Alters the statistical distribution of a raster image matching it to a specified PDF. - - Keyword arguments: - - i -- Input raster file. - histo_file -- Input reference probability distribution function (pdf) text file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--histo_file='{}'".format(histo_file)) - args.append("--output='{}'".format(output)) - return self.run_tool('histogram_matching', args, callback) # returns 1 if error - - def histogram_matching_two_images(self, input1, input2, output, callback=None): - """This tool alters the cumulative distribution function of a raster image to that of another image. - - Keyword arguments: - - input1 -- Input raster file to modify. - input2 -- Input reference raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('histogram_matching_two_images', args, callback) # returns 1 if error - - def min_max_contrast_stretch(self, i, output, min_val, max_val, num_tones=256, callback=None): - """Performs a min-max contrast stretch on an input greytone image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - min_val -- Lower tail clip value. - max_val -- Upper tail clip value. - num_tones -- Number of tones in the output image. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--min_val='{}'".format(min_val)) - args.append("--max_val='{}'".format(max_val)) - args.append("--num_tones={}".format(num_tones)) - return self.run_tool('min_max_contrast_stretch', args, callback) # returns 1 if error - - def panchromatic_sharpening(self, pan, output, red=None, green=None, blue=None, composite=None, method="brovey", callback=None): - """Increases the spatial resolution of image data by combining multispectral bands with panchromatic data. - - Keyword arguments: - - red -- Input red band image file. Optionally specified if colour-composite not specified. - green -- Input green band image file. Optionally specified if colour-composite not specified. - blue -- Input blue band image file. Optionally specified if colour-composite not specified. - composite -- Input colour-composite image file. Only used if individual bands are not specified. - pan -- Input panchromatic band file. - output -- Output colour composite file. - method -- Options include 'brovey' (default) and 'ihs'. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if red is not None: args.append("--red='{}'".format(red)) - if green is not None: args.append("--green='{}'".format(green)) - if blue is not None: args.append("--blue='{}'".format(blue)) - if composite is not None: args.append("--composite='{}'".format(composite)) - args.append("--pan='{}'".format(pan)) - args.append("--output='{}'".format(output)) - args.append("--method={}".format(method)) - return self.run_tool('panchromatic_sharpening', args, callback) # returns 1 if error - - def percentage_contrast_stretch(self, i, output, clip=1.0, tail="both", num_tones=256, callback=None): - """Performs a percentage linear contrast stretch on input images. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - clip -- Optional amount to clip the distribution tails by, in percent. - tail -- Specified which tails to clip; options include 'upper', 'lower', and 'both' (default is 'both'). - num_tones -- Number of tones in the output image. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--clip={}".format(clip)) - args.append("--tail={}".format(tail)) - args.append("--num_tones={}".format(num_tones)) - return self.run_tool('percentage_contrast_stretch', args, callback) # returns 1 if error - - def sigmoidal_contrast_stretch(self, i, output, cutoff=0.0, gain=1.0, num_tones=256, callback=None): - """Performs a sigmoidal contrast stretch on input images. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - cutoff -- Cutoff value between 0.0 and 0.95. - gain -- Gain value. - num_tones -- Number of tones in the output image. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--cutoff={}".format(cutoff)) - args.append("--gain={}".format(gain)) - args.append("--num_tones={}".format(num_tones)) - return self.run_tool('sigmoidal_contrast_stretch', args, callback) # returns 1 if error - - def standard_deviation_contrast_stretch(self, i, output, stdev=2.0, num_tones=256, callback=None): - """Performs a standard-deviation contrast stretch on input images. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - stdev -- Standard deviation clip value. - num_tones -- Number of tones in the output image. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--stdev={}".format(stdev)) - args.append("--num_tones={}".format(num_tones)) - return self.run_tool('standard_deviation_contrast_stretch', args, callback) # returns 1 if error - - ############### - # LiDAR Tools # - ############### - - def classify_overlap_points(self, i, output, resolution=2.0, filter=False, callback=None): - """Classifies or filters LAS points in regions of overlapping flight lines. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output LiDAR file. - resolution -- The size of the square area used to evaluate nearby points in the LiDAR data. - filter -- Filter out points from overlapping flightlines? If false, overlaps will simply be classified. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--resolution={}".format(resolution)) - if filter: args.append("--filter") - return self.run_tool('classify_overlap_points', args, callback) # returns 1 if error - - def clip_lidar_to_polygon(self, i, polygons, output, callback=None): - """Clips a LiDAR point cloud to a vector polygon or polygons. - - Keyword arguments: - - i -- Input LiDAR file. - polygons -- Input vector polygons file. - output -- Output LiDAR file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--polygons='{}'".format(polygons)) - args.append("--output='{}'".format(output)) - return self.run_tool('clip_lidar_to_polygon', args, callback) # returns 1 if error - - def erase_polygon_from_lidar(self, i, polygons, output, callback=None): - """Erases (cuts out) a vector polygon or polygons from a LiDAR point cloud. - - Keyword arguments: - - i -- Input LiDAR file. - polygons -- Input vector polygons file. - output -- Output LiDAR file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--polygons='{}'".format(polygons)) - args.append("--output='{}'".format(output)) - return self.run_tool('erase_polygon_from_lidar', args, callback) # returns 1 if error - - def filter_lidar_scan_angles(self, i, output, threshold, callback=None): - """Removes points in a LAS file with scan angles greater than a threshold. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output LiDAR file. - threshold -- Scan angle threshold. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--threshold='{}'".format(threshold)) - return self.run_tool('filter_lidar_scan_angles', args, callback) # returns 1 if error - - def find_flightline_edge_points(self, i, output, callback=None): - """Identifies points along a flightline's edge in a LAS file. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('find_flightline_edge_points', args, callback) # returns 1 if error - - def flightline_overlap(self, i=None, output=None, resolution=1.0, callback=None): - """Reads a LiDAR (LAS) point file and outputs a raster containing the number of overlapping flight lines in each grid cell. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output file. - resolution -- Output raster's grid resolution. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if i is not None: args.append("--input='{}'".format(i)) - if output is not None: args.append("--output='{}'".format(output)) - args.append("--resolution={}".format(resolution)) - return self.run_tool('flightline_overlap', args, callback) # returns 1 if error - - def las_to_ascii(self, inputs, callback=None): - """Converts one or more LAS files into ASCII text files. - - Keyword arguments: - - inputs -- Input LiDAR files. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - return self.run_tool('las_to_ascii', args, callback) # returns 1 if error - - def las_to_multipoint_shapefile(self, i=None, callback=None): - """Converts one or more LAS files into MultipointZ vector Shapefiles. When the input parameter is not specified, the tool grids all LAS files contained within the working directory. - - Keyword arguments: - - i -- Input LiDAR file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if i is not None: args.append("--input='{}'".format(i)) - return self.run_tool('las_to_multipoint_shapefile', args, callback) # returns 1 if error - - def las_to_shapefile(self, i=None, callback=None): - """Converts one or more LAS files into a vector Shapefile of POINT ShapeType. - - Keyword arguments: - - i -- Input LiDAR file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if i is not None: args.append("--input='{}'".format(i)) - return self.run_tool('las_to_shapefile', args, callback) # returns 1 if error - - def lidar_block_maximum(self, i=None, output=None, resolution=1.0, callback=None): - """Creates a block-maximum raster from an input LAS file. When the input/output parameters are not specified, the tool grids all LAS files contained within the working directory. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output file. - resolution -- Output raster's grid resolution. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if i is not None: args.append("--input='{}'".format(i)) - if output is not None: args.append("--output='{}'".format(output)) - args.append("--resolution={}".format(resolution)) - return self.run_tool('lidar_block_maximum', args, callback) # returns 1 if error - - def lidar_block_minimum(self, i=None, output=None, resolution=1.0, callback=None): - """Creates a block-minimum raster from an input LAS file. When the input/output parameters are not specified, the tool grids all LAS files contained within the working directory. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output file. - resolution -- Output raster's grid resolution. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if i is not None: args.append("--input='{}'".format(i)) - if output is not None: args.append("--output='{}'".format(output)) - args.append("--resolution={}".format(resolution)) - return self.run_tool('lidar_block_minimum', args, callback) # returns 1 if error - - def lidar_classify_subset(self, base, subset, output, subset_class, nonsubset_class=None, callback=None): - """Classifies the values in one LiDAR point cloud that correpond with points in a subset cloud. - - Keyword arguments: - - base -- Input base LiDAR file. - subset -- Input subset LiDAR file. - output -- Output LiDAR file. - subset_class -- Subset point class value (must be 0-18; see LAS specifications). - nonsubset_class -- Non-subset point class value (must be 0-18; see LAS specifications). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--base='{}'".format(base)) - args.append("--subset='{}'".format(subset)) - args.append("--output='{}'".format(output)) - args.append("--subset_class='{}'".format(subset_class)) - if nonsubset_class is not None: args.append("--nonsubset_class='{}'".format(nonsubset_class)) - return self.run_tool('lidar_classify_subset', args, callback) # returns 1 if error - - def lidar_colourize(self, in_lidar, in_image, output, callback=None): - """Adds the red-green-blue colour fields of a LiDAR (LAS) file based on an input image. - - Keyword arguments: - - in_lidar -- Input LiDAR file. - in_image -- Input colour image file. - output -- Output LiDAR file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--in_lidar='{}'".format(in_lidar)) - args.append("--in_image='{}'".format(in_image)) - args.append("--output='{}'".format(output)) - return self.run_tool('lidar_colourize', args, callback) # returns 1 if error - - def lidar_construct_vector_tin(self, i=None, output=None, returns="all", exclude_cls=None, minz=None, maxz=None, callback=None): - """Creates a vector triangular irregular network (TIN) fitted to LiDAR points. - - Keyword arguments: - - i -- Input LiDAR file (including extension). - output -- Output raster file (including extension). - returns -- Point return types to include; options are 'all' (default), 'last', 'first'. - exclude_cls -- Optional exclude classes from interpolation; Valid class values range from 0 to 18, based on LAS specifications. Example, --exclude_cls='3,4,5,6,7,18'. - minz -- Optional minimum elevation for inclusion in interpolation. - maxz -- Optional maximum elevation for inclusion in interpolation. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if i is not None: args.append("--input='{}'".format(i)) - if output is not None: args.append("--output='{}'".format(output)) - args.append("--returns={}".format(returns)) - if exclude_cls is not None: args.append("--exclude_cls='{}'".format(exclude_cls)) - if minz is not None: args.append("--minz='{}'".format(minz)) - if maxz is not None: args.append("--maxz='{}'".format(maxz)) - return self.run_tool('lidar_construct_vector_tin', args, callback) # returns 1 if error - - def lidar_elevation_slice(self, i, output, minz=None, maxz=None, cls=False, inclassval=2, outclassval=1, callback=None): - """Outputs all of the points within a LiDAR (LAS) point file that lie between a specified elevation range. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output LiDAR file. - minz -- Minimum elevation value (optional). - maxz -- Maximum elevation value (optional). - cls -- Optional boolean flag indicating whether points outside the range should be retained in output but reclassified. - inclassval -- Optional parameter specifying the class value assigned to points within the slice. - outclassval -- Optional parameter specifying the class value assigned to points within the slice. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - if minz is not None: args.append("--minz='{}'".format(minz)) - if maxz is not None: args.append("--maxz='{}'".format(maxz)) - if cls: args.append("--class") - args.append("--inclassval={}".format(inclassval)) - args.append("--outclassval={}".format(outclassval)) - return self.run_tool('lidar_elevation_slice', args, callback) # returns 1 if error - - def lidar_ground_point_filter(self, i, output, radius=2.0, min_neighbours=0, slope_threshold=45.0, height_threshold=1.0, classify=True, slope_norm=True, callback=None): - """Identifies ground points within LiDAR dataset using a slope-based method. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output LiDAR file. - radius -- Search Radius. - min_neighbours -- The minimum number of neighbouring points within search areas. If fewer points than this threshold are idenfied during the fixed-radius search, a subsequent kNN search is performed to identify the k number of neighbours. - slope_threshold -- Maximum inter-point slope to be considered an off-terrain point. - height_threshold -- Inter-point height difference to be considered an off-terrain point. - classify -- Classify points as ground (2) or off-ground (1). - slope_norm -- Perform initial ground slope normalization?. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--radius={}".format(radius)) - args.append("--min_neighbours={}".format(min_neighbours)) - args.append("--slope_threshold={}".format(slope_threshold)) - args.append("--height_threshold={}".format(height_threshold)) - if classify: args.append("--classify") - if slope_norm: args.append("--slope_norm") - return self.run_tool('lidar_ground_point_filter', args, callback) # returns 1 if error - - def lidar_hex_binning(self, i, output, width, orientation="horizontal", callback=None): - """Hex-bins a set of LiDAR points. - - Keyword arguments: - - i -- Input base file. - output -- Output vector polygon file. - width -- The grid cell width. - orientation -- Grid Orientation, 'horizontal' or 'vertical'. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--width='{}'".format(width)) - args.append("--orientation={}".format(orientation)) - return self.run_tool('lidar_hex_binning', args, callback) # returns 1 if error - - def lidar_hillshade(self, i, output, azimuth=315.0, altitude=30.0, radius=1.0, callback=None): - """Calculates a hillshade value for points within a LAS file and stores these data in the RGB field. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output file. - azimuth -- Illumination source azimuth in degrees. - altitude -- Illumination source altitude in degrees. - radius -- Search Radius. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--azimuth={}".format(azimuth)) - args.append("--altitude={}".format(altitude)) - args.append("--radius={}".format(radius)) - return self.run_tool('lidar_hillshade', args, callback) # returns 1 if error - - def lidar_histogram(self, i, output, parameter="elevation", clip=1.0, callback=None): - """Creates a histogram of LiDAR data. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output HTML file (default name will be based on input file if unspecified). - parameter -- Parameter; options are 'elevation' (default), 'intensity', 'scan angle', 'class'. - clip -- Amount to clip distribution tails (in percent). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--parameter={}".format(parameter)) - args.append("--clip={}".format(clip)) - return self.run_tool('lidar_histogram', args, callback) # returns 1 if error - - def lidar_idw_interpolation(self, i=None, output=None, parameter="elevation", returns="all", resolution=1.0, weight=1.0, radius=2.5, exclude_cls=None, minz=None, maxz=None, callback=None): - """Interpolates LAS files using an inverse-distance weighted (IDW) scheme. When the input/output parameters are not specified, the tool interpolates all LAS files contained within the working directory. - - Keyword arguments: - - i -- Input LiDAR file (including extension). - output -- Output raster file (including extension). - parameter -- Interpolation parameter; options are 'elevation' (default), 'intensity', 'class', 'scan angle', 'user data'. - returns -- Point return types to include; options are 'all' (default), 'last', 'first'. - resolution -- Output raster's grid resolution. - weight -- IDW weight value. - radius -- Search Radius. - exclude_cls -- Optional exclude classes from interpolation; Valid class values range from 0 to 18, based on LAS specifications. Example, --exclude_cls='3,4,5,6,7,18'. - minz -- Optional minimum elevation for inclusion in interpolation. - maxz -- Optional maximum elevation for inclusion in interpolation. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if i is not None: args.append("--input='{}'".format(i)) - if output is not None: args.append("--output='{}'".format(output)) - args.append("--parameter={}".format(parameter)) - args.append("--returns={}".format(returns)) - args.append("--resolution={}".format(resolution)) - args.append("--weight={}".format(weight)) - args.append("--radius={}".format(radius)) - if exclude_cls is not None: args.append("--exclude_cls='{}'".format(exclude_cls)) - if minz is not None: args.append("--minz='{}'".format(minz)) - if maxz is not None: args.append("--maxz='{}'".format(maxz)) - return self.run_tool('lidar_idw_interpolation', args, callback) # returns 1 if error - - def lidar_info(self, i, output=None, vlr=False, geokeys=False, callback=None): - """Prints information about a LiDAR (LAS) dataset, including header, point return frequency, and classification data and information about the variable length records (VLRs) and geokeys. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output HTML file for summary report. - vlr -- Flag indicating whether or not to print the variable length records (VLRs). - geokeys -- Flag indicating whether or not to print the geokeys. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - if output is not None: args.append("--output='{}'".format(output)) - if vlr: args.append("--vlr") - if geokeys: args.append("--geokeys") - return self.run_tool('lidar_info', args, callback) # returns 1 if error - - def lidar_join(self, inputs, output, callback=None): - """Joins multiple LiDAR (LAS) files into a single LAS file. - - Keyword arguments: - - inputs -- Input LiDAR files. - output -- Output LiDAR file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - return self.run_tool('lidar_join', args, callback) # returns 1 if error - - def lidar_kappa_index(self, input1, input2, output, class_accuracy, resolution=1.0, callback=None): - """Performs a kappa index of agreement (KIA) analysis on the classifications of two LAS files. - - Keyword arguments: - - input1 -- Input LiDAR classification file. - input2 -- Input LiDAR reference file. - output -- Output HTML file. - class_accuracy -- Output classification accuracy raster file. - resolution -- Output raster's grid resolution. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - args.append("--class_accuracy='{}'".format(class_accuracy)) - args.append("--resolution={}".format(resolution)) - return self.run_tool('lidar_kappa_index', args, callback) # returns 1 if error - - def lidar_nearest_neighbour_gridding(self, i=None, output=None, parameter="elevation", returns="all", resolution=1.0, radius=2.5, exclude_cls=None, minz=None, maxz=None, callback=None): - """Grids LAS files using nearest-neighbour scheme. When the input/output parameters are not specified, the tool grids all LAS files contained within the working directory. - - Keyword arguments: - - i -- Input LiDAR file (including extension). - output -- Output raster file (including extension). - parameter -- Interpolation parameter; options are 'elevation' (default), 'intensity', 'class', 'scan angle', 'user data'. - returns -- Point return types to include; options are 'all' (default), 'last', 'first'. - resolution -- Output raster's grid resolution. - radius -- Search Radius. - exclude_cls -- Optional exclude classes from interpolation; Valid class values range from 0 to 18, based on LAS specifications. Example, --exclude_cls='3,4,5,6,7,18'. - minz -- Optional minimum elevation for inclusion in interpolation. - maxz -- Optional maximum elevation for inclusion in interpolation. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if i is not None: args.append("--input='{}'".format(i)) - if output is not None: args.append("--output='{}'".format(output)) - args.append("--parameter={}".format(parameter)) - args.append("--returns={}".format(returns)) - args.append("--resolution={}".format(resolution)) - args.append("--radius={}".format(radius)) - if exclude_cls is not None: args.append("--exclude_cls='{}'".format(exclude_cls)) - if minz is not None: args.append("--minz='{}'".format(minz)) - if maxz is not None: args.append("--maxz='{}'".format(maxz)) - return self.run_tool('lidar_nearest_neighbour_gridding', args, callback) # returns 1 if error - - def lidar_point_density(self, i=None, output=None, returns="all", resolution=1.0, radius=2.5, exclude_cls=None, minz=None, maxz=None, callback=None): - """Calculates the spatial pattern of point density for a LiDAR data set. When the input/output parameters are not specified, the tool grids all LAS files contained within the working directory. - - Keyword arguments: - - i -- Input LiDAR file (including extension). - output -- Output raster file (including extension). - returns -- Point return types to include; options are 'all' (default), 'last', 'first'. - resolution -- Output raster's grid resolution. - radius -- Search Radius. - exclude_cls -- Optional exclude classes from interpolation; Valid class values range from 0 to 18, based on LAS specifications. Example, --exclude_cls='3,4,5,6,7,18'. - minz -- Optional minimum elevation for inclusion in interpolation. - maxz -- Optional maximum elevation for inclusion in interpolation. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if i is not None: args.append("--input='{}'".format(i)) - if output is not None: args.append("--output='{}'".format(output)) - args.append("--returns={}".format(returns)) - args.append("--resolution={}".format(resolution)) - args.append("--radius={}".format(radius)) - if exclude_cls is not None: args.append("--exclude_cls='{}'".format(exclude_cls)) - if minz is not None: args.append("--minz='{}'".format(minz)) - if maxz is not None: args.append("--maxz='{}'".format(maxz)) - return self.run_tool('lidar_point_density', args, callback) # returns 1 if error - - def lidar_point_stats(self, i=None, resolution=1.0, num_points=True, num_pulses=False, z_range=False, intensity_range=False, predom_class=False, callback=None): - """Creates several rasters summarizing the distribution of LAS point data. When the input/output parameters are not specified, the tool works on all LAS files contained within the working directory. - - Keyword arguments: - - i -- Input LiDAR file. - resolution -- Output raster's grid resolution. - num_points -- Flag indicating whether or not to output the number of points raster. - num_pulses -- Flag indicating whether or not to output the number of pulses raster. - z_range -- Flag indicating whether or not to output the elevation range raster. - intensity_range -- Flag indicating whether or not to output the intensity range raster. - predom_class -- Flag indicating whether or not to output the predominant classification raster. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if i is not None: args.append("--input='{}'".format(i)) - args.append("--resolution={}".format(resolution)) - if num_points: args.append("--num_points") - if num_pulses: args.append("--num_pulses") - if z_range: args.append("--z_range") - if intensity_range: args.append("--intensity_range") - if predom_class: args.append("--predom_class") - return self.run_tool('lidar_point_stats', args, callback) # returns 1 if error - - def lidar_remove_duplicates(self, i, output, include_z=False, callback=None): - """Removes duplicate points from a LiDAR data set. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output LiDAR file. - include_z -- Include z-values in point comparison?. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - if include_z: args.append("--include_z") - return self.run_tool('lidar_remove_duplicates', args, callback) # returns 1 if error - - def lidar_remove_outliers(self, i, output, radius=2.0, elev_diff=50.0, callback=None): - """Removes outliers (high and low points) in a LiDAR point cloud. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output LiDAR file. - radius -- Search Radius. - elev_diff -- Max. elevation difference. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--radius={}".format(radius)) - args.append("--elev_diff={}".format(elev_diff)) - return self.run_tool('lidar_remove_outliers', args, callback) # returns 1 if error - - def lidar_segmentation(self, i, output, radius=5.0, norm_diff=10.0, maxzdiff=1.0, callback=None): - """Segments a LiDAR point cloud based on normal vectors. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output file. - radius -- Search Radius. - norm_diff -- Maximum difference in normal vectors, in degrees. - maxzdiff -- Maximum difference in elevation (z units) between neighbouring points of the same segment. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--radius={}".format(radius)) - args.append("--norm_diff={}".format(norm_diff)) - args.append("--maxzdiff={}".format(maxzdiff)) - return self.run_tool('lidar_segmentation', args, callback) # returns 1 if error - - def lidar_segmentation_based_filter(self, i, output, radius=5.0, norm_diff=2.0, maxzdiff=1.0, classify=False, callback=None): - """Identifies ground points within LiDAR point clouds using a segmentation based approach. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output file. - radius -- Search Radius. - norm_diff -- Maximum difference in normal vectors, in degrees. - maxzdiff -- Maximum difference in elevation (z units) between neighbouring points of the same segment. - classify -- Classify points as ground (2) or off-ground (1). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--radius={}".format(radius)) - args.append("--norm_diff={}".format(norm_diff)) - args.append("--maxzdiff={}".format(maxzdiff)) - if classify: args.append("--classify") - return self.run_tool('lidar_segmentation_based_filter', args, callback) # returns 1 if error - - def lidar_thin(self, i, output, resolution=2.0, method="lowest", save_filtered=False, callback=None): - """Thins a LiDAR point cloud, reducing point density. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output LiDAR file. - resolution -- The size of the square area used to evaluate nearby points in the LiDAR data. - method -- Point selection method; options are 'first', 'last', 'lowest' (default), 'highest', 'nearest'. - save_filtered -- Save filtered points to seperate file?. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--resolution={}".format(resolution)) - args.append("--method={}".format(method)) - if save_filtered: args.append("--save_filtered") - return self.run_tool('lidar_thin', args, callback) # returns 1 if error - - def lidar_thin_high_density(self, i, output, density, resolution=1.0, save_filtered=False, callback=None): - """Thins points from high density areas within a LiDAR point cloud. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output LiDAR file. - resolution -- Output raster's grid resolution. - density -- Max. point density (points / m^3). - save_filtered -- Save filtered points to seperate file?. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--resolution={}".format(resolution)) - args.append("--density='{}'".format(density)) - if save_filtered: args.append("--save_filtered") - return self.run_tool('lidar_thin_high_density', args, callback) # returns 1 if error - - def lidar_tile(self, i, width=1000.0, height=1000.0, origin_x=0.0, origin_y=0.0, min_points=2, callback=None): - """Tiles a LiDAR LAS file into multiple LAS files. - - Keyword arguments: - - i -- Input LiDAR file. - width -- Width of tiles in the X dimension; default 1000.0. - height -- Height of tiles in the Y dimension. - origin_x -- Origin point X coordinate for tile grid. - origin_y -- Origin point Y coordinate for tile grid. - min_points -- Minimum number of points contained in a tile for it to be saved. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--width={}".format(width)) - args.append("--height={}".format(height)) - args.append("--origin_x={}".format(origin_x)) - args.append("--origin_y={}".format(origin_y)) - args.append("--min_points={}".format(min_points)) - return self.run_tool('lidar_tile', args, callback) # returns 1 if error - - def lidar_tile_footprint(self, output, i=None, callback=None): - """Creates a vector polygon of the convex hull of a LiDAR point cloud. When the input/output parameters are not specified, the tool works with all LAS files contained within the working directory. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output vector polygon file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if i is not None: args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('lidar_tile_footprint', args, callback) # returns 1 if error - - def lidar_tin_gridding(self, i=None, output=None, parameter="elevation", returns="all", resolution=1.0, exclude_cls=None, minz=None, maxz=None, max_triangle_edge_length=None, callback=None): - """Creates a raster grid based on a Delaunay triangular irregular network (TIN) fitted to LiDAR points. - - Keyword arguments: - - i -- Input LiDAR file (including extension). - output -- Output raster file (including extension). - parameter -- Interpolation parameter; options are 'elevation' (default), 'intensity', 'class', 'scan angle', 'user data'. - returns -- Point return types to include; options are 'all' (default), 'last', 'first'. - resolution -- Output raster's grid resolution. - exclude_cls -- Optional exclude classes from interpolation; Valid class values range from 0 to 18, based on LAS specifications. Example, --exclude_cls='3,4,5,6,7,18'. - minz -- Optional minimum elevation for inclusion in interpolation. - maxz -- Optional maximum elevation for inclusion in interpolation. - max_triangle_edge_length -- Optional maximum triangle edge length; triangles larger than this size will not be gridded. - callback -- Custom function for handling tool text outputs. - """ - args = [] - if i is not None: args.append("--input='{}'".format(i)) - if output is not None: args.append("--output='{}'".format(output)) - args.append("--parameter={}".format(parameter)) - args.append("--returns={}".format(returns)) - args.append("--resolution={}".format(resolution)) - if exclude_cls is not None: args.append("--exclude_cls='{}'".format(exclude_cls)) - if minz is not None: args.append("--minz='{}'".format(minz)) - if maxz is not None: args.append("--maxz='{}'".format(maxz)) - if max_triangle_edge_length is not None: args.append("--max_triangle_edge_length='{}'".format(max_triangle_edge_length)) - return self.run_tool('lidar_tin_gridding', args, callback) # returns 1 if error - - def lidar_tophat_transform(self, i, output, radius=1.0, callback=None): - """Performs a white top-hat transform on a Lidar dataset; as an estimate of height above ground, this is useful for modelling the vegetation canopy. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output LiDAR file. - radius -- Search Radius. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--radius={}".format(radius)) - return self.run_tool('lidar_tophat_transform', args, callback) # returns 1 if error - - def normal_vectors(self, i, output, radius=1.0, callback=None): - """Calculates normal vectors for points within a LAS file and stores these data (XYZ vector components) in the RGB field. - - Keyword arguments: - - i -- Input LiDAR file. - output -- Output LiDAR file. - radius -- Search Radius. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--radius={}".format(radius)) - return self.run_tool('normal_vectors', args, callback) # returns 1 if error - - def select_tiles_by_polygon(self, indir, outdir, polygons, callback=None): - """Copies LiDAR tiles overlapping with a polygon into an output directory. - - Keyword arguments: - - indir -- Input LAS file source directory. - outdir -- Output directory into which LAS files within the polygon are copied. - polygons -- Input vector polygons file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--indir='{}'".format(indir)) - args.append("--outdir='{}'".format(outdir)) - args.append("--polygons='{}'".format(polygons)) - return self.run_tool('select_tiles_by_polygon', args, callback) # returns 1 if error - - ######################## - # Math and Stats Tools # - ######################## - - def And(self, input1, input2, output, callback=None): - """Performs a logical AND operator on two Boolean raster images. - - Keyword arguments: - - input1 -- Input raster file. - input2 -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('and', args, callback) # returns 1 if error - - def Not(self, input1, input2, output, callback=None): - """Performs a logical NOT operator on two Boolean raster images. - - Keyword arguments: - - input1 -- Input raster file. - input2 -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('not', args, callback) # returns 1 if error - - def Or(self, input1, input2, output, callback=None): - """Performs a logical OR operator on two Boolean raster images. - - Keyword arguments: - - input1 -- Input raster file. - input2 -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('or', args, callback) # returns 1 if error - - def absolute_value(self, i, output, callback=None): - """Calculates the absolute value of every cell in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('absolute_value', args, callback) # returns 1 if error - - def add(self, input1, input2, output, callback=None): - """Performs an addition operation on two rasters or a raster and a constant value. - - Keyword arguments: - - input1 -- Input raster file or constant value. - input2 -- Input raster file or constant value. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('add', args, callback) # returns 1 if error - - def anova(self, i, features, output, callback=None): - """Performs an analysis of variance (ANOVA) test on a raster dataset. - - Keyword arguments: - - i -- Input raster file. - features -- Feature definition (or class) raster. - output -- Output HTML file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--features='{}'".format(features)) - args.append("--output='{}'".format(output)) - return self.run_tool('anova', args, callback) # returns 1 if error - - def arc_cos(self, i, output, callback=None): - """Returns the inverse cosine (arccos) of each values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('arc_cos', args, callback) # returns 1 if error - - def arc_sin(self, i, output, callback=None): - """Returns the inverse sine (arcsin) of each values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('arc_sin', args, callback) # returns 1 if error - - def arc_tan(self, i, output, callback=None): - """Returns the inverse tangent (arctan) of each values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('arc_tan', args, callback) # returns 1 if error - - def atan2(self, input_y, input_x, output, callback=None): - """Returns the 2-argument inverse tangent (atan2). - - Keyword arguments: - - input_y -- Input y raster file or constant value (rise). - input_x -- Input x raster file or constant value (run). - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input_y='{}'".format(input_y)) - args.append("--input_x='{}'".format(input_x)) - args.append("--output='{}'".format(output)) - return self.run_tool('atan2', args, callback) # returns 1 if error - - def attribute_correlation(self, i, output=None, callback=None): - """Performs a correlation analysis on attribute fields from a vector database. - - Keyword arguments: - - i -- Input raster file. - output -- Output HTML file (default name will be based on input file if unspecified). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - if output is not None: args.append("--output='{}'".format(output)) - return self.run_tool('attribute_correlation', args, callback) # returns 1 if error - - def attribute_histogram(self, i, field, output, callback=None): - """Creates a histogram for the field values of a vector's attribute table. - - Keyword arguments: - - i -- Input raster file. - field -- Input field name in attribute table. - output -- Output HTML file (default name will be based on input file if unspecified). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--field='{}'".format(field)) - args.append("--output='{}'".format(output)) - return self.run_tool('attribute_histogram', args, callback) # returns 1 if error - - def attribute_scattergram(self, i, fieldx, fieldy, output, trendline=False, callback=None): - """Creates a scattergram for two field values of a vector's attribute table. - - Keyword arguments: - - i -- Input raster file. - fieldx -- Input field name in attribute table for the x-axis. - fieldy -- Input field name in attribute table for the y-axis. - output -- Output HTML file (default name will be based on input file if unspecified). - trendline -- Draw the trendline. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--fieldx='{}'".format(fieldx)) - args.append("--fieldy='{}'".format(fieldy)) - args.append("--output='{}'".format(output)) - if trendline: args.append("--trendline") - return self.run_tool('attribute_scattergram', args, callback) # returns 1 if error - - def ceil(self, i, output, callback=None): - """Returns the smallest (closest to negative infinity) value that is greater than or equal to the values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('ceil', args, callback) # returns 1 if error - - def cos(self, i, output, callback=None): - """Returns the cosine (cos) of each values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('cos', args, callback) # returns 1 if error - - def cosh(self, i, output, callback=None): - """Returns the hyperbolic cosine (cosh) of each values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('cosh', args, callback) # returns 1 if error - - def crispness_index(self, i, output=None, callback=None): - """Calculates the Crispness Index, which is used to quantify how crisp (or conversely how fuzzy) a probability image is. - - Keyword arguments: - - i -- Input raster file. - output -- Optional output html file (default name will be based on input file if unspecified). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - if output is not None: args.append("--output='{}'".format(output)) - return self.run_tool('crispness_index', args, callback) # returns 1 if error - - def cross_tabulation(self, input1, input2, output, callback=None): - """Performs a cross-tabulation on two categorical images. - - Keyword arguments: - - input1 -- Input raster file 1. - input2 -- Input raster file 1. - output -- Output HTML file (default name will be based on input file if unspecified). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('cross_tabulation', args, callback) # returns 1 if error - - def cumulative_distribution(self, i, output, callback=None): - """Converts a raster image to its cumulative distribution function. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('cumulative_distribution', args, callback) # returns 1 if error - - def decrement(self, i, output, callback=None): - """Decreases the values of each grid cell in an input raster by 1.0 (see also InPlaceSubtract). - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('decrement', args, callback) # returns 1 if error - - def divide(self, input1, input2, output, callback=None): - """Performs a division operation on two rasters or a raster and a constant value. - - Keyword arguments: - - input1 -- Input raster file or constant value. - input2 -- Input raster file or constant value. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('divide', args, callback) # returns 1 if error - - def equal_to(self, input1, input2, output, callback=None): - """Performs a equal-to comparison operation on two rasters or a raster and a constant value. - - Keyword arguments: - - input1 -- Input raster file or constant value. - input2 -- Input raster file or constant value. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('equal_to', args, callback) # returns 1 if error - - def exp(self, i, output, callback=None): - """Returns the exponential (base e) of values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('exp', args, callback) # returns 1 if error - - def exp2(self, i, output, callback=None): - """Returns the exponential (base 2) of values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('exp2', args, callback) # returns 1 if error - - def extract_raster_statistics(self, i, features, output=None, stat="average", out_table=None, callback=None): - """Extracts descriptive statistics for a group of patches in a raster. - - Keyword arguments: - - i -- Input data raster file. - features -- Input feature definition raster file. - output -- Output raster file. - stat -- Statistic to extract, including 'average', 'minimum', 'maximum', 'range', 'standard deviation', and 'total'. - out_table -- Output HTML Table file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--features='{}'".format(features)) - if output is not None: args.append("--output='{}'".format(output)) - args.append("--stat={}".format(stat)) - if out_table is not None: args.append("--out_table='{}'".format(out_table)) - return self.run_tool('extract_raster_statistics', args, callback) # returns 1 if error - - def floor(self, i, output, callback=None): - """Returns the largest (closest to positive infinity) value that is less than or equal to the values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('floor', args, callback) # returns 1 if error - - def greater_than(self, input1, input2, output, incl_equals=False, callback=None): - """Performs a greater-than comparison operation on two rasters or a raster and a constant value. - - Keyword arguments: - - input1 -- Input raster file or constant value. - input2 -- Input raster file or constant value. - output -- Output raster file. - incl_equals -- Perform a greater-than-or-equal-to operation. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - if incl_equals: args.append("--incl_equals") - return self.run_tool('greater_than', args, callback) # returns 1 if error - - def image_autocorrelation(self, inputs, output, contiguity="Rook", callback=None): - """Performs Moran's I analysis on two or more input images. - - Keyword arguments: - - inputs -- Input raster files. - contiguity -- Contiguity type. - output -- Output HTML file (default name will be based on input file if unspecified). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--contiguity={}".format(contiguity)) - args.append("--output='{}'".format(output)) - return self.run_tool('image_autocorrelation', args, callback) # returns 1 if error - - def image_correlation(self, inputs, output=None, callback=None): - """Performs image correlation on two or more input images. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output HTML file (default name will be based on input file if unspecified). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - if output is not None: args.append("--output='{}'".format(output)) - return self.run_tool('image_correlation', args, callback) # returns 1 if error - - def image_regression(self, input1, input2, output, out_residuals=None, standardize=False, callback=None): - """Performs image regression analysis on two input images. - - Keyword arguments: - - input1 -- Input raster file (independent variable, X). - input2 -- Input raster file (dependent variable, Y). - output -- Output HTML file for regression summary report. - out_residuals -- Output raster regression resdidual file. - standardize -- Optional flag indicating whether to standardize the residuals map. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - if out_residuals is not None: args.append("--out_residuals='{}'".format(out_residuals)) - if standardize: args.append("--standardize") - return self.run_tool('image_regression', args, callback) # returns 1 if error - - def in_place_add(self, input1, input2, callback=None): - """Performs an in-place addition operation (input1 += input2). - - Keyword arguments: - - input1 -- Input raster file. - input2 -- Input raster file or constant value. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - return self.run_tool('in_place_add', args, callback) # returns 1 if error - - def in_place_divide(self, input1, input2, callback=None): - """Performs an in-place division operation (input1 /= input2). - - Keyword arguments: - - input1 -- Input raster file. - input2 -- Input raster file or constant value. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - return self.run_tool('in_place_divide', args, callback) # returns 1 if error - - def in_place_multiply(self, input1, input2, callback=None): - """Performs an in-place multiplication operation (input1 *= input2). - - Keyword arguments: - - input1 -- Input raster file. - input2 -- Input raster file or constant value. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - return self.run_tool('in_place_multiply', args, callback) # returns 1 if error - - def in_place_subtract(self, input1, input2, callback=None): - """Performs an in-place subtraction operation (input1 -= input2). - - Keyword arguments: - - input1 -- Input raster file. - input2 -- Input raster file or constant value. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - return self.run_tool('in_place_subtract', args, callback) # returns 1 if error - - def increment(self, i, output, callback=None): - """Increases the values of each grid cell in an input raster by 1.0. (see also InPlaceAdd). - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('increment', args, callback) # returns 1 if error - - def integer_division(self, input1, input2, output, callback=None): - """Performs an integer division operation on two rasters or a raster and a constant value. - - Keyword arguments: - - input1 -- Input raster file or constant value. - input2 -- Input raster file or constant value. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('integer_division', args, callback) # returns 1 if error - - def is_no_data(self, i, output, callback=None): - """Identifies NoData valued pixels in an image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('is_no_data', args, callback) # returns 1 if error - - def kappa_index(self, input1, input2, output, callback=None): - """Performs a kappa index of agreement (KIA) analysis on two categorical raster files. - - Keyword arguments: - - input1 -- Input classification raster file. - input2 -- Input reference raster file. - output -- Output HTML file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('kappa_index', args, callback) # returns 1 if error - - def ks_test_for_normality(self, i, output, num_samples=None, callback=None): - """Evaluates whether the values in a raster are normally distributed. - - Keyword arguments: - - i -- Input raster file. - output -- Output HTML file. - num_samples -- Number of samples. Leave blank to use whole image. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - if num_samples is not None: args.append("--num_samples='{}'".format(num_samples)) - return self.run_tool('ks_test_for_normality', args, callback) # returns 1 if error - - def less_than(self, input1, input2, output, incl_equals=False, callback=None): - """Performs a less-than comparison operation on two rasters or a raster and a constant value. - - Keyword arguments: - - input1 -- Input raster file or constant value. - input2 -- Input raster file or constant value. - output -- Output raster file. - incl_equals -- Perform a less-than-or-equal-to operation. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - if incl_equals: args.append("--incl_equals") - return self.run_tool('less_than', args, callback) # returns 1 if error - - def list_unique_values(self, i, field, output, callback=None): - """Lists the unique values contained in a field witin a vector's attribute table. - - Keyword arguments: - - i -- Input raster file. - field -- Input field name in attribute table. - output -- Output HTML file (default name will be based on input file if unspecified). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--field='{}'".format(field)) - args.append("--output='{}'".format(output)) - return self.run_tool('list_unique_values', args, callback) # returns 1 if error - - def ln(self, i, output, callback=None): - """Returns the natural logarithm of values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('ln', args, callback) # returns 1 if error - - def log10(self, i, output, callback=None): - """Returns the base-10 logarithm of values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('log10', args, callback) # returns 1 if error - - def log2(self, i, output, callback=None): - """Returns the base-2 logarithm of values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('log2', args, callback) # returns 1 if error - - def max(self, input1, input2, output, callback=None): - """Performs a MAX operation on two rasters or a raster and a constant value. - - Keyword arguments: - - input1 -- Input raster file or constant value. - input2 -- Input raster file or constant value. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('max', args, callback) # returns 1 if error - - def min(self, input1, input2, output, callback=None): - """Performs a MIN operation on two rasters or a raster and a constant value. - - Keyword arguments: - - input1 -- Input raster file or constant value. - input2 -- Input raster file or constant value. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('min', args, callback) # returns 1 if error - - def modulo(self, input1, input2, output, callback=None): - """Performs a modulo operation on two rasters or a raster and a constant value. - - Keyword arguments: - - input1 -- Input raster file or constant value. - input2 -- Input raster file or constant value. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('modulo', args, callback) # returns 1 if error - - def multiply(self, input1, input2, output, callback=None): - """Performs a multiplication operation on two rasters or a raster and a constant value. - - Keyword arguments: - - input1 -- Input raster file or constant value. - input2 -- Input raster file or constant value. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('multiply', args, callback) # returns 1 if error - - def negate(self, i, output, callback=None): - """Changes the sign of values in a raster or the 0-1 values of a Boolean raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('negate', args, callback) # returns 1 if error - - def not_equal_to(self, input1, input2, output, callback=None): - """Performs a not-equal-to comparison operation on two rasters or a raster and a constant value. - - Keyword arguments: - - input1 -- Input raster file or constant value. - input2 -- Input raster file or constant value. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('not_equal_to', args, callback) # returns 1 if error - - def power(self, input1, input2, output, callback=None): - """Raises the values in grid cells of one rasters, or a constant value, by values in another raster or constant value. - - Keyword arguments: - - input1 -- Input raster file or constant value. - input2 -- Input raster file or constant value. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('power', args, callback) # returns 1 if error - - def principal_component_analysis(self, inputs, output, num_comp=None, standardized=False, callback=None): - """Performs a principal component analysis (PCA) on a multi-spectral dataset. - - Keyword arguments: - - inputs -- Input raster files. - output -- Output HTML report file. - num_comp -- Number of component images to output; <= to num. input images. - standardized -- Perform standardized PCA?. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--inputs='{}'".format(inputs)) - args.append("--output='{}'".format(output)) - if num_comp is not None: args.append("--num_comp='{}'".format(num_comp)) - if standardized: args.append("--standardized") - return self.run_tool('principal_component_analysis', args, callback) # returns 1 if error - - def quantiles(self, i, output, num_quantiles=5, callback=None): - """Transforms raster values into quantiles. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - num_quantiles -- Number of quantiles. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--num_quantiles={}".format(num_quantiles)) - return self.run_tool('quantiles', args, callback) # returns 1 if error - - def random_field(self, base, output, callback=None): - """Creates an image containing random values. - - Keyword arguments: - - base -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--base='{}'".format(base)) - args.append("--output='{}'".format(output)) - return self.run_tool('random_field', args, callback) # returns 1 if error - - def random_sample(self, base, output, num_samples=1000, callback=None): - """Creates an image containing randomly located sample grid cells with unique IDs. - - Keyword arguments: - - base -- Input raster file. - output -- Output raster file. - num_samples -- Number of samples. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--base='{}'".format(base)) - args.append("--output='{}'".format(output)) - args.append("--num_samples={}".format(num_samples)) - return self.run_tool('random_sample', args, callback) # returns 1 if error - - def raster_histogram(self, i, output, callback=None): - """Creates a histogram from raster values. - - Keyword arguments: - - i -- Input raster file. - output -- Output HTML file (default name will be based on input file if unspecified). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('raster_histogram', args, callback) # returns 1 if error - - def raster_summary_stats(self, i, callback=None): - """Measures a rasters min, max, average, standard deviation, num. non-nodata cells, and total. - - Keyword arguments: - - i -- Input raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - return self.run_tool('raster_summary_stats', args, callback) # returns 1 if error - - def reciprocal(self, i, output, callback=None): - """Returns the reciprocal (i.e. 1 / z) of values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('reciprocal', args, callback) # returns 1 if error - - def rescale_value_range(self, i, output, out_min_val, out_max_val, clip_min=None, clip_max=None, callback=None): - """Performs a min-max contrast stretch on an input greytone image. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - out_min_val -- New minimum value in output image. - out_max_val -- New maximum value in output image. - clip_min -- Optional lower tail clip value. - clip_max -- Optional upper tail clip value. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--out_min_val='{}'".format(out_min_val)) - args.append("--out_max_val='{}'".format(out_max_val)) - if clip_min is not None: args.append("--clip_min='{}'".format(clip_min)) - if clip_max is not None: args.append("--clip_max='{}'".format(clip_max)) - return self.run_tool('rescale_value_range', args, callback) # returns 1 if error - - def root_mean_square_error(self, i, base, callback=None): - """Calculates the RMSE and other accuracy statistics. - - Keyword arguments: - - i -- Input raster file. - base -- Input base raster file used for comparison. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--base='{}'".format(base)) - return self.run_tool('root_mean_square_error', args, callback) # returns 1 if error - - def round(self, i, output, callback=None): - """Rounds the values in an input raster to the nearest integer value. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('round', args, callback) # returns 1 if error - - def sin(self, i, output, callback=None): - """Returns the sine (sin) of each values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('sin', args, callback) # returns 1 if error - - def sinh(self, i, output, callback=None): - """Returns the hyperbolic sine (sinh) of each values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('sinh', args, callback) # returns 1 if error - - def square(self, i, output, callback=None): - """Squares the values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('square', args, callback) # returns 1 if error - - def square_root(self, i, output, callback=None): - """Returns the square root of the values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('square_root', args, callback) # returns 1 if error - - def subtract(self, input1, input2, output, callback=None): - """Performs a differencing operation on two rasters or a raster and a constant value. - - Keyword arguments: - - input1 -- Input raster file or constant value. - input2 -- Input raster file or constant value. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('subtract', args, callback) # returns 1 if error - - def tan(self, i, output, callback=None): - """Returns the tangent (tan) of each values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('tan', args, callback) # returns 1 if error - - def tanh(self, i, output, callback=None): - """Returns the hyperbolic tangent (tanh) of each values in a raster. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('tanh', args, callback) # returns 1 if error - - def to_degrees(self, i, output, callback=None): - """Converts a raster from radians to degrees. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('to_degrees', args, callback) # returns 1 if error - - def to_radians(self, i, output, callback=None): - """Converts a raster from degrees to radians. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('to_radians', args, callback) # returns 1 if error - - def trend_surface(self, i, output, order=1, callback=None): - """Estimates the trend surface of an input raster file. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - order -- Polynomial order (1 to 10). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - args.append("--order={}".format(order)) - return self.run_tool('trend_surface', args, callback) # returns 1 if error - - def trend_surface_vector_points(self, i, field, output, cell_size, order=1, callback=None): - """Estimates a trend surface from vector points. - - Keyword arguments: - - i -- Input vector Points file. - field -- Input field name in attribute table. - output -- Output raster file. - order -- Polynomial order (1 to 10). - cell_size -- Optionally specified cell size of output raster. Not used when base raster is specified. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--field='{}'".format(field)) - args.append("--output='{}'".format(output)) - args.append("--order={}".format(order)) - args.append("--cell_size='{}'".format(cell_size)) - return self.run_tool('trend_surface_vector_points', args, callback) # returns 1 if error - - def truncate(self, i, output, num_decimals=None, callback=None): - """Truncates the values in a raster to the desired number of decimal places. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - num_decimals -- Number of decimals left after truncation (default is zero). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - if num_decimals is not None: args.append("--num_decimals='{}'".format(num_decimals)) - return self.run_tool('truncate', args, callback) # returns 1 if error - - def turning_bands_simulation(self, base, output, range, iterations=1000, callback=None): - """Creates an image containing random values based on a turning-bands simulation. - - Keyword arguments: - - base -- Input base raster file. - output -- Output file. - range -- The field's range, in xy-units, related to the extent of spatial autocorrelation. - iterations -- The number of iterations. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--base='{}'".format(base)) - args.append("--output='{}'".format(output)) - args.append("--range='{}'".format(range)) - args.append("--iterations={}".format(iterations)) - return self.run_tool('turning_bands_simulation', args, callback) # returns 1 if error - - def xor(self, input1, input2, output, callback=None): - """Performs a logical XOR operator on two Boolean raster images. - - Keyword arguments: - - input1 -- Input raster file. - input2 -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input1='{}'".format(input1)) - args.append("--input2='{}'".format(input2)) - args.append("--output='{}'".format(output)) - return self.run_tool('xor', args, callback) # returns 1 if error - - def z_scores(self, i, output, callback=None): - """Standardizes the values in an input raster by converting to z-scores. - - Keyword arguments: - - i -- Input raster file. - output -- Output raster file. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--input='{}'".format(i)) - args.append("--output='{}'".format(output)) - return self.run_tool('z_scores', args, callback) # returns 1 if error - - ########################### - # Stream Network Analysis # - ########################### - - def distance_to_outlet(self, d8_pntr, streams, output, esri_pntr=False, zero_background=False, callback=None): - """Calculates the distance of stream grid cells to the channel network outlet cell. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('distance_to_outlet', args, callback) # returns 1 if error - - def extract_streams(self, flow_accum, output, threshold, zero_background=False, callback=None): - """Extracts stream grid cells from a flow accumulation raster. - - Keyword arguments: - - flow_accum -- Input raster D8 flow accumulation file. - output -- Output raster file. - threshold -- Threshold in flow accumulation values for channelization. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--flow_accum='{}'".format(flow_accum)) - args.append("--output='{}'".format(output)) - args.append("--threshold='{}'".format(threshold)) - if zero_background: args.append("--zero_background") - return self.run_tool('extract_streams', args, callback) # returns 1 if error - - def extract_valleys(self, dem, output, variant="Lower Quartile", line_thin=True, filter=5, callback=None): - """Identifies potential valley bottom grid cells based on local topolography alone. - - Keyword arguments: - - dem -- Input raster DEM file. - output -- Output raster file. - variant -- Options include 'lq' (lower quartile), 'JandR' (Johnston and Rosenfeld), and 'PandD' (Peucker and Douglas); default is 'lq'. - line_thin -- Optional flag indicating whether post-processing line-thinning should be performed. - filter -- Optional argument (only used when variant='lq') providing the filter size, in grid cells, used for lq-filtering (default is 5). - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - args.append("--variant={}".format(variant)) - if line_thin: args.append("--line_thin") - args.append("--filter={}".format(filter)) - return self.run_tool('extract_valleys', args, callback) # returns 1 if error - - def farthest_channel_head(self, d8_pntr, streams, output, esri_pntr=False, zero_background=False, callback=None): - """Calculates the distance to the furthest upstream channel head for each stream cell. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('farthest_channel_head', args, callback) # returns 1 if error - - def find_main_stem(self, d8_pntr, streams, output, esri_pntr=False, zero_background=False, callback=None): - """Finds the main stem, based on stream lengths, of each stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('find_main_stem', args, callback) # returns 1 if error - - def hack_stream_order(self, d8_pntr, streams, output, esri_pntr=False, zero_background=False, callback=None): - """Assigns the Hack stream order to each tributary in a stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('hack_stream_order', args, callback) # returns 1 if error - - def horton_stream_order(self, d8_pntr, streams, output, esri_pntr=False, zero_background=False, callback=None): - """Assigns the Horton stream order to each tributary in a stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('horton_stream_order', args, callback) # returns 1 if error - - def length_of_upstream_channels(self, d8_pntr, streams, output, esri_pntr=False, zero_background=False, callback=None): - """Calculates the total length of channels upstream. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('length_of_upstream_channels', args, callback) # returns 1 if error - - def long_profile(self, d8_pntr, streams, dem, output, esri_pntr=False, callback=None): - """Plots the stream longitudinal profiles for one or more rivers. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - dem -- Input raster DEM file. - output -- Output HTML file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('long_profile', args, callback) # returns 1 if error - - def long_profile_from_points(self, d8_pntr, points, dem, output, esri_pntr=False, callback=None): - """Plots the longitudinal profiles from flow-paths initiating from a set of vector points. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - points -- Input vector points file. - dem -- Input raster DEM file. - output -- Output HTML file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--points='{}'".format(points)) - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('long_profile_from_points', args, callback) # returns 1 if error - - def raster_streams_to_vector(self, streams, d8_pntr, output, esri_pntr=False, callback=None): - """Converts a raster stream file into a vector file. - - Keyword arguments: - - streams -- Input raster streams file. - d8_pntr -- Input raster D8 pointer file. - output -- Output vector file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--streams='{}'".format(streams)) - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('raster_streams_to_vector', args, callback) # returns 1 if error - - def rasterize_streams(self, streams, base, output, nodata=True, feature_id=False, callback=None): - """Rasterizes vector streams based on Lindsay (2016) method. - - Keyword arguments: - - streams -- Input vector streams file. - base -- Input base raster file. - output -- Output raster file. - nodata -- Use NoData value for background?. - feature_id -- Use feature number as output value?. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--streams='{}'".format(streams)) - args.append("--base='{}'".format(base)) - args.append("--output='{}'".format(output)) - if nodata: args.append("--nodata") - if feature_id: args.append("--feature_id") - return self.run_tool('rasterize_streams', args, callback) # returns 1 if error - - def remove_short_streams(self, d8_pntr, streams, output, min_length, esri_pntr=False, callback=None): - """Removes short first-order streams from a stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - min_length -- Minimum tributary length (in map units) used for network prunning. - esri_pntr -- D8 pointer uses the ESRI style scheme. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - args.append("--min_length='{}'".format(min_length)) - if esri_pntr: args.append("--esri_pntr") - return self.run_tool('remove_short_streams', args, callback) # returns 1 if error - - def shreve_stream_magnitude(self, d8_pntr, streams, output, esri_pntr=False, zero_background=False, callback=None): - """Assigns the Shreve stream magnitude to each link in a stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('shreve_stream_magnitude', args, callback) # returns 1 if error - - def strahler_stream_order(self, d8_pntr, streams, output, esri_pntr=False, zero_background=False, callback=None): - """Assigns the Strahler stream order to each link in a stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('strahler_stream_order', args, callback) # returns 1 if error - - def stream_link_class(self, d8_pntr, streams, output, esri_pntr=False, zero_background=False, callback=None): - """Identifies the exterior/interior links and nodes in a stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('stream_link_class', args, callback) # returns 1 if error - - def stream_link_identifier(self, d8_pntr, streams, output, esri_pntr=False, zero_background=False, callback=None): - """Assigns a unique identifier to each link in a stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('stream_link_identifier', args, callback) # returns 1 if error - - def stream_link_length(self, d8_pntr, linkid, output, esri_pntr=False, zero_background=False, callback=None): - """Estimates the length of each link (or tributary) in a stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - linkid -- Input raster streams link ID (or tributary ID) file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--linkid='{}'".format(linkid)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('stream_link_length', args, callback) # returns 1 if error - - def stream_link_slope(self, d8_pntr, linkid, dem, output, esri_pntr=False, zero_background=False, callback=None): - """Estimates the average slope of each link (or tributary) in a stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - linkid -- Input raster streams link ID (or tributary ID) file. - dem -- Input raster DEM file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--linkid='{}'".format(linkid)) - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('stream_link_slope', args, callback) # returns 1 if error - - def stream_slope_continuous(self, d8_pntr, streams, dem, output, esri_pntr=False, zero_background=False, callback=None): - """Estimates the slope of each grid cell in a stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - dem -- Input raster DEM file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--dem='{}'".format(dem)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('stream_slope_continuous', args, callback) # returns 1 if error - - def topological_stream_order(self, d8_pntr, streams, output, esri_pntr=False, zero_background=False, callback=None): - """Assigns each link in a stream network its topological order. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('topological_stream_order', args, callback) # returns 1 if error - - def tributary_identifier(self, d8_pntr, streams, output, esri_pntr=False, zero_background=False, callback=None): - """Assigns a unique identifier to each tributary in a stream network. - - Keyword arguments: - - d8_pntr -- Input raster D8 pointer file. - streams -- Input raster streams file. - output -- Output raster file. - esri_pntr -- D8 pointer uses the ESRI style scheme. - zero_background -- Flag indicating whether a background value of zero should be used. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--d8_pntr='{}'".format(d8_pntr)) - args.append("--streams='{}'".format(streams)) - args.append("--output='{}'".format(output)) - if esri_pntr: args.append("--esri_pntr") - if zero_background: args.append("--zero_background") - return self.run_tool('tributary_identifier', args, callback) # returns 1 if error diff --git a/wb_runner.py b/wb_runner.py index 4937a9b21..167e3bd5f 100644 --- a/wb_runner.py +++ b/wb_runner.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # This script is part of the WhiteboxTools geospatial analysis library. -# Authors: Dr. John Lindsay -# Created: November 28, 2017 -# Last Modified: November 28, 2017 +# Authors: Dr. John Lindsay, Rachel Broders +# Created: 28/11/2017 +# Last Modified: 05/11/2019 # License: MIT import __future__ @@ -16,6 +16,7 @@ # from __future__ import print_function # from enum import Enum import platform +import re #Added by Rachel for snake_to_camel function from pathlib import Path import glob from sys import platform as _platform @@ -25,6 +26,7 @@ from tkinter.scrolledtext import ScrolledText from tkinter import filedialog from tkinter import messagebox +from tkinter import PhotoImage import webbrowser from whitebox_tools import WhiteboxTools, to_camelcase @@ -49,7 +51,7 @@ def __init__(self, json_str, runner, master=None): self.runner = runner - ttk.Frame.__init__(self, master, padding='0.1i') + ttk.Frame.__init__(self, master, padding='0.02i') self.grid() self.label = ttk.Label(self, text=self.name, justify=tk.LEFT) @@ -68,10 +70,12 @@ def __init__(self, json_str, runner, master=None): if default_value: self.value.set(default_value) - # self.img = tk.PhotoImage(file=script_dir + "/img/open.gif") - # self.open_button = ttk.Button(fs_frame, width=55, image=self.img, command=self.select_dir) - self.open_button = ttk.Button( - fs_frame, width=4, text="...", command=self.select_file) + # dir_path = os.path.dirname(os.path.realpath(__file__)) + # print(dir_path) + # self.open_file_icon = tk.PhotoImage(file = dir_path + '//img//open.png') #Added by Rachel to replace file selector "..." button with open file icon + + # self.open_button = ttk.Button(fs_frame, width=4, image = self.open_file_icon, command=self.select_file, padding = '0.02i') + self.open_button = ttk.Button(fs_frame, width=4, text="...", command=self.select_file, padding = '0.02i') self.open_button.grid(row=0, column=1, sticky=tk.E) self.open_button.columnconfigure(0, weight=1) fs_frame.grid(row=1, column=0, sticky=tk.NSEW) @@ -97,12 +101,12 @@ def select_file(self): ftypes = [('All files', '*.*')] if 'RasterAndVector' in self.file_type: ftypes = [("Shapefiles", "*.shp"), ('Raster files', ('*.dep', '*.tif', - '*.tiff', '*.gtif', '*.gtiff', '*.flt', + '*.tiff', '*.flt', '*.sdat', '*.rdc', '*.asc'))] elif 'Raster' in self.file_type: ftypes = [('Raster files', ('*.dep', '*.tif', - '*.tiff', '*.gtif', '*.gtiff', '*.flt', + '*.tiff', '*.flt', '*.sdat', '*.rdc', '*.asc'))] elif 'Lidar' in self.file_type: @@ -195,7 +199,7 @@ def __init__(self, json_str, runner, master=None): ttk.Frame.__init__(self, master) self.grid() - self['padding'] = '0.1i' + self['padding'] = '0.02i' self.label = ttk.Label(self, text=self.name, justify=tk.LEFT) self.label.grid(row=0, column=0, sticky=tk.W) @@ -358,7 +362,7 @@ def __init__(self, json_str, runner, master=None): ttk.Frame.__init__(self, master) self.grid() - self['padding'] = '0.1i' + self['padding'] = '0.05i' self.label = ttk.Label(self, text=self.name, justify=tk.LEFT) self.label.grid(row=0, column=0, sticky=tk.W) @@ -482,7 +486,7 @@ def __init__(self, json_str, master=None): ttk.Frame.__init__(self, master) self.grid() - self['padding'] = '0.1i' + self['padding'] = '0.05i' frame = ttk.Frame(self, padding='0.0i') @@ -524,7 +528,7 @@ def __init__(self, json_str, master=None): ttk.Frame.__init__(self, master) self.grid() - self['padding'] = '0.1i' + self['padding'] = '0.02i' frame = ttk.Frame(self, padding='0.0i') @@ -757,11 +761,6 @@ def __init__(self, tool_name=None, master=None): self.grid() self.tool_name = tool_name self.master.title("WhiteboxTools Runner") - # widthpixels = 800 - # heightpixels = 600 - # self.master.geometry('{}x{}'.format(widthpixels, heightpixels)) - # self.master.resizable(0, 0) - # self.master.lift() if _platform == "darwin": os.system( '''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "Python" to true' ''') @@ -769,162 +768,423 @@ def __init__(self, tool_name=None, master=None): self.working_dir = str(Path.home()) def create_widgets(self): - toplevel_frame = ttk.Frame(self, padding='0.1i') - (self.toolslist, selected_item) = self.get_tools_list() - self.tools_frame = ttk.LabelFrame(toplevel_frame, text="{} Available Tools".format( - len(self.toolslist)), padding='0.1i') - self.toolnames = tk.StringVar(value=self.toolslist) - self.tools_listbox = tk.Listbox( - self.tools_frame, height=22, listvariable=self.toolnames) - self.tools_listbox.bind("<>", self.update_tool_help) - self.tools_listbox.grid(row=0, column=0, sticky=tk.NSEW) - self.tools_listbox.columnconfigure(0, weight=10) - self.tools_listbox.rowconfigure(0, weight=1) - s = ttk.Scrollbar(self.tools_frame, orient=tk.VERTICAL, - command=self.tools_listbox.yview) - s.grid(row=0, column=1, sticky=(tk.N, tk.S)) - self.tools_listbox['yscrollcommand'] = s.set + ######################################################### + # Overall/Top level Frame # + ######################################################### + #define left-side frame (toplevel_frame) and right-side frame (overall_frame) + toplevel_frame = ttk.Frame(self, padding='0.1i') + overall_frame = ttk.Frame(self, padding='0.1i') + #set-up layout + overall_frame.grid(row=0, column=1, sticky=tk.NSEW) + toplevel_frame.grid(row=0, column=0, sticky=tk.NSEW) + ######################################################### + # Calling basics # + ######################################################### + #Create all needed lists of tools and toolboxes + self.toolbox_list = self.get_toolboxes() + self.sort_toolboxes() + self.tools_and_toolboxes = wbt.toolbox('') + self.sort_tools_by_toolbox() + self.get_tools_list() + #Icons to be used in tool treeview + self.tool_icon = tk.PhotoImage(file = self.script_dir + '//img//tool.png') + self.open_toolbox_icon = tk.PhotoImage(file = self.script_dir + '//img//open.png') + self.closed_toolbox_icon = tk.PhotoImage(file = self.script_dir + '//img//closed.png') + ######################################################### + # Toolboxes Frame #FIXME: change width or make horizontally scrollable + ######################################################### + #define tools_frame and tool_tree + self.tools_frame = ttk.LabelFrame(toplevel_frame, text="{} Available Tools".format(len(self.tools_list)), padding='0.1i') + self.tool_tree = ttk.Treeview(self.tools_frame, height = 21) + #Set up layout + self.tool_tree.grid(row=0, column=0, sticky=tk.NSEW) + self.tool_tree.column("#0", width = 280) #Set width so all tools are readable within the frame self.tools_frame.grid(row=0, column=0, sticky=tk.NSEW) self.tools_frame.columnconfigure(0, weight=10) self.tools_frame.columnconfigure(1, weight=1) - self.tools_frame.rowconfigure(0, weight=1) - - overall_frame = ttk.Frame(toplevel_frame, padding='0.1i') - - # json_str = '{"default_value": null, "description": "Directory containing data files.", "flags": ["--wd"], "name": "Working Directory", "optional": true, "parameter_type": "Directory"}' - # self.wd = FileSelector(json_str, overall_frame) - # self.wd.grid(row=0, column=0, sticky=tk.NSEW) - - current_tool_frame = ttk.Frame(overall_frame, padding='0.1i') - self.current_tool_lbl = ttk.Label(current_tool_frame, text="Current Tool: {}".format( - self.tool_name), justify=tk.LEFT) # , font=("Helvetica", 12, "bold") - self.current_tool_lbl.grid(row=0, column=0, sticky=tk.W) - self.view_code_button = ttk.Button( - current_tool_frame, text="View Code", width=12, command=self.view_code) + self.tools_frame.rowconfigure(0, weight=10) + self.tools_frame.rowconfigure(1, weight=1) + #Add toolboxes and tools to treeview + index = 0 + for toolbox in self.lower_toolboxes: + if toolbox.find('/') != (-1): #toolboxes + self.tool_tree.insert(toolbox[:toolbox.find('/')], 0, text = " " + toolbox[toolbox.find('/') + 1:], iid = toolbox[toolbox.find('/') + 1:], tags = 'toolbox', image = self.closed_toolbox_icon) + for tool in self.sorted_tools[index]: #add tools within toolbox + self.tool_tree.insert(toolbox[toolbox.find('/') + 1:], 'end', text = " " + tool, tags = 'tool', iid = tool, image = self.tool_icon) + else: #subtoolboxes + self.tool_tree.insert('', 'end', text = " " + toolbox, iid = toolbox, tags = 'toolbox', image = self.closed_toolbox_icon) + for tool in self.sorted_tools[index]: #add tools within subtoolbox + self.tool_tree.insert(toolbox, 'end', text = " " + tool, iid = tool, tags = 'tool', image = self.tool_icon) + index = index + 1 + #bind tools in treeview to self.tree_update_tool_help function and toolboxes to self.update_toolbox_icon function + self.tool_tree.tag_bind('tool', "<>", self.tree_update_tool_help) + self.tool_tree.tag_bind('toolbox', "<>", self.update_toolbox_icon) + #Add vertical scrollbar to treeview frame + s = ttk.Scrollbar(self.tools_frame, orient=tk.VERTICAL,command=self.tool_tree.yview) + s.grid(row=0, column=1, sticky=(tk.N, tk.S)) + self.tool_tree['yscrollcommand'] = s.set + ######################################################### + # Search Bar # + ######################################################### + #create variables for search results and search input + self.search_list = [] + self.search_text = tk.StringVar() + #Create the elements of the search frame + self.search_frame = ttk.LabelFrame(toplevel_frame, padding='0.1i', text="{} Tools Found".format(len(self.search_list))) + self.search_label = ttk.Label(self.search_frame, text = "Search: ") + self.search_bar = ttk.Entry(self.search_frame, width = 30, textvariable = self.search_text) + self.search_results_listbox = tk.Listbox(self.search_frame, height=11) + self.search_scroll = ttk.Scrollbar(self.search_frame, orient=tk.VERTICAL, command=self.search_results_listbox.yview) + self.search_results_listbox['yscrollcommand'] = self.search_scroll.set + #Add bindings + self.search_results_listbox.bind("<>", self.search_update_tool_help) + self.search_bar.bind('', self.update_search) + #Define layout of the frame + self.search_frame.grid(row = 1, column = 0, sticky=tk.NSEW) + self.search_label.grid(row = 0, column = 0, sticky=tk.NW) + self.search_bar.grid(row = 0, column = 1, sticky=tk.NE) + self.search_results_listbox.grid(row = 1, column = 0, columnspan = 2, sticky=tk.NSEW, pady = 5) + self.search_scroll.grid(row=1, column=2, sticky=(tk.N, tk.S)) + #Configure rows and columns of the frame + self.search_frame.columnconfigure(0, weight=1) + self.search_frame.columnconfigure(1, weight=10) + self.search_frame.columnconfigure(1, weight=1) + self.search_frame.rowconfigure(0, weight=1) + self.search_frame.rowconfigure(1, weight = 10) + ######################################################### + # Current Tool Frame # + ######################################################### + #Create the elements of the current tool frame + self.current_tool_frame = ttk.Frame(overall_frame, padding='0.01i') + self.current_tool_lbl = ttk.Label(self.current_tool_frame, text="Current Tool: {}".format(self.tool_name), justify=tk.LEFT) # , font=("Helvetica", 12, "bold") + self.view_code_button = ttk.Button(self.current_tool_frame, text="View Code", width=12, command=self.view_code) + #Define layout of the frame self.view_code_button.grid(row=0, column=1, sticky=tk.E) - current_tool_frame.grid(row=1, column=0, sticky=tk.NSEW) - current_tool_frame.columnconfigure(0, weight=1) - current_tool_frame.columnconfigure(1, weight=1) - - # tool_args_frame = ttk.Frame(overall_frame, padding='0.0i') - self.tool_args_frame = ttk.Frame(overall_frame, padding='0.0i') - self.tool_args_frame.grid(row=2, column=0, sticky=tk.NSEW) - self.tool_args_frame.columnconfigure(0, weight=1) - - # args_frame = ttk.Frame(overall_frame, padding='0.1i') - # self.args_label = ttk.Label(args_frame, text="Tool Arguments:", justify=tk.LEFT) - # self.args_label.grid(row=0, column=0, sticky=tk.W) - # args_frame2 = ttk.Frame(args_frame, padding='0.0i') - # self.args_value = tk.StringVar() - # self.args_text = ttk.Entry(args_frame2, width=45, justify=tk.LEFT, textvariable=self.args_value) - # self.args_text.grid(row=0, column=0, sticky=tk.NSEW) - # self.args_text.columnconfigure(0, weight=1) - # self.clearButton = ttk.Button(args_frame2, text="Clear", width=4, command=self.clear_args_box) - # self.clearButton.pack(pady=10, padx=10) - # self.clearButton.grid(row=0, column=1, sticky=tk.E) - # self.clearButton.columnconfigure(0, weight=1) - # args_frame2.grid(row=1, column=0, sticky=tk.NSEW) - # args_frame2.columnconfigure(0, weight=10) - # args_frame2.columnconfigure(1, weight=1) - # args_frame.grid(row=2, column=0, sticky=tk.NSEW) - # args_frame.columnconfigure(0, weight=1) - - # # Add the bindings - # if _platform == "darwin": - # self.args_text.bind("", self.args_select_all) - # else: - # self.args_text.bind("", self.args_select_all) - - buttonsFrame = ttk.Frame(overall_frame, padding='0.1i') - self.run_button = ttk.Button( - buttonsFrame, text="Run", width=8, command=self.run_tool) - # self.run_button.pack(pady=10, padx=10) + self.current_tool_lbl.grid(row=0, column=0, sticky=tk.W) + self.current_tool_frame.grid(row=0, column=0, columnspan = 2, sticky=tk.NSEW) + #Configure rows and columns of the frame + self.current_tool_frame.columnconfigure(0, weight=1) + self.current_tool_frame.columnconfigure(1, weight=1) + ######################################################### + # Args Frame # + ######################################################### + # #Create the elements of the tool arguments frame + # self.arg_scroll = ttk.Scrollbar(overall_frame, orient='vertical') + # self.arg_canvas = tk.Canvas(overall_frame, bd=0, highlightthickness=0, yscrollcommand=self.arg_scroll.set) + # self.arg_scroll.config(command=self.arg_canvas.yview) #self.arg_scroll scrolls over self.arg_canvas + # self.arg_scroll_frame = ttk.Frame(self.arg_canvas) # create a frame inside the self.arg_canvas which will be scrolled with it + # self.arg_scroll_frame_id = self.arg_canvas.create_window(0, 0, window=self.arg_scroll_frame, anchor="nw") + # #Define layout of the frame + # self.arg_scroll.grid(row = 1, column = 1, sticky = (tk.NS, tk.E)) + # self.arg_canvas.grid(row = 1, column = 0, sticky = tk.NSEW) + # # reset the view + # self.arg_canvas.xview_moveto(0) + # self.arg_canvas.yview_moveto(0) + # #Add bindings + # self.arg_scroll_frame.bind('', self.configure_arg_scroll_frame) + # self.arg_canvas.bind('', self.configure_arg_canvas) + self.arg_scroll_frame = ttk.Frame(overall_frame, padding='0.0i') + self.arg_scroll_frame.grid(row=1, column=0, sticky=tk.NSEW) + self.arg_scroll_frame.columnconfigure(0, weight=1) + ######################################################### + # Buttons Frame # + ######################################################### + #Create the elements of the buttons frame + buttons_frame = ttk.Frame(overall_frame, padding='0.1i') + self.run_button = ttk.Button(buttons_frame, text="Run", width=8, command=self.run_tool) + self.quit_button = ttk.Button(buttons_frame, text="Cancel", width=8, command=self.cancel_operation) + self.help_button = ttk.Button(buttons_frame, text="Help", width=8, command=self.tool_help_button) + #Define layout of the frame self.run_button.grid(row=0, column=0) - self.quitButton = ttk.Button( - buttonsFrame, text="Cancel", width=8, command=self.cancel_operation) - self.quitButton.grid(row=0, column=1) - buttonsFrame.grid(row=3, column=0, sticky=tk.E) - - output_frame = ttk.Frame(overall_frame, padding='0.1i') + self.quit_button.grid(row=0, column=1) + self.help_button.grid(row = 0, column = 2) + buttons_frame.grid(row=2, column=0, columnspan = 2, sticky=tk.E) + ######################################################### + # Output Frame # + ######################################################### + #Create the elements of the output frame + output_frame = ttk.Frame(overall_frame) outlabel = ttk.Label(output_frame, text="Output:", justify=tk.LEFT) - outlabel.grid(row=0, column=0, sticky=tk.NW) - k = wbt.tool_help(self.tool_name) - self.out_text = ScrolledText( - output_frame, width=63, height=10, wrap=tk.NONE, padx=7, pady=7) + self.out_text = ScrolledText(output_frame, width=63, height=15, wrap=tk.NONE, padx=7, pady=7, exportselection = 0) + output_scrollbar = ttk.Scrollbar(output_frame, orient=tk.HORIZONTAL, command = self.out_text.xview) + self.out_text['xscrollcommand'] = output_scrollbar.set + #Retreive and insert the text for the current tool + k = wbt.tool_help(self.tool_name) self.out_text.insert(tk.END, k) + #Define layout of the frame + outlabel.grid(row=0, column=0, sticky=tk.NW) self.out_text.grid(row=1, column=0, sticky=tk.NSEW) + output_frame.grid(row=3, column=0, columnspan = 2, sticky=(tk.NS, tk.E)) + output_scrollbar.grid(row=2, column=0, sticky=(tk.W, tk.E)) + #Configure rows and columns of the frame self.out_text.columnconfigure(0, weight=1) - output_frame.grid(row=4, column=0, sticky=tk.NSEW) output_frame.columnconfigure(0, weight=1) - # Add the binding if _platform == "darwin": self.out_text.bind("", self.select_all) - # self.out_text.bind("", self.select_all) else: self.out_text.bind("", self.select_all) - + ######################################################### + # Progress Frame # + ######################################################### + #Create the elements of the progress frame progress_frame = ttk.Frame(overall_frame, padding='0.1i') - self.progress_label = ttk.Label( - progress_frame, text="Progress:", justify=tk.LEFT) - self.progress_label.grid(row=0, column=0, sticky=tk.E, padx=5) + self.progress_label = ttk.Label(progress_frame, text="Progress:", justify=tk.LEFT) self.progress_var = tk.DoubleVar() - self.progress = ttk.Progressbar( - progress_frame, orient="horizontal", variable=self.progress_var, length=200, maximum=100) + self.progress = ttk.Progressbar(progress_frame, orient="horizontal", variable=self.progress_var, length=200, maximum=100) + #Define layout of the frame + self.progress_label.grid(row=0, column=0, sticky=tk.E, padx=5) self.progress.grid(row=0, column=1, sticky=tk.E) - progress_frame.grid(row=5, column=0, sticky=tk.E) - - overall_frame.grid(row=0, column=1, sticky=tk.NSEW) - - overall_frame.columnconfigure(0, weight=1) - toplevel_frame.columnconfigure(0, weight=1) - toplevel_frame.columnconfigure(1, weight=4) - # self.pack(fill=tk.BOTH, expand=1) - # toplevel_frame.columnconfigure(0, weight=1) - # toplevel_frame.rowconfigure(0, weight=1) - - toplevel_frame.grid(row=0, column=0, sticky=tk.NSEW) - + progress_frame.grid(row=4, column=0, columnspan = 2, sticky=tk.SE) + ######################################################### + # Tool Selection # + ######################################################### # Select the appropriate tool, if specified, otherwise the first tool - self.tools_listbox.select_set(selected_item) - self.tools_listbox.event_generate("<>") - self.tools_listbox.see(selected_item) - + self.tool_tree.focus(self.tool_name) + self.tool_tree.selection_set(self.tool_name) + self.tool_tree.event_generate("<>") + ######################################################### + # Menus # + ######################################################### menubar = tk.Menu(self) + filemenu = tk.Menu(menubar, tearoff=0) - filemenu.add_command(label="Set Working Directory", - command=self.set_directory) - filemenu.add_command( - label="Locate WhiteboxTools exe", command=self.select_exe) + filemenu.add_command(label="Set Working Directory", command=self.set_directory) + filemenu.add_command(label="Locate WhiteboxTools exe", command=self.select_exe) filemenu.add_command(label="Refresh Tools", command=self.refresh_tools) filemenu.add_separator() filemenu.add_command(label="Exit", command=self.quit) menubar.add_cascade(label="File", menu=filemenu) editmenu = tk.Menu(menubar, tearoff=0) - editmenu.add_command( - label="Cut", command=lambda: self.focus_get().event_generate("<>")) - editmenu.add_command( - label="Copy", command=lambda: self.focus_get().event_generate("<>")) - editmenu.add_command( - label="Paste", command=lambda: self.focus_get().event_generate("<>")) - + editmenu.add_command(label="Cut", command=lambda: self.focus_get().event_generate("<>")) + editmenu.add_command(label="Copy", command=lambda: self.focus_get().event_generate("<>")) + editmenu.add_command(label="Paste", command=lambda: self.focus_get().event_generate("<>")) menubar.add_cascade(label="Edit ", menu=editmenu) helpmenu = tk.Menu(menubar, tearoff=0) - helpmenu.add_command( - label="About", command=self.help) + helpmenu.add_command(label="About", command=self.help) + helpmenu.add_command(label="License", command=self.license) + menubar.add_cascade(label="Help ", menu=helpmenu) - helpmenu.add_command( - label="License", command=self.license) + self.master.config(menu=menubar) - menubar.add_cascade(label="Help ", menu=helpmenu) + ######################################################### + # Functions (added/edited by Rachel) # + ######################################################### + def get_toolboxes(self): + toolboxes = set() + for item in wbt.toolbox().splitlines(): # run wbt.toolbox with no tool specified--returns all + if item: + tb = item.split(":")[1].strip() + toolboxes.add(tb) + return sorted(toolboxes) + + def sort_toolboxes(self): + self.upper_toolboxes = [] + self.lower_toolboxes = [] + for toolbox in self.toolbox_list: + if toolbox.find('/') == (-1): #Does not contain a subtoolbox, i.e. does not contain '/' + self.upper_toolboxes.append(toolbox) #add to both upper toolbox list and lower toolbox list + self.lower_toolboxes.append(toolbox) + else: #Contains a subtoolbox + self.lower_toolboxes.append(toolbox) #add to only the lower toolbox list + self.upper_toolboxes = sorted(self.upper_toolboxes) #sort both lists alphabetically + self.lower_toolboxes = sorted(self.lower_toolboxes) + + def sort_tools_by_toolbox(self): + self.sorted_tools = [[] for i in range(len(self.lower_toolboxes))] #One list for each lower toolbox + count = 1 + for toolAndToolbox in self.tools_and_toolboxes.split('\n'): + if toolAndToolbox.strip(): + tool = toolAndToolbox.strip().split(':')[0].strip().replace("TIN", "Tin").replace("KS", "Ks").replace("FD", "Fd") #current tool + itemToolbox = toolAndToolbox.strip().split(':')[1].strip() #current toolbox + index = 0 + for toolbox in self.lower_toolboxes: #find which toolbox the current tool belongs to + if toolbox == itemToolbox: + self.sorted_tools[index].append(tool) #add current tool to list at appropriate index + break + index = index + 1 + count = count + 1 - self.master.config(menu=menubar) + def get_tools_list(self): + self.tools_list = [] + selected_item = -1 + for item in wbt.list_tools().keys(): + if item: + value = to_camelcase(item).replace("TIN", "Tin").replace("KS", "Ks").replace("FD", "Fd") #format tool name + self.tools_list.append(value) #add tool to list + if item == self.tool_name: #update selected_item it tool found + selected_item = len(self.tools_list) - 1 + if selected_item == -1: #set self.tool_name as default tool + selected_item = 0 + self.tool_name = self.tools_list[0] + + def tree_update_tool_help(self, event): # read selection when tool selected from treeview then call self.update_tool_help + curItem = self.tool_tree.focus() + self.tool_name = self.tool_tree.item(curItem).get('text').replace(" ", "") + self.update_tool_help() + + def search_update_tool_help(self, event): # read selection when tool selected from search results then call self.update_tool_help + selection = self.search_results_listbox.curselection() + self.tool_name = self.search_results_listbox.get(selection[0]) + self.update_tool_help() + + def update_tool_help(self): + self.out_text.delete('1.0', tk.END) + for widget in self.arg_scroll_frame.winfo_children(): + widget.destroy() - # self.get_toolboxes() + k = wbt.tool_help(self.tool_name) + self.print_to_output(k) + j = json.loads(wbt.tool_parameters(self.tool_name)) + param_num = 0 + for p in j['parameters']: + json_str = json.dumps( + p, sort_keys=True, indent=2, separators=(',', ': ')) + pt = p['parameter_type'] + if 'ExistingFileOrFloat' in pt: + ff = FileOrFloat(json_str, self, self.arg_scroll_frame) + ff.grid(row=param_num, column=0, sticky=tk.NSEW) + param_num = param_num + 1 + elif ('ExistingFile' in pt or 'NewFile' in pt or 'Directory' in pt): + fs = FileSelector(json_str, self, self.arg_scroll_frame) + fs.grid(row=param_num, column=0, sticky=tk.NSEW) + param_num = param_num + 1 + elif 'FileList' in pt: + b = MultifileSelector(json_str, self, self.arg_scroll_frame) + b.grid(row=param_num, column=0, sticky=tk.W) + param_num = param_num + 1 + elif 'Boolean' in pt: + b = BooleanInput(json_str, self.arg_scroll_frame) + b.grid(row=param_num, column=0, sticky=tk.W) + param_num = param_num + 1 + elif 'OptionList' in pt: + b = OptionsInput(json_str, self.arg_scroll_frame) + b.grid(row=param_num, column=0, sticky=tk.W) + param_num = param_num + 1 + elif ('Float' in pt or 'Integer' in pt or + 'String' in pt or 'StringOrNumber' in pt or + 'StringList' in pt or 'VectorAttributeField' in pt): + b = DataInput(json_str, self.arg_scroll_frame) + b.grid(row=param_num, column=0, sticky=tk.NSEW) + param_num = param_num + 1 + else: + messagebox.showinfo( + "Error", "Unsupported parameter type: {}.".format(pt)) + self.update_args_box() + self.out_text.see("%d.%d" % (1, 0)) + + def update_toolbox_icon(self, event): + curItem = self.tool_tree.focus() + dict = self.tool_tree.item(curItem) #retreive the toolbox name + self.toolbox_name = dict.get('text'). replace(" ", "") #delete the space between the icon and text + self.toolbox_open = dict.get('open') #retreive whether the toolbox is open or not + if self.toolbox_open == True: #set image accordingly + self.tool_tree.item(self.toolbox_name, image = self.open_toolbox_icon) + else: + self.tool_tree.item(self.toolbox_name, image = self.closed_toolbox_icon) + + def update_search(self, event): + self.search_list = [] + self.search_string = self.search_text.get().lower() + self.search_results_listbox.delete(0, 'end') #empty the search results + num_results = 0 + for tool in self.tools_list: #search tool names + toolLower = tool.lower() + if toolLower.find(self.search_string) != (-1): #search string found within tool name + num_results = num_results + 1 + self.search_results_listbox.insert(num_results, tool) #tool added to listbox and to search results string + self.search_list.append(tool) + index = 0 + self.get_descriptions() + for description in self.descriptionList: #search tool descriptions + descriptionLower = description.lower() + if descriptionLower.find(self.search_string) != (-1): #search string found within tool description + found = 0 + for item in self.search_list: # check if this tool is already in the listbox + if self.tools_list[index] == item: + found = 1 + if found == 0: # add to listbox + num_results = num_results + 1 + self.search_results_listbox.insert(num_results, self.tools_list[index]) #tool added to listbox and to search results string + index = index + 1 + self.search_frame['text'] = "{} Tools Found".format(num_results) #update search label + + def get_descriptions(self): + self.descriptionList = [] + tools = wbt.list_tools() + toolsItems = tools.items() + for t in toolsItems: + self.descriptionList.append(t[1]) #second entry in tool dictionary is the description + + def configure_arg_scroll_frame(self, event): + # update the scrollbars to match the size of the inner frame + size = (self.arg_scroll_frame.winfo_reqwidth(), self.arg_scroll_frame.winfo_reqheight()) + self.arg_canvas.config(scrollregion="0 0 %s %s" % size) + if self.arg_scroll_frame.winfo_reqwidth() != self.arg_canvas.winfo_width(): + # update the canvas's width to fit the inner frame + self.arg_canvas.config(width=self.arg_scroll_frame.winfo_reqwidth()) + + def configure_arg_canvas(self, event): + if self.arg_scroll_frame.winfo_reqwidth() != self.arg_canvas.winfo_width(): + # update the inner frame's width to fill the canvas + self.arg_canvas.itemconfigure(self.arg_scroll_frame_id, width=self.arg_canvas.winfo_width()) + + def tool_help_button(self): + index = 0 + found = False + #find toolbox corresponding to the current tool + for toolbox in self.lower_toolboxes: + for tool in self.sorted_tools[index]: + if tool == self.tool_name: + self.toolbox_name = toolbox + found = True + break + if found: + break + index = index + 1 + #change LiDAR to Lidar + if index == 10: + self.toolbox_name = to_camelcase(self.toolbox_name) + #format subtoolboxes as for URLs + self.toolbox_name = self.camel_to_snake(self.toolbox_name).replace('/', '').replace(' ', '') + #open the user manual section for the current tool + webbrowser.open_new_tab("https://jblindsay.github.io/wbt_book/available_tools/" + self.toolbox_name + ".html#" + self.tool_name) + + def camel_to_snake(self, s): # taken from tools_info.py + _underscorer1 = re.compile(r'(.)([A-Z][a-z]+)') + _underscorer2 = re.compile('([a-z0-9])([A-Z])') + subbed = _underscorer1.sub(r'\1_\2', s) + return _underscorer2.sub(r'\1_\2', subbed).lower() + + def refresh_tools(self): + #refresh lists + self.tools_and_toolboxes = wbt.toolbox('') + self.sort_tools_by_toolbox() + self.get_tools_list() + #clear self.tool_tree + self.tool_tree.delete(*self.tool_tree.get_children()) + #Add toolboxes and tools to treeview + index = 0 + for toolbox in self.lower_toolboxes: + if toolbox.find('/') != (-1): #toolboxes + self.tool_tree.insert(toolbox[:toolbox.find('/')], 0, text = " " + toolbox[toolbox.find('/') + 1:], iid = toolbox[toolbox.find('/') + 1:], tags = 'toolbox', image = self.closed_toolbox_icon) + for tool in self.sorted_tools[index]: #add tools within toolbox + self.tool_tree.insert(toolbox[toolbox.find('/') + 1:], 'end', text = " " + tool, tags = 'tool', iid = tool, image = self.tool_icon) + else: #subtoolboxes + self.tool_tree.insert('', 'end', text = " " + toolbox, iid = toolbox, tags = 'toolbox', image = self.closed_toolbox_icon) + for tool in self.sorted_tools[index]: #add tools within subtoolbox + self.tool_tree.insert(toolbox, 'end', text = " " + tool, iid = tool, tags = 'tool', image = self.tool_icon) + index = index + 1 + #Update label + self.tools_frame["text"] = "{} Available Tools".format(len(self.tools_list)) + + ######################################################### + # Functions (original) # + ######################################################### def help(self): self.print_to_output(wbt.version()) @@ -956,7 +1216,7 @@ def run_tool(self): # args = shlex.split(self.args_value.get()) args = [] - for widget in self.tool_args_frame.winfo_children(): + for widget in self.arg_scroll_frame.winfo_children(): v = widget.get_value() if v: args.append(v) @@ -993,58 +1253,7 @@ def cancel_operation(self): def view_code(self): webbrowser.open_new_tab(wbt.view_code(self.tool_name).strip()) - - def update_tool_help(self, event): - selection = self.tools_listbox.curselection() - if(len(selection) == 0): - return - self.tool_name = self.tools_listbox.get(selection[0]) - self.out_text.delete('1.0', tk.END) - for widget in self.tool_args_frame.winfo_children(): - widget.destroy() - - k = wbt.tool_help(self.tool_name) - self.print_to_output(k) - - j = json.loads(wbt.tool_parameters(self.tool_name)) - param_num = 0 - for p in j['parameters']: - json_str = json.dumps( - p, sort_keys=True, indent=2, separators=(',', ': ')) - pt = p['parameter_type'] - if 'ExistingFileOrFloat' in pt: - ff = FileOrFloat(json_str, self, self.tool_args_frame) - ff.grid(row=param_num, column=0, sticky=tk.NSEW) - param_num = param_num + 1 - elif ('ExistingFile' in pt or 'NewFile' in pt or 'Directory' in pt): - fs = FileSelector(json_str, self, self.tool_args_frame) - fs.grid(row=param_num, column=0, sticky=tk.NSEW) - param_num = param_num + 1 - elif 'FileList' in pt: - b = MultifileSelector(json_str, self, self.tool_args_frame) - b.grid(row=param_num, column=0, sticky=tk.W) - param_num = param_num + 1 - elif 'Boolean' in pt: - b = BooleanInput(json_str, self.tool_args_frame) - b.grid(row=param_num, column=0, sticky=tk.W) - param_num = param_num + 1 - elif 'OptionList' in pt: - b = OptionsInput(json_str, self.tool_args_frame) - b.grid(row=param_num, column=0, sticky=tk.W) - param_num = param_num + 1 - elif ('Float' in pt or 'Integer' in pt or - 'String' in pt or 'StringOrNumber' in pt or - 'StringList' in pt or 'VectorAttributeField' in pt): - b = DataInput(json_str, self.tool_args_frame) - b.grid(row=param_num, column=0, sticky=tk.NSEW) - param_num = param_num + 1 - else: - messagebox.showinfo( - "Error", "Unsupported parameter type: {}.".format(pt)) - - self.update_args_box() - self.out_text.see("%d.%d" % (1, 0)) - + def update_args_box(self): s = "" self.current_tool_lbl['text'] = "Current Tool: {}".format( @@ -1068,13 +1277,6 @@ def update_args_box(self): # self.args_value.set(s.strip()) - def clear_args_box(self): - self.args_value.set("") - - def args_select_all(self, event): - self.args_text.select_range(0, tk.END) - return 'break' - def custom_callback(self, value): ''' A custom callback for dealing with tool output. ''' @@ -1102,42 +1304,6 @@ def select_all(self, event): self.out_text.see(tk.INSERT) return 'break' - def get_tools_list(self): - list = [] - selected_item = -1 - for item in wbt.list_tools().keys(): - if item: - value = to_camelcase(item) - list.append(value) - if value == self.tool_name: - selected_item = len(list) - 1 - if selected_item == -1: - selected_item = 0 - self.tool_name = list[0] - - return (list, selected_item) - - def get_toolboxes(self): - toolboxes = set() - for item in wbt.toolbox().splitlines(): # run wbt.toolbox with no tool specified--returns all - if item: - tb = item.split(":")[1].strip() - toolboxes.add(tb) - - for v in sorted(toolboxes): - # print(v) - self.print_line_to_output(v) - - def refresh_tools(self): - (self.toolslist, selected_item) = self.get_tools_list() - self.tools_listbox.delete(0, len(self.toolslist)) - for item in sorted(self.toolslist): - self.tools_listbox.insert(len(self.toolslist), item) - - self.tools_frame["text"] = "{} Available Tools".format( - len(self.toolslist)) - - class JsonPayload(object): def __init__(self, j): self.__dict__ = json.loads(j) @@ -1147,10 +1313,9 @@ def main(): tool_name = None if len(sys.argv) > 1: tool_name = str(sys.argv[1]) - wbr = WbRunner(tool_name) wbr.mainloop() if __name__ == '__main__': - main() + main() \ No newline at end of file diff --git a/wb_runner_new.py b/wb_runner_new.py deleted file mode 100644 index 1042c874e..000000000 --- a/wb_runner_new.py +++ /dev/null @@ -1,1321 +0,0 @@ -#!/usr/bin/env python3 - -# This script is part of the WhiteboxTools geospatial analysis library. -# Authors: Dr. John Lindsay -# Created: November 28, 2017 -# Last Modified: November 28, 2017 -# License: MIT - -import __future__ -import sys -# if sys.version_info[0] < 3: -# raise Exception("Must be using Python 3") -import json -import os -from os import path -# from __future__ import print_function -# from enum import Enum -import platform -import re #Added by Rachel for snake_to_camel function -from pathlib import Path -import glob -from sys import platform as _platform -import shlex -import tkinter as tk -from tkinter import ttk -from tkinter.scrolledtext import ScrolledText -from tkinter import filedialog -from tkinter import messagebox -from tkinter import PhotoImage -import webbrowser -from whitebox_tools import WhiteboxTools, to_camelcase - -wbt = WhiteboxTools() - - -class FileSelector(tk.Frame): - def __init__(self, json_str, runner, master=None): - # first make sure that the json data has the correct fields - j = json.loads(json_str) - self.name = j['name'] - self.description = j['description'] - self.flag = j['flags'][len(j['flags']) - 1] - self.parameter_type = j['parameter_type'] - self.file_type = "" - if "ExistingFile" in self.parameter_type: - self.file_type = j['parameter_type']['ExistingFile'] - elif "NewFile" in self.parameter_type: - self.file_type = j['parameter_type']['NewFile'] - self.optional = j['optional'] - default_value = j['default_value'] - - self.runner = runner - - ttk.Frame.__init__(self, master, padding='0.1i') - self.grid() - - self.label = ttk.Label(self, text=self.name, justify=tk.LEFT) - self.label.grid(row=0, column=0, sticky=tk.W) - self.label.columnconfigure(0, weight=1) - - if not self.optional: - self.label['text'] = self.label['text'] + "*" - - fs_frame = ttk.Frame(self, padding='0.0i') - self.value = tk.StringVar() - self.entry = ttk.Entry( - fs_frame, width=45, justify=tk.LEFT, textvariable=self.value) - self.entry.grid(row=0, column=0, sticky=tk.NSEW) - self.entry.columnconfigure(0, weight=1) - if default_value: - self.value.set(default_value) - - # dir_path = os.path.dirname(os.path.realpath(__file__)) - # print(dir_path) - # self.open_file_icon = tk.PhotoImage(file = dir_path + '//img//open.png') #Added by Rachel to replace file selector "..." button with open file icon - - # self.open_button = ttk.Button(fs_frame, width=4, image = self.open_file_icon, command=self.select_file, padding = '0.02i') - self.open_button = ttk.Button(fs_frame, width=4, text="...", command=self.select_file, padding = '0.02i') - self.open_button.grid(row=0, column=1, sticky=tk.E) - self.open_button.columnconfigure(0, weight=1) - fs_frame.grid(row=1, column=0, sticky=tk.NSEW) - fs_frame.columnconfigure(0, weight=10) - fs_frame.columnconfigure(1, weight=1) - # self.pack(fill=tk.BOTH, expand=1) - self.columnconfigure(0, weight=1) - self.rowconfigure(0, weight=1) - self.rowconfigure(1, weight=1) - - # Add the bindings - if _platform == "darwin": - self.entry.bind("", self.select_all) - else: - self.entry.bind("", self.select_all) - - def select_file(self): - try: - result = self.value.get() - if self.parameter_type == "Directory": - result = filedialog.askdirectory() - elif "ExistingFile" in self.parameter_type: - ftypes = [('All files', '*.*')] - if 'RasterAndVector' in self.file_type: - ftypes = [("Shapefiles", "*.shp"), ('Raster files', ('*.dep', '*.tif', - '*.tiff', '*.flt', - '*.sdat', '*.rdc', - '*.asc'))] - elif 'Raster' in self.file_type: - ftypes = [('Raster files', ('*.dep', '*.tif', - '*.tiff', '*.flt', - '*.sdat', '*.rdc', - '*.asc'))] - elif 'Lidar' in self.file_type: - ftypes = [("LiDAR files", ('*.las', '*.zip'))] - elif 'Vector' in self.file_type: - ftypes = [("Shapefiles", "*.shp")] - elif 'Text' in self.file_type: - ftypes = [("Text files", "*.txt"), ("all files", "*.*")] - elif 'Csv' in self.file_type: - ftypes = [("CSC files", "*.csv"), ("all files", "*.*")] - elif 'Html' in self.file_type: - ftypes = [("HTML files", "*.html")] - - result = filedialog.askopenfilename( - initialdir=self.runner.working_dir, title="Select file", filetypes=ftypes) - - elif "NewFile" in self.parameter_type: - result = filedialog.asksaveasfilename() - - self.value.set(result) - # update the working directory - self.runner.working_dir = os.path.dirname(result) - - except: - t = "file" - if self.parameter_type == "Directory": - t = "directory" - messagebox.showinfo("Warning", "Could not find {}".format(t)) - - def get_value(self): - if self.value.get(): - v = self.value.get() - # Do some quality assurance here. - # Is there a directory included? - if not path.dirname(v): - v = path.join(self.runner.working_dir, v) - - # What about a file extension? - ext = os.path.splitext(v)[-1].lower().strip() - if not ext: - ext = "" - if 'RasterAndVector' in self.file_type: - ext = '.tif' - elif 'Raster' in self.file_type: - ext = '.tif' - elif 'Lidar' in self.file_type: - ext = '.las' - elif 'Vector' in self.file_type: - ext = '.shp' - elif 'Text' in self.file_type: - ext = '.txt' - elif 'Csv' in self.file_type: - ext = '.csv' - elif 'Html' in self.file_type: - ext = '.html' - - v += ext - - v = path.normpath(v) - - return "{}='{}'".format(self.flag, v) - else: - t = "file" - if self.parameter_type == "Directory": - t = "directory" - if not self.optional: - messagebox.showinfo( - "Error", "Unspecified {} parameter {}.".format(t, self.flag)) - - return None - - def select_all(self, event): - self.entry.select_range(0, tk.END) - return 'break' - - -class FileOrFloat(tk.Frame): - def __init__(self, json_str, runner, master=None): - # first make sure that the json data has the correct fields - j = json.loads(json_str) - self.name = j['name'] - self.description = j['description'] - self.flag = j['flags'][len(j['flags']) - 1] - self.parameter_type = j['parameter_type'] - self.file_type = j['parameter_type']['ExistingFileOrFloat'] - self.optional = j['optional'] - default_value = j['default_value'] - - self.runner = runner - - ttk.Frame.__init__(self, master) - self.grid() - self['padding'] = '0.1i' - - self.label = ttk.Label(self, text=self.name, justify=tk.LEFT) - self.label.grid(row=0, column=0, sticky=tk.W) - self.label.columnconfigure(0, weight=1) - - if not self.optional: - self.label['text'] = self.label['text'] + "*" - - fs_frame = ttk.Frame(self, padding='0.0i') - self.value = tk.StringVar() - self.entry = ttk.Entry( - fs_frame, width=35, justify=tk.LEFT, textvariable=self.value) - self.entry.grid(row=0, column=0, sticky=tk.NSEW) - self.entry.columnconfigure(0, weight=1) - if default_value: - self.value.set(default_value) - - # self.img = tk.PhotoImage(file=script_dir + "/img/open.gif") - # self.open_button = ttk.Button(fs_frame, width=55, image=self.img, command=self.select_dir) - self.open_button = ttk.Button( - fs_frame, width=4, text="...", command=self.select_file) - self.open_button.grid(row=0, column=1, sticky=tk.E) - # self.open_button.columnconfigure(0, weight=1) - - self.label = ttk.Label(fs_frame, text='OR', justify=tk.LEFT) - self.label.grid(row=0, column=2, sticky=tk.W) - # self.label.columnconfigure(0, weight=1) - - self.value2 = tk.StringVar() - self.entry2 = ttk.Entry( - fs_frame, width=10, justify=tk.LEFT, textvariable=self.value2) - self.entry2.grid(row=0, column=3, sticky=tk.NSEW) - self.entry2.columnconfigure(0, weight=1) - self.entry2['justify'] = 'right' - - fs_frame.grid(row=1, column=0, sticky=tk.NSEW) - fs_frame.columnconfigure(0, weight=10) - fs_frame.columnconfigure(1, weight=1) - # self.pack(fill=tk.BOTH, expand=1) - self.columnconfigure(0, weight=1) - self.rowconfigure(0, weight=1) - self.rowconfigure(1, weight=1) - - # Add the bindings - if _platform == "darwin": - self.entry.bind("", self.select_all) - else: - self.entry.bind("", self.select_all) - - def select_file(self): - try: - result = self.value.get() - ftypes = [('All files', '*.*')] - if 'RasterAndVector' in self.file_type: - ftypes = [("Shapefiles", "*.shp"), ('Raster files', ('*.dep', '*.tif', - '*.tiff', '*.flt', - '*.sdat', '*.rdc', - '*.asc'))] - elif 'Raster' in self.file_type: - ftypes = [('Raster files', ('*.dep', '*.tif', - '*.tiff', '*.flt', - '*.sdat', '*.rdc', - '*.asc'))] - elif 'Lidar' in self.file_type: - ftypes = [("LiDAR files", ('*.las', '*.zip'))] - elif 'Vector' in self.file_type: - ftypes = [("Shapefiles", "*.shp")] - elif 'Text' in self.file_type: - ftypes = [("Text files", "*.txt"), ("all files", "*.*")] - elif 'Csv' in self.file_type: - ftypes = [("CSC files", "*.csv"), ("all files", "*.*")] - elif 'Html' in self.file_type: - ftypes = [("HTML files", "*.html")] - - result = filedialog.askopenfilename( - initialdir=self.runner.working_dir, title="Select file", filetypes=ftypes) - - self.value.set(result) - # update the working directory - self.runner.working_dir = os.path.dirname(result) - - except: - t = "file" - if self.parameter_type == "Directory": - t = "directory" - messagebox.showinfo("Warning", "Could not find {}".format(t)) - - def RepresentsFloat(self, s): - try: - float(s) - return True - except ValueError: - return False - - def get_value(self): - if self.value.get(): - v = self.value.get() - # Do some quality assurance here. - # Is there a directory included? - if not path.dirname(v): - v = path.join(self.runner.working_dir, v) - - # What about a file extension? - ext = os.path.splitext(v)[-1].lower() - if not ext: - ext = "" - if 'RasterAndVector' in self.file_type: - ext = '.tif' - elif 'Raster' in self.file_type: - ext = '.tif' - elif 'Lidar' in self.file_type: - ext = '.las' - elif 'Vector' in self.file_type: - ext = '.shp' - elif 'Text' in self.file_type: - ext = '.txt' - elif 'Csv' in self.file_type: - ext = '.csv' - elif 'Html' in self.file_type: - ext = '.html' - - v = v + ext - - v = path.normpath(v) - - return "{}='{}'".format(self.flag, v) - elif self.value2.get(): - v = self.value2.get() - if self.RepresentsFloat(v): - return "{}={}".format(self.flag, v) - else: - messagebox.showinfo( - "Error", "Error converting parameter {} to type Float.".format(self.flag)) - else: - if not self.optional: - messagebox.showinfo( - "Error", "Unspecified file/numeric parameter {}.".format(self.flag)) - - return None - - def select_all(self, event): - self.entry.select_range(0, tk.END) - return 'break' - - -class MultifileSelector(tk.Frame): - def __init__(self, json_str, runner, master=None): - # first make sure that the json data has the correct fields - j = json.loads(json_str) - self.name = j['name'] - self.description = j['description'] - self.flag = j['flags'][len(j['flags']) - 1] - self.parameter_type = j['parameter_type'] - self.file_type = "" - self.file_type = j['parameter_type']['FileList'] - self.optional = j['optional'] - default_value = j['default_value'] - - self.runner = runner - - ttk.Frame.__init__(self, master) - self.grid() - self['padding'] = '0.1i' - - self.label = ttk.Label(self, text=self.name, justify=tk.LEFT) - self.label.grid(row=0, column=0, sticky=tk.W) - self.label.columnconfigure(0, weight=1) - - if not self.optional: - self.label['text'] = self.label['text'] + "*" - - fs_frame = ttk.Frame(self, padding='0.0i') - # , variable=self.value) - self.opt = tk.Listbox(fs_frame, width=44, height=4) - self.opt.grid(row=0, column=0, sticky=tk.NSEW) - s = ttk.Scrollbar(fs_frame, orient=tk.VERTICAL, command=self.opt.yview) - s.grid(row=0, column=1, sticky=(tk.N, tk.S)) - self.opt['yscrollcommand'] = s.set - - btn_frame = ttk.Frame(fs_frame, padding='0.0i') - self.open_button = ttk.Button( - btn_frame, width=4, text="...", command=self.select_file) - self.open_button.grid(row=0, column=0, sticky=tk.NE) - self.open_button.columnconfigure(0, weight=1) - self.open_button.rowconfigure(0, weight=1) - - self.delete_button = ttk.Button( - btn_frame, width=4, text="del", command=self.delete_entry) - self.delete_button.grid(row=1, column=0, sticky=tk.NE) - self.delete_button.columnconfigure(0, weight=1) - self.delete_button.rowconfigure(1, weight=1) - - btn_frame.grid(row=0, column=2, sticky=tk.NE) - - fs_frame.grid(row=1, column=0, sticky=tk.NSEW) - fs_frame.columnconfigure(0, weight=10) - fs_frame.columnconfigure(1, weight=1) - fs_frame.columnconfigure(2, weight=1) - # self.pack(fill=tk.BOTH, expand=1) - self.columnconfigure(0, weight=1) - self.rowconfigure(0, weight=1) - self.rowconfigure(1, weight=1) - - def select_file(self): - try: - #result = self.value.get() - init_dir = self.runner.working_dir - ftypes = [('All files', '*.*')] - if 'RasterAndVector' in self.file_type: - ftypes = [("Shapefiles", "*.shp"), ('Raster files', ('*.dep', '*.tif', - '*.tiff', '*.flt', - '*.sdat', '*.rdc', - '*.asc'))] - elif 'Raster' in self.file_type: - ftypes = [('Raster files', ('*.dep', '*.tif', - '*.tiff', '*.flt', - '*.sdat', '*.rdc', - '*.asc'))] - elif 'Lidar' in self.file_type: - ftypes = [("LiDAR files", ('*.las', '*.zip'))] - elif 'Vector' in self.file_type: - ftypes = [("Shapefiles", "*.shp")] - elif 'Text' in self.file_type: - ftypes = [("Text files", "*.txt"), ("all files", "*.*")] - elif 'Csv' in self.file_type: - ftypes = [("CSC files", "*.csv"), ("all files", "*.*")] - elif 'Html' in self.file_type: - ftypes = [("HTML files", "*.html")] - - result = filedialog.askopenfilenames( - initialdir=init_dir, title="Select files", filetypes=ftypes) - if result: - for v in result: - self.opt.insert(tk.END, v) - - # update the working directory - self.runner.working_dir = os.path.dirname(result[0]) - - except: - messagebox.showinfo("Warning", "Could not find file") - - def delete_entry(self): - self.opt.delete(tk.ANCHOR) - - def get_value(self): - try: - l = self.opt.get(0, tk.END) - if l: - s = "" - for i in range(0, len(l)): - v = l[i] - if not path.dirname(v): - v = path.join(self.runner.working_dir, v) - v = path.normpath(v) - if i < len(l) - 1: - s += "{};".format(v) - else: - s += "{}".format(v) - - return "{}='{}'".format(self.flag, s) - else: - if not self.optional: - messagebox.showinfo( - "Error", "Unspecified non-optional parameter {}.".format(self.flag)) - - except: - messagebox.showinfo( - "Error", "Error formating files for parameter {}".format(self.flag)) - - return None - - -class BooleanInput(tk.Frame): - def __init__(self, json_str, master=None): - # first make sure that the json data has the correct fields - j = json.loads(json_str) - self.name = j['name'] - self.description = j['description'] - self.flag = j['flags'][len(j['flags']) - 1] - self.parameter_type = j['parameter_type'] - # just for quality control. BooleanInputs are always optional. - self.optional = True - default_value = j['default_value'] - - ttk.Frame.__init__(self, master) - self.grid() - self['padding'] = '0.1i' - - frame = ttk.Frame(self, padding='0.0i') - - self.value = tk.IntVar() - c = ttk.Checkbutton(frame, text=self.name, - width=55, variable=self.value) - c.grid(row=0, column=0, sticky=tk.W) - - # set the default value - if j['default_value'] != None and j['default_value'] != 'false': - self.value.set(1) - else: - self.value.set(0) - - frame.grid(row=1, column=0, sticky=tk.W) - frame.columnconfigure(0, weight=1) - - # self.pack(fill=tk.BOTH, expand=1) - self.columnconfigure(0, weight=1) - self.rowconfigure(0, weight=1) - - def get_value(self): - if self.value.get() == 1: - return self.flag - else: - return None - - -class OptionsInput(tk.Frame): - def __init__(self, json_str, master=None): - # first make sure that the json data has the correct fields - j = json.loads(json_str) - self.name = j['name'] - self.description = j['description'] - self.flag = j['flags'][len(j['flags']) - 1] - self.parameter_type = j['parameter_type'] - self.optional = j['optional'] - default_value = j['default_value'] - - ttk.Frame.__init__(self, master) - self.grid() - self['padding'] = '0.1i' - - frame = ttk.Frame(self, padding='0.0i') - - self.label = ttk.Label(self, text=self.name, justify=tk.LEFT) - self.label.grid(row=0, column=0, sticky=tk.W) - self.label.columnconfigure(0, weight=1) - - frame2 = ttk.Frame(frame, padding='0.0i') - opt = ttk.Combobox(frame2, width=40) - opt.grid(row=0, column=0, sticky=tk.NSEW) - - self.value = None # initialize in event of no default and no selection - i = 1 - default_index = -1 - list = j['parameter_type']['OptionList'] - values = () - for v in list: - values += (v,) - # opt.insert(tk.END, v) - if v == default_value: - default_index = i - 1 - i = i + 1 - - opt['values'] = values - - # opt.bind("<>", self.select) - opt.bind("<>", self.select) - if default_index >= 0: - opt.current(default_index) - opt.event_generate("<>") - # opt.see(default_index) - - frame2.grid(row=0, column=0, sticky=tk.W) - frame.grid(row=1, column=0, sticky=tk.W) - frame.columnconfigure(0, weight=1) - - # self.pack(fill=tk.BOTH, expand=1) - self.columnconfigure(0, weight=1) - self.rowconfigure(0, weight=1) - - # # first make sure that the json data has the correct fields - # j = json.loads(json_str) - # self.name = j['name'] - # self.description = j['description'] - # self.flag = j['flags'][len(j['flags']) - 1] - # self.parameter_type = j['parameter_type'] - # self.optional = j['optional'] - # default_value = j['default_value'] - - # ttk.Frame.__init__(self, master) - # self.grid() - # self['padding'] = '0.1i' - - # frame = ttk.Frame(self, padding='0.0i') - - # self.label = ttk.Label(self, text=self.name, justify=tk.LEFT) - # self.label.grid(row=0, column=0, sticky=tk.W) - # self.label.columnconfigure(0, weight=1) - - # frame2 = ttk.Frame(frame, padding='0.0i') - # opt = tk.Listbox(frame2, width=40) # , variable=self.value) - # opt.grid(row=0, column=0, sticky=tk.NSEW) - # s = ttk.Scrollbar(frame2, orient=tk.VERTICAL, command=opt.yview) - # s.grid(row=0, column=1, sticky=(tk.N, tk.S)) - # opt['yscrollcommand'] = s.set - - # self.value = None # initialize in event of no default and no selection - # i = 1 - # default_index = -1 - # list = j['parameter_type']['OptionList'] - # for v in list: - # #opt.insert(i, v) - # opt.insert(tk.END, v) - # if v == default_value: - # default_index = i - 1 - # i = i + 1 - - # if i - 1 < 4: - # opt['height'] = i - 1 - # else: - # opt['height'] = 3 - - # opt.bind("<>", self.select) - # if default_index >= 0: - # opt.select_set(default_index) - # opt.event_generate("<>") - # opt.see(default_index) - - # frame2.grid(row=0, column=0, sticky=tk.W) - # frame.grid(row=1, column=0, sticky=tk.W) - # frame.columnconfigure(0, weight=1) - - # # self.pack(fill=tk.BOTH, expand=1) - # self.columnconfigure(0, weight=1) - # self.rowconfigure(0, weight=1) - - def get_value(self): - if self.value: - return "{}='{}'".format(self.flag, self.value) - else: - if not self.optional: - messagebox.showinfo( - "Error", "Unspecified non-optional parameter {}.".format(self.flag)) - - return None - - def select(self, event): - widget = event.widget - # selection = widget.curselection() - self.value = widget.get() # selection[0]) - - -class DataInput(tk.Frame): - def __init__(self, json_str, master=None): - # first make sure that the json data has the correct fields - j = json.loads(json_str) - self.name = j['name'] - self.description = j['description'] - self.flag = j['flags'][len(j['flags']) - 1] - self.parameter_type = j['parameter_type'] - self.optional = j['optional'] - default_value = j['default_value'] - - ttk.Frame.__init__(self, master) - self.grid() - self['padding'] = '0.1i' - - self.label = ttk.Label(self, text=self.name, justify=tk.LEFT) - self.label.grid(row=0, column=0, sticky=tk.W) - self.label.columnconfigure(0, weight=1) - - self.value = tk.StringVar() - if default_value: - self.value.set(default_value) - else: - self.value.set("") - - self.entry = ttk.Entry(self, justify=tk.LEFT, textvariable=self.value) - self.entry.grid(row=0, column=1, sticky=tk.NSEW) - self.entry.columnconfigure(1, weight=10) - - if not self.optional: - self.label['text'] = self.label['text'] + "*" - - if ("Integer" in self.parameter_type or - "Float" in self.parameter_type or - "Double" in self.parameter_type): - self.entry['justify'] = 'right' - - # Add the bindings - if _platform == "darwin": - self.entry.bind("", self.select_all) - else: - self.entry.bind("", self.select_all) - - # self.pack(fill=tk.BOTH, expand=1) - self.columnconfigure(0, weight=1) - self.columnconfigure(1, weight=10) - self.rowconfigure(0, weight=1) - - def RepresentsInt(self, s): - try: - int(s) - return True - except ValueError: - return False - - def RepresentsFloat(self, s): - try: - float(s) - return True - except ValueError: - return False - - def get_value(self): - v = self.value.get() - if v: - if "Integer" in self.parameter_type: - if self.RepresentsInt(self.value.get()): - return "{}={}".format(self.flag, self.value.get()) - else: - messagebox.showinfo( - "Error", "Error converting parameter {} to type Integer.".format(self.flag)) - elif "Float" in self.parameter_type: - if self.RepresentsFloat(self.value.get()): - return "{}={}".format(self.flag, self.value.get()) - else: - messagebox.showinfo( - "Error", "Error converting parameter {} to type Float.".format(self.flag)) - elif "Double" in self.parameter_type: - if self.RepresentsFloat(self.value.get()): - return "{}={}".format(self.flag, self.value.get()) - else: - messagebox.showinfo( - "Error", "Error converting parameter {} to type Double.".format(self.flag)) - else: # String or StringOrNumber types - return "{}='{}'".format(self.flag, self.value.get()) - else: - if not self.optional: - messagebox.showinfo( - "Error", "Unspecified non-optional parameter {}.".format(self.flag)) - - return None - - def select_all(self, event): - self.entry.select_range(0, tk.END) - return 'break' - - -class WbRunner(tk.Frame): - def __init__(self, tool_name=None, master=None): - if platform.system() == 'Windows': - self.ext = '.exe' - else: - self.ext = '' - - exe_name = "whitebox_tools{}".format(self.ext) - - self.exe_path = path.dirname(path.abspath(__file__)) - os.chdir(self.exe_path) - for filename in glob.iglob('**/*', recursive=True): - if filename.endswith(exe_name): - self.exe_path = path.dirname(path.abspath(filename)) - break - - wbt.set_whitebox_dir(self.exe_path) - - ttk.Frame.__init__(self, master) - self.script_dir = os.path.dirname(os.path.realpath(__file__)) - self.grid() - self.tool_name = tool_name - self.master.title("WhiteboxTools Runner") - if _platform == "darwin": - os.system( - '''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "Python" to true' ''') - self.create_widgets() - self.working_dir = str(Path.home()) - - def create_widgets(self): - - ######################################################### - # Overall/Top level Frame # - ######################################################### - #define left-side frame (toplevel_frame) and right-side frame (overall_frame) - toplevel_frame = ttk.Frame(self, padding='0.1i') - overall_frame = ttk.Frame(self, padding='0.1i') - #set-up layout - overall_frame.grid(row=0, column=1, sticky=tk.NSEW) - toplevel_frame.grid(row=0, column=0, sticky=tk.NSEW) - ######################################################### - # Calling basics # - ######################################################### - #Create all needed lists of tools and toolboxes - self.toolbox_list = self.get_toolboxes() - self.sort_toolboxes() - self.tools_and_toolboxes = wbt.toolbox('') - self.sort_tools_by_toolbox() - self.get_tools_list() - #Icons to be used in tool treeview - self.tool_icon = tk.PhotoImage(file = self.script_dir + '//img//tool.png') - self.open_toolbox_icon = tk.PhotoImage(file = self.script_dir + '//img//open.png') - self.closed_toolbox_icon = tk.PhotoImage(file = self.script_dir + '//img//closed.png') - ######################################################### - # Toolboxes Frame #FIXME: change width or make horizontally scrollable - ######################################################### - #define tools_frame and tool_tree - self.tools_frame = ttk.LabelFrame(toplevel_frame, text="{} Available Tools".format(len(self.tools_list)), padding='0.1i') - self.tool_tree = ttk.Treeview(self.tools_frame, height = 21) - #Set up layout - self.tool_tree.grid(row=0, column=0, sticky=tk.NSEW) - self.tool_tree.column("#0", width = 280) #Set width so all tools are readable within the frame - self.tools_frame.grid(row=0, column=0, sticky=tk.NSEW) - self.tools_frame.columnconfigure(0, weight=10) - self.tools_frame.columnconfigure(1, weight=1) - self.tools_frame.rowconfigure(0, weight=10) - self.tools_frame.rowconfigure(1, weight=1) - #Add toolboxes and tools to treeview - index = 0 - for toolbox in self.lower_toolboxes: - if toolbox.find('/') != (-1): #toolboxes - self.tool_tree.insert(toolbox[:toolbox.find('/')], 0, text = " " + toolbox[toolbox.find('/') + 1:], iid = toolbox[toolbox.find('/') + 1:], tags = 'toolbox', image = self.closed_toolbox_icon) - for tool in self.sorted_tools[index]: #add tools within toolbox - self.tool_tree.insert(toolbox[toolbox.find('/') + 1:], 'end', text = " " + tool, tags = 'tool', iid = tool, image = self.tool_icon) - else: #subtoolboxes - self.tool_tree.insert('', 'end', text = " " + toolbox, iid = toolbox, tags = 'toolbox', image = self.closed_toolbox_icon) - for tool in self.sorted_tools[index]: #add tools within subtoolbox - self.tool_tree.insert(toolbox, 'end', text = " " + tool, iid = tool, tags = 'tool', image = self.tool_icon) - index = index + 1 - #bind tools in treeview to self.tree_update_tool_help function and toolboxes to self.update_toolbox_icon function - self.tool_tree.tag_bind('tool', "<>", self.tree_update_tool_help) - self.tool_tree.tag_bind('toolbox', "<>", self.update_toolbox_icon) - #Add vertical scrollbar to treeview frame - s = ttk.Scrollbar(self.tools_frame, orient=tk.VERTICAL,command=self.tool_tree.yview) - s.grid(row=0, column=1, sticky=(tk.N, tk.S)) - self.tool_tree['yscrollcommand'] = s.set - ######################################################### - # Search Bar # - ######################################################### - #create variables for search results and search input - self.search_list = [] - self.search_text = tk.StringVar() - #Create the elements of the search frame - self.search_frame = ttk.LabelFrame(toplevel_frame, padding='0.1i', text="{} Tools Found".format(len(self.search_list))) - self.search_label = ttk.Label(self.search_frame, text = "Search: ") - self.search_bar = ttk.Entry(self.search_frame, width = 30, textvariable = self.search_text) - self.search_results_listbox = tk.Listbox(self.search_frame, height=11) - self.search_scroll = ttk.Scrollbar(self.search_frame, orient=tk.VERTICAL, command=self.search_results_listbox.yview) - self.search_results_listbox['yscrollcommand'] = self.search_scroll.set - #Add bindings - self.search_results_listbox.bind("<>", self.search_update_tool_help) - self.search_bar.bind('', self.update_search) - #Define layout of the frame - self.search_frame.grid(row = 1, column = 0, sticky=tk.NSEW) - self.search_label.grid(row = 0, column = 0, sticky=tk.NW) - self.search_bar.grid(row = 0, column = 1, sticky=tk.NE) - self.search_results_listbox.grid(row = 1, column = 0, columnspan = 2, sticky=tk.NSEW, pady = 5) - self.search_scroll.grid(row=1, column=2, sticky=(tk.N, tk.S)) - #Configure rows and columns of the frame - self.search_frame.columnconfigure(0, weight=1) - self.search_frame.columnconfigure(1, weight=10) - self.search_frame.columnconfigure(1, weight=1) - self.search_frame.rowconfigure(0, weight=1) - self.search_frame.rowconfigure(1, weight = 10) - ######################################################### - # Current Tool Frame # - ######################################################### - #Create the elements of the current tool frame - self.current_tool_frame = ttk.Frame(overall_frame, padding='0.2i') - self.current_tool_lbl = ttk.Label(self.current_tool_frame, text="Current Tool: {}".format(self.tool_name), justify=tk.LEFT) # , font=("Helvetica", 12, "bold") - self.view_code_button = ttk.Button(self.current_tool_frame, text="View Code", width=12, command=self.view_code) - #Define layout of the frame - self.view_code_button.grid(row=0, column=1, sticky=tk.E) - self.current_tool_lbl.grid(row=0, column=0, sticky=tk.W) - self.current_tool_frame.grid(row=0, column=0, columnspan = 2, sticky=tk.NSEW) - #Configure rows and columns of the frame - self.current_tool_frame.columnconfigure(0, weight=1) - self.current_tool_frame.columnconfigure(1, weight=1) - ######################################################### - # Args Frame # - ######################################################### - # #Create the elements of the tool arguments frame - # self.arg_scroll = ttk.Scrollbar(overall_frame, orient='vertical') - # self.arg_canvas = tk.Canvas(overall_frame, bd=0, highlightthickness=0, yscrollcommand=self.arg_scroll.set) - # self.arg_scroll.config(command=self.arg_canvas.yview) #self.arg_scroll scrolls over self.arg_canvas - # self.arg_scroll_frame = ttk.Frame(self.arg_canvas) # create a frame inside the self.arg_canvas which will be scrolled with it - # self.arg_scroll_frame_id = self.arg_canvas.create_window(0, 0, window=self.arg_scroll_frame, anchor="nw") - # #Define layout of the frame - # self.arg_scroll.grid(row = 1, column = 1, sticky = (tk.NS, tk.E)) - # self.arg_canvas.grid(row = 1, column = 0, sticky = tk.NSEW) - # # reset the view - # self.arg_canvas.xview_moveto(0) - # self.arg_canvas.yview_moveto(0) - # #Add bindings - # self.arg_scroll_frame.bind('', self.configure_arg_scroll_frame) - # self.arg_canvas.bind('', self.configure_arg_canvas) - self.arg_scroll_frame = ttk.Frame(overall_frame, padding='0.0i') - self.arg_scroll_frame.grid(row=2, column=0, sticky=tk.NSEW) - self.arg_scroll_frame.columnconfigure(0, weight=1) - ######################################################### - # Buttons Frame # - ######################################################### - #Create the elements of the buttons frame - self.buttons_frame = ttk.Frame(overall_frame, padding='0.2i') - self.run_button = ttk.Button(self.buttons_frame, text="Run", width=8, command=self.run_tool) - self.quit_button = ttk.Button(self.buttons_frame, text="Cancel", width=8, command=self.cancel_operation) - self.help_button = ttk.Button(self.buttons_frame, text="Help", width=8, command=self.tool_help_button) - #Define layout of the frame - self.run_button.grid(row=0, column=0) - self.quit_button.grid(row=0, column=1) - self.help_button.grid(row = 0, column = 2) - self.buttons_frame.grid(row=2, column=0, columnspan = 2, sticky=tk.E) - ######################################################### - # Output Frame # - ######################################################### - #Create the elements of the output frame - output_frame = ttk.Frame(overall_frame) - outlabel = ttk.Label(output_frame, text="Output:", justify=tk.LEFT) - self.out_text = ScrolledText(output_frame, width=63, height=15, wrap=tk.NONE, padx=7, pady=7, exportselection = 0) - output_scrollbar = ttk.Scrollbar(output_frame, orient=tk.HORIZONTAL, command = self.out_text.xview) - self.out_text['xscrollcommand'] = output_scrollbar.set - #Retreive and insert the text for the current tool - k = wbt.tool_help(self.tool_name) - self.out_text.insert(tk.END, k) - #Define layout of the frame - outlabel.grid(row=0, column=0, sticky=tk.NW) - self.out_text.grid(row=1, column=0, sticky=tk.NSEW) - output_frame.grid(row=3, column=0, columnspan = 2, sticky=(tk.NS, tk.E)) - output_scrollbar.grid(row=2, column=0, sticky=(tk.W, tk.E)) - #Configure rows and columns of the frame - self.out_text.columnconfigure(0, weight=1) - output_frame.columnconfigure(0, weight=1) - # Add the binding - if _platform == "darwin": - self.out_text.bind("", self.select_all) - else: - self.out_text.bind("", self.select_all) - ######################################################### - # Progress Frame # - ######################################################### - #Create the elements of the progress frame - progress_frame = ttk.Frame(overall_frame, padding='0.2i') - self.progress_label = ttk.Label(progress_frame, text="Progress:", justify=tk.LEFT) - self.progress_var = tk.DoubleVar() - self.progress = ttk.Progressbar(progress_frame, orient="horizontal", variable=self.progress_var, length=200, maximum=100) - #Define layout of the frame - self.progress_label.grid(row=0, column=0, sticky=tk.E, padx=5) - self.progress.grid(row=0, column=1, sticky=tk.E) - progress_frame.grid(row=4, column=0, columnspan = 2, sticky=tk.SE) - ######################################################### - # Tool Selection # - ######################################################### - # Select the appropriate tool, if specified, otherwise the first tool - self.tool_tree.focus(self.tool_name) - self.tool_tree.selection_set(self.tool_name) - self.tool_tree.event_generate("<>") - ######################################################### - # Menus # - ######################################################### - menubar = tk.Menu(self) - - filemenu = tk.Menu(menubar, tearoff=0) - filemenu.add_command(label="Set Working Directory", command=self.set_directory) - filemenu.add_command(label="Locate WhiteboxTools exe", command=self.select_exe) - filemenu.add_command(label="Refresh Tools", command=self.refresh_tools) - filemenu.add_separator() - filemenu.add_command(label="Exit", command=self.quit) - menubar.add_cascade(label="File", menu=filemenu) - - editmenu = tk.Menu(menubar, tearoff=0) - editmenu.add_command(label="Cut", command=lambda: self.focus_get().event_generate("<>")) - editmenu.add_command(label="Copy", command=lambda: self.focus_get().event_generate("<>")) - editmenu.add_command(label="Paste", command=lambda: self.focus_get().event_generate("<>")) - menubar.add_cascade(label="Edit ", menu=editmenu) - - helpmenu = tk.Menu(menubar, tearoff=0) - helpmenu.add_command(label="About", command=self.help) - helpmenu.add_command(label="License", command=self.license) - menubar.add_cascade(label="Help ", menu=helpmenu) - - self.master.config(menu=menubar) - - ######################################################### - # Functions (added/edited by Rachel) # - ######################################################### - def get_toolboxes(self): - toolboxes = set() - for item in wbt.toolbox().splitlines(): # run wbt.toolbox with no tool specified--returns all - if item: - tb = item.split(":")[1].strip() - toolboxes.add(tb) - return sorted(toolboxes) - - def sort_toolboxes(self): - self.upper_toolboxes = [] - self.lower_toolboxes = [] - for toolbox in self.toolbox_list: - if toolbox.find('/') == (-1): #Does not contain a subtoolbox, i.e. does not contain '/' - self.upper_toolboxes.append(toolbox) #add to both upper toolbox list and lower toolbox list - self.lower_toolboxes.append(toolbox) - else: #Contains a subtoolbox - self.lower_toolboxes.append(toolbox) #add to only the lower toolbox list - self.upper_toolboxes = sorted(self.upper_toolboxes) #sort both lists alphabetically - self.lower_toolboxes = sorted(self.lower_toolboxes) - - def sort_tools_by_toolbox(self): - self.sorted_tools = [[] for i in range(len(self.lower_toolboxes))] #One list for each lower toolbox - count = 1 - for toolAndToolbox in self.tools_and_toolboxes.split('\n'): - if toolAndToolbox.strip(): - tool = toolAndToolbox.strip().split(':')[0].strip().replace("TIN", "Tin").replace("KS", "Ks").replace("FD", "Fd") #current tool - itemToolbox = toolAndToolbox.strip().split(':')[1].strip() #current toolbox - index = 0 - for toolbox in self.lower_toolboxes: #find which toolbox the current tool belongs to - if toolbox == itemToolbox: - self.sorted_tools[index].append(tool) #add current tool to list at appropriate index - break - index = index + 1 - count = count + 1 - - def get_tools_list(self): - self.tools_list = [] - selected_item = -1 - for item in wbt.list_tools().keys(): - if item: - value = to_camelcase(item).replace("TIN", "Tin").replace("KS", "Ks").replace("FD", "Fd") #format tool name - self.tools_list.append(value) #add tool to list - if item == self.tool_name: #update selected_item it tool found - selected_item = len(self.tools_list) - 1 - if selected_item == -1: #set self.tool_name as default tool - selected_item = 0 - self.tool_name = self.tools_list[0] - - def tree_update_tool_help(self, event): # read selection when tool selected from treeview then call self.update_tool_help - curItem = self.tool_tree.focus() - self.tool_name = self.tool_tree.item(curItem).get('text').replace(" ", "") - self.update_tool_help() - - def search_update_tool_help(self, event): # read selection when tool selected from search results then call self.update_tool_help - selection = self.search_results_listbox.curselection() - self.tool_name = self.search_results_listbox.get(selection[0]) - self.update_tool_help() - - def update_tool_help(self): - self.out_text.delete('1.0', tk.END) - for widget in self.arg_scroll_frame.winfo_children(): - widget.destroy() - - k = wbt.tool_help(self.tool_name) - self.print_to_output(k) - - j = json.loads(wbt.tool_parameters(self.tool_name)) - param_num = 0 - for p in j['parameters']: - json_str = json.dumps( - p, sort_keys=True, indent=2, separators=(',', ': ')) - pt = p['parameter_type'] - if 'ExistingFileOrFloat' in pt: - ff = FileOrFloat(json_str, self, self.arg_scroll_frame) - ff.grid(row=param_num, column=0, sticky=tk.NSEW) - param_num = param_num + 1 - elif ('ExistingFile' in pt or 'NewFile' in pt or 'Directory' in pt): - fs = FileSelector(json_str, self, self.arg_scroll_frame) - fs.grid(row=param_num, column=0, sticky=tk.NSEW) - param_num = param_num + 1 - elif 'FileList' in pt: - b = MultifileSelector(json_str, self, self.arg_scroll_frame) - b.grid(row=param_num, column=0, sticky=tk.W) - param_num = param_num + 1 - elif 'Boolean' in pt: - b = BooleanInput(json_str, self.arg_scroll_frame) - b.grid(row=param_num, column=0, sticky=tk.W) - param_num = param_num + 1 - elif 'OptionList' in pt: - b = OptionsInput(json_str, self.arg_scroll_frame) - b.grid(row=param_num, column=0, sticky=tk.W) - param_num = param_num + 1 - elif ('Float' in pt or 'Integer' in pt or - 'String' in pt or 'StringOrNumber' in pt or - 'StringList' in pt or 'VectorAttributeField' in pt): - b = DataInput(json_str, self.arg_scroll_frame) - b.grid(row=param_num, column=0, sticky=tk.NSEW) - param_num = param_num + 1 - else: - messagebox.showinfo( - "Error", "Unsupported parameter type: {}.".format(pt)) - self.update_args_box() - self.out_text.see("%d.%d" % (1, 0)) - - def update_toolbox_icon(self, event): - curItem = self.tool_tree.focus() - dict = self.tool_tree.item(curItem) #retreive the toolbox name - self.toolbox_name = dict.get('text'). replace(" ", "") #delete the space between the icon and text - self.toolbox_open = dict.get('open') #retreive whether the toolbox is open or not - if self.toolbox_open == True: #set image accordingly - self.tool_tree.item(self.toolbox_name, image = self.open_toolbox_icon) - else: - self.tool_tree.item(self.toolbox_name, image = self.closed_toolbox_icon) - - def update_search(self, event): - self.search_list = [] - self.search_string = self.search_text.get().lower() - self.search_results_listbox.delete(0, 'end') #empty the search results - num_results = 0 - for tool in self.tools_list: #search tool names - toolLower = tool.lower() - if toolLower.find(self.search_string) != (-1): #search string found within tool name - num_results = num_results + 1 - self.search_results_listbox.insert(num_results, tool) #tool added to listbox and to search results string - self.search_list.append(tool) - index = 0 - self.get_descriptions() - for description in self.descriptionList: #search tool descriptions - descriptionLower = description.lower() - if descriptionLower.find(self.search_string) != (-1): #search string found within tool description - found = 0 - for item in self.search_list: # check if this tool is already in the listbox - if self.tools_list[index] == item: - found = 1 - if found == 0: # add to listbox - num_results = num_results + 1 - self.search_results_listbox.insert(num_results, self.tools_list[index]) #tool added to listbox and to search results string - index = index + 1 - self.search_frame['text'] = "{} Tools Found".format(num_results) #update search label - - def get_descriptions(self): - self.descriptionList = [] - tools = wbt.list_tools() - toolsItems = tools.items() - for t in toolsItems: - self.descriptionList.append(t[1]) #second entry in tool dictionary is the description - - def configure_arg_scroll_frame(self, event): - # update the scrollbars to match the size of the inner frame - size = (self.arg_scroll_frame.winfo_reqwidth(), self.arg_scroll_frame.winfo_reqheight()) - self.arg_canvas.config(scrollregion="0 0 %s %s" % size) - if self.arg_scroll_frame.winfo_reqwidth() != self.arg_canvas.winfo_width(): - # update the canvas's width to fit the inner frame - self.arg_canvas.config(width=self.arg_scroll_frame.winfo_reqwidth()) - - def configure_arg_canvas(self, event): - if self.arg_scroll_frame.winfo_reqwidth() != self.arg_canvas.winfo_width(): - # update the inner frame's width to fill the canvas - self.arg_canvas.itemconfigure(self.arg_scroll_frame_id, width=self.arg_canvas.winfo_width()) - - def tool_help_button(self): - index = 0 - found = False - #find toolbox corresponding to the current tool - for toolbox in self.lower_toolboxes: - for tool in self.sorted_tools[index]: - if tool == self.tool_name: - self.toolbox_name = toolbox - found = True - break - if found: - break - index = index + 1 - #change LiDAR to Lidar - if index == 10: - self.toolbox_name = to_camelcase(self.toolbox_name) - #format subtoolboxes as for URLs - self.toolbox_name = self.camel_to_snake(self.toolbox_name).replace('/', '').replace(' ', '') - #open the user manual section for the current tool - webbrowser.open_new_tab("https://jblindsay.github.io/wbt_book/available_tools/" + self.toolbox_name + ".html#" + self.tool_name) - - def camel_to_snake(self, s): # taken from tools_info.py - _underscorer1 = re.compile(r'(.)([A-Z][a-z]+)') - _underscorer2 = re.compile('([a-z0-9])([A-Z])') - subbed = _underscorer1.sub(r'\1_\2', s) - return _underscorer2.sub(r'\1_\2', subbed).lower() - - def refresh_tools(self): - #refresh lists - self.tools_and_toolboxes = wbt.toolbox('') - self.sort_tools_by_toolbox() - self.get_tools_list() - #clear self.tool_tree - self.tool_tree.delete(*self.tool_tree.get_children()) - #Add toolboxes and tools to treeview - index = 0 - for toolbox in self.lower_toolboxes: - if toolbox.find('/') != (-1): #toolboxes - self.tool_tree.insert(toolbox[:toolbox.find('/')], 0, text = " " + toolbox[toolbox.find('/') + 1:], iid = toolbox[toolbox.find('/') + 1:], tags = 'toolbox', image = self.closed_toolbox_icon) - for tool in self.sorted_tools[index]: #add tools within toolbox - self.tool_tree.insert(toolbox[toolbox.find('/') + 1:], 'end', text = " " + tool, tags = 'tool', iid = tool, image = self.tool_icon) - else: #subtoolboxes - self.tool_tree.insert('', 'end', text = " " + toolbox, iid = toolbox, tags = 'toolbox', image = self.closed_toolbox_icon) - for tool in self.sorted_tools[index]: #add tools within subtoolbox - self.tool_tree.insert(toolbox, 'end', text = " " + tool, iid = tool, tags = 'tool', image = self.tool_icon) - index = index + 1 - #Update label - self.tools_frame["text"] = "{} Available Tools".format(len(self.tools_list)) - - ######################################################### - # Functions (original) # - ######################################################### - def help(self): - self.print_to_output(wbt.version()) - - def license(self): - self.print_to_output(wbt.license()) - - def set_directory(self): - try: - self.working_dir = filedialog.askdirectory( - initialdir=self.exe_path) - wbt.set_working_dir(self.working_dir) - except: - messagebox.showinfo( - "Warning", "Could not find WhiteboxTools executable file.") - - def select_exe(self): - try: - filename = filedialog.askopenfilename(initialdir=self.exe_path) - self.exe_path = path.dirname(path.abspath(filename)) - wbt.set_whitebox_dir(self.exe_path) - self.refresh_tools() - except: - messagebox.showinfo( - "Warning", "Could not find WhiteboxTools executable file.") - - def run_tool(self): - # wd_str = self.wd.get_value() - wbt.set_working_dir(self.working_dir) - # args = shlex.split(self.args_value.get()) - - args = [] - for widget in self.arg_scroll_frame.winfo_children(): - v = widget.get_value() - if v: - args.append(v) - elif not widget.optional: - messagebox.showinfo( - "Error", "Non-optional tool parameter not specified.") - return - - self.print_line_to_output("") - # self.print_line_to_output("Tool arguments:{}".format(args)) - # self.print_line_to_output("") - # Run the tool and check the return value for an error - if wbt.run_tool(self.tool_name, args, self.custom_callback) == 1: - print("Error running {}".format(self.tool_name)) - - else: - self.run_button["text"] = "Run" - self.progress_var.set(0) - self.progress_label['text'] = "Progress:" - self.progress.update_idletasks() - - def print_to_output(self, value): - self.out_text.insert(tk.END, value) - self.out_text.see(tk.END) - - def print_line_to_output(self, value): - self.out_text.insert(tk.END, value + "\n") - self.out_text.see(tk.END) - - def cancel_operation(self): - wbt.cancel_op = True - self.print_line_to_output("Cancelling operation...") - self.progress.update_idletasks() - - def view_code(self): - webbrowser.open_new_tab(wbt.view_code(self.tool_name).strip()) - - def update_args_box(self): - s = "" - self.current_tool_lbl['text'] = "Current Tool: {}".format( - self.tool_name) - # self.spacer['width'] = width=(35-len(self.tool_name)) - for item in wbt.tool_help(self.tool_name).splitlines(): - if item.startswith("-"): - k = item.split(" ") - if "--" in k[1]: - value = k[1].replace(",", "") - else: - value = k[0].replace(",", "") - - if "flag" in item.lower(): - s = s + value + " " - else: - if "file" in item.lower(): - s = s + value + "='{}' " - else: - s = s + value + "={} " - - # self.args_value.set(s.strip()) - - def custom_callback(self, value): - ''' A custom callback for dealing with tool output. - ''' - if "%" in value: - try: - str_array = value.split(" ") - label = value.replace( - str_array[len(str_array) - 1], "").strip() - progress = float( - str_array[len(str_array) - 1].replace("%", "").strip()) - self.progress_var.set(int(progress)) - self.progress_label['text'] = label - except ValueError as e: - print("Problem converting parsed data into number: ", e) - except Exception as e: - print(e) - else: - self.print_line_to_output(value) - - self.update() # this is needed for cancelling and updating the progress bar - - def select_all(self, event): - self.out_text.tag_add(tk.SEL, "1.0", tk.END) - self.out_text.mark_set(tk.INSERT, "1.0") - self.out_text.see(tk.INSERT) - return 'break' - -class JsonPayload(object): - def __init__(self, j): - self.__dict__ = json.loads(j) - - -def main(): - tool_name = None - if len(sys.argv) > 1: - tool_name = str(sys.argv[1]) - wbr = WbRunner(tool_name) - wbr.mainloop() - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/whitebox_example.py b/whitebox_example.py deleted file mode 100644 index 7f32fc81a..000000000 --- a/whitebox_example.py +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env python3 -''' This module provides examples of how to call the whitebox_tool script and the -whitebox-tools geospatial analysis library using Python code. -''' - -# This script is part of the WhiteboxTools geospatial library. -# Authors: Dr. John Lindsay -# Created: November 28, 2017 -# Last Modified: Feb. 17, 2018 -# License: MIT - -from __future__ import print_function -import os -import sys -from whitebox_tools import WhiteboxTools -import urllib.request - - -def main(): - ''' main function - ''' - try: - wbt = WhiteboxTools() - - # Get the root directory of WhiteboxTools source code or executable file - root_dir = os.path.dirname(os.path.abspath(__file__)) - # WhiteboxTools executable file name for MS Windows - wbt_win_bin = os.path.join(root_dir, "whitebox_tools.exe") - # WhiteboxTools executable file name for MacOS/Linux - wbt_linux_bin = os.path.join(root_dir, "whitebox_tools") - - # If the WhiteboxTools executable file (whitbox_tools.exe) is in the same - # directory as this script, set wbt path to the current directory - # otherwise, set wbt path to (root_dir + "/target/release/") - if os.path.isfile(wbt_win_bin) or os.path.isfile(wbt_linux_bin): - wbt.set_whitebox_dir(root_dir) - else: - wbt.set_whitebox_dir(root_dir + "/target/release/") # or simply wbt.exe_path = ... - - # Set the working directory. This is the path to the folder containing the data, - # i.e. files sent to tools as input/output parameters. You don't need to set - # the working directory if you specify full path names as tool parameters. - wbt.work_dir = os.path.dirname( - os.path.abspath(__file__)) + "/testdata/" - - # If test datasets do not exist, download them from the WhiteboxTools repo - if not os.path.exists(wbt.work_dir): - os.mkdir(wbt.work_dir) - dem_url = "https://github.com/jblindsay/whitebox-tools/raw/master/testdata/DEM.tif" - dep_url = "https://github.com/jblindsay/whitebox-tools/raw/master/testdata/DEM.dep" - urllib.request.urlretrieve(dem_url, "testdata/DEM.tif") - urllib.request.urlretrieve(dep_url, "testdata/DEM.dep") - - # Sets verbose mode (True or False). Most tools will suppress output (e.g. updating - # progress) when verbose mode is False. The default is True - # wbt.set_verbose_mode(False) # or simply, wbt.verbose = False - - # The most convenient way to run a tool is to use its associated method, e.g.: - if wbt.elev_percentile("DEM.tif", "output.tif", 15, 15) != 0: - print("ERROR running tool") - - # You may also provide an optional custom callback for processing output from the - # tool. If you don't provide a callback, and verbose is set to True, tool output - # will simply be printed to the standard output. Also, notice that each tool has a - # convenience method. While internally, whitebox_tools.exe uses CamelCase (MeanFilter) - # to denote tool names, but the Python interface of whitebox_tools.py uses - # snake_case (mean_filter), according to Python style conventions. - - # All of the convenience methods just call the 'run_tool' method, feeding it an - # args array. This is an alternative way of calling tools: - tool_name = "elev_percentile" - args = ["--dem=\"DEM.dep\"", - "--output=\"DEV_101.dep\"", - "--filterx=101"] - - if wbt.run_tool(tool_name, args, my_callback) != 0: - print("ERROR running {}".format(tool_name)) - - # Prints the whitebox-tools help...a listing of available commands - print(wbt.help()) - - # Prints the whitebox-tools license - print(wbt.license()) - - # Prints the whitebox-tools version - print("Version information: {}".format(wbt.version())) - - # List all available tools in whitebox-tools - print(wbt.list_tools()) - - # Lists tools with 'lidar' or 'LAS' in tool name or description. - print(wbt.list_tools(['lidar', 'LAS'])) - - # Print the help for a specific tool. - print(wbt.tool_help("ElevPercentile")) - # Notice that tool names within WhiteboxTools.exe are CamelCase but - # you can also use snake_case here, e.g. print(wbt.tool_help("elev_percentile")) - - except: - print("Unexpected error:", sys.exc_info()[0]) - raise - - -def my_callback(out_str): - ''' Create a custom callback to process the text coming out of the tool. - If a callback is not provided, it will simply print the output stream. - A custom callback allows for processing of the output stream. - ''' - try: - if not hasattr(my_callback, 'prev_line_progress'): - my_callback.prev_line_progress = False - if "%" in out_str: - str_array = out_str.split(" ") - label = out_str.replace(str_array[len(str_array) - 1], "").strip() - progress = int( - str_array[len(str_array) - 1].replace("%", "").strip()) - if my_callback.prev_line_progress: - print('{0} {1}%'.format(label, progress), end="\r") - else: - my_callback.prev_line_progress = True - print(out_str) - elif "error" in out_str.lower(): - print("ERROR: {}".format(out_str)) - my_callback.prev_line_progress = False - elif "elapsed time (excluding i/o):" in out_str.lower(): - elapsed_time = ''.join( - ele for ele in out_str if ele.isdigit() or ele == '.') - units = out_str.lower().replace("elapsed time (excluding i/o):", - "").replace(elapsed_time, "").strip() - print("Elapsed time: {0}{1}".format(elapsed_time, units)) - my_callback.prev_line_progress = False - else: - if callback.prev_line_progress: - print('\n{0}'.format(out_str)) - my_callback.prev_line_progress = False - else: - print(out_str) - - except: - print(out_str) - - -main() diff --git a/whitebox_tools.py b/whitebox_tools.py index 042f03c5b..188f1728b 100644 --- a/whitebox_tools.py +++ b/whitebox_tools.py @@ -378,6 +378,7 @@ def list_tools(self, keywords=[]): + ############## @@ -3216,6 +3217,26 @@ def breach_single_cell_pits(self, dem, output, callback=None): args.append("--output='{}'".format(output)) return self.run_tool('breach_single_cell_pits', args, callback) # returns 1 if error + def burn_streams_at_roads(self, dem, streams, roads, output, width=None, callback=None): + """Burns-in streams at the sites of road embankments. + + Keyword arguments: + + dem -- Input raster digital elevation model (DEM) file. + streams -- Input vector streams file. + roads -- Input vector roads file. + output -- Output raster file. + width -- Maximum road embankment width, in map units. + callback -- Custom function for handling tool text outputs. + """ + args = [] + args.append("--dem='{}'".format(dem)) + args.append("--streams='{}'".format(streams)) + args.append("--roads='{}'".format(roads)) + args.append("--output='{}'".format(output)) + if width is not None: args.append("--width='{}'".format(width)) + return self.run_tool('burn_streams_at_roads', args, callback) # returns 1 if error + def d8_flow_accumulation(self, dem, output, out_type="cells", log=False, clip=False, callback=None): """Calculates a D8 flow accumulation raster from an input DEM. @@ -3670,6 +3691,30 @@ def jenson_snap_pour_points(self, pour_pts, streams, output, snap_dist, callback args.append("--snap_dist='{}'".format(snap_dist)) return self.run_tool('jenson_snap_pour_points', args, callback) # returns 1 if error + def least_cost_breach_depressions(self, dem, output, radius, max_cost=None, min_dist=True, flat_increment=None, fill=True, callback=None): + """Breaches the depressions in a DEM using a least-cost pathway method. + + Keyword arguments: + + dem -- Input raster DEM file. + output -- Output raster file. + radius -- . + max_cost -- Optional maximum breach cost (default is Inf). + min_dist -- Optional flag indicating whether to minimize breach distances. + flat_increment -- Optional elevation increment applied to flat areas. + fill -- Optional flag indicating whether to fill any remaining unbreached depressions. + callback -- Custom function for handling tool text outputs. + """ + args = [] + args.append("--dem='{}'".format(dem)) + args.append("--output='{}'".format(output)) + args.append("--radius='{}'".format(radius)) + if max_cost is not None: args.append("--max_cost='{}'".format(max_cost)) + if min_dist: args.append("--min_dist") + if flat_increment is not None: args.append("--flat_increment='{}'".format(flat_increment)) + if fill: args.append("--fill") + return self.run_tool('least_cost_breach_depressions', args, callback) # returns 1 if error + def longest_flowpath(self, dem, basins, output, callback=None): """Delineates the longest flowpaths for a group of subbasins or watersheds. @@ -3878,6 +3923,20 @@ def unnest_basins(self, d8_pntr, pour_pts, output, esri_pntr=False, callback=Non if esri_pntr: args.append("--esri_pntr") return self.run_tool('unnest_basins', args, callback) # returns 1 if error + def upslope_depression_storage(self, dem, output, callback=None): + """Estimates the average upslope depression storage depth. + + Keyword arguments: + + dem -- Input raster DEM file. + output -- Output raster file. + callback -- Custom function for handling tool text outputs. + """ + args = [] + args.append("--dem='{}'".format(dem)) + args.append("--output='{}'".format(output)) + return self.run_tool('upslope_depression_storage', args, callback) # returns 1 if error + def watershed(self, d8_pntr, pour_pts, output, esri_pntr=False, callback=None): """Identifies the watershed, or drainage basin, draining to a set of target cells. @@ -5142,6 +5201,22 @@ def standard_deviation_contrast_stretch(self, i, output, stdev=2.0, num_tones=25 # LiDAR Tools # ############### + def classify_buildings_in_lidar(self, i, buildings, output, callback=None): + """Reclassifies a LiDAR points that lie within vector building footprints. + + Keyword arguments: + + i -- Input LiDAR file. + buildings -- Input vector polygons file. + output -- Output LiDAR file. + callback -- Custom function for handling tool text outputs. + """ + args = [] + args.append("--input='{}'".format(i)) + args.append("--buildings='{}'".format(buildings)) + args.append("--output='{}'".format(output)) + return self.run_tool('classify_buildings_in_lidar', args, callback) # returns 1 if error + def classify_overlap_points(self, i, output, resolution=2.0, filter=False, callback=None): """Classifies or filters LAS points in regions of overlapping flight lines. @@ -5518,7 +5593,7 @@ def lidar_idw_interpolation(self, i=None, output=None, parameter="elevation", re if maxz is not None: args.append("--maxz='{}'".format(maxz)) return self.run_tool('lidar_idw_interpolation', args, callback) # returns 1 if error - def lidar_info(self, i, output=None, vlr=False, geokeys=False, callback=None): + def lidar_info(self, i, output=None, vlr=True, geokeys=True, callback=None): """Prints information about a LiDAR (LAS) dataset, including header, point return frequency, and classification data and information about the variable length records (VLRs) and geokeys. Keyword arguments: @@ -5714,6 +5789,40 @@ def lidar_remove_outliers(self, i, output, radius=2.0, elev_diff=50.0, use_media if classify: args.append("--classify") return self.run_tool('lidar_remove_outliers', args, callback) # returns 1 if error + def lidar_rfb_interpolation(self, i=None, output=None, parameter="elevation", returns="all", resolution=1.0, radius=2.5, exclude_cls=None, minz=None, maxz=None, func_type="ThinPlateSpline", poly_order="none", weight=0.1, callback=None): + """Interpolates LAS files using a radial basis function (RFB) scheme. When the input/output parameters are not specified, the tool interpolates all LAS files contained within the working directory. + + Keyword arguments: + + i -- Input LiDAR file (including extension). + output -- Output raster file (including extension). + parameter -- Interpolation parameter; options are 'elevation' (default), 'intensity', 'class', 'return_number', 'number_of_returns', 'scan angle', 'rgb', 'user data'. + returns -- Point return types to include; options are 'all' (default), 'last', 'first'. + resolution -- Output raster's grid resolution. + radius -- Search Radius. + exclude_cls -- Optional exclude classes from interpolation; Valid class values range from 0 to 18, based on LAS specifications. Example, --exclude_cls='3,4,5,6,7,18'. + minz -- Optional minimum elevation for inclusion in interpolation. + maxz -- Optional maximum elevation for inclusion in interpolation. + func_type -- Radial basis function type; options are 'ThinPlateSpline' (default), 'PolyHarmonic', 'Gaussian', 'MultiQuadric', 'InverseMultiQuadric'. + poly_order -- Polynomial order; options are 'none' (default), 'constant', 'affine'. + weight -- Weight parameter used in basis function. + callback -- Custom function for handling tool text outputs. + """ + args = [] + if i is not None: args.append("--input='{}'".format(i)) + if output is not None: args.append("--output='{}'".format(output)) + args.append("--parameter={}".format(parameter)) + args.append("--returns={}".format(returns)) + args.append("--resolution={}".format(resolution)) + args.append("--radius={}".format(radius)) + if exclude_cls is not None: args.append("--exclude_cls='{}'".format(exclude_cls)) + if minz is not None: args.append("--minz='{}'".format(minz)) + if maxz is not None: args.append("--maxz='{}'".format(maxz)) + args.append("--func_type={}".format(func_type)) + args.append("--poly_order={}".format(poly_order)) + args.append("--weight={}".format(weight)) + return self.run_tool('lidar_rfb_interpolation', args, callback) # returns 1 if error + def lidar_segmentation(self, i, output, radius=5.0, norm_diff=10.0, maxzdiff=1.0, classes=False, min_size=1, callback=None): """Segments a LiDAR point cloud based on normal vectors. @@ -7186,26 +7295,6 @@ def zonal_statistics(self, i, features, output=None, stat="mean", out_table=None # Stream Network Analysis # ########################### - def burn_streams_at_roads(self, dem, streams, roads, output, width=None, callback=None): - """Rasterizes vector streams based on Lindsay (2016) method. - - Keyword arguments: - - dem -- Input raster digital elevation model (DEM) file. - streams -- Input vector streams file. - roads -- Input vector roads file. - output -- Output raster file. - width -- Maximum road embankment width, in map units. - callback -- Custom function for handling tool text outputs. - """ - args = [] - args.append("--dem='{}'".format(dem)) - args.append("--streams='{}'".format(streams)) - args.append("--roads='{}'".format(roads)) - args.append("--output='{}'".format(output)) - if width is not None: args.append("--width='{}'".format(width)) - return self.run_tool('burn_streams_at_roads', args, callback) # returns 1 if error - def distance_to_outlet(self, d8_pntr, streams, output, esri_pntr=False, zero_background=False, callback=None): """Calculates the distance of stream grid cells to the channel network outlet cell.