blob: fd0181e26382384fecf69d5a87dafae92295c47c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.geometry.spherical.twod;
import java.util.List;
import org.apache.commons.geometry.core.Geometry;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
import org.apache.commons.geometry.euclidean.threed.Vector3D;
import org.apache.commons.geometry.spherical.oned.Arc;
import org.apache.commons.numbers.angle.PlaneAngleRadians;
/** Spherical polygons boundary edge.
* @see SphericalPolygonsSet#getBoundaryLoops()
* @see Vertex
*/
public class Edge {
/** Start vertex. */
private final Vertex start;
/** End vertex. */
private Vertex end;
/** Length of the arc. */
private final double length;
/** Circle supporting the edge. */
private final Circle circle;
/** Build an edge not contained in any node yet.
* @param start start vertex
* @param end end vertex
* @param length length of the arc (it can be greater than \( \pi \))
* @param circle circle supporting the edge
*/
Edge(final Vertex start, final Vertex end, final double length, final Circle circle) {
this.start = start;
this.end = end;
this.length = length;
this.circle = circle;
// connect the vertices back to the edge
start.setOutgoing(this);
end.setIncoming(this);
}
/** Get start vertex.
* @return start vertex
*/
public Vertex getStart() {
return start;
}
/** Get end vertex.
* @return end vertex
*/
public Vertex getEnd() {
return end;
}
/** Get the length of the arc.
* @return length of the arc (can be greater than \( \pi \))
*/
public double getLength() {
return length;
}
/** Get the circle supporting this edge.
* @return circle supporting this edge
*/
public Circle getCircle() {
return circle;
}
/** Get an intermediate point.
* <p>
* The angle along the edge should normally be between 0 and {@link #getLength()}
* in order to remain within edge limits. However, there are no checks on the
* value of the angle, so user can rebuild the full circle on which an edge is
* defined if they want.
* </p>
* @param alpha angle along the edge, counted from {@link #getStart()}
* @return an intermediate point
*/
public Vector3D getPointAt(final double alpha) {
return circle.getPointAt(alpha + circle.getPhase(start.getLocation().getVector()));
}
/** Connect the instance with a following edge.
* @param next edge following the instance
*/
void setNextEdge(final Edge next) {
end = next.getStart();
end.setIncoming(this);
end.bindWith(getCircle());
}
/** Split the edge.
* <p>
* Once split, this edge is not referenced anymore by the vertices,
* it is replaced by the two or three sub-edges and intermediate splitting
* vertices are introduced to connect these sub-edges together.
* </p>
* @param splitCircle circle splitting the edge in several parts
* @param outsideList list where to put parts that are outside of the split circle
* @param insideList list where to put parts that are inside the split circle
*/
void split(final Circle splitCircle,
final List<Edge> outsideList, final List<Edge> insideList) {
// get the inside arc, synchronizing its phase with the edge itself
final double edgeStart = circle.getPhase(start.getLocation().getVector());
final Arc arc = circle.getInsideArc(splitCircle);
final double arcRelativeStart = PlaneAngleRadians.normalize(arc.getInf(), edgeStart + Math.PI) - edgeStart;
final double arcRelativeEnd = arcRelativeStart + arc.getSize();
final double unwrappedEnd = arcRelativeEnd - Geometry.TWO_PI;
// build the sub-edges
final DoublePrecisionContext precision = circle.getPrecision();
Vertex previousVertex = start;
if (precision.compare(unwrappedEnd, length) >= 0) {
// the edge is entirely contained inside the circle
// we don't split anything
insideList.add(this);
} else {
// there are at least some parts of the edge that should be outside
// (even is they are later be filtered out as being too small)
double alreadyManagedLength = 0;
if (unwrappedEnd >= 0) {
// the start of the edge is inside the circle
previousVertex = addSubEdge(previousVertex,
new Vertex(S2Point.ofVector(circle.getPointAt(edgeStart + unwrappedEnd))),
unwrappedEnd, insideList, splitCircle);
alreadyManagedLength = unwrappedEnd;
}
if (precision.compare(arcRelativeStart, length) >= 0) {
// the edge ends while still outside of the circle
if (unwrappedEnd >= 0) {
previousVertex = addSubEdge(previousVertex, end,
length - alreadyManagedLength, outsideList, splitCircle);
} else {
// the edge is entirely outside of the circle
// we don't split anything
outsideList.add(this);
}
} else {
// the edge is long enough to enter inside the circle
previousVertex = addSubEdge(previousVertex,
new Vertex(S2Point.ofVector(circle.getPointAt(edgeStart + arcRelativeStart))),
arcRelativeStart - alreadyManagedLength, outsideList, splitCircle);
alreadyManagedLength = arcRelativeStart;
if (precision.compare(arcRelativeEnd, length) >= 0) {
// the edge ends while still inside of the circle
previousVertex = addSubEdge(previousVertex, end,
length - alreadyManagedLength, insideList, splitCircle);
} else {
// the edge is long enough to exit outside of the circle
previousVertex = addSubEdge(previousVertex,
new Vertex(S2Point.ofVector(circle.getPointAt(edgeStart + arcRelativeStart))),
arcRelativeStart - alreadyManagedLength, insideList, splitCircle);
alreadyManagedLength = arcRelativeStart;
previousVertex = addSubEdge(previousVertex, end,
length - alreadyManagedLength, outsideList, splitCircle);
}
}
}
}
/** Add a sub-edge to a list if long enough.
* <p>
* If the length of the sub-edge to add is smaller than the {@link Circle#getTolerance()}
* tolerance of the support circle, it will be ignored.
* </p>
* @param subStart start of the sub-edge
* @param subEnd end of the sub-edge
* @param subLength length of the sub-edge
* @param splitCircle circle splitting the edge in several parts
* @param list list where to put the sub-edge
* @return end vertex of the edge ({@code subEnd} if the edge was long enough and really
* added, {@code subStart} if the edge was too small and therefore ignored)
*/
private Vertex addSubEdge(final Vertex subStart, final Vertex subEnd, final double subLength,
final List<Edge> list, final Circle splitCircle) {
if (circle.getPrecision().eqZero(subLength)) {
// the edge is too short, we ignore it
return subStart;
}
// really add the edge
subEnd.bindWith(splitCircle);
final Edge edge = new Edge(subStart, subEnd, subLength, circle);
list.add(edge);
return subEnd;
}
}