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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26