Commits (2)
......@@ -99,7 +99,7 @@ def set_plot(bounds, to_local_coordinates):
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)
for way in ways:
......@@ -136,15 +136,19 @@ def place_trees(forest, ways, region, to_local, tree_distance, min_distance_2):
y = potential_tree.y
if local_region.contains(geometry.Point(x, y)):
forest.add_tree_if_possible(min_distance_2, x, y,
color='red',
description='fake_tree'
color='#DFFF00',
type='Fake Tree',
description='Tilia tomentosa'
radius=3
)
return forest.xs_ys
return forest
def plot_trees(bounds, tree_xs, tree_ys, tree_distance):
plt.scatter(tree_xs, tree_ys, s=2, c='green')
def plot_trees(bounds, forest, tree_distance) -> None:
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.title(f"{bounds}\nTree distance : {tree_distance} m")
......@@ -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)
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)
interactive_map = folium.Map()
interactive_map.fit_bounds([(bounds.S, bounds.W), (bounds.N, bounds.E)])
radius = 2 # [m]
for x, y in zip(tree_xs, tree_ys):
lon, lat = to_wgs84.transform(x, y)
for tree in forest:
lon, lat = to_wgs84.transform(tree.x, tree.y)
folium.Circle(
location=[lat, lon],
radius=radius,
radius=tree.radius or 2, # [m], when defined
color="black",
weight=1,
fill_opacity=0.9,
opacity=1,
fill_color="#00ff15",
fill_color=tree.color,
fill=False, # gets overridden by fill_color
popup="{} meters".format(radius),
tooltip="I am a tree in street STREET",
popup=repr(tree),
tooltip=str(tree),
).add_to(interactive_map)
folium.TileLayer(
......@@ -183,23 +187,25 @@ def export_map(bounds, tree_xs, tree_ys, epsg_id):
).add_to(interactive_map)
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:
csv.write(f"# Fake trees for; {wkt_polygon}\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"# EPSG; {epsg_id}\n")
csv.write("# X; Y\n")
csv.write("# [m]; [m]\n")
for x, y in zip(tree_xs, tree_ys):
csv.write(f"{x};{y}\n")
csv.write("# X; Y; Type; Description; Radius\n")
csv.write("# [m]; [m]; [?]; [?]; [m]\n")
for tree in forest:
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)
ways = get_osm_roads(bounds)
......@@ -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)
set_plot(bounds, to_local)
tree_xs, tree_ys = place_trees(existing_forest, ways, region, to_local, tree_distance, min_distance**2)
print(repr(existing_forest))
forest = place_trees(existing_forest, ways, region, to_local, tree_distance, min_distance**2)
print(existing_forest)
plot_trees(bounds, tree_xs, tree_ys, tree_distance)
export_map(bounds, tree_xs, tree_ys, epsg_id)
export_csv(bounds, tree_xs, tree_ys, wkt_polygon,
plot_trees(bounds, forest, tree_distance)
export_map(bounds, forest, epsg_id)
export_csv(bounds, forest, wkt_polygon,
tree_distance, min_distance, epsg_id)
......
......@@ -4,7 +4,14 @@ from tree import Tree, Forest
def get_existing_forest(shp_input):
print(f"Importing {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)
if __name__ == "__main__":
......
from dataclasses import dataclass
from collections import UserList
import kdtree
......@@ -9,8 +10,8 @@ class Tree:
z: float = 0
height: float = None
radius: float = None
description: str = None
type: str = None
description: str = '?'
color: str = 'green'
def __len__(self):
......@@ -19,32 +20,36 @@ class Tree:
def __getitem__(self, 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=[]):
if existing_trees:
self.kd_tree = kdtree.create(existing_trees, dimensions=2)
else:
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):
_nearest_tree, distance_2 = self.kd_tree.search_nn((x, y))
if distance_2 > min_distance_2:
self.kd_tree.add((x, y))
self.trees.append(Tree(x, y, **kparams))
self.append(Tree(x, y, **kparams))
@property
def xs_ys(self):
xs, ys = [], []
for tree in self.trees:
def xs_ys_cs(self):
xs, ys, colors = [], [], []
for tree in self:
xs.append(tree.x)
ys.append(tree.y)
return xs, ys
colors.append(tree.color)
return xs, ys, colors
def __str__(self):
return f"Forest with {len(self.trees)} trees."
return f"Forest with {len(self)} trees."
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])