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.zoom;
034:
035: import java.awt.BasicStroke;
036: import java.awt.Color;
037: import java.awt.Shape;
038: import java.awt.geom.NoninvertibleTransformException;
039: import java.lang.reflect.InvocationTargetException;
040: import java.util.Collection;
041: import java.util.Iterator;
042:
043: import javax.swing.ImageIcon;
044:
045: import com.vividsolutions.jts.geom.Envelope;
046: import com.vividsolutions.jts.geom.Geometry;
047: import com.vividsolutions.jts.geom.GeometryCollection;
048: import com.vividsolutions.jts.geom.GeometryFactory;
049: import com.vividsolutions.jts.util.Assert;
050: import com.vividsolutions.jump.geom.CoordUtil;
051: import com.vividsolutions.jump.geom.EnvelopeUtil;
052: import com.vividsolutions.jump.workbench.WorkbenchContext;
053: import com.vividsolutions.jump.workbench.plugin.AbstractPlugIn;
054: import com.vividsolutions.jump.workbench.plugin.EnableCheckFactory;
055: import com.vividsolutions.jump.workbench.plugin.MultiEnableCheck;
056: import com.vividsolutions.jump.workbench.plugin.PlugInContext;
057: import com.vividsolutions.jump.workbench.ui.GUIUtil;
058: import com.vividsolutions.jump.workbench.ui.LayerViewPanel;
059: import com.vividsolutions.jump.workbench.ui.images.IconLoader;
060: import com.vividsolutions.jump.workbench.ui.renderer.ThreadQueue;
061:
062: /**
063: * Zoom to the features, then flash them.
064: */
065: public class ZoomToSelectedItemsPlugIn extends AbstractPlugIn {
066:
067: public boolean execute(PlugInContext context) throws Exception {
068: reportNothingToUndoYet(context);
069: zoom(context.getLayerViewPanel().getSelectionManager()
070: .getSelectedItems(), context.getLayerViewPanel());
071: return true;
072: }
073:
074: public void zoom(final Collection geometries,
075: final LayerViewPanel panel)
076: throws NoninvertibleTransformException {
077: if (envelope(geometries).isNull()) {
078: return;
079: }
080: Envelope proposedEnvelope = EnvelopeUtil.bufferByFraction(
081: envelope(geometries),
082: zoomBufferAsExtentFraction(geometries));
083:
084: if ((proposedEnvelope.getWidth() > panel.getLayerManager()
085: .getEnvelopeOfAllLayers().getWidth())
086: || (proposedEnvelope.getHeight() > panel
087: .getLayerManager().getEnvelopeOfAllLayers()
088: .getHeight())) {
089: //We've zoomed out farther than we would if we zoomed to all layers.
090: //This is too far. Set scale to that of zooming to all layers,
091: //and center on the selected features. [Jon Aquino]
092: proposedEnvelope = panel.getViewport().fullExtent();
093: EnvelopeUtil.translate(proposedEnvelope, CoordUtil
094: .subtract(
095: EnvelopeUtil.centre(envelope(geometries)),
096: EnvelopeUtil.centre(proposedEnvelope)));
097: }
098:
099: panel.getViewport().zoom(proposedEnvelope);
100: //Wait until the zoom is complete before executing the flash. [Jon Aquino]
101: ThreadQueue.Listener listener = new ThreadQueue.Listener() {
102: public void allRunningThreadsFinished() {
103: panel.getRenderingManager()
104: .getDefaultRendererThreadQueue().remove(this );
105: try {
106: GUIUtil.invokeOnEventThread(new Runnable() {
107: public void run() {
108: try {
109: flash(geometries, panel);
110: } catch (NoninvertibleTransformException e) {
111: }
112: }
113: });
114: } catch (InterruptedException e) {
115: } catch (InvocationTargetException e) {
116: }
117: }
118: };
119: panel.getRenderingManager().getDefaultRendererThreadQueue()
120: .add(listener);
121:
122: }
123:
124: private Envelope envelope(Collection geometries) {
125: Envelope envelope = new Envelope();
126:
127: for (Iterator i = geometries.iterator(); i.hasNext();) {
128: Geometry geometry = (Geometry) i.next();
129: envelope.expandToInclude(geometry.getEnvelopeInternal());
130: }
131:
132: return envelope;
133: }
134:
135: private double zoomBufferAsExtentFraction(Collection geometries) {
136: //Easiest to express zoomBuffer as a multiple of the average extent of
137: //the individual features, rather than a multiple of the average extent
138: //of the features combined. For example, 2 * the average extent of the
139: //features combined can be a huge zoomBuffer if the features are far
140: //apart. But if you consider the average extent of the individual features,
141: //you don't need to think about how far apart the features are. [Jon Aquino]
142: double zoomBuffer = 2 * averageExtent(geometries);
143: double averageFullExtent = averageFullExtent(geometries);
144:
145: if (averageFullExtent == 0) {
146: //Point feature. Just return 0. Rely on EnvelopeUtil#buffer to choose
147: //a reasonable buffer for point features. [Jon Aquino]
148: return 0;
149: }
150:
151: return zoomBuffer / averageFullExtent;
152: }
153:
154: private double averageExtent(Collection geometries) {
155: Assert.isTrue(!geometries.isEmpty());
156:
157: double extentSum = 0;
158:
159: for (Iterator i = geometries.iterator(); i.hasNext();) {
160: Geometry geometry = (Geometry) i.next();
161: extentSum += geometry.getEnvelopeInternal().getWidth();
162: extentSum += geometry.getEnvelopeInternal().getHeight();
163: }
164:
165: return extentSum / (2d * geometries.size());
166: //2 because width and height [Jon Aquino]
167: }
168:
169: private double averageFullExtent(Collection geometries) {
170: Envelope envelope = envelope(geometries);
171:
172: return (envelope.getWidth() + envelope.getHeight()) / 2d;
173: }
174:
175: public static MultiEnableCheck createEnableCheck(
176: WorkbenchContext workbenchContext) {
177: EnableCheckFactory checkFactory = new EnableCheckFactory(
178: workbenchContext);
179:
180: return new MultiEnableCheck()
181: .add(
182: checkFactory
183: .createWindowWithLayerViewPanelMustBeActiveCheck())
184: .add(
185: checkFactory
186: .createAtLeastNItemsMustBeSelectedCheck(1));
187: }
188:
189: public void flash(Collection geometries, final LayerViewPanel panel)
190: throws NoninvertibleTransformException {
191: final GeometryCollection gc = toGeometryCollection(geometries);
192:
193: if (!panel.getViewport().getEnvelopeInModelCoordinates()
194: .intersects(gc.getEnvelopeInternal())) {
195: return;
196: }
197:
198: panel.flash(gc);
199: }
200:
201: private GeometryCollection toGeometryCollection(
202: Collection geometries) {
203: return new GeometryFactory()
204: .createGeometryCollection((Geometry[]) geometries
205: .toArray(new Geometry[] {}));
206: }
207:
208: public ImageIcon getIcon() {
209: return IconLoader.icon("ZoomSelected.gif");
210: }
211: }
|