Converting DataFrame XYZ to Geopandas LineString - geopandas

I have DataFrame as shown below
gdf = gpd.GeoDataFrame(geo_df, geometry=gpd.points_from_xy(geo_df.x.astype(float),geo_df.y.astype(float)))
x y z station geometry
651669.5725 4767831.32 -74.46883723 Loc1 POINT (651669.5725 4767831.32)
651529.8717 4767843.542 -77.35837037 Loc1 POINT (651529.8717 4767843.542)
651528.5995 4767846.217 -77.39481647 Loc2 POINT (651528.5995 4767846.217)
651455.8623 4767730.411 -73.44656122 Loc2 POINT (651455.8623 4767730.411)
651406.0155 4767551.434 -72.57809472 Loc2 POINT (651406.0155 4767551.434)
651403.4501 4767537.248 -72.721502 Loc2 POINT (651403.4501 4767537.248)
I am converting points to Linestring
geo_df = gdf.groupby('station')['geometry'].apply(lambda x: LineString(x.tolist()))
It successfully converts to LineString. But when i try to plot it, it says data is not numeric.
Any ideas?

your sample data does not contain lomdrivename so have used station
almost same technique as your code, using geometry of 3D points to generate LINESTRING
output shows this has worked by showing both lines and individual line
import io
import shapely.geometry
import geopandas as gpd
import pandas as pd
df = pd.read_csv(io.StringIO(""" x y z station geometry
651669.5725 4767831.32 -74.46883723 Loc1 POINT (651669.5725 4767831.32)
651529.8717 4767843.542 -77.35837037 Loc1 POINT (651529.8717 4767843.542)
651528.5995 4767846.217 -77.39481647 Loc2 POINT (651528.5995 4767846.217)
651455.8623 4767730.411 -73.44656122 Loc2 POINT (651455.8623 4767730.411)
651406.0155 4767551.434 -72.57809472 Loc2 POINT (651406.0155 4767551.434)
651403.4501 4767537.248 -72.721502 Loc2 POINT (651403.4501 4767537.248)"""), sep="\s\s+", engine="python")
# convert back into a GeoDataFrame
df = gpd.GeoDataFrame(df, geometry=df.apply(lambda r: shapely.geometry.Point(r.x,r.y,r.z), axis=1))
# generate Linestrings grouping by station
gls = gpd.GeoSeries(df.groupby("station").apply(lambda d: shapely.geometry.LineString(d["geometry"].values)))
gls.plot()
gls.loc["Loc2"]

Related

how to get valid latitude and longitude from linestring

I want a list of latitudes and longitudes of North Carolina roads for my research work. So I got the .shp file from here
https://xfer.services.ncdot.gov/gisdot/DistDOTData/NCRoutes_SHP.zip
and I loaded the file using geopandas.
import geopandas as gpd
graph = gpd.read_file("NCRoutes.shp")
Here is the geometry column of the shapefile.
graph['geometry']
Output:
0 MULTILINESTRING Z ((950413.442 761781.527 0.00...
1 MULTILINESTRING Z ((947047.370 633980.630 2181...
2 MULTILINESTRING Z ((946481.250 821340.560 3756...
3 LINESTRING Z (1000455.242 564424.433 1854.400,...
4 LINESTRING Z (1840729.024 842228.554 588.800, ...
...
373365 LINESTRING Z (2474108.250 658112.370 67.400, 2...
373366 LINESTRING Z (2331115.610 180293.340 37.400, 2...
373367 LINESTRING Z (2398439.990 968349.560 156.400, ...
373368 LINESTRING Z (1465953.417 567437.417 810.200, ...
373369 LINESTRING Z (1782694.744 871896.463 833.000, ...
Name: geometry, Length: 373370, dtype: geometry
When I print a single linestring, it looks like this-
graph['geometry'][3].coords.xy
Output:
(array('d', [1000455.2419360131, 1000541.414176017, 1000666.0802560151, 1000866.2999680042, 1001138.8699360043, 1001250.1976800114, 1001361.2661760151, 1001444.3955520093, 1001527.3755040169, 1001610.2039200068, 1001692.8797440082,.....
how do I convert these multistring and linestrings to latitudes and longitudes?
You need to transform your geometries to a geographic coordinate system.. Let's use the poplar WGS 84. But first of all, let's check to see that the data has a CRS defined. Printing the gdf.crs attribute on our gdf lets us know that the data are currently in a NC State Plane projected coordinate system. Hint: This can also be deduced by looking at the .prj file where it's stored in WKT(well-known text) format. You can also find this information in QGIS, ArcGIS, etc.
import geopandas as gpd
gdf = gpd.read_file('NCRoutes.shp')
gdf.crs
Next we can use gpd's .to_crs() to convert to WGS 84, which uses EPSG code 4326.
gdf_wgs84 = gdf.to_crs(4326)
Let's look at the first few geometry rows. As you can see the coordinates are no longer measured in feet, but are now longitude and latitude.
gdf_wgs84['geometry'].head()
Output:
0 MULTILINESTRING ((-82.53992 35.79168, -82.5399...
1 MULTILINESTRING ((-82.53591 35.44045, -82.5358...
2 MULTILINESTRING ((-82.56037 35.95481, -82.5603...
3 LINESTRING (-82.34883 35.25454, -82.34854 35.2...
4 LINESTRING (-79.53887 36.06291, -79.53876 36.0...
Name: geometry, dtype: geometry

Access Z coordinate in a LINESTRING Z in geopandas?

I have a GeoDataFrame with a LINESTRING Z geometry where Z is my altitude for the lat/long. (There are other columns in the dataframe that I deleted for ease of sharing but are relevant when displaying the resulting track)
TimeUTC Latitude Longitude AGL geometry
0 2021-06-16 00:34:04+00:00 42.835413 -70.919610 82.2 LINESTRING Z (-70.91961 42.83541 82.20000, -70...
I would like to find the maximum Z value in that linestring but I am unable to find a way to access it or extract the x,y,z values in a way that I can determine the maximum value outside of the linestring.
line.geometry.bounds only returns the x,y min/max.
The best solution I could come up with was to turn all the points into a list of tuples:
points = line.apply(lambda x: [y for y in x['geometry'].coords], axis=1)
And then find the maximum value of the third element:
from operator import itemgetter
max(ft2,key=itemgetter(2))[2]
I hope there is a better solution available.
Thank you.
You can take your lambda function approach and just take it one step further:
import numpy as np
line['geometry'].apply(lambda geom: np.max([coord[2] for coord in geom.coords]))
Here's a fully reproducible example from start to finish:
import shapely
import numpy as np
import geopandas as gpd
linestring = shapely.geometry.LineString([[0,0,0],
[1,1,1],
[2,2,2]])
gdf = gpd.GeoDataFrame({'id':[1,2,3],
'geometry':[linestring,
linestring,
linestring]})
gdf['max_z'] = (gdf['geometry']
.apply(lambda geom:
np.max([coord[2] for coord in geom.coords])))
In the example above, I create a new column called "max_z" that stores the maximum Z value for each row.
Important note
This solution will only work if you exclusively have LineStrings in your geometries. If, for example, you have MultiLineStrings, you'll have to adapt the function I wrote to take care of that.

Geopandas: Get a box that coveres area of a geopandas GeoDataFrame to use it to invert a map

I'm trying to invert a map.
import geopandas as gpd
import geoplot as gplt
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
denmark = world[world.name == 'Denmark']
I would like to find out the boundaries of the "denmark" dataframe, to that I can create a box shaped GeoDataFrame that covers all of Denmark.
I'd then intersect that with "denmark" to get a shape of all that is not denmark, which I can later use to cover parts of a map I don't want to show.
I tried looking through the GeoDataFrame to create this box manually, but that doesn't work well.
cords = [c3
for c in mapping(denmark['geometry'])['features']
for c2 in c['geometry']['coordinates']
for c3 in c2
]
xcords = [x[0] for x in cords if isinstance(x[0], float)]
ycords = [y[1] for y in cords if isinstance(y[1], float)]
w3 = gpd.GeoDataFrame(
[Polygon([[max(xcords), max(ycords)],
[max(xcords), min(ycords)],
[min(xcords), min(ycords)],
[min(xcords), max(ycords)]
])],
columns = ['geometry'],
geometry='geometry')
Is there an easy, quick way to get this box?
Or is there a way tp invert a GeoDataFrame?
A GeoDataFrame has the total_bounds attribute, which returns the minx, miny, maxx, maxy of all geometries (the min/max of the bounds of all geometries).
And to create a Polygon of this, you can then pass those values to the shapely.geometry.box function:
>>> denmark.total_bounds
array([ 8.08997684, 54.80001455, 12.69000614, 57.73001659])
>>> from shapely.geometry import box
>>> box(*denmark.total_bounds)
<shapely.geometry.polygon.Polygon at 0x7f06be3e7668>
>>> print(box(*denmark.total_bounds))
POLYGON ((12.6900061377556 54.80001455343792, 12.6900061377556 57.73001658795485, 8.089976840862221 57.73001658795485, 8.089976840862221 54.80001455343792, 12.6900061377556 54.80001455343792))
Looks like a GeoDataFrame has a property "total_bounds"
So it's
denmark.total_bounds
which returns
array([ 8.08997684, 54.80001455, 12.69000614, 57.73001659])

How to get equidistant points from a linestring (geographical coordinates)

I want to resample geographical coordinates based on a specific number of values, let's say, 1663 for the following case:
-78.0599088 -11.89402416
-78.04317744 -11.88622134
-78.0267798 -11.87700462
-78.010353 -11.8692050399999
-77.9953194 -11.86129017
-77.96128788 -11.8449840599999
-77.92870572 -11.82838707
-77.89554864 -11.8117820699999
-77.86357524 -11.79488952
-77.83013412 -11.77942518
-77.7978615599999 -11.76223743
-77.765589 -11.7456140699999
-77.73216732 -11.72927727
-77.6996085599999 -11.7117892799999
-77.6673594 -11.6965884599999
-77.63510052 -11.6819618399999
-77.6045808 -11.6618759099999
-77.57262108 -11.6432262
-77.5406624399999 -11.62628883
-77.5072638 -11.6099197199999
-77.4753066 -11.5923951899999
-77.4427813199999 -11.57658786
-77.4093902399999 -11.5599159
-77.38064244 -11.5446833099999
However, the tricky part here is to keep the first and last positions and to use open-source software tools (such as GDAL, AWK, GMT or other bash shell command line tools, that would be great).
As example I am looking for something similar to the "Equidistant points (fixed number)" option of XTools Pro: https://help.xtools.pro/pro/12.2/en/XTools_Pro_Components/Feature_conversions/Convert_Features_to_Points.htm
Here an expected output, a line of distance X from which 7 points (node or vertex) were created considering the first and last positions:
Any support is appreciated.
The following answer assumes that your coordinates are on a sphere and not on an ellipsoid.
Your input contains a set of points on the great-circle between two points p and q with coordinates in longitude and latitude to be:
p = {φp,λp} = {-78.0599088, -11.89402416}
q = {φq,λq} = {-77.38064244, -11.5446833099999}
Call np the unit vector of p and nq the unit vector of q, then its coordinates are:
np = {cos(φp) cos(λp),cos(φp) sin(λp),sin(φp)}
nq = {cos(φq) cos(λq),cos(φq) sin(λq),sin(φq)}
Call α then angle between np and nq
α = arccos(np·nq)
If you now want to have n points equidistantly spaced between p and q, you have to separate them over an angle Δα = α/(n-1).
The coordinates over these points are then:
ni = np cos(i Δα) + nr sin(i Δα)
nr = Normalized[nq - (nq·np) np] = (nq - cos(α) np) / sin(α)
for i &in; [0,n-1]. The above is understood as a simple rotation over iΔα of np in the np-nr plane (np·nr = 0)
These coordinates can then be transformed back to longitude and latitude, giving you all the intermediate points.
remark: This is for equidistant points on a sphere and not an ellipsoid such as WGS 84
remark: The above will fail for antipodal points.
A very good formulary is Ed William's Aviation Formulary
Python did the trick:
from shapely.geometry import LineString
import csv
with open('input_xy.txt') as fin:
reader = csv.reader(fin)
xy_floats = map(lambda x: (float(x[0]), float(x[1])), list(reader))
line = LineString(xy_floats)
num_points = 9 # includes first and last
new_points = [line.interpolate(i/float(num_points - 1), normalized=True) for i in range(num_points)]
with open('output_xy.txt', 'w') as fout:
writer = csv.writer(fout)
writer.writerows([ [point.x, point.y] for point in new_points])
Hope this helps someone else.

Remove values outside of axis range?

Alright, I have a basic script to plot trajectories of an object. I have basic equations of motion solving the position of the object with respect to time. The plot itself is a 3D representation of the trajectory of the object.
I've successfully got the axis limits set, and now I want to make sure I don't see any values for this trajectory that fall OUTSIDE the axis limits. Right now, the trajectory falls below the x-y plane and continues downwards, outside of the 3D plot... Is there any way to prevent this?
Here's the entire code:
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
### Define Variables ###
time = (10) #Time to calculate the equations
t = np.linspace(0, time, 100)
g = (9.81) #Gravity
vxo = (3) #initial velocity in the x-direction
vyo = (1) #initial velocity in the y-direction
vzo = (0) #initial velocity in the z-direction
xo = (0) #initial x-position
yo = (0) #initial y-position
zo = (9) #initial z-position
### Equations of Motion ###
x = (xo + (vxo * t))
y = (yo + (vyo * t))
z = (10 - (.5 * g * (t**2)))
ax.plot(x, y, z, label=('Trajectory of Soccer Ball ' + str(time)))
ax.legend()
### Set axis limits ###
ax.set_xlim3d(0,10)
ax.set_ylim3d(0,10)
ax.set_zlim3d(0,10)
plt.show()
If your object is going beyond the limits of your axis; it is because the equations are telling it to do that. You'll have to constraint your results and filter before plotting them. Something like:
x = equation1()
if( x > someLimit )
handle error / object bounces by inverting the direction perhaps
plot(x)

Resources