/**
* Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org>
*
* Licensed 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.onebusaway.geospatial.grid;
import org.onebusaway.utility.collections.TreeUnionFind;
import org.onebusaway.utility.collections.TreeUnionFind.Sentry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class BoundaryFactory {
private boolean _pruneAllButCorners = false;
public void setPruneAllButCorners(boolean pruneAllButCorners) {
_pruneAllButCorners = pruneAllButCorners;
}
public <T> List<Boundary> getBoundaries(Grid<T> grid) {
return getBoundaries(grid,grid.getEntries());
}
public <T> List<Boundary> getBoundaries(Grid<T> grid, Iterable<Grid.Entry<T>> entries) {
List<BoundaryContext> contexts = new ArrayList<BoundaryContext>();
Set<BoundaryEdge> closed = new HashSet<BoundaryEdge>();
TreeUnionFind<GridIndex> clusters = new TreeUnionFind<GridIndex>();
for (Grid.Entry<?> entry : entries) {
GridIndex index = entry.getIndex();
for (EDirection direction : EDirection.values()) {
BoundaryEdge edge = new BoundaryEdge(index, direction);
if (!closed.add(edge))
continue;
if (isOpen(grid, index, direction)) {
BoundaryContext context = new BoundaryContext(grid);
exploreBoundary(context, index, direction, true);
BoundaryPath path = context.getPath();
for (int i = 0; i < path.size(); i++)
closed.add(new BoundaryEdge(path.getIndex(i), path.getDirection(i)));
contexts.add(context);
} else {
GridIndex adjacent = getIndex(index, direction);
clusters.union(index, adjacent);
}
}
}
Map<Sentry, Boundary> boundariesByCluster = new HashMap<Sentry, Boundary>();
List<Boundary> boundaries = new ArrayList<Boundary>();
for (BoundaryContext context : contexts) {
BoundaryPath path = context.getPath();
GridIndex first = path.getIndex(0);
Sentry sentry = clusters.find(first);
Boundary boundary = boundariesByCluster.get(sentry);
if (boundary == null) {
boundary = new Boundary();
boundariesByCluster.put(sentry, boundary);
boundaries.add(boundary);
}
boolean outer = context.getTurns() >= 4;
if (_pruneAllButCorners)
path = pruneAllButCorners(path);
if (outer) {
if (boundary.getOuterBoundary() != null)
throw new IllegalStateException("multiple outer boundaries");
boundary.setOuterBoundary(path);
} else {
boundary.addInnerBoundary(path);
}
}
return boundaries;
}
private boolean isOpen(Grid<?> grid, GridIndex index, EDirection direction) {
return !grid.contains(getIndex(index, direction));
}
private GridIndex getIndex(GridIndex index, EDirection direction) {
int x = index.getX();
int y = index.getY();
switch (direction) {
case UP:
y++;
break;
case RIGHT:
x++;
break;
case DOWN:
y--;
break;
case LEFT:
x--;
break;
default:
throw new IllegalStateException();
}
return new GridIndex(x, y);
}
private void exploreBoundary(BoundaryContext context, GridIndex index,
EDirection direction, boolean isOpen) {
if (context.isBackToStart(index, direction))
return;
if (isOpen) {
context.addEdge(index, direction);
EDirection nextDirection = direction.getNext();
context.rightTurn();
boolean open = isOpen(context.getGrid(), index, nextDirection);
exploreBoundary(context, index, nextDirection, open);
} else {
GridIndex nextIndex = getIndex(index, direction);
EDirection nextDirection = direction.getPrev();
context.leftTurn();
boolean open = isOpen(context.getGrid(), nextIndex, nextDirection);
exploreBoundary(context, nextIndex, nextDirection, open);
}
}
private BoundaryPath pruneAllButCorners(BoundaryPath path) {
for (int offset = 0; offset < path.size(); offset++) {
int pre = offset - 1;
if (pre < 0)
pre += path.size();
if (!path.getDirection(pre).equals(path.getDirection(offset))) {
EDirection prev = null;
BoundaryPath reduced = new BoundaryPath();
for (int i = 0; i < path.size(); i++) {
int index = (i + offset) % path.size();
GridIndex gridIndex = path.getIndex(index);
EDirection direction = path.getDirection(index);
if (prev == null || !prev.equals(direction))
reduced.addEdge(gridIndex, direction);
prev = direction;
}
return reduced;
}
}
throw new IllegalStateException();
}
/*****************************************************************************
*
****************************************************************************/
private static class BoundaryContext {
private BoundaryPath _path = new BoundaryPath();
private int _turns = 0;
private Grid<?> _grid;
public BoundaryContext(Grid<?> grid) {
_grid = grid;
}
public Grid<?> getGrid() {
return _grid;
}
public BoundaryPath getPath() {
return _path;
}
public void addEdge(GridIndex index, EDirection direction) {
_path.addEdge(index, direction);
}
public boolean isBackToStart(GridIndex index, EDirection direction) {
if (_path.isEmpty())
return false;
GridIndex firstIndex = _path.getIndex(0);
EDirection firstDirection = _path.getDirection(0);
return index.equals(firstIndex) && direction.equals(firstDirection);
}
public int getTurns() {
return _turns;
}
public void leftTurn() {
_turns--;
}
public void rightTurn() {
_turns++;
}
}
private static class BoundaryEdge {
private final GridIndex _index;
private final EDirection _direction;
public BoundaryEdge(GridIndex index, EDirection direction) {
_index = index;
_direction = direction;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((_direction == null) ? 0 : _direction.hashCode());
result = prime * result + ((_index == null) ? 0 : _index.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof BoundaryEdge))
return false;
BoundaryEdge other = (BoundaryEdge) obj;
if (_direction == null) {
if (other._direction != null)
return false;
} else if (!_direction.equals(other._direction))
return false;
if (_index == null) {
if (other._index != null)
return false;
} else if (!_index.equals(other._index))
return false;
return true;
}
}
}