Touch edges two rectangles with shapely - python

I have two rectangles. One rectangle contains the other. I want to find the length of the segments where they touch each other.
I have tried this code but the result is 798 which is wrong because the result I am looking for is 399.
from shapely.geometry import Polygon
big_rect = Polygon([(0,0), (0,600), (800,600), (800,0)])
small_rect = Polygon([(0,0), (0,199), (200,199), (200,0)])
intersection = big_rect.intersection(small_rect)
touch_length = intersection.boundary.length
print(touch_length)

You could do this using shapely by first finding the boundaries of each rectangle, then computing the intersection to get the intersecting line segment:
from shapely.geometry import Polygon
big_rect = Polygon([(0,0), (0,600), (800,600), (800,0)])
small_rect = Polygon([(0,0), (0,199), (200,199), (200,0)])
intersection = big_rect.boundary.intersection(
small_rect.boundary
)
touch_length = intersection.length
print(touch_length)

I solved my problem without shapely. In case someone has the same problem, here is the code:
big_rect = [(0,0), (0,600), (800,600), (800,0), (0,0)]
small_rect = [(0,199), (0,399), (200,399), (200,199), (0,199)]
def calc_touch_length(big_rect, small_rect):
touch_length = 0
for i in range(len(small_rect) - 1):
p1 = small_rect[i]
p2 = small_rect[i + 1]
if p1[0] == 0 and p2[0] == 0:
touch_length += abs(p2[1] - p1[1])
elif p1[1] == 0 and p2[1] == 0:
touch_length += abs(p2[0] - p1[0])
return touch_length
print(calc_touch_length(big_rect, small_rect))

Related

OpenCV-python get pixel set in between two points (line) [duplicate]

I want to use the LineIterator in OpenCV 3.0 using Python, is it still available with OpenCV 3.0 built for Python? It seems that the answers on the internet are all pointing to cv.InitLineIterator which is part of the cv module. I've tried importing this module but it seems like it is not included with the current build. Has it been renamed or strictly just removed?
I've solved my own problem. Line iterator seems to be unavailable in the cv2 library. Therefore, I made my own line iterator. No loops are used, so it should be pretty fast. Here is the code if anybody needs it:
def createLineIterator(P1, P2, img):
"""
Produces and array that consists of the coordinates and intensities of each pixel in a line between two points
Parameters:
-P1: a numpy array that consists of the coordinate of the first point (x,y)
-P2: a numpy array that consists of the coordinate of the second point (x,y)
-img: the image being processed
Returns:
-it: a numpy array that consists of the coordinates and intensities of each pixel in the radii (shape: [numPixels, 3], row = [x,y,intensity])
"""
#define local variables for readability
imageH = img.shape[0]
imageW = img.shape[1]
P1X = P1[0]
P1Y = P1[1]
P2X = P2[0]
P2Y = P2[1]
#difference and absolute difference between points
#used to calculate slope and relative location between points
dX = P2X - P1X
dY = P2Y - P1Y
dXa = np.abs(dX)
dYa = np.abs(dY)
#predefine numpy array for output based on distance between points
itbuffer = np.empty(shape=(np.maximum(dYa,dXa),3),dtype=np.float32)
itbuffer.fill(np.nan)
#Obtain coordinates along the line using a form of Bresenham's algorithm
negY = P1Y > P2Y
negX = P1X > P2X
if P1X == P2X: #vertical line segment
itbuffer[:,0] = P1X
if negY:
itbuffer[:,1] = np.arange(P1Y - 1,P1Y - dYa - 1,-1)
else:
itbuffer[:,1] = np.arange(P1Y+1,P1Y+dYa+1)
elif P1Y == P2Y: #horizontal line segment
itbuffer[:,1] = P1Y
if negX:
itbuffer[:,0] = np.arange(P1X-1,P1X-dXa-1,-1)
else:
itbuffer[:,0] = np.arange(P1X+1,P1X+dXa+1)
else: #diagonal line segment
steepSlope = dYa > dXa
if steepSlope:
slope = dX.astype(np.float32)/dY.astype(np.float32)
if negY:
itbuffer[:,1] = np.arange(P1Y-1,P1Y-dYa-1,-1)
else:
itbuffer[:,1] = np.arange(P1Y+1,P1Y+dYa+1)
itbuffer[:,0] = (slope*(itbuffer[:,1]-P1Y)).astype(np.int) + P1X
else:
slope = dY.astype(np.float32)/dX.astype(np.float32)
if negX:
itbuffer[:,0] = np.arange(P1X-1,P1X-dXa-1,-1)
else:
itbuffer[:,0] = np.arange(P1X+1,P1X+dXa+1)
itbuffer[:,1] = (slope*(itbuffer[:,0]-P1X)).astype(np.int) + P1Y
#Remove points outside of image
colX = itbuffer[:,0]
colY = itbuffer[:,1]
itbuffer = itbuffer[(colX >= 0) & (colY >=0) & (colX<imageW) & (colY<imageH)]
#Get intensities from img ndarray
itbuffer[:,2] = img[itbuffer[:,1].astype(np.uint),itbuffer[:,0].astype(np.uint)]
return itbuffer
Edit:
The function line from scikit-image can make the same effect and it's faster than anything we could code.
from skimage.draw import line
# being start and end two points (x1,y1), (x2,y2)
discrete_line = list(zip(*line(*start, *end)))
Also the timeit result is quite faster. So, use this.
Old "deprecated" answer:
As previous answer says, it's not implemented so you must do it yourself.
I didn't do it from scratch i just rewrote some parts of the function in a fancier and more modern way that should handle all cases correctly unlike the most voted answer that didn't work correctly for me. I took the example from here and did some cleanup and some styling.
Feel free to comment it. Also i added the clipline test like in the source code that can be found in the drawing.cpp in the source code for OpenCv 4.x
Thank you all for the references and the hard work.
def bresenham_march(img, p1, p2):
x1 = p1[0]
y1 = p1[1]
x2 = p2[0]
y2 = p2[1]
#tests if any coordinate is outside the image
if (
x1 >= img.shape[0]
or x2 >= img.shape[0]
or y1 >= img.shape[1]
or y2 >= img.shape[1]
): #tests if line is in image, necessary because some part of the line must be inside, it respects the case that the two points are outside
if not cv2.clipLine((0, 0, *img.shape), p1, p2):
print("not in region")
return
steep = math.fabs(y2 - y1) > math.fabs(x2 - x1)
if steep:
x1, y1 = y1, x1
x2, y2 = y2, x2
# takes left to right
also_steep = x1 > x2
if also_steep:
x1, x2 = x2, x1
y1, y2 = y2, y1
dx = x2 - x1
dy = math.fabs(y2 - y1)
error = 0.0
delta_error = 0.0
# Default if dx is zero
if dx != 0:
delta_error = math.fabs(dy / dx)
y_step = 1 if y1 < y2 else -1
y = y1
ret = []
for x in range(x1, x2):
p = (y, x) if steep else (x, y)
if p[0] < img.shape[0] and p[1] < img.shape[1]:
ret.append((p, img[p]))
error += delta_error
if error >= 0.5:
y += y_step
error -= 1
if also_steep: # because we took the left to right instead
ret.reverse()
return ret
I compared the 4 methods provided on this page:
Using python 2.7.6 and scikit-image 0.9.3 with some minor code changes.
Image input is via OpenCV.
A line segment (1, 76) to (867, 190)
Method 1: Sci-kit Image Line
Compute time: 0.568 ms
Number of pixels found: 867
Correct start pixel: yes
Correct end pixel: yes
Method 2: Code from #trenixjetix code
There seems to be a bug where the image width and height are flipped.
Compute time: 0.476 ms
Number of pixels found: 866
Correct start pixel: yes
Correct end pixel: no, off by 1
Method 3: Code from ROS.org
https://answers.ros.org/question/10160/opencv-python-lineiterator-returning-position-information/
Compute time: 0.433 ms (should be same as method 2)
Number of pixels found: 866
Correct start pixel: yes
Correct end pixel: no, off by 1
Method 4: Code from #mohikhsan
Compute time: 0.156 ms
Number of pixels found: 866
Correct start pixel: no, off by 1
Correct end pixel: yes
Summary:
Most accurate method: Sci-kit Image Line
Fastest method: Code from #mohikhsan
It could be nice to have a python implementation that matches the OpenCV C++ implementation?
https://github.com/opencv/opencv/blob/master/modules/imgproc/src/drawing.cpp
or uses a python generator:
https://wiki.python.org/moin/Generators
Not a fancy way to do this, but an effective and very very simple one-liner:
points_on_line = np.linspace(pt_a, pt_b, 100) # 100 samples on the line
If you want to approximately get each pixel along the way
points_on_line = np.linspace(pt_a, pt_b, np.linalg.norm(pt_a - pt_b))
(e.g. number of samples as the number of pixels between point A and point B)
For example:
pt_a = np.array([10, 11])
pt_b = np.array([45, 67])
im = np.zeros((80, 80, 3), np.uint8)
for p in np.linspace(pt_a, pt_b, np.linalg.norm(pt_a-pt_b)):
cv2.circle(im, tuple(np.int32(p)), 1, (255,0,0), -1)
plt.imshow(im)
This is not exactly an answer, but I can't add comment so I write it here.
The solution by trenixjetix is really great to cover the most 2 efficient ways to do this. I just want to give minor clarification for the scikit-image method he mentioned.
# being start and end two points (x1,y1), (x2,y2)
discrete_line = list(zip(*line(*start, *end)))
In scikit-image metric, starting and ending point of line is followed (row, col), while opencv use (x,y) coordinate, which is reversed in term of function parameters. Pay attention to that.
Add up the David's answer, I got the scikit execution time is faster than trenixjetix's function, using python 3.8. The result can vary, but almost every time scikit is faster.
trenixjetix time(ms) 0.22279999999996747
scikit-image time(ms) 0.13810000000002987
I got troubles running the skimage example from trenixjetix so I created a small wrapper function accepting points from numpy array slices, tuples or lists all the same:
from skimage.draw import line as skidline
def get_linepnts(p0, p1):
p0, p1 = np.array(p0).flatten(), np.array(p1).flatten()
return np.array(list(zip(*skidline(p0[0],p0[1], p1[0],p1[1]))))
The resulting array can be used to retrieve values from numpy arrays in the following way:
l0 = get_linepnts(p0, p1)
#if p0/p1 are in (x,y) format, then this needs to be swapped for retrieval:
vals = yournpmat[l0[:,1], l0[:,0]]

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

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

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)

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