Commits (2)
...@@ -99,7 +99,7 @@ def set_plot(bounds, to_local_coordinates): ...@@ -99,7 +99,7 @@ def set_plot(bounds, to_local_coordinates):
return ax return ax
def place_trees(forest, ways, region, to_local, tree_distance, min_distance_2): def place_trees(forest, ways, region, to_local, tree_distance, min_distance_2) -> Forest:
local_region = transform(to_local.transform, region) local_region = transform(to_local.transform, region)
for way in ways: for way in ways:
...@@ -136,15 +136,19 @@ def place_trees(forest, ways, region, to_local, tree_distance, min_distance_2): ...@@ -136,15 +136,19 @@ def place_trees(forest, ways, region, to_local, tree_distance, min_distance_2):
y = potential_tree.y y = potential_tree.y
if local_region.contains(geometry.Point(x, y)): if local_region.contains(geometry.Point(x, y)):
forest.add_tree_if_possible(min_distance_2, x, y, forest.add_tree_if_possible(min_distance_2, x, y,
color='red', color='#DFFF00',
description='fake_tree' type='Fake Tree',
description='Tilia tomentosa'
radius=3
) )
return forest.xs_ys return forest
def plot_trees(bounds, tree_xs, tree_ys, tree_distance): def plot_trees(bounds, forest, tree_distance) -> None:
plt.scatter(tree_xs, tree_ys, s=2, c='green') print("Exporting diagram...")
tree_xs, tree_ys, colors = forest.xs_ys_cs
plt.scatter(tree_xs, tree_ys, s=2, c=colors)
plt.grid(True) plt.grid(True)
plt.title(f"{bounds}\nTree distance : {tree_distance} m") plt.title(f"{bounds}\nTree distance : {tree_distance} m")
...@@ -153,25 +157,25 @@ def plot_trees(bounds, tree_xs, tree_ys, tree_distance): ...@@ -153,25 +157,25 @@ def plot_trees(bounds, tree_xs, tree_ys, tree_distance):
SCRIPT_DIR / f"{get_basename(bounds)}.png", bbox_inches='tight', dpi=300) SCRIPT_DIR / f"{get_basename(bounds)}.png", bbox_inches='tight', dpi=300)
def export_map(bounds, tree_xs, tree_ys, epsg_id): def export_map(bounds, forest, epsg_id) -> None:
print("Exporting Map...")
to_wgs84 = Transformer.from_crs(f"EPSG:{epsg_id}", "EPSG:4326", always_xy=True) to_wgs84 = Transformer.from_crs(f"EPSG:{epsg_id}", "EPSG:4326", always_xy=True)
interactive_map = folium.Map() interactive_map = folium.Map()
interactive_map.fit_bounds([(bounds.S, bounds.W), (bounds.N, bounds.E)]) interactive_map.fit_bounds([(bounds.S, bounds.W), (bounds.N, bounds.E)])
radius = 2 # [m] for tree in forest:
for x, y in zip(tree_xs, tree_ys): lon, lat = to_wgs84.transform(tree.x, tree.y)
lon, lat = to_wgs84.transform(x, y)
folium.Circle( folium.Circle(
location=[lat, lon], location=[lat, lon],
radius=radius, radius=tree.radius or 2, # [m], when defined
color="black", color="black",
weight=1, weight=1,
fill_opacity=0.9, fill_opacity=0.9,
opacity=1, opacity=1,
fill_color="#00ff15", fill_color=tree.color,
fill=False, # gets overridden by fill_color fill=False, # gets overridden by fill_color
popup="{} meters".format(radius), popup=repr(tree),
tooltip="I am a tree in street STREET", tooltip=str(tree),
).add_to(interactive_map) ).add_to(interactive_map)
folium.TileLayer( folium.TileLayer(
...@@ -183,23 +187,25 @@ def export_map(bounds, tree_xs, tree_ys, epsg_id): ...@@ -183,23 +187,25 @@ def export_map(bounds, tree_xs, tree_ys, epsg_id):
).add_to(interactive_map) ).add_to(interactive_map)
interactive_map.save(f"{get_basename(bounds)}_trees.html") interactive_map.save(f"{get_basename(bounds)}_trees.html")
print(" DONE!")
def export_csv(bounds, tree_xs, tree_ys, wkt_polygon, tree_distance, min_distance, epsg_id): def export_csv(bounds, forest, wkt_polygon, tree_distance, min_distance, epsg_id) -> None:
print("Exporting CSV...")
with open(SCRIPT_DIR / f"{get_basename(bounds)}_trees.csv", "w") as csv: with open(SCRIPT_DIR / f"{get_basename(bounds)}_trees.csv", "w") as csv:
csv.write(f"# Fake trees for; {wkt_polygon}\n") csv.write(f"# Fake trees for; {wkt_polygon}\n")
csv.write(f"# Tree distance along roads; {tree_distance}; [m]\n") csv.write(f"# Tree distance along roads; {tree_distance}; [m]\n")
csv.write(f"# Minimum allowed distance between trees; {min_distance}; [m]\n") csv.write(f"# Minimum allowed distance between trees; {min_distance}; [m]\n")
csv.write(f"# EPSG; {epsg_id}\n") csv.write(f"# EPSG; {epsg_id}\n")
csv.write("# X; Y\n") csv.write("# X; Y; Type; Description; Radius\n")
csv.write("# [m]; [m]\n") csv.write("# [m]; [m]; [?]; [?]; [m]\n")
for x, y in zip(tree_xs, tree_ys): for tree in forest:
csv.write(f"{x};{y}\n") csv.write(f"{tree.x};{tree.y};{tree.type};{tree.description};{tree.radius}\n")
print("DONE!") print(" DONE!")
def main(wkt_polygon, epsg_id, tree_distance, min_distance, import_tree_shp): def main(wkt_polygon, epsg_id, tree_distance, min_distance, import_tree_shp) -> None:
region, bounds = load_region(wkt_polygon) region, bounds = load_region(wkt_polygon)
ways = get_osm_roads(bounds) ways = get_osm_roads(bounds)
...@@ -213,12 +219,12 @@ def main(wkt_polygon, epsg_id, tree_distance, min_distance, import_tree_shp): ...@@ -213,12 +219,12 @@ def main(wkt_polygon, epsg_id, tree_distance, min_distance, import_tree_shp):
to_local = Transformer.from_crs("EPSG:4326", f"EPSG:{epsg_id}", always_xy=True) to_local = Transformer.from_crs("EPSG:4326", f"EPSG:{epsg_id}", always_xy=True)
set_plot(bounds, to_local) set_plot(bounds, to_local)
tree_xs, tree_ys = place_trees(existing_forest, ways, region, to_local, tree_distance, min_distance**2) forest = place_trees(existing_forest, ways, region, to_local, tree_distance, min_distance**2)
print(repr(existing_forest)) print(existing_forest)
plot_trees(bounds, tree_xs, tree_ys, tree_distance) plot_trees(bounds, forest, tree_distance)
export_map(bounds, tree_xs, tree_ys, epsg_id) export_map(bounds, forest, epsg_id)
export_csv(bounds, tree_xs, tree_ys, wkt_polygon, export_csv(bounds, forest, wkt_polygon,
tree_distance, min_distance, epsg_id) tree_distance, min_distance, epsg_id)
......
...@@ -4,7 +4,14 @@ from tree import Tree, Forest ...@@ -4,7 +4,14 @@ from tree import Tree, Forest
def get_existing_forest(shp_input): def get_existing_forest(shp_input):
print(f"Importing {shp_input}") print(f"Importing {shp_input}")
df = gpd.read_file(shp_input) df = gpd.read_file(shp_input)
trees = [Tree(p.x, p.y) for p in df.geometry] trees = []
for tree_row in df.itertuples():
point = tree_row.geometry
trees.append(Tree(point.x, point.y,
description=tree_row.Bezeichnun,
radius=tree_row.Kroneradi,
type=tree_row.Baumart
))
return Forest(trees) return Forest(trees)
if __name__ == "__main__": if __name__ == "__main__":
......
from dataclasses import dataclass from dataclasses import dataclass
from collections import UserList
import kdtree import kdtree
...@@ -9,8 +10,8 @@ class Tree: ...@@ -9,8 +10,8 @@ class Tree:
z: float = 0 z: float = 0
height: float = None height: float = None
radius: float = None radius: float = None
description: str = None
type: str = None type: str = None
description: str = '?'
color: str = 'green' color: str = 'green'
def __len__(self): def __len__(self):
...@@ -19,32 +20,36 @@ class Tree: ...@@ -19,32 +20,36 @@ class Tree:
def __getitem__(self, i): def __getitem__(self, i):
return [self.x, self.y][i] return [self.x, self.y][i]
def __str__(self):
return f"{self.type} ({self.description}), {self.radius or '?'} m (X={self.x:.1f}, Y={self.y:.1f})"
class Forest: class Forest(UserList):
def __init__(self, existing_trees=[]): def __init__(self, existing_trees=[]):
if existing_trees: if existing_trees:
self.kd_tree = kdtree.create(existing_trees, dimensions=2) self.kd_tree = kdtree.create(existing_trees, dimensions=2)
else: else:
self.kd_tree = kdtree.create([(0, 0)], dimensions=2) self.kd_tree = kdtree.create([(0, 0)], dimensions=2)
self.trees = existing_trees self.data = existing_trees
def add_tree_if_possible(self, min_distance_2, x, y, **kparams): def add_tree_if_possible(self, min_distance_2, x, y, **kparams):
_nearest_tree, distance_2 = self.kd_tree.search_nn((x, y)) _nearest_tree, distance_2 = self.kd_tree.search_nn((x, y))
if distance_2 > min_distance_2: if distance_2 > min_distance_2:
self.kd_tree.add((x, y)) self.kd_tree.add((x, y))
self.trees.append(Tree(x, y, **kparams)) self.append(Tree(x, y, **kparams))
@property @property
def xs_ys(self): def xs_ys_cs(self):
xs, ys = [], [] xs, ys, colors = [], [], []
for tree in self.trees: for tree in self:
xs.append(tree.x) xs.append(tree.x)
ys.append(tree.y) ys.append(tree.y)
return xs, ys colors.append(tree.color)
return xs, ys, colors
def __str__(self): def __str__(self):
return f"Forest with {len(self.trees)} trees." return f"Forest with {len(self)} trees."
def __repr__(self): def __repr__(self):
return "\n".join([str(self)] + [str(tree) for tree in self.trees]) return "\n".join([str(self)] + [str(tree) for tree in self])