/[schmitzm]/branches/1.0-gt2-2.6/src/skrueger/geotools/XMapPane.java
ViewVC logotype

Contents of /branches/1.0-gt2-2.6/src/skrueger/geotools/XMapPane.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 531 - (show annotations)
Thu Nov 19 09:42:59 2009 UTC (15 years, 3 months ago) by alfonx
File size: 66429 byte(s)
Moved XMapPane from gtmig to skrueger.geotools
1 package skrueger.geotools;
2
3 import java.awt.Color;
4 import java.awt.Cursor;
5 import java.awt.Font;
6 import java.awt.Graphics;
7 import java.awt.Graphics2D;
8 import java.awt.Image;
9 import java.awt.Point;
10 import java.awt.Rectangle;
11 import java.awt.RenderingHints;
12 import java.awt.event.ActionEvent;
13 import java.awt.event.ActionListener;
14 import java.awt.event.ComponentAdapter;
15 import java.awt.event.ComponentEvent;
16 import java.awt.event.InputEvent;
17 import java.awt.event.MouseEvent;
18 import java.awt.event.MouseListener;
19 import java.awt.geom.AffineTransform;
20 import java.awt.geom.NoninvertibleTransformException;
21 import java.awt.geom.Point2D;
22 import java.awt.image.BufferedImage;
23 import java.io.IOException;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.Vector;
27
28 import javax.swing.Timer;
29
30 import org.apache.log4j.Logger;
31 import org.geotools.feature.FeatureCollection;
32 import org.geotools.geometry.jts.JTS;
33 import org.geotools.geometry.jts.ReferencedEnvelope;
34 import org.geotools.map.DefaultMapContext;
35 import org.geotools.map.MapContext;
36 import org.geotools.map.MapLayer;
37 import org.geotools.map.event.MapLayerEvent;
38 import org.geotools.map.event.MapLayerListEvent;
39 import org.geotools.map.event.MapLayerListListener;
40 import org.geotools.map.event.MapLayerListener;
41 import org.geotools.referencing.CRS;
42 import org.geotools.renderer.GTRenderer;
43 import org.geotools.renderer.label.LabelCacheImpl;
44 import org.geotools.renderer.lite.LabelCache;
45 import org.geotools.renderer.lite.StreamingRenderer;
46 import org.geotools.swing.JMapPane;
47 import org.geotools.swing.event.MapMouseEvent;
48 import org.geotools.swing.event.MapPaneEvent;
49 import org.geotools.swing.event.MapPaneListener;
50 import org.opengis.feature.simple.SimpleFeature;
51 import org.opengis.feature.simple.SimpleFeatureType;
52 import org.opengis.referencing.FactoryException;
53 import org.opengis.referencing.crs.CoordinateReferenceSystem;
54 import org.opengis.referencing.operation.MathTransform;
55 import org.opengis.referencing.operation.TransformException;
56
57 import schmitzm.geotools.GTUtil;
58 import schmitzm.geotools.JTSUtil;
59 import schmitzm.geotools.gui.SelectableXMapPane;
60 import schmitzm.geotools.map.event.JMapPaneListener;
61 import schmitzm.geotools.map.event.MapLayerAdapter;
62 import schmitzm.lang.LangUtil;
63 import schmitzm.swing.JPanel;
64 import schmitzm.swing.SwingUtil;
65
66 import com.vividsolutions.jts.geom.Coordinate;
67 import com.vividsolutions.jts.geom.Envelope;
68 import com.vividsolutions.jts.geom.Geometry;
69
70 /**
71 * The {@link XMapPane} class uses a Geotools {@link GTRenderer} to paint up to
72 * two {@link MapContext}s: a "local" {@link MapContext} and a "background"
73 * {@link MapContext}. The idea is, that rendering a background layer made up of
74 * e.g. OSM data, may take much longer than rendering local data.<br>
75 * Every {@link MapContext} is rendered on a {@link Thread} of it's own.
76 * Starting/ cancelling these threads is done by the {@link RenderingExecutor}.<br>
77 * <br>
78 * While the renderers are rending the map, a <br>
79 * The {@link XMapPane} is based on schmitzm {@link JPanel}, so
80 * {@link #print(Graphics)} will automatically set the background of components
81 * to pure white.
82 *
83 * The XMapPane has a {@link MouseListener} that manages zooming.<br>
84 * A logo/icon to float in the lower left corner may be set with
85 * {@link #setMapImage(BufferedImage)}<br>
86 *
87 * @see SelectableXMapPane - an extension of {@link XMapPane} that supports
88 * selecting features.
89 *
90 * @author stefan
91 *
92 */
93 public class XMapPane extends JPanel {
94
95 private static final int IMAGETYPE = BufferedImage.TYPE_INT_RGB;
96 private static final int IMAGETYPE_withAlpha = BufferedImage.TYPE_INT_ARGB;
97
98 private static Logger LOGGER = Logger.getLogger(XMapPane.class);
99
100 private boolean acceptsRepaintCalls = true;
101
102 /**
103 * Main {@link MapContext} that holds all layers that are rendered into the
104 * {@link #localImage} by the {@link #localRenderer}
105 */
106 MapContext localContext;
107
108 /**
109 * {@link MapContext} holding the background layers. Use it for layers that
110 * CAN take very long for rendering, like layer from the Internet: WMS, WFS,
111 * OSM...<br>
112 * <code>null</code> by default.
113 *
114 * @see #setBgContext(MapContext)
115 * @see #getBgContext()
116 */
117 MapContext bgContext;
118
119 /**
120 * While threads are working, calls {@link XMapPane#updateFinalImage()}
121 * regularly and {@link #repaint()}. This {@link Timer} is stopped when all
122 * renderers have finished.
123 *
124 * @see #INITIAL_REPAINT_DELAYAL
125 * @see #REPEATING_REPAINT_DELAY
126 */
127 final private Timer repaintTimer;
128
129 /**
130 * The initial delay in milliseconds until the {@link #finalImage} is
131 * updated the first time.
132 */
133 public static final int INITIAL_REPAINT_DELAY = 1000;
134
135 /**
136 * While the {@link #bgExecuter} and {@link #localExecuter} are rendering,
137 * the {@link #repaintTimer} is regularly updating the {@link #finalImage}
138 * with previews.
139 */
140 public static final long REPEATING_REPAINT_DELAY = 500;
141
142 /**
143 * Default delay (milliseconds) before the map will be redrawn when resizing
144 * the pane. This is to avoid flickering while drag-resizing.
145 */
146 public static final int DEFAULT_RESIZING_PAINT_DELAY = 500;
147
148 public static final int NONE = -123;
149
150 /**
151 * Flag fuer Modus "Kartenausschnitt bewegen". Nicht fuer Window-Auswahl
152 * moeglich!
153 *
154 * @see #setState(int)
155 */
156 public static final int PAN = 1;
157
158 /**
159 * Flag fuer Modus "SimpleFeature-Auswahl auf allen (sichtbaren) Layern".
160 *
161 * @see #setState(int)
162 * @see #setState(int)
163 */
164 public static final int SELECT_ALL = 103;
165 /**
166 * Flag fuer Modus
167 * "Auswahl nur eines Features, das erste sichtbare von Oben".
168 *
169 * @see #setState(int)
170 * @see #setState(int)
171 */
172 public static final int SELECT_ONE_FROM_TOP = 104;
173 /**
174 * Flag fuer Modus
175 * "SimpleFeature-Auswahl auf dem obersten (sichtbaren) Layer".
176 *
177 * @see #setState(int)
178 * @see #setState(int)
179 */
180 public static final int SELECT_TOP = 4;
181
182 public static final Cursor WAIT_CURSOR = Cursor
183 .getPredefinedCursor(Cursor.WAIT_CURSOR);
184
185 final static Font waitFont = new Font("Arial", Font.BOLD, 30);
186
187 /**
188 * Flag fuer Modus "Heran zoomen".
189 *
190 * @see #setState(int)
191 * @see #setState(int)
192 */
193 public static final int ZOOM_IN = 2;
194
195 /**
196 * Flag fuer Modus "Heraus zoomen". Nicht fuer Window-Auswahl moeglich!
197 *
198 * @see #setState(int)
199 */
200 public static final int ZOOM_OUT = 3;
201
202 /**
203 * Konvertiert die Maus-Koordinaten (relativ zum <code>JMapPane</code>) in
204 * Karten-Koordinaten.
205 *
206 * @param e
207 * Maus-Ereignis
208 */
209 public static Point2D getMapCoordinatesFromEvent(final MouseEvent e) {
210 // aktuelle Geo-Position aus GeoMouseEvent ermitteln
211 if (e != null && e instanceof MapMouseEvent)
212 try {
213 return ((MapMouseEvent) e).getMapPosition().toPoint2D();
214 } catch (final Exception err) {
215 LOGGER
216 .error(
217 "return ((GeoMouseEvent) e).getMapCoordinate(null).toPoint2D();",
218 err);
219 }
220
221 // aktuelle Geo-Position ueber Transformation des JMapPane berechnen
222 if (e != null && e.getSource() instanceof XMapPane) {
223
224 final XMapPane xMapPane = (XMapPane) e.getSource();
225
226 if (!xMapPane.isWellDefined())
227 return null;
228
229 final AffineTransform at = xMapPane.getScreenToWorld();
230 if (at != null)
231 return at.transform(e.getPoint(), null);
232 return null;
233 }
234 throw new IllegalArgumentException(
235 "MouseEvent has to be of instance MapMouseEvent or come from an XMapPane");
236 }
237
238 /**
239 * Listens to changes of the "background" {@link MapContext} and triggers
240 * repaints where needed.
241 */
242 private final MapLayerListListener bgContextListener = new MapLayerListListener() {
243
244 @Override
245 public void layerAdded(final MapLayerListEvent event) {
246 event.getLayer().addMapLayerListener(bgMapLayerListener);
247
248 if (localContext.getLayers().length == 0
249 && bgContext.getLayers().length == 1) { // the first one and
250 // localContext is
251 // empty
252 if (!setMapArea(localContext.getAreaOfInterest()))
253 requestStartRendering();
254 return;
255 }
256
257 // We need to redraw, even in case that the mapArea didn't change
258 // mapImageInvalid = true;
259 // repaint();
260 requestStartRendering();
261
262 }
263
264 @Override
265 public void layerChanged(final MapLayerListEvent event) {
266 // mapImageInvalid = true;
267 // repaint();
268 requestStartRendering();
269 }
270
271 @Override
272 public void layerMoved(final MapLayerListEvent event) {
273 // mapImageInvalid = true;
274 // repaint();
275 requestStartRendering();
276 }
277
278 @Override
279 public void layerRemoved(final MapLayerListEvent event) {
280 if (event.getLayer() != null)
281 event.getLayer().removeMapLayerListener(bgMapLayerListener);
282 // mapImageInvalid = true;
283 // repaint();
284 requestStartRendering();
285 }
286 };
287
288 /**
289 * compass and icon are rendered into this image
290 */
291 // protected BufferedImage gadgetsImage;
292
293 protected RenderingExecutor bgExecuter;
294
295 /**
296 * The Renderer for the LocalLayers uses this Image. When set to null,
297 * please dispose this {@link Graphics2D}
298 */
299 private BufferedImage localImage;
300
301 private BufferedImage finalImage;
302 /**
303 * If # {@link #bgExecuter} is using {@link #bgRenderer} for the Background
304 * uses this Image. When set to null, please dispose the {@link Graphics2D}
305 */
306 private BufferedImage bgImage;
307
308 /**
309 * Optionally a transparent image to paint over the map in the lower right
310 * corner.
311 *
312 * @see #addGadgets(Graphics2D)
313 * @see #setMapImage(BufferedImage)
314 **/
315 private BufferedImage mapImage = null;
316
317 /**
318 * Listens to each layer in the local {@link MapContext} for changes and
319 * triggers repaints.
320 */
321 protected MapLayerListener bgMapLayerListener = new MapLayerAdapter() {
322
323 @Override
324 public void layerChanged(final MapLayerEvent event) {
325 // Change of SLD for example
326 // mapImageInvalid = true;
327 // repaint();
328 requestStartRendering();
329 }
330
331 @Override
332 public void layerHidden(final MapLayerEvent event) {
333 // mapImageInvalid = true;
334 // repaint();
335 requestStartRendering();
336 }
337
338 @Override
339 public void layerShown(final MapLayerEvent event) {
340 // mapImageInvalid = true;
341 // repaint();
342 requestStartRendering();
343 }
344 };
345
346 /**
347 * A flag indicating if dispose() was already called. If true, then further
348 * use of this {@link SelectableXMapPane} is undefined.
349 */
350 private boolean disposed = false;
351
352 /**
353 * While dragging, the {@link #updateFinalImage()} method is translating the
354 * cached images while setting it together.
355 **/
356 Point imageOrigin = new Point(0, 0);
357 /**
358 * For every rendering thread started,
359 * {@link GTUtil#createGTRenderer(MapContext)} is called to create a new
360 * renderer. These Java2D rendering hints are passed to the
361 * {@link GTRenderer}. The java2dHints are the same for all renderers (bg
362 * and local).
363 */
364 private RenderingHints java2dHints;
365
366 protected LabelCache labelCache = new LabelCacheImpl();
367
368 /**
369 * Listens to changes of the "local" {@link MapContext} and triggers
370 * repaints where needed.
371 */
372 private final MapLayerListListener localContextListener = new MapLayerListListener() {
373
374 @Override
375 public void layerAdded(final MapLayerListEvent event) {
376 event.getLayer().addMapLayerListener(localMapLayerListener);
377
378 localRenderer.setContext(getContext());
379
380 if (localContext.getLayers().length == 1) { // the first one
381 // if the Area of Interest is unset, the LayerBounds are used
382 if (!setMapArea(localContext.getAreaOfInterest()))
383 repaint();
384
385 return;
386 }
387
388 // We need to redraw, even in case that the mapArea didn't change
389 // mapImageInvalid = true;
390 // repaint();
391 requestStartRendering();
392
393 }
394
395 @Override
396 public void layerChanged(final MapLayerListEvent event) {
397 // mapImageInvalid = true;
398 // repaint();
399 localRenderer.setContext(getContext());
400 requestStartRendering();
401 }
402
403 @Override
404 public void layerMoved(final MapLayerListEvent event) {
405 // mapImageInvalid = true;
406 // repaint();
407 localRenderer.setContext(getContext());
408 requestStartRendering();
409 }
410
411 @Override
412 public void layerRemoved(final MapLayerListEvent event) {
413 if (event.getLayer() != null)
414 event.getLayer().removeMapLayerListener(localMapLayerListener);
415 // mapImageInvalid = true;
416 // repaint();
417 localRenderer.setContext(getContext());
418 requestStartRendering();
419 }
420 };
421
422 private final RenderingExecutor localExecuter = new RenderingExecutor(this);
423
424 /**
425 * Listens to each layer in the local {@link MapContext} for changes and
426 * triggers repaints.
427 */
428 protected MapLayerListener localMapLayerListener = new MapLayerAdapter() {
429
430 @Override
431 public void layerChanged(final MapLayerEvent event) {
432 localRenderer.setContext(getContext()); // betters for SLD changes?!
433 // Change of SLD for example
434 // mapImageInvalid = true;
435 // repaint();
436 requestStartRendering();
437 }
438
439 @Override
440 public void layerHidden(final MapLayerEvent event) {
441 // mapImageInvalid = true;
442 // repaint();
443 requestStartRendering();
444 }
445
446 @Override
447 public void layerShown(final MapLayerEvent event) {
448 // mapImageInvalid = true;
449 // repaint();
450 requestStartRendering();
451 }
452 };
453
454 private final GTRenderer localRenderer = GTUtil.createGTRenderer();
455
456 private final GTRenderer bgRenderer = GTUtil.createGTRenderer();
457
458 /**
459 * the area of the map to draw
460 */
461 protected Envelope mapArea = null;
462
463 /**
464 * A flag set it {@link #setMapArea(Envelope)} to indicated the
465 * {@link #paintComponent(Graphics)} method, that the image on-screen is
466 * associated with {@link #oldMapArea} and may hence be scaled for a quick
467 * preview.<br>
468 * The flag is reset
469 **/
470 private boolean mapAreaChanged = false;
471
472 /**
473 * This color is used as the default background color when painting a map.
474 */
475 private Color mapBackgroundColor = Color.WHITE;
476
477 /**
478 * A flag indicating that the shown image is invalid and needs to be
479 * re-rendered.
480 */
481 protected boolean mapImageInvalid = true;
482
483 /**
484 * Holds a flag for each layer, whether it is regarded or ignored on
485 * {@link #SELECT_TOP}, {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP}
486 * actions.
487 */
488 final protected HashMap<MapLayer, Boolean> mapLayerSelectable = new HashMap<MapLayer, Boolean>();
489
490 /**
491 * List of listeners of this {@link XMapPane}
492 */
493 protected Vector<JMapPaneListener> mapPaneListeners = new Vector<JMapPaneListener>();
494 /**
495 * If not <code>null</code>, the {@link XMapPane} will not allow to zoom/pan
496 * out of that area
497 **/
498 private Envelope maxExtend = null;
499 private Double maxZoomScale = Double.MIN_VALUE;
500
501 private Double minZoomScale = Double.MAX_VALUE;
502
503 /**
504 * We store the old mapArea for a moment to use it for the
505 * "quick scaled preview" in case of ZoomOut
506 **/
507 protected Envelope oldMapArea = null;
508
509 /**
510 * We store the old transform for a moment to use it for the
511 * "quick scaled preview" in case of ZoomIn
512 **/
513 protected AffineTransform oldScreenToWorld = null;
514
515 /**
516 * A flag indicating, that the image size has changed and the buffered
517 * images are not big enough any more
518 **/
519 protected boolean paneResized = false;
520
521 private BufferedImage preFinalImage;
522
523 // if null, no quick preview will be shown
524 private int quickPreviewHint = 0;
525
526 private Map<Object, Object> rendererHints;
527
528 private volatile Boolean requestStartRendering = false;
529
530 private final Timer resizeTimer;
531
532 private final int resizingPaintDelay;
533
534 /**
535 * Transformation zwischen Fenster-Koordinaten und Karten-Koordinaten
536 * (lat/lon)
537 */
538 protected AffineTransform screenToWorld = null;
539
540 /**
541 * The flag {@link #requestStartRendering} can be set to true by events.
542 * This {@link Timer} checks the flag regularly and starts one renderer
543 * thread.
544 */
545 final private Timer startRenderThreadsTimer;
546
547 /**
548 * The default state is ZOOM_IN, hence by default the
549 * {@link #zoomMapPaneMouseListener} is also enabled.
550 **/
551 private int state = ZOOM_IN;
552
553 /**
554 * Manuell gesetzter statischer Cursor, unabhaengig von der aktuellen
555 * MapPane-Funktion
556 */
557 protected Cursor staticCursor = null;
558
559 private AffineTransform worldToScreen;
560
561 /**
562 * This {@link MouseListener} is managing all zoom related tasks
563 */
564 public final ZoomXMapPaneMouseListener zoomMapPaneMouseListener = new ZoomXMapPaneMouseListener(
565 this);
566
567 // TODO doku
568 public XMapPane() {
569 this(null, null);
570 }
571
572 /**
573 * full constructor extending JPanel
574 *
575 * @param rendererHints
576 *
577 * @param layout
578 * - layout (probably shouldn't be set)
579 * @param isDoubleBuffered
580 * - a Swing thing I don't really understand
581 * @param render
582 * - what to draw the map with
583 * @param localContext
584 * - {@link MapContext} of layer to render.
585 */
586 public XMapPane(final MapContext localContext_,
587 final Map<Object, Object> rendererHints) {
588 super(true);
589
590 setRendererHints(rendererHints);
591
592 setOpaque(true);
593
594 localRenderer.setJava2DHints(getJava2dHints());
595
596 if (localContext_ != null)
597 setLocalContext(localContext_);
598
599 /**
600 * Adding the #zoomMapPaneMouseListener
601 */
602 this.addMouseListener(zoomMapPaneMouseListener);
603 this.addMouseMotionListener(zoomMapPaneMouseListener);
604 this.addMouseWheelListener(zoomMapPaneMouseListener);
605
606 /*
607 * We use a Timer object to avoid rendering delays and flickering when
608 * the user is drag-resizing the parent container of this map pane.
609 *
610 * Using a ComponentListener doesn't work because, unlike a JFrame, the
611 * pane receives a stream of events during drag-resizing.
612 */
613 resizingPaintDelay = DEFAULT_RESIZING_PAINT_DELAY;
614 resizeTimer = new Timer(resizingPaintDelay, new ActionListener() {
615
616 public void actionPerformed(final ActionEvent e) {
617 paneResized = true;
618
619 if (!isWellDefined())
620 return;
621
622 final Rectangle bounds = getVisibleRect();
623
624 final Envelope geoMapArea = tranformWindowToGeo(bounds.x,
625 bounds.y, bounds.x + bounds.width, bounds.y
626 + bounds.height);
627
628 setMapArea(bestAllowedMapArea(geoMapArea));
629 }
630 });
631 resizeTimer.setRepeats(false);
632 this.addComponentListener(new ComponentAdapter() {
633
634 @Override
635 public void componentResized(final ComponentEvent e) {
636 if (bgExecuter != null)
637 bgExecuter.cancelTask();
638 if (localExecuter != null)
639 localExecuter.cancelTask();
640 resizeTimer.restart();
641 }
642
643 });
644
645 /*
646 * Setting up the repaintTimer. Not started automatically.
647 */
648 repaintTimer = new Timer((int) REPEATING_REPAINT_DELAY,
649 new ActionListener() {
650
651 @Override
652 public void actionPerformed(final ActionEvent e) {
653 updateFinalImage();
654 XMapPane.this.repaint();
655 }
656 });
657 repaintTimer.setInitialDelay(INITIAL_REPAINT_DELAY);
658 repaintTimer.setRepeats(true);
659
660 /*
661 * Setting up the startRenderThreadsTimer. This Timer starts
662 * automatically.
663 */
664 startRenderThreadsTimer = new Timer(100, new ActionListener() {
665
666 @Override
667 public void actionPerformed(final ActionEvent e) {
668 synchronized (requestStartRendering) {
669 if (requestStartRendering && isWellDefined()) {
670
671 if (localExecuter.isRunning()) {
672 localExecuter.cancelTask();
673 } else {
674 requestStartRendering = false;
675 startRendering();
676 }
677 }
678 }
679 }
680 });
681 startRenderThreadsTimer.start();
682
683 }
684
685 /**
686 * Fuegt der Map einen Listener hinzu.
687 *
688 * @param l
689 * neuer Listener
690 */
691 public void addMapPaneListener(final JMapPaneListener l) {
692 mapPaneListeners.add(l);
693 }
694
695 /**
696 * Korrigiert den {@link Envelope} aka {@code mapArea} auf die beste
697 * erlaubte Flaeche damit die Massstabsbeschaenkungen noch eingehalten
698 * werden, FALLS der uebergeben Envelope nicht schon gueltig sein sollte.<br>
699 * Since 21. April 09: Before thecalculation starts, the aspect ratio is
700 * corrected. This change implies, that setMapArea() will most of the time
701 * not allow setting to a wrong aspectRatio.
702 *
703 * @author <a href="mailto:[email protected]">Stefan Alfons
704 * Kr&uuml;ger</a>
705 */
706 public Envelope bestAllowedMapArea(Envelope env) {
707 if (getWidth() == 0)
708 return env;
709 if (env == null)
710 return null;
711
712 Envelope newArea = null;
713
714 /**
715 * Correct the aspect Ratio before we check the rest. Otherwise we might
716 * easily fail. We allow to grow here, because we don't check against
717 * the maxExtend
718 */
719 final Rectangle curPaintArea = getVisibleRect();
720
721 env = JTSUtil.fixAspectRatio(curPaintArea, env, true);
722
723 final double scale = env.getWidth() / getWidth();
724 final double centerX = env.getMinX() + env.getWidth() / 2.;
725 final double centerY = env.getMinY() + env.getHeight() / 2.;
726 double newWidth2 = 0;
727 double newHeight2 = 0;
728 if (scale < getMaxZoomScale()) {
729 // ****************************************************************************
730 // Wir zoomen weiter rein als erlaubt => Anpassen des envelope
731 // ****************************************************************************
732 newWidth2 = getMaxZoomScale() * getWidth() / 2.;
733 newHeight2 = getMaxZoomScale() * getHeight() / 2.;
734 } else if (scale > getMinZoomScale()) {
735 // ****************************************************************************
736 // Wir zoomen weiter raus als erlaubt => Anpassen des envelope
737 // ****************************************************************************
738 newWidth2 = getMinZoomScale() * getWidth() / 2.;
739 newHeight2 = getMinZoomScale() * getHeight() / 2.;
740 } else {
741 // ****************************************************************************
742 // Die mapArea / der Envelope ist ist gueltig! Keine Aenderungen
743 // ****************************************************************************
744 newArea = env;
745 }
746
747 if (newArea == null) {
748
749 final Coordinate ll = new Coordinate(centerX - newWidth2, centerY
750 - newHeight2);
751 final Coordinate ur = new Coordinate(centerX + newWidth2, centerY
752 + newHeight2);
753
754 newArea = new Envelope(ll, ur);
755 }
756
757 final Envelope maxAllowedExtend = getMaxExtend();
758 while (maxAllowedExtend != null && !maxAllowedExtend.contains(newArea)
759 && newArea != null && !newArea.isNull()
760 && !Double.isNaN(newArea.getMinX())
761 && !Double.isNaN(newArea.getMaxX())
762 && !Double.isNaN(newArea.getMinY())
763 && !Double.isNaN(newArea.getMaxY())) {
764 /*
765 * If a maxExtend is set, we have to honour that...
766 */
767
768 // Exceeds top? Move down and maybe cut
769 if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {
770 final double divY = newArea.getMaxY()
771 - maxAllowedExtend.getMaxY();
772 // LOGGER.debug("Moving area down by " + divY);
773
774 newArea = new Envelope(new Coordinate(newArea.getMinX(),
775 newArea.getMinY() - divY), new Coordinate(newArea
776 .getMaxX(), newArea.getMaxY() - divY));
777
778 if (newArea.getMinY() < maxAllowedExtend.getMinY()) {
779 // LOGGER.debug("Now it exeeds the bottom border.. cut!");
780 // And cut the bottom if it moved out of the area
781 newArea = new Envelope(new Coordinate(newArea.getMinX(),
782 maxAllowedExtend.getMinY()), new Coordinate(newArea
783 .getMaxX(), newArea.getMaxY()));
784
785 // LOGGER.debug("and fix aspect ratio");
786
787 newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
788 false);
789 }
790 }
791
792 // Exceeds bottom? Move up and maybe cut
793 if (newArea.getMinY() < maxAllowedExtend.getMinY()) {
794 final double divY = newArea.getMinY()
795 - maxAllowedExtend.getMinY();
796 // LOGGER.debug("Moving area up by " + divY);
797
798 newArea = new Envelope(new Coordinate(newArea.getMinX(),
799 newArea.getMinY() - divY), new Coordinate(newArea
800 .getMaxX(), newArea.getMaxY() - divY));
801
802 if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {
803 // LOGGER.debug("Now it exeeds the top border.. cut!");
804 // And cut the bottom if it moved out of the area
805 newArea = new Envelope(new Coordinate(newArea.getMinX(),
806 newArea.getMinY()), new Coordinate(newArea
807 .getMaxX(), maxAllowedExtend.getMaxY()));
808
809 // LOGGER.debug("and fix aspect ratio");
810
811 newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
812 false);
813 }
814 }
815
816 // Exceeds to the right? move and maybe cut
817 if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {
818
819 // Move left..
820 final double divX = newArea.getMaxX()
821 - maxAllowedExtend.getMaxX();
822 // LOGGER.debug("Moving area left by " + divX);
823
824 newArea = new Envelope(new Coordinate(newArea.getMinX() - divX,
825 newArea.getMinY()), new Coordinate(newArea.getMaxX()
826 - divX, newArea.getMaxY()));
827
828 if (newArea.getMinX() < maxAllowedExtend.getMinX()) {
829 // LOGGER.debug("Now it exeeds the left border.. cut!");
830 // And cut the left if it moved out of the area
831 newArea = new Envelope(new Coordinate(maxAllowedExtend
832 .getMinX(), newArea.getMinY()), new Coordinate(
833 newArea.getMaxX(), newArea.getMaxY()));
834
835 // LOGGER.debug("and fix aspect ratio");
836
837 newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
838 false);
839 }
840 }
841
842 // Exceeds to the left? move and maybe cut
843 if (newArea.getMinX() < maxAllowedExtend.getMinX()) {
844
845 // Move right..
846 final double divX = newArea.getMinX()
847 - maxAllowedExtend.getMinX();
848 // LOGGER.debug("Moving area right by " + divX);
849
850 newArea = new Envelope(new Coordinate(newArea.getMinX() - divX,
851 newArea.getMinY()), new Coordinate(newArea.getMaxX()
852 - divX, newArea.getMaxY()));
853
854 if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {
855 // LOGGER.debug("Now it exeeds the right border.. cut!");
856 // And cut the left if it moved out of the area
857 newArea = new Envelope(new Coordinate(newArea.getMinX(),
858 newArea.getMinY()), new Coordinate(maxAllowedExtend
859 .getMaxX(), newArea.getMaxY()));
860
861 // LOGGER.debug("and fix aspect ratio");
862
863 newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
864 false);
865 }
866 }
867 }
868
869 return newArea;
870 }
871
872 /**
873 * Should be called when the {@link JMapPane} is not needed no more to help
874 * the GarbageCollector
875 *
876 * Removes all {@link JMapPaneListener}s that are registered
877 *
878 * @author <a href="mailto:[email protected]">Stefan Alfons
879 * Kr&uuml;ger</a>
880 */
881 public void dispose() {
882 if (isDisposed())
883 return;
884
885 setPainting(false);
886
887 resizeTimer.stop();
888 startRenderThreadsTimer.stop();
889
890 disposed = true;
891
892 if (bgExecuter != null) {
893 bgExecuter.cancelTask();
894 bgExecuter.dispose();
895 }
896
897 if (localExecuter != null) {
898 int i = 0;
899 localExecuter.cancelTask();
900 while (i++ < 10 && localExecuter.isRunning()) {
901 try {
902 Thread.sleep(200);
903 } catch (final InterruptedException e) {
904 // TODO Auto-generated catch block
905 e.printStackTrace();
906 }
907 }
908 if (localExecuter.isRunning()) {
909 System.out
910 .println("BAD BAD BAD... still running the thread....");
911 }
912 localExecuter.dispose();
913 }
914 //
915 // if (bgImage != null) {
916 // bgImage = dispose(bgImage);
917 // bgImage = null;
918 // // LangUtil.gcTotal();
919 // }
920 //
921 // if (localImage != null) {
922 // localImage = dispose(localImage);
923 // localImage = null;
924 // // LangUtil.gcTotal();
925 // }
926 //
927 // if (finalImage != null) {
928 // finalImage = dispose(finalImage);
929 // finalImage = null;
930 // // LangUtil.gcTotal();
931 // }
932 //
933 // if (preFinalImage != null) {
934 // preFinalImage = dispose(preFinalImage);
935 // preFinalImage = null;
936 // }
937 disposeImages();
938
939 // LangUtil.gcTotal();
940
941 // Alle mapPaneListener entfernen
942 mapPaneListeners.clear();
943
944 removeMouseMotionListener(zoomMapPaneMouseListener);
945 removeMouseListener(zoomMapPaneMouseListener);
946
947 if (localContext != null)
948 getContext().clearLayerList();
949 if (bgContext != null)
950 getBgContext().clearLayerList();
951
952 removeAll();
953 }
954
955 /**
956 * Draws a rectangle in XOR mode from the origin at {@link #startPos} to the
957 * given point. All in screen coordinates.
958 */
959 protected void drawRectangle(final Graphics graphics, final Point startPos,
960 final Point e) {
961
962 if (!isWellDefined())
963 return;
964
965 // undraw last box/draw new box
966 final int left = Math.min(startPos.x, e.x);
967 final int right = Math.max(startPos.x, e.x);
968 final int top = Math.max(startPos.y, e.y);
969 final int bottom = Math.min(startPos.y, e.y);
970 final int width = right - left;
971 final int height = top - bottom;
972
973 if (width == 0 && height == 0)
974 return;
975
976 graphics.setXORMode(Color.WHITE);
977 graphics.drawRect(left, bottom, width, height);
978 }
979
980 /**
981 * Diretly paints scaled preview into the {@link SelectableXMapPane}. Used
982 * to give the user something to look at while we are rendering. Method
983 * should be called after {@link #setMapArea(Envelope)} has been set to the
984 * new mapArea and transform has been reset.<br>
985 *
986 * @param g
987 * Graphics2D to paint the preview into
988 *
989 * @param state
990 * Max be {@link #ZOOM_IN} or {@link #ZOOM_OUT}
991 */
992 protected boolean drawScaledPreviewImage_Zoom(final Graphics2D graphics) {
993
994 if (quickPreviewHint == 0)
995 return false;
996
997 graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
998 RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
999 graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1000 RenderingHints.VALUE_ANTIALIAS_OFF);
1001 graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
1002 RenderingHints.VALUE_RENDER_SPEED);
1003
1004 if (oldMapArea == null)
1005 return false;
1006
1007 final Rectangle visibleArea = getVisibleRect();
1008
1009 // Calculate the oldMapArea in the current WindowCoordinates:
1010 final Envelope oldMapWindow = tranformGeoToWindow(oldMapArea.getMinX(),
1011 oldMapArea.getMinY(), oldMapArea.getMaxX(), oldMapArea
1012 .getMaxY(), null);
1013
1014 final int xx1 = (int) Math.round(oldMapWindow.getMinX());
1015 final int yy1 = (int) Math.round(oldMapWindow.getMinY());
1016 final int xx2 = (int) Math.round(oldMapWindow.getMaxX());
1017 final int yy2 = (int) Math.round(oldMapWindow.getMaxY());
1018
1019 graphics.drawImage(getPreFinalImage(), xx1, yy1, xx2, yy2,
1020 (int) visibleArea.getMinX(), (int) visibleArea.getMinY(),
1021 (int) visibleArea.getMaxX(), (int) visibleArea.getMaxY(),
1022 getMapBackgroundColor(), null);
1023
1024 final Rectangle painedArea = new Rectangle(xx1, yy1, xx2 - xx1, yy2
1025 - yy1);
1026
1027 SwingUtil.clearAround(graphics, painedArea, visibleArea);
1028
1029 addGadgets(graphics);
1030
1031 quickPreviewHint = 0;
1032
1033 graphics.dispose();
1034
1035 // Something has been drawn
1036 return true;
1037 }
1038
1039 public MapContext getBgContext() {
1040 return bgContext;
1041 }
1042
1043 /**
1044 * Lazyly initializes a {@link BufferedImage} for the background renderer.
1045 */
1046 private Image getBgImage() {
1047 //
1048 // if (bgImage == null) {
1049 // bgImage = createImage(getBounds().width, getBounds().height);
1050 // }
1051
1052 return bgImage;
1053 }
1054
1055 public MapContext getContext() {
1056 if (localContext == null) {
1057 setLocalContext(new DefaultMapContext());
1058 }
1059 return localContext;
1060 }
1061
1062 private BufferedImage getFinalImage() {
1063 //
1064 if (finalImage == null) {
1065 // Rectangle curPaintArea = getVisibleRect();
1066 finalImage = new BufferedImage(getBounds().width,
1067 getBounds().height, IMAGETYPE);
1068
1069 requestStartRendering();
1070 }
1071 return finalImage;
1072 }
1073
1074 public RenderingHints getJava2dHints() {
1075 return java2dHints;
1076 }
1077
1078 /**
1079 * Lazyly initializes a {@link BufferedImage} for the background renderer.
1080 */
1081 private BufferedImage getLocalImage() {
1082
1083 if (localImage == null) {
1084 localImage = new BufferedImage(getBounds().width,
1085 getBounds().height, IMAGETYPE_withAlpha);
1086 }
1087
1088 return localImage;
1089 }
1090
1091 /**
1092 * Returns a copy of the mapArea
1093 *
1094 * @return
1095 */
1096 public Envelope getMapArea() {
1097 if (mapArea == null) {
1098 ReferencedEnvelope mapArea_ = null;
1099 try {
1100 mapArea_ = localContext.getLayerBounds();
1101 } catch (final IOException e) {
1102 LOGGER.warn("context.getLayerBounds()", e);
1103 }
1104
1105 if (mapArea_ != null) {
1106 mapImageInvalid = true; /* note we need to redraw */
1107 // setMapArea(mapArea_); // results in a loop
1108 mapArea = bestAllowedMapArea(mapArea_);
1109 }
1110 }
1111
1112 if (mapArea == null)
1113 return null;
1114
1115 return new Envelope(mapArea);
1116 }
1117
1118 /**
1119 * Returns the background {@link Color} of the map pane. Default is white.
1120 **/
1121 public Color getMapBackgroundColor() {
1122 return mapBackgroundColor;
1123 }
1124
1125 /**
1126 * Get the BufferedImage to use as a flaoting icon in the lower right
1127 * corner.
1128 *
1129 * @return <code>null</code> if the feature is deactivated.
1130 */
1131 public BufferedImage getMapImage() {
1132 return mapImage;
1133 }
1134
1135 /**
1136 * Returns the evelope of the viewable area. The JMapPane will never show
1137 * anything outside of this extend. If this has been set to
1138 * <code>null</code> via {@link #setMaxExtend(Envelope)}, it tries to return
1139 * quickly the context's bounds. It it takes to long to determine the
1140 * context bounds, <code>null</code> is returned.
1141 *
1142 * @param maxExtend
1143 * <code>null</code> to not have this restriction.
1144 */
1145
1146 public Envelope getMaxExtend() {
1147 if (maxExtend == null) {
1148 final ReferencedEnvelope layerBounds = GTUtil
1149 .getVisibleLayoutBounds(localContext);
1150 if (layerBounds == null) {
1151 // TODO Last fallback could be the CRS valid area
1152 return null;
1153 }
1154
1155 // Kartenbereich um 10% vergroessern
1156 return JTSUtil.fixAspectRatio(this.getBounds(), JTSUtil
1157 .expandEnvelope(layerBounds, 0.1), true);
1158 }
1159 return maxExtend;
1160 }
1161
1162 /**
1163 * Retuns the maximum allowed zoom scale. This is the smaller number value
1164 * of the two. Defaults to {@link Double}.MIN_VALUE
1165 *
1166 * @author <a href="mailto:[email protected]">Stefan Alfons
1167 * Kr&uuml;ger</a>
1168 */
1169 public Double getMaxZoomScale() {
1170 return maxZoomScale;
1171 }
1172
1173 /**
1174 * Retuns the minimum allowed zoom scale. This is the bigger number value of
1175 * the two. Defaults to {@link Double}.MAX_VALUE
1176 *
1177 * @author <a href="mailto:[email protected]">Stefan Alfons
1178 * Kr&uuml;ger</a>
1179 */
1180 public Double getMinZoomScale() {
1181 return minZoomScale;
1182 }
1183
1184 private Image getPreFinalImage() {
1185 // if (preFinalImage == null) {
1186 //
1187 // // Rectangle curPaintArea = getVisibleRect();
1188 // // preFinalImage = new BufferedImage(curPaintArea.width,
1189 // // curPaintArea.height, BufferedImage.TYPE_INT_RGB);
1190 //
1191 // preFinalImage = createImage(getBounds().width, getBounds().height);
1192 //
1193 // requestStartRendering();
1194 // }
1195 return preFinalImage;
1196 }
1197
1198 public Map<Object, Object> getRendererHints() {
1199 return rendererHints;
1200 }
1201
1202 /**
1203 * Liefert eine affine Transformation, um von den Fenster-Koordinaten in die
1204 * Karten-Koordinaten (Lat/Lon) umzurechnen.
1205 *
1206 * @return eine Kopie der aktuellen Transformation; <code>null</code> wenn
1207 * noch keine Karte angezeigt wird
1208 */
1209 public AffineTransform getScreenToWorld() {
1210 if (screenToWorld == null)
1211 resetTransforms();
1212 // nur Kopie der Transformation zurueckgeben!
1213 if (screenToWorld == null)
1214 return null;
1215 return new AffineTransform(screenToWorld);
1216 }
1217
1218 public int getState() {
1219 return state;
1220 }
1221
1222 /**
1223 * Liefert den statisch eingestellten Cursor, der unabhaengig von der
1224 * eingestellten MapPane-Aktion (Zoom, Auswahl, ...) verwendet wird.
1225 *
1226 * @return {@code null}, wenn kein statischer Cursor verwendet, sondern der
1227 * Cursor automatisch je nach MapPane-Aktion eingestellt wird.
1228 */
1229 public Cursor getStaticCursor() {
1230 return this.staticCursor;
1231 }
1232
1233 public AffineTransform getWorldToScreenTransform() {
1234 if (worldToScreen == null) {
1235 resetTransforms();
1236 }
1237 // nur Kopie der Transformation zurueckgeben!
1238 return new AffineTransform(worldToScreen);
1239 }
1240
1241 /**
1242 * A flag indicating if dispose() has already been called. If true, then
1243 * further use of this {@link SelectableXMapPane} is undefined.
1244 */
1245 private boolean isDisposed() {
1246 return disposed;
1247 }
1248
1249 /**
1250 * Returns whether a layer is regarded or ignored on {@link #SELECT_TOP},
1251 * {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP} actions. Returns
1252 * <code>true</code> if the selectability has not been defined.
1253 *
1254 * @param layer
1255 * a layer
1256 */
1257 public boolean isMapLayerSelectable(final MapLayer layer) {
1258 final Boolean selectable = mapLayerSelectable.get(layer);
1259 return selectable == null ? true : selectable;
1260 }
1261
1262 /**
1263 * Return <code>true</code> if a CRS and a {@link #mapArea} are set and the
1264 * {@link XMapPane} is visible and has bounds set.
1265 */
1266 public boolean isWellDefined() {
1267
1268 try {
1269
1270 if (getContext() == null)
1271 return false;
1272 if (getContext().getLayerCount() <= 0)
1273 return false;
1274 if (getMapArea() == null)
1275 return false;
1276 if (getBounds().getWidth() == 0)
1277 return false;
1278 if (getBounds().getHeight() == 0)
1279 return false;
1280 } catch (final Exception e) {
1281 return false;
1282 }
1283 return true;
1284 }
1285
1286 public void mouseDragged(final Point startPos, final Point lastPos,
1287 final MouseEvent event) {
1288
1289 if ((getState() == XMapPane.PAN)
1290 || ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0)) {
1291
1292 if (getCursor() != SwingUtil.PANNING_CURSOR) {
1293 setCursor(SwingUtil.PANNING_CURSOR);
1294
1295 // While panning, we deactivate the rendering. So the tasts are
1296 // ready to start when the panning os done.
1297 if (bgExecuter != null)
1298 bgExecuter.cancelTask();
1299 if (localExecuter != null)
1300 localExecuter.cancelTask();
1301 }
1302
1303 if (lastPos.x > 0 && lastPos.y > 0) {
1304 final int dx = event.getX() - lastPos.x;
1305 final int dy = event.getY() - lastPos.y;
1306
1307 // TODO Stop dragging when the drag would not be valid...
1308 // boolean dragValid = true;
1309 // // check if this panning results in a valid mapArea
1310 // {
1311 // Rectangle winBounds = xMapPane.getBounds();
1312 // winBounds.translate(xMapPane.imageOrigin.x,
1313 // -xMapPane.imageOrigin.y);
1314 // Envelope newMapAreaBefore = xMapPane.tranformWindowToGeo(
1315 // winBounds.x, winBounds.y, winBounds.x
1316 // + winBounds.width, winBounds.y
1317 // + winBounds.height);
1318 //
1319 //
1320 // winBounds = xMapPane.getBounds();
1321 // Point testIng = new Point(xMapPane.imageOrigin);
1322 // testIng.translate(dx, dy);
1323 // winBounds.translate(testIng.x, -testIng.y);
1324 // Envelope newMapAreaAfter = xMapPane.tranformWindowToGeo(
1325 // winBounds.x, winBounds.y, winBounds.x
1326 // + winBounds.width, winBounds.y
1327 // + winBounds.height);
1328 //
1329 // // If the last drag doesn't change the MapArea anymore cancel
1330 // it.
1331 // if (xMapPane.bestAllowedMapArea(newMapAreaAfter).equals(
1332 // xMapPane.bestAllowedMapArea(newMapAreaBefore))){
1333 // dragValid = false;
1334 // return;
1335 // }
1336 // }
1337
1338 imageOrigin.translate(dx, dy);
1339 updateFinalImage();
1340 repaint();
1341 }
1342
1343 } else if ((getState() == XMapPane.ZOOM_IN)
1344 || (getState() == XMapPane.ZOOM_OUT)
1345 || (getState() == XMapPane.SELECT_ALL)
1346 || (getState() == XMapPane.SELECT_TOP)
1347 // || (getState() == XMapPane.SELECT_ONE_FROM_TOP)
1348 ) {
1349 final Graphics graphics = getGraphics();
1350
1351 drawRectangle(graphics, startPos, event.getPoint());
1352
1353 if ((lastPos.x > 0) && (lastPos.y > 0)) {
1354 drawRectangle(graphics, startPos, lastPos);
1355 }
1356
1357 graphics.dispose();
1358
1359 }
1360
1361 }
1362
1363 /**
1364 * Called by the {@link RenderingExecutor} when rendering was cancelled.
1365 */
1366 public void onRenderingCancelled() {
1367 repaintTimer.stop();
1368 LOGGER.debug("Rendering cancelled");
1369 }
1370
1371 /**
1372 * Called by the {@link RenderingExecutor} when rendering has been
1373 * completed.
1374 */
1375 public void onRenderingCompleted() {
1376 repaintTimer.stop();
1377 updateFinalImage();
1378 repaint();
1379 }
1380
1381 /**
1382 * Called by the {@linkplain XMapPane.RenderingTask} when rendering failed.
1383 * Publishes a {@linkplain MapPaneEvent} of type {@code
1384 * MapPaneEvent.Type.RENDERING_STOPPED} to listeners.
1385 *
1386 * @param renderingError
1387 * The error that occured during rendering
1388 *
1389 * @see MapPaneListener#onRenderingStopped(org.geotools.swing.event.MapPaneEvent)
1390 */
1391 public void onRenderingFailed(final Exception renderingError) {
1392 repaintTimer.stop();
1393 LOGGER.warn("Rendering failed", renderingError);
1394 updateFinalImage();
1395 repaint();
1396
1397 }
1398
1399 public void onRenderingPending() {
1400 // LOGGER.debug("Pending rendering updates the preview...");
1401 updateFinalImage();
1402 repaint();
1403 }
1404
1405 @Override
1406 protected void paintComponent(final Graphics g) {
1407 if (!acceptsRepaintCalls)
1408 return;
1409
1410 // Maybe update the cursor and maybe stop the repainting timer
1411 updateCursor();
1412
1413 super.paintComponent(g); // candidate for removal
1414
1415 boolean paintedSomething = false;
1416
1417 if (mapImageInvalid) { /* if the map changed then redraw */
1418
1419 mapImageInvalid = false; // Reset for next round
1420
1421 // If the new mapArea and the oldMapArea intersect, we can draw some
1422 // quick scaled preview to make the user feel that something is
1423 // happening.
1424 if (mapAreaChanged && oldMapArea != null
1425 && getMapArea().intersects(oldMapArea)
1426 & !getMapArea().equals(oldMapArea)) {
1427
1428 mapAreaChanged = false;
1429
1430 if (getMapArea().covers(oldMapArea)) {
1431 setQuickPreviewHint(ZOOM_OUT);
1432 paintedSomething = drawScaledPreviewImage_Zoom((Graphics2D) g);
1433 } else if (oldMapArea.covers(getMapArea())) {
1434 setQuickPreviewHint(ZOOM_IN);
1435 paintedSomething = drawScaledPreviewImage_Zoom((Graphics2D) g);
1436 }
1437
1438 }
1439
1440 if (paneResized) {
1441 paneResized = false;
1442 disposeImages();
1443 }
1444
1445 // Start the Threads and Timers to render the image
1446 requestStartRendering();
1447
1448 }
1449
1450 if (!paintedSomething) {
1451
1452 g.drawImage(getFinalImage(), 0, 0, null);
1453
1454 g.dispose(); // cand. for removal
1455
1456 paintedSomething = true; // cand. for removal
1457 }
1458
1459 }
1460
1461 /**
1462 * Heavily works on releasing all resources related to the four
1463 * {@link Image}s used to cache the results of the different renderers.<br>
1464 * In November 2009 i had some memory leaking problems with {@link XMapPane}
1465 * . The resources of the buffered images were never released. It seem to be
1466 * important to call the GC right after flushing the images.<br>
1467 * Hence this method may take a while, because it calls the GC up to four
1468 * times.
1469 */
1470 private void disposeImages() {
1471
1472 // System.out.println("vorher = "
1473 // + new MbDecimalFormatter().format(LangUtil.gcTotal()));
1474 // bi.flush();
1475 // return bi = null;
1476 // System.out.println("nacher = "
1477 // + new MbDecimalFormatter().format(LangUtil.gcTotal()));
1478 //
1479 // System.out.println("\n");
1480
1481 if (preFinalImage != null) {
1482 preFinalImage.flush();
1483 preFinalImage = null;
1484 LangUtil.gc();
1485 }
1486 if (finalImage != null) {
1487 finalImage.flush();
1488 finalImage = null;
1489 LangUtil.gc();
1490 }
1491 if (localImage != null) {
1492 localImage.flush();
1493 localImage = null;
1494 LangUtil.gc();
1495 }
1496 if (bgImage != null) {
1497 bgImage.flush();
1498 bgImage = null;
1499 LangUtil.gc();
1500 }
1501
1502 }
1503
1504 /**
1505 * Performs a {@value #PAN} action. During panning, the displacement is
1506 * stored in {@link #imageOrigin} object. Calling {@link #performPan()} will
1507 * reset the offset and call {@link #setMapArea(Envelope)}.
1508 */
1509 public void performPan() {
1510
1511 final Rectangle winBounds = getBounds();
1512 winBounds.translate(-imageOrigin.x, -imageOrigin.y);
1513 final Envelope newMapArea = tranformWindowToGeo(winBounds.x,
1514 winBounds.y, winBounds.x + winBounds.width, winBounds.y
1515 + winBounds.height);
1516
1517 imageOrigin.x = 0;
1518 imageOrigin.y = 0;
1519
1520 if (!setMapArea(newMapArea)) {
1521 /**
1522 * If setMapArea returns true, the finalImage is updated anyways.
1523 * This if-case exists to ensure that we repaint a correct image
1524 * even if the new panning area has been denied.
1525 */
1526 updateFinalImage();
1527 repaint();
1528 }
1529
1530 if (getCursor() == SwingUtil.PANNING_CURSOR)
1531 setCursor(SwingUtil.PAN_CURSOR);
1532 }
1533
1534 //
1535 // /**
1536 // * Nuetzlich wenn die Componente gedruckt (z.B. wenn ein Screenshot
1537 // gemacht
1538 // * wird) wird. Dann werden wird der Hintergrund auf WEISS gesetzt.
1539 // *
1540 // * @author <a href="mailto:[email protected]">Stefan Alfons
1541 // * Kr&uuml;ger</a>
1542 // */
1543 // @Override
1544 // public void print(final Graphics g) {
1545 // final Color orig = getBackground();
1546 // setBackground(Color.WHITE);
1547 //
1548 // // wrap in try/finally so that we always restore the state
1549 // try {
1550 // super.print(g);
1551 // } finally {
1552 // setBackground(orig);
1553 // }
1554 // }
1555
1556 /**
1557 * Entfernt einen Listener von der Map.
1558 *
1559 * @param l
1560 * zu entfernender Listener
1561 */
1562 public void removeMapPaneListener(final JMapPaneListener l) {
1563 mapPaneListeners.remove(l);
1564 }
1565
1566 /**
1567 * Cancels all running renderers and sets the flag to start new ones. <br>
1568 *
1569 * @see #startRenderThreadsTimer
1570 */
1571 private void requestStartRendering() {
1572 if (bgExecuter != null)
1573 bgExecuter.cancelTask();
1574 if (localExecuter != null)
1575 localExecuter.cancelTask();
1576 requestStartRendering = true;
1577
1578 }
1579
1580 /**
1581 * Berechnet die Transformation zwischen Fenster- und Karten-Koordinaten
1582 * neu.
1583 */
1584 protected void resetTransforms() {
1585 if (getMapArea() == null || getWidth() == 0 || getHeight() == 0)
1586 return;
1587
1588 // We store the last Transform
1589 oldScreenToWorld = screenToWorld;
1590
1591 this.screenToWorld = new AffineTransform(
1592 // Genauso wie die Fenster-Koordinaten, werden die Longitude-Koordinaten
1593 // nach rechts (Osten) hin groesser
1594 // --> positive Verschiebung
1595 getMapArea().getWidth() / getWidth(),
1596 // keine Verzerrung
1597 0.0, 0.0,
1598 // Waehrend die Fenster-Koordinaten nach unten hin groesser
1599 // werden,
1600 // werden Latitude-Koordinaten nach Sueden hin keiner
1601 // --> negative Verschiebung
1602 -getMapArea().getHeight() / getHeight(),
1603 // Die Longitude-Koordinaten werden nach Osten hin groesser
1604 // --> obere linke Ecke des Fensters hat also den Minimalwert
1605 getMapArea().getMinX(),
1606 // Die Latitude-Koordinaten werden nach Norden hin groesser
1607 // --> obere linke Ecke des Fensters hat also den Maximalwert
1608 getMapArea().getMaxY());
1609
1610 try {
1611 this.worldToScreen = screenToWorld.createInverse();
1612 } catch (final NoninvertibleTransformException e) {
1613 LOGGER.error(e);
1614 }
1615 }
1616
1617 public void setBgContext(final MapContext context) {
1618
1619 // Remove the default listener from the old context
1620 if (this.bgContext != null) {
1621 this.bgContext.removeMapLayerListListener(bgContextListener);
1622
1623 // adding listener to all layers
1624 for (final MapLayer mapLayer : bgContext.getLayers()) {
1625 mapLayer.removeMapLayerListener(bgMapLayerListener);
1626 }
1627 }
1628
1629 this.bgContext = context;
1630
1631 if (context != null) {
1632 setMapArea(bgContext.getAreaOfInterest());
1633
1634 this.bgContext.addMapLayerListListener(bgContextListener);
1635
1636 // adding listener to all layers
1637 for (final MapLayer mapLayer : bgContext.getLayers()) {
1638 mapLayer.addMapLayerListener(bgMapLayerListener);
1639 }
1640 }
1641 mapImageInvalid = true;
1642 repaint();
1643 }
1644
1645 public void setJava2dHints(final RenderingHints java2dHints) {
1646 this.java2dHints = java2dHints;
1647 }
1648
1649 /**
1650 *
1651 * @param context
1652 */
1653 public void setLocalContext(final MapContext context) {
1654 // Remove the default listener from the old context
1655 if (this.localContext != null) {
1656 this.localContext.removeMapLayerListListener(localContextListener);
1657
1658 // adding listener to all layers
1659 for (final MapLayer mapLayer : localContext.getLayers()) {
1660 mapLayer.removeMapLayerListener(localMapLayerListener);
1661 }
1662 }
1663
1664 this.localContext = context;
1665
1666 if (context != null) {
1667
1668 setMapArea(localContext.getAreaOfInterest());
1669
1670 localRenderer.setContext(localContext);
1671
1672 this.localContext.addMapLayerListListener(localContextListener);
1673
1674 // adding listener to all layers
1675 for (final MapLayer mapLayer : localContext.getLayers()) {
1676 mapLayer.addMapLayerListener(localMapLayerListener);
1677 }
1678 }
1679
1680 mapImageInvalid = true;
1681 repaint();
1682 }
1683
1684 /**
1685 * @param newMapArea
1686 * @return <code>true</code> if the mapArea has been changed and a repaint
1687 * has been triggered.
1688 */
1689 public boolean setMapArea(final Envelope newMapArea) {
1690
1691 if (newMapArea == null
1692 || bestAllowedMapArea(newMapArea).equals(mapArea)) {
1693 // No change.. no need to repaint
1694 return false;
1695 }
1696
1697 // Testing, whether NaN or Infinity are used in the newMapArea
1698 if (newMapArea.isNull() || Double.isInfinite(newMapArea.getMaxX())
1699 || Double.isInfinite(newMapArea.getMaxY())
1700 || Double.isInfinite(newMapArea.getMinX())
1701 || Double.isInfinite(newMapArea.getMinY())) {
1702 // No change.. ugly new values
1703 LOGGER.warn("setMapArea has been called with newArea = "
1704 + newMapArea);
1705 return false;
1706 }
1707
1708 // Testing, whether the difference if just minimal
1709 if (mapArea != null) {
1710 final Envelope candNew = bestAllowedMapArea(newMapArea);
1711 final double tolX = mapArea.getWidth() / 1000.;
1712 final double tolY = mapArea.getHeight() / 1000.;
1713 if ((candNew.getMinX() - tolX < mapArea.getMinX())
1714 && (mapArea.getMinX() < candNew.getMinX() + tolX)
1715 && (candNew.getMaxX() - tolX < mapArea.getMaxX())
1716 && (mapArea.getMaxX() < candNew.getMaxX() + tolX)
1717
1718 && (candNew.getMinY() - tolY < mapArea.getMinY())
1719 && (mapArea.getMinY() < candNew.getMinY() + tolY)
1720 && (candNew.getMaxY() - tolY < mapArea.getMaxY())
1721 && (mapArea.getMaxY() < candNew.getMaxY() + tolY)
1722
1723 ) {
1724 // The two mapAreas only differ my 1/1000th.. ignore
1725
1726 return false;
1727 }
1728 }
1729
1730 oldMapArea = mapArea;
1731
1732 this.mapArea = bestAllowedMapArea(newMapArea);
1733
1734 if (localContext != null) {
1735 localContext.setAreaOfInterest(mapArea, localContext
1736 .getCoordinateReferenceSystem());
1737 }
1738 if (bgContext != null) {
1739 bgContext.setAreaOfInterest(mapArea, localContext
1740 .getCoordinateReferenceSystem());
1741 }
1742 resetTransforms();
1743 mapImageInvalid = true;
1744 mapAreaChanged = true;
1745 repaint();
1746
1747 // LOGGER.debug("New maparea = " + mapArea);
1748 return true;
1749 }
1750
1751 /**
1752 * Set the background color of the map.
1753 *
1754 * @param if <code>null</code>, white is used.
1755 */
1756 public void setMapBackgroundColor(Color bgColor) {
1757 if (bgColor == null)
1758 bgColor = Color.WHITE;
1759 this.mapBackgroundColor = bgColor;
1760 }
1761
1762 /**
1763 * Set the BufferedImage to use as a flaoting icon in the lower right corner
1764 *
1765 * @param mapImageIcon
1766 * <code>null</code> is allowed and deactivates this icon.
1767 */
1768 public void setMapImage(final BufferedImage mapImage) {
1769 this.mapImage = mapImage;
1770 }
1771
1772 /**
1773 * Sets whether a layer is regarded or ignored on {@link #SELECT_TOP},
1774 * {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP} actions.
1775 *
1776 * @param layer
1777 * a layer
1778 * @param selectable
1779 * if {@code false} the layer is ignored during the upper
1780 * mentioned actions. If <code>null</code>, the default (true)
1781 * will be used.
1782 */
1783 public void setMapLayerSelectable(final MapLayer layer,
1784 final Boolean selectable) {
1785 if (selectable == null)
1786 mapLayerSelectable.remove(layer);
1787 else
1788 mapLayerSelectable.put(layer, selectable);
1789 }
1790
1791 /**
1792 * Defines an evelope of the viwable area. The JMapPane will never show
1793 * anything outside of this extend.
1794 *
1795 * @param maxExtend
1796 * <code>null</code> to not have this restriction.
1797 */
1798 public void setMaxExtend(final Envelope maxExtend) {
1799 this.maxExtend = maxExtend;
1800 }
1801
1802 /**
1803 * Set the maximum allowed zoom scale. This is the smaller number value of
1804 * the two. If <code>null</code> is passed, Double.MINVALUE are used which
1805 * mean there is no restriction.
1806 *
1807 * @author <a href="mailto:[email protected]">Stefan Alfons
1808 * Kr&uuml;ger</a>
1809 */
1810 public void setMaxZoomScale(final Double maxZoomScale) {
1811 this.maxZoomScale = maxZoomScale == null ? Double.MIN_VALUE
1812 : maxZoomScale;
1813 }
1814
1815 // /** Stored the time used for the last real rendering in ms. **/
1816 // private long lastRenderingDuration = Long.MAX_VALUE;
1817
1818 /**
1819 * Set the minimum (nearest) allowed zoom scale. This is the bigger number
1820 * value of the two. If <code>null</code> is passed, Double.MAXVALUE are
1821 * used which mean there is no restriction.
1822 *
1823 *
1824 * @author <a href="mailto:[email protected]">Stefan Alfons
1825 * Kr&uuml;ger</a>
1826 */
1827 public void setMinZoomScale(final Double minZoomScale) {
1828 this.minZoomScale = minZoomScale == null ? Double.MAX_VALUE
1829 : minZoomScale;
1830 }
1831
1832 /**
1833 *
1834 * @param b
1835 */
1836 public void setPainting(final boolean b) {
1837 acceptsRepaintCalls = b;
1838 }
1839
1840 // /**
1841 // * Returns in milli seconds the time the last rending of the
1842 // * {@link SelectableXMapPane} took. #Long.MAX_VALUE if the JMapPane has
1843 // not
1844 // * been rendered yet.
1845 // */
1846 // public long getLastRenderingDuration() {
1847 // return lastRenderingDuration;
1848 // }
1849
1850 public void setQuickPreviewHint(final int quickPreviewHint) {
1851 this.quickPreviewHint = quickPreviewHint;
1852
1853 }
1854
1855 private void setRendererHints(final Map<Object, Object> rendererHints) {
1856 this.rendererHints = rendererHints;
1857 }
1858
1859 /**
1860 * Enables/Disables the ZOOM Mouse Listener. Upates the Cursor and stops the
1861 * repaint Timer if
1862 *
1863 * @param state
1864 */
1865 public void setState(final int state) {
1866 this.state = state;
1867
1868 zoomMapPaneMouseListener.setEnabled((state == ZOOM_IN
1869 || state == ZOOM_OUT || state == PAN));
1870
1871 // Je nach Aktion den Cursor umsetzen
1872 updateCursor();
1873 }
1874
1875 /**
1876 * Standardmaessig wird der Cursor automatisch je nach MapPane-Aktion (Zoom,
1877 * Auswahl, ...) gesetzt. Mit dieser Methode kann ein statischer Cursor
1878 * gesetzt werden, der unabhaengig von der aktuellen MapPanes-Aktion
1879 * beibehalten wird. Um diesen statischen Cursor wieder zu entfernen, kann
1880 * {@code null} als Parameter uebergeben werden
1881 *
1882 * @param cursor
1883 * Cursor
1884 */
1885 public void setStaticCursor(final Cursor cursor) {
1886 this.staticCursor = cursor;
1887 if (cursor != null)
1888 super.setCursor(cursor);
1889 }
1890
1891 /**
1892 * Starts rendering on one or two threads
1893 */
1894 private void startRendering() {
1895
1896 if (!isWellDefined())
1897 return;
1898
1899 if (bgExecuter != null) {
1900 // Stop all renderers
1901 bgExecuter.cancelTask();
1902 }
1903
1904 if (localExecuter != null) {
1905 localExecuter.cancelTask();
1906 }
1907
1908 final Rectangle curPaintArea = getVisibleRect();
1909
1910 /**
1911 * We have to set new renderer
1912 */
1913
1914 if (getBgContext() != null) {
1915 // bgExecuter = new RenderingExecutor();
1916 // LOGGER.debug("starting bg renderer:");
1917 // // /* System.out.println("rendering"); */
1918 // final GTRenderer createGTRenderer = GTUtil.createGTRenderer(
1919 // bgContext, getRendererHints());
1920 // createGTRenderer.setJava2DHints(getJava2dHints());
1921 // bgExecuter.submit(getBgContext().getAreaOfInterest(),
1922 // curPaintArea,
1923 // (Graphics2D) getBgImage().getGraphics(), createGTRenderer);
1924 }
1925
1926 if (getContext() != null) {
1927 // localExecuter = new RenderingExecutor(this, 150l);
1928 // LOGGER.debug("starting local renderer:");
1929
1930 // Clear label cache
1931 labelCache.clear();
1932 final Map<Object, Object> rh = localRenderer.getRendererHints();
1933 rh.put(StreamingRenderer.LABEL_CACHE_KEY, labelCache);
1934 localRenderer.setRendererHints(rh);
1935
1936 final boolean submitted = localExecuter.submit(getContext()
1937 .getAreaOfInterest(), curPaintArea,
1938 (Graphics2D) getLocalImage().getGraphics(), localRenderer,
1939 getWorldToScreenTransform());
1940 if (submitted)
1941 repaintTimer.restart();
1942 else
1943 requestStartRendering = true; // Try to start rendering again in
1944 // a moment
1945 }
1946
1947 updateCursor();
1948 }
1949
1950 /**
1951 * Transformiert einen Geo-Koordinaten-Bereich in Fenster-Koordinaten.
1952 *
1953 * @param ox
1954 * X-Koordinate der VON-Position
1955 * @param oy
1956 * Y-Koordinate der VON-Position
1957 * @param px
1958 * X-Koordinate der BIS-Position
1959 * @param py
1960 * Y-Koordinate der BIS-Position
1961 * @param winToGeotransform
1962 * Eine Window to Geo transform. If <code>null</code>,
1963 * {@link #getScreenToWorld()} is used.
1964 */
1965 public Envelope tranformGeoToWindow(final double ox, final double oy,
1966 final double px, final double py,
1967 final AffineTransform winToGeotransform) {
1968 final AffineTransform at = winToGeotransform == null ? getScreenToWorld()
1969 : winToGeotransform;
1970 Point2D geoO;
1971 try {
1972 geoO = at.inverseTransform(new Point2D.Double(ox, oy), null);
1973 final Point2D geoP = at.inverseTransform(
1974 new Point2D.Double(px, py), null);
1975 return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP
1976 .getY());
1977 } catch (final NoninvertibleTransformException e) {
1978 LOGGER.error(e);
1979 return new Envelope(ox, oy, px, py);
1980 }
1981 }
1982
1983 /**
1984 * Transformiert einen Fenster-Koordinaten-Bereich in Geo-Koordinaten.
1985 *
1986 * @param ox
1987 * X-Koordinate der VON-Position
1988 * @param oy
1989 * Y-Koordinate der VON-Position
1990 * @param px
1991 * X-Koordinate der BIS-Position
1992 * @param py
1993 * Y-Koordinate der BIS-Position
1994 */
1995 public Envelope tranformWindowToGeo(final int ox, final int oy,
1996 final int px, final int py) {
1997 final AffineTransform at = getScreenToWorld();
1998 final Point2D geoO = at.transform(new Point2D.Double(ox, oy), null);
1999 final Point2D geoP = at.transform(new Point2D.Double(px, py), null);
2000 return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP.getY());
2001 }
2002
2003 /**
2004 * Will update the cursor. If all rendering is finished also stops the
2005 * {@link #repaintTimer}
2006 */
2007 public void updateCursor() {
2008
2009 // if the renderers have stopped, also stop the timer that is updating
2010 // the final image
2011 if (bgExecuter != null && bgExecuter.isRunning()
2012 || localExecuter != null && localExecuter.isRunning()) {
2013 setCursor(WAIT_CURSOR);
2014 return;
2015 } else {
2016 // Allow one last rendering
2017 if (repaintTimer.isRunning()) {
2018 repaintTimer.stop();
2019 updateFinalImage();
2020 repaint();
2021 }
2022 }
2023
2024 // wenn manueller Cursor gesetzt ist, dann diesen verwenden (unabhaengig
2025 // von der aktuellen Aktion
2026 if (this.staticCursor != null) {
2027 setCursor(staticCursor);
2028 return;
2029 }
2030 if (getCursor() == SwingUtil.PANNING_CURSOR) {
2031 // This cursor will reset itself
2032 return;
2033 }
2034
2035 // Set the cursor depending on what tool is in use...
2036 switch (state) {
2037 case SELECT_TOP:
2038 case SELECT_ONE_FROM_TOP:
2039 case SELECT_ALL:
2040 setCursor(SwingUtil.CROSSHAIR_CURSOR);
2041 break;
2042 case ZOOM_IN:
2043 setCursor(SwingUtil.ZOOMIN_CURSOR);
2044 break;
2045 case ZOOM_OUT:
2046 setCursor(SwingUtil.ZOOMOUT_CURSOR);
2047 break;
2048 case PAN:
2049 setCursor(SwingUtil.PAN_CURSOR);
2050 break;
2051 default:
2052 setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
2053 break;
2054 }
2055 }
2056
2057 /**
2058 * The renderers are all rendering into their own {@link Image}s. This
2059 * method combines all images to one {@link #finalImage}. The
2060 * {@link #repaintTimer} is calling this method regularely to update the
2061 * {@link #finalImage} even if the renderers are still working.
2062 */
2063 synchronized protected Image updateFinalImage() {
2064
2065 // Render the two map images first, into the preFinalImage
2066 if (bgExecuter != null) {
2067 final Graphics2D preFinalG = (Graphics2D) getPreFinalImage()
2068 .getGraphics();
2069 preFinalG.setBackground(getMapBackgroundColor());
2070
2071 preFinalG.drawImage(getBgImage(), 0, 0, getMapBackgroundColor(),
2072 null);
2073
2074 // // Draw the local layers image
2075 preFinalG.drawImage(getLocalImage(), 0, 0, null);
2076 preFinalG.dispose();
2077
2078 } else {
2079 preFinalImage = getLocalImage();
2080 }
2081
2082 final Graphics2D finalG = getFinalImage().createGraphics();
2083 finalG.setBackground(getMapBackgroundColor());
2084 finalG.drawImage(getPreFinalImage(), imageOrigin.x, imageOrigin.y,
2085 getMapBackgroundColor(), null);
2086
2087 final int finalImageHeight = getFinalImage().getHeight(null);
2088 final int finalImageWidth = getFinalImage().getWidth(null);
2089
2090 final Rectangle painedArea = new Rectangle(imageOrigin.x,
2091 imageOrigin.y, finalImageWidth, finalImageHeight);
2092 SwingUtil.clearAround(finalG, painedArea, getVisibleRect());
2093
2094 addGadgets(finalG);
2095
2096 finalG.dispose();
2097
2098 return finalImage;
2099 }
2100
2101 /**
2102 * Paints some optional stuff into the given {@link Graphics2D}. Usually
2103 * called as the last layer when {@link #updateFinalImage()}
2104 */
2105 private void addGadgets(final Graphics2D graphics) {
2106
2107 // Paint a logo to the bottom right if available
2108 if (mapImage != null) {
2109 graphics.drawImage(mapImage, getBounds().width
2110 - mapImage.getWidth() - 10, getBounds().height
2111 - mapImage.getHeight() - 10, null);
2112 }
2113
2114 // If the rendering process is still running, indicate this is the image
2115 if (bgExecuter != null && bgExecuter.isRunning()
2116 || localExecuter != null && localExecuter.isRunning()) {
2117
2118 final Color c = graphics.getColor();
2119 graphics.setFont(waitFont);
2120 String waitStr = "Wait..."; //i8n
2121
2122 graphics.setColor(Color.WHITE);
2123 graphics.drawString(waitStr, 39, 69);
2124 graphics.setColor(Color.BLACK);
2125 graphics.drawString(waitStr, 40, 70);
2126
2127 graphics.setColor(c);
2128 }
2129
2130 }
2131
2132 /**
2133 * Sets the {@link #mapArea} to best possibly present the given features. If only one
2134 * single point is given, the window is moved over the point.
2135 *
2136 * @param features
2137 * if <code>null</code> or size==0, the function doesn nothing.
2138 */
2139 public void zoomTo(
2140 final FeatureCollection<SimpleFeatureType, SimpleFeature> features) {
2141
2142 final CoordinateReferenceSystem mapCRS = getContext()
2143 .getCoordinateReferenceSystem();
2144 final CoordinateReferenceSystem fCRS = features.getSchema()
2145 .getGeometryDescriptor().getCoordinateReferenceSystem();
2146
2147 double width = mapArea.getWidth();
2148 double height = mapArea.getHeight();
2149 final double ratio = height / width;
2150
2151 if (features == null || features.size() == 0) {
2152 // feature count == 0 Zoom to the full extend
2153 return;
2154 } else if (features.size() == 1) {
2155
2156 // feature count == 1 Just move the window to the point and zoom 'a
2157 // bit'
2158 final SimpleFeature singleFeature = features.iterator().next();
2159
2160 if (((Geometry) singleFeature.getDefaultGeometry())
2161 .getCoordinates().length > 1) {
2162 // System.out.println("Zoomed to only pne poylgon");
2163 // Poly
2164 // TODO max width vs. height
2165 width = features.getBounds().getWidth() * 3;
2166 height = ratio * width;
2167 } else {
2168 // System.out.println("Zoomed in a bit becasue only one point");
2169 // width *= .9;
2170 // height *= .9;
2171 }
2172
2173 Coordinate centre = features.getBounds().centre();
2174 if (!mapCRS.equals(fCRS)) {
2175 // only to calculations if the CRS differ
2176 try {
2177 MathTransform fToMap;
2178 fToMap = CRS.findMathTransform(fCRS, mapCRS);
2179 // centre is transformed to the mapCRS
2180 centre = JTS.transform(centre, null, fToMap);
2181 } catch (final FactoryException e) {
2182 LOGGER.error("Looking for a Math transform", e);
2183 } catch (final TransformException e) {
2184 LOGGER.error("Looking for a Math transform", e);
2185 }
2186 }
2187
2188 final Coordinate newLeftBottom = new Coordinate(centre.x - width
2189 / 2., centre.y - height / 2.);
2190 final Coordinate newTopRight = new Coordinate(
2191 centre.x + width / 2., centre.y + height / 2.);
2192
2193 final Envelope newMapArea = new Envelope(newLeftBottom, newTopRight);
2194
2195 setMapArea(newMapArea);
2196
2197 } else {
2198 final ReferencedEnvelope fBounds = features.getBounds();
2199
2200 Envelope bounds;
2201 if (!mapCRS.equals(fCRS)) {
2202 bounds = JTSUtil.transformEnvelope(fBounds, fCRS, mapCRS);
2203 } else {
2204 bounds = fBounds;
2205 }
2206 // BB umrechnen von Layer-CRS in Map-CRS
2207
2208 // Expand a bit
2209 bounds.expandBy(bounds.getWidth() / 6., bounds.getHeight() / 6.);
2210
2211 setMapArea(bounds);
2212 }
2213 }
2214
2215 /**
2216 * Zooms towards a point.
2217 *
2218 * @param center
2219 * position in window coordinates
2220 * @param zoomFactor
2221 * > 1 for zoom in, < 1 for zoom out. Default is 1.33
2222 */
2223 public void zoomTo(final Point center) {
2224 zoomTo(center, null);
2225 }
2226
2227 /**
2228 * Zooms towards a point.
2229 *
2230 * @param center
2231 * position in window coordinates
2232 * @param zoomFaktor
2233 * > 1 for zoom in, < 1 for zoom out. Default is 1.33.
2234 */
2235 public void zoomTo(Point center, Double zoomFaktor) {
2236 if (zoomFaktor == null || zoomFaktor == 0.)
2237 zoomFaktor = 2.;
2238
2239 final Point2D gcenter = getScreenToWorld().transform(center, null);
2240 center = null;
2241
2242 if (Double.isNaN(gcenter.getX()) || Double.isNaN(gcenter.getY())
2243 || Double.isInfinite(gcenter.getX())
2244 || Double.isInfinite(gcenter.getY())
2245
2246 ) {
2247 // Not inside valid CRS area! cancel
2248 return;
2249 }
2250
2251 final Envelope mapArea = getMapArea();
2252
2253 final Envelope newMapArea = new Envelope(mapArea);
2254 newMapArea.expandBy((mapArea.getWidth() * zoomFaktor - mapArea
2255 .getWidth()) / 2., (mapArea.getHeight() * zoomFaktor - mapArea
2256 .getHeight()) / 2.);
2257
2258 // Move the newMapArea above the new center
2259 newMapArea.translate(gcenter.getX() - mapArea.centre().x, gcenter
2260 .getY()
2261 - mapArea.centre().y);
2262
2263 setMapArea(newMapArea);
2264 }
2265
2266 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26