/[schmitzm]/trunk/src/skrueger/geotools/XMapPane.java
ViewVC logotype

Contents of /trunk/src/skrueger/geotools/XMapPane.java

Parent Directory Parent Directory | Revision Log Revision Log


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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26