Calculate bounding box for linestring containing geographic coordinates - python

I calculated linestring from google maps directions api.
I converted the linestring to GEOSGeometry object. I need to another region which covers all the points at a distance of 'd' from the linestring object.
The distance is in m, km.
GEOS API provides GEOSGeometry.buffer(width, quadsegs=8) to do so which works well in 2-D projection.
But how to do so for spherical model ? Is it related to SRID.
from django.contrib.gis.geos import LineString
from django.contrib.gis.geos import GEOSGeometry
directions = maps_client.directions(source, destination)
overview_polyline = decode_polyline(directions[0]['overview_polyline'])
linestring_obj = LineString(overview_polyline)
# FOR 2-D projection
bounding_box = linestring_obj.buffer(width=100)
# For spherical model
# ???

For geographical distance in meters to make sense, you will always have to go through a projected coordinate system, so I would suggest that you transform your data to a projected coordinate system, create the buffer and project it back. For instance:
# Specify the original srid of your data
orig_srid = 4326
# Create the linestring with the correct srid
linestring_obj = LineString(overview_polyline, srid=orig_srid)
# Transform (project) the linestring into a projected coorinate system
linestring_obj.transform(3857)
# Compute bbox in in that system
bounding_box = linestring_obj.buffer(width=100)
# Transform bounding box into the original coorinate system of your data
bounding_box.transform(orig_srid)

Related

Calculate the centroid of a rectangle geometry in python

I have the following Polygon geometry. When I calculate the centroid, I get an invalid / inaccurate POINT. I am using geopandas centroid to calculate the point.
https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoSeries.centroid.html
import shapely
from shapely import wkt
from shapely.geometry import box
import geopandas as gpd
g = "POLYGON ((-96.8115234375 32.87109375, -96.8115234375 -96.767578125, 32.8271484375 -96.767578125, 32.8271484375 32.87109375, -96.8115234375 32.87109375))"
print(wkt.loads(g).centroid)
POINT (-31.9921875 -31.9482421875)
How do I calculate the centroid POINT lat, long coordinates of the box? The shape of the Polygon is rectangle.
Your code is fine, your polygon is not and does not represents a rectangle area in Dallas, TX. Let's split it into vertices to make it apparent:
g = "POLYGON ((-96.8115234375 32.87109375,
-96.8115234375 -96.767578125,
32.8271484375 -96.767578125,
32.8271484375 32.87109375,
-96.8115234375 32.87109375))"
The first point is good and is indeed in Dallas. The second point has wrong latitude, -96.7... (it is south of South pole :) - however Shapely does not care and accepts it). Third point has wrong both latitude and longitude, etc. Apparently, latitudes and longitudes got mixed up here.

project local coordinates to global GPS with reference point

I have a bunch of shapes (e.g. shapely LineStrings or Polygons) in a geopandas GeoDataFrame.
The shapes specify coordinates in a local 200x200 meters grid, i.e. all coordinates are between (0, 0) and (200, 200).
I now would like to "place" these lines globally.
For this, I want to specify a GPS Point (with a given lat/lon) as a reference.
My first (naive) approach would be to use geographiclib, take all shapes' coords (in local X/Y) and apply the following transformation and "recreate" the shape:
# Convert coordinates to GPS location
from shapely.geometry import LineString
from geographiclib.geodesic import Geodesic
geod = Geodesic.WGS84 # the base geodesic (i.e. the world)
origin = (48.853772345870176, 2.350983211585546) # this is somewhere in Paris, for example
def local_to_latlong(x, y, orientation=0, scale=1):
""" Two step process.
- First walk x meters to east from origin.
- Then, from that point, walk y meters north from origin.
Optional:
- orientation allows to "spin" the coordinates
- scale allows to grow/shrink the distances
"""
go_X = geod.Direct(*origin, orientation + 90, x * scale) # x is East-coordinate
go_Y = geod.Direct(go_X["lat2"], go_X["lon2"], orientation + 0, y * scale) # y is North-coordinate
return go_Y["lat2"], go_Y["lon2"]
original_line = LineString([(0,0), (100,100), (200,100)])
global_line = LineString([local_to_latlong(x, y) for y, x in original_line.coords])
However, I hope that this is not the smartest way to do it, and that there are smarter ways out there...
I would like to apply such a transformation onto any shape within a GeoDataFrame. Ideally, it would work using a "to_crs", but I am not sure how to transform the shapes so they are "in reference to a origin" and which crs to use.
given your origin is EPSG:4326, you can estimate the UTM zone
with this you can get UTM zone coordinates of origin
translate your custom 200x200 metre zone into co-ordinates of UTM zone
finally use to_crs() to transform into EPSG:4326
import shapely.geometry
import geopandas as gpd
import pandas as pd
import numpy as np
# generate some polygons (squares), where grid is 200*200
gdf = gpd.GeoDataFrame(
geometry=pd.DataFrame(
np.repeat(np.sort(np.random.randint(0, 200, [20, 2]), axis=1), 2, axis=1)
).apply(lambda d: shapely.geometry.box(*d), axis=1)
)
# chage to linestrings, clearer when we plot
gdf["geometry"] = gdf["geometry"].exterior
origin = (2.350983211585546, 48.853772345870176) # this is somewhere in Paris, for example
# work out utm crs of point. utm is in metres
gdf_o = gpd.GeoDataFrame(geometry=[shapely.geometry.Point(origin)], crs="EPSG:4326")
crs = gdf_o.estimate_utm_crs()
# where is origin in utm zone
xo,yo = gdf_o.to_crs(crs).loc[0,"geometry"].xy
# translate custom zone to co-ordinates of utm zone
# assume point is center of 200x200 grid (hence subtract 100)
gdf_gps = gdf["geometry"].translate(xoff=xo[0]-100, yoff=yo[0]-100).set_crs(crs).to_crs("epsg:4326")
# plot on map to show it has worked...
m = gdf_gps.explore()
m = gdf_o.explore(m=m, color="red", marker_kwds={"radius":20})
m

Calculate distance between LineString and Point in meters

I have LineString and Point, and I want to calculate distance between them in meters:
line = LINESTRING (7.301606 52.5602366, 7.300065099999999 52.5587741)
point = POINT (8.02 52.303333)
I use: line.distance(point). I got a distance, but I don't know how to convert to meters or km. I am using geopandas, and I read that unit is CRS. Any help will be appreciated.
as per comments you need to specify the CRS I am using geopandas, and I read that unit is CRS. I assume by this statement you mean WSG84 CRS, which is EPSG:4326
given limitation of geopandas to have only one geometry column per GeoDataFrame have put LINESTRING and POINT into separate data frames
project from WSG84 onto UTM CRS https://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system. This then means co-ordinates are expressed in meters. This will not work if LINESTRING and POINT are in different UTM zones
then it's exceptionally simple to use https://geopandas.readthedocs.io/en/latest/docs/reference/api/geopandas.GeoSeries.distance.html
import shapely.wkt
import geopandas as gpd
gdfl = gpd.GeoDataFrame(geometry=[shapely.wkt.loads("LINESTRING (7.301606 52.5602366, 7.300065099999999 52.5587741)")], crs="EPSG:4326")
gdfp = gpd.GeoDataFrame(geometry=[shapely.wkt.loads("POINT (8.02 52.303333)")], crs="EPSG:4326")
utm = gdfl.estimate_utm_crs()
gdfl.to_crs(utm).distance(gdfp.to_crs(utm))
output
56592.878178

Intersect two shapely polygons on the Earth projection

as i know, shapely use only cartesian coordinate system. I have two point on the earth with lat and lon coordinates. I need create buffer with 1km radius around this two points and find polygon, where this buffers intersect.
But construstion
buffer = Point(54.4353,65.87343).buffer(0.001) create simple circle, but in projection on the Earth it becomes ellipse, but i need two real circle with 1 km radius.
I think, i need convert my buffers into new projection and then intersect it, but dont now how correct do it.
You need to do what you say. For that, you will need to use a library that handles projections (pyproj is the choice here). There is a similar question in Geodesic buffering in python
import pyproj
from shapely.geometry import MultiPolygon, Polygon, Point
from shapely.ops import transform as sh_transform
from functools import partial
wgs84_globe = pyproj.Proj(proj='latlong', ellps='WGS84')
def point_buff_on_globe(lat, lon, radius):
#First, you build the Azimuthal Equidistant Projection centered in the
# point given by WGS84 lat, lon coordinates
aeqd = pyproj.Proj(proj='aeqd', ellps='WGS84', datum='WGS84',
lat_0=lat, lon_0=lon)
#You then transform the coordinates of that point in that projection
project_coords = pyproj.transform(wgs84_globe, aeqd, lon, lat)
# Build a shapely point with that coordinates and buffer it in the aeqd projection
aeqd_buffer = Point(project_coords).buffer(radius)
# Transform back to WGS84 each coordinate of the aeqd buffer.
# Notice the clever use of sh_transform with partial functor, this is
# something that I learned here in SO. A plain iteration in the coordinates
# will do the job too.
projected_pol = sh_transform(partial(pyproj.transform, aeqd, wgs84_globe),
aeqd_buffer)
return projected_pol
The function point_buff_on_globe will give you a polygon in lat lon that is the result of buffering the given point in the Azimuthal Equidistant Projection centered in that point (the best you can do with your requirements. Two observations:
I don't remember the units of the radius argument. I think is in meters, so if you need a 10 km buffer, you will need to pass it 10e3. But please, check it!
Beware of using this with radius to wide or points that are to far away from each other. Projections work well when the points are near to the point you are centering the projection.

Ordering polygon coordinates for plotting

I have a model grid composed of many cells for which I would like to plot a shaded polygon on a matplotlib basemap.
Using pyproj, I first projected the points, before creating a polygon using shapely.geometry's Polygon class to extract the grid's exterior coordinates from. I then revert them back to WGS84 for passing to my plotting function:
grid_x_mesh, grid_y_mesh = pyproj.transform(wgs84, nplaea, grid_lons, grid_lats)
grid_x = grid_x_mesh.ravel()
grid_y = grid_y_mesh.ravel()
grid_poly = Polygon(zip(grid_x, grid_y))
grid_x, grid_y = grid_poly.exterior.coords.xy
grid_plons, grid_plats = pyproj.transform(nplaea, wgs84, grid_x, grid_y)
Then, using the matplotlib.basemap method, I projected the WSG84 coordinates to the map projection (nplaea in this case) and
grid_poly_x, grid_poly_y = m(grid_plons, grid_plats)
grid_poly_xy = zip(grid_poly_x, grid_poly_y)
grid_poly = Polygon(grid_poly_xy, facecolor='red', alpha=0.4)
plt.gca().add_patch(grid_poly)
When attempting to do so, I am getting a criss-cross pattern, which I assume has to do the ordering of the coordinates that I supplied to the polygon function.
I would think this has to do with either how I extracted the exterior coordinates, or just the ordering of the coordinate lists when I created the final polygon to plot.
Is there a clever way of ordering these properly if that is the problem?
Plotted polygon
Close-up
I agree there is some misarrangement of the grid coordinates. How was grid_lons created? Possibly a cleaner way to use Pyproj with Shapely geometries is to use a relatively new function shapely.ops.transform. For example:
import pyproj
from shapely.geometry import Polygon, Point
from shapely.ops import transform
from functools import partial
project = partial(
pyproj.transform,
pyproj.Proj(init='epsg:4326'), # WGS84 geographic
pyproj.Proj(init='epsg:3575')) # North Pole LAEA Europe
# Example grid cell, in long/lat
poly_g = Polygon(((5, 52), (5, 60), (15, 60), (15, 52), (5, 52)))
# Transform to projected system
poly_p = transform(project, poly_g)
The sanity of the coordinates should be preserved through the transformation (assuming that they were sane to begin with).
Soooo... apparently the shapely.geometry.Polygon method was drawing the polygon with all interior grid coordinates, which I realized due to the grid_plons and grid_plats having the same length as the np.ravel()'ed mesh coordinate array.
I ended up just doing a manual extraction of the external coordinates from the mesh coordinate arrays before passing them to the Polygon method (see below). Though, I imagine there may be a prettier and more general way of doing this.
Manual Extraction method:
grid_x_mesh, grid_y_mesh = pyproj.transform(wgs84, nplaea, grid_lons, grid_lats)
# The coordinates must be ordered in the order they are to be drawn
[grid_x.append(i) for i in grid_x_mesh[0,:]]
[grid_x.append(i) for i in grid_x_mesh[1:-1,-1]]
# Note that these two sides of the polygon are appended in reverse
[grid_x.append(i) for i in (grid_x_mesh[-1,:])[::-1]]
[grid_x.append(i) for i in (grid_x_mesh[1:-1,0])[::-1]]
[grid_y.append(i) for i in grid_y_mesh[0,:]]
[grid_y.append(i) for i in grid_y_mesh[1:-1,-1]]
[grid_y.append(i) for i in (grid_y_mesh[-1,:])[::-1]]
[grid_y.append(i) for i in (grid_y_mesh[1:-1,0])[::-1]]
grid_poly = Polygon(zip(grid_x, grid_y))

Categories

Resources