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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26