Python: find if point lay on the border of a polygon - python

I have a point-i and i wish to create a function to know if this point lies on the border of a polygon.
using:
def point_inside_polygon(x, y, poly):
"""Deciding if a point is inside (True, False otherwise) a polygon,
where poly is a list of pairs (x,y) containing the coordinates
of the polygon's vertices. The algorithm is called the 'Ray Casting Method'"""
n = len(poly)
inside = False
p1x, p1y = poly[0]
for i in range(n):
p2x, p2y = poly[i % n]
if y > min(p1y, p2y):
if y <= max(p1y, p2y):
if x <= max(p1x, p2x):
if p1y != p2y:
xinters = (y-p1y) * (p2x-p1x) / (p2y-p1y) + p1x
if p1x == p2x or x <= xinters:
inside = not inside
p1x, p1y = p2x, p2y
return inside
I am able to know only if the points lies within the polygon.
poly = [(0,0), (2,0), (2,2), (0,2)]
point_inside_polygon(1,1, poly)
True
point_inside_polygon(0,0, poly)
false
point_inside_polygon(2,0, poly)
False
point_inside_polygon(2,2, poly)
True
point_inside_polygon(0,2, poly)
True
How can I write a function to find if a point lay on the border of a polygon instead?

It might help to break down the problem into three steps:
Write a function that can determine if a point is on a line segment.
Compute all of the line segments that make up the border of the polygon.
The point is on the border if it is on any of the line segments.
Here's some python code, assuming you've written or found a suitable candidate for isPointOnLineSegmentBetweenPoints:
def pointOnPolygon(point, polygonVertices):
n = len(polygonVertices)
for i in range(n):
p1 = polygonVertices[i]
p2 = polygonVertices[-n+i+1]
if isPointOnLineSegmentBetweenPoints(point, p1, p2):
return true
return false

I haven't tested this, but the general idea is:
def pointOnBorder(x, y, poly):
n = len(poly)
for(i in range(n)):
p1x, p1y = poly[i]
p2x, p2y = poly[(i + 1) % n]
v1x = p2x - p1x
v1y = p2y - p1y #vector for the edge between p1 and p2
v2x = x - p1x
v2y = y - p1y #vector from p1 to the point in question
if(v1x * v2y - v1y * v2x == 0): #if vectors are parallel
if(v2x / v1x > 0): #if vectors are pointing in the same direction
if(v1x * v1x + v1y * v1y >= v2x * v2x + v2y * v2y): #if v2 is shorter than v1
return true
return false

For each pair of adjacent vertices A,B:
construct a vector from A to B, call it p
now construct a vector from A to your test point X call it q
the dot product formula for a pair of vectors is p.q = |p||q|cosC
where C is the angle between the vectors.
so if p.q/|p||q| == 1 then the points AX and AB are co-linear. Working on a computer, you will want 1 - p.q/|p||q| < some_small_value depending on how accurate you want to be.
also need to check that |q| < |p| (ie X is closer to A than B)
if 4&5 are true your point is on the border.
Edit
The other way I think I've seen this done is to take your test point X, and construct a line through X perpendicular to the line between A and B. Find where this line and the line A->B cross. Work out the distance from X to this crossing point, if that is sufficiently small you consider the point as being on the line.
Edit -- fun little exercise!
Posted some code that was wrong earlier due to me misremembering some maths.
Had a play in Pythonista on the train home and came up with this which seems to basically work. Have left the maths proof out as editing posts on iPad is painful!
Not much testing done, no testing for division by zero etc, caveat user.
# we determine the point of intersection X between
# the line between A and B and a line through T
# that is perpendicular to the line AB (can't draw perpendicular
# in ascii, you'll have to imagine that angle between AB and XT is 90
# degrees.
#
# B
# /
#. X
# / \
# / T
# A
# once we know X we can work out the closest the line AB
# comes to T, if that distance is 0 (or small enough)
# we can consider T to be on the line
import math
# work out where the line through test point t
# that is perpendicular to ab crosses ab
#
# inputs must be 2-tuples or 2-element lists of floats (x,y)
# returns (x,y) of point of intersection
def intersection_of_perpendicular(a,b,t):
if a[0] == b[0]:
return (a[0],t[1])
if a[1] == b[1]:
return (t[0],a[1])
m = (a[1] - b[1])/(a[0] - b[0]) #slope of ab
x_inter = (t[1] - a[1] + m*a[0] + (1/m)*t[0])*m/(m**2 + 1)
y_inter = m*(x_inter - a[0]) + a[1]
y_inter2 = -(1/m)*(x_inter - t[0]) + t[1]
#print '...computed ',m,(x_inter, y_inter), y_inter2
return (x_inter, y_inter)
# basic Pythagorean formula for distance between two points
def distance(a,b):
return math.sqrt( (a[0]-b[0])**2 + (a[1]-b[1])**2 )
# check if a point is within the box defined by a,b at
# diagonally opposite corners
def point_in_box(a,b,t):
xmin = min(a[0],b[0])
xmax = max(a[0],b[0])
ymin = min(a[1],b[1])
ymax = max(a[1],b[1])
x_in_bounds = True
if xmax != xmin:
x_in_bounds = xmin <= t[0] <= xmax
y_in_bounds = True
if ymax != ymin:
y_in_bounds = ymin <= t[1] <= ymax
return x_in_bounds and y_in_bounds
# determine if point t is within 'tolerance' distance
# of the line between a and b
# returns Boolean
def is_on_line_between(a,b,t,tolerance=0.01):
intersect = intersection_of_perpendicular(a,b,t)
dist = distance(intersect, t)
in_bounds = point_in_box(a,b,t)
return in_bounds and (dist < tolerance)
a = (0,0)
b = (2,2)
t = (0,2)
p = intersection_of_perpendicular(a,b,t)
bounded = point_in_box(a,b,t)
print 'd ',distance(p,t), ' p ',p, bounded
a = (0,2)
b = (2,2)
t = (1,3)
p = intersection_of_perpendicular(a,b,t)
bounded = point_in_box(a,b,t)
print 'd ',distance(p,t),' p ',p, bounded
a = (0.0,2.0)
b = (2.0,7.0)
t = (1.7,6.5)
p = intersection_of_perpendicular(a,b,t)
bounded = point_in_box(a,b,t)
on = is_on_line_between(a,b,t,0.2)
print 'd ',distance(p,t),' p ',p, bounded,on

Everyone is overcomplicating things. Here is a short point on polygon, assuming you have a distance function and a small EPSILON.
def pointOnPolygon(point, poly):
for i in range(len(poly)):
a, b = poly[i - 1], poly[i]
if abs(dist(a, point) + dist(b, point) - dist(a, b)) < EPSILON:
return true
return false

For a convex polygon, you can solve the problem in O(log n) time by ordering vertices clock-wise and storing for each vertex the angle between the vertex and a point c in the interior of the polygon. Then for a query point x, you get the angle from c to x and binary search to find the unique pair of adjacent vertices (v1,v2) such that the angle to x is between the angles to v1 and to v2. Then x is either on the edge (v1,v2), or x is not on the boundary.
If you have a more complicated polygon, then you can try decomposing the polygon into a union of convex polygons by adding some interior edges (e.g. first triangulate and then remove edges to get bigger convex polygons); if the number of convex polygons you get is small (say k), then you can test each convex polygon to see if a point is on an edge, and the overall running time is O(k lg n) where n is the total number of vertices in your polygon.
Alternatively, if you are not worried about using extra space, and you really want to quickly determine if you're on an edge, then you can break up each edge into equally spaced segments by adding extra "vertices" along each edge; it's hard to say how many is enough (sounds like an interesting math problem), but clearly if you add enough extra vertices along each edge then you can tell which edge a point must lie on simply by finding the nearest neighbor out of your set of vertices (original vertices and the ones you added), and then just test the one or two edges that the nearest neighbor vertex lies on. You can very quickly find k-nearest neighbors in 2-d if you use a 2-dimensional kd-tree (you build the tree as a pre-processing step, and then the tree supports fast k-nearest neighbor queries), and the kd-tree tree only uses linear space.

Found the solution to this at: http://geospatialpython.com/2011/08/point-in-polygon-2-on-line.html
Here the code:
# Improved point in polygon test which includes edge
# and vertex points
def point_in_poly(x,y,poly):
# check if point is a vertex
if (x,y) in poly: return "IN"
# check if point is on a boundary
for i in range(len(poly)):
p1 = None
p2 = None
if i==0:
p1 = poly[0]
p2 = poly[1]
else:
p1 = poly[i-1]
p2 = poly[i]
if p1[1] == p2[1] and p1[1] == y and x > min(p1[0], p2[0]) and x < max(p1[0], p2[0]):
return "IN"
n = len(poly)
inside = False
p1x,p1y = poly[0]
for i in range(n+1):
p2x,p2y = poly[i % n]
if y > min(p1y,p2y):
if y <= max(p1y,p2y):
if x <= max(p1x,p2x):
if p1y != p2y:
xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
if p1x == p2x or x <= xints:
inside = not inside
p1x,p1y = p2x,p2y
if inside: return "IN"
else: return "OUT"
# Test a vertex for inclusion
polygon = [(-33.416032,-70.593016), (-33.415370,-70.589604),
(-33.417340,-70.589046), (-33.417949,-70.592351),
(-33.416032,-70.593016)]
lat= -33.416032
lon= -70.593016
print point_in_poly(lat, lon, polygon)
# test a boundary point for inclusion
poly2 = [(1,1), (5,1), (5,5), (1,5), (1,1)]
x = 3
y = 1
print point_in_poly(x, y, poly2)

Related

How can I vectorize this numpy rotation code?

The Goal:
I would like to vectorize (or otherwise speed up) this code. It rotates a 3d numpy model around its center point (let x,y,z denote the dimensions; then we want to rotate around the z-axis). The np model is binary voxels that are either "on" or "off"
I bet some basic matrix operation could do it, like take a layer and apply the rotation matrix to each element. The only issue with that is decimals; where should I have the new value land since cos(pi / 6) == sqrt(3) / 2?
The Code:
def rotate_model(m, theta):
'''
theta in degrees
'''
n =np.zeros(m.shape)
for i,layer in enumerate(m):
rotated = rotate(layer,theta)
n[i] = rotated
return n
where rotate() is:
def rotate(arr, theta):
'''
Rotates theta clockwise
rotated.shape == arr.shape, unlike scipy.ndimage.rotate(), which inflates size and also does some strange mixing
'''
if theta == int(theta):
theta *= pi / 180
theta = -theta
# theta=-theta b/c clockwise. Otherwise would default to counterclockwise
rotated =np.zeros(arr.shape)
#print rotated.shape[0], rotated.shape[1]
y_mid = arr.shape[0]//2
x_mid = arr.shape[1]//2
val = 0
for x_new in range(rotated.shape[1]):
for y_new in range(rotated.shape[0]):
x_centered = x_new - x_mid
y_centered = y_new - y_mid
x = x_centered*cos(theta) - y_centered*sin(theta)
y = x_centered*sin(theta) + y_centered*cos(theta)
x += x_mid
y += y_mid
x = int(round(x)); y = int(round(y)) # cast so range() picks it up
# lossy rotation
if x in range(arr.shape[1]) and y in range(arr.shape[0]):
val = arr[y,x]
rotated[y_new,x_new] = val
#print val
#print x,y
return rotated
You have a couple of problems in your code. First, if you want to fit the original image onto a rotated grid then you need a larger grid (usually). Alternatively, imagine a regular grid but the shape of your object - a rectangle - is rotated, thus becoming a "rhomb". It is obvious if you want to fit the entire rhomb - you need a larger output grid (array). On the other hand, you say in the code "rotated.shape == arr.shape, unlike scipy.ndimage.rotate(), which inflates size". If that is the case, maybe you do not want to fit the entire object? So, maybe it is OK to do this: rotated=np.zeros(arr.shape). But in general, yeah, one has to have a larger grid in order to fit the entire input image after it is rotated.
Another issue is angle conversion that you are doing:
if theta == int(theta):
theta *= pi / 180
theta = -theta
Why??? What will happen when I want to rotate the image by 1 radian? Or 2 radians? Am I forbidden to use integer number of radians? I think you are trying to do too much in this function and therefore it will be very confusing to do use it. Just require the caller to convert angles to radians. Or, you can do it inside this function if input theta is always in degrees. Or, you can add another parameter called, e.g., units and caller could set it to radians or degrees. Don't try to guess it based on "integer-ness" of input!
Now, let's rewrite your code a little bit:
rotated = np.zeros_like(arr) # instead of np.zero(arr.shape)
y_mid = arr.shape[0] // 2
x_mid = arr.shape[1] // 2
# val = 0 <- this is unnecessary
# pre-compute cos(theta) and sin(theta):
cs = cos(theta)
sn = sin(theta)
for x_new in range(rotated.shape[1]):
for y_new in range(rotated.shape[0]):
x = int(round((x_new - x_mid) * cs - (y_new - y_mid) * sn + x_mid)
y = int(round((x_new - x_mid) * sn - (y_new - y_mid) * cs + y_mid)
# just use comparisons, don't search through many values!
if 0 <= x < arr.shape[1] and 0 <= y < arr.shape[0]:
rotated[y_new, x_new] = arr[y, x]
So, now I can see (more easily) that for each pixel from the output array is mapped to a location in the input array. Yes, you can vectorize this.
import numpy as np
def rotate(arr, theta, unit='rad'):
# deal with theta units:
if unit.startswith('deg'):
theta = np.deg2rad(theta)
# for convenience, store array size:
ny, nx = arr.shape
# generate arrays of indices and flatten them:
y_new, x_new = np.indices(arr.shape)
x_new = x_new.ravel()
y_new = y_new.ravel()
# compute center of the array:
x0 = nx // 2
y0 = ny // 2
# compute old coordinates
xc = x_new - x0
yc = y_new - y0
x = np.round(np.cos(theta) * xc - np.sin(theta) * yc + x0).astype(np.int)
y = np.round(np.sin(theta) * xc - np.cos(theta) * yc + y0).astype(np.int)
# main idea to deal with indices is to create a mask:
mask = (x >= 0) & (x < nx) & (y >= 0) & (y < ny)
# ... and then select only those coordinates (both in
# input and "new" coordinates) that satisfy the above condition:
x = x[mask]
y = y[mask]
x_new = x_new[mask]
y_new = y_new[mask]
# map input values to output pixels *only* for selected "good" pixels:
rotated = np.zeros_like(arr)
rotated[y_new, x_new] = arr[y, x]
return rotated
Here is some code for anyone also doing 3d modeling. It solved my specific use-case pretty well. Still figuring out how to rotate in the proper plane. Hope it's helpful to you as well:
def rotate_model(m, theta):
'''
Redefines the prev 'rotate_model()' method
theta has to be in degrees
'''
rotated = scipy.ndimage.rotate(m, theta, axes=(1,2))
# have tried (1,0), (2,0), and now (1,2)
# ^ z is "up" and "2"
# scipy.ndimage.rotate() shrinks the model
# TODO: regrow it back
x_r = rotated.shape[1]
y_r = rotated.shape[0]
x_m = m.shape[1]
y_m = m.shape[0]
x_diff = abs(x_r - x_m)
y_diff = abs(y_r - y_m)
if x_diff%2==0 and y_diff%2==0:
return rotated[
x_diff//2 : x_r-x_diff//2,
y_diff//2 : y_r-y_diff//2,
:
]
elif x_diff%2==0 and y_diff%2==1:
# if this shift ends up turning the model to shit in a few iterations,
# change the following lines to include a flag that alternates cutting off the top and bottom bits of the array
return rotated[
x_diff//2 : x_r-x_diff//2,
y_diff//2+1 : y_r-y_diff//2,
:
]
elif x_diff%2==1 and y_diff%2==0:
return rotated[
x_diff//2+1 : x_r-x_diff//2,
y_diff//2 : y_r-y_diff//2,
:
]
else:
# x_diff%2==1 and y_diff%2==1:
return rotated[
x_diff//2+1 : x_r-x_diff//2,
y_diff//2+1 : y_r-y_diff//2,
:
]

How to Expand a Polygon Until One of the Borders Reaches a Point

I have code to expand the polygon, it works by multiplying the xs and ys by a factor then re centering the resultant polyon at the center of the original.
I also have code to find the value for the expansion factor, given a point that the polygon needs to reach:
import numpy as np
import itertools as IT
import copy
from shapely.geometry import LineString, Point
def getPolyCenter(points):
"""
http://stackoverflow.com/a/14115494/190597 (mgamba)
"""
area = area_of_polygon(*zip(*points))
result_x = 0
result_y = 0
N = len(points)
points = IT.cycle(points)
x1, y1 = next(points)
for i in range(N):
x0, y0 = x1, y1
x1, y1 = next(points)
cross = (x0 * y1) - (x1 * y0)
result_x += (x0 + x1) * cross
result_y += (y0 + y1) * cross
result_x /= (area * 6.0)
result_y /= (area * 6.0)
return (result_x, result_y)
def expandPoly(points, factor):
points = np.array(points, dtype=np.float64)
expandedPoly = points*factor
expandedPoly -= getPolyCenter(expandedPoly)
expandedPoly += getPolyCenter(points)
return np.array(expandedPoly, dtype=np.int64)
def distanceLine2Point(points, point):
points = np.array(points, dtype=np.float64)
point = np.array(point, dtype=np.float64)
points = LineString(points)
point = Point(point)
return points.distance(point)
def distancePolygon2Point(points, point):
distances = []
for i in range(len(points)):
if i==len(points)-1:
j = 0
else:
j = i+1
line = [points[i], points[j]]
distances.append(distanceLine2Point(line, point))
minDistance = np.min(distances)
#index = np.where(distances==minDistance)[0][0]
return minDistance
"""
Returns the distance from a point to the nearest line of the polygon,
AND the distance from where the normal to the line (to reach the point)
intersets the line to the center of the polygon.
"""
def distancePolygon2PointAndCenter(points, point):
distances = []
for i in range(len(points)):
if i==len(points)-1:
j = 0
else:
j = i+1
line = [points[i], points[j]]
distances.append(distanceLine2Point(line, point))
minDistance = np.min(distances)
i = np.where(distances==minDistance)[0][0]
if i==len(points)-1:
j = 0
else:
j = i+1
line = copy.deepcopy([points[i], points[j]])
centerDistance = distanceLine2Point(line, getPolyCenter(points))
return minDistance, centerDistance
minDistance, centerDistance = distancePolygon2PointAndCenter(points, point)
expandedPoly = expandPoly(points, 1+minDistance/centerDistance)
This code only works when the point is directly opposing one of the polygons lines.
Modify your method distancePolygon2PointAndCenter to instead of
Returns the distance from a point to the nearest line of the polygon
To return the distance from a point to the segment intersected by a ray from the center to the point. This is the line that will intersect the point once the polygon is fully expanded. To get this segment, take both endpoints of each segment of your polygon, and plug them into the equation for the line parallel & intersecting the ray mentioned earlier. That is y = ((centerY-pointY)/(centerX-pointX)) * (x - centerX) + centerY. You want to want to find endpoints where either one of them intersect the line, or the two are on opposite sides of the line.
Then, the only thing left to do is make sure that we pick the segment intersecting the right "side" of the line. To do this, there are a few options. The fail-safe method would be to use the formula cos(theta) = sqrt((centerX**2 + centerY**2)*(pointX**2 + pointY**2)) / (centerX * pointX + centerY * pointY) however, you could use methods such as comparing x and y values, taking the arctan2(), and such to figure out which segment is on the correct "side" of center. You'll just have lots of edge cases to cover. After all this is said and done, your two (unless its not convex, in which case take the segment farthest from you center) endpoints makeup the segment to expand off of.
Determine what is "polygon center" as central point C of expanding. Perhaps it is centroid (or some point with another properties?).
Make a segment from your point P to C. Find intersection point I between PC and polygon edges. If polygon is concave and there are some intersection points, choose the closest one to P.
Calculate coefficient of expanding:
E = Length(PC) / Length(CI)
Calculate new vertex coordinates. For i-th vertex of polygon:
V'[i].X = C.X + (V[i].X - C.X) * E
V'[i].Y = C.Y + (V[i].Y - C.Y) * E
Decide which point you want to reach, then calculate how much % your polygon needs to expand to reach that point and use the shapely.affinity.scale function. For example, in my case I just needed to make the polygon 5% bigger:
region = shapely.affinity.scale(myPolygon,
xfact=1.05, yfact=1.05 )

Deciding if a Point is Inside a Polygon

I am trying to detect if a given point(x,y) is in a polygon of n*2 array. But it seems that some points on the borders of the polygon return that it's not include.
def point_inside_polygon(x,y,poly):
n = len(poly)
inside =False
p1x,p1y = poly[0]
for i in range(n+1):
p2x,p2y = poly[i % n]
if y > min(p1y,p2y):
if y <= max(p1y,p2y):
if x <= max(p1x,p2x):
if p1y != p2y:
xinters = (y-p1y)*(p2x-p1x)/float((p2y-p1y))+p1x
if p1x == p2x or x <= xinters:
inside = not inside
p1x,p1y = p2x,p2y
return inside
Here is a simple way with multiple options
from shapely.geometry import Point, Polygon
# Point objects(Geo-coordinates)
p1 = Point(24.952242, 60.1696017)
p2 = Point(24.976567, 60.1612500)
# Polygon
coords = [(24.950899, 60.169158), (24.953492, 60.169158), (24.953510, 60.170104), (24.950958, 60.169990)]
poly = Polygon(coords)
Method 1:
Using within Function:
Syntax: point.within(polygon)
# Check if p1 is within the polygon using the within function
print(p1.within(poly))
# True
# Check if p2 is within the polygon
print(p2.within(poly))
# False
Method 2:
Using contains Function:
Syntax: polygon.contains(point)
# Check if polygon contains p1
print(poly.contains(p1))
# True
# Check if polygon contains p2
print(poly.contains(p2))
# False
To check if point is lying on the boundary of Polygon:
Using touches Function:
Syntax: polygon.touches(point)
poly1 = Polygon([(0, 0), (1, 0), (1, 1)])
point1 = Point(0, 0)
poly1.touches(point1)
# True
If you want to speed up the process then please use
import shapely.speedups
shapely.speedups.enable()
and
Make use of Geopandas
Reference:
https://automating-gis-processes.github.io/CSC18/lessons/L4/point-in-polygon.html#point-in-polygon-using-geopandas
https://shapely.readthedocs.io/en/latest/manual.html
https://streamhacker.com/2010/03/23/python-point-in-polygon-shapely/
You may use contains_point function from matplotlib.path with small negative and positive radius (small trick). Something like this:
import matplotlib.path as mplPath
import numpy as np
crd = np.array([[0,0], [0,1], [1,1], [1,0]])# poly
bbPath = mplPath.Path(crd)
pnts = [[0.0, 0.0],[1,1],[0.0,0.5],[0.5,0.0]] # points on edges
r = 0.001 # accuracy
isIn = [ bbPath.contains_point(pnt,radius=r) or bbPath.contains_point(pnt,radius=-r) for pnt in pnts]
The result is
[True, True, True, True]
By default (or r=0) all the points on borders are not included, and the result is
[False, False, False, False]
Here is the correct code that includes edges:
def point_inside_polygon(x, y, poly, include_edges=True):
'''
Test if point (x,y) is inside polygon poly.
poly is N-vertices polygon defined as
[(x1,y1),...,(xN,yN)] or [(x1,y1),...,(xN,yN),(x1,y1)]
(function works fine in both cases)
Geometrical idea: point is inside polygon if horisontal beam
to the right from point crosses polygon even number of times.
Works fine for non-convex polygons.
'''
n = len(poly)
inside = False
p1x, p1y = poly[0]
for i in range(1, n + 1):
p2x, p2y = poly[i % n]
if p1y == p2y:
if y == p1y:
if min(p1x, p2x) <= x <= max(p1x, p2x):
# point is on horisontal edge
inside = include_edges
break
elif x < min(p1x, p2x): # point is to the left from current edge
inside = not inside
else: # p1y!= p2y
if min(p1y, p2y) <= y <= max(p1y, p2y):
xinters = (y - p1y) * (p2x - p1x) / float(p2y - p1y) + p1x
if x == xinters: # point is right on the edge
inside = include_edges
break
if x < xinters: # point is to the left from current edge
inside = not inside
p1x, p1y = p2x, p2y
return inside
Updated: fixed a bug

Scipy interpolation.griddata freezes when called on a particular point

I am trying to perform a simple 2D linear interpolation with Scipy interpolation.griddata but it behaves in a strange way : it runs forever and the computation can't be interrupted (100% CPU, RAM doesn't move, and I have to kill the process).
The data + coordinates that should be interpolated are located inside a polygon. At first, I tried to use a meshgrid containing only points located inside that polygon, but it didn't solve the problem.
I also tried to reduce to a very tiny interpolation grid (10*10), but it kept on freezing.
Then I tried to interpolate the meshgrid points one by one and found out that only some of them were causing the issue, and I can't figure out why.
Here is a sample of the code :
# Sets negative levels to zero
x_a = x_a.clip(min = 0)
coordinate = np.array(coordinate)
# Experiment zone polygon.
if city == "sf":
zone = [[-122.397752, 37.799137],
[-122.423329, 37.795746],
[-122.418523, 37.772886],
[-122.39243, 37.793982]]
zone.append(zone[0]) # to close the polygon.
# Background map extent lat/long coordinates.
x_min_map = min([xy[0] for xy in zone])
x_max_map = max([xy[0] for xy in zone])
y_min_map = min([xy[1] for xy in zone])
y_max_map = max([xy[1] for xy in zone])
def IsPointInsidePolygon(x, y, poly):
n = len(poly)
inside = False
p1x, p1y = poly[0]
for i in range(n + 1):
p2x, p2y = poly[i % n]
if y > min(p1y, p2y):
if y <= max(p1y, p2y):
if x <= max(p1x, p2x):
if p1y != p2y:
xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
if p1x == p2x or x <= xinters:
inside = not inside
p1x, p1y = p2x, p2y
return inside
# Creates a mesh grid adapted to the sf zone, on which to interpolate.
N = 10
xi = np.linspace(x_min_map, x_max_map, N)
yi = np.linspace(y_min_map, y_max_map, N)
xi, yi = np.meshgrid(xi, yi)
xi, yi = xi.flatten(), yi.flatten()
all_points = np.vstack((xi, yi)).T
delete_index = []
for i, point in enumerate(all_points):
if not IsPointInsidePolygon(point[0], point[1], zone):
delete_index.append(i)
grid_points = np.delete(all_points, delete_index, axis = 0)
# Interpolation
zi = interpolate.griddata(coordinate, x_a, grid_points, method = "linear")
The "coordinate" variable is an array that contains approximately 1e4 float coordinates that are located inside the "zone" polygon.
The x_min_map etc. are the computed max and min x, y values of the polygon, so that the original meshgrid is the smallest rectangle containing the whole polygon.
Any advice will be appreciated !

Produce PDF files, draw polygons with rounded corners

What's the right tool for the job if I want to write a Python script that produces vector graphics in PDF format? In particular, I need to draw filled polygons with rounded corners (i.e., plane figures that are composed of straight lines and circular arcs).
It seems that matplotlib makes it fairly easy to draw rectangles with rounded corners and general polygons with sharp corners. However, to draw polygons with rounded corners, it seems that I have to first compute a Bézier curve that approximates the shape.
Is there anything more straightforward available? Or is there another library that I can use to compute the Bézier curve that approximates the shape that I want to produce? Ideally, I would simply specify the (location, corner radius) pair for each vertex.
Here is an example: I would like to specify the red polygon (+ the radius of each corner) and the library would output the grey figure:
(For convex polygons I could cheat and use a thick pen to draw the outline of the polygon. However, this does not work in the non-convex case.)
Here's a somewhat hacky matplotlib solution. The main complications are related to using matplotlib Path objects to build a composite Path.
#!/usr/bin/env python
import numpy as np
from matplotlib.path import Path
from matplotlib.patches import PathPatch, Polygon
from matplotlib.transforms import Bbox, BboxTransformTo
def side(a, b, c):
"On which side of line a-b is point c? Returns -1, 0, or 1."
return np.sign(np.linalg.det(np.c_[[a,b,c],[1,1,1]]))
def center((prev, curr, next), radius):
"Find center of arc approximating corner at curr."
p0, p1 = prev
c0, c1 = curr
n0, n1 = next
dp = radius * np.hypot(c1 - p1, c0 - p0)
dn = radius * np.hypot(c1 - n1, c0 - n0)
p = p1 * c0 - p0 * c1
n = n1 * c0 - n0 * c1
results = \
np.linalg.solve([[p1 - c1, c0 - p0],
[n1 - c1, c0 - n0]],
[[p - dp, p - dp, p + dp, p + dp],
[n - dn, n + dn, n - dn, n + dn]])
side_n = side(prev, curr, next)
side_p = side(next, curr, prev)
for r in results.T:
if (side(prev, curr, r), side(next, curr, r)) == (side_n, side_p):
return r
raise ValueError, "Cannot find solution"
def proj((prev, curr, next), center):
"Project center onto lines prev-curr and next-curr."
p0, p1 = prev = np.asarray(prev)
c0, c1 = curr = np.asarray(curr)
n0, n1 = next = np.asarray(next)
pc = curr - prev
nc = curr - next
pc2 = np.dot(pc, pc)
nc2 = np.dot(nc, nc)
return (prev + np.dot(center - prev, pc)/pc2 * pc,
next + np.dot(center - next, nc)/nc2 * nc)
def rad2deg(angle):
return angle * 180.0 / np.pi
def angle(center, point):
x, y = np.asarray(point) - np.asarray(center)
return np.arctan2(y, x)
def arc_path(center, start, end):
"Return a Path for an arc from start to end around center."
# matplotlib arcs always go ccw so we may need to mirror
mirror = side(center, start, end) < 0
if mirror:
start *= [1, -1]
center *= [1, -1]
end *= [1, -1]
return Path.arc(rad2deg(angle(center, start)),
rad2deg(angle(center, end))), \
mirror
def path(vertices, radii):
"Return a Path for a closed rounded polygon."
if np.isscalar(radii):
radii = np.repeat(radii, len(vertices))
else:
radii = np.asarray(radii)
pv = []
pc = []
first = True
for i in range(len(vertices)):
if i == 0:
seg = (vertices[-1], vertices[0], vertices[1])
elif i == len(vertices) - 1:
seg = (vertices[-2], vertices[-1], vertices[0])
else:
seg = vertices[i-1:i+2]
r = radii[i]
c = center(seg, r)
a, b = proj(seg, c)
arc, mirror = arc_path(c, a, b)
m = [1,1] if not mirror else [1,-1]
bb = Bbox([c, c + (r, r)])
iter = arc.iter_segments(BboxTransformTo(bb))
for v, c in iter:
if c == Path.CURVE4:
pv.extend([m * v[0:2], m * v[2:4], m * v[4:6]])
pc.extend([c, c, c])
elif c == Path.MOVETO:
pv.append(m * v)
if first:
pc.append(Path.MOVETO)
first = False
else:
pc.append(Path.LINETO)
pv.append([0,0])
pc.append(Path.CLOSEPOLY)
return Path(pv, pc)
if __name__ == '__main__':
from matplotlib import pyplot
fig = pyplot.figure()
ax = fig.add_subplot(111)
vertices = [[3,0], [5,2], [10,0], [6,9], [6,5], [3, 5], [0,2]]
patch = Polygon(vertices, edgecolor='red', facecolor='None',
linewidth=1)
ax.add_patch(patch)
patch = PathPatch(path(vertices, 0.5),
edgecolor='black', facecolor='blue', alpha=0.4,
linewidth=2)
ax.add_patch(patch)
ax.set_xlim(-1, 11)
ax.set_ylim(-1, 9)
fig.savefig('foo.pdf')
As for producing PDF files, I would suggest to have a look at the cairo library, a vector graphics libaray which support "drawing" into PDF surfaces. It has Python bindings too.
As for drawing polygons with rounded corners, I'm not aware of any graphics library which supports this out of the box.
But it shouldn't be too complicated to compute the arc coordinates at polygon corners given the corner radius. Basically you have to find the point on the angle bisector of two adjacent edges which has the distance r (i.e. the desired radius) from both edges. This is the center of the arc, for finding the starting and ending point, you'll project from this point to the two edges.
There might be non-trivial cases, e.g. what to do, if polygon edges are too short to fit two arcs (I guess you'll have to select a smaller radius in this case), and maybe others, I'm currently not aware of ...
HTH

Categories