Access Z coordinate in a LINESTRING Z in geopandas? - 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.

Related

adding two three dimensional numpy array:

I have two numpy array : X shape is (68,44,13) and X_toadd shape is: (68,44,7)I want to add them together in a way that I will  have X_new shape as (68,44, 20). So, I need to keep the first two dimensions of X and add the 7 columns from X_toadd's third dimension to the 13 columns.
how should I do that?
add, append, and concatenate are tried but the result is not what I want which should have the shape (68,44,20)!
You need to specify which axis to use to glue things together.
Here, -1 denotes the first axis from the the back.
import numpy as np
a,b = np.zeros((68,44,13)), np.zeros((68,44,7))
c = np.concatenate([a,b], axis=-1)
c.shape
(68, 44, 20)

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

How to solve the lack of precision of the coordinates of the centroid points after a buffer?

When generating polygons by buffer (here squares), the geometric points used for generation have different coordinates than those taken by the .centroid method on the polygon after their generation.
Here is an example with just one point.
from shapely.ops import transform
import geopandas as gpd
import shapely.wkt
import pyproj
from math import sqrt
def edge_size(area): return sqrt(area)*1e3
point = "POINT (4379065.583907348 2872272.254645019)"
point = shapely.wkt.loads(point)
center = gpd.GeoSeries(point)
project = pyproj.Transformer.from_proj(
pyproj.Proj('epsg:3395'),
pyproj.Proj('epsg:4326'),
always_xy=True)
center = center.apply(lambda p: transform(project.transform, p))
print(center.iloc[0])
square = point.buffer(
edge_size(3), cap_style=3) #distance of 3km2
square = gpd.GeoSeries(square)
square = square.apply(lambda p: transform(project.transform, p))
square = square.apply(lambda p: p.centroid)
print(square.iloc[0])
#POINT (39.33781544185747 25.11929860805248)
#POINT (39.33781544185747 25.11929777802279)
This leads to processing errors afterwards.
First of all, is this normal? And how to solve this problem?
I also reported my problem here. Thank you for your attention.
Copying my answer from GitHub for posterity.
This is not a bug but a misunderstanding of coordinate transformation. You have to keep in mind that what is square in one projection is not square in another.
If you stick to the same CRS, the output of the centroid of a buffer equals the initial point. But the centroid of a reprojected polygon is slightly off, specifically because you did reprojection that skewed the geometry in one direction.
How to overcome this problem?
Do all your operations in one CRS and reproject once you are done.

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])

matplotlib: histogram and bin labels

I'm trying to plot a histogram with bar chart, and I'm having difficulties figuring out how to align the x-axis labels with the actual bins. The code below generates the following plot:
as you can see, the end of each x-label is not aligned to the center of its bin. The way i'm thinking about this is: when i apply a 45-degree rotation, the label pivots around its geometrical center. I was wondering if it's possible to move the pivot up to the top of the label. (Or simply translate all the labels slightly left.)
import matplotlib.pyplot as plt
import numpy as np
#data
np.random.seed(42)
data = np.random.rand(5)
names = ['A:GBC_1233','C:WERT_423','A:LYD_342','B:SFS_23','D:KDE_2342']
ax = plt.subplot(111)
width=0.3
bins = map(lambda x: x-width/2,range(1,len(data)+1))
ax.bar(bins,data,width=width)
ax.set_xticks(map(lambda x: x, range(1,len(data)+1)))
ax.set_xticklabels(names,rotation=45)
plt.show()
Use:
ax.set_xticklabels(names,rotation=45, rotation_mode="anchor", ha="right")
The output is:

Resources