Shapely polygon buffer problem with 'y' coordinates(double value) - python

I use shapes to work with contours. I need to add to the contours of different sizes around the field by a given value. Do not scale the contour by a certain percentage, but expand the border by the same given value, regardless of the size of the contour itself.
I am trying to do it like this:
from shapely.geometry import Polygon, LinearRing
coords = [(30.3283020760901, 59.929340439331035), (30.32625283518726, 59.929669569762034), (30.326617897500824, 59.93065894162025), (30.328354001537814, 59.93056342794558), (30.329838363175877, 59.93089851628186), (30.330225213253033, 59.929729335995624), (30.3283020760901, 59.929340439331035)]
poly_B = Polygon(coords)
poly_A = s.buffer(0.005, quad_segs=10.0, cap_style=1, join_style=2, mitre_limit=10.0)
Or like this:
r = LinearRing(coords)
poly_B = Polygon(r)
poly_A = Polygon(s.buffer(0.005).exterior, [r])
But every time I get a contour in which the Y coordinate is doubled(see image).
Help me figure out where I'm wrong.
I need the fields of the larger contour to be uniform relative to the smaller one.

Related

How to project a shapely geometries to a different shape

I have a template in the form of a GeoPandas GeoDataFrame with several shapely geometries that correspond to areas on a page.
For this process, the pages are photographed and I need the geometries in my template with the corresponding areas on the photographed page. Right now, my "projection" of the template to the bounding box of the photographed page does not align with the photographed image. I'm also pretty sure there is a better way to do this.
I've created a simplified example to illustrate:
I have this document, with both axes scaled to 1.
I created template that matches the objects on the image
shapes = [
('line1', LineString([(0.146, 0.216), (0.476 , 0.216)])),
('line2', LineString([(0.498, 0.871), (0.838, 0.871)]))
]
shapes = gpd.GeoDataFrame(shapes, columns=['name', 'geometry'], geometry='geometry')
Plotting that on top of the image we can verify that the shapes closely align with the shapes in the image.
plt.imshow(img, extent=[0,1,1,0], aspect=1.4142)
ax = plt.gca()
shapes.plot(ax=ax)
ax.set_aspect(1.4142)
I take a photograph of the same document get the bounding box of the page.
photo = load_image('example_photo.jpg')
bbox = Polygon([(0.847429096698761, 0.047594085335731506),
(0.9442346692085266, 0.8787651062011719),
(0.05563090369105339, 0.8789013028144836),
(0.12740036845207214, 0.06380380690097809),
(0.847429096698761, 0.047594085335731506)])
plt.figure(figsize=(6,6))
plt.imshow(photo, extent=[0,1,1,0], aspect=1.4142)
plt.plot(*bbox.boundary.xy)
The problem is in the next step as I try to remap or project the original template into the shape of the bounding box. This is what I have tried, but I'm sure this isn't the most efficient way to do this. Also it doesn't work.
Summary of the method below is:
figure out which edge is top, bottom, right, left and orient them.
map each point into the new shape finding the intersection of the lines meeting each opposite side of the bounding box at proportionaly the same place as in the unit square.
def get_edges(bbox, visualize=True):
'''Takes shapely polygon with exactly 5 points'''
# check for 5 points
if len(bbox.boundary.coords) != 5:
raise('Polygon must have 5 points (4 sides)')
#find top/bottom edge
x, y = bbox.boundary.xy
# remove last point which must be the same as the first
x = np.array(x)[:-1]
y = np.array(y)[:-1]
# sort by y values. Higher is closer to top, lower closer to bottom
y_sorted = np.argsort(y)
# get the index of the top and bottom lines
top_points_idx = y_sorted[-2:]
bot_points_idx = y_sorted[:2]
# order the top point coords left to right
top_point_order = top_points_idx[
np.argsort(x[top_points_idx])
]
bot_point_order = bot_points_idx[
np.argsort(x[bot_points_idx])
]
top_points = np.array(bbox.boundary.coords)[top_point_order]
bot_points = np.array(bbox.boundary.coords)[bot_point_order]
left_points = LineString([bot_points[0], top_points[0]])
right_points = LineString([bot_points[1], top_points[1]])
top_points = LineString(top_points)
bot_points = LineString(bot_points)
return top_points, bot_points, left_points, right_points
def project_unit_square_point(point, bbox, visualize=False):
# check for 5 points
if len(bbox.boundary.coords) != 5:
raise('Polygon must have 5 points (4 sides)')
# use the position as the portion of the each side
x_scale, y_scale = point.coords[0]
top, bot, left, right = get_edges(bbox)
# find proportional intersections on edges
top_point = top.interpolate(x_scale*top.length)
bot_point = bot.interpolate(x_scale*bot.length)
left_point = left.interpolate(y_scale*left.length)
right_point = right.interpolate(y_scale*right.length)
# connect edge points
vline = LineString([top_point, bot_point])
hline = LineString([left_point, right_point])
# new point is intersection of vline and hline
new_point = vline.intersection(hline)
return new_point
def project_unit_square_geom(geom, bbox):
new_points = []
for point in geom.coords:
new_points.append(project_unit_square_point(Point(point), bbox))
new_geom = LineString(new_points)
return new_geom
# project geoms onto form
projected_shapes = []
for shape in shapes.geometry:
projected_shapes.append(
project_unit_square_geom(shape, bbox)
)
# create a new df for the mapped shapes
projected_shapes = gpd.GeoSeries(projected_shapes, name='geometry')
projected_shapes = gpd.GeoDataFrame({'name': shapes['name'],
'geometry': projected_shapes},
geometry='geometry')
Then when I visualize the result I get this:
plt.figure(figsize=(6,6))
plt.imshow(photo, extent=[0,1,1,0])
plt.plot(*bbox.boundary.xy)
ax = plt.gca()
projected_shapes.plot(ax=ax)
ax.set_aspect(1.4142)
Close but not close enough. Obvioulsy my approach is not working. How can I map the template shapes onto the new shape defined by the bounding box?
Here are the original images to work with.

Given a geotiff file, how does one find the single pixel closest to a given latitude/longitude?

I have a geotiff file that I'm opening with gdal in Python, and I need to find the single pixel closest to a specified latitude/longitude. I was previously working with an unrelated file type for similar data, so I'm completely new to both gdal and geotiff.
How does one do this? What I have so far is
import gdal
ds = gdal.Open('foo.tiff')
width = ds.RasterXSize
height = ds.RasterYSize
gt = ds.GetGeoTransform()
gp = ds.GetProjection()
data = np.array(ds.ReadAsArray())
print(gt)
print(gp)
which produces (for my files)
(-3272421.457337171, 2539.703, 0.0, 3790842.1060354356, 0.0, -2539.703)
and
PROJCS["unnamed",GEOGCS["Coordinate System imported from GRIB file",DATUM["unnamed",SPHEROID["Sphere",6371200,0]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["latitude_of_origin",25],PARAMETER["central_meridian",265],PARAMETER["standard_parallel_1",25],PARAMETER["standard_parallel_2",25],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH]]
Ideally, there'd be a single simple function call, and it would also return an indication whether the specified location falls outside the bounds of the raster.
My fallback is to obtain a grid from another source containing the latitudes and longitudes for each pixel and then do a brute force search for the desired location, but I'm hoping there's a more elegant way.
Note: I think what I'm trying to do is equivalent to the command line
gdallocationinfo -wgs84 foo.tif <longitude> <latitude>
which returns results like
Report:
Location: (1475P,1181L)
Band 1:
Value: 66
This suggests to me that the functionality is probably already in the gdal module, if I can just find the right method to call.
You basically need two steps:
Convert the lat/lon point to the raster-projection
Convert the mapx/mapy (in raster proj) to pixel coordinates
Given the code you already posted above, defining both projection systems can be done with:
from osgeo import gdal, osr
point_srs = osr.SpatialReference()
point_srs.ImportFromEPSG(4326) # hardcode for lon/lat
# GDAL>=3: make sure it's x/y
# see https://trac.osgeo.org/gdal/wiki/rfc73_proj6_wkt2_srsbarn
point_srs.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER)
file_srs = osr.SpatialReference()
file_srs.ImportFromWkt(gp)
Creating the coordinate transformation, and using it to convert the point from lon/lat to mapx/mapy coordinates (whatever projection it is) with:
ct = osr.CoordinateTransformation(point_srs, file_srs)
point_x = -114.06138 # lon
point_y = 51.03163 # lat
mapx, mapy, z = ct.TransformPoint(point_x, point_y)
To go from map coordinates to pixel coordinates, the geotransform needs to be inverted first. And can then be used to retrieve the pixel coordinates like:
gt_inv = gdal.InvGeoTransform(gt)
pixel_x, pixel_y = gdal.ApplyGeoTransform(gt_inv, mapx, mapy)
Rounding those pixel coordinates should allow you to use them for indexing the data array. You might need to clip them if the point you're querying is outside the raster.
# round to pixel
pixel_x = round(pixel_x)
pixel_y = round(pixel_y)
# clip to file extent
pixel_x = max(min(pixel_x, width-1), 0)
pixel_y = max(min(pixel_y, height-1), 0)
pixel_data = data[pixel_y, pixel_x]

Extract superpixels, retrieve edges and reduce mesh

How would one extract superpixels, retrieve edges then simplify those?
This is what I've gotten so far:
import skimage
import numpy as np
from rasterio import features
from shapely.geometry import Polygon, MultiPolygon
image = skimage.util.img_as_float(skimage.io.imread("image.jpg"))
labels = skimage.segmentation.slic(image, n_segments = 200, sigma = 5)
boundaries = skimage.segmentation.find_boundaries(labels).astype(np.uint8)
#this is where it goes wrong as rasterio creates shapes with distinct edges
shapes = rasterio.features.shapes(boundaries)
polygons = MultiPolygon([Polygon(sh[0]["coordinates"][0]) for sh in shapes])
out = polygons.simplify(0.05)
The problem here is that the simplification works on a per polygon basis and therefore its output isn't a tight mesh.
I'm looking to achieve something similar to this, so obtaining the edges and being able to simplify.

Cartesian projection issue in a FITS image through PyFITS / AstroPy

I've looked and looked for a solution to this problem and am turning up nothing.
I'm generating rectangular FITS images through matplotlib and subsequently applying WCS coordinates to them using AstroPy (or PyFITS). My images are in galactic latitude and longitude, so the header keywords appropriate for my maps should be GLON-CAR and GLAT-CAR (for Cartesian projection). I've looked at other maps that use this same map projection in SAO DS9 and the coordinates work great... the grid is perfectly orthogonal as it should be. The FITS standard projections can be found here.
But when I generate my maps, the coordinates are not at all Cartesian. Here's a side-by-side comparison of my map (left) and another reference map of roughly the same region (right). Both are listed GLON-CAR and GLAT-CAR in the FITS header, but mine is screwy when looked at in SAO DS9 (note that the coordinate grid is something SAO DS9 generates based on the data in the FITS header, or at least stored somewhere in the FITS file):
This is problematic, because the coordinate-assigning algorithm will assign incorrect coordinates to each pixel if the projection is wrong.
Has anyone encountered this, or know what could be the problem?
I've tried applying other projections (just to see how they perform in SAO DS9) and they come out fine... but my Cartesian and Mercator projections do not come out with the orthogonal grid like they should.
I can't believe this would be a bug in AstroPy, but I can't find any other cause... unless my arguments in the header are incorrectly formatted, but I still don't see how that could cause the problem I'm experiencing. Or would you recommend using something else? (I've looked at matplotlib basemap but have had some trouble getting that to work on my computer).
My header code is below:
from __future__ import division
import numpy as np
from astropy.io import fits as pyfits # or use 'import pyfits, same thing'
#(lots of code in between: defining variables and simple calculations...
#probably not relevant)
header['BSCALE'] = (1.00000, 'REAL = TAPE*BSCALE + BZERO')
header['BZERO'] = (0.0)
header['BUNIT'] = ('mag ', 'UNIT OF INTENSITY')
header['BLANK'] = (-100.00, 'BLANK VALUE')
header['CRVAL1'] = (glon_center, 'REF VALUE POINT DEGR') #FIRST COORDINATE OF THE CENTER
header['CRPIX1'] = (center_x+0.5, 'REF POINT PIXEL LOCATION') ## REFERENCE X PIXEL
header['CTYPE1'] = ('GLON-CAR', 'COORD TYPE : VALUE IS DEGR')
header['CDELT1'] = (-glon_length/x_length, 'COORD VALUE INCREMENT WITH COUNT DGR') ### degrees per pixel
header['CROTA1'] = (0, 'CCW ROTATION in DGR')
header['CRVAL2'] = (glat_center, 'REF VALUE POINT DEGR') #Y COORDINATE OF THE CENTER
header['CRPIX2'] = (center_y+0.5, 'REF POINT PIXEL LOCATION') #Y REFERENCE PIXEL
header['CTYPE2'] = ('GLAT-CAR', 'COORD TYPE: VALUE IS DEGR') # WAS CAR OR TAN
header['CDELT2'] = (glat_length/y_length, 'COORD VALUE INCREMENT WITH COUNT DGR') #degrees per pixel
header['CROTA2'] = (rotation, 'CCW ROTATION IN DEGR') #NEGATIVE ROTATES CCW around origin (bottom left).
header['DATAMIN'] = (data_min, 'Minimum data value in the file')
header['DATAMAX'] = (data_max, 'Maximum data value in the file')
header['TELESCOP'] = ("Produced from 2MASS")
pyfits.update(filename, map_data, header)
Thanks for any help you can provide.
In the modern definition of the -CAR projection (from Calabretta et al.), GLON-CAR/GLAT-CAR projection only produces a rectilinear grid if CRVAL2 is set to zero. If CRVAL2 is not zero, then the grid is curved (this should have nothing to do with Astropy). You can try and fix this by adjusting CRVAL2 and CRPIX2 so that CRVAL2 is zero. Does this help?
Just to clarify what I mean, try, after your code above, and before writing out the file:
header['CRPIX2'] -= header['CRVAL2'] / header['CDELT2']
header['CRVAL2'] = 0.
Any luck?
If you look at the header for the 'reference' file you looked at, you'll see that CRVAL2 is zero there. Just to be clear, there's nothing wrong with CRVAL2 being non-zero, but the grid is then no longer rectilinear.

Data binning: irregular polygons to regular mesh

I have thousands of polygons stored in a table format (given their 4 corner coordinates) which represent small regions of the earth. In addition, each polygon has a data value.
The file looks for example like this:
lat1, lat2, lat3, lat4, lon1, lon2, lon3, lon4, data
57.27, 57.72, 57.68, 58.1, 151.58, 152.06, 150.27, 150.72, 13.45
56.96, 57.41, 57.36, 57.79, 151.24, 151.72, 149.95, 150.39, 56.24
57.33, 57.75, 57.69, 58.1, 150.06, 150.51, 148.82, 149.23, 24.52
56.65, 57.09, 57.05, 57.47, 150.91, 151.38, 149.63, 150.06, 38.24
57.01, 57.44, 57.38, 57.78, 149.74, 150.18, 148.5, 148.91, 84.25
...
Many of the polygons intersect or overlap. Now I would like to create a n*m matrix ranging from -90° to 90° latitude and -180° to 180° longitude in steps of, for instance, 0.25°x0.25° to store the (area-weighted) mean data value of all polygons that fall within each pixel.
So, one pixel in the regular mesh shall get the mean value of one or more polygons (or none if no polygon overlaps with the pixel). Each polygon should contribute to this mean value depending on its area fraction within this pixel.
Basically the regular mesh and the polygons look like this:
If you look at pixel 2, you see that two polygons are inside this pixel. Thus, I have to take the mean data value of both polygons considering their area fractions. The result should be then stored in the regular mesh pixel.
I looked around the web and found no satisfactory approach for this so far. Since I am using Python/Numpy for daily work I would like to stick to it. Is this possible? The package shapely looks promising but I don't know where to begin with...
Porting everything to a postgis database is an awful amount of effort and I guess there will be quite a few obstacles in my way.
There are plenty of ways to do it, but yes, Shapely can help. It appears that your polygons are quadrilateral, but the approach I'll sketch doesn't count on that. You won't need anything other than box() and Polygon() from shapely.geometry.
For each pixel, find the polygons that approximately overlap with it by comparing the pixels bounds to the minimum bounding box of each polygon.
from shapely.geometry import box, Polygon
for pixel in pixels:
# say the pixel has llx, lly, urx, ury values.
pixel_shape = box(llx, lly, urx, ury)
for polygon in approximately_overlapping:
# say the polygon has a ``value`` and a 2-D array of coordinates
# [[x0,y0],...] named ``xy``.
polygon_shape = Polygon(xy)
pixel_value += polygon_shape.intersection(pixel_shape).area * value
If the pixel and polygon don't intersect, the area of their intersection will be 0 and the contribution of that polygon to that pixel vanishes.
I added a couple of things to my initial question, but this is a working solution so far. Do you have any ideas to speed things up? It is still quite slow. As input, I have over 100000 polygons and the meshgrid has 720*1440 grid cells. That is also why I changed the order, because there are a lot of grid cells with no intersecting polygons. Furthermore, when there is only one polygon that intersects with a grid cell, the grid cell receives the whole data value of the polygon.
In addition, since I have to store the area fraction and the data value for the "post-processing" part, I set the possible number of intersections to 10.
from shapely.geometry import box, Polygon
import h5py
import numpy as np
f = h5py.File('data.he5','r')
geo = f['geo'][:] #10 columns: 4xlat, lat center, 4xlon, lon center
product = f['product'][:]
f.close()
#prepare the regular meshgrid
delta = 0.25
darea = delta**-2
llx, lly = np.meshgrid( np.arange(-180, 180, delta), np.arange(-90, 90, delta) )
urx, ury = np.meshgrid( np.arange(-179.75, 180.25, delta), np.arange(-89.75, 90.25, delta) )
lly = np.flipud(lly)
ury = np.flipud(ury)
llx = llx.flatten()
lly = lly.flatten()
urx = urx.flatten()
ury = ury.flatten()
#initialize the data structures
data = np.zeros(len(llx),'f2')+np.nan
counter = np.zeros(len(llx),'f2')
fraction = np.zeros( (len(llx),10),'f2')
value = np.zeros( (len(llx),10),'f2')
#go through all polygons
for ii in np.arange(1000):#len(hcho)):
percent = (float(ii)/float(len(hcho)))*100
print("Polygon: %i (%0.3f %%)" % (ii, percent))
xy = [ [geo[ii,5],geo[ii,0]], [geo[ii,7],geo[ii,2]], [geo[ii,8],geo[ii,3]], [geo[ii,6],geo[ii,1]] ]
polygon_shape = Polygon(xy)
# only go through grid cells which might intersect with the polygon
minx = np.min( geo[ii,5:9] )
miny = np.min( geo[ii,:3] )
maxx = np.max( geo[ii,5:9] )
maxy = np.max( geo[ii,:3] )
mask = np.argwhere( (lly>=miny) & (lly<=maxy) & (llx>=minx) & (llx<=maxx) )
if mask.size:
cc = 0
for mm in mask:
cc = int(counter[mm])
pixel_shape = box(llx[mm], lly[mm], urx[mm], ury[mm])
fraction[mm,cc] = polygon_shape.intersection(pixel_shape).area * darea
value[mm,cc] = hcho[ii]
counter[mm] += 1
print("post-processing")
mask = np.argwhere(counter>0)
for mm in mask:
for cc in np.arange(counter[mm]):
maxfraction = np.sum(fraction[mm,:])
value[mm,cc] = (fraction[mm,cc]/maxfraction) * value[mm,cc]
data[mm] = np.mean(value[mm,:int(counter[mm])])
data = data.reshape( 720, 1440 )

Categories