/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * Copyright (C) 2003 Vivid Solutions * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.vividsolutions.jump.workbench.ui; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jump.geom.CoordUtil; import com.vividsolutions.jump.geom.EnvelopeUtil; import com.vividsolutions.jump.workbench.ui.renderer.java2D.Java2DConverter; /** * Controls the area on the model being viewed by a LayerViewPanel. */ //<<TODO:NAMING>> Rename to Viewport [Jon Aquino] public class Viewport implements Java2DConverter.PointConverter { static private final int INITIAL_VIEW_ORIGIN_X = 0; static private final int INITIAL_VIEW_ORIGIN_Y = 0; private ArrayList listeners = new ArrayList(); private Java2DConverter java2DConverter; private LayerViewPanel panel; /** * Origin of view as perceived by model, that is, in model space */ private Point2D viewOriginAsPerceivedByModel = new Point2D.Double(INITIAL_VIEW_ORIGIN_X, INITIAL_VIEW_ORIGIN_Y); private double scale = 1; private AffineTransform modelToViewTransform; private ZoomHistory zoomHistory; public Viewport(LayerViewPanel panel) { this.panel = panel; zoomHistory = new ZoomHistory(panel); java2DConverter = new Java2DConverter(this); panel.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { fireZoomChanged(getEnvelopeInModelCoordinates()); } }); } public LayerViewPanel getPanel() { return panel; } public void addListener(ViewportListener l) { listeners.add(l); } public void removeListener(ViewportListener l) { listeners.remove(l); } public Java2DConverter getJava2DConverter() { return java2DConverter; } public void setJava2DConverter(Java2DConverter converter) { java2DConverter = converter; } public ZoomHistory getZoomHistory() { return zoomHistory; } public void update() throws NoninvertibleTransformException { modelToViewTransform = modelToViewTransform(scale, viewOriginAsPerceivedByModel, panel.getSize().height); panel.repaint(); } public static AffineTransform modelToViewTransform( double scale, Point2D viewOriginAsPerceivedByModel, double panelHeight) { AffineTransform modelToViewTransform = new AffineTransform(); modelToViewTransform.translate(0, panelHeight); modelToViewTransform.scale(1, -1); modelToViewTransform.scale(scale, scale); modelToViewTransform.translate( -viewOriginAsPerceivedByModel.getX(), -viewOriginAsPerceivedByModel.getY()); return modelToViewTransform; } public double getScale() { return scale; } /** * Set both values but repaint once. */ public void initialize(double newScale, Point2D newViewOriginAsPerceivedByModel) { setScale(newScale); viewOriginAsPerceivedByModel = newViewOriginAsPerceivedByModel; //Don't call #update here, because this method may be called before the //panel has been made visible, causing LayerViewPanel#createImage //to return null, causing a NullPointerException in //LayerViewPanel#updateImageBuffer. [Jon Aquino] } public Point2D getOriginInModelCoordinates() { return viewOriginAsPerceivedByModel; } /** * Of widthOfNewViewAsPerceivedByOldView and heightOfNewViewAsPerceivedByOldView, * this method will choose the one producing the least zoom. */ public void zoom( Point2D centreOfNewViewAsPerceivedByOldView, double widthOfNewViewAsPerceivedByOldView, double heightOfNewViewAsPerceivedByOldView) throws NoninvertibleTransformException { double zoomFactor = Math.min( panel.getSize().width / widthOfNewViewAsPerceivedByOldView, panel.getSize().height / heightOfNewViewAsPerceivedByOldView); double realWidthOfNewViewAsPerceivedByOldView = panel.getSize().width / zoomFactor; double realHeightOfNewViewAsPerceivedByOldView = panel.getSize().height / zoomFactor; Envelope zoomEnvelope; try { zoomEnvelope = toModelEnvelope( centreOfNewViewAsPerceivedByOldView.getX() - (0.5 * realWidthOfNewViewAsPerceivedByOldView), centreOfNewViewAsPerceivedByOldView.getX() + (0.5 * realWidthOfNewViewAsPerceivedByOldView), centreOfNewViewAsPerceivedByOldView.getY() - (0.5 * realHeightOfNewViewAsPerceivedByOldView), centreOfNewViewAsPerceivedByOldView.getY() + (0.5 * realHeightOfNewViewAsPerceivedByOldView)); } catch (NoninvertibleTransformException ex){ //LDB: (for Mouse Wheel Zooming) eat exception and restore a valid zoom zoomToFullExtent(); return; } zoom(zoomEnvelope); } public Point2D toModelPoint(Point2D viewPoint) throws NoninvertibleTransformException { return getModelToViewTransform().inverseTransform(toPoint2DDouble(viewPoint), null); } private Point2D.Double toPoint2DDouble(Point2D p) { //If you pass a non-Double Point2D to an AffineTransform, the AffineTransform //will be done using floats instead of doubles. [Jon Aquino] if (p instanceof Point2D.Double) { return (Point2D.Double) p; } return new Point2D.Double(p.getX(), p.getY()); } public Coordinate toModelCoordinate(Point2D viewPoint) throws NoninvertibleTransformException { return CoordUtil.toCoordinate(toModelPoint(viewPoint)); } public Point2D toViewPoint(Point2D modelPoint) throws NoninvertibleTransformException { return getModelToViewTransform().transform(toPoint2DDouble(modelPoint), null); } public Point2D toViewPoint(Coordinate modelCoordinate) throws NoninvertibleTransformException { //Optimization recommended by Todd Warnes [Jon Aquino 2004-02-06] Point2D.Double pt = new Point2D.Double(modelCoordinate.x, modelCoordinate.y); return getModelToViewTransform().transform(pt, pt); } public Envelope toModelEnvelope(double x1, double x2, double y1, double y2) throws NoninvertibleTransformException { Coordinate c1 = toModelCoordinate(new Point2D.Double(x1, y1)); Coordinate c2 = toModelCoordinate(new Point2D.Double(x2, y2)); return new Envelope(c1, c2); } public AffineTransform getModelToViewTransform() throws NoninvertibleTransformException { if (modelToViewTransform == null) { update(); } return modelToViewTransform; } public Envelope getEnvelopeInModelCoordinates() { double widthAsPerceivedByModel = panel.getWidth() / scale; double heightAsPerceivedByModel = panel.getHeight() / scale; return new Envelope( viewOriginAsPerceivedByModel.getX(), viewOriginAsPerceivedByModel.getX() + widthAsPerceivedByModel, viewOriginAsPerceivedByModel.getY(), viewOriginAsPerceivedByModel.getY() + heightAsPerceivedByModel); } //<<TODO:IMPROVE>> Currently the zoomed image is aligned west in the viewport. //It should be centred. [Jon Aquino] public void zoom(Envelope modelEnvelope) throws NoninvertibleTransformException { if (modelEnvelope.isNull()) { return; } if (!zoomHistory.hasNext() && !zoomHistory.hasPrev()) { //When the first extent is added, first add the existing extent. //Must do this late because it's hard to tell when the panel is realized. //[Jon Aquino] zoomHistory.add(getEnvelopeInModelCoordinates()); } setScale( Math.min( panel.getWidth() / modelEnvelope.getWidth(), panel.getHeight() / modelEnvelope.getHeight())); double xCenteringOffset = ((panel.getWidth() / scale) - modelEnvelope.getWidth()) / 2d; double yCenteringOffset = ((panel.getHeight() / scale) - modelEnvelope.getHeight()) / 2d; viewOriginAsPerceivedByModel = new Point2D.Double(modelEnvelope.getMinX() - xCenteringOffset, modelEnvelope.getMinY() - yCenteringOffset); update(); zoomHistory.add(modelEnvelope); fireZoomChanged(modelEnvelope); } private void setScale(double scale) { this.scale = scale; } private void fireZoomChanged(Envelope modelEnvelope) { for (Iterator i = listeners.iterator(); i.hasNext();) { ViewportListener l = (ViewportListener) i.next(); l.zoomChanged(modelEnvelope); } } public void zoomToFullExtent() throws NoninvertibleTransformException { zoom(fullExtent()); } public Envelope fullExtent() { return EnvelopeUtil.bufferByFraction(panel.getLayerManager().getEnvelopeOfAllLayers(true), 0.03); } public void zoomToViewPoint(Point2D centreOfNewViewAsPerceivedByOldView, double zoomFactor) throws NoninvertibleTransformException { double widthOfNewViewAsPerceivedByOldView = panel.getWidth() / zoomFactor; double heightOfNewViewAsPerceivedByOldView = panel.getHeight() / zoomFactor; zoom( centreOfNewViewAsPerceivedByOldView, widthOfNewViewAsPerceivedByOldView, heightOfNewViewAsPerceivedByOldView); } public Collection toViewPoints(Collection modelCoordinates) throws NoninvertibleTransformException { ArrayList viewPoints = new ArrayList(); for (Iterator i = modelCoordinates.iterator(); i.hasNext();) { Coordinate modelCoordinate = (Coordinate) i.next(); viewPoints.add(toViewPoint(modelCoordinate)); } return viewPoints; } public Rectangle2D toViewRectangle(Envelope envelope) throws NoninvertibleTransformException { Point2D p1 = toViewPoint(new Coordinate(envelope.getMinX(), envelope.getMinY())); Point2D p2 = toViewPoint(new Coordinate(envelope.getMaxX(), envelope.getMaxY())); return new Rectangle2D.Double( Math.min(p1.getX(), p2.getX()), Math.min(p1.getY(), p2.getY()), Math.abs(p1.getX() - p2.getX()), Math.abs(p1.getY() - p2.getY())); } }