001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2006, Geotools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.geometry.jts;
017:
018: import java.awt.Rectangle;
019:
020: import org.geotools.referencing.operation.matrix.AffineTransform2D;
021: import org.opengis.referencing.operation.MathTransform;
022: import org.opengis.referencing.operation.TransformException;
023:
024: import com.vividsolutions.jts.geom.Envelope;
025: import com.vividsolutions.jts.geom.Geometry;
026: import com.vividsolutions.jts.geom.GeometryCollection;
027: import com.vividsolutions.jts.geom.LineString;
028: import com.vividsolutions.jts.geom.MultiPoint;
029: import com.vividsolutions.jts.geom.Point;
030: import com.vividsolutions.jts.geom.Polygon;
031:
032: /**
033: * Accepts geometries and collapses all the vertices that will be rendered to
034: * the same pixel.
035: *
036: * @author jeichar
037: * @since 2.1.x
038: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/geometry/jts/Decimator.java $
039: */
040: public final class Decimator {
041:
042: private double spanx = -1;
043:
044: private double spany = -1;
045:
046: /**
047: * djb - noticed that the old way of finding out the decimation is based on
048: * the (0,0) location of the image. This is often wildly unrepresentitive of
049: * the scale of the entire map.
050: *
051: * A better thing to do is to decimate this on a per-shape basis (and use
052: * the shape's center). Another option would be to sample the image at
053: * different locations (say 9) and choose the smallest spanx/spany you find.
054: *
055: * Also, if the xform is an affine Xform, you can be a bit more aggressive
056: * in the decimation. If its not an affine xform (ie. its actually doing a
057: * CRS xform), you may find this is a bit too aggressive due to any number
058: * of mathematical issues.
059: *
060: * This is just a simple method that uses the centre of the given rectangle
061: * instead of (0,0).
062: *
063: * NOTE: this could need more work based on CRS, but the rectangle is in
064: * pixels so it should be fairly immune to all but crazy projections.
065: *
066: *
067: * @param screenToWorld
068: * @param paintArea
069: */
070: public Decimator(MathTransform screenToWorld, Rectangle paintArea) {
071: if (screenToWorld != null) {
072: double[] original = new double[] {
073: paintArea.x + paintArea.width / 2.0,
074: paintArea.y + paintArea.height / 2.0,
075: paintArea.x + paintArea.width / 2.0 + 1,
076: paintArea.y + paintArea.height / 2.0 + 1, };
077: double[] coords = new double[4];
078: try {
079: screenToWorld.transform(original, 0, coords, 0, 2);
080: } catch (TransformException e) {
081: return;
082: }
083: this .spanx = Math.abs(coords[0] - coords[2]) * 0.8;
084: // 0.8 is just so you dont decimate "too much". magic
085: // number.
086: this .spany = Math.abs(coords[1] - coords[3]) * 0.8;
087: } else {
088: this .spanx = 1;
089: this .spany = 1;
090: }
091: }
092:
093: /**
094: * @throws TransformException
095: * @deprecated use the other constructor (with rectange) see javadox. This
096: * works fine, but it the results are often poor if you're also
097: * doing CRS xforms.
098: */
099: public Decimator(MathTransform screenToWorld) {
100: this (screenToWorld, new Rectangle()); // do at (0,0)
101: }
102:
103: public Decimator(double spanx, double spany) {
104: this .spanx = spanx;
105: this .spany = spany;
106: }
107:
108: public final void decimateTransformGeneralize(Geometry geometry,
109: MathTransform transform) throws TransformException {
110: if (geometry instanceof GeometryCollection) {
111: GeometryCollection collection = (GeometryCollection) geometry;
112: final int length = collection.getNumGeometries();
113: for (int i = 0; i < length; i++) {
114: decimateTransformGeneralize(collection.getGeometryN(i),
115: transform);
116: }
117: } else if (geometry instanceof Point) {
118: LiteCoordinateSequence seq = (LiteCoordinateSequence) ((Point) geometry)
119: .getCoordinateSequence();
120: decimateTransformGeneralize(seq, transform);
121: } else if (geometry instanceof Polygon) {
122: Polygon polygon = (Polygon) geometry;
123: decimateTransformGeneralize(polygon.getExteriorRing(),
124: transform);
125: final int length = polygon.getNumInteriorRing();
126: for (int i = 0; i < length; i++) {
127: decimateTransformGeneralize(
128: polygon.getInteriorRingN(i), transform);
129: }
130: } else if (geometry instanceof LineString) {
131: LiteCoordinateSequence seq = (LiteCoordinateSequence) ((LineString) geometry)
132: .getCoordinateSequence();
133: decimateTransformGeneralize(seq, transform);
134: }
135: }
136:
137: /**
138: * decimates JTS geometries.
139: */
140: public final void decimate(Geometry geom) {
141: if (spanx == -1)
142: return;
143: if (geom instanceof MultiPoint) {
144: // TODO check geometry and if its bbox is too small turn it into a 1
145: // point geom
146: return;
147: }
148: if (geom instanceof GeometryCollection) {
149: // TODO check geometry and if its bbox is too small turn it into a
150: // 1-2 point geom
151: // takes a bit of work because the geometry will need to be
152: // recreated.
153: GeometryCollection collection = (GeometryCollection) geom;
154: final int numGeometries = collection.getNumGeometries();
155: for (int i = 0; i < numGeometries; i++) {
156: decimate(collection.getGeometryN(i));
157: }
158: } else if (geom instanceof LineString) {
159: LineString line = (LineString) geom;
160: LiteCoordinateSequence seq = (LiteCoordinateSequence) line
161: .getCoordinateSequence();
162: if (decimateOnEnvelope(line, seq)) {
163: return;
164: }
165: decimate(seq);
166: } else if (geom instanceof Polygon) {
167: Polygon line = (Polygon) geom;
168: decimate(line.getExteriorRing());
169: final int numRings = line.getNumInteriorRing();
170: for (int i = 0; i < numRings; i++) {
171: decimate(line.getInteriorRingN(i));
172: }
173: }
174: }
175:
176: /**
177: * @param geom
178: * @param seq
179: */
180: private boolean decimateOnEnvelope(Geometry geom,
181: LiteCoordinateSequence seq) {
182: Envelope env = geom.getEnvelopeInternal();
183: if (env.getWidth() <= spanx && env.getHeight() <= spany) {
184: double[] coords = seq.getArray();
185: int dim = seq.getDimension();
186: double[] newcoords = new double[dim * 2];
187: for (int i = 0; i < dim; i++) {
188: newcoords[i] = coords[i];
189: newcoords[dim + i] = coords[coords.length - dim + i];
190: }
191: seq.setArray(coords);
192: return true;
193: }
194: return false;
195: }
196:
197: /**
198: * 1. remove any points that are within the spanx,spany. We ALWAYS keep 1st
199: * and last point 2. transform to screen coordinates 3. remove any points
200: * that are close (span <1)
201: *
202: * @param seq
203: * @param tranform
204: */
205: private final void decimateTransformGeneralize(
206: LiteCoordinateSequence seq, MathTransform transform)
207: throws TransformException {
208: // decimates before XFORM
209: int ncoords = seq.size();
210: double coords[] = seq.getXYArray(); // 2*#of points
211:
212: if (ncoords < 2) {
213: if (ncoords == 1) // 1 coordinate -- just xform it
214: {
215: // double[] newCoordsXformed2 = new double[2];
216: if (transform != null) {
217: transform.transform(coords, 0, coords, 0, 1);
218: seq.setArray(coords);
219: }
220: return;
221: } else
222: return; // ncoords =0
223: }
224:
225: int actualCoords = 1;
226: double lastX = coords[0];
227: double lastY = coords[1];
228: for (int t = 1; t < (ncoords - 1); t++) {
229: // see if this one should be added
230: double x = coords[t * 2];
231: double y = coords[t * 2 + 1];
232: if ((Math.abs(x - lastX) > spanx)
233: || (Math.abs(y - lastY)) > spany) {
234: coords[actualCoords * 2] = x;
235: coords[actualCoords * 2 + 1] = y;
236: lastX = x;
237: lastY = y;
238: actualCoords++;
239: }
240: }
241: // always have last one
242: coords[actualCoords * 2] = coords[(ncoords - 1) * 2];
243: coords[actualCoords * 2 + 1] = coords[(ncoords - 1) * 2 + 1];
244: actualCoords++;
245:
246: // DO THE XFORM
247: if ((transform == null) || (transform.isIdentity())) {
248: // no actual xform
249: } else {
250: transform.transform(coords, 0, coords, 0, actualCoords);
251: }
252:
253: int actualCoordsGen = 1;
254: if (!(transform instanceof AffineTransform2D)) {
255: // GENERALIZE again -- we should be in screen space so spanx=spany=1.0
256: for (int t = 1; t < (actualCoords - 1); t++) {
257: // see if this one should be added
258: double x = coords[t * 2];
259: double y = coords[t * 2 + 1];
260: if ((Math.abs(x - lastX) > 0.75)
261: || (Math.abs(y - lastY)) > 0.75) // 0.75
262: // instead of 1 just because it tends to look nicer for slightly
263: // more work. magic number.
264: {
265: coords[actualCoordsGen * 2] = x;
266: coords[actualCoordsGen * 2 + 1] = y;
267: lastX = x;
268: lastY = y;
269: actualCoordsGen++;
270: }
271: }
272: // always have last one
273: coords[actualCoordsGen * 2] = coords[(actualCoords - 1) * 2];
274: coords[actualCoordsGen * 2 + 1] = coords[(actualCoords - 1) * 2 + 1];
275: actualCoordsGen++;
276: } else {
277: actualCoordsGen = actualCoords;
278: }
279:
280: // stick back in
281: if (actualCoordsGen * 2 < coords.length) {
282: double[] seqDouble = new double[2 * actualCoordsGen];
283: System.arraycopy(coords, 0, seqDouble, 0,
284: actualCoordsGen * 2);
285: seq.setArray(seqDouble);
286: } else {
287: seq.setArray(coords);
288: }
289: }
290:
291: private void decimate(LiteCoordinateSequence seq) {
292: double[] coords = seq.getXYArray();
293: int dim = seq.getDimension();
294: int numDoubles = coords.length;
295: int readDoubles = 0;
296: double prevx, currx, prevy, curry, diffx, diffy;
297: for (int currentDoubles = 0; currentDoubles < numDoubles; currentDoubles += dim) {
298: if (currentDoubles >= dim
299: && currentDoubles < numDoubles - 1) {
300: prevx = coords[readDoubles - dim];
301: currx = coords[currentDoubles];
302: diffx = Math.abs(prevx - currx);
303: prevy = coords[readDoubles - dim + 1];
304: curry = coords[currentDoubles + 1];
305: diffy = Math.abs(prevy - curry);
306: if (diffx > spanx || diffy > spany) {
307: readDoubles = copyCoordinate(coords, dim,
308: readDoubles, currentDoubles);
309: }
310: } else {
311: readDoubles = copyCoordinate(coords, dim, readDoubles,
312: currentDoubles);
313: }
314: }
315: if (readDoubles < numDoubles) {
316: double[] newCoords = new double[readDoubles];
317: System.arraycopy(coords, 0, newCoords, 0, readDoubles);
318: seq.setArray(newCoords);
319: }
320: }
321:
322: /**
323: * @param coords
324: * @param dimension
325: * @param readDoubles
326: * @param currentDoubles
327: */
328: private int copyCoordinate(double[] coords, int dimension,
329: int readDoubles, int currentDoubles) {
330: for (int i = 0; i < dimension; i++) {
331: coords[readDoubles + i] = coords[currentDoubles + i];
332: }
333: readDoubles += dimension;
334: return readDoubles;
335: }
336: }
|