001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
003: * for visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions
024: * Suite #1A
025: * 2328 Government Street
026: * Victoria BC V8T 5G5
027: * Canada
028: *
029: * (250)385-6040
030: * www.vividsolutions.com
031: */
032:
033: package com.vividsolutions.jump.workbench.ui.renderer.java2D;
034:
035: import java.awt.Shape;
036: import java.awt.geom.GeneralPath;
037: import java.awt.geom.NoninvertibleTransformException;
038: import java.awt.geom.Line2D;
039: import java.awt.geom.Point2D;
040: import java.awt.geom.Rectangle2D;
041: import java.awt.geom.Point2D.Double;
042: import java.awt.geom.PathIterator;
043: import java.util.ArrayList;
044:
045: import com.vividsolutions.jts.geom.Coordinate;
046: import com.vividsolutions.jts.geom.CoordinateSequence;
047: import com.vividsolutions.jts.geom.Envelope;
048: import com.vividsolutions.jts.geom.Geometry;
049: import com.vividsolutions.jts.geom.GeometryCollection;
050: import com.vividsolutions.jts.geom.GeometryFactory;
051: import com.vividsolutions.jts.geom.LineString;
052: import com.vividsolutions.jts.geom.MultiLineString;
053: import com.vividsolutions.jts.geom.MultiPoint;
054: import com.vividsolutions.jts.geom.MultiPolygon;
055: import com.vividsolutions.jts.geom.Point;
056: import com.vividsolutions.jts.geom.Polygon;
057:
058: /**
059: * Converts JTS Geometry objects into Java 2D Shape objects
060: */
061: // Optimizations from larry becker's SkyJUMP code to OpenJUMP [mmichaud]
062: // 1 - Added point decimation to toViewCoordinates.
063: // Results in much improved render speeds when displaying full extent of large datasets.
064: // 2 - Optimized the toShape conversion for linestrings.
065: // Reduced darw times by 60%.
066: // 3 - Made toViewCoordinates(Coordinate[]) public to make use
067: // of its decimation optimization in AbstractSelectionRenderer.
068: public class Java2DConverter {
069: private static double POINT_MARKER_SIZE = 3.0;
070: private PointConverter pointConverter;
071: // Add the resolution of the decimator as an option to be able to choose more easily
072: // between speed and quality (ex. speed is preferred for light-gray display while
073: // dragging the zoombar or while using the mousewheel) [mmichaud 2007-05-27]
074: // Default resolution for the decimator is half a pixel as discussed on the list
075: private double decimatorResolution = 0.5;
076:
077: public Java2DConverter(PointConverter pointConverter) {
078: this .pointConverter = pointConverter;
079: }
080:
081: // Add a constructor to choose another decimatorResolution
082: public Java2DConverter(PointConverter pointConverter,
083: double resolution) {
084: this .pointConverter = pointConverter;
085: this .decimatorResolution = resolution;
086: }
087:
088: private Shape toShape(Polygon p)
089: throws NoninvertibleTransformException {
090: ArrayList holeVertexCollection = new ArrayList();
091:
092: for (int j = 0; j < p.getNumInteriorRing(); j++) {
093: holeVertexCollection.add(toViewCoordinates(p
094: .getInteriorRingN(j).getCoordinates()));
095: }
096:
097: return new PolygonShape(toViewCoordinates(p.getExteriorRing()
098: .getCoordinates()), holeVertexCollection);
099: }
100:
101: public Coordinate[] toViewCoordinates(Coordinate[] modelCoordinates)
102: throws NoninvertibleTransformException {
103: Coordinate[] viewCoordinates = new Coordinate[modelCoordinates.length];
104: double ps = decimatorResolution / pointConverter.getScale(); // convert in model units
105: Coordinate p0 = modelCoordinates[0];
106: int npts = 0;
107: int mpts = modelCoordinates.length;
108: for (int i = 0; i < mpts; i++) {
109: Coordinate pi = modelCoordinates[i];
110: //inline Decimator
111: double xd = Math.abs(p0.x - pi.x);
112: double yd = Math.abs(p0.y - pi.y);
113: if ((xd >= ps) || (yd >= ps) || (npts < 4)
114: || (i == mpts - 1)) {
115: //LDB: have replaced the following with inline code but
116: // it was no faster. AffineTransform must be highly optimized!
117: Point2D point2D = pointConverter.toViewPoint(pi);
118: viewCoordinates[npts++] = new Coordinate(
119: point2D.getX(), point2D.getY());
120: p0 = pi;
121: }
122: }
123: if (npts != mpts) {
124: Coordinate[] viewCoordinates2 = new Coordinate[npts];
125: for (int i = 0; i < npts; i++) {
126: viewCoordinates2[i] = viewCoordinates[i];
127: }
128: return viewCoordinates2;
129: // LDB: benchmarkes verify that the following line is slower than
130: // the above loop probably because of copying vs. referencing
131: //return Arrays.copyOfRange(viewCoordinates,0,npts);
132: } else
133: return viewCoordinates;
134: }
135:
136: private Shape toShape(GeometryCollection gc)
137: throws NoninvertibleTransformException {
138: GeometryCollectionShape shape = new GeometryCollectionShape();
139:
140: for (int i = 0; i < gc.getNumGeometries(); i++) {
141: Geometry g = (Geometry) gc.getGeometryN(i);
142: shape.add(toShape(g));
143: }
144:
145: return shape;
146: }
147:
148: private GeneralPath toShape(MultiLineString mls)
149: throws NoninvertibleTransformException {
150: GeneralPath path = new GeneralPath();
151:
152: for (int i = 0; i < mls.getNumGeometries(); i++) {
153: LineString lineString = (LineString) mls.getGeometryN(i);
154: path.append(toShape(lineString), false);
155: }
156:
157: //BasicFeatureRenderer expects LineStrings and MultiLineStrings to be
158: //converted to GeneralPaths. [Jon Aquino]
159: return path;
160: }
161:
162: class LineStringPath extends LineString implements PathIterator {
163:
164: private int iterate;
165: private int numPoints;
166: private Coordinate[] points;
167: private Java2DConverter j2D;
168: private boolean closed;
169:
170: public LineStringPath(LineString linestring, Java2DConverter j2D) {
171: super (null, new GeometryFactory());
172: //this.linestring = linestring;
173: this .j2D = j2D;
174: try {
175: points = j2D.toViewCoordinates(linestring
176: .getCoordinates());
177: } catch (NoninvertibleTransformException ex) {
178: }
179: this .numPoints = points.length; //linestring.getNumPoints();
180: iterate = 0;
181: closed = (numPoints > 1)
182: && (points[0].equals2D(points[numPoints - 1]));
183: }
184:
185: private int getSegType() {
186: // tip from Larry Becker for a better rendering 2007-07-13 [mmichaud]
187: if (closed && (iterate == numPoints - 1))
188: return PathIterator.SEG_CLOSE;
189: return (iterate == 0) ? PathIterator.SEG_MOVETO
190: : PathIterator.SEG_LINETO;
191: }
192:
193: public int currentSegment(double[] coords) {
194: coords[0] = points[iterate].x;
195: coords[1] = points[iterate].y;
196: return getSegType();
197: }
198:
199: public int currentSegment(float[] coords) {
200: coords[0] = (float) points[iterate].x;
201: coords[1] = (float) points[iterate].y;
202: return getSegType();
203: }
204:
205: public int getWindingRule() {
206: return GeneralPath.WIND_NON_ZERO;
207: }
208:
209: public boolean isDone() {
210: return !(iterate < numPoints);
211: }
212:
213: public void next() {
214: iterate++;
215: }
216:
217: }
218:
219: private GeneralPath toShape(LineString lineString)
220: throws NoninvertibleTransformException {
221: int numPoints = lineString.getNumPoints();
222: GeneralPath shape = new GeneralPath(GeneralPath.WIND_NON_ZERO,
223: numPoints);
224: PathIterator pi = new LineStringPath(lineString, this );
225: shape.append(pi, false);
226: //Point2D viewPoint = toViewPoint(lineString.getCoordinateN(0));
227: //shape.moveTo((float) viewPoint.getX(), (float) viewPoint.getY());
228: //
229: //for (int i = 1; i < lineString.getNumPoints(); i++) {
230: // viewPoint = toViewPoint(lineString.getCoordinateN(i));
231: // shape.lineTo((float) viewPoint.getX(), (float) viewPoint.getY());
232: //}
233:
234: //BasicFeatureRenderer expects LineStrings and MultiLineStrings to be
235: //converted to GeneralPaths. [Jon Aquino]
236: return shape;
237: }
238:
239: private Shape toShape(Point point)
240: throws NoninvertibleTransformException {
241: Rectangle2D.Double pointMarker = new Rectangle2D.Double(0.0,
242: 0.0, POINT_MARKER_SIZE, POINT_MARKER_SIZE);
243: Point2D viewPoint = toViewPoint(point.getCoordinate());
244: pointMarker.x = (double) (viewPoint.getX() - (POINT_MARKER_SIZE / 2));
245: pointMarker.y = (double) (viewPoint.getY() - (POINT_MARKER_SIZE / 2));
246:
247: return pointMarker;
248: }
249:
250: private Point2D toViewPoint(Coordinate modelCoordinate)
251: throws NoninvertibleTransformException {
252: //Do the rounding now; don't rely on Java 2D rounding, because it
253: //seems to do it differently for drawing and filling, resulting in the draw
254: //being a pixel off from the fill sometimes. [Jon Aquino]
255: //LDB 04/25/2007: this assumption doesn't seem to be true any longer
256: Point2D viewPoint = pointConverter.toViewPoint(modelCoordinate);
257: //Optimization recommended by Todd Warnes [Jon Aquino 2004-02-06]
258: //viewPoint.setLocation(
259: // Math.round(viewPoint.getX()),
260: // Math.round(viewPoint.getY()));
261: return viewPoint;
262: }
263:
264: public static interface PointConverter {
265: public Point2D toViewPoint(Coordinate modelCoordinate)
266: throws NoninvertibleTransformException;
267:
268: public double getScale() throws NoninvertibleTransformException;
269: }
270:
271: /**
272: * If you pass in a general GeometryCollection, note that a Shape cannot
273: * preserve information about which elements are 1D and which are 2D.
274: * For example, if you pass in a GeometryCollection containing a ring and a
275: * disk, you cannot render them as such: if you use Graphics.fill, you'll get
276: * two disks, and if you use Graphics.draw, you'll get two rings. Solution:
277: * create Shapes for each element.
278: */
279: public Shape toShape(Geometry geometry)
280: throws NoninvertibleTransformException {
281:
282: // [NOTE] I tested a short-circuit here to return a 1 pixel representation of
283: // geometries having an envelope of less than 1/2 pixel, but I get only an ugly
284: // render for a small performance improvement [mmichaud - 2007-05-23]
285:
286: if (geometry.isEmpty()) {
287: return new GeneralPath();
288: }
289:
290: if (geometry instanceof Polygon) {
291: return toShape((Polygon) geometry);
292: }
293:
294: if (geometry instanceof MultiPolygon) {
295: return toShape((MultiPolygon) geometry);
296: }
297:
298: if (geometry instanceof LineString) {
299: return toShape((LineString) geometry);
300: }
301:
302: if (geometry instanceof MultiLineString) {
303: return toShape((MultiLineString) geometry);
304: }
305:
306: if (geometry instanceof Point) {
307: return toShape((Point) geometry);
308: }
309:
310: if (geometry instanceof GeometryCollection) {
311: return toShape((GeometryCollection) geometry);
312: }
313:
314: throw new IllegalArgumentException(
315: "Unrecognized Geometry class: " + geometry.getClass());
316: }
317: }
|