Commit 0b72648f authored by Hoek, Steven's avatar Hoek, Steven
Browse files

Extra methods were added in order to indicate points which are representative...

Extra methods were added in order to indicate points which are representative for a shape - esp. for those with centroids outside of the actual shape
parent 834c702e
......@@ -46,7 +46,7 @@ class Triangle(object):
if hasattr(self, "__len__"):
assert len(self.__points) == 3, "Triangle must have 3 corners!"
# Calculate the centroid by averaging coordinates
# Calculate the centroid by averaging the coordinates
if hasattr(self.__points[0], "__len__"):
xc = sum([pt[0] for pt in self.__points]) / 3
yc = sum([pt[1] for pt in self.__points]) / 3
......@@ -89,6 +89,8 @@ class SimplePoint(object):
self.y = y
class SimpleShape(object):
# TODO: things may not work in case of island polygons
# Points either have x and y attributes or are sequences of length 2
shape = None
def __init__(self):
self.shape = InnerShape()
......@@ -127,9 +129,12 @@ class SimpleShape(object):
total_area = total_xweighted = total_yweighted = 0.0
for tr in triangles:
mytr = Triangle(tr)
total_area += mytr.area()
total_xweighted += mytr.centroid()[0] * mytr.area()
total_yweighted += mytr.centroid()[1] * mytr.area()
centroid = mytr.centroid()
if self.contains_point(centroid):
area = mytr.area()
total_area += area
total_xweighted += centroid[0] * area
total_yweighted += centroid[1] * area
result = (total_xweighted / total_area, total_yweighted / total_area)
except Exception as e:
......@@ -137,11 +142,51 @@ class SimpleShape(object):
finally:
return result
def get_nearest_point(self, otherpoint):
def get_nearest_point(self, otherpoint) -> typing.Vertex:
# Validate input
if hasattr(otherpoint, "__len__"): x0, y0 = otherpoint[0], otherpoint[1]
elif hasattr(otherpoint, "x") and hasattr(otherpoint, "y"): x0, y0 = otherpoint.x, otherpoint.y
# Get the 2 points that are nearest to the other point
pt1, pt2 = self.get_nearest_edge_points(otherpoint)
# Determine a line through the otherpoint perpendicular to the line through these points
line1 = typing.get_standard_line(typing.Vertex(pt1[0], pt1[1]), typing.Vertex(pt2[0], pt2[1]))
if line1.B == 0: result = typing.Vertex(pt1[0], y0)
elif line1.A == 0: result = typing.Vertex(x0, pt1[1])
else:
line2 = self.get_plumb_line(line1, otherpoint)
result = typing.get_line_intersection(line1, line2)
# Check that the result lies on the edge of the polygon
A = (min(pt1[0], pt2[0])) <= result.x and (max(pt1[0], pt2[0]) >= result.x)
B = (min(pt1[1], pt2[1])) <= result.y and (max(pt1[1], pt2[1]) >= result.y)
if (not A) or (not B): result = typing.Vertex(pt1[0], pt1[1])
return result
def get_nearest_edge_points(self, otherpoint) -> (typing.Vertex, typing.Vertex):
# Process input
if hasattr(otherpoint, "__len__"): x0, y0 = otherpoint[0], otherpoint[1]
elif hasattr(otherpoint, "x") and hasattr(otherpoint, "y"): x0, y0 = otherpoint.x, otherpoint.y
# Get the 2 points of the polygon that are nearest to the given point
points = sum(self.get_point_series(), [])
mydicts = [{"point":(p.x, p.y), "distance":distance((p.x, p.y), otherpoint)} for p in points]
minval = min([d["distance"] for d in mydicts])
result = list(filter(lambda d: abs(d["distance"] - minval) < 0.000000001, mydicts))[0]
mydicts = [{"point":(p.x, p.y), "distance":distance((p.x, p.y), (x0, y0))} for p in points]
sorted_points = sorted(mydicts, key=lambda p: p['distance'])
pt1, pt2 = sorted_points[0]["point"], sorted_points[1]["point"]
return typing.Vertex(pt1[0], pt1[1]), typing.Vertex(pt2[0], pt2[1])
def get_plumb_line(self, line, point) -> typing.StandardLine:
# Validate / process input
if line.A == 0: raise ValueError("Line without slope not allowed as input!")
if hasattr(point, "__len__"):
x0, y0 = point[0], point[1]
elif hasattr(point, "x") and hasattr(point, "y"):
x0, y0 = point.x, point.y
# Determine a line perpendicular to the given line which passes through the given point
intercept = y0 - (line.B / line.A) * x0
result = typing.StandardLine(-1 * line.B / line.A, 1.0, intercept)
return result
def get_area(self):
......@@ -153,9 +198,70 @@ class SimpleShape(object):
result = 0.0
for tr in triangles:
mytr = Triangle(tr)
centroid = mytr.centroid()
if self.contains_point(centroid):
result += mytr.area()
return result
def contains_point(self, point):
# Initialise
if hasattr(point, "__len__"): x, y = point[0], point[1]
else: x, y = point.x, point.y
poly = [(p.x, p.y) for p in sum(self.get_point_series(), [])]
n = len(poly)
result = False
# Loop over the points
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:
result = not result
p1x, p1y = p2x, p2y
return result
def get_point_inside(self, edgepoint, distance) -> typing.Vertex:
# Initialise
result = None
# Validate input
try:
if distance <= 0.0: raise ValueError("Invalid value for distance!")
if hasattr(edgepoint, "__len__"): x, y = edgepoint[0], edgepoint[1]
else: x, y = edgepoint.x, edgepoint.y
# Get the nearest 2 edge points of the polygon and check that the point lies on the edge of the polygon
pt1, pt2 = self.get_nearest_edge_points(edgepoint)
eps = 0.0000000001
if abs(pt1.x - pt2.x) < eps and abs(pt1.y - pt2.y) < eps: # TODO: find a good way to handle this
raise ValueError("Two consecutive points of the shape are located too close to each other!")
A = (min(pt1[0], pt2[0])) <= x and (max(pt1[0], pt2[0]) >= x)
B = (min(pt1[1], pt2[1])) <= y and (max(pt1[1], pt2[1]) >= y)
if A and B:
# At least the edgepoint is located inside the boundary box around pt1 and pt2
# Make sure a point inside the shape is found that is distance away from the edge
line1 = typing.get_standard_line(typing.Vertex(pt1[0], pt1[1]), typing.Vertex(pt2[0], pt2[1]))
if line1.A == 0:
if self.contains_point((x, y + distance)): result = typing.Vertex(x, y + distance)
else: result = typing.Vertex(x, y - distance)
else:
line2 = self.get_plumb_line(line1, edgepoint)
slope = line2.A / line2.B
dx = distance / sqrt(1 + slope**2)
dy = slope * dx
if self.contains_point((x + dx, y + dy)): result = typing.Vertex(x + dx, y + dy)
else: result = typing.Vertex(x - dx, y - dy)
else: raise ValueError("Given point is not located on the edge!")
except Exception as e:
print(e)
finally:
return result
class InnerShape(object):
# A list called parts holds index which indicates the point of next part
# A list called points holds list / array with only 2 places
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment