/*
* GeoGrid.java
*
*/
package ika.geo;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
/**
* @author Bernhard Jenny, Institute of Cartography, ETH Zurich.
*/
public class GeoBinaryGrid extends AbstractRaster {
public class BinaryImage {
public BufferedImage image;
public Graphics2D g2d;
public BinaryImage(BufferedImage image, Graphics2D g2d) {
this.image = image;
this.g2d = g2d;
}
}
/**
* grid to accumulate rasterized GeoPaths
*/
private BinaryImage grid;
/**
* temporarily used grid to rasterize a single GeoPath and to test
* overlaps with the accumulated paths in grid
*/
private BinaryImage rasterizerGrid;
/** Creates a new instance of GeoBinaryGrid */
public GeoBinaryGrid(int cols, int rows, double west, double north, double cellSize) {
this.cellSize = cellSize;
this.west = west;
this.north = north;
this.grid = initGrid(cols, rows);
}
private BinaryImage initGrid(int w, int h) {
// create binary image with black as transparent background color
// with a transparent color, paths rasterized to rasterizerGrid can
// be copied to grid to accumulate all paths without overwriting what
// has been accumulated before.
final byte ff = (byte) 0xff;
final byte[] r = {0, ff};
final byte[] g = {0, ff};
final byte[] b = {0, ff};
IndexColorModel cm = new IndexColorModel(1, 2, r, g, b, 0);
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_BINARY, cm);
Graphics2D g2d = bi.createGraphics();
g2d.setColor(Color.WHITE);
g2d.setBackground(Color.BLACK);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
// transform from Geo space to user space, last transformation first!
/*
* Transformation:
* x_ = (x-west)*scale;
* y_ = (north-y)*scale = (y-north)*(-scale);
*/
final double scale = 1. / cellSize;
g2d.scale(scale, -scale);
g2d.translate(-west, -north);
return new BinaryImage(bi, g2d);
}
public void rasterize(GeoPath geoPath) {
rasterize(geoPath, this.grid.g2d);
}
private void rasterize(GeoPath geoPath, Graphics2D g2d) {
VectorSymbol vectorSymbol = geoPath.getVectorSymbol();
// render path
GeneralPath path = new GeneralPath();
path.append(geoPath.toPathIterator(null), false);
if (vectorSymbol.isFilled()) {
g2d.fill(path);
}
if (vectorSymbol.isStroked()) {
g2d.setStroke(new BasicStroke(vectorSymbol.getStrokeWidth()));
g2d.draw(path);
}
}
public boolean isAddingCausingOverlay(GeoPath geoPath, boolean addIfNotOverlaying) {
int w = this.grid.image.getWidth();
int h = this.grid.image.getHeight();
this.rasterizerGrid = initGrid(w, h);
rasterize(geoPath, rasterizerGrid.g2d);
Rectangle2D objBounds = geoPath.getBounds2D(GeoObject.UNDEFINED_SCALE);
final double objWest = objBounds.getMinX();
final double objEast = objBounds.getMaxX();
final double objSouth = objBounds.getMinY();
final double objNorth = objBounds.getMaxY();
VectorSymbol vectorSymbol = geoPath.getVectorSymbol();
final double lineWidth = vectorSymbol != null ? vectorSymbol.getStrokeWidth() * 2. : 0;
int firstCol = (int) ((objWest - lineWidth - this.west) / cellSize);
int firstRow = (int) ((objNorth - lineWidth - this.getNorth()) / cellSize);
firstCol = Math.max(0, firstCol);
firstRow = Math.max(0, firstRow);
int lastCol = (int) Math.ceil((objEast + lineWidth - this.west) / cellSize);
int lastRow = (int) Math.ceil((this.getNorth() - objSouth + lineWidth) / cellSize);
lastCol = Math.min(grid.image.getWidth(), lastCol);
lastRow = Math.min(grid.image.getHeight(), lastRow);
Raster r1 = rasterizerGrid.image.getRaster();
Raster r2 = grid.image.getRaster();
for (int r = firstRow; r < lastRow; ++r) {
for (int c = firstCol; c < lastCol; ++c) {
if (r1.getSample(c, r, 0) == 1 && r2.getSample(c, r, 0) == 1) {
return true;
}
}
}
if (addIfNotOverlaying) {
AffineTransform trans = this.grid.g2d.getTransform();
this.grid.g2d.setTransform(new AffineTransform());
this.grid.g2d.drawImage(rasterizerGrid.image, 0, 0, null);
this.grid.g2d.setTransform(trans);
}
return false;
}
public final boolean contains(double x, double y) {
int c = (int) Math.floor((x - west) / cellSize);
int r = (int) Math.floor((north - y) / cellSize);
int w = this.grid.image.getWidth();
int h = this.grid.image.getHeight();
return r >= 0 && c >= 0 && r < h && c < w && grid.image.getRaster().getSample(c, r, 0) == 1;
}
@Override
public void drawNormalState(RenderParams rp) {
GeoPath.newRect(getBounds2D(rp.scale)).drawNormalState(rp);
}
@Override
public void drawSelectedState(RenderParams rp) {
if (!this.isSelected()) {
return;
}
GeoPath.newRect(getBounds2D(rp.scale)).drawNormalState(rp);
}
@Override
public java.awt.geom.Rectangle2D getBounds2D(double scale) {
int w = this.grid.image.getWidth();
int h = this.grid.image.getHeight();
final double width = this.cellSize * (w - 1);
final double height = this.cellSize * (h - 1);
final double x = this.west;
final double y = this.north - height;
return new Rectangle2D.Double(x, y, width, height);
}
@Override
public int getCols() {
if (grid == null || grid.image == null) {
return 0;
}
return grid.image.getRaster().getWidth();
}
@Override
public int getRows() {
if (grid == null || grid.image == null) {
return 0;
}
return this.grid.image.getRaster().getHeight();
}
@Override
public double getSouth() {
return this.north - (getRows() - 1) * this.cellSize;
}
@Override
public double getEast() {
return this.west + (getCols() - 1) * this.cellSize;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
sb.append("\nDimension: ");
sb.append(getCols()).append(" x ").append(getRows());
sb.append("\nCell size: ");
sb.append(getCellSize());
sb.append("\nWest: ");
sb.append(getWest());
sb.append("\nNorth: ");
sb.append(getNorth());
return sb.toString();
}
@Override
public boolean isPointOnSymbol(Point2D point, double tolDist, double scale) {
return this.contains(point.getX(), point.getY());
}
@Override
public boolean isIntersectedByRectangle(Rectangle2D rect, double scale) {
Rectangle2D bbox = getBounds2D(GeoObject.UNDEFINED_SCALE);
return ika.utils.GeometryUtils.rectanglesIntersect(rect, bbox);
}
@Override
public void transform(AffineTransform affineTransform) {
throw new UnsupportedOperationException("Not supported yet.");
}
}