1+ import pandas as pd
2+ import geopandas as gpd
3+ from typing import Callable
4+ from abc import ABC , abstractmethod
5+ class BaseSampler (ABC ):
6+ """Base class for samplers."""
7+
8+ @abstractmethod
9+ def __call__ (self , line : gpd .GeoDataFrame , dem : Callable , use_z : bool ) -> pd .DataFrame :
10+ """Sample the line and return a DataFrame with X, Y, Z coordinates and attributes."""
11+ pass
12+ class RegularSpacingSampler (BaseSampler ):
13+ def __init__ (self , spacing : float ):
14+ self .spacing = spacing
15+ def __call__ (self , line : gpd .GeoDataFrame , dem : Callable , use_z : bool ) -> pd .DataFrame :
16+ """Sample the line at regular intervals and return a DataFrame with X, Y, Z coordinates and attributes."""
17+ points = []
18+ feature_id = 0
19+ if line is None :
20+ return pd .DataFrame (points , columns = ['X' , 'Y' , 'Z' , 'feature_id' ])
21+ for geom in line .geometry :
22+ attributes = line .iloc [feature_id ].to_dict ()
23+ attributes .pop ('geometry' , None ) # Remove geometry from attributes
24+ if geom .geom_type == 'LineString' :
25+ length = geom .length
26+ num_points = max (int (length // self .spacing ), 1 )
27+ for i in range (num_points + 1 ):
28+ point = geom .interpolate (i * self .spacing )
29+ x , y = point .x , point .y
30+ # Use Z from geometry if available, otherwise use DEM
31+ if use_z and hasattr (point , 'z' ):
32+ z = point .z
33+ else :
34+ z = dem (x , y )
35+ points .append ({'X' : x , 'Y' : y , 'Z' : z , 'feature_id' : feature_id , ** attributes })
36+ elif geom .geom_type == 'MultiLineString' :
37+ for l in geom .geoms :
38+ length = l .length
39+ num_points = max (int (length // self .spacing ), 1 )
40+ for i in range (num_points + 1 ):
41+ point = l .interpolate (i * self .spacing )
42+ x , y = point .x , point .y
43+ # Use Z from geometry if available, otherwise use DEM
44+ if use_z and hasattr (point , 'z' ):
45+ z = point .z
46+ else :
47+ z = dem (x , y )
48+ points .append ({'X' : x , 'Y' : y , 'Z' : z , 'feature_id' : feature_id , ** attributes })
49+ elif geom .geom_type == 'Point' :
50+ coords = list (geom .coords [0 ])
51+ # Use Z from geometry if available, otherwise use DEM
52+ if use_z and len (coords ) > 2 :
53+ z = coords [2 ]
54+ elif dem is not None :
55+ z = dem (coords [0 ], coords [1 ])
56+ else :
57+ z = 0
58+ points .append ({'X' : coords [0 ], 'Y' : coords [1 ], 'Z' : z , 'feature_id' : feature_id , ** attributes })
59+ feature_id += 1
60+ df = pd .DataFrame (points )
61+ return df
62+ class AllSampler (BaseSampler ):
63+ """This is a simple sampler that just returns all the points, or all of the vertices
64+ of a line. It will also copy the elevation from the DEM or the elevation set in the data manager.
65+ """
66+
67+ def __call__ (self , line : gpd .GeoDataFrame , dem : Callable , use_z : bool ) -> pd .DataFrame :
68+ """Sample the line and return a DataFrame with X, Y, Z coordinates and attributes."""
69+ points = []
70+ feature_id = 0
71+ if line is None :
72+ return pd .DataFrame (points , columns = ['X' , 'Y' , 'Z' , 'feature_id' ])
73+ for geom in line .geometry :
74+ attributes = line .iloc [feature_id ].to_dict ()
75+ attributes .pop ('geometry' , None ) # Remove geometry from attributes
76+ if geom .geom_type == 'LineString' :
77+ coords = list (geom .coords )
78+ for coord in coords :
79+ x , y = coord [0 ], coord [1 ]
80+ # Use Z from geometry if available, otherwise use DEM
81+ if use_z and len (coord ) > 2 :
82+ z = coord [2 ]
83+ else :
84+ z = dem (x , y )
85+ points .append ({'X' : x , 'Y' : y , 'Z' : z , 'feature_id' : feature_id , ** attributes })
86+ elif geom .geom_type == 'MultiLineString' :
87+ for l in geom .geoms :
88+ coords = list (l .coords )
89+ for coord in coords :
90+ x , y = coord [0 ], coord [1 ]
91+ # Use Z from geometry if available, otherwise use DEM
92+ if use_z and len (coord ) > 2 :
93+ z = coord [2 ]
94+ else :
95+ z = dem (x , y )
96+ points .append (
97+ {'X' : x , 'Y' : y , 'Z' : z , 'feature_id' : feature_id , ** attributes }
98+ )
99+ elif geom .geom_type == 'Point' :
100+
101+ coords = list (geom .coords [0 ])
102+ # Use Z from geometry if available, otherwise use DEM
103+ if use_z and len (coords ) > 2 :
104+ z = coords [2 ]
105+ elif dem is not None :
106+ z = dem (coords [0 ], coords [1 ])
107+ else :
108+ z = 0
109+ points .append ({'X' : coords [0 ], 'Y' : coords [1 ], 'Z' : z , 'feature_id' : feature_id , ** attributes })
110+ feature_id += 1
111+ df = pd .DataFrame (points )
112+ return df
0 commit comments