Rectangular room with reflecting walls with a hero and enemy puzzle - python-2.6

Current situation
Am stuck with this Google foobar question from past 9 days with 8/10 test cases passing. Could someone please help me as to what is wrong with my code?
Problem statement
Uh-oh - you've been cornered by one of Commander Lambdas elite guards! Fortunately, you grabbed a beam weapon from an abandoned guardpost while you were running through the station, so you have a chance to fight your way out. But the beam weapon is potentially dangerous to you as well as to the elite guard: its beams reflect off walls, meaning you'll have to be very careful where you shoot to avoid bouncing a shot toward yourself!
Luckily, the beams can only travel a certain maximum distance before becoming too weak to cause damage. You also know that if a beam hits a corner, it will bounce back in exactly the same direction. And of course, if the beam hits either you or the guard, it will stop immediately (albeit painfully).
Write a function answer(dimensions, your_position, guard_position, distance) that gives an array of 2 integers of the width and height of the room, an array of 2 integers of your x and y coordinates in the room, an array of 2 integers of the guard's x and y coordinates in the room, and returns an integer of the number of distinct directions that you can fire to hit the elite guard, given the maximum distance that the beam can travel.
The room has integer dimensions [1 < x_dim <= 1000, 1 < y_dim <= 1000]. You and the elite guard are both positioned on the integer lattice at different distinct positions (x, y) inside the room such that [0 < x < x_dim, 0 < y < y_dim]. Finally, the maximum distance that the beam can travel before becoming harmless will be given as an integer 1 < distance <= 10000.
For example, if you and the elite guard were positioned in a room with dimensions [3, 2], you_position [1, 1], guard_position [2, 1], and a maximum shot distance of 4, you could shoot in seven different directions to hit the elite guard (given as vector bearings from your location): [1, 0], [1, 2], [1, -2], [3, 2], [3, -2], [-3, 2], and [-3, -2]. As specific examples, the shot at bearing [1, 0] is the straight line horizontal shot of distance 1, the shot at bearing [-3, -2] bounces off the left wall and then the bottom wall before hitting the elite guard with a total shot distance of sqrt(13), and the shot at bearing [1, 2] bounces off just the top wall before hitting the elite guard with a total shot distance of sqrt(5).
Links referred
This post gives a test case and it is passing in my solution
This post gives a GUI to understand the problem
My approach
I reflect the enemy position in the mirrored planes of the rectangular room on all directions as long as the reflected point is within the given distance from the source point. This will give the enemy positions in reflected planes as a straight line from source. This distance is less than the given distance. Once these points are generated, I check if any of the points from that list makes the beam passes through hero before hitting the enemy.
Code in Python
from math import *
BOTTOM_LEFT = (0,0)
TOP_RIGHT = (0,0)
SOURCE = (0,0)
TARGET = (0,0)
DISTANCE = 0
BOTTOM_RIGHT = (0,0)
TOP_LEFT = (0,0)
def answer(tr, src, bp, dist):
global BOTTOM_LEFT
BOTTOM_LEFT = (0,0)
global TOP_RIGHT
TOP_RIGHT = tr
global SOURCE
SOURCE = src
global TARGET
TARGET = bp
global DISTANCE
DISTANCE = dist
global BOTTOM_RIGHT
BOTTOM_RIGHT = (TOP_RIGHT[0], BOTTOM_LEFT[1])
global TOP_LEFT
TOP_LEFT = (BOTTOM_LEFT[0], TOP_RIGHT[1])
all_targets = get_targets(start_point = TARGET, distance = DISTANCE)
CORNERS = [BOTTOM_LEFT, BOTTOM_RIGHT, TOP_LEFT, TOP_RIGHT]
corner_angles = [4]
i = 0
while i < 4:
ca = degrees(atan2(CORNERS[i][1] - SOURCE[1], CORNERS[i][0] - SOURCE[0]))
corner_angles.append(ca)
i = i+1
valid_angles = set()
for tgt in all_targets:
pa = degrees(atan2(tgt[1] - SOURCE[1], tgt[0] - SOURCE[0]))
if(tgt[0] - SOURCE[0] > 0 ):
valid_angles.add(pa)
elif(tgt[0] - SOURCE[0] < 0):
if pa % 45 != 0 and pa not in corner_angles:
valid_angles.add(pa)
else:
valid_angles.add(pa)
if(src == bp):
return 0
else:
return len(valid_angles)
def get_mirrored(point):
ret = []
x = point[0]
y = point[1] - 2*(point[1] - TOP_RIGHT[1])
ret.append((x,y))
ret.append((point[0], point[1] - 2*(point[1] - BOTTOM_LEFT[1])))
ret.append((point[0] - 2*(point[0] - BOTTOM_LEFT[0]), point[1]))
ret.append((point[0] - 2*(point[0] - TOP_RIGHT[0]), point[1]))
return ret
def get_targets(start_point, distance):
targets = []
targets.append(start_point)
all_targets = set()
all_targets.add((start_point[0],start_point[1]))
last_targets = all_targets
while True:
new_level_targets = set()
for tgt in last_targets:
new_targets = get_mirrored(tgt)
new_targets = set(t for t in new_targets if hypot(SOURCE[0] - t[0], SOURCE[1] - t[1]) <= DISTANCE)
new_targets -= all_targets
new_level_targets |= new_targets
if not new_level_targets:
break
all_targets |= new_level_targets
last_targets = new_level_targets
return all_targets
print(answer((3,2), (1,1), (2,1), 4)) #Answer 7
print(answer((300,275), (150,150), (185,100), 500) #Answer 9

Related

Fast algorithm to generate rectangles that contains a number of 2D points

I have one problem which I'm struggling with.
Given the following:
an array all_points containing 2D points, each points is represented as a tuple (x, y).
an array musthave_points containing the indices of points that are in all_points.
an integer m, with m < len(all_points).
Return a list of rectangles, in which a rectangle is represented by a tuple containing its 4 vertices ((x0, y0), (x1, y1), (x2, y2), (x3, y3)), each rectangle must satisfy the conditions below:
Contains m points from all_points, these m points must lay completely inside the rectangle, i.e not on either 4 of the rectangle's edges.
Contains all points from musthave_points. If musthave_points is an empty list, the rectangles only need to satisfy the first condition.
If there's no such rectangle, return an empty list. Two rectangles are considered "identical" if they contain the same subset of points and there should not be "identical" rectangles in the output.
Note: One simple brute-force solution is to first generate all combinations of m points, each of them contains all points from musthave_points. For each combination, create one rectangle that covers all points in the combination. Then count the number of points that lays inside the rectangle, if the number of points is m, it's a valid rectangle.
But that solution runs in factorial time complexity. Can you come up with something faster than that?
I already implemented the brute-force as shown below, but it's terribly slow.
import itertools
import numpy as np
import cv2
import copy
import sys
from shapely.geometry import Point
from shapely.geometry.polygon import Polygon
# Credit: https://github.com/dbworth/minimum-area-bounding-rectangle/blob/master/python/min_bounding_rect.py
def minBoundingRect(hull_points_2d):
#print "Input convex hull points: "
#print hull_points_2d
# Compute edges (x2-x1,y2-y1)
edges = np.zeros((len(hull_points_2d) - 1, 2)) # empty 2 column array
for i in range(len(edges)):
edge_x = hull_points_2d[i+1, 0] - hull_points_2d[i, 0]
edge_y = hull_points_2d[i+1, 1] - hull_points_2d[i, 1]
edges[i] = [edge_x,edge_y]
# Calculate edge angles atan2(y/x)
edge_angles = np.zeros((len(edges))) # empty 1 column array
for i in range(len(edge_angles)):
edge_angles[i] = np.math.atan2(edges[i,1], edges[i,0])
# Check for angles in 1st quadrant
for i in range(len(edge_angles)):
edge_angles[i] = np.abs(edge_angles[i] % (np.math.pi/2)) # want strictly positive answers
# Remove duplicate angles
edge_angles = np.unique(edge_angles)
# Test each angle to find bounding box with smallest area
min_bbox = (0, sys.maxsize, 0, 0, 0, 0, 0, 0) # rot_angle, area, width, height, min_x, max_x, min_y, max_y
for i in range(len(edge_angles) ):
R = np.array([[np.math.cos(edge_angles[i]), np.math.cos(edge_angles[i]-(np.math.pi/2))], [np.math.cos(edge_angles[i]+(np.math.pi/2)), np.math.cos(edge_angles[i])]])
# Apply this rotation to convex hull points
rot_points = np.dot(R, np.transpose(hull_points_2d)) # 2x2 * 2xn
# Find min/max x,y points
min_x = np.nanmin(rot_points[0], axis=0)
max_x = np.nanmax(rot_points[0], axis=0)
min_y = np.nanmin(rot_points[1], axis=0)
max_y = np.nanmax(rot_points[1], axis=0)
# Calculate height/width/area of this bounding rectangle
width = max_x - min_x
height = max_y - min_y
area = width*height
# Store the smallest rect found first (a simple convex hull might have 2 answers with same area)
if (area < min_bbox[1]):
min_bbox = (edge_angles[i], area, width, height, min_x, max_x, min_y, max_y)
# Re-create rotation matrix for smallest rect
angle = min_bbox[0]
R = np.array([[np.math.cos(angle), np.math.cos(angle-(np.math.pi/2))], [np.math.cos(angle+(np.math.pi/2)), np.math.cos(angle)]])
# Project convex hull points onto rotated frame
proj_points = np.dot(R, np.transpose(hull_points_2d)) # 2x2 * 2xn
#print "Project hull points are \n", proj_points
# min/max x,y points are against baseline
min_x = min_bbox[4]
max_x = min_bbox[5]
min_y = min_bbox[6]
max_y = min_bbox[7]
#print "Min x:", min_x, " Max x: ", max_x, " Min y:", min_y, " Max y: ", max_y
# Calculate center point and project onto rotated frame
center_x = (min_x + max_x)/2
center_y = (min_y + max_y)/2
center_point = np.dot([center_x, center_y], R)
#print "Bounding box center point: \n", center_point
# Calculate corner points and project onto rotated frame
corner_points = np.zeros((4,2)) # empty 2 column array
corner_points[0] = np.dot([max_x, min_y], R)
corner_points[1] = np.dot([min_x, min_y], R)
corner_points[2] = np.dot([min_x, max_y], R)
corner_points[3] = np.dot([max_x, max_y], R)
return (angle, min_bbox[1], min_bbox[2], min_bbox[3], center_point, corner_points) # rot_angle, area, width, height, center_point, corner_points
class PatchGenerator:
def __init__(self, all_points, musthave_points, m):
self.all_points = copy.deepcopy(all_points)
self.n = len(all_points)
self.musthave_points = copy.deepcopy(musthave_points)
self.m = m
#staticmethod
def create_rectangle(points):
rot_angle, area, width, height, center_point, corner_points = minBoundingRect(points)
return corner_points
#staticmethod
def is_point_inside_rectangle(rect, point):
pts = Point(*point)
polygon = Polygon(rect)
return polygon.contains(pts)
def check_valid_rectangle(self, rect, the_complement):
# checking if the rectangle contains any other point from `the_complement`
for point in the_complement:
if self.is_point_inside_rectangle(rect, point):
return False
return True
def generate(self):
rects = []
# generate all combinations of m points, including points from musthave_points
the_rest_indices = list(set(range(self.n)).difference(self.musthave_points))
comb_indices = itertools.combinations(the_rest_indices, self.m - len(self.musthave_points))
comb_indices = [self.musthave_points + list(inds) for inds in comb_indices]
# for each combination
for comb in comb_indices:
comb_points = np.array(self.all_points)[comb]
## create the rectangle that covers all m points
rect = self.create_rectangle(comb_points)
## check if the rectangle is valid
the_complement_indices = list(set(range(self.n)).difference(comb))
the_complement_points = list(np.array(self.all_points)[the_complement_indices])
if self.check_valid_rectangle(rect, the_complement_points):
rects.append([comb, rect]) # indices of m points and 4 vertices of the valid rectangle
return rects
if __name__ == '__main__':
all_points = [[47.43, 20.5 ], [47.76, 43.8 ], [47.56, 23.74], [46.61, 23.73], [47.49, 18.94], [46.95, 25.29], [54.31, 23.5], [48.07, 17.77],
[48.2 , 34.87], [47.24, 22.07], [47.32, 27.05], [45.56, 17.95], [41.29, 19.33], [45.48, 28.49], [42.94, 15.24], [42.05, 34.3 ],
[41.04, 26.3 ], [45.37, 21.17], [45.44, 24.78], [44.54, 43.89], [30.49, 26.79], [40.55, 22.81]]
musthave_points = [3, 5, 9]
m = 17
patch_generator = PatchGenerator(all_points, musthave_points, 17)
patches = patch_generator.generate()
Every such rectangle can be shrunk to the minimum size such that it still contains the same points. Thus you only need to check such minimal rectangles. Let n be the total number of points. Then there are at most n possible coordinates for the left side, and likewise for the other sides. For each possible pair of left and right side coordinates, you can do a linear sweep for the top and bottom coordinates. Final time complexity would be O(n^3).

How to search for objects within a specified radius of an XY coordinate (without gems)?

-- BACKGROUND --
First off, unfortunately as the software I'm working with has an in-built Ruby library, it doesn't allow for the installation of gems. I think Geocoder looked promising, alas...
I have X amount of links and nodes in 2D space and I wish to find the closest link to a given XY coordinate. Essentially looking to create my own method to do this, something like:
def manual_search_from_point(x, y, distance_from_point_to_search, collection_of_objects)
...
end
where "collection_of_objects" is normally an Array of the object group I am trying to retrieve (links).
Unsure if there is a way to continually radially search out further and further (increasing "distance_from_point_to_search") to find objects without already calculating the distance between objects. With that in mind I thought that gathering all links XY coordinates (endpoints etc.) and finding the distance from that point to the initial XY point may be worthwhile, using:
def self.distance(x1, y1, x2, y2)
Math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
end
...and going from there.
-- CURRENT STATUS --
I'm currently trying to add all links coordinates to a hash as values and the link object itself as a key, to then work through each link's XY values and work out the distance, and create a new hash with the link object again as Key, and distance to that object as value. I have made the first hash, however unsure how to cycle through the values as individual XY's. (One link objects can have many XY points)
points_hash = Hash[ array.map {|k| [k, k.bends]} ]
-- DESIRED OUTPUT --
From:
{#<Link:0x803a2e8>=>[394514.80, 421898.01, 394512.92, 421895.82]...} #(X1, Y1, X2, Y2)
to
{#<Link:0x803a2e8>=>[157], #(distance in metres (value not real distance))
\#<Link:0x803a2e8>=>[196]...} #(distance in metres (value not real distance))
Can give more information if its a little tricky to get what I'm after. Also happy to be told I'm going about this all wrong!
Many thanks.
Approach
Consider the link
[a, b]
where a and b are two-element arrays that correspond to x-y coordinates in Euclidean space. I will assume a != b.
In vector terminology, every point on the line that goes through the points a and b can be expressed as:
αa + (1-α)b
for some value of the scalar α. α satisfies 0 <= α <= 1 for points falling on the line segment between a and b. Those points are said to comprise a convex combination of a and b. I will compute the value of α (alpha) that corresponds to a point that is on both the line that passes through a and b and line that is perpendicular to that line and passes through a given point p.
First calculate the slope of the line that passes between a and b. Let
a = [ax,ay]
b = [bx,by]
Points c = [cx,cy] on the line that passes through a and b are expressed:
cx = alpha*ax + (1-alpha)*bx
cy = alpha*ay + (1-alpha)*by
which we can simplify to:
cx = alpha*(ax-bx) + bx
cy = alpha*(ay-by) + by
Note that cx and cy equal bx and by when alpha is zero and equal ax and ay when alpha equals 1.
Suppose now we are given a point:
p = [px,py]
and wish to find the point on the line (not necessarily the line segment) that passes through a and b that is the closest point to p. We can do that as follows.
First calculate the slope of the line that passes through a and b (assuming bx != ax, which would correspond to a vertical line):
slope = (by-ay)/(bx-ax)
Lines perpendicular to this line have a slope equal to:
pslope = -1/slope
Let's compute the intercept of such a line that passes through point p:
intercept + pslope*px = py
So
intercept = py - pslope*px
Now let's see where this line intersects the line that passes through a and b in terms of the value of alpha:
intercept + pslope(alpha*(ax-bx) + bx) = alpha*(ay-by) + by
Solving for alpha:
alpha = (by - pslope*bx - intercept)/(pslope*(ax-bx) - (ay-by))
Let's try this with an example. Consider just two links of a graph, as shown in the professionally-prepared drawing below. (Whoops! I see my graphic service neglected to label the point [2,3] as "A".) First consider the link, or line segment, A-B and the point P.
ax = 2
ay = 3
bx = 5
by = 7
px = 5
py = 2
slope = (by-ay).fdiv(bx-ax)
#=> (7-3).fdiv(5-2) => 1.333..
pslope = -1.0/slope
#=> -0.75
intercept = py - pslope*px
#=> 5.75
alpha = (by - pslope*bx - intercept)/(pslope*(ax-bx) - (ay-by))
#=> 0.8
cx = alpha*(ax-bx) + bx
#=> 2.6
cy = alpha*(ay-by) + by
#=> 3.8
Because 0 <= alpha (0.8) <= 1, (cx,cy) falls in the line segment A-B, so that point is the closest point on the line segment to P, not just the point on the line going through A and B that is closest to P.
The square of the distance from P to C is found to equal
(cx-px)**2 + (cy-py)**2
#=> (2.6-5.0)**2 + (3.8-2.0)**2 => 9
If we wished to include all links no farther than, say, 3.5 from P, that is equivalent to including all links whose squared distance to P is no more than:
max_dist_sqrd = 3.5**2
#=> 12.25
As 9.0 <= max_dist_sqrd (12.25), this link would be included.
Now consider the link D-E.
dx = 7
dy = 6
ex = 6
ey = 9
px = 5
py = 2
slope = (ey-dy).fdiv(ex-dx)
#=> (9-6).fdiv(6-7) => -3.0
pslope = -1.0/slope
#=> 1.0/3.0 => 0.333
intercept = py - pslope*px
#=> 2 - (0.333)*5 => 0.333..
alpha = (ey - pslope*ex - intercept)/(pslope*(dx-ex) - (dy-ey))
#=> (9 - 0.333*6 - 0.333)/(0.333*(7-6) - (6-9)) #=> 2.0
Because alpha > 1.0 we know that the point F is not on the line segment D-E. Let's take a brief excursion to see where the point is:
fx = alpha*(dx-ex) + ex
#=> 2.0(7-6) + 6 => 8.0
fy = alpha*(dy-ey) + ey
#=> 2.0(6-9) + 9 => 3.0
Because alpha > 1.0 we know that the closest point to P on the line segment D-E is D. Had alpha > 1.0, the closest point would have been E.
We therefore find that the closest distance squared from the point P to the line segment D-E equals:
(dx-px)**2 + (dy-py)**2
#=> (7-5)**2 + (6-2)**2 => 20
Since 20.0 > max_dist_sqrd (12.25), this link would not be included.
Code
def links_in_point_circle(links, point, max_dist)
max_dist_sqrd = max_dist**2
links.select { |link| sqrd_dist_to_link(link, point) <= max_dist_sqrd }
end
def sqrd_dist_to_link( ((xlow,ylow),(xhigh,yhigh)),(px,py) )
return vert_link_dist(xlow,ylow,yhigh,px,py) if xlow == xhigh
slope = -1.0/((yhigh-ylow).fdiv(xhigh-xlow))
intercept = py - slope*px
alpha = (yhigh - slope*xhigh - intercept)/(slope*(xlow-xhigh) - (ylow-yhigh))
closest_x, closest_y =
case alpha
when -Float::INFINITY...0
[xhigh, yhigh]
when 0..1.0
[alpha*(xlow-xhigh) + xhigh, alpha*(ylow-yhigh) + yhigh]
else
[xlow, ylow]
end
(closest_x-px)**2 + (closest_y-py)**2
end
def vert_link_dist(xlow, ylow, yhigh, px, py)
return Float::INFINITY if px != xlow
case
when py < ylow then (ylow-py)**2
when ylow..yhigh then 0
else (py-yhigh)**2
end
end
Example
links = [[[2,3],[5,7]], [[7,6], [6,9]]]
point = [5,2]
max_dist = 3.5
links_in_point_circle(links, point, max_dist)
#=> [[[2, 3], [5, 7]]]
Create an AABB-Tree: AABB-Tree is an spatial index i.e., it accelerate spatial queries. You can use other index datastructure like kd-tree, uniform grid, quad-tree, R-tree, etc. But I found AABB-tree simpler and near optimal for range queries (what you want to do is a range query e.g., like nearest neighbors)
1.1) First put each and every link into an AABB. Compute the centroid of each AABB.
1.2) Put all your AABBs into a big AABB that contains all of them. Make that big AABB the tree root node.
1.3) Find the largest side of the big AABB (it can be X or Y).
1.4) Sort all the AABBs by centroid coordinate (X or Y) corresponding to the largest side.
1.5) Find the middle of the sorted list and split it in two lists.
1.6) Create two child nodes attached to the root node and assign one list to each node.
1.7) Call step 1.2) recursively to build each child node into a subtree.
1.8) Stop the recursion when the list is of size <= N. Then assign all AABBs to that node.
Do a range query to find the closest link to a point (i.e., traverse the tree recursively).
2.1) Define a circle of infinite radius centered at the point you want to query. Pass the circle as parameter to the traverse function.
2.2) If the root node's AABB intersect with the circle, then call recursively the traverse function with the left and the right children. The order is important, so you should choose first the child closer to the circle center.
2.3) If the node is a leaf node (i.e., no children, it hold the list of AABBs of links) then check if circle intersect the AABBs of the links, if it does then compute the distance of point to each link and keep the shortest distance. While you compute the distance to each link compare it with the radius of the circle. If the distance is smaller than the radius update the radius if the circle to the shortest distance, and keep the ID of the link with shortest distance.
Once traverse function finish, you will get the shortest distance in logarithmic time.
Using methods provided by Cary Swoveland above, the answer is almost there. Using an example below:
class Link
attr_accessor :bends
def initialize(bends)
#bends = bends
end
end
link_1 = Link.new([1, 1, 2, 2, 3, 3, 4, 4])
link_2 = Link.new([5, 5, 6, 6, 7, 7, 8, 8])
link_3 = Link.new([11, 11, 14, 14, 17, 17, 22, 22, 25, 25])
valid_links = Array.new
links = [link_1, link_2, link_3]
links.each do |link|
# Conditional goes here, to populate valid_links
valid_links << link
end
point = [1.1, 1.1]
def link_coordinates(link)
# To get the link XYs in desired format:
# [[[x1, y1], [x2, y2]], [[x2, y2], [x3, y3]], [[x3, y3], [x4, y4]]] (1)
link = link.bends.each_slice(2).each_cons(2).to_a
#=> [[[1, 1], [2, 2]], [[2, 2], [3, 3]], [[3, 3], [4, 4]]] (for first link)
end
closest_segment = valid_links.min_by { |link| sqrd_dist_to_link(link_coordinates(link), point)}
The above yields an error within sqrd_dist_to_link. Where before when "links" was only using values and in the format:
links = [[[x1, y1], [x2, y2]], [[x2, y2], [x3, y3]], [[x3, y3], [x4, y4]]] (2)
#closest_segment = links.min_by { |link| sqrd_dist_to_link(link, point)}
succeeded in returning the nearest XY coordinates of the segment in the format: [[x1, y1], [x2, y2]].
Considering the "links" labelled "(1)" and the links labelled "(2)" output the same format, I'm uncertain how to tweak things so that valid_links.min_by { |link| sqrd_dist_to_link(link_coordinates(link), point)} will retrieve the link object of the nearest XY segment as desired.
Note:
I will update this when I have it working correctly so that it qualifies correctly as an answer.

Confused about rectangles in ruby

This code takes the coordinates of two rectangles and finds their intersection.
def rec_intersection(rect1, rect2)
x_min = [rect1[0][0], rect2[0][1]].max
x_max = [rect1[1][0], rect2[1][1]].min
y_min = [rect1[0][0], rect2[0][1]].max
y_max = [rect1[1][0], rect2[1][1]].min
return nil if ((x_max < x_min) || (y_max < y_min))
return [[x_min, y_min], [x_max, y_max]]
end
rec_intersection([[1, 1], [2, 2]],[[0, 0], [5, 5]])
I don't really understand it. Specifically I would like to know more about exactly what the coordinates mean (I know they are coordinates for the left-bottom and right-top) but can anybody elaborate more? What are they relative to? The size of the rectangle? Or it's placement? How does a rectangle with a bottom-left coordinate of [1,1] differ from one with a bottom-left of [0,0]?
Also I would like to know why in order to find the x_min, the max method is being used (and vice-versa). Any clarification is appreciated.
This is a comment, which I will delete after the OP has seen it. Here's a graph of the two rectangles, where rect1 is contained within rect2.
Earlier, where I referred to [1,1] and [2,2] as the "top-left" and "bottom-right" corners, respectively, of rect1, that should have been the "bottom-left" and "top-right" corners.

Calculate bounding polygon of alpha shape from the Delaunay triangulation

Given a set of points in the plane, a notion of alpha-shape, for a given positive number alpha, is defined by finding the Delaunay triangulation and deleting any triangles for which at least one edge exceeds alpha in length. Here's an example using d3:
http://bl.ocks.org/gka/1552725
The problem is that, when there are thousands of points, simply drawing all the interior triangles is too slow for an interactive visualization, so I'd like to just find the bounding polygons. This isn't so simple, because as you can see from that example sometimes there might be two such polygons.
As a simplification, suppose some clustering has been performed so that there's guaranteed to be a unique bounding polygon for each triangulation. What's the best way to find this bounding polygon? In particular, the edges have to be ordered consistently and it must support the possibility of "holes" (think a torus or donut shape--this is expressible in GeoJSON).
Here is some Python code that does what you need. I modified the alpha-shape (concave hull) computation from here so that it doesn't insert inner edges (the only_outer parameter). I also made it self-contained so it doesn't depend on an outside library.
from scipy.spatial import Delaunay
import numpy as np
def alpha_shape(points, alpha, only_outer=True):
"""
Compute the alpha shape (concave hull) of a set of points.
:param points: np.array of shape (n,2) points.
:param alpha: alpha value.
:param only_outer: boolean value to specify if we keep only the outer border
or also inner edges.
:return: set of (i,j) pairs representing edges of the alpha-shape. (i,j) are
the indices in the points array.
"""
assert points.shape[0] > 3, "Need at least four points"
def add_edge(edges, i, j):
"""
Add a line between the i-th and j-th points,
if not in the list already
"""
if (i, j) in edges or (j, i) in edges:
# already added
assert (j, i) in edges, "Can't go twice over same directed edge right?"
if only_outer:
# if both neighboring triangles are in shape, it is not a boundary edge
edges.remove((j, i))
return
edges.add((i, j))
tri = Delaunay(points)
edges = set()
# Loop over triangles:
# ia, ib, ic = indices of corner points of the triangle
for ia, ib, ic in tri.simplices:
pa = points[ia]
pb = points[ib]
pc = points[ic]
# Computing radius of triangle circumcircle
# www.mathalino.com/reviewer/derivation-of-formulas/derivation-of-formula-for-radius-of-circumcircle
a = np.sqrt((pa[0] - pb[0]) ** 2 + (pa[1] - pb[1]) ** 2)
b = np.sqrt((pb[0] - pc[0]) ** 2 + (pb[1] - pc[1]) ** 2)
c = np.sqrt((pc[0] - pa[0]) ** 2 + (pc[1] - pa[1]) ** 2)
s = (a + b + c) / 2.0
area = np.sqrt(s * (s - a) * (s - b) * (s - c))
circum_r = a * b * c / (4.0 * area)
if circum_r < alpha:
add_edge(edges, ia, ib)
add_edge(edges, ib, ic)
add_edge(edges, ic, ia)
return edges
If you run it with the following test code you will get
this figure:
from matplotlib.pyplot import *
# Constructing the input point data
np.random.seed(0)
x = 3.0 * np.random.rand(2000)
y = 2.0 * np.random.rand(2000) - 1.0
inside = ((x ** 2 + y ** 2 > 1.0) & ((x - 3) ** 2 + y ** 2 > 1.0) & ((x - 1.5) ** 2 + y ** 2 > 0.09))
points = np.vstack([x[inside], y[inside]]).T
# Computing the alpha shape
edges = alpha_shape(points, alpha=0.25, only_outer=True)
# Plotting the output
figure()
axis('equal')
plot(points[:, 0], points[:, 1], '.')
for i, j in edges:
plot(points[[i, j], 0], points[[i, j], 1])
show()
Create a graph in which the nodes correspond to Delaunay triangles and in which there is a graph edge between two triangles if and only if they share two vertices.
Compute the connected components of the graph.
For each connected component, find all of the nodes that have less than three adjacent nodes (that is, those that have degree 0, 1, or 2). These correspond to the boundary triangles. We define the edges of a boundary triangle that are not shared with another triangle to be the boundary edges of that boundary triangle.
As an example, I have highlighted these boundary triangles in your example "question mark" Delaunay triangulation:
By definition, every boundary triangle is adjacent to at most two other boundary triangles. The boundary edges of boundary triangles form cycles. You can simply traverse those cycles to determine polygon shapes for the boundaries. This will also work for polygons with holes if you keep them in mind in your implementation.
I know it is a delayed answer, but the methods posted here did not work for me for various reasons.
The package Alphashape that is mentioned is generally good but its downside is that it uses Shapely's cascade_union and triangulation methods to give you the final concave hull which is super slow for large datasets and low alpha values (high precision). In this case the code posted by Iddo Hanniel (and the revision by Harold) will keep a great number of edges on the interior and the only way to dissolve them is to use the aforementioned cascade_union and triangulation from Shapely.
I generally work with complex forms and the code below works fine and it is fast (2 seconds for 100K 2D points):
import numpy as np
from shapely.geometry import MultiLineString
from shapely.ops import unary_union, polygonize
from scipy.spatial import Delaunay
from collections import Counter
import itertools
def concave_hull(coords, alpha): # coords is a 2D numpy array
# i removed the Qbb option from the scipy defaults.
# it is much faster and equally precise without it.
# unless your coords are integers.
# see http://www.qhull.org/html/qh-optq.htm
tri = Delaunay(coords, qhull_options="Qc Qz Q12").vertices
ia, ib, ic = (
tri[:, 0],
tri[:, 1],
tri[:, 2],
) # indices of each of the triangles' points
pa, pb, pc = (
coords[ia],
coords[ib],
coords[ic],
) # coordinates of each of the triangles' points
a = np.sqrt((pa[:, 0] - pb[:, 0]) ** 2 + (pa[:, 1] - pb[:, 1]) ** 2)
b = np.sqrt((pb[:, 0] - pc[:, 0]) ** 2 + (pb[:, 1] - pc[:, 1]) ** 2)
c = np.sqrt((pc[:, 0] - pa[:, 0]) ** 2 + (pc[:, 1] - pa[:, 1]) ** 2)
s = (a + b + c) * 0.5 # Semi-perimeter of triangle
area = np.sqrt(
s * (s - a) * (s - b) * (s - c)
) # Area of triangle by Heron's formula
filter = (
a * b * c / (4.0 * area) < 1.0 / alpha
) # Radius Filter based on alpha value
# Filter the edges
edges = tri[filter]
# now a main difference with the aforementioned approaches is that we dont
# use a Set() because this eliminates duplicate edges. in the list below
# both (i, j) and (j, i) pairs are counted. The reasoning is that boundary
# edges appear only once while interior edges twice
edges = [
tuple(sorted(combo)) for e in edges for combo in itertools.combinations(e, 2)
]
count = Counter(edges) # count occurrences of each edge
# keep only edges that appear one time (concave hull edges)
edges = [e for e, c in count.items() if c == 1]
# these are the coordinates of the edges that comprise the concave hull
edges = [(coords[e[0]], coords[e[1]]) for e in edges]
# use this only if you need to return your hull points in "order" (i think
# its CCW)
ml = MultiLineString(edges)
poly = polygonize(ml)
hull = unary_union(list(poly))
hull_vertices = hull.exterior.coords.xy
return edges, hull_vertices
There now exists a python package alphashape which is extremely easy to use, and can be installed by pip or conda.
The main function has similar inputs to the answer given by #Iddo Hanniel, adjusting the second positional argument would give you the desired outline.
Alternatively, you could leave the seconda positional argument blank and the function would optimize that parameter for you to give you the best concave hull. Beware, the computational time is increased greatly if you let the function optimize the value.
Slightly revise Hanniel's answer for 3d point case (tetrahedron).
def alpha_shape(points, alpha, only_outer=True):
"""
Compute the alpha shape (concave hull) of a set of points.
:param points: np.array of shape (n, 3) points.
:param alpha: alpha value.
:param only_outer: boolean value to specify if we keep only the outer border
or also inner edges.
:return: set of (i,j) pairs representing edges of the alpha-shape. (i,j) are
the indices in the points array.
"""
assert points.shape[0] > 3, "Need at least four points"
def add_edge(edges, i, j):
"""
Add a line between the i-th and j-th points,
if not in the set already
"""
if (i, j) in edges or (j, i) in edges:
# already added
if only_outer:
# if both neighboring triangles are in shape, it's not a boundary edge
if (j, i) in edges:
edges.remove((j, i))
return
edges.add((i, j))
tri = Delaunay(points)
edges = set()
# Loop over triangles:
# ia, ib, ic, id = indices of corner points of the tetrahedron
print(tri.vertices.shape)
for ia, ib, ic, id in tri.vertices:
pa = points[ia]
pb = points[ib]
pc = points[ic]
pd = points[id]
# Computing radius of tetrahedron Circumsphere
# http://mathworld.wolfram.com/Circumsphere.html
pa2 = np.dot(pa, pa)
pb2 = np.dot(pb, pb)
pc2 = np.dot(pc, pc)
pd2 = np.dot(pd, pd)
a = np.linalg.det(np.array([np.append(pa, 1), np.append(pb, 1), np.append(pc, 1), np.append(pd, 1)]))
Dx = np.linalg.det(np.array([np.array([pa2, pa[1], pa[2], 1]),
np.array([pb2, pb[1], pb[2], 1]),
np.array([pc2, pc[1], pc[2], 1]),
np.array([pd2, pd[1], pd[2], 1])]))
Dy = - np.linalg.det(np.array([np.array([pa2, pa[0], pa[2], 1]),
np.array([pb2, pb[0], pb[2], 1]),
np.array([pc2, pc[0], pc[2], 1]),
np.array([pd2, pd[0], pd[2], 1])]))
Dz = np.linalg.det(np.array([np.array([pa2, pa[0], pa[1], 1]),
np.array([pb2, pb[0], pb[1], 1]),
np.array([pc2, pc[0], pc[1], 1]),
np.array([pd2, pd[0], pd[1], 1])]))
c = np.linalg.det(np.array([np.array([pa2, pa[0], pa[1], pa[2]]),
np.array([pb2, pb[0], pb[1], pb[2]]),
np.array([pc2, pc[0], pc[1], pc[2]]),
np.array([pd2, pd[0], pd[1], pd[2]])]))
circum_r = math.sqrt(math.pow(Dx, 2) + math.pow(Dy, 2) + math.pow(Dz, 2) - 4 * a * c) / (2 * abs(a))
if circum_r < alpha:
add_edge(edges, ia, ib)
add_edge(edges, ib, ic)
add_edge(edges, ic, id)
add_edge(edges, id, ia)
add_edge(edges, ia, ic)
add_edge(edges, ib, id)
return edges
It turns out TopoJSON has a merge algorithm which performs just this task: https://github.com/mbostock/topojson/wiki/API-Reference#merge
There's even an example showing it in action: http://bl.ocks.org/mbostock/9927735
In my case, it was easy for me to generate TopoJSON data, and this library function accomplished the task perfectly for me.
Building up on #Timothy's answer, I used the following algorithm to calculate the boundary rings of a Delaunay triangulation.
from matplotlib.tri import Triangulation
import numpy as np
def get_boundary_rings(x, y, elements):
mpl_tri = Triangulation(x, y, elements)
idxs = np.vstack(list(np.where(mpl_tri.neighbors == -1))).T
unique_edges = list()
for i, j in idxs:
unique_edges.append((mpl_tri.triangles[i, j],
mpl_tri.triangles[i, (j+1) % 3]))
unique_edges = np.asarray(unique_edges)
ring_collection = list()
initial_idx = 0
for i in range(1, len(unique_edges)-1):
if unique_edges[i-1, 1] != unique_edges[i, 0]:
try:
idx = np.where(
unique_edges[i-1, 1] == unique_edges[i:, 0])[0][0]
unique_edges[[i, idx+i]] = unique_edges[[idx+i, i]]
except IndexError:
ring_collection.append(unique_edges[initial_idx:i, :])
initial_idx = i
continue
# if there is just one ring, the exception is never reached,
# so populate ring_collection before returning.
if len(ring_collection) == 0:
ring_collection.append(np.asarray(unique_edges))
return ring_collection
Alpha shapes is defined as a delaunay triangulation without edges exceeding alpha. First of remove all interior triangles and then all edges exceeding alpha.

Points enclosed by a custom defined Hypercube

I have a N-dimensional vector, X and 'n' equidistant points along each dimension and a parameter 'delta'. I need a way to find the total of n^N vectors enclosed by the Hypercube defined with the vector X at the center and each side of Hypercube being of size 2*delta.
For example:
Consider a case of N=3, so we have a Cube of size (2*delta) enclosing the point X.
------------\
|\--------|--\
| | X | |
----------- |
\ |_2*del___\|
Along each dimension I have 'n' points. So, I have a total of n^3 vectors around X. I need to find all the vectors. Is there any standard algorithm/method for the same? If you have done anything similar, please suggest.
If the problem is not clear, let me know.
This is what I was looking at: Considering one dimension, length of a side is 2*delta and I have n divisions. So, each sub-division is of size (2*delta/n). So I just move to the origin that is (x-delta) (since x is the mid point of the side) and obtain the 'n' points by {(x-delta) + 1*(2*delta/n),(x-delta) + 2*(2*delta/n)....+ (x-delta) + 1*(n*delta/n) } . I do this for all the N-dimensions and then take a permutation of the co-ordinates. That way I have all the points.
(I would like to close this)
If i understand your problem correctly, you have an axis-aligned hypercube centred around a point X, and you have subdivided the interior of this hypercube into a regular lattice where the lattice points and spacing are in the coordinate system of the hypercube. All you have to do is let X = 0, find the vectors to each of the lattice points, and then go back and translate them by X.
Edit: let me add an example
let x = (5,5,5), delta = 1 and n = 3
then, moving x to the origin, your lattice points are (-1, -1, -1), (0, -1, -1), (1, -1, -1) and so on for a total of 27. translating back, we have (4, 4, 4), (5, 4, 4), (6, 4, 4) and so on.
Ok, I didn't fully understand your question. There are total of 2^(N-1)*N "lines" about a point in an N-dimensional hypercube.
If you just want to create n points on lines which look like the axis, but translated at a distance of delta from the origin, here's some (poorly written, for clarity) MATLAB code:
n = 10;
delta = 10;
N = 3;
step = (2*delta)/(n-1);
P = zeros(n,N,N);
X = [20 30 25];
for line_dim = 1:N
for point = 1:n
for point_dim = 1:N
if(point_dim ~= line_dim)
P(point,point_dim,line_dim) = X(point_dim)-delta;
else
P(point,point_dim,line_dim) = X(point_dim)-delta+step*(point-1);
end
end
end
end
The code's for a cube, but it should work for any N. All I've done is:
Draw those n equidistant points on the axes.
Translate the axes by (X-delta)
Display:
% Display stuff
PP = reshape(permute(P,[1 3 2]),[n*N N]);
plot3(X(1),X(2),X(3),'r*',PP(:,1),PP(:,2),PP(:,3),'.')
axis([0 Inf 0 Inf 0 Inf]);
grid on;

Resources