Skip to content

Commit

Permalink
pytests for new header options and saveCenterlineMAT
Browse files Browse the repository at this point in the history
  • Loading branch information
cyschneck committed Jun 23, 2023
1 parent d822646 commit fc2d73d
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 7 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ centerline_width.riverCenterline(csv_data=None,

**Solutions for sparse data:**

`interpolate_data` is an option that can be used to find a centerline when the existing data generates a Voronoi graph that is jagged or contains gaps due to the combination of sparse data and a narrow river (See: Debugging, Error Handling, and Edge Cases - Fix Gaps and Jagged Centerlines). By default, `interpolate_data=True` will add 5 additional points between each existing point but can be changed with the `interpolate_n` option
`interpolate_data` is an option that can be used to find a centerline when the existing data generates a Voronoi graph that is jagged or contains gaps due to the combination of sparse data and a narrow river (See: Debugging, Error Handling, and Edge Cases - Fix Gaps and Jagged Centerlines). By default, `interpolate_data=True` will add 5 additional points between each existing point but can be increased or decreased by modifying the `interpolate_n` option

`interpolate_n_centerpoints` is an option that can be used to increase the resolution (number of points) of the centerline found by the Voronoi vertices. By default, will evenly space out to the size of the dataframe. Can artificially increase the amount of width lines generated by increasing the number of center points. When `interpolate_n_centerpoints` increases, the number of width lines generated will increase (and visa versa)

Expand Down Expand Up @@ -226,7 +226,7 @@ There are four types of centerline coordinates formed from the riverbank data

- **Voronoi centerline**: centerline generated from where Voronoi vertices intersect within the river
![example+png](https://raw.githubusercontent.com/cyschneck/centerline-width/main/data/doc_examples/voronoi_centerline.png)
- **Equal Distance Centerline**: centerline based on Voronoi centerline but each point is equally spaced out from the previous (in meters)
- **Equal Distance Centerline**: centerline based on Voronoi centerline but each point is equally spaced out from the previous (in meters) and takes into account the radius of the Earth to convert degrees to meters
![example+png](https://raw.githubusercontent.com/cyschneck/centerline-width/main/data/doc_examples/equal_distance_centerline.png)
- **Evenly Spaced Centerline**: centerline based on Voronoi centerline but evenly spaced with a fixed number of points
![example+png](https://raw.githubusercontent.com/cyschneck/centerline-width/main/data/doc_examples/evenly_spaced_centerline.png)
Expand Down
1 change: 1 addition & 0 deletions centerline_width/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@
from .error_handling import errorHandlingPlotCenterlineWidth
from .error_handling import errorHandlingRiverWidthFromCenterline
from .error_handling import errorHandlingSaveCenterlineCSV
from .error_handling import errorHandlingSaveCenterlineMAT
from .error_handling import errorHandlingExtractPointsToTextFile
from .error_handling import errorHandlingRiverCenterlineClass
11 changes: 10 additions & 1 deletion centerline_width/centerline.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,11 @@ def centerlineLength(centerline_coordinates=None):

def saveCenterlineCSV(river_object=None, save_to_csv=None, latitude_header=None, longitude_header=None, centerline_type="Voronoi"):
# Save Centerline Coordinates generated by Voronoi Diagram to .CSV
centerline_width.errorHandlingSaveCenterlineCSV(river_object=river_object, save_to_csv=save_to_csv, centerline_type=centerline_type)
centerline_width.errorHandlingSaveCenterlineCSV(river_object=river_object,
save_to_csv=save_to_csv,
latitude_header=latitude_header,
longitude_header=longitude_header,
centerline_type=centerline_type)
centerline_type = centerline_type.title()

if centerline_type == "Voronoi": centerline_coordinates_by_type = river_object.centerlineVoronoi
Expand All @@ -449,6 +453,11 @@ def saveCenterlineCSV(river_object=None, save_to_csv=None, latitude_header=None,

def saveCenterlineMAT(river_object=None, save_to_mat=None, latitude_header=None, longitude_header=None, centerline_type="Voronoi"):
# Save Centerline Coordinates generated by Voronoi Diagram to .MAT
centerline_width.errorHandlingSaveCenterlineMAT(river_object=river_object,
save_to_mat=save_to_mat,
latitude_header=latitude_header,
longitude_header=longitude_header,
centerline_type=centerline_type)
centerline_type = centerline_type.title()

if centerline_type == "Voronoi": centerline_coordinates_by_type = river_object.centerlineVoronoi
Expand Down
67 changes: 65 additions & 2 deletions centerline_width/error_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def errorHandlingRiverWidthFromCenterline(river_object=None,
logger.critical("\nCRITICAL ERROR, [save_to_csv]: Extension must be a .csv file, current extension = '{0}'".format(save_to_csv.split(".")[1]))
exit()

def errorHandlingSaveCenterlineCSV(river_object=None, save_to_csv=None, centerline_type=None):
def errorHandlingSaveCenterlineCSV(river_object=None, latitude_header=None, longitude_header=None, save_to_csv=None, centerline_type=None):
# Error Handling for saveCenterlineCSV()
if river_object is None:
logger.critical("\nCRITICAL ERROR, [river_object]: Requires a river object (see: centerline_width.riverCenterline)")
Expand All @@ -176,6 +176,14 @@ def errorHandlingSaveCenterlineCSV(river_object=None, save_to_csv=None, centerli
logger.critical("\nCRITICAL ERROR, [river_object]: Must be a river object (see: centerline_width.riverCenterline), current type = '{0}'".format(type(river_object)))
exit()

if latitude_header is not None and type(latitude_header) != str:
logger.critical("\nCRITICAL ERROR, [latitude_header]: Must be a str, current type = '{0}'".format(type(latitude_header)))
exit()

if longitude_header is not None and type(longitude_header) != str:
logger.critical("\nCRITICAL ERROR, [longitude_header]: Must be a str, current type = '{0}'".format(type(longitude_header)))
exit()

if save_to_csv is None:
logger.critical("\nCRITICAL ERROR, [save_to_csv]: Requires csv filename")
exit()
Expand All @@ -197,6 +205,53 @@ def errorHandlingSaveCenterlineCSV(river_object=None, save_to_csv=None, centerli
logger.critical("\nCRITICAL ERROR, [centerline_type]: Must be an available option in {0}, current option = '{1}'".format(centerline_type_options, centerline_type))
exit()

def errorHandlingSaveCenterlineMAT(river_object=None, latitude_header=None, longitude_header=None, save_to_mat=None, centerline_type=None):
# Error Handling for saveCenterlineMAT()
if river_object is None:
logger.critical("\nCRITICAL ERROR, [river_object]: Requires a river object (see: centerline_width.riverCenterline)")
exit()
else:
if not isinstance(river_object, centerline_width.riverCenterline):
logger.critical("\nCRITICAL ERROR, [river_object]: Must be a river object (see: centerline_width.riverCenterline), current type = '{0}'".format(type(river_object)))
exit()

if latitude_header is not None:
if type(latitude_header) != str:
logger.critical("\nCRITICAL ERROR, [latitude_header]: Must be a str, current type = '{0}'".format(type(latitude_header)))
exit()
if any(not character.isalnum() for character in latitude_header):
logger.critical("\nCRITICAL ERROR, [latitude_header]: Column names cannot contain any whitespace or non-alphanumeric characters, currently = '{0}'".format(latitude_header))
exit()

if longitude_header is not None:
if type(longitude_header) != str:
logger.critical("\nCRITICAL ERROR, [longitude_header]: Must be a str, current type = '{0}'".format(type(longitude_header)))
exit()
if any(not character.isalnum() for character in longitude_header):
logger.critical("\nCRITICAL ERROR, [longitude_header]: Column names cannot contain any whitespace or non-alphanumeric characters, currently = '{0}'".format(longitude_header))
exit()

if save_to_mat is None:
logger.critical("\nCRITICAL ERROR, [save_to_mat]: Requires mat filename")
exit()
else:
if type(save_to_mat) != str:
logger.critical("\nCRITICAL ERROR, [save_to_mat]: Must be a str, current type = '{0}'".format(type(save_to_mat)))
exit()
else:
if not save_to_mat.lower().endswith(".mat"):
logger.critical("\nCRITICAL ERROR, [save_to_mat]: Extension must be a .mat file, current extension = '{0}'".format(save_to_mat.split(".")[1]))
exit()

centerline_type_options = ["Voronoi", "Evenly Spaced", "Smoothed", "Equal Distance"]
if type(centerline_type) != str:
logger.critical("\nCRITICAL ERROR, [centerline_type]: Must be a str, current type = '{0}'".format(type(centerline_type)))
exit()
else:
if centerline_type.title() not in centerline_type_options:
logger.critical("\nCRITICAL ERROR, [centerline_type]: Must be an available option in {0}, current option = '{1}'".format(centerline_type_options, centerline_type))
exit()

# Error Handling: getCoordinatesKML.py
def errorHandlingExtractPointsToTextFile(left_kml=None, right_kml=None, text_output_name=None):
# Error Handling for extractPointsToTextFile()
Expand Down Expand Up @@ -239,7 +294,8 @@ def errorHandlingRiverCenterlineClass(csv_data=None,
optional_cutoff=None,
interpolate_data=None,
interpolate_n=None,
interpolate_n_centerpoints=None):
interpolate_n_centerpoints=None,
equal_distance=None):
# Error Handling for riverCenterlineClass()
if csv_data is None:
logger.critical("\nCRITICAL ERROR, [csv_data]: Requires csv_data location")
Expand Down Expand Up @@ -273,3 +329,10 @@ def errorHandlingRiverCenterlineClass(csv_data=None,
if interpolate_n_centerpoints < 2:
logger.critical("\nCRITICAL ERROR, [interpolate_n_centerpoints]: Must be a greater than 1, currently = '{0}'".format(interpolate_n_centerpoints))
exit()

if type(equal_distance) != int and type(equal_distance) != float:
logger.critical("\nCRITICAL ERROR, [equal_distance]: Must be a int or float, current type = '{0}'".format(type(equal_distance)))
exit()
if equal_distance <= 0:
logger.critical("WARNING, [equal_distance]: Must be a postive value, greater than 0, currently = '{0}'".format(equal_distance))
exit()
101 changes: 100 additions & 1 deletion centerline_width/pytests/test_centerline.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,26 @@ def test_saveCenterlineCSV_csvRequired(caplog):
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [save_to_csv]: Extension must be a .csv file, current extension = 'txt'"

@pytest.mark.parametrize("latitude_header_invalid, latitude_header_error_output", invalid_non_str_options)
def test_saveCenterlineCSV_latitudeHeaderTypeInvalidTypes(caplog, latitude_header_invalid, latitude_header_error_output):
with pytest.raises(SystemExit):
centerline_width.saveCenterlineCSV(river_object=river_class_example,
save_to_csv="testing.csv",
latitude_header=latitude_header_invalid)
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [latitude_header]: Must be a str, current type = '{0}'".format(latitude_header_error_output)

@pytest.mark.parametrize("longitude_header_invalid, longitude_header_error_output", invalid_non_str_options)
def test_saveCenterlineCSV_longitudeHeaderTypeInvalidTypes(caplog, longitude_header_invalid, longitude_header_error_output):
with pytest.raises(SystemExit):
centerline_width.saveCenterlineCSV(river_object=river_class_example,
save_to_csv="testing.csv",
longitude_header=longitude_header_invalid)
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [longitude_header]: Must be a str, current type = '{0}'".format(longitude_header_error_output)

@pytest.mark.parametrize("centerline_type_invalid, centerline_type_error_output", invalid_non_str_options)
def test_saveCenterlineCSV_centerlineTypeInvalidTypes(caplog, centerline_type_invalid, centerline_type_error_output):
with pytest.raises(SystemExit):
Expand All @@ -157,4 +177,83 @@ def test_saveCenterlineCSV_centerlineTypeInvalidOptions(caplog):
centerline_type="not valid")
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [centerline_type]: Must be an available option in ['Voronoi', 'Evenly Spaced', 'Smoothed'], current option = 'not valid'"
assert log_record.message == "\nCRITICAL ERROR, [centerline_type]: Must be an available option in ['Voronoi', 'Evenly Spaced', 'Smoothed', 'Equal Distance'], current option = 'not valid'"

## saveCenterlineMAT() #####################################################
def test_saveCenterlineMAT_riverObjectRequired(caplog):
with pytest.raises(SystemExit):
centerline_width.saveCenterlineMAT(river_object=None)
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [river_object]: Requires a river object (see: centerline_width.riverCenterline)"

def test_saveCenterlineMAT_matInvalidExtension(caplog):
with pytest.raises(SystemExit):
centerline_width.saveCenterlineMAT(river_object=river_class_example, save_to_mat=None)
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [save_to_mat]: Requires mat filename"

def test_saveCenterlineMAT_matRequired(caplog):
with pytest.raises(SystemExit):
centerline_width.saveCenterlineMAT(river_object=river_class_example, save_to_mat="filename.txt")
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [save_to_mat]: Extension must be a .mat file, current extension = 'txt'"

@pytest.mark.parametrize("latitude_header_invalid, latitude_header_error_output", invalid_non_str_options)
def test_saveCenterlineMAT_latitudeHeaderTypeInvalidTypes(caplog, latitude_header_invalid, latitude_header_error_output):
with pytest.raises(SystemExit):
centerline_width.saveCenterlineMAT(river_object=river_class_example,
save_to_mat="testing.mat",
latitude_header=latitude_header_invalid)
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [latitude_header]: Must be a str, current type = '{0}'".format(latitude_header_error_output)

def test_saveCenterlineMAT_latitudeHeaderTypeInvalidAlphanumeric(caplog):
with pytest.raises(SystemExit):
centerline_width.saveCenterlineMAT(river_object=river_class_example,
save_to_mat="testing.mat",
latitude_header="invalid whitespace")
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [latitude_header]: Column names cannot contain any whitespace or non-alphanumeric characters, currently = 'invalid whitespace'"

@pytest.mark.parametrize("longitude_header_invalid, longitude_header_error_output", invalid_non_str_options)
def test_saveCenterlineMAT_longitudeHeaderTypeInvalidTypes(caplog, longitude_header_invalid, longitude_header_error_output):
with pytest.raises(SystemExit):
centerline_width.saveCenterlineMAT(river_object=river_class_example,
save_to_mat="testing.mat",
longitude_header=longitude_header_invalid)
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [longitude_header]: Must be a str, current type = '{0}'".format(longitude_header_error_output)

def test_saveCenterlineMAT_longitudeHeaderTypeInvalidAlphanumeric(caplog):
with pytest.raises(SystemExit):
centerline_width.saveCenterlineMAT(river_object=river_class_example,
save_to_mat="testing.mat",
longitude_header="invalid whitespace")
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [longitude_header]: Column names cannot contain any whitespace or non-alphanumeric characters, currently = 'invalid whitespace'"

@pytest.mark.parametrize("centerline_type_invalid, centerline_type_error_output", invalid_non_str_options)
def test_saveCenterlineMAT_centerlineTypeInvalidTypes(caplog, centerline_type_invalid, centerline_type_error_output):
with pytest.raises(SystemExit):
centerline_width.saveCenterlineMAT(river_object=river_class_example,
save_to_mat="testing.mat",
centerline_type=centerline_type_invalid)
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [centerline_type]: Must be a str, current type = '{0}'".format(centerline_type_error_output)

def test_saveCenterlineMAT_centerlineTypeInvalidOptions(caplog):
with pytest.raises(SystemExit):
centerline_width.saveCenterlineMAT(river_object=river_class_example,
save_to_mat="testing.mat",
centerline_type="not valid")
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [centerline_type]: Must be an available option in ['Voronoi', 'Evenly Spaced', 'Smoothed', 'Equal Distance'], current option = 'not valid'"
12 changes: 12 additions & 0 deletions centerline_width/pytests/test_riverCenterlineClass.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
([], "<class 'list'>"),
(False, "<class 'bool'>")]

invalid_non_num_options = [("testing_string", "<class 'str'>"),
([], "<class 'list'>"),
(False, "<class 'bool'>")]

invalid_non_str_options = [(1961, "<class 'int'>"),
(3.1415, "<class 'float'>"),
([], "<class 'list'>"),
Expand Down Expand Up @@ -63,3 +67,11 @@ def test_riverCenterline_interpolateNInvalidTypes(caplog, interpolate_n_invalid,
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [interpolate_n]: Must be a int, current type = '{0}'".format(interpolate_n_error_output)

@pytest.mark.parametrize("equal_distance_invalid, equal_distance_error_output", invalid_non_num_options)
def test_riverCenterline_equalDistanceInvalidTypes(caplog, equal_distance_invalid, equal_distance_error_output):
with pytest.raises(SystemExit):
centerline_width.riverCenterline(csv_data="csv_example.csv", equal_distance=equal_distance_invalid)
log_record = caplog.records[0]
assert log_record.levelno == logging.CRITICAL
assert log_record.message == "\nCRITICAL ERROR, [equal_distance]: Must be a int or float, current type = '{0}'".format(equal_distance_error_output)
3 changes: 2 additions & 1 deletion centerline_width/riverCenterlineClass.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ def __init__(self,
optional_cutoff=optional_cutoff,
interpolate_data=interpolate_data,
interpolate_n=interpolate_n,
interpolate_n_centerpoints=interpolate_n_centerpoints)
interpolate_n_centerpoints=interpolate_n_centerpoints,
equal_distance=equal_distance)

# Description and dataframe
self.river_name = csv_data
Expand Down

0 comments on commit fc2d73d

Please sign in to comment.