/*
* 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 java.util.*;
import org.jgrapht.Graphs;
public class MaximalPlanarCanonicalOrdering<V, E> implements PlanarCanonicalOrdering<V, E>{
private enum State {
PROCESSED(0),
UNPROCESSED(1),
ONE_NEIGHBOR_PROCESSED(2),
READY(3);
private final int value;
private State(int value) {
this.value = value;
}
public int getValue() {
return value;
}
};
@Override
public List<V> getOrder(PlanarGraph<V, E> graph,
V first) {
if (graph == null) {
throw new NullPointerException();
}
Set<V> vertices = graph.vertexSet();
List<V> ordering = new ArrayList<V>(vertices.size());
if (first == null) {
first = graph.getBoundary().getSource();
}
if (vertices.isEmpty()) {
return ordering;
}
else if (vertices.size() == 1) {
ordering.add(first);
return ordering;
}
V second = PlanarGraphs.getPrevVertexOnBoundary(graph, first);
Queue<V> readyQueue = new LinkedList<V>();
Map<V, V> processedNeighbors = new HashMap<V, V>(graph.vertexSet().size());
Map<V, Integer> status = new HashMap<V, Integer>();
for (V vertex : graph.vertexSet()) {
status.put(vertex, State.UNPROCESSED.getValue());
}
readyQueue.add(first);
status.put(first, State.READY.getValue());
readyQueue.add(second);
status.put(second, State.READY.getValue());
while (!readyQueue.isEmpty()) {
V u = readyQueue.poll();
if (status.get(u) != State.READY.getValue() && !u.equals(second)) {
continue;
}
List<E> embeddingList = new ArrayList<E>(graph.edgesOf(u));
int priorEdgeItr = embeddingList.size() - 1;
// Skip self loops
while (graph.getEdgeSource(embeddingList.get(priorEdgeItr)).
equals(graph.getEdgeTarget(embeddingList.get(priorEdgeItr)))) {
--priorEdgeItr;
}
for (int edgeItr = 0, edgeEndItr = embeddingList.size(); edgeItr != edgeEndItr; ++edgeItr) {
E edge = embeddingList.get(edgeItr);
int nextEdgeItr = (edgeItr + 1) % embeddingList.size();
V v = graph.getEdgeSource(edge).equals(u) ? graph.getEdgeTarget(edge) : graph.getEdgeSource(edge);
V priorVertex = Graphs.getOppositeVertex(graph, embeddingList.get(priorEdgeItr), u);
V nextVertex = Graphs.getOppositeVertex(graph, embeddingList.get(nextEdgeItr), u);
// Need priorVertex, u, v, and nextVertex to all be
// distinct. This is possible, since the input graph is
// triangulated. It'll be true all the time in a simple
// graph, but loops and parallel edges cause some complications.
if (priorVertex.equals(v) || priorVertex.equals(u)) {
priorEdgeItr = edgeItr;
continue;
}
// Skip any self-loops
if (u.equals(v)) {
continue;
}
// Move next_edge_itr (and next_vertex) forwards
// past any loops or parallel edges
while (nextVertex.equals(v) || nextVertex.equals(u)) {
nextEdgeItr = (nextEdgeItr + 1) % embeddingList.size();
nextVertex = Graphs.getOppositeVertex(graph, embeddingList.get(nextEdgeItr), u);
}
if (status.get(v) == State.UNPROCESSED.getValue()) {
status.put(v, State.ONE_NEIGHBOR_PROCESSED.getValue());
processedNeighbors.put(v, u);
} else if (status.get(v) == State.ONE_NEIGHBOR_PROCESSED.getValue()) {
V x = processedNeighbors.get(v);
// Are edges (v,u) and (v,x) adjacent in the planar
// embedding? if so, set status[v] = ready. otherwise, set
// status[v] = ready pending.
if ((nextVertex.equals(x)
&& !(first.equals(u) && second.equals(x)))
|| (priorVertex.equals(x)
&& !(first.equals(x) && second.equals(u)))) {
status.put(v, State.READY.getValue());
} else {
status.put(v, State.READY.getValue() + 1);
}
} else if (status.get(v) > State.ONE_NEIGHBOR_PROCESSED.getValue()) {
// check the two edges before and after (v,u) in the planar
// embedding, and update status[v] accordingly
boolean processedPrior = false;
if (status.get(priorVertex) == State.PROCESSED.getValue()) {
processedPrior = true;
}
boolean processedNext = false;
if (status.get(nextVertex) == State.PROCESSED.getValue()) {
processedNext = true;
}
if (!processedPrior && !processedNext) {
status.put(v, status.get(v) + 1);
} else if (processedPrior && processedNext) {
status.put(v, status.get(v) - 1);
}
}
if (status.get(v) == State.READY.getValue()) {
readyQueue.add(v);
}
priorEdgeItr = edgeItr;
}
status.put(u, State.PROCESSED.getValue());
ordering.add(u);
}
return ordering;
}
}