From 4c7d0537704ea1ae2aa5e91588b6ebd422aa80cf Mon Sep 17 00:00:00 2001 From: Sharon Fitzpatrick Date: Wed, 10 Jul 2024 11:56:40 -0700 Subject: [PATCH] Fix #271 --- src/coastseg/common.py | 16 +++---- tests/test_common.py | 101 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 11 deletions(-) diff --git a/src/coastseg/common.py b/src/coastseg/common.py index e0bbdd15..105eb3ed 100644 --- a/src/coastseg/common.py +++ b/src/coastseg/common.py @@ -1608,6 +1608,7 @@ def export_dataframe_as_geojson(data:pd.DataFrame, output_file_path:str, x_col:s def create_complete_line_string(points): """ Create a complete LineString from a list of points. + If there is only a single point in the list, a Point object is returned instead of a LineString. Args: points (numpy.ndarray): An array of points representing the coordinates. @@ -1653,6 +1654,9 @@ def create_complete_line_string(points): current_point = nearest_point # Convert the sorted list of points to a LineString + if len(sorted_points) < 2: + return Point(sorted_points[0]) + return LineString(sorted_points) def order_linestrings_gdf(gdf,dates, output_crs='epsg:4326'): @@ -1682,6 +1686,7 @@ def order_linestrings_gdf(gdf,dates, output_crs='epsg:4326'): lines.append(line_string) gdf = gpd.GeoDataFrame({'geometry': lines,'date': dates},crs=output_crs) + return gdf @@ -1731,9 +1736,7 @@ def add_shore_points_to_timeseries(timeseries_data: pd.DataFrame, shore_y_utm = first[1]+distances*np.sin(angle) # points_utm = [shapely.Point(xy) for xy in zip(shore_x_utm, shore_y_utm)] - # #conversion from utm to wgs84, put them in the transect_timeseries csv and utm gdf - # dummy_gdf_utm = gpd.GeoDataFrame({'geometry':points_utm}, - # crs=utm_crs) + #conversion from utm to wgs84, put them in the transect_timeseries csv and utm gdf points_utm = gpd.GeoDataFrame({'geometry': [Point(x, y) for x, y in zip(shore_x_utm, shore_y_utm)]}, crs=utm_crs) # Convert shore points to WGS84 points_wgs84 = points_utm.to_crs(org_crs) @@ -1743,13 +1746,6 @@ def add_shore_points_to_timeseries(timeseries_data: pd.DataFrame, # Update timeseries data with shore_x and shore_y timeseries_data.loc[idx, 'shore_x'] = coords_wgs84[:, 0] timeseries_data.loc[idx, 'shore_y'] = coords_wgs84[:, 1] - # points_wgs84 = [shapely.get_coordinates(p) for p in dummy_gdf_wgs84.geometry] - # points_wgs84 = np.array(points_wgs84) - # points_wgs84 = points_wgs84.reshape(len(points_wgs84),2) - # x_wgs84 = points_wgs84[:,0] - # y_wgs84 = points_wgs84[:,1] - # timeseries_data.loc[idxes,'shore_x'] = x_wgs84 - # timeseries_data.loc[idxes,'shore_y'] = y_wgs84 return timeseries_data diff --git a/tests/test_common.py b/tests/test_common.py index c94d8e67..d291ba53 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -22,6 +22,87 @@ from typing import Dict, List, Union from unittest.mock import patch +def test_order_linestrings_gdf_empty(): + gdf = gpd.GeoDataFrame({'geometry': []}) + dates = [] + result = common.order_linestrings_gdf(gdf, dates) + assert result.empty, "Expected an empty GeoDataFrame for empty input" + +def test_order_linestrings_gdf_single_linestring(): + points = np.array([[0, 0], [1, 1]]) + gdf = gpd.GeoDataFrame({'geometry': [LineString(points)]}, crs='epsg:4326') + dates = ['2023-01-01'] + result = common.order_linestrings_gdf(gdf, dates) + expected_line = common.create_complete_line_string(points) + assert len(result) == 1, "Expected one linestring in the result" + assert result.iloc[0].geometry.equals(expected_line), "The geometry of the linestring is incorrect" + assert result.iloc[0].date == '2023-01-01', "The date is incorrect" + +def test_order_linestrings_gdf_multiple_linestrings(): + points1 = np.array([[0, 0], [1, 1]]) + points2 = np.array([[1, 1], [2, 2]]) + gdf = gpd.GeoDataFrame({'geometry': [LineString(points1), LineString(points2)]}, crs='epsg:4326') + dates = ['2023-01-01', '2023-01-02'] + result = common.order_linestrings_gdf(gdf, dates) + expected_line1 = common.create_complete_line_string(points1) + expected_line2 = common.create_complete_line_string(points2) + assert len(result) == 2, "Expected two linestrings in the result" + assert result.iloc[0].geometry.equals(expected_line1), "The geometry of the first linestring is incorrect" + assert result.iloc[1].geometry.equals(expected_line2), "The geometry of the second linestring is incorrect" + assert result.iloc[0].date == '2023-01-01', "The first date is incorrect" + assert result.iloc[1].date == '2023-01-02', "The second date is incorrect" + +# def test_order_linestrings_gdf_crs_conversion(): +# points = np.array([[0, 0], [1, 1]]) +# gdf = gpd.GeoDataFrame({'geometry': [LineString(points)]}, crs='epsg:3857') +# dates = ['2023-01-01'] +# result = common.order_linestrings_gdf(gdf, dates, output_crs='epsg:4326') +# assert result.crs.to_string() == 'epsg:4326', "The CRS is not converted to 'epsg:4326'" + +def test_order_linestrings_gdf_duplicate_points(): + points = np.array([[0, 0], [1, 1], [1, 1], [2, 2]]) + gdf = gpd.GeoDataFrame({'geometry': [LineString(points)]}, crs='epsg:4326') + dates = ['2023-01-01'] + result = common.order_linestrings_gdf(gdf, dates) + expected_line = common.create_complete_line_string(points) + assert len(result) == 1, "Expected one linestring in the result" + assert result.iloc[0].geometry.equals(expected_line), "The geometry of the linestring is incorrect" + assert result.iloc[0].date == '2023-01-01', "The date is incorrect" + + +def test_no_points(): + points = np.array([]) + result = common.create_complete_line_string(points) + assert result is None, "Expected None for no points" + +def test_single_point(): + points = np.array([[1, 1]]) + result = common.create_complete_line_string(points) + assert isinstance(result, Point), "Expected Point for a single point" + assert result.x == 1 and result.y == 1, "Expected Point with coordinates (1,1)" + +def test_multiple_points_straight_line(): + points = np.array([[0, 0], [1, 1], [2, 2]]) + result = common.create_complete_line_string(points) + assert isinstance(result, LineString), "Expected LineString for multiple points" + expected_coords = [(0, 0), (1, 1), (2, 2)] + assert list(result.coords) == expected_coords, "Expected coordinates to be in a straight line" + +def test_multiple_points_non_straight_line(): + points = np.array([[0, 0], [2, 2], [1, 1], [3, 3]]) + result = common.create_complete_line_string(points) + assert isinstance(result, LineString), "Expected LineString for multiple points" + expected_coords = [(0, 0), (1, 1), (2, 2), (3, 3)] + assert list(result.coords) == expected_coords, "Expected coordinates to be in a sorted line" + +def test_duplicate_points(): + points = np.array([[0, 0], [1, 1], [1, 1], [2, 2]]) + result = common.create_complete_line_string(points) + assert isinstance(result, LineString), "Expected LineString for multiple points" + expected_coords = [(0, 0), (1, 1), (2, 2)] + assert list(result.coords) == expected_coords, "Expected coordinates to be unique and sorted" + + def test_get_missing_roi_dirs(): roi_settings = { "mgm8": { @@ -2236,4 +2317,22 @@ def test_convert_points_to_linestrings_not_enough_pts(): linestrings_gdf = convert_points_to_linestrings(gdf, output_crs=output_crs) # Check the result - assert len(linestrings_gdf) == 0 \ No newline at end of file + assert len(linestrings_gdf) == 0 + +def test_convert_points_to_linestrings_single_point_per_date(): + # Create a GeoDataFrame with points + points = [Point(0, 0), Point(1, 1)] + gdf = gpd.GeoDataFrame(geometry=points,) + # this should cause the last point to be filtered out because it doesn't have a another points with a matching date + gdf['date'] = ['1/1/2020','1/1/2020'] + + + # Set an initial CRS + gdf.crs = "EPSG:4326" + + # Convert points to LineStrings with a different CRS + output_crs = "EPSG:3857" + linestrings_gdf = convert_points_to_linestrings(gdf, output_crs=output_crs) + + # Check the result + assert len(linestrings_gdf) == 1 \ No newline at end of file