From 018b6147a0aed924ba394f8ca446a41a7128d095 Mon Sep 17 00:00:00 2001 From: Daniel Crawl Date: Sat, 25 May 2024 13:04:12 -0700 Subject: [PATCH 1/4] fix: heat_depth can be null --- driptorch/firing/strip_contour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driptorch/firing/strip_contour.py b/driptorch/firing/strip_contour.py index 1944e7a..a6ea15e 100644 --- a/driptorch/firing/strip_contour.py +++ b/driptorch/firing/strip_contour.py @@ -112,7 +112,7 @@ def _init_paths(self, paths: dict, **kwargs) -> dict: source_line, neighborhood_size=1, z_multiplier=elevation_influence) # Determine level set values for ignition path slicing - if heat_depth == depth: + if not heat_depth or heat_depth == depth: levels = range(depth, int(np.max(cost_distance.data)), depth) else: levels = [depth] From c7de95391fba5c71aabeaeb1ad3be29c859f6681 Mon Sep 17 00:00:00 2001 From: Daniel Crawl Date: Sun, 18 Aug 2024 16:57:27 -0700 Subject: [PATCH 2/4] bumping gcsfs to match s3fs version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 53e054a..b8b14ca 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ 'pandas==1.4.2', 'pyproj==3.3.1', 'folium==0.12.1.post1', - 'gcsfs==2022.1.0', + 'gcsfs==2024.2.0', 'zarr==2.13.3', 'scipy==1.9.3', 'scikit-image==0.19.3' From f6afd7b6e6abdf7aa5a2c3da556e14db58eba01c Mon Sep 17 00:00:00 2001 From: Daniel Crawl Date: Sun, 18 Aug 2024 16:58:36 -0700 Subject: [PATCH 3/4] adding max_time to to_json() for max segment time (#148) --- driptorch/pattern.py | 50 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/driptorch/pattern.py b/driptorch/pattern.py index 7c4bb66..711c35c 100644 --- a/driptorch/pattern.py +++ b/driptorch/pattern.py @@ -133,8 +133,14 @@ def empty_path_dict() -> dict: return {"heat": [], "igniter": [], "leg": [], "geometry": []} - def to_json(self) -> dict: + def to_json(self, max_time=None) -> dict: """Write the Pattern to a GeoJSON dictionary + + Parameters + ---------- + max_time : int + Maximum time (seconds) for any two line segments. Does not + split line segments. Returns ------- @@ -155,6 +161,48 @@ def to_json(self) -> dict: # Read the jagged times array as an Awkward array for vectorized operations times = ak.Array(times) + + if max_time is not None: + + new_times = [] + new_geoms = [] + new_heat = [] + new_igniter = [] + new_leg = [] + + for i in range(len(times)): + + segment_times = [] + segment_coords = [] + prev_time = 0 + + for j in range(len(times[i])): + + if times[i][j] - prev_time > max_time and len(segment_times) > 1: + + new_times.append(segment_times) + new_geoms.append(LineString(segment_coords)) + + segment_times = [prev_time] + segment_coords = [self.geometry[i].coords[j-1]] + + prev_time = times[i][j] + + segment_times.append(times[i][j]) + segment_coords.append(self.geometry[i].coords[j]) + + new_heat.append(self.heat[i]) + new_igniter.append(self.heat[i]) + new_leg.append(self.heat[i]) + + new_times.append(segment_times) + new_geoms.append(LineString(segment_coords)) + + times = ak.Array(new_times) + self.geometry = new_geoms + self.heat = new_heat + self.igniter = new_igniter + self.leg = new_leg # Convert to milliseconds since Epoch (this is what Leaflet wants) times = (times * 1000) + (unix_time() * 1000) From 1e70a63d7467d95a34b14312f308b9458d6f2bae Mon Sep 17 00:00:00 2001 From: Daniel Crawl Date: Tue, 20 Aug 2024 12:01:25 -0700 Subject: [PATCH 4/4] moving line segmentation to write_geojson() --- driptorch/io.py | 76 +++++++++++++++++++++++++++++++++++++++++++- driptorch/pattern.py | 52 +++--------------------------- 2 files changed, 80 insertions(+), 48 deletions(-) diff --git a/driptorch/io.py b/driptorch/io.py index 6eb1fe3..8f66b4c 100644 --- a/driptorch/io.py +++ b/driptorch/io.py @@ -15,6 +15,8 @@ from shapely.geometry.base import BaseGeometry from shapely.ops import transform from typing import Union +import json +import copy class Projector: @@ -183,7 +185,7 @@ def read_geojson_polygon(geojson: dict) -> Polygon: def write_geojson(geometries: list[BaseGeometry], src_epsg: int, dst_epsg: int = 4326, properties={}, - style={}, elapsed_time=None) -> dict: + style={}, elapsed_time=None, max_line_segment_time=None) -> dict: """Write a list of shapely geometries to GeoJSON Parameters @@ -200,6 +202,8 @@ def write_geojson(geometries: list[BaseGeometry], src_epsg: int, dst_epsg: int = Rendering style applied to all features. Defaults to {}. elapsed_time : float, optional Time elapsed during the firing operation. Defaults to None. + max_line_segment_time : int, optional + Maximum time (in milliseconds) for a line segment. Returns ------- @@ -229,6 +233,76 @@ def write_geojson(geometries: list[BaseGeometry], src_epsg: int, dst_epsg: int = } ) + if max_line_segment_time is not None and 'times' in features[0]['properties']: + + new_features = [] + + for feature in features: + + # only split lines + if feature['geometry']['type'] != 'LineString': + new_features.append(copy.deepcopy(feature)) + continue + + times = feature['properties']['times'] + coords = feature['geometry']['coordinates'] + prev_idx = 0 + cur_idx = 0 + + while cur_idx < len(times): + + while times[cur_idx] - times[prev_idx] > max_line_segment_time: + + # create a new feature ending at previous time + max time + end_time = times[prev_idx] + max_line_segment_time + new_times = times[prev_idx:cur_idx] + [end_time] + + fraction = max_line_segment_time / (times[cur_idx] - times[prev_idx]) + + # calculate distance for accumulated line + distance = LineString(coords[prev_idx:cur_idx+1]).length + + max_distance = distance * fraction + + # find the segment where the distance along accumulated line + # is past the max distance + accum_idx = prev_idx + accum_distance = 0 + while accum_distance < max_distance: + next_distance = Point(coords[accum_idx]).distance(Point(coords[accum_idx+1])) + accum_distance += next_distance + accum_idx += 1 + + subfraction = (max_distance - (accum_distance - next_distance)) / next_distance + + new_x = coords[accum_idx-1][0] + (coords[accum_idx][0] - coords[accum_idx-1][0]) * subfraction + new_y = coords[accum_idx-1][1] + (coords[accum_idx][1] - coords[accum_idx-1][1]) * subfraction + end_coords = (new_x, new_y) + new_coords = coords[prev_idx:accum_idx] + (end_coords,) + + new_feature = copy.deepcopy(feature) + new_feature['properties']['times'] = new_times + new_feature['geometry']['coordinates'] = new_coords + new_features.append(new_feature) + + # modify current feature to start at previous time + max_line_segment_time + feature['properties']['times'] = [end_time] + times[accum_idx:] + feature['geometry']['coordinates'] = (end_coords,) + coords[accum_idx:] + times = feature['properties']['times'] + coords = feature['geometry']['coordinates'] + + prev_idx = 0 + cur_idx = 0 + + cur_idx += 1 + + + if len(times) > 0: + new_features.append(copy.deepcopy(feature)) + + + features = new_features + # Compile the features in a feature collection geojson = { 'type': 'FeatureCollection', diff --git a/driptorch/pattern.py b/driptorch/pattern.py index 711c35c..79f4b76 100644 --- a/driptorch/pattern.py +++ b/driptorch/pattern.py @@ -133,14 +133,13 @@ def empty_path_dict() -> dict: return {"heat": [], "igniter": [], "leg": [], "geometry": []} - def to_json(self, max_time=None) -> dict: + def to_json(self, max_line_segment_time=None) -> dict: """Write the Pattern to a GeoJSON dictionary - + Parameters ---------- - max_time : int - Maximum time (seconds) for any two line segments. Does not - split line segments. + max_line_segment_time : int, optional + Maximum time (in milliseconds) for a line segment. Returns ------- @@ -161,48 +160,6 @@ def to_json(self, max_time=None) -> dict: # Read the jagged times array as an Awkward array for vectorized operations times = ak.Array(times) - - if max_time is not None: - - new_times = [] - new_geoms = [] - new_heat = [] - new_igniter = [] - new_leg = [] - - for i in range(len(times)): - - segment_times = [] - segment_coords = [] - prev_time = 0 - - for j in range(len(times[i])): - - if times[i][j] - prev_time > max_time and len(segment_times) > 1: - - new_times.append(segment_times) - new_geoms.append(LineString(segment_coords)) - - segment_times = [prev_time] - segment_coords = [self.geometry[i].coords[j-1]] - - prev_time = times[i][j] - - segment_times.append(times[i][j]) - segment_coords.append(self.geometry[i].coords[j]) - - new_heat.append(self.heat[i]) - new_igniter.append(self.heat[i]) - new_leg.append(self.heat[i]) - - new_times.append(segment_times) - new_geoms.append(LineString(segment_coords)) - - times = ak.Array(new_times) - self.geometry = new_geoms - self.heat = new_heat - self.igniter = new_igniter - self.leg = new_leg # Convert to milliseconds since Epoch (this is what Leaflet wants) times = (times * 1000) + (unix_time() * 1000) @@ -224,6 +181,7 @@ def to_json(self, max_time=None) -> dict: properties=props, style=style, elapsed_time=self.elapsed_time, + max_line_segment_time=max_line_segment_time ) def translate(self, x_off: float, y_off: float) -> Pattern: