org.opentripplanner.visibility.VisibilityPolygon Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of otp Show documentation
Show all versions of otp Show documentation
The OpenTripPlanner multimodal journey planning system
package org.opentripplanner.visibility;
import java.util.PriorityQueue;
import java.util.HashSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.ArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
Ported by David Turner from Visilibity, by Karl J. Obermeyer
This port undoubtedly introduced a number of bugs (and removed some features).
Bug reports should be directed to the OpenTripPlanner project, unless they
can be reproduced in the original VisiLibity
*/
public class VisibilityPolygon extends VLPolygon {
private static Logger log = LoggerFactory.getLogger(VisibilityPolygon.class);
VLPoint observer;
public boolean is_spike(VLPoint observer, VLPoint point1, VLPoint point2, VLPoint point3, double epsilon) {
return (
// Make sure observer not colocated with any of the points.
observer.distance(point1) > epsilon && observer.distance(point2) > epsilon
&& observer.distance(point3) > epsilon
// Test whether there is a spike with point2 as the tip
&& ((observer.distance(point2) >= observer.distance(point1) && observer
.distance(point2) >= observer.distance(point3)) || (observer
.distance(point2) <= observer.distance(point1) && observer.distance(point2) <= observer
.distance(point3)))
// && the pike is sufficiently sharp,
&& Math.max(point2.distance(new Ray(observer, point1)),
point2.distance(new Ray(observer, point3))) <= epsilon);
// Formerly used
// Math.abs( Polygon(point1, point2, point3).area() ) < epsilon
}
public void chop_spikes_at_back(VLPoint observer, double epsilon) {
// Eliminate "special case" vertices of the visibility polygon.
// While the top three vertices form a spike.
while (vertices.size() >= 3
&& is_spike(observer, vertices.get(vertices.size() - 3),
vertices.get(vertices.size() - 2), vertices.get(vertices.size() - 1),
epsilon)) {
vertices.set(vertices.size() - 2, vertices.get(vertices.size() - 1));
vertices.remove(vertices.size() - 1);
}
}
void chop_spikes_at_wrap_around(VLPoint observer, double epsilon) {
// Eliminate "special case" vertices of the visibility polygon at
// wrap-around. While the there's a spike at the wrap-around,
while (vertices.size() >= 3
&& is_spike(observer, vertices.get(vertices.size() - 2),
vertices.get(vertices.size() - 1), vertices.get(0), epsilon)) {
// Chop off the tip of the spike.
vertices.remove(vertices.size() - 1);
}
}
void chop_spikes(VLPoint observer, double epsilon) {
HashSet spike_tips = new HashSet();
ArrayList vertices_temp = new ArrayList();
// Middle point is potentially the tip of a spike
for (int i = 0; i < vertices.size(); i++)
if (get(i + 2).distance(new LineSegment(get(i), get(i + 1))) <= epsilon
|| get(i).distance(new LineSegment(get(i + 1), get(i + 2))) <= epsilon)
spike_tips.add(get(i + 1));
for (int i = 0; i < vertices.size(); i++)
if (!spike_tips.contains(vertices.get(i)))
vertices_temp.add(vertices.get(i));
vertices = vertices_temp;
}
public VisibilityPolygon(VLPoint observer, Environment environment_temp, double epsilon) {
this.observer = observer;
// Visibility polygon algorithm for environments with holes
// Radial line (AKA angular plane) sweep technique.
//
// Based on algorithms described in
//
// [1] "Automated Camera Layout to Satisfy Task-Specific and
// Floorplan-Specific Coverage Requirements" by Ugur Murat Erdem
// && Stan Scarloff, April 15, 2004
// available at BUCS Technical Report Archive:
// http://www.cs.bu.edu/techreports/pdf/2004-015-camera-layout.pdf
//
// [2] "Art Gallery Theorems && Algorithms" by Joseph O'Rourke
//
// [3] "Visibility Algorithms in the Plane" by Ghosh
//
// We define a k-point is a point seen on the other side of a
// visibility occluding corner. This name is appropriate because
// the vertical line in the letter "k" is like a line-of-sight past
// the corner of the "k".
//
// Preconditions:
// (1) the Environment is epsilon-valid,
// (2) the Point observer is actually in the Environment
// environment_temp,
// (3) the guard has been epsilon-snapped to the boundary, followed
// by vertices of the environment (the order of the snapping
// is important).
//
// :WARNING:
// For efficiency, the assertions corresponding to these
// preconditions have been excluded.
//
assert (environment_temp.is_valid(epsilon));
assert (environment_temp.is_in_standard_form());
assert (observer.in(environment_temp, epsilon));
// true => data printed to terminal
// false => silent
// The visibility polygon cannot have more vertices than the environment.
vertices.ensureCapacity(environment_temp.n());
//
// --------PREPROCESSING--------
//
// construct a POLAR EDGE LIST from environment_temp's outer
// boundary and holes. During this construction, those edges are
// split which either (1) cross the ray emanating from the observer
// parallel to the x-axis (of world coords), or (2) contain the
// observer in their relative interior (w/in epsilon). Also, edges
// having first vertex bearing >= second vertex bearing are
// eliminated because they cannot possibly contribute to the
// visibility polygon.
final Angle ANGLE_PI = new Angle(Math.PI);
final Angle ANGLE_ZERO = new Angle(0.0);
ArrayList elp = new ArrayList();
PolarPoint ppoint1, ppoint2;
PolarPoint split_bottom, split_top;
double t;
// If the observer is standing on the Enviroment boundary with its
// back to the wall, these will be the bearings of the next vertex
// to the right && to the left, respectively.
Angle right_wall_bearing = new Angle(0.0);
Angle left_wall_bearing = new Angle(0.0);
for (int i = 0; i <= environment_temp.h(); i++) {
VLPolygon polygon = environment_temp.get(i);
for (int j = 0; j < polygon.n(); j++) {
ppoint1 = new PolarPoint(observer, polygon.get(j));
ppoint2 = new PolarPoint(observer, polygon.get(j + 1));
log.debug("contemplating " + ppoint1 + " and " + ppoint1);
// If the observer is in the relative interior of the edge.
if (observer.in_relative_interior_of(new LineSegment(ppoint1, ppoint2), epsilon)) {
log.debug("in relative interior");
// Split the edge at the observer && add the resulting two
// edges to elp (the polar edge list).
split_bottom = new PolarPoint(observer, observer);
split_top = new PolarPoint(observer, observer);
if (ppoint2.bearing.equals(ANGLE_ZERO))
ppoint2.set_bearing_to_2pi();
left_wall_bearing = ppoint1.bearing.clone();
right_wall_bearing = ppoint2.bearing.clone();
elp.add(new PolarEdge(ppoint1, split_bottom));
elp.add(new PolarEdge(split_top, ppoint2));
continue;
}
// Else if the observer is on first vertex of edge.
else if (observer.distance(ppoint1) <= epsilon) {
log.debug("on first vertex");
if (ppoint2.bearing.equals(ANGLE_ZERO)) {
ppoint2.set_bearing_to_2pi();
}
// Get right wall bearing.
right_wall_bearing = ppoint2.bearing.clone();
elp.add(new PolarEdge(new PolarPoint(observer, observer), ppoint2));
continue;
}
// Else if the observer is on second vertex of edge.
else if (observer.distance(ppoint2) <= epsilon) {
log.debug("on second vertex");
// Get left wall bearing.
left_wall_bearing = ppoint1.bearing.clone();
elp.add(new PolarEdge(ppoint1, new PolarPoint(observer, observer)));
continue;
}
// Otherwise the observer is not on the edge.
// If edge not horizontal (w/in epsilon).
else if (Math.abs(ppoint1.y - ppoint2.y) > epsilon) {
log.debug("off edge");
// Possible source of numerical instability?
t = (observer.y - ppoint2.y) / (ppoint1.y - ppoint2.y);
// If edge crosses the ray emanating horizontal && right of
// the observer.
if (0 < t && t < 1 && observer.x < t * ppoint1.x + (1 - t) * ppoint2.x) {
log.debug("crosses ray");
// If first point is above, omit edge because it runs
// 'against the grain'.
if (ppoint1.y > observer.y)
continue;
// Otherwise split the edge, making sure angles are assigned
// correctly on each side of the split point.
split_bottom = new PolarPoint(observer, new VLPoint(t * ppoint1.x + (1 - t)
* ppoint2.x, observer.y));
split_top = new PolarPoint(observer, new VLPoint(t * ppoint1.x + (1 - t)
* ppoint2.x, observer.y));
split_top.set_bearing(ANGLE_ZERO);
split_bottom.set_bearing_to_2pi();
elp.add(new PolarEdge(ppoint1, split_bottom));
elp.add(new PolarEdge(split_top, ppoint2));
continue;
} else {
if (ppoint1.bearing.compareTo(ppoint2.bearing) >= 0
&& ppoint2.bearing.equals(ANGLE_ZERO)
&& ppoint1.bearing.compareTo(ANGLE_PI) > 0) {
ppoint2.set_bearing_to_2pi();
// Filter out edges which run 'against the grain'.
} else if ((ppoint1.bearing.equals(ANGLE_ZERO) &&
ppoint2.bearing.compareTo(ANGLE_PI) > 0)
|| ppoint1.bearing.compareTo(ppoint2.bearing) >= 0) {
continue;
}
}
elp.add(new PolarEdge(ppoint1, ppoint2));
continue;
}
// If edge is horizontal (w/in epsilon).
else {
log.debug("epsilon horizontal");
// Filter out edges which run 'against the grain'.
if (ppoint1.bearing.compareTo(ppoint2.bearing) >= 0)
continue;
elp.add(new PolarEdge(ppoint1, ppoint2));
}
}
}
// construct a SORTED LIST, q1, OF VERTICES represented by
// PolarPointWithEdgeInfo objects. A
// PolarPointWithEdgeInfo is a derived class of PolarPoint
// which includes (1) a pointer to the corresponding edge
// (represented as a PolarEdge) in the polar edge list elp, and
// (2) a boolean(is_first) which is true iff that vertex is the
// first Point of the respective edge (is_first == false => it's
// second Point). q1 is sorted according to lex. order of polar
// coordinates just as PolarPoints are, but with the additional
// requirement that if two vertices have equal polar coordinates,
// the vertex which is the first point of its respective edge is
// considered greater. q1 will serve as an event point queue for
// the radial sweep.
ArrayList q1 = new ArrayList();
PolarPointWithEdgeInfo ppoint_wei1 = new PolarPointWithEdgeInfo(), ppoint_wei2 = new PolarPointWithEdgeInfo();
Iterator elp_iterator = elp.iterator();
while (elp_iterator.hasNext()) {
PolarEdge edge = elp_iterator.next();
ppoint_wei1.set_polar_point(edge.first);
ppoint_wei1.incident_edge = edge;
ppoint_wei1.is_first = true;
ppoint_wei2.set_polar_point(edge.second);
ppoint_wei2.incident_edge = edge;
ppoint_wei2.is_first = false;
// If edge contains the observer, then adjust the bearing of
// the PolarPoint containing the observer.
if (observer.distance(ppoint_wei1) <= epsilon) {
if (right_wall_bearing.compareTo(left_wall_bearing) > 0) {
ppoint_wei1.set_bearing(right_wall_bearing);
edge.first.set_bearing(right_wall_bearing);
} else {
ppoint_wei1.set_bearing(ANGLE_ZERO);
edge.first.set_bearing(ANGLE_ZERO);
}
} else if (observer.distance(ppoint_wei2) <= epsilon) {
if (right_wall_bearing.compareTo(left_wall_bearing) > 0) {
ppoint_wei2.set_bearing(right_wall_bearing);
edge.second.set_bearing(right_wall_bearing);
} else {
ppoint_wei2.set_bearing_to_2pi();
edge.second.set_bearing_to_2pi();
}
}
q1.add(ppoint_wei1.clone());
q1.add(ppoint_wei2.clone());
}
// Put event point in correct order.
// Collections.sort is a stable sort.
Collections.sort(q1);
for (PolarPointWithEdgeInfo q : q1) {
log.debug("q: " + q);
}
//
// -------PREPARE FOR MAIN LOOP-------
//
// current_vertex is used to hold the event point (from q1)
// considered at iteration of the main loop.
//
PolarPointWithEdgeInfo current_vertex = new PolarPointWithEdgeInfo();
// Note active_edge and e are not actually edges themselves, but
// iterators pointing to edges. active_edge keeps track of the
// current edge visible during the sweep. e is an auxiliary
// variable used in calculation of k-points
PolarEdge active_edge, e;
// More aux vars for computing k-points.
PolarPoint k = new PolarPoint();
double k_range;
LineSegment xing;
// Priority queue of edges, where lower (first) priority indicates closer
// range to observer along current ray (of ray sweep).
IncidentEdgeCompare my_iec = new IncidentEdgeCompare(observer, current_vertex, epsilon);
PriorityQueue q2 = new PriorityQueue(elp.size(), my_iec);
// Initialize main loop.
current_vertex.set(q1.remove(0));
active_edge = current_vertex.incident_edge;
// Insert e into q2 as long as it doesn't contain the
// observer.
if (observer.distance(active_edge.first) > epsilon
&& observer.distance(active_edge.second) > epsilon) {
q2.add(active_edge);
}
vertices.add(new VLPoint(current_vertex));
log.debug("adding: " + current_vertex + "\n--");
// -------BEGIN MAIN LOOP-------//
//
// Perform radial sweep by sequentially considering each vertex
// (event point) in q1.
while (!q1.isEmpty()) {
// Pop current_vertex from q1.
current_vertex.set(q1.remove(0));
log.debug("cv: " + current_vertex);
// ---Handle Event Point---
// TYPE 1: current_vertex is the _second_vertex_ of active_edge.
if (current_vertex.incident_edge.equals(active_edge) && !current_vertex.is_first) {
log.debug( "type 1");
if (!q1.isEmpty()) {
// If the next vertex in q1 is contiguous.
if (current_vertex.distance(q1.get(0)) <= epsilon) {
continue;
}
}
// Push current_vertex onto visibility polygon
vertices.add(new VLPoint(current_vertex));
log.debug("adding: " + current_vertex);
chop_spikes_at_back(observer, epsilon);
while (!q2.isEmpty()) {
e = q2.peek();
log.debug("q2: " + e);
// If the current_vertex bearing has not passed, in the
// lex. order sense, the bearing of the second point of the
// edge at the front of q2.
if ((current_vertex.bearing.get() <= e.second.bearing.get())
// For robustness.
&& new Ray(observer, current_vertex.bearing).distance(e.second) >= epsilon
/*
* was && std::min( distance(Ray(observer, current_vertex.bearing), e.second),
* distance(Ray(observer, e.second.bearing), current_vertex) ) >= epsilon
*/
) {
// Find intersection point k of ray (through
// current_vertex) with edge e.
xing = new Ray(observer, current_vertex.bearing).intersection(
new LineSegment(e.first, e.second), epsilon);
// assert( xing.size() > 0 );
if (xing.size() > 0) {
k = new PolarPoint(observer, xing.first());
} else { // Error contingency.
k = current_vertex;
e = current_vertex.incident_edge;
}
// Push k onto the visibility polygon.
vertices.add(new VLPoint(k));
log.debug("adding k1: " + k);
chop_spikes_at_back(observer, epsilon);
active_edge = e;
break;
}
q2.poll();
}
} // Close Type 1.
// If current_vertex is the _first_vertex_ of its edge.
if (current_vertex.is_first) {
log.debug("is first");
// Find intersection point k of ray (through current_vertex)
// with active_edge.
xing = new Ray(observer, current_vertex.bearing).intersection(new LineSegment(
active_edge.first, active_edge.second), epsilon);
if (xing.size() == 0
|| (active_edge.first.distance(observer) <= epsilon && active_edge.second.bearing
.compareTo(current_vertex.bearing) <= 0)
|| active_edge.second.compareTo(current_vertex) < 0) {
k_range = Double.POSITIVE_INFINITY;
} else {
k = new PolarPoint(observer, xing.first());
k_range = k.range;
}
// Incident edge of current_vertex.
e = current_vertex.incident_edge;
// Insert e into q2 as long as it doesn't contain the
// observer.
if (observer.distance(e.first) > epsilon && observer.distance(e.second) > epsilon) {
q2.add(e);
}
// TYPE 2: current_vertex is (1) a first vertex of some edge
// other than active_edge, && (2) that edge should not become
// the next active_edge. This happens, e.g., if that edge is
// (rangewise) in back along the current bearing.
if (k_range < current_vertex.range) {
// this is empty, in the original too -DMT
log.debug("type 2");
} // Close Type 2.
// TYPE 3: current_vertex is (1) the first vertex of some edge
// other than active_edge, && (2) that edge should become the
// next active_edge. This happens, e.g., if that edge is
// (rangewise) in front along the current bearing.
if (k_range >= current_vertex.range) {
// Push k onto the visibility polygon unless effectively
// contiguous with current_vertex.
log.debug("type 3");
if (xing.size() > 0 && k_range != Double.POSITIVE_INFINITY
&& k.distance(current_vertex) > epsilon
&& active_edge.first.distance(observer) > epsilon) {
// Push k-point onto the visibility polygon.
vertices.add(new VLPoint(k));
log.debug("adding k2: " + k);
chop_spikes_at_back(observer, epsilon);
}
// Push current_vertex onto the visibility polygon.
vertices.add(new VLPoint(current_vertex));
log.debug("adding: " + current_vertex);
chop_spikes_at_back(observer, epsilon);
// Set active_edge to edge of current_vertex.
active_edge = e;
} // Close Type 3.
}
} //
//
// -------END MAIN LOOP-------//
// The VisibilityPolygon should have a minimal representation
chop_spikes_at_wrap_around(observer, epsilon);
eliminate_redundant_vertices(epsilon);
chop_spikes(observer, epsilon);
enforce_standard_form();
}
VisibilityPolygon(VLPoint observer, VLPolygon polygon_temp, double epsilon) {
this(observer, new Environment(polygon_temp), epsilon);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy