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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26