Commit 1275160f authored by mey's avatar mey
Browse files

implemented customizable edge handling when converting potential to flow maps

added EdgeHandler for handling map edges
added EdgeHandledPotentialMap with getter for edge handler
ConvolvingPotentialMap.java
    implements EdgeHandledPotentialMap and returns the one from its convolveOp
FlowFromPotentialsMap.java
    handles edges according to map's edge handler
        default to extend instead of returning zero potentials
    added test for custom edge handler
PotentialMap.java
    added java 8 TODO
SimplePotentialMap.java
    implements EdgeHandledPotentialMap
    added field edgeHandler and accessor with value set by constructor
ConvolveOp.java
    uses newly added EdgeHandler instead of internal EdgeHint
    updated test
Double2DCloseTo.java
    added default max error and factory method using it
renamed TestConstantPathfindingMap to TestPathfindingMap
    updated TestConstantFlowMap
parent 13e175cf
......@@ -17,8 +17,8 @@ import sim.portrayal.portrayable.ProvidesPortrayable;
* @author mey
*
*/
public class ConvolvingPotentialMap extends AbstractDynamicMap
implements GridBackedPotentialMap, ProvidesPortrayable<FieldPortrayable<DoubleGrid2D>>, ProvidesInspector {
public class ConvolvingPotentialMap extends AbstractDynamicMap implements GridBackedPotentialMap,
EdgeHandledPotentialMap, ProvidesPortrayable<FieldPortrayable<DoubleGrid2D>>, ProvidesInspector {
private static final long serialVersionUID = 1L;
private final DoubleGrid2D mapGrid;
......@@ -26,7 +26,8 @@ public class ConvolvingPotentialMap extends AbstractDynamicMap
private final DoubleGrid2D src;
/**
* Constructs a new ConvolvingPotentialsMap.
* Constructs a new {@link ConvolvingPotentialMap} with default
* {@link EdgeHandler}.
*
* @param convolveOp
* the {@link ConvolveOp} to be used
......@@ -62,6 +63,11 @@ public class ConvolvingPotentialMap extends AbstractDynamicMap
mapGrid.set(x, y, convolveOp.filter(x, y, src));
}
@Override
public EdgeHandler getEdgeHandler() {
return convolveOp.getEdgeHandler();
}
/**
* Returns the field portrayable.<br>
* <b>NOTE:</b> This displays the field as is, including not-updated dirty
......
package de.zmt.pathfinding;
interface EdgeHandledPotentialMap extends PotentialMap {
/**
* Returns a handler for access to locations beyond map boundaries.
*
* @return the edge handler
*/
EdgeHandler getEdgeHandler();
}
package de.zmt.pathfinding;
import de.zmt.util.MathUtil;
import sim.field.grid.DoubleGrid2D;
/**
* Handler for edges of potential maps or their raw grid data. The default is to
* use the nearest value within boundaries. Instances are immutable.
*
* @author mey
*
*/
public class EdgeHandler {
private static final EdgeHandler DEFAULT_EDGE_HANDLER = new EdgeHandler(Hint.EXTEND, 0);
private final Hint hint;
private final double value;
/**
* Returns default {@link EdgeHandler} with {@link Hint#EXTEND}.
*
* @return the default edge handler
*/
public static EdgeHandler getDefault() {
return DEFAULT_EDGE_HANDLER;
}
/**
* Constructs a new {@link EdgeHandler} with {@link Hint#CUSTOM}.
*
* @param value
*/
public EdgeHandler(double value) {
this(Hint.CUSTOM, value);
}
private EdgeHandler(Hint hint, double value) {
super();
this.hint = hint;
this.value = value;
}
/**
* Gets value from grid at given position and handle locations beyond
* boundaries according to this {@link EdgeHandler}.
*
* @param grid
* @param x
* @param y
* @return the value from grid according to this object
*/
public double getValue(DoubleGrid2D grid, int x, int y) {
switch (hint) {
case EXTEND:
return grid.get(MathUtil.clamp(x, 0, grid.getWidth() - 1), MathUtil.clamp(y, 0, grid.getHeight() - 1));
case CUSTOM:
if (x < 0 || x >= grid.getWidth() || y < 0 || y >= grid.getHeight()) {
return value;
}
return grid.get(x, y);
default:
throw new UnsupportedOperationException(hint + " not implemented.");
}
}
/**
* Gets value from map at given position and handle locations beyond
* boundaries according to this {@link EdgeHandler}.
*
* @param map
* @param x
* @param y
* @return the value from map according to this object
*/
public double getValue(PotentialMap map, int x, int y) {
switch (hint) {
case EXTEND:
return map.obtainPotential(MathUtil.clamp(x, 0, map.getWidth() - 1),
MathUtil.clamp(y, 0, map.getHeight() - 1));
case CUSTOM:
if (x < 0 || x >= map.getWidth() || y < 0 || y >= map.getHeight()) {
return value;
}
return map.obtainPotential(x, y);
default:
throw new UnsupportedOperationException(hint + " not implemented.");
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((hint == null) ? 0 : hint.hashCode());
// only consider value if valid
if (hint == Hint.CUSTOM) {
long temp;
temp = Double.doubleToLongBits(value);
result = prime * result + (int) (temp ^ (temp >>> 32));
}
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
EdgeHandler other = (EdgeHandler) obj;
if (hint != other.hint) {
return false;
}
// only consider value if valid
if (hint == Hint.CUSTOM && Double.doubleToLongBits(value) != Double.doubleToLongBits(other.value)) {
return false;
}
return true;
}
@Override
public String toString() {
if (hint == Hint.EXTEND) {
return getClass().getSimpleName() + "[condition=" + hint + ", value=" + value + "]";
}
return getClass().getSimpleName() + "[condition=" + hint + "]";
}
/**
* The hint for handling edges.
*
* @author mey
*
*/
private static enum Hint {
/** The nearest value within boundaries is used. */
EXTEND,
/** A custom value is used. */
CUSTOM
}
}
\ No newline at end of file
......@@ -99,22 +99,22 @@ public class FlowFromPotentialsMap extends DerivedFlowMap<PotentialMap> {
// sum potentials for every neighbor
for (PotentialMap map : getUnderlyingMaps()) {
eastSum += obtainWeightedPotentialSafe(x + 1, y, map);
southSum += obtainWeightedPotentialSafe(x, y + 1, map);
westSum += obtainWeightedPotentialSafe(x - 1, y, map);
northSum += obtainWeightedPotentialSafe(x, y - 1, map);
southEastSum += obtainWeightedPotentialSafe(x + 1, y + 1, map);
southWestSum += obtainWeightedPotentialSafe(x - 1, y + 1, map);
northWestSum += obtainWeightedPotentialSafe(x - 1, y - 1, map);
northEastSum += obtainWeightedPotentialSafe(x + 1, y - 1, map);
eastSum += obtainWeightedPotentialSafe(map, x + 1, y);
southSum += obtainWeightedPotentialSafe(map, x, y + 1);
westSum += obtainWeightedPotentialSafe(map, x - 1, y);
northSum += obtainWeightedPotentialSafe(map, x, y - 1);
southEastSum += obtainWeightedPotentialSafe(map, x + 1, y + 1);
southWestSum += obtainWeightedPotentialSafe(map, x - 1, y + 1);
northWestSum += obtainWeightedPotentialSafe(map, x - 1, y - 1);
northEastSum += obtainWeightedPotentialSafe(map, x + 1, y - 1);
}
// sum all directions weighted by their potential sum
Double2D sumVector = EAST.multiply(eastSum).add(SOUTH.multiply(southSum))
.add(WEST.multiply(westSum)).add(NORTH.multiply(northSum))
.add(SOUTHEAST.multiply(southEastSum)).add(SOUTHWEST.multiply(southWestSum))
.add(NORTHWEST.multiply(northWestSum)).add(NORTHEAST.multiply(northEastSum));
Double2D sumVector = EAST.multiply(eastSum).add(SOUTH.multiply(southSum)).add(WEST.multiply(westSum))
.add(NORTH.multiply(northSum)).add(SOUTHEAST.multiply(southEastSum))
.add(SOUTHWEST.multiply(southWestSum)).add(NORTHWEST.multiply(northWestSum))
.add(NORTHEAST.multiply(northEastSum));
// if neutral direction: return it
if (sumVector.equals(NEUTRAL)) {
......@@ -125,15 +125,19 @@ public class FlowFromPotentialsMap extends DerivedFlowMap<PotentialMap> {
}
/**
* Returns the weighted potential from given map and handle edges the map's
* {@link EdgeHandler} or the default one.
*
* @param map
* @param x
* @param y
* @param map
* @return weighted potential from given map or '0' if out of bounds
* @return weighted potential from given map
*/
private double obtainWeightedPotentialSafe(int x, int y, PotentialMap map) {
if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
return 0;
private double obtainWeightedPotentialSafe(PotentialMap map, int x, int y) {
EdgeHandler edgeHandler = EdgeHandler.getDefault();
if (map instanceof EdgeHandledPotentialMap) {
edgeHandler = ((EdgeHandledPotentialMap) map).getEdgeHandler();
}
return map.obtainPotential(x, y) * getWeight(map);
return edgeHandler.getValue(map, x, y) * getWeight(map);
}
}
......@@ -13,6 +13,8 @@ import sim.portrayal.portrayable.ProvidesPortrayable;
* @author mey
*
*/
// TODO when in java 8, make default methods getEdgeHint, getName
// and delete interfaces
public interface PotentialMap extends PathfindingMap, ProvidesPortrayable<FieldPortrayable<DoubleGrid2D>> {
/** Value for maximum repulsion. */
public static final double MAX_REPULSIVE_VALUE = -1;
......
......@@ -13,22 +13,24 @@ import sim.portrayal.inspector.ProvidesInspector;
*
*/
public class SimplePotentialMap extends SimplePathfindingMap<DoubleGrid2D>
implements GridBackedPotentialMap, ProvidesInspector {
implements GridBackedPotentialMap, EdgeHandledPotentialMap, ProvidesInspector {
private static final long serialVersionUID = 1L;
private final EdgeHandler edgeHandler;
/**
* Constructs a new {@code SimplePotentialMap} backed by given grid.
*
* @param mapGrid
* grid that backs this map
* the grid that backs this map
*/
public SimplePotentialMap(DoubleGrid2D mapGrid) {
super(mapGrid);
this(mapGrid, EdgeHandler.getDefault());
}
/**
* Constructs a new {@code SimplePotentialMap} backed by a new grid
* containing given values.
* containing given values. To be used in tests.
*
* @param values
*/
......@@ -36,11 +38,30 @@ public class SimplePotentialMap extends SimplePathfindingMap<DoubleGrid2D>
this(new DoubleGrid2D(values));
}
/**
* Cosntructs a new {@link SimplePotentialMap} with given
* {@link EdgeHandler}.
*
* @param mapGrid
* the grid that backs the map
* @param edgeHandler
* the edge handler
*/
public SimplePotentialMap(DoubleGrid2D mapGrid, EdgeHandler edgeHandler) {
super(mapGrid);
this.edgeHandler = edgeHandler;
}
@Override
public double obtainPotential(int x, int y) {
return getMapGrid().get(x, y);
}
@Override
public EdgeHandler getEdgeHandler() {
return edgeHandler;
}
@Override
public Inspector provideInspector(GUIState state, String name) {
return new PotentialMapInspector(state, this);
......
......@@ -2,6 +2,7 @@ package de.zmt.pathfinding.filter;
import java.io.Serializable;
import de.zmt.pathfinding.EdgeHandler;
import sim.field.grid.BooleanGrid;
import sim.field.grid.DoubleGrid2D;
......@@ -23,37 +24,34 @@ public class ConvolveOp implements Serializable {
private static final long serialVersionUID = 1L;
private final Kernel kernel;
private final EdgeHint edgeHint;
private final double value;
private final EdgeHandler edgeHandler;
/**
* Constructs a new {@link ConvolveOp}. Accessed values beyond grid
* boundaries are extended.
* Constructs a new {@link ConvolveOp} with {@link EdgeHandler#getDefault()}
* .
*
* @param kernel
* the kernel used for the convolve operation
* the kernel to be used
*/
public ConvolveOp(Kernel kernel) {
super();
this.kernel = kernel;
this.edgeHint = EdgeHint.EXTEND;
this.value = 0;
this.edgeHandler = EdgeHandler.getDefault();
}
/**
* Constructs a new {@link ConvolveOp}. For accessed values beyond grid
* boundaries the given value is used.
* Constructs a new {@link ConvolveOp}. For handling grid edges the given
* {@link EdgeHandler} is used.
*
* @param kernel
* the kernel used for the convolve operation
* @param value
* the value used when beyond grid boundaries
* @param edgeHandler
* the edge hanlder to be used
*/
public ConvolveOp(Kernel kernel, double value) {
public ConvolveOp(Kernel kernel, EdgeHandler edgeHandler) {
super();
this.kernel = kernel;
this.edgeHint = EdgeHint.CUSTOM;
this.value = value;
this.edgeHandler = edgeHandler;
}
/**
......@@ -141,59 +139,23 @@ public class ConvolveOp implements Serializable {
int fieldY = y + j - kernel.getyOrigin();
// extend the field on borders
double value = getFromGrid(src, fieldX, fieldY);
double value = edgeHandler.getValue(src, fieldX, fieldY);
result += value * weight;
}
}
return result;
}
/**
* Gets value from grid at given position. If position is beyond grid
* boundaries a value is returned according to {@link #edgeHint}.
*
* @param grid
* @param fieldX
* @param fieldY
* @return the value from grid or according to {@link #edgeHint}
*/
private final double getFromGrid(DoubleGrid2D grid, int fieldX, int fieldY) {
switch (edgeHint) {
case EXTEND:
return grid.get(clamp(fieldX, 0, grid.getWidth() - 1), clamp(fieldY, 0, grid.getHeight() - 1));
case CUSTOM:
if (fieldX < 0 || fieldX >= grid.getWidth() || fieldY < 0 || fieldY >= grid.getHeight()) {
return value;
}
return grid.get(fieldX, fieldY);
default:
throw new UnsupportedOperationException(edgeHint + " not implemented.");
}
}
private static int clamp(int val, int min, int max) {
return Math.max(min, Math.min(max, val));
}
public Kernel getKernel() {
return kernel;
}
public EdgeHandler getEdgeHandler() {
return edgeHandler;
}
@Override
public String toString() {
return getClass().getSimpleName() + "[kernel=" + kernel + "]";
}
/**
* Hint for handling access to values beyond grid boundaries.
*
* @author mey
*
*/
private enum EdgeHint {
/** The nearest value within grid boundaries is used. */
EXTEND,
/** A custom value is used. */
CUSTOM
}
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ import org.hamcrest.Double2DCloseTo;
import org.junit.Before;
import org.junit.Test;
import sim.field.grid.DoubleGrid2D;
import sim.util.Double2D;
public class FlowFromPotentialsMapTest {
......@@ -55,6 +56,21 @@ public class FlowFromPotentialsMapTest {
obtainDirectionAtMapCenter(), is(SOUTH));
}
@Test
public void obtainDirectionOnCustomEdgeHint() {
map = new FlowFromPotentialsMap(
new SimplePotentialMap(new DoubleGrid2D(MAP_SIZE, MAP_SIZE, 0), new EdgeHandler(1)));
assertThat(map.obtainDirection(0, 0), is(Double2DCloseTo.closeTo(NORTHWEST)));
assertThat(map.obtainDirection(0, 1), is(Double2DCloseTo.closeTo(WEST)));
assertThat(map.obtainDirection(0, 2), is(Double2DCloseTo.closeTo(SOUTHWEST)));
assertThat(map.obtainDirection(1, 0), is(Double2DCloseTo.closeTo(NORTH)));
assertThat(map.obtainDirection(1, 1), is(Double2DCloseTo.closeTo(NEUTRAL)));
assertThat(map.obtainDirection(1, 2), is(Double2DCloseTo.closeTo(SOUTH)));
assertThat(map.obtainDirection(2, 0), is(Double2DCloseTo.closeTo(NORTHEAST)));
assertThat(map.obtainDirection(2, 1), is(Double2DCloseTo.closeTo(EAST)));
assertThat(map.obtainDirection(2, 2), is(Double2DCloseTo.closeTo(SOUTHEAST)));
}
@Test
public void obtainDirectionOnMulti() {
map.addMap(createDirectedMap(SOUTH));
......
......@@ -4,7 +4,7 @@ import sim.field.grid.ObjectGrid2D;
import sim.portrayal.portrayable.FieldPortrayable;
import sim.util.Double2D;
class TestConstantFlowMap extends TestConstantPathfindingMap implements FlowMap {
class TestConstantFlowMap extends TestPathfindingMap implements FlowMap {
private final Double2D value;
public TestConstantFlowMap(int width, int height, Double2D value) {
......
......@@ -6,6 +6,7 @@ import static org.junit.Assert.assertThat;
import org.junit.Test;
import de.zmt.pathfinding.EdgeHandler;
import sim.field.grid.BooleanGrid;
import sim.field.grid.DoubleGrid2D;
......@@ -97,6 +98,7 @@ public class ConvolveOpTest {
@Test
public void filterConstantOnCustomValue() {
// no change in constant kernel with average filter
assertThat(new ConvolveOp(KERNEL_CONSTANT, -1).filter(SINGULAR_GRID).get(0, 0), is(closeTo(-8 + 1, 1E-15d)));
assertThat(new ConvolveOp(KERNEL_CONSTANT, new EdgeHandler(-1)).filter(SINGULAR_GRID).get(0, 0),
is(closeTo(-8 + 1, 1E-15d)));
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment