/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is 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
* Lesser General Public License for more details.
*/
package org.geotools.renderer;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.CoordinateSequenceFactory;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
/**
* The screenmap is a packed bitmap of the screen, one bit per pixels. It can be used to avoid
* rendering a lot of very small features in the same pixel.
*
* <p>
* The screenmap can be used two ways:
* <ul>
* <li>By working directly against the pixels using {@link #checkAndSet(int, int)}</li>
* <li>By working with real world envelopes using {@link #checkAndSet(Envelope)}, in that case the
* full math transform from data to screen, and the generalization spans must be set</li>
* </ul>
*
* When checkAndSet returns false the geometry sits in a pixel that has been already populated
* and can be skipped.
*
* @author jeichar
* @author Andrea Aime - OpenGeo
*
* @source $URL: http://svn.osgeo.org/geotools/trunk/modules/library/main/src/main/java/org/geotools/renderer/ScreenMap.java $
*/
public class ScreenMap {
double[] point = new double[2];
int[] pixels;
int width;
int height;
private int minx;
private int miny;
MathTransform mt;
double spanX;
double spanY;
public ScreenMap(int x, int y, int width, int height, MathTransform mt) {
this.width = width;
this.height = height;
this.minx = x;
this.miny = y;
int arraySize = ((width * height) / 32) + 1;
pixels = new int[arraySize];
this.mt = mt;
}
public ScreenMap(int x, int y, int width, int height) {
this(x, y, width, height, null);
}
public void setTransform(MathTransform mt) {
this.mt = mt;
}
public boolean checkAndSet(Envelope envelope) throws TransformException {
if (!canSimplify(envelope)) {
return false;
}
point[0] = (envelope.getMinX() + envelope.getMaxX()) / 2;
point[1] = (envelope.getMinY() + envelope.getMaxY()) / 2;
mt.transform(point, 0, point, 0, 1);
int r = (int) point[0];
int c = (int) point[1];
return checkAndSet(r, c);
}
public boolean canSimplify(Envelope envelope) {
return envelope.getWidth() < spanX && envelope.getHeight() < spanY;
}
public void setSpans(double spanX, double spanY) {
this.spanX = spanX;
this.spanY = spanY;
}
/**
* Checks if the geometry should be skipped. If the test returns true it means the geometry
* sits in a pixel that has already been used
*/
public boolean checkAndSet(int x, int y) {
if ((x - minx) < 0 || (x - minx) > width - 1 || (y - miny) < 0 || (y - miny) > height - 1)
return true;
int bit = bit(x - minx, y - miny);
int index = bit / 32;
int offset = bit % 32;
int mask = 1 << offset;
try {
if ((pixels[index] & mask) != 0) {
return true;
} else {
pixels[index] = pixels[index] | mask;
return false;
}
} catch (Exception e) {
return true;
}
}
/**
* Returns true if the pixel at location x,y is set or out of bounds.
*/
public boolean get(int x, int y) {
if ((x - minx) < 0 || (x - minx) > width - 1 || (y - miny) < 0 || (y - miny) > height - 1)
return true;
int bit = bit(x - minx, y - miny);
int index = bit / 32;
int offset = bit % 32;
int mask = 1 << offset;
try {
return ((pixels[index] & mask) != 0) ? true : false;
} catch (Exception e) {
return true;
}
}
private int bit(int x, int y) {
return (width * y) + x;
}
/**
* Returns geometry suitable for rendering the pixel that has just been occupied.
* The geometry is designed to actually fill the pixel
* @param minx
* @param miny
* @param maxx
* @param maxy
* @param geometryFactory
* @param geometryType
* @return
*/
public Geometry getSimplifiedShape(double minx, double miny, double maxx, double maxy,
GeometryFactory geometryFactory, Class geometryType) {
CoordinateSequenceFactory csf = geometryFactory.getCoordinateSequenceFactory();
double midx = (minx + maxx) / 2;
double midy = (miny + maxy) / 2;
double x0 = midx - spanX / 2;
double x1 = midx + spanX / 2;
double y0 = midy - spanY / 2;
double y1 = midy + spanY / 2;
if (Point.class.isAssignableFrom(geometryType)
|| MultiPoint.class.isAssignableFrom(geometryType)) {
CoordinateSequence cs = csf.create(1, 2);
cs.setOrdinate(0, 0, midx);
cs.setOrdinate(0, 1, midy);
if (Point.class.isAssignableFrom(geometryType)) {
// people should not call this method for a point, but... whatever
return geometryFactory.createPoint(cs);
} else {
return geometryFactory.createMultiPoint(new Point[] { geometryFactory
.createPoint(cs) });
}
} else if (LineString.class.isAssignableFrom(geometryType)
|| MultiLineString.class.isAssignableFrom(geometryType)) {
CoordinateSequence cs = csf.create(2, 2);
cs.setOrdinate(0, 0, x0);
cs.setOrdinate(0, 1, y0);
cs.setOrdinate(1, 0, x1);
cs.setOrdinate(1, 1, y1);
if (MultiLineString.class.isAssignableFrom(geometryType)) {
return geometryFactory.createMultiLineString(new LineString[] { geometryFactory
.createLineString(cs) });
} else {
return geometryFactory.createLineString(cs);
}
} else {
CoordinateSequence cs = csf.create(5, 2);
cs.setOrdinate(0, 0, x0);
cs.setOrdinate(0, 1, y0);
cs.setOrdinate(1, 0, x0);
cs.setOrdinate(1, 1, y1);
cs.setOrdinate(2, 0, x1);
cs.setOrdinate(2, 1, y1);
cs.setOrdinate(3, 0, x1);
cs.setOrdinate(3, 1, y0);
cs.setOrdinate(4, 0, x0);
cs.setOrdinate(4, 1, y0);
LinearRing ring = geometryFactory.createLinearRing(cs);
if (MultiPolygon.class.isAssignableFrom(geometryType)) {
return geometryFactory.createMultiPolygon(new Polygon[] { geometryFactory
.createPolygon(ring, null) });
} else {
return geometryFactory.createPolygon(ring, null);
}
}
}
/**
* Sets location at position x,y to the value.
*/
public void set(int x, int y, boolean value) {
if ((x - minx) < 0 || (x - minx) > width - 1 || (y - miny) < 0 || (y - miny) > height - 1)
return;
int bit = bit(x - minx, y - miny);
int index = bit / 32;
int offset = bit % 32;
int mask = 1;
mask = mask << offset;
if (value) {
pixels[index] = pixels[index] | mask;
} else {
int tmp = pixels[index];
tmp = ~tmp;
tmp = (tmp | mask);
tmp = ~tmp;
pixels[index] = tmp;
}
}
}