Given land polygons as a Shapely MultiPolygon, I want to find the (Multi-)Polygon that represents the e.g. 12 nautical mile buffer around the coastlines.
Using the Shapely buffer method does not work since it uses euclidean calculations.
Can somebody tell me how to calculate geodesic buffers in python?
This is not a shapely problem, since shapely explicitly tells in its documentation that the library is for planar computation only. Nevertheless, in order to answer your question, you should specify the coordinate systems you are using for your multipolygons.
Assuming you are using WGS84 projection (lat,lon), this is a recipe I found in another SO question (fix-up-shapely-polygon-object-when-discontinuous-after-map-projection). You will need pyproj library.
import pyproj
from shapely.geometry import MultiPolygon, Polygon
from shapely.ops import transform as sh_transform
from functools import partial
wgs84_globe = pyproj.Proj(proj='latlong', ellps='WGS84')
def pol_buff_on_globe(pol, radius):
_lon, _lat = pol.centroid.coords[0]
aeqd = pyproj.Proj(proj='aeqd', ellps='WGS84', datum='WGS84',
lat_0=_lat, lon_0=_lon)
project_pol = sh_transform(partial(pyproj.transform, wgs84_globe, aeqd), pol)
return sh_transform( partial(pyproj.transform, aeqd, wgs84_globe),
project_pol.buffer(radius))
def multipol_buff_on_globe(multipol, radius):
return MultiPolygon([pol_buff_on_globe(g, radius) for g in multipol])
pol_buff_on_globe function does the following. First, build an azimuthal equidistant projection centered in the polygon centroid. Then, change the coordinate system of the polygon to that projection. After that, builds the buffer there, and then change the coordinate system of the buffered polygon to WGS84 coordinate system.
Some special care is needed:
You will need to find out how to translate the distance you want to the distance used in aeqd projection.
Be careful of not buffering including the poles (see the mentioned SO question).
The fact that we are using the centroid of the polygon to center the projection should guaranty the answer is good enough, but if you have specif precision requirements you should NOT USE this solution, or at least make a characterization of the error for the typical polygon you are using.
Related
Long story short I can't get cartopy to install in my environment so I'm looking for alternative ways of doing things it might be used for.
I've recently been following this tutorial which uses cartopy to alter the path of shapely linestrings to take into account the curvature of the earth:
"Cartopy can be used to manipulate the way that lines are plotted. The transform=ccrs.Geodetic() method transforms the LineStrings to account for the earths curvature"
assuming I can just google the actual value that is the curvature of the earth, are there any ways I could manually manipulate the linestrings to achieve roughly the same effect?
It's probably feasible to implement the great circle algorithms yourself, but there are also other options. If you manage the install pyproj for example, you can use the example below, it samples a given amount of points between two locations on earth.
Note that although I still use Cartopy to show the coastlines (for reference), the actual plotting of the line (great circle) is fully independent of Cartopy and can be used with a plain Matplotlib axes as well.
And you can always read the coastlines or other borders/annotation etc for your map without Cartopy as well, as long as you pay attention to the projection. Matplotlib has all the primatives to do this (lines, PathCollections etc), it's just that Cartopy makes it a lot more convenient.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from pyproj import Geod
# from
lat1 = 55.
lon1 = -65
# to
lat2 = 30
lon2 = 80
n_samples = 1000
g = Geod(ellps='WGS84')
coords = g.npts(
lon1, lat1, lon2, lat2, n_samples, initial_idx=0, terminus_idx=0,
)
lons, lats = zip(*coords)
fig, ax = plt.subplots(
figsize=(10,5), dpi=86, facecolor="w",
subplot_kw=dict(projection=ccrs.PlateCarree(), xlim=(-180,180), ylim=(-90, 90)),
)
ax.axis("off")
ax.coastlines(lw=.3)
ax.plot(lons, lats, "r-") # <- no cartopy, just x/y points!
The amount of points you sample should probably depend on the distance between both points, which can also be calculated using the g.inv(...) function. And the amount is a tradeoff between accuracy and performance. For most applications you probably want to keep it as low as possible, just above where you start to visibly see the effect.
I am using gluLookAt with a camera whose coordinates are xCam, yCam and zCam. The coordinates of the object the camera is looking at are xPos, yPos, and zPos. There are variables named mouseturnX and mouseturnY, which measure the deviation of the mouse from the middle of the screen in the x-axis and the y-axis. The variable camdist describes the distance between camera and the object it looks at.
The code of the cameraposition is this:
xCam = sin(mouseturnX)*camdist+xPos
yCam = mouseturnY+yPos
zCam = cos(mouseturnX)*camdist+zPos
I now made a polygon object, which I rotate with:
glRotatef(mouseturnX,0,1,0)
Usually it should only show me the backside of the object, it does not matter which position the camera has. But now it does not turn correctly. I tried it with other rotation-axises, there it works fine, but with the y-axis it just does not want to work. I tried changing the camdist from positive to negative, the mouseturnX in the glRotatef function from positive to negative and back to positive again. It just does not work. I used glPushMatrix before the rotation command and glPopMatrix after it. One line before the rotation command I used the translate function to set a fixpoint for the polygon.
Edit: The polygon actually spins, but not in the right amount. It seems like I have to multiply the rotation of the polygon with something.
I found the multiplicator by trying. It is 56.5. It is not perfect, but it works.
I am working on converting a small project I wrote to find overlapping boundaries of a shape file within a radius of a certain point. This original project was a mock up project I wrote using Shapely and GeoPandas, to make this more suitable for production, I am converting it all to GeoDjango.
There is one thing that is vital to this program, which is to create an equidistant projection of a circle on a map. I was able to do this with shapely objects using pyproj and functools.
Let it be known that this solution was found on stackoverflow and is not my original solution.
from shapely import geometry
from functools import partial
def createGeoCircle(lat, lng, mi):
proj_wgs84 = pyproj.Proj(init='epsg:4326')
aeqd_proj = '+proj=aeqd +lat_0={lat} +lon_0={lng} +x_0=0 +y_0=0'
project = partial(
pyproj.transform,
pyproj.Proj(aeqd_proj.format(lat=lat, lng=lng)),
proj_wgs84)
buf = geometry.Point(0, 0).buffer(mi * 1.60934 * 1000)
circle = transform(project, buf)
return circle
I attempted to again use this solution and create a geoDjango MultiPolygon object from the shapely object, but it results in incorrect placement and shapes.
Here is the code I use to cast the shapely object coming from the above function.
shape_model(geometry=geos.MultiPolygon(geos.GEOSGeometry(createGeoCircle(41.378397, -81.2446768, 1).wkt)), state="CircleTest").save()
Here is the output in Django Admin. this picture is zoomed in to show the shape, but the location is in the middle of Antarctica. The coordinates given were meant to show in Ohio.
To clear a few things up, my model is as follows:
class shape_model(geo_models.Model):
state = geo_models.CharField('State Territory ID', max_length=80)
aFactor = geo_models.FloatField()
bFactor = geo_models.FloatField()
geometry = geo_models.MultiPolygonField(srid=4326)
I can get the location correct by simply using a geodjango point and buffer, but it shows up as an oval as it is not equidistant. If anyone has any suggestions or hints, I would be very appreciative to hear them!
Okay, I have found a solution to this problem. I used the shapely equidistant projection code and expanded it to convert it back to EPSG:4326. The updated function is as follows:
def createGeoCircle(lat, lng, mi):
point = geometry.Point(lat, lng)
local_azimuthal_projection = f"+proj=aeqd +lat_0={lat} +lon_0={lng} +x_0=0 +y_0=0"
proj_wgs84 = pyproj.Proj('epsg:4326')
wgs84_to_aeqd = partial(
pyproj.transform,
proj_wgs84,
pyproj.Proj(local_azimuthal_projection),
)
aeqd_to_wgs84 = partial(
pyproj.transform,
pyproj.Proj(local_azimuthal_projection),
proj_wgs84,
)
point_transformed = transform(wgs84_to_aeqd, point)
buffer = point_transformed.buffer(mi * 1.60934 * 1000)
buffer_wgs84 = transform(aeqd_to_wgs84, buffer)
return json.dumps(geometry.mapping(buffer_wgs84))
I also dump the geometry mapping from this function so it can now be loaded directly into the geos MultiPolygon rather than using the wkt of the object. I load the circle into a model and save it using the following:
shape_model(geometry=geos.MultiPolygon(geos.GEOSGeometry(createGeoCircle(41.378397, -81.2446768, 1))), state="CircleTest", aFactor=1.0, bFactor=1.0).save()
FYI this is not a native geodjango solution and relies on many other packages. If someone has a native solution, I would greatly prefer that!
I need to develop a tool (eg: calculate polygon area) and integrate it with Google Maps. I am not familiar with java. Can I do this using python? If yes, how can I go about integrating my code with Maps?
You can do it, using OpenStreetMap instead of Google map, in IPython/Jupyter Notebook, through ipyleaflet package.
Just write(or import) your script in Ipython Notebook(a python based env.) and then take a look at here;
https://github.com/ellisonbg/ipyleaflet/tree/master/examples
you will be able to draw whatever you want defining new Layer and so on...
Here an example:
Open your Ipython Notebook and import these modules;
from ipyleaflet import (
Map,
Marker,
TileLayer, ImageOverlay,
Polyline, Polygon, Rectangle, Circle, CircleMarker,
GeoJSON,
DrawControl
)
m = Map(zoom=0)
dc = DrawControl()
def handle_draw(self, action, geo_json):
print(action)
print(geo_json)
dc.on_draw(handle_draw)
m.add_control(dc)
m
The map will be appeared
Zoom by double clicking on the your interesting spot, then draw your polygon using "Draw a polygon" item.
This is just a suggestion, you can use other methods to calculate the polygon's area
import pyproj
import shapely
import shapely.ops as ops
from shapely.geometry.polygon import Polygon
from functools import partial
my_poly = dc.last_draw['geometry']['coordinates'][0]
geom = Polygon(my_poly)
geom_area = ops.transform(
partial(
pyproj.transform,
pyproj.Proj(init='EPSG:4326'),
pyproj.Proj(
proj='aea',
lat1=geom.bounds[1],
lat2=geom.bounds[3])),
geom)
print (geom_area.area, 'square meters, which is equal to',geom_area.area/1000000, 'square kilometers')
2320899322382.008 square meters, which is equal to 2320899.3223820077 square kilometers
In reading TLE and calculating the orbit of satellites, does PyEphem regard the earth as the sphere or as the ellipse ?
The underlying astronomy library beneath PyEphem is named libastro, and here is its code for doing satellite computations:
https://github.com/brandon-rhodes/pyephem/blob/master/libastro-3.7.5/earthsat.c
It looks like it simply considers the Earth a sphere; the only place that I see the shape of the Earth even coming into the calculation is where the height is produced from its distance from the Earth's surface, where it just uses a constant radius instead of anything fancier:
#if SSPELLIPSE
#else
*Height = r - EarthRadius;
#endif
So I think your answer is “sphere.”