Commit edb0a4a4 authored by Matthias Betz's avatar Matthias Betz
Browse files

Merge branch 'master' of transfer.hft-stuttgart.de:circulargreensimcity/circulargreensimcity

parents 0b3a4414 0842edf1
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
# Created by https://www.gitignore.io/api/java,maven,macos,linux,eclipse,windows,netbeans,intellij # Created by https://www.gitignore.io/api/java,maven,macos,linux,eclipse,windows,netbeans,intellij
# Edit at https://www.gitignore.io/?templates=java,maven,macos,linux,eclipse,windows,netbeans,intellij # Edit at https://www.gitignore.io/?templates=java,maven,macos,linux,eclipse,windows,netbeans,intellij
data/Marieke
data/*.gml
# User specific # User specific
.sonarlint/ .sonarlint/
......
Name
 .
├──  75Bäume
│ ├──  Baum_IST_Grombühl_alle.cpg
│ ├──  Baum_IST_Grombühl_alle.dbf
│ ├──  Baum_IST_Grombühl_alle.prj
│ ├──  Baum_IST_Grombühl_alle.sbn
│ ├──  Baum_IST_Grombühl_alle.sbx
│ ├──  Baum_IST_Grombühl_alle.shp
│ ├── 謹 Baum_IST_Grombühl_alle.shp.xml
│ ├──  Baum_IST_Grombühl_alle.shx
│ ├──  Grombuehl_Block_15-17.gml
│ ├──  trees_75_15.cpg
│ ├──  trees_75_15.dbf
│ ├──  trees_75_15.prj
│ ├──  trees_75_15.sbn
│ ├──  trees_75_15.sbx
│ ├──  trees_75_15.shp
│ ├── 謹 trees_75_15.shp.xml
│ └──  trees_75_15.shx
├──  250Bäume
│ ├──  Abstand5m
│ │ ├──  Grombuehl_Block_15-17.gml
│ │ ├──  trees_250_5.cpg
│ │ ├──  trees_250_5.dbf
│ │ ├──  trees_250_5.prj
│ │ ├──  trees_250_5.sbn
│ │ ├──  trees_250_5.sbx
│ │ ├──  trees_250_5.shp
│ │ ├── 謹 trees_250_5.shp.xml
│ │ └──  trees_250_5.shx
│ ├──  Abstand15m
│ │ ├──  49_801844__49_802502__9_948075__9_949534_15m_Block24
│ │ │ ├──  Grombuehl_Block_24.gml
│ │ │ ├──  trees_250_15_kl.cpg
│ │ │ ├──  trees_250_15_kl.dbf
│ │ │ ├──  trees_250_15_kl.prj
│ │ │ ├──  trees_250_15_kl.shp
│ │ │ └──  trees_250_15_kl.shx
│ │ └──  49_802605__49_804087__9_94036__9_948417_15m_Block7-11_14-17
│ │ ├──  Grombuehl_Block_7-11_14-17.gml
│ │ ├──  trees_150_15_gr.cpg
│ │ ├──  trees_150_15_gr.dbf
│ │ ├──  trees_150_15_gr.prj
│ │ ├──  trees_150_15_gr.sbn
│ │ ├──  trees_150_15_gr.sbx
│ │ ├──  trees_150_15_gr.shp
│ │ ├── 謹 trees_150_15_gr.shp.xml
│ │ └──  trees_150_15_gr.shx
│ ├──  Baum_IST_Grombühl_alle.cpg
│ ├──  Baum_IST_Grombühl_alle.dbf
│ ├──  Baum_IST_Grombühl_alle.prj
│ ├──  Baum_IST_Grombühl_alle.sbn
│ ├──  Baum_IST_Grombühl_alle.sbx
│ ├──  Baum_IST_Grombühl_alle.shp
│ ├── 謹 Baum_IST_Grombühl_alle.shp.xml
│ └──  Baum_IST_Grombühl_alle.shx
├──  500Bäume
│ ├──  Abstand10m
│ │ ├──  49_801844__49_802502__9_948075__9_949534_10m_block_24
│ │ │ ├──  Grombuehl_Block_24.gml
│ │ │ ├──  trees_500_10_kl.cpg
│ │ │ ├──  trees_500_10_kl.dbf
│ │ │ ├──  trees_500_10_kl.prj
│ │ │ ├──  trees_500_10_kl.shp
│ │ │ └──  trees_500_10_kl.shx
│ │ └──  49_802605__49_804087__9_94036__9_948417_10m_block_7-11_14-17
│ │ ├──  Grombuehl_Block_7-11_14-17.gml
│ │ ├──  trees_500_10_gr.cpg
│ │ ├──  trees_500_10_gr.dbf
│ │ ├──  trees_500_10_gr.prj
│ │ ├──  trees_500_10_gr.sbn
│ │ ├──  trees_500_10_gr.sbx
│ │ ├──  trees_500_10_gr.shp
│ │ ├── 謹 trees_500_10_gr.shp.xml
│ │ └──  trees_500_10_gr.shx
│ ├──  Baum_IST_Grombühl_alle.cpg
│ ├──  Baum_IST_Grombühl_alle.dbf
│ ├──  Baum_IST_Grombühl_alle.prj
│ ├──  Baum_IST_Grombühl_alle.sbn
│ ├──  Baum_IST_Grombühl_alle.sbx
│ ├──  Baum_IST_Grombühl_alle.shp
│ ├── 謹 Baum_IST_Grombühl_alle.shp.xml
│ └──  Baum_IST_Grombühl_alle.shx
├──  file_structure.txt
└──  ganzes_gebiet
├──  Grombühl_BA_IST.gml
├──  trees_75_15.cpg
├──  trees_75_15.dbf
├──  trees_75_15.prj
├──  trees_75_15.sbn
├──  trees_75_15.sbx
├──  trees_75_15.shp
├── 謹 trees_75_15.shp.xml
├──  trees_75_15.shx
├──  trees_250_5.cpg
├──  trees_250_5.dbf
├──  trees_250_5.prj
├──  trees_250_5.sbn
├──  trees_250_5.sbx
├──  trees_250_5.shp
├── 謹 trees_250_5.shp.xml
├──  trees_250_5.shx
├──  trees_250_15.cpg
├──  trees_250_15.dbf
├──  trees_250_15.prj
├──  trees_250_15.sbn
├──  trees_250_15.sbx
├──  trees_250_15.shp
├── 謹 trees_250_15.shp.xml
├──  trees_250_15.shx
├──  trees_500_10.cpg
├──  trees_500_10.dbf
├──  trees_500_10.prj
├──  trees_500_10.sbn
├──  trees_500_10.sbx
├──  trees_500_10.shp
├──  trees_500_10.shx
├── 謹 trees_500_10.xml
├──  trees_500_15.cpg
├──  trees_500_15.dbf
├──  trees_500_15.prj
├──  trees_500_15.sbn
├──  trees_500_15.sbx
├──  trees_500_15.shp
├── 謹 trees_500_15.shp.xml
└──  trees_500_15.shx
...@@ -7,7 +7,6 @@ import java.util.ArrayList; ...@@ -7,7 +7,6 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.geotools.data.DataStore; import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder; import org.geotools.data.DataStoreFinder;
import org.geotools.data.FeatureSource; import org.geotools.data.FeatureSource;
...@@ -17,7 +16,9 @@ import org.locationtech.jts.geom.Point; ...@@ -17,7 +16,9 @@ import org.locationtech.jts.geom.Point;
import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.simple.SimpleFeatureType;
public class TreeKatasterData {
public class TreeKatasterData
{
public static final double TRUNK_PERCENTAGE = 0.2; public static final double TRUNK_PERCENTAGE = 0.2;
public static final double CROWN_PERCENTAGE = 1 - TRUNK_PERCENTAGE; public static final double CROWN_PERCENTAGE = 1 - TRUNK_PERCENTAGE;
......
...@@ -17,7 +17,9 @@ import org.xmlobjects.gml.model.geometry.aggregates.MultiSurface; ...@@ -17,7 +17,9 @@ import org.xmlobjects.gml.model.geometry.aggregates.MultiSurface;
import org.xmlobjects.gml.model.geometry.aggregates.MultiSurfaceProperty; import org.xmlobjects.gml.model.geometry.aggregates.MultiSurfaceProperty;
import org.xmlobjects.gml.model.measures.Length; import org.xmlobjects.gml.model.measures.Length;
public class TreeUtils {
public class TreeUtils
{
private static final GeometryFactory FACTORY = new GeometryFactory(); private static final GeometryFactory FACTORY = new GeometryFactory();
...@@ -36,10 +38,10 @@ public class TreeUtils { ...@@ -36,10 +38,10 @@ public class TreeUtils {
// generateTreesFromKataster(cityModel, katasterData2); // generateTreesFromKataster(cityModel, katasterData2);
// All kataster trees are taken, osm trees are removed // All kataster trees are taken, osm trees are removed
// filterDuplicateTreesFromOSM(osmData, katasterData); // filterDuplicateTreesFromOSM(osmData, katasterData);
// filterDuplicateTreesFromOSM(osmData, katasterData2); // filterDuplicateTreesFromOSM(osmData, katasterData2);
// generateTreesFromOSM(cityModel, osmData, wktPolygon); // generateTreesFromOSM(cityModel, osmData, wktPolygon);
} }
......
package de.hft.stuttgart.scripts;
import java.io.File;
import java.io.IOException;
import org.citygml4j.xml.CityGMLContextException;
import org.citygml4j.xml.reader.CityGMLReadException;
import org.citygml4j.xml.writer.CityGMLWriteException;
import org.locationtech.jts.io.ParseException;
import de.hft.stuttgart.citygml.green.osm.GreenEnricher;
import jakarta.xml.bind.JAXBException;
public class PrepareDataForMarieke
{
// Add Trees to GML files inside data/Marieke/...
public static void main(String[] whatever) throws Exception {
processWholeRegion();
processSmallBlocks();
processStatusQuo();
}
private static void processStatusQuo() throws IOException, CityGMLContextException, CityGMLReadException,
InterruptedException, CityGMLWriteException, JAXBException, ParseException {
File folder = new File("data/Marieke/GebietsausschnittStatusQuo/");
File shapefile = new File("data/Marieke/GebietsausschnittStatusQuo/Baum_alle.shp");
File[] gmls = folder.listFiles((dir, name) -> name.endsWith(".gml"));
for (File gml : gmls) {
System.err.println(">>> " + gml);
String extension = shapefile.getName().replaceAll("\\.shp", "");
String[] args = new String[] { gml.toString(), // Input GML
shapefile.toString(), // Added trees, in Baumkatasterformat,
extension, // Output GML suffix
};
GreenEnricher.main(args);
}
}
private static void processSmallBlocks() throws IOException, CityGMLContextException, CityGMLReadException,
InterruptedException, CityGMLWriteException, JAXBException, ParseException {
File[] folders = {
new File("data/Marieke/75Bäume/"),
new File("data/Marieke/250Bäume/Abstand5m"),
new File("data/Marieke/250Bäume/Abstand15m/49_801844__49_802502__9_948075__9_949534_15m_Block24"),
new File("data/Marieke/250Bäume/Abstand15m/49_802605__49_804087__9_94036__9_948417_15m_Block7-11_14-17"),
new File("data/Marieke/500Bäume/Abstand10m/49_801844__49_802502__9_948075__9_949534_10m_block_24"),
new File("data/Marieke/500Bäume/Abstand10m/49_802605__49_804087__9_94036__9_948417_10m_block_7-11_14-17"),
};
for (File folder : folders) {
System.err.println("### " + folder);
String gml = folder.listFiles((dir, name) -> name.endsWith(".gml"))[0].getName();
File[] shapefiles = folder.listFiles((dir, name) -> name.endsWith(".shp"));
for (File shapefile : shapefiles) {
System.out.println(shapefile);
String extension = shapefile.getName().replaceAll("\\.shp", "");
String[] args = new String[] { folder.toPath().resolve(gml).toString(), // Input GML
shapefile.toString(), // Added trees, in Baumkatasterformat,
extension, // Output GML suffix
};
GreenEnricher.main(args);
}
}
}
private static void processWholeRegion() throws IOException, CityGMLContextException, CityGMLReadException,
InterruptedException, CityGMLWriteException, JAXBException, ParseException {
File folder = new File("data/Marieke/ganzes_gebiet/");
String gml = "Grombühl_BA_IST.gml";
File[] shapefiles = folder.listFiles((dir, name) -> name.endsWith(".shp"));
for (File shapefile : shapefiles) {
System.err.println(">>> " + shapefile);
String extension = shapefile.getName().replaceAll("\\.shp", "");
String[] args = new String[] { folder.toPath().resolve(gml).toString(), // Input GML
shapefile.toString(), // Added trees, in Baumkatasterformat,
extension, // Output GML suffix
};
GreenEnricher.main(args);
}
}
}
...@@ -8,7 +8,8 @@ Trees are exported in a CSV table, a PNG diagram and an HTML interactive map. ...@@ -8,7 +8,8 @@ Trees are exported in a CSV table, a PNG diagram and an HTML interactive map.
""" """
import pickle import pickle
from pathlib import Path from pathlib import Path
from collections import namedtuple from collections import namedtuple, Counter
import re
import folium import folium
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
...@@ -30,7 +31,9 @@ from import_existing_trees import get_existing_forest ...@@ -30,7 +31,9 @@ from import_existing_trees import get_existing_forest
# TODO: Write tests? # TODO: Write tests?
# From RegionChooser, or https://transfer.hft-stuttgart.de/gitlab/circulargreensimcity/circulargreensimcity/-/wikis/Fallstudien/Gromb%C3%BChl # From RegionChooser, or https://transfer.hft-stuttgart.de/gitlab/circulargreensimcity/circulargreensimcity/-/wikis/Fallstudien/Gromb%C3%BChl
WKT = "POLYGON((9.947021 49.803063, 9.947011 49.800917, 9.955025 49.800810, 9.955110 49.803019, 9.947021 49.803063))" # WKT = "POLYGON((9.947021 49.803063, 9.947011 49.800917, 9.955025 49.800810, 9.955110 49.803019, 9.947021 49.803063))"
# Grafenbühl
WKT = "POLYGON((9.147551 48.908059, 9.148635 48.907953, 9.149525 48.907819, 9.151177 48.907819, 9.151413 48.907840, 9.153226 48.908087, 9.153387 48.906705, 9.149160 48.906634, 9.148999 48.906620, 9.147551 48.908059))"
# Replace with None if no existing tree should be imported # Replace with None if no existing tree should be imported
EXISTING_TREES = 'existing_trees/Trees_ideal_2_20240227.shp' EXISTING_TREES = 'existing_trees/Trees_ideal_2_20240227.shp'
# EXISTING_TREES = 'existing_trees/baumkataster/Baum.shp' # EXISTING_TREES = 'existing_trees/baumkataster/Baum.shp'
...@@ -41,9 +44,15 @@ TREE_DISTANCE = 10 # [m] ...@@ -41,9 +44,15 @@ TREE_DISTANCE = 10 # [m]
MIN_DISTANCE = TREE_DISTANCE * 0.5 # [m] MIN_DISTANCE = TREE_DISTANCE * 0.5 # [m]
# For display purposes only: # For display purposes only:
GRID = 100 # [m] GRID = 100 # [m]
# If streets from OSM don't have known width, which one should be used? In [m]
IGNORE_ROADS = set(['primary', 'unclassified', 'secondary', # Leave empty to get default from similar streets, if available
'secondary_link', 'trunk', 'trunk_link', 'primary_link']) # Set to -1 if you want to disable trees along the roads
DEFAULT_WIDTHS = {
'unclassified': 0,
# 'residential': 8,
# 'motorway': -1,
# 'trunk': -1,
}
SCRIPT_DIR = Path(__file__).resolve().parent SCRIPT_DIR = Path(__file__).resolve().parent
...@@ -51,17 +60,17 @@ OUTPUT_DIR = SCRIPT_DIR / 'output' ...@@ -51,17 +60,17 @@ OUTPUT_DIR = SCRIPT_DIR / 'output'
Bounds = namedtuple("Bounds", "W S E N") Bounds = namedtuple("Bounds", "W S E N")
def load_region(wkt_polygon): def load_region(wkt_polygon: str):
region = wkt.loads(wkt_polygon) region = wkt.loads(wkt_polygon)
bounds = Bounds(*region.bounds) bounds = Bounds(*region.bounds)
return region, bounds return region, bounds
def get_basename(bounds): def get_basename(bounds: Bounds):
return f'{bounds.S}__{bounds.N}__{bounds.W}__{bounds.E}_{TREE_DISTANCE}m'.replace('.', '_') return f'{bounds.S}__{bounds.N}__{bounds.W}__{bounds.E}_{TREE_DISTANCE}m'.replace('.', '_')
def get_osm_roads(bounds): def get_osm_roads(bounds: Bounds):
cache_dir = SCRIPT_DIR / 'cache' cache_dir = SCRIPT_DIR / 'cache'
cache_dir.mkdir(exist_ok=True) cache_dir.mkdir(exist_ok=True)
...@@ -88,7 +97,7 @@ def get_osm_roads(bounds): ...@@ -88,7 +97,7 @@ def get_osm_roads(bounds):
return ways return ways
def set_plot(bounds, to_local_coordinates): def set_plot(bounds: Bounds, to_local_coordinates):
x_min, y_min = to_local_coordinates.transform(bounds.W, bounds.S) x_min, y_min = to_local_coordinates.transform(bounds.W, bounds.S)
x_max, y_max = to_local_coordinates.transform(bounds.E, bounds.N) x_max, y_max = to_local_coordinates.transform(bounds.E, bounds.N)
ax = plt.axes() ax = plt.axes()
...@@ -103,15 +112,50 @@ def set_plot(bounds, to_local_coordinates): ...@@ -103,15 +112,50 @@ def set_plot(bounds, to_local_coordinates):
return ax return ax
def place_trees(forest, ways, region, to_local, tree_distance, min_distance_2) -> Forest: def get_width(way: overpy.Way) -> float:
width_str = way.tags.get("width", '0')
# NOTE: Some widths are written with units, so try to remove trailing [m] first.
width_str = re.sub(' ?m$', '', width_str)
return float(width_str)
def get_default_widths(ways: list) -> dict[str, float]:
"""Check existing OSM highways, and extract the most common width for each type"""
width_counters: dict[str, Counter] = {}
for way in ways:
width = get_width(way)
way_type = way.tags.get("highway")
if width:
if way_type not in width_counters:
width_counters[way_type] = Counter()
width_counters[way_type][width] += 1
return {w: c.most_common(1)[0][0] for w, c in width_counters.items()}
def place_trees(forest: Forest, ways: list, region: str, to_local, tree_distance: float, min_distance_2: float) -> Forest:
local_region = transform(to_local.transform, region) local_region = transform(to_local.transform, region)
default_widths = {**get_default_widths(ways), **DEFAULT_WIDTHS}
print(f"Default widths: {default_widths}")
for way in ways: for way in ways:
width = float(way.tags.get("width", 0)) way_type = way.tags.get("highway")
highway = way.tags.get("highway") width = get_width(way)
if highway in IGNORE_ROADS: default_width = default_widths.get(way_type, 0)
if default_width < 0:
# Ignore this type of streets
continue
if width:
# Defined in OSM
color = 'blue'
else:
if default_width:
# From OSM most common width
color = 'orange' color = 'orange'
width = default_width
else: else:
# Unknown
color = 'gray' color = 'gray'
road_xy_s = [to_local.transform(node.lon, node.lat) for node in way.nodes] road_xy_s = [to_local.transform(node.lon, node.lat) for node in way.nodes]
...@@ -123,15 +167,13 @@ def place_trees(forest, ways, region, to_local, tree_distance, min_distance_2) - ...@@ -123,15 +167,13 @@ def place_trees(forest, ways, region, to_local, tree_distance, min_distance_2) -
tree_path = road_as_polygon.exterior tree_path = road_as_polygon.exterior
displayed_width = width displayed_width = width
else: else:
# NOTE: Could try to guess width depending on highway type.
displayed_width = 1 displayed_width = 1
road_xs, road_ys = zip(*road_xy_s) road_xs, road_ys = zip(*road_xy_s)
plt.plot(road_xs, road_ys, linewidth=displayed_width, c=color, alpha=0.8, zorder=-1) plt.plot(road_xs, road_ys, linewidth=displayed_width, c=color, alpha=0.8, zorder=-1)
distances = np.arange(0, tree_path.length, tree_distance) distances = np.arange(0, tree_path.length, tree_distance)
potential_trees = [tree_path.interpolate( potential_trees = [tree_path.interpolate(distance) for distance in distances]
distance) for distance in distances]
if tree_path.boundary: if tree_path.boundary:
potential_trees += [tree_path.boundary.geoms[-1]] potential_trees += [tree_path.boundary.geoms[-1]]
...@@ -145,6 +187,7 @@ def place_trees(forest, ways, region, to_local, tree_distance, min_distance_2) - ...@@ -145,6 +187,7 @@ def place_trees(forest, ways, region, to_local, tree_distance, min_distance_2) -
description='Tilia tomentosa', description='Tilia tomentosa',
diameter=6, diameter=6,
height=10, height=10,
trunk_diameter=0.5,
source='add_trees.py' source='add_trees.py'
) )
...@@ -164,7 +207,7 @@ def plot_trees(bounds: Bounds, forest: Forest, tree_distance: float) -> None: ...@@ -164,7 +207,7 @@ def plot_trees(bounds: Bounds, forest: Forest, tree_distance: float) -> None:
print(" DONE!") print(" DONE!")
def export_map(bounds, forest, epsg_id) -> None: def export_map(bounds: Bounds, forest: Forest, epsg_id: int) -> None:
print("Exporting Map...") 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()
...@@ -197,7 +240,7 @@ def export_map(bounds, forest, epsg_id) -> None: ...@@ -197,7 +240,7 @@ def export_map(bounds, forest, epsg_id) -> None:
print(" DONE!") print(" DONE!")
def export_csv(bounds, forest, wkt_polygon, tree_distance, min_distance, epsg_id) -> None: def export_csv(bounds: Bounds, forest: Forest, wkt_polygon: str, tree_distance: float, min_distance: float, epsg_id: int) -> None:
print("Exporting CSV...") print("Exporting CSV...")
with open(OUTPUT_DIR / f"{get_basename(bounds)}_trees.csv", "w") as csv: with open(OUTPUT_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")
...@@ -212,7 +255,7 @@ def export_csv(bounds, forest, wkt_polygon, tree_distance, min_distance, epsg_id ...@@ -212,7 +255,7 @@ def export_csv(bounds, forest, wkt_polygon, tree_distance, min_distance, epsg_id
print(" DONE!") print(" DONE!")
def export_shapefile(bounds: Bounds, forest: Forest, epsg_id: str) -> None: def export_shapefile(bounds: Bounds, forest: Forest, epsg_id: int) -> None:
print("Exporting shapefile") print("Exporting shapefile")
data = [{ data = [{
...@@ -239,7 +282,7 @@ def export_shapefile(bounds: Bounds, forest: Forest, epsg_id: str) -> None: ...@@ -239,7 +282,7 @@ def export_shapefile(bounds: Bounds, forest: Forest, epsg_id: str) -> None:
print(" DONE!") print(" DONE!")
def main(wkt_polygon, epsg_id, tree_distance, min_distance, import_tree_shp) -> None: def main(wkt_polygon: str, epsg_id: int, tree_distance: float, min_distance: float, 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)
......
...@@ -5,18 +5,20 @@ from tree import Tree, Forest ...@@ -5,18 +5,20 @@ 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 = [] forest = Forest()
for tree_row in df.itertuples(): for tree_row in df.itertuples():
point = tree_row.geometry point = tree_row.geometry
trees.append(Tree(point.x, point.y, added = forest.add_tree_if_possible(0.1, point.x, point.y,
description=tree_row.Bezeichnun, description=tree_row.Bezeichnun,
diameter=tree_row.Kronenbrei, diameter=tree_row.Kronenbrei,
type=tree_row.Baumart, type=tree_row.Baumart,
trunk_diameter=tree_row.Stammumfan, trunk_diameter=tree_row.Stammumfan,
height=tree_row.Baumhöhe, height=tree_row.Baumhöhe,
source=Path(shp_input).name source=Path(shp_input).name
)) )
return Forest(trees) if not added:
print(f"WARNING! tree seems to be too close to others! Is it a duplicate?\n\t{tree_row}")
return forest
if __name__ == "__main__": if __name__ == "__main__":
print(repr(get_existing_forest('existing_trees/Trees_ideal_2_20240227.shp'))) print(repr(get_existing_forest('existing_trees/Trees_ideal_2_20240227.shp')))
......
...@@ -10,8 +10,8 @@ class Tree: ...@@ -10,8 +10,8 @@ class Tree:
diameter: float diameter: float
height: float height: float
z: float = 0 z: float = 0
trunk_diameter: float = None trunk_diameter: float|None = None
type: str = None type: str|None = None
description: str = '?' description: str = '?'
source: str = '?' source: str = '?'
color: str = 'green' color: str = 'green'
...@@ -39,11 +39,13 @@ class Forest(UserList): ...@@ -39,11 +39,13 @@ class Forest(UserList):
self.data = 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) -> bool:
_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.append(Tree(x, y, **kparams)) self.append(Tree(x, y, **kparams))
return True
return False
@property @property
def xs_ys_cs(self): def xs_ys_cs(self):
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment