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;
034:
035: import java.awt.event.ComponentAdapter;
036: import java.awt.event.ComponentEvent;
037: import java.awt.geom.AffineTransform;
038: import java.awt.geom.NoninvertibleTransformException;
039: import java.awt.geom.Point2D;
040: import java.awt.geom.Rectangle2D;
041: import java.util.ArrayList;
042: import java.util.Collection;
043: import java.util.Iterator;
044:
045: import com.vividsolutions.jts.geom.Coordinate;
046: import com.vividsolutions.jts.geom.Envelope;
047: import com.vividsolutions.jump.geom.CoordUtil;
048: import com.vividsolutions.jump.geom.EnvelopeUtil;
049: import com.vividsolutions.jump.workbench.ui.renderer.java2D.Java2DConverter;
050:
051: /**
052: * Controls the area on the model being viewed by a LayerViewPanel.
053: */
054:
055: //<<TODO:NAMING>> Rename to Viewport [Jon Aquino]
056: public class Viewport implements Java2DConverter.PointConverter {
057: static private final int INITIAL_VIEW_ORIGIN_X = 0;
058: static private final int INITIAL_VIEW_ORIGIN_Y = 0;
059: private ArrayList listeners = new ArrayList();
060: private Java2DConverter java2DConverter;
061: private LayerViewPanel panel;
062:
063: /**
064: * Origin of view as perceived by model, that is, in model space
065: */
066: private Point2D viewOriginAsPerceivedByModel = new Point2D.Double(
067: INITIAL_VIEW_ORIGIN_X, INITIAL_VIEW_ORIGIN_Y);
068: private double scale = 1;
069: private AffineTransform modelToViewTransform;
070: private ZoomHistory zoomHistory;
071:
072: public Viewport(LayerViewPanel panel) {
073: this .panel = panel;
074: zoomHistory = new ZoomHistory(panel);
075: java2DConverter = new Java2DConverter(this );
076: panel.addComponentListener(new ComponentAdapter() {
077: public void componentResized(ComponentEvent e) {
078: fireZoomChanged(getEnvelopeInModelCoordinates());
079: }
080: });
081: }
082:
083: public LayerViewPanel getPanel() {
084: return panel;
085: }
086:
087: public void addListener(ViewportListener l) {
088: listeners.add(l);
089: }
090:
091: public void removeListener(ViewportListener l) {
092: listeners.remove(l);
093: }
094:
095: public Java2DConverter getJava2DConverter() {
096: return java2DConverter;
097: }
098:
099: public void setJava2DConverter(Java2DConverter converter) {
100: java2DConverter = converter;
101: }
102:
103: public ZoomHistory getZoomHistory() {
104: return zoomHistory;
105: }
106:
107: public void update() throws NoninvertibleTransformException {
108: modelToViewTransform = modelToViewTransform(scale,
109: viewOriginAsPerceivedByModel, panel.getSize().height);
110: panel.repaint();
111: }
112:
113: public static AffineTransform modelToViewTransform(double scale,
114: Point2D viewOriginAsPerceivedByModel, double panelHeight) {
115: AffineTransform modelToViewTransform = new AffineTransform();
116: modelToViewTransform.translate(0, panelHeight);
117: modelToViewTransform.scale(1, -1);
118: modelToViewTransform.scale(scale, scale);
119: modelToViewTransform.translate(-viewOriginAsPerceivedByModel
120: .getX(), -viewOriginAsPerceivedByModel.getY());
121: return modelToViewTransform;
122: }
123:
124: public double getScale() {
125: return scale;
126: }
127:
128: /**
129: * Set both values but repaint once.
130: */
131: public void initialize(double newScale,
132: Point2D newViewOriginAsPerceivedByModel) {
133: setScale(newScale);
134: viewOriginAsPerceivedByModel = newViewOriginAsPerceivedByModel;
135:
136: //Don't call #update here, because this method may be called before the
137: //panel has been made visible, causing LayerViewPanel#createImage
138: //to return null, causing a NullPointerException in
139: //LayerViewPanel#updateImageBuffer. [Jon Aquino]
140: }
141:
142: public Point2D getOriginInModelCoordinates() {
143: return viewOriginAsPerceivedByModel;
144: }
145:
146: /**
147: * Of widthOfNewViewAsPerceivedByOldView and heightOfNewViewAsPerceivedByOldView,
148: * this method will choose the one producing the least zoom.
149: */
150: public void zoom(Point2D centreOfNewViewAsPerceivedByOldView,
151: double widthOfNewViewAsPerceivedByOldView,
152: double heightOfNewViewAsPerceivedByOldView)
153: throws NoninvertibleTransformException {
154: double zoomFactor = Math.min(panel.getSize().width
155: / widthOfNewViewAsPerceivedByOldView,
156: panel.getSize().height
157: / heightOfNewViewAsPerceivedByOldView);
158: double realWidthOfNewViewAsPerceivedByOldView = panel.getSize().width
159: / zoomFactor;
160: double realHeightOfNewViewAsPerceivedByOldView = panel
161: .getSize().height
162: / zoomFactor;
163: Envelope zoomEnvelope;
164: try {
165: zoomEnvelope = toModelEnvelope(
166: centreOfNewViewAsPerceivedByOldView.getX()
167: - (0.5 * realWidthOfNewViewAsPerceivedByOldView),
168: centreOfNewViewAsPerceivedByOldView.getX()
169: + (0.5 * realWidthOfNewViewAsPerceivedByOldView),
170: centreOfNewViewAsPerceivedByOldView.getY()
171: - (0.5 * realHeightOfNewViewAsPerceivedByOldView),
172: centreOfNewViewAsPerceivedByOldView.getY()
173: + (0.5 * realHeightOfNewViewAsPerceivedByOldView));
174: } catch (NoninvertibleTransformException ex) {
175: //LDB: (for Mouse Wheel Zooming) eat exception and restore a valid zoom
176: zoomToFullExtent();
177: return;
178: }
179: zoom(zoomEnvelope);
180: }
181:
182: public Point2D toModelPoint(Point2D viewPoint)
183: throws NoninvertibleTransformException {
184: return getModelToViewTransform().inverseTransform(
185: toPoint2DDouble(viewPoint), null);
186: }
187:
188: private Point2D.Double toPoint2DDouble(Point2D p) {
189: //If you pass a non-Double Point2D to an AffineTransform, the AffineTransform
190: //will be done using floats instead of doubles. [Jon Aquino]
191: if (p instanceof Point2D.Double) {
192: return (Point2D.Double) p;
193: }
194: return new Point2D.Double(p.getX(), p.getY());
195: }
196:
197: public Coordinate toModelCoordinate(Point2D viewPoint)
198: throws NoninvertibleTransformException {
199: return CoordUtil.toCoordinate(toModelPoint(viewPoint));
200: }
201:
202: public Point2D toViewPoint(Point2D modelPoint)
203: throws NoninvertibleTransformException {
204: return getModelToViewTransform().transform(
205: toPoint2DDouble(modelPoint), null);
206: }
207:
208: public Point2D toViewPoint(Coordinate modelCoordinate)
209: throws NoninvertibleTransformException {
210: //Optimization recommended by Todd Warnes [Jon Aquino 2004-02-06]
211: Point2D.Double pt = new Point2D.Double(modelCoordinate.x,
212: modelCoordinate.y);
213: return getModelToViewTransform().transform(pt, pt);
214: }
215:
216: public Envelope toModelEnvelope(double x1, double x2, double y1,
217: double y2) throws NoninvertibleTransformException {
218: Coordinate c1 = toModelCoordinate(new Point2D.Double(x1, y1));
219: Coordinate c2 = toModelCoordinate(new Point2D.Double(x2, y2));
220:
221: return new Envelope(c1, c2);
222: }
223:
224: public AffineTransform getModelToViewTransform()
225: throws NoninvertibleTransformException {
226: if (modelToViewTransform == null) {
227: update();
228: }
229:
230: return modelToViewTransform;
231: }
232:
233: public Envelope getEnvelopeInModelCoordinates() {
234: double widthAsPerceivedByModel = panel.getWidth() / scale;
235: double heightAsPerceivedByModel = panel.getHeight() / scale;
236:
237: return new Envelope(viewOriginAsPerceivedByModel.getX(),
238: viewOriginAsPerceivedByModel.getX()
239: + widthAsPerceivedByModel,
240: viewOriginAsPerceivedByModel.getY(),
241: viewOriginAsPerceivedByModel.getY()
242: + heightAsPerceivedByModel);
243: }
244:
245: //<<TODO:IMPROVE>> Currently the zoomed image is aligned west in the viewport.
246: //It should be centred. [Jon Aquino]
247: public void zoom(Envelope modelEnvelope)
248: throws NoninvertibleTransformException {
249: if (modelEnvelope.isNull()) {
250: return;
251: }
252:
253: if (!zoomHistory.hasNext() && !zoomHistory.hasPrev()) {
254: //When the first extent is added, first add the existing extent.
255: //Must do this late because it's hard to tell when the panel is realized.
256: //[Jon Aquino]
257: zoomHistory.add(getEnvelopeInModelCoordinates());
258: }
259:
260: setScale(Math.min(panel.getWidth() / modelEnvelope.getWidth(),
261: panel.getHeight() / modelEnvelope.getHeight()));
262: double xCenteringOffset = ((panel.getWidth() / scale) - modelEnvelope
263: .getWidth()) / 2d;
264: double yCenteringOffset = ((panel.getHeight() / scale) - modelEnvelope
265: .getHeight()) / 2d;
266: viewOriginAsPerceivedByModel = new Point2D.Double(modelEnvelope
267: .getMinX()
268: - xCenteringOffset, modelEnvelope.getMinY()
269: - yCenteringOffset);
270: update();
271: zoomHistory.add(modelEnvelope);
272: fireZoomChanged(modelEnvelope);
273: }
274:
275: private void setScale(double scale) {
276: this .scale = scale;
277: }
278:
279: private void fireZoomChanged(Envelope modelEnvelope) {
280: for (Iterator i = listeners.iterator(); i.hasNext();) {
281: ViewportListener l = (ViewportListener) i.next();
282: l.zoomChanged(modelEnvelope);
283: }
284: }
285:
286: public void zoomToFullExtent()
287: throws NoninvertibleTransformException {
288: zoom(fullExtent());
289: }
290:
291: public Envelope fullExtent() {
292: return EnvelopeUtil.bufferByFraction(panel.getLayerManager()
293: .getEnvelopeOfAllLayers(true), 0.03);
294: }
295:
296: public void zoomToViewPoint(
297: Point2D centreOfNewViewAsPerceivedByOldView,
298: double zoomFactor) throws NoninvertibleTransformException {
299: double widthOfNewViewAsPerceivedByOldView = panel.getWidth()
300: / zoomFactor;
301: double heightOfNewViewAsPerceivedByOldView = panel.getHeight()
302: / zoomFactor;
303: zoom(centreOfNewViewAsPerceivedByOldView,
304: widthOfNewViewAsPerceivedByOldView,
305: heightOfNewViewAsPerceivedByOldView);
306: }
307:
308: public Collection toViewPoints(Collection modelCoordinates)
309: throws NoninvertibleTransformException {
310: ArrayList viewPoints = new ArrayList();
311: for (Iterator i = modelCoordinates.iterator(); i.hasNext();) {
312: Coordinate modelCoordinate = (Coordinate) i.next();
313: viewPoints.add(toViewPoint(modelCoordinate));
314: }
315: return viewPoints;
316: }
317:
318: public Rectangle2D toViewRectangle(Envelope envelope)
319: throws NoninvertibleTransformException {
320: Point2D p1 = toViewPoint(new Coordinate(envelope.getMinX(),
321: envelope.getMinY()));
322: Point2D p2 = toViewPoint(new Coordinate(envelope.getMaxX(),
323: envelope.getMaxY()));
324: return new Rectangle2D.Double(Math.min(p1.getX(), p2.getX()),
325: Math.min(p1.getY(), p2.getY()), Math.abs(p1.getX()
326: - p2.getX()), Math.abs(p1.getY() - p2.getY()));
327: }
328: }
|