Can you do Azimuthal Equidistant projections natively in GeoDjango? - python

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!

Related

How to draw colour gradient lines into folium map?

I would like to represent a tree (virus spreading) into the world map. I use dendropy for working with trees; geopandas & shapely for processing regions, nodes & edges; folium for rendering this into interactive web-maps. The current result of the code below:
def colour_map(country_name: str) -> str:
if country_name == "Russia":
return "green"
else:
return "blue"
world_map = folium.Map(
location=(30.328056, -0.352369),
zoom_start=3,
tiles="cartodbdark_matter",
max_bounds=True,
min_lon=-45,
max_lon=315,
)
exUSSR.explore(
column="Name",
m=world_map,
highlight=False,
tooltip=False,
cmap=colour_map,
legend=False,
)
gpd.GeoDataFrame(nodes).convert_dtypes().explore(m=world_map, color="red")
gpd.GeoDataFrame(edges).convert_dtypes().explore(m=world_map, color="red")
This has not yet been brought to the state that I would desire. One of the problems is that it is really hard to trace the virus dissipation. In my view, it may be solved with edges colour gradient depending onto the age, especially if using logarithmic scale approximated to bifurcation rate. But I do not know how to set it. For instance, I can set a colourmap via cmap attribute in explore method from geopandas but it sets a colour of the whole object (edge in the case) depending onto column attribute values. The same is able to achieve via direct PolyLine initialisation in folium.
So my question is either how to give colour gradient to folium object or achieve a similar effect? I am ready to heed any good practice advice!

How do I test that PyVista successfully plotted a figure?

I am generating 3D meshes in PyVista, and I would like to update my integration test suite to ensure that it successfully shows my plots.
I'm hoping to adapt the methodology described here, to work with PyVista. Unfortunately, I can't find any results for any equivalent function to plt.gcf() in PyVista.
Does anyone know of a workaround?
There's a few ways of doing this. First, pyvista returns a instance of pyvista.plotting.renderer.CameraPosition upon a successful plot. For example:
>>> import pyvista
>>> sphere = pyvista.Sphere()
>>> cpos = sphere.plot(off_screen=True)
>>> print(type(cpos))
<class 'pyvista.plotting.renderer.CameraPosition'>
Since it's necessary to setup a plot and renderer to properly display a plot, getting a return camera position means that your plot was successful.
Alternatively, you can save the screenshot and check that the file exists:
import os
import pyvista
sphere = pyvista.Sphere()
cpos = sphere.plot(off_screen=True, screenshot='tmp.png')
assert os.path.isfile('tmp.png')
You could also check the content of the saved image as well (or potentially file size)

Google maps using python

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

Geodesic buffering in python

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.

Rasterizing a GDAL layer

Edit
Here is the proper way to do it, and the documentation:
import random
from osgeo import gdal, ogr
RASTERIZE_COLOR_FIELD = "__color__"
def rasterize(pixel_size=25):
# Open the data source
orig_data_source = ogr.Open("test.shp")
# Make a copy of the layer's data source because we'll need to
# modify its attributes table
source_ds = ogr.GetDriverByName("Memory").CopyDataSource(
orig_data_source, "")
source_layer = source_ds.GetLayer(0)
source_srs = source_layer.GetSpatialRef()
x_min, x_max, y_min, y_max = source_layer.GetExtent()
# Create a field in the source layer to hold the features colors
field_def = ogr.FieldDefn(RASTERIZE_COLOR_FIELD, ogr.OFTReal)
source_layer.CreateField(field_def)
source_layer_def = source_layer.GetLayerDefn()
field_index = source_layer_def.GetFieldIndex(RASTERIZE_COLOR_FIELD)
# Generate random values for the color field (it's here that the value
# of the attribute should be used, but you get the idea)
for feature in source_layer:
feature.SetField(field_index, random.randint(0, 255))
source_layer.SetFeature(feature)
# Create the destination data source
x_res = int((x_max - x_min) / pixel_size)
y_res = int((y_max - y_min) / pixel_size)
target_ds = gdal.GetDriverByName('GTiff').Create('test.tif', x_res,
y_res, 3, gdal.GDT_Byte)
target_ds.SetGeoTransform((
x_min, pixel_size, 0,
y_max, 0, -pixel_size,
))
if source_srs:
# Make the target raster have the same projection as the source
target_ds.SetProjection(source_srs.ExportToWkt())
else:
# Source has no projection (needs GDAL >= 1.7.0 to work)
target_ds.SetProjection('LOCAL_CS["arbitrary"]')
# Rasterize
err = gdal.RasterizeLayer(target_ds, (3, 2, 1), source_layer,
burn_values=(0, 0, 0),
options=["ATTRIBUTE=%s" % RASTERIZE_COLOR_FIELD])
if err != 0:
raise Exception("error rasterizing layer: %s" % err)
Original question
I'm looking for information on how to use osgeo.gdal.RasterizeLayer() (the docstring is very succinct, and I can't find it in the C or C++ API docs. I only found a doc for the java bindings).
I adapted a unit test and tried it on a .shp made of polygons:
import os
import sys
from osgeo import gdal, gdalconst, ogr, osr
def rasterize():
# Create a raster to rasterize into.
target_ds = gdal.GetDriverByName('GTiff').Create('test.tif', 1280, 1024, 3,
gdal.GDT_Byte)
# Create a layer to rasterize from.
cutline_ds = ogr.Open("data.shp")
# Run the algorithm.
err = gdal.RasterizeLayer(target_ds, [3,2,1], cutline_ds.GetLayer(0),
burn_values=[200,220,240])
if err != 0:
print("error:", err)
if __name__ == '__main__':
rasterize()
It runs fine, but all I obtain is a black .tif.
What's the burn_values parameter for ? Can RasterizeLayer() be used to rasterize a layer with features colored differently based on the value of an attribute ?
If it can't, what should I use ? Is AGG suitable for rendering geographic data (I want no antialiasing and a very robust renderer, able to draw very large and very small features correctly, possibly from "dirty data" (degenerate polygons, etc...), and sometimes specified in large coordinates) ?
Here, the polygons are differentiated by the value of an attribute (the colors don't matter, I just want to have a different one for each value of the attribute).
EDIT: I guess I'd use qGIS python bindings: http://www.qgis.org/wiki/Python_Bindings
That's the easiest way I can think of. I remember hand rolling something before, but it's ugly. qGIS would be easier, even if you had to make a separate Windows installation (to get python to work with it) then set up an XML-RPC server to run it in a separate python process.
I you can get GDAL to rasterize properly that's great too.
I haven't used gdal for a while, but here's my guess:
burn_values is for false color if you don't use Z-values. Everything inside your polygon is [255,0,0] (red) if you use burn=[1,2,3],burn_values=[255,0,0]. I'm not sure what happens to points - they might not plot.
Use gdal.RasterizeLayer(ds,bands,layer,burn_values, options = ["BURN_VALUE_FROM=Z"]) if you want to use the Z values.
I'm just pulling this from the tests you were looking at: http://svn.osgeo.org/gdal/trunk/autotest/alg/rasterize.py
Another approach - pull the polygon objects out, and draw them using shapely, which may not be attractive. Or look into geodjango (I think it uses openlayers to plot into browsers using JavaScript).
Also, do you need to rasterize? A pdf export might be better, if you really want precision.
Actually, I think I found using Matplotlib (after extracting and projecting the features) was easier than rasterization, and I could get a lot more control.
EDIT:
A lower level approach is here:
http://svn.osgeo.org/gdal/trunk/gdal/swig/python/samples/gdal2grd.py\
Finally, you can iterate over the polygons (after transforming them into a local projection), and plot them directly. But you better not have complex polygons, or you will have a bit of grief. If you have complex polygons ... you are probably best off using shapely and r-tree from http://trac.gispython.org/lab if you want to roll your own plotter.
Geodjango might be a good place to ask .. they will know a lot more than me. Do they have a mailing list? There's also lots of python mapping experts around, but none of them seem to worry about this. I guess they just plot it in qGIS or GRASS or something.
Seriously, I hope that somebody who knows what they are doing can reply.

Categories

Resources