/*
* This file is part of the Trickl Open Source Libraries.
*
* Trickl Open Source Libraries - http://open.trickl.com/
*
* Copyright (C) 2011 Tim Gee.
*
* Trickl Open Source Libraries are free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Trickl Open Source Libraries are distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this project. If not, see <http://www.gnu.org/licenses/>.
*/
package com.trickl.graph.planar;
import com.trickl.graph.edges.UndirectedEdge;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import org.jgrapht.EdgeFactory;
public class MaximalPlanar<V, E> {
private static class TriangulationVisitor<V, E> implements PlanarFaceTraversalVisitor<V, E> {
private class Detail<V> {
int marked;
int degreeSize;
}
private final Map<V, Detail<V>> vertexDetails;
private final LinkedList<V> verticesOnFace;
private int timestamp = 0;
private final PlanarGraph<V, E> graph;
private final boolean addEdges;
private final boolean checkInteriorOnly;
private final Set<E> missingEdges;
TriangulationVisitor(PlanarGraph<V, E> graph, boolean addEdges, boolean checkInteriorOnly) {
this.graph = graph;
this.addEdges = addEdges;
this.checkInteriorOnly = checkInteriorOnly;
this.vertexDetails = new HashMap<>();
this.verticesOnFace = new LinkedList<>();
this.missingEdges = new HashSet<>();
graph.vertexSet().stream().forEach((vertex) -> {
Detail<V> detail = new Detail<>();
detail.degreeSize = graph.edgesOf(vertex).size();
vertexDetails.put(vertex, detail);
});
}
@Override
public void nextVertex(V vertex) {
if (!verticesOnFace.isEmpty() &&
(verticesOnFace.getFirst().equals(vertex) ||
verticesOnFace.getLast().equals(vertex))) return;
verticesOnFace.add(vertex);
}
@Override
public void endFace(V source, V target) {
++timestamp;
if (verticesOnFace.size() <= 3) {
// At most three vertices on this face - don't need to triangulate
verticesOnFace.clear();
return;
}
if (checkInteriorOnly && graph.isBoundary(source, target)) {
// Do not triangulate the outer face
return;
}
// Find vertex on face of minimum degree
int minDegree = graph.vertexSet().size();
V minDegreeVertex = null;
for (V vertex : verticesOnFace) {
Detail<V> detail = vertexDetails.get(vertex);
if (detail.degreeSize < minDegree) {
minDegreeVertex = vertex;
minDegree = detail.degreeSize;
}
}
// Put this vertex first in the list
Collections.rotate(verticesOnFace, -verticesOnFace.indexOf(minDegreeVertex));
// Mark all of the min degree vertex's neighbours
for (E edge : graph.edgesOf(minDegreeVertex)) {
V vertex = graph.getEdgeSource(edge).equals(minDegreeVertex) ?
graph.getEdgeTarget(edge) :
graph.getEdgeSource(edge);
vertexDetails.get(vertex).marked = timestamp;
}
// The iterator manipulations on the next two lines are safe because
// verticesOnFace.size() > 3 (from the first test in this function)
int markedNeighbourIndex = -1;
for (int index = 2; index < verticesOnFace.size() - 1; ++index)
{
V vertex = verticesOnFace.get(index);
if (vertexDetails.get(vertex).marked == timestamp) {
markedNeighbourIndex = index;
}
}
if (markedNeighbourIndex < 0) {
addEdgeRange(verticesOnFace.get(0),
verticesOnFace.get(verticesOnFace.size() - 1),
verticesOnFace.subList(2, verticesOnFace.size() - 1));
}
else {
addEdgeRange(verticesOnFace.get(1),
verticesOnFace.get(0),
verticesOnFace.subList(markedNeighbourIndex + 1, verticesOnFace.size()));
addEdgeRange(verticesOnFace.get(markedNeighbourIndex + 1),
verticesOnFace.get(markedNeighbourIndex),
verticesOnFace.subList(2, markedNeighbourIndex));
}
// Reset for the next face
verticesOnFace.clear();
}
@Override
public void beginTraversal() {
}
@Override
public void nextEdge(V source, V target) {
}
@Override
public void beginFace(V source, V target) {
}
@Override
public void endTraversal() {
}
public Set<E> getMissingEdges() {
return missingEdges;
}
private void addEdgeRange(V source, V before, List<V> targets) {
for (V target : targets) {
E edge = graph.getEdgeFactory().createEdge(source, target);
if (addEdges) {
graph.addEdge(source, target, before, null, edge);
}
missingEdges.add(edge);
vertexDetails.get(source).degreeSize++;
vertexDetails.get(target).degreeSize++;
}
}
};
public MaximalPlanar() {
}
public Set<E> getMaximalPlanarEdgeDeficit(PlanarGraph<V, E> graph) {
return traverse(graph, false, false);
}
public Set<E> getInteriorTriangulatedEdgeDeficit(PlanarGraph<V, E> graph) {
return traverse(graph, false, true);
}
public boolean isMaximalPlanar(PlanarGraph<V, E> graph) {
return getMaximalPlanarEdgeDeficit(graph).isEmpty();
}
public boolean isInteriorTriangulated(PlanarGraph<V, E> graph) {
return getInteriorTriangulatedEdgeDeficit(graph).isEmpty();
}
public Set<E> makeMaximalPlanar(PlanarGraph<V, E> graph) {
return traverse(graph, true, false);
}
private Set<E> traverse(PlanarGraph<V, E> graph, boolean addEdges, boolean checkInteriorOnly) {
BreadthFirstPlanarFaceTraversal<V, E> planarFaceTraversal = new BreadthFirstPlanarFaceTraversal<>(graph);
TriangulationVisitor<V, E> triangulationVisitor = new TriangulationVisitor(graph, addEdges, checkInteriorOnly);
planarFaceTraversal.traverse(triangulationVisitor);
return triangulationVisitor.getMissingEdges();
}
}