1- from shapely .geometry import LineString , Point
21from typing import List
2+ from shapely .geometry import LineString
33from pyproj import Transformer
44import numpy as np
55import matplotlib .pyplot as plt
6- from mpl_toolkits .mplot3d import Axes3D
76
87
98class GeoLineString :
10- def __init__ (self , coords : List , epsg_proj : str = 'epsg:2163' , epsg_from : str = 'epsg:4326' , isXY : bool = False ):
9+ """Base GeoLinString type"""
10+
11+ def __init__ (
12+ self ,
13+ coords : List ,
14+ epsg_proj : str = "epsg:2163" ,
15+ epsg_from : str = "epsg:4326" ,
16+ is_xy : bool = False ,
17+ ):
18+ """
19+ Args:
20+ ----
21+
22+ coords (List): Input coordinates in (longitude, latitude, altitude) or (x, y, z)
23+ epsg_proj (str): EPSG code to transform longitude, latitude into x, y (default: 2163)
24+ epsg_from (str): EPSG code to transform longitude, latitude from (default: 4326)
25+ is_xy (bool): Boolean for if the input coordinates are already in (x, y, [z]) format
26+
27+ """
1128 # coords should contain [[lon1, lat1, alt1],[lon2,...]]
1229 # Units for altitude should be meters
1330
1431 self .coords = coords
1532 self .epsg_proj = epsg_proj
1633 self .epsg_from = epsg_from
17- self .isXY = isXY
34+ self .is_xy = is_xy
1835 self .transformer = Transformer .from_crs (
19- self .epsg_from , self .epsg_proj , always_xy = True )
36+ self .epsg_from , self .epsg_proj , always_xy = True
37+ )
2038
2139 # convert input coordinates to X, Y
22- if not self .isXY :
40+ if not self .is_xy :
2341 self .lonlat_coords = self .coords
2442 self .xy_coords = self .project_coords ()
2543 else :
2644 self .xy_coords = self .coords
2745
2846 self .np_coords = np .array (self .xy_coords )
47+ self .length = self .geolinestring_length ()
2948 dimensions = len (self .xy_coords [0 ])
3049
3150 # need linestring for each dimension
@@ -39,15 +58,43 @@ def __init__(self, coords: List, epsg_proj: str = 'epsg:2163', epsg_from: str =
3958
4059 if dimensions == 3 :
4160
42- self .xy = LineString ([(coord [0 ], coord [1 ], coord [2 ])
43- for coord in self .xy_coords ])
44- self .xz = LineString ([(coord [0 ], coord [2 ], coord [1 ])
45- for coord in self .xy_coords ])
46- self .yz = LineString ([(coord [1 ], coord [2 ], coord [0 ])
47- for coord in self .xy_coords ])
61+ self .xy = LineString (
62+ [(coord [0 ], coord [1 ], coord [2 ]) for coord in self .xy_coords ]
63+ )
64+ self .xz = LineString (
65+ [(coord [0 ], coord [2 ], coord [1 ]) for coord in self .xy_coords ]
66+ )
67+ self .yz = LineString (
68+ [(coord [1 ], coord [2 ], coord [0 ]) for coord in self .xy_coords ]
69+ )
70+
71+ def geolinestring_length (self ):
72+ """
73+ Determines the length of the GeoLineString in meters
74+
75+
76+ Returns:
77+ -------
78+ length (float): Length of GeoLineString
79+
80+ """
81+
82+ # sqrt( (x2-x1)^2 + (y2-y1)^2 + (z2-z1)^2)
83+ # then take sum for each segment for total distance
84+ length = np .sum (
85+ np .sqrt (np .sum (np .square (np .diff (self .np_coords , axis = 0 )), axis = 1 )))
86+ return length
4887
4988 def project_coords (self ):
89+ """
90+ Projects the longitude, latitude coordinates to X, Y (meters)
91+
92+
93+ Returns:
94+ -------
95+ xy_coords (List): List of coordinates in (X, Y, [Z])
5096
97+ """
5198 xy_coords = []
5299 for coordinate in self .coords :
53100 x , y = self .transformer .transform (coordinate [0 ], coordinate [1 ])
@@ -59,21 +106,49 @@ def project_coords(self):
59106 return xy_coords
60107
61108 def plot (self , geo_obj = None , label_fontsize = 12 ):
109+ """
110+ Plot the 3D LineString using Matplotlib.
111+
112+ Args:
113+ ----
114+ geo_obj (GeoLineString): Second GeoLineString to add to plot (Optional)
115+ label_fontsize (int): Fontsize for the axes labels
116+
117+
118+ Returns:
119+ -------
120+ fig: Matplotlib figure
121+ ax: Matplotlin axes
122+
123+ """
124+
62125 fig = plt .figure ()
63- ax = fig .add_subplot (111 , projection = '3d' )
126+ ax = fig .add_subplot (111 , projection = "3d" )
64127 ax .plot (self .np_coords [:, 0 ],
65128 self .np_coords [:, 1 ], self .np_coords [:, 2 ])
66129
67130 if geo_obj is not None :
68- ax .plot (geo_obj .np_coords [:, 0 ],
69- geo_obj .np_coords [:, 1 ], geo_obj .np_coords [:, 2 ])
70- ax .set_xlabel ('x' , fontsize = label_fontsize )
71- ax .set_ylabel ('y' , fontsize = label_fontsize )
72- ax .set_zlabel ('z' , fontsize = label_fontsize )
131+ ax .plot (
132+ geo_obj .np_coords [:, 0 ],
133+ geo_obj .np_coords [:, 1 ],
134+ geo_obj .np_coords [:, 2 ],
135+ )
136+ ax .set_xlabel ("x" , fontsize = label_fontsize )
137+ ax .set_ylabel ("y" , fontsize = label_fontsize )
138+ ax .set_zlabel ("z" , fontsize = label_fontsize )
139+ return fig , ax
73140
74141 def intersects (self , geo_obj ):
75142 """
76- Does current GeoLineString intersect with geo_obj?
143+ Determines if the GeoLineString intersects with geo_obj
144+
145+ Args:
146+ ----
147+ geo_obj (GeoLineString): Second GeoLineString for comparision
148+
149+ Returns:
150+ -------
151+ bool: True if GeoLineStrings intersect, False otherwise
77152
78153 """
79154 xy_check = self .xy .intersects (geo_obj .xy )
@@ -82,10 +157,21 @@ def intersects(self, geo_obj):
82157
83158 if all ([xy_check , xz_check , yz_check ]):
84159 return True
160+ else :
161+ return False
85162
86163 def intersection (self , geo_obj , lonlat = False ):
87164 """
88- Does current GeoLineString intersect with geo_obj?
165+ Determines where the GeoLineString intersects with geo_obj
166+
167+ Args:
168+ ----
169+ geo_obj (GeoLineString): Second GeoLineString for comparision
170+ lonlat (bool): Format of output coordinates. True returns the coordinates in longitude, latitude. False returns coordinates in x, y
171+
172+ Returns:
173+ -------
174+ intersection (List): Intersection coordinates
89175
90176 """
91177 xy_check = self .xy .intersects (geo_obj .xy )
@@ -100,22 +186,22 @@ def intersection(self, geo_obj, lonlat=False):
100186 inter2 = self .xz .intersection (geo_obj .xz )
101187 inter3 = self .yz .intersection (geo_obj .yz )
102188
103- try :
189+ if not hasattr ( inter1 , "geoms" ) :
104190 xy = list (inter1 .coords )[0 ]
105191 n_xy_intersections = 1
106- except :
192+ else :
107193 n_xy_intersections = len (inter1 .geoms )
108194
109- try :
195+ if not hasattr ( inter2 , "geoms" ) :
110196 xz = list (inter2 .coords )[0 ]
111197 n_xz_intersections = 1
112- except :
198+ else :
113199 n_xz_intersections = len (inter2 .geoms )
114200
115- try :
201+ if not hasattr ( inter3 , "geoms" ) :
116202 yz = list (inter3 .coords )[0 ]
117203 n_yz_intersections = 1
118- except :
204+ else :
119205 n_yz_intersections = len (inter3 .geoms )
120206
121207 for i in range (n_xy_intersections ):
@@ -124,19 +210,19 @@ def intersection(self, geo_obj, lonlat=False):
124210
125211 for k in range (n_yz_intersections ):
126212
127- try :
213+ if not hasattr ( inter1 , "geoms" ) :
128214 xy = list (inter1 .geoms [i ].coords )
129- except :
215+ else :
130216 xy = list (inter1 .coords )
131217
132- try :
218+ if not hasattr ( inter2 , "geoms" ) :
133219 xz = list (inter2 .geoms [j ].coords )
134- except :
220+ else :
135221 xz = list (inter2 .coords )
136222
137- try :
223+ if not hasattr ( inter3 , "geoms" ) :
138224 yz = list (inter3 .geoms [k ].coords )
139- except :
225+ else :
140226 yz = list (inter3 .coords )
141227
142228 for ii in range (len (xy )):
@@ -156,7 +242,12 @@ def intersection(self, geo_obj, lonlat=False):
156242
157243 if lonlat :
158244 lon , lat , alt = self .transformer .transform (
159- intersection [:, 0 ], intersection [:, 1 ], intersection [:, 2 ], direction = 'INVERSE' )
245+ intersection [:, 0 ],
246+ intersection [:, 1 ],
247+ intersection [:, 2 ],
248+ direction = "INVERSE" ,
249+ )
160250 intersection = np .array (
161- [[lon [i ], lat [i ], alt [i ]] for i in range (len (lon ))])
251+ [[lon [i ], lat [i ], alt [i ]] for i in range (len (lon ))]
252+ )
162253 return intersection
0 commit comments