Skip to content

Commit 7b3585a

Browse files
authored
Merge pull request #1 from marcbrittain/dev/v0.0.2
v0.0.2 add distance calculation
2 parents 107a6ab + d052aa9 commit 7b3585a

File tree

3 files changed

+127
-36
lines changed

3 files changed

+127
-36
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The core focus of this repository is to make working with 3D geospatial/geograph
4242
This project is very early on and is something that I am working on in my free time. Getting some of the initial functionality of GeoLineStrings like intersections and coordinate transformations was a first step, but there is a long way to go. Here I list some of the next major items that need to be addressed.
4343

4444
* GeoLineStrings
45-
1. Handling heterogeneous intersection types (LineString, Point, etc.)
45+
1. Heterogeneous intersection types (LineString, Point, etc.)
4646
2. Add function for distance calculation
4747
3. Add function for GeoLineString splits
4848
4. Optimize intersection function for efficiency

pygeoshape/geolinestring.py

Lines changed: 125 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,50 @@
1-
from shapely.geometry import LineString, Point
21
from typing import List
2+
from shapely.geometry import LineString
33
from pyproj import Transformer
44
import numpy as np
55
import matplotlib.pyplot as plt
6-
from mpl_toolkits.mplot3d import Axes3D
76

87

98
class 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

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = pygeoshape
3-
version = 0.0.1
3+
version = 0.0.2
44
author = Marc Brittain
55
author_email = marcbrittain@yahoo.com
66
description = A 3D geospatial package to make working with geographical & trajectory data easier in python

0 commit comments

Comments
 (0)