/[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 515 - (show annotations)
Mon Nov 9 18:51:54 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: 66662 byte(s)
Styles with wronlgy spelled property names are now automatically corrected when loaded. This helps when upgrading atlases that have been created with GeoPublisher/AtlasStyler 1.2


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 // allow a single pixel margin at the right and bottom edges
1475 curPaintArea.width -= 1;
1476 curPaintArea.height -= 1;
1477
1478 labelCache.clear();
1479
1480 /**
1481 */
1482
1483 /**
1484 * We have to set new renderer
1485 */
1486
1487 if (getBgContext() != null) {
1488 bgExecuter = new RenderingExecutor(this, 333l);
1489 LOGGER.debug("starting bg renderer:");
1490 // /* System.out.println("rendering"); */
1491 final GTRenderer createGTRenderer = GTUtil.createGTRenderer(
1492 localContext, getRendererHints());
1493 createGTRenderer.setJava2DHints(getJava2dHints());
1494 bgExecuter.submit(getBgContext().getAreaOfInterest(), curPaintArea,
1495 (Graphics2D) getBgImage().getGraphics(), createGTRenderer);
1496 }
1497
1498 if (getContext() != null) {
1499 localExecuter = new RenderingExecutor(this, 150l);
1500 LOGGER.debug("starting local renderer:");
1501 final GTRenderer createGTRenderer = GTUtil.createGTRenderer(
1502 localContext, getRendererHints());
1503 createGTRenderer.setJava2DHints(getJava2dHints());
1504 localExecuter.submit(getContext().getAreaOfInterest(),
1505 curPaintArea, (Graphics2D) getLocalImage().getGraphics(),
1506 createGTRenderer);
1507 }
1508
1509 updateCursor();
1510 //
1511 // // start regular repaints until all renderers are done.
1512 // repainterTimer.setRepeats(true);
1513 // repainterTimer.restart();
1514
1515 }
1516
1517 /**
1518 * Lazyly initializes a {@link BufferedImage} for the background renderer.
1519 */
1520 private BufferedImage getBgImage() {
1521
1522 if (bgImage == null) {
1523 LOGGER.debug("creating a new background image");
1524
1525 Rectangle curPaintArea = getVisibleRect();
1526 // allow a single pixel margin at the right and bottom edges
1527 curPaintArea.width -= 1;
1528 curPaintArea.height -= 1;
1529
1530 bgImage = new BufferedImage(curPaintArea.width + 1,
1531 curPaintArea.height + 1, BufferedImage.TYPE_INT_ARGB);
1532 }
1533
1534 return bgImage;
1535 }
1536
1537 /** An (transparent) image to paint over the map in the lower right corner **/
1538 private BufferedImage mapImage = null;
1539
1540 private boolean acceptsRepaintCalls = true;
1541
1542 /**
1543 * Get the BufferedImage to use as a flaoting icon in the lower right
1544 * corner.
1545 *
1546 * @return <code>null</code> if the feature is deactivated.
1547 */
1548 public BufferedImage getMapImage() {
1549 return mapImage;
1550 }
1551
1552 /**
1553 * Set the BufferedImage to use as a flaoting icon in the lower right corner
1554 *
1555 * @param mapImageIcon
1556 * <code>null</code> is allowed and deactivates this icon.
1557 */
1558 public void setMapImage(BufferedImage mapImage) {
1559 this.mapImage = mapImage;
1560 // gadgetsImage = null;
1561 }
1562
1563 /**
1564 * Lazyly initializes a {@link BufferedImage} for the background renderer.
1565 */
1566 private BufferedImage getLocalImage() {
1567
1568 if (localImage == null) {
1569 LOGGER.debug("creating a new local image");
1570
1571 Rectangle curPaintArea = getVisibleRect();
1572 // allow a single pixel margin at the right and bottom edges
1573 curPaintArea.width -= 1;
1574 curPaintArea.height -= 1;
1575
1576 localImage = new BufferedImage(curPaintArea.width + 1,
1577 curPaintArea.height + 1, BufferedImage.TYPE_INT_ARGB);
1578 }
1579
1580 return localImage;
1581 }
1582
1583 //
1584 // /**
1585 // * Lazyly initializes a {@link BufferedImage} for the background renderer.
1586 // */
1587 // private BufferedImage getGadgetsImage() {
1588 //
1589 // if (gadgetsImage == null) {
1590 // LOGGER.debug("creating a new gadgets image");
1591 //
1592 // Rectangle curPaintArea = getVisibleRect();
1593 // // allow a single pixel margin at the right and bottom edges
1594 // curPaintArea.width -= 1;
1595 // curPaintArea.height -= 1;
1596 //
1597 // gadgetsImage = new BufferedImage(curPaintArea.width + 1,
1598 // curPaintArea.height + 1, BufferedImage.TYPE_INT_ARGB);
1599 //
1600 // if (mapImage != null)
1601 // gadgetsImage.getGraphics().drawImage(mapImage,
1602 // curPaintArea.width - mapImage.getWidth() - 10,
1603 // curPaintArea.height - mapImage.getHeight() - 10, this);
1604 //
1605 // }
1606 //
1607 // return gadgetsImage;
1608 // }
1609
1610 /**
1611 * Called by the {@linkplain XMapPane.RenderingTask} when rendering has been
1612 * completed Publishes a {@linkplain MapPaneEvent} of type {@code
1613 * MapPaneEvent.Type.RENDERING_STOPPED} to listeners.
1614 *
1615 * @see MapPaneListener#onRenderingStopped(org.geotools.swing.event.MapPaneEvent)
1616 */
1617 public void onRenderingCompleted() {
1618 System.out.println("onRenderingCompleted");
1619
1620 updateFinalImage();
1621
1622 repaint();
1623
1624 }
1625
1626 /**
1627 * Called by the {@linkplain XMapPane.RenderingTask} when rendering was
1628 * cancelled. Publishes a {@linkplain MapPaneEvent} of type {@code
1629 * MapPaneEvent.Type.RENDERING_STOPPED} to listeners.
1630 *
1631 * @see MapPaneListener#onRenderingStopped(org.geotools.swing.event.MapPaneEvent)
1632 */
1633 public void onRenderingCancelled() {
1634 LOGGER.debug("Rendering cancelled");
1635 }
1636
1637 /**
1638 * Called by the {@linkplain XMapPane.RenderingTask} when rendering failed.
1639 * Publishes a {@linkplain MapPaneEvent} of type {@code
1640 * MapPaneEvent.Type.RENDERING_STOPPED} to listeners.
1641 *
1642 * @param renderingError
1643 * The error that occured during rendering
1644 *
1645 * @see MapPaneListener#onRenderingStopped(org.geotools.swing.event.MapPaneEvent)
1646 */
1647 public void onRenderingFailed(Exception renderingError) {
1648 // MapPaneEvent ev = new MapPaneEvent(this,
1649 // MapPaneEvent.Type.RENDERING_STOPPED);
1650 // publishEvent(ev);
1651 LOGGER.warn("Rendering failed", renderingError);
1652 updateFinalImage();
1653 repaint();
1654
1655 }
1656
1657 /**
1658 * Called when a rendering request has been rejected. This will be common,
1659 * such as when the user pauses during drag-resizing fo the map pane. The
1660 * base implementation does nothing. It is provided for sub-classes to
1661 * override if required.
1662 */
1663 public void onRenderingRejected() {
1664 LOGGER.warn("Rendering rejected");
1665 repaint();
1666 }
1667
1668 @Override
1669 public void propertyChange(final PropertyChangeEvent evt) {
1670 final String prop = evt.getPropertyName();
1671
1672 if (prop.equalsIgnoreCase("crs")) {
1673 localContext.setAreaOfInterest(localContext.getAreaOfInterest(),
1674 (CoordinateReferenceSystem) evt.getNewValue());
1675 }
1676 }
1677
1678 // xulu.sn
1679 /**
1680 * Korrigiert den {@link Envelope} aka {@code mapArea} auf die beste
1681 * erlaubte Flaeche damit die Massstabsbeschaenkungen noch eingehalten
1682 * werden, FALLS der uebergeben Envelope nicht schon gueltig sein sollte.<br/>
1683 * Since 21. April 09: Before thecalculation starts, the aspect ratio is
1684 * corrected. This change implies, that setMapArea() will most of the time
1685 * not allow setting to a wrong aspectRatio.
1686 *
1687 * @author <a href="mailto:[email protected]">Stefan Alfons
1688 * Kr&uuml;ger</a>
1689 */
1690 public Envelope bestAllowedMapArea(Envelope env) {
1691 if (getWidth() == 0)
1692 return env;
1693 if (env == null)
1694 return null;
1695
1696 Envelope newArea = null;
1697
1698 /**
1699 * Correct the aspect Ratio before we check the rest. Otherwise we might
1700 * easily fail. We allow to grow here, because we don't check against
1701 * the maxExtend
1702 */
1703 Rectangle curPaintArea = getVisibleRect();
1704
1705 env = JTSUtil.fixAspectRatio(curPaintArea, env, true);
1706
1707 final double scale = env.getWidth() / getWidth();
1708 final double centerX = env.getMinX() + env.getWidth() / 2.;
1709 final double centerY = env.getMinY() + env.getHeight() / 2.;
1710 double newWidth2 = 0;
1711 double newHeight2 = 0;
1712 if (scale < getMaxZoomScale()) {
1713 // ****************************************************************************
1714 // Wir zoomen weiter rein als erlaubt => Anpassen des envelope
1715 // ****************************************************************************
1716 newWidth2 = getMaxZoomScale() * getWidth() / 2.;
1717 newHeight2 = getMaxZoomScale() * getHeight() / 2.;
1718 } else if (scale > getMinZoomScale()) {
1719 // ****************************************************************************
1720 // Wir zoomen weiter raus als erlaubt => Anpassen des envelope
1721 // ****************************************************************************
1722 newWidth2 = getMinZoomScale() * getWidth() / 2.;
1723 newHeight2 = getMinZoomScale() * getHeight() / 2.;
1724 } else {
1725 // ****************************************************************************
1726 // Die mapArea / der Envelope ist ist gueltig! Keine Aenderungen
1727 // ****************************************************************************
1728 newArea = env;
1729 }
1730
1731 if (newArea == null) {
1732
1733 final Coordinate ll = new Coordinate(centerX - newWidth2, centerY
1734 - newHeight2);
1735 final Coordinate ur = new Coordinate(centerX + newWidth2, centerY
1736 + newHeight2);
1737
1738 newArea = new Envelope(ll, ur);
1739 }
1740
1741 Envelope maxAllowedExtend = getMaxExtend();
1742 while (maxAllowedExtend != null && !maxAllowedExtend.contains(newArea)
1743 && newArea != null && !newArea.isNull()
1744 && !Double.isNaN(newArea.getMinX())
1745 && !Double.isNaN(newArea.getMaxX())
1746 && !Double.isNaN(newArea.getMinY())
1747 && !Double.isNaN(newArea.getMaxY())) {
1748 /*
1749 * If a maxExtend is set, we have to honour that...
1750 */
1751
1752 // Exceeds top? Move down and maybe cut
1753 if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {
1754 double divY = newArea.getMaxY() - maxAllowedExtend.getMaxY();
1755 // LOGGER.debug("Moving area down by " + divY);
1756
1757 newArea = new Envelope(new Coordinate(newArea.getMinX(),
1758 newArea.getMinY() - divY), new Coordinate(newArea
1759 .getMaxX(), newArea.getMaxY() - divY));
1760
1761 if (newArea.getMinY() < maxAllowedExtend.getMinY()) {
1762 // LOGGER.debug("Now it exeeds the bottom border.. cut!");
1763 // And cut the bottom if it moved out of the area
1764 newArea = new Envelope(new Coordinate(newArea.getMinX(),
1765 maxAllowedExtend.getMinY()), new Coordinate(newArea
1766 .getMaxX(), newArea.getMaxY()));
1767
1768 // LOGGER.debug("and fix aspect ratio");
1769
1770 newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
1771 false);
1772 }
1773 }
1774
1775 // Exceeds bottom? Move up and maybe cut
1776 if (newArea.getMinY() < maxAllowedExtend.getMinY()) {
1777 double divY = newArea.getMinY() - maxAllowedExtend.getMinY();
1778 // LOGGER.debug("Moving area up by " + divY);
1779
1780 newArea = new Envelope(new Coordinate(newArea.getMinX(),
1781 newArea.getMinY() - divY), new Coordinate(newArea
1782 .getMaxX(), newArea.getMaxY() - divY));
1783
1784 if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {
1785 // LOGGER.debug("Now it exeeds the top border.. cut!");
1786 // And cut the bottom if it moved out of the area
1787 newArea = new Envelope(new Coordinate(newArea.getMinX(),
1788 newArea.getMinY()), new Coordinate(newArea
1789 .getMaxX(), maxAllowedExtend.getMaxY()));
1790
1791 // LOGGER.debug("and fix aspect ratio");
1792
1793 newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
1794 false);
1795 }
1796 }
1797
1798 // Exceeds to the right? move and maybe cut
1799 if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {
1800
1801 // Move left..
1802 double divX = newArea.getMaxX() - maxAllowedExtend.getMaxX();
1803 // LOGGER.debug("Moving area left by " + divX);
1804
1805 newArea = new Envelope(new Coordinate(newArea.getMinX() - divX,
1806 newArea.getMinY()), new Coordinate(newArea.getMaxX()
1807 - divX, newArea.getMaxY()));
1808
1809 if (newArea.getMinX() < maxAllowedExtend.getMinX()) {
1810 // LOGGER.debug("Now it exeeds the left border.. cut!");
1811 // And cut the left if it moved out of the area
1812 newArea = new Envelope(new Coordinate(maxAllowedExtend
1813 .getMinX(), newArea.getMinY()), new Coordinate(
1814 newArea.getMaxX(), newArea.getMaxY()));
1815
1816 // LOGGER.debug("and fix aspect ratio");
1817
1818 newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
1819 false);
1820 }
1821 }
1822
1823 // Exceeds to the left? move and maybe cut
1824 if (newArea.getMinX() < maxAllowedExtend.getMinX()) {
1825
1826 // Move right..
1827 double divX = newArea.getMinX() - maxAllowedExtend.getMinX();
1828 // LOGGER.debug("Moving area right by " + divX);
1829
1830 newArea = new Envelope(new Coordinate(newArea.getMinX() - divX,
1831 newArea.getMinY()), new Coordinate(newArea.getMaxX()
1832 - divX, newArea.getMaxY()));
1833
1834 if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {
1835 // LOGGER.debug("Now it exeeds the right border.. cut!");
1836 // And cut the left if it moved out of the area
1837 newArea = new Envelope(new Coordinate(newArea.getMinX(),
1838 newArea.getMinY()), new Coordinate(maxAllowedExtend
1839 .getMaxX(), newArea.getMaxY()));
1840
1841 // LOGGER.debug("and fix aspect ratio");
1842
1843 newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
1844 false);
1845 }
1846 }
1847
1848 }
1849
1850 return newArea;
1851 }
1852
1853 /**
1854 * Retuns the minimum allowed zoom scale. This is the bigger number value of
1855 * the two. Defaults to {@link Double}.MAX_VALUE
1856 *
1857 * @author <a href="mailto:[email protected]">Stefan Alfons
1858 * Kr&uuml;ger</a>
1859 */
1860 public Double getMinZoomScale() {
1861 return minZoomScale;
1862 }
1863
1864 /**
1865 * Retuns the maximum allowed zoom scale. This is the smaller number value
1866 * of the two. Defaults to {@link Double}.MIN_VALUE
1867 *
1868 * @author <a href="mailto:[email protected]">Stefan Alfons
1869 * Kr&uuml;ger</a>
1870 */
1871 public Double getMaxZoomScale() {
1872 return maxZoomScale;
1873 }
1874
1875 /**
1876 * Set the maximum allowed zoom scale. This is the smaller number value of
1877 * the two. If <code>null</code> is passed, Double.MINVALUE are used which
1878 * mean there is no restriction.
1879 *
1880 * @author <a href="mailto:[email protected]">Stefan Alfons
1881 * Kr&uuml;ger</a>
1882 */
1883 public void setMaxZoomScale(final Double maxZoomScale) {
1884 this.maxZoomScale = maxZoomScale == null ? Double.MIN_VALUE
1885 : maxZoomScale;
1886 }
1887
1888 /**
1889 * Set the minimum (nearest) allowed zoom scale. This is the bigger number
1890 * value of the two. If <code>null</code> is passed, Double.MAXVALUE are
1891 * used which mean there is no restriction.
1892 *
1893 *
1894 * @author <a href="mailto:[email protected]">Stefan Alfons
1895 * Kr&uuml;ger</a>
1896 */
1897 public void setMinZoomScale(final Double minZoomScale) {
1898 this.minZoomScale = minZoomScale == null ? Double.MAX_VALUE
1899 : minZoomScale;
1900 }
1901
1902 /**
1903 * Defines an evelope of the viwable area. The JMapPane will never show
1904 * anything outside of this extend.
1905 *
1906 * @param maxExtend
1907 * <code>null</code> to not have this restriction.
1908 */
1909 public void setMaxExtend(Envelope maxExtend) {
1910 this.maxExtend = maxExtend;
1911 }
1912
1913 /**
1914 * Returns the evelope of the viewable area. The JMapPane will never show
1915 * anything outside of this extend. If this has been set to
1916 * <code>null</code> via {@link #setMaxExtend(Envelope)}, it tries to return
1917 * quickly the context's bounds. It it takes to long to determine the
1918 * context bounds, <code>null</code> is returned.
1919 *
1920 * @param maxExtend
1921 * <code>null</code> to not have this restriction.
1922 */
1923
1924 public Envelope getMaxExtend() {
1925 if (maxExtend == null) {
1926 final ReferencedEnvelope layerBounds = GTUtil
1927 .getVisibleLayoutBounds(localContext);
1928 if (layerBounds == null) {
1929 // TODO Last fallback could be the CRS valid area
1930 return null;
1931 }
1932
1933 // Kartenbereich um 10% vergroessern
1934 return JTSUtil.fixAspectRatio(this.getBounds(), JTSUtil
1935 .expandEnvelope(layerBounds, 0.1), true);
1936 }
1937 return maxExtend;
1938 }
1939
1940 /**
1941 * Set the background color of the map.
1942 *
1943 * @param if <code>null</code>, white is used.
1944 */
1945 public void setMapBackgroundColor(Color bgColor) {
1946 if (bgColor == null)
1947 bgColor = Color.WHITE;
1948 this.mapBackgroundColor = bgColor;
1949 }
1950
1951 /**
1952 * Returns the background {@link Color} of the map pane. Default is white.
1953 **/
1954 public Color getMapBackgroundColor() {
1955 return mapBackgroundColor;
1956 }
1957
1958 /**
1959 *
1960 * @param b
1961 */
1962 public void setPainting(boolean b) {
1963 acceptsRepaintCalls = b;
1964 }
1965
1966 /**
1967 * Fuegt der Map einen Listener hinzu.
1968 *
1969 * @param l
1970 * neuer Listener
1971 */
1972 public void addMapPaneListener(JMapPaneListener l) {
1973 mapPaneListeners.add(l);
1974 }
1975
1976 /**
1977 * Liste der angeschlossenen Listener, die auf Aktionen des MapPanes
1978 * lauschen.
1979 */
1980 protected Vector<JMapPaneListener> mapPaneListeners = new Vector<JMapPaneListener>();
1981
1982 /**
1983 * A flag indicating if dispose() was already called. If true, then further
1984 * use of this {@link SelectableXMapPane} is undefined.
1985 */
1986 private boolean disposed = false;
1987
1988 /**
1989 * Entfernt einen Listener von der Map.
1990 *
1991 * @param l
1992 * zu entfernender Listener
1993 */
1994 public void removeMapPaneListener(JMapPaneListener l) {
1995 mapPaneListeners.remove(l);
1996 }
1997
1998 /** Stored the time used for the last real rendering in ms. **/
1999 private long lastRenderingDuration = Long.MAX_VALUE;
2000
2001 // if null, no quick preview will be shown
2002 private int quickPreviewHint = 0;
2003
2004 /**
2005 * For every rendering thread started,
2006 * {@link GTUtil#createGTRenderer(MapContext)} is called to create a new
2007 * renderer. These Java2D rendering hints are passed to the
2008 * {@link GTRenderer}. The java2dHints are the same for all renderers (bg
2009 * and local).
2010 */
2011 private RenderingHints java2dHints;
2012
2013 /**
2014 * Returns in milli seconds the time the last rending of the
2015 * {@link SelectableXMapPane} took. #Long.MAX_VALUE if the JMapPane has not
2016 * been rendered yet.
2017 */
2018 public long getLastRenderingDuration() {
2019 return lastRenderingDuration;
2020 }
2021
2022 /**
2023 * Should be called when the {@link JMapPane} is not needed no more to help
2024 * the GarbageCollector
2025 *
2026 * Removes all {@link JMapPaneListener}s that are registered
2027 *
2028 * @author <a href="mailto:[email protected]">Stefan Alfons
2029 * Kr&uuml;ger</a>
2030 */
2031 public void dispose() {
2032 if (isDisposed())
2033 return;
2034
2035 disposed = true;
2036
2037 if (bgExecuter != null) {
2038 bgExecuter.cancelTask();
2039 }
2040
2041 if (localExecuter != null) {
2042 localExecuter.cancelTask();
2043 }
2044
2045 startRenderThreadsTimer.stop();
2046 // repainterTimer.stop();
2047
2048 if (bgImage != null)
2049 bgImage.flush();
2050 if (localImage != null)
2051 localImage.flush();
2052 // if (gadgetsImage != null)
2053 // gadgetsImage.flush();
2054 if (finalImage != null)
2055 finalImage.flush();
2056 if (preFinalImage != null)
2057 preFinalImage.flush();
2058
2059 // if (dragWaitCursorListener != null)
2060 // this.removeMouseListener(dragWaitCursorListener);
2061 // if (mouseWheelZoomListener != null)
2062 // this.removeMouseWheelListener(mouseWheelZoomListener);
2063
2064 // Alle mapPaneListener entfernen
2065 mapPaneListeners.clear();
2066
2067 removeMouseMotionListener(zoomMapPaneMouseListener);
2068 removeMouseListener(zoomMapPaneMouseListener);
2069
2070 if (localContext != null)
2071 getContext().clearLayerList();
2072 if (bgContext != null)
2073 getBgContext().clearLayerList();
2074
2075 removeAll();
2076 }
2077
2078 /**
2079 * A flag indicating if dispose() has already been called. If true, then
2080 * further use of this {@link SelectableXMapPane} is undefined.
2081 */
2082 private boolean isDisposed() {
2083 return disposed;
2084 }
2085
2086 public void setQuickPreviewHint(int quickPreviewHint) {
2087 this.quickPreviewHint = quickPreviewHint;
2088
2089 }
2090
2091 public void setJava2dHints(RenderingHints java2dHints) {
2092 this.java2dHints = java2dHints;
2093 }
2094
2095 public RenderingHints getJava2dHints() {
2096 return java2dHints;
2097 }
2098
2099 /**
2100 * Zooms towards a point.
2101 *
2102 * @param center
2103 * position in window coordinates
2104 * @param zoomFaktor
2105 * > 1 for zoom in, < 1 for zoom out. Default is 1.33.
2106 */
2107 public void zoomTo(Point center, Double zoomFaktor) {
2108 if (zoomFaktor == null || zoomFaktor == 0.)
2109 zoomFaktor = 2.;
2110
2111 Point2D gcenter = getScreenToWorld().transform(center, null);
2112 center = null;
2113
2114 if (Double.isNaN(gcenter.getX()) || Double.isNaN(gcenter.getY())
2115 || Double.isInfinite(gcenter.getX())
2116 || Double.isInfinite(gcenter.getY())
2117
2118 ) {
2119 // Not inside valid CRS area! cancel
2120 return;
2121 }
2122
2123 final Envelope mapArea = getMapArea();
2124
2125 Envelope newMapArea = new Envelope(mapArea);
2126 newMapArea.expandBy((mapArea.getWidth() * zoomFaktor - mapArea
2127 .getWidth()) / 2., (mapArea.getHeight() * zoomFaktor - mapArea
2128 .getHeight()) / 2.);
2129
2130 // Move the newMapArea above the new center
2131 newMapArea.translate(gcenter.getX() - mapArea.centre().x, gcenter
2132 .getY()
2133 - mapArea.centre().y);
2134
2135 setMapArea(newMapArea);
2136 }
2137
2138 /**
2139 * Zooms towards a point.
2140 *
2141 * @param center
2142 * position in window coordinates
2143 * @param zoomFactor
2144 * > 1 for zoom in, < 1 for zoom out. Default is 1.33
2145 */
2146 public void zoomTo(Point center) {
2147 zoomTo(center, null);
2148 }
2149
2150 public void mouseDragged(Point startPos, Point lastPos, MouseEvent event) {
2151
2152 if ((getState() == XMapPane.PAN)
2153 || ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0)) {
2154
2155 if (getCursor() != SwingUtil.PANNING_CURSOR) {
2156 setCursor(SwingUtil.PANNING_CURSOR);
2157
2158 // While panning, we deactivate the rendering. So the tasts are
2159 // ready to start when the panning os done.
2160 if (bgExecuter != null)
2161 bgExecuter.cancelTask();
2162 if (localExecuter != null)
2163 localExecuter.cancelTask();
2164 }
2165
2166 if (lastPos.x > 0 && lastPos.y > 0) {
2167 final int dx = event.getX() - lastPos.x;
2168 final int dy = event.getY() - lastPos.y;
2169
2170 // TODO Stop dragging when the drag would not be valid...
2171 // boolean dragValid = true;
2172 // // check if this panning results in a valid mapArea
2173 // {
2174 // Rectangle winBounds = xMapPane.getBounds();
2175 // winBounds.translate(xMapPane.imageOrigin.x,
2176 // -xMapPane.imageOrigin.y);
2177 // Envelope newMapAreaBefore = xMapPane.tranformWindowToGeo(
2178 // winBounds.x, winBounds.y, winBounds.x
2179 // + winBounds.width, winBounds.y
2180 // + winBounds.height);
2181 //
2182 //
2183 // winBounds = xMapPane.getBounds();
2184 // Point testIng = new Point(xMapPane.imageOrigin);
2185 // testIng.translate(dx, dy);
2186 // winBounds.translate(testIng.x, -testIng.y);
2187 // Envelope newMapAreaAfter = xMapPane.tranformWindowToGeo(
2188 // winBounds.x, winBounds.y, winBounds.x
2189 // + winBounds.width, winBounds.y
2190 // + winBounds.height);
2191 //
2192 // // If the last drag doesn't change the MapArea anymore cancel
2193 // it.
2194 // if (xMapPane.bestAllowedMapArea(newMapAreaAfter).equals(
2195 // xMapPane.bestAllowedMapArea(newMapAreaBefore))){
2196 // dragValid = false;
2197 // return;
2198 // }
2199 // }
2200
2201 imageOrigin.translate(dx, dy);
2202 updateFinalImage();
2203 repaint();
2204 }
2205
2206 } else if ((getState() == XMapPane.ZOOM_IN)
2207 || (getState() == XMapPane.ZOOM_OUT)
2208 || (getState() == XMapPane.SELECT_ALL)
2209 || (getState() == XMapPane.SELECT_TOP)
2210 // || (getState() == XMapPane.SELECT_ONE_FROM_TOP)
2211 ) {
2212 final Graphics graphics = getGraphics();
2213
2214 drawRectangle(graphics, startPos, event.getPoint());
2215
2216 if ((lastPos.x > 0) && (lastPos.y > 0)) {
2217 drawRectangle(graphics, startPos, lastPos);
2218 }
2219
2220 }
2221
2222 }
2223
2224 /**
2225 * Draws a rectangle in XOR mode from the origin at {@link #startPos} to the
2226 * given point. All in screen coordinates.
2227 */
2228 protected void drawRectangle(final Graphics graphics, Point startPos,
2229 Point e) {
2230 // undraw last box/draw new box
2231 final int left = Math.min(startPos.x, e.x);
2232 final int right = Math.max(startPos.x, e.x);
2233 final int top = Math.max(startPos.y, e.y);
2234 final int bottom = Math.min(startPos.y, e.y);
2235 final int width = right - left;
2236 final int height = top - bottom;
2237
2238 if (width == 0 && height == 0)
2239 return;
2240
2241 graphics.setXORMode(Color.WHITE);
2242 graphics.drawRect(left, bottom, width, height);
2243 }
2244
2245 /**
2246 * Finalizes a PAN action
2247 */
2248 public void performPan() {
2249 Rectangle winBounds = getBounds();
2250 winBounds.translate(-imageOrigin.x, -imageOrigin.y);
2251 Envelope newMapArea = tranformWindowToGeo(winBounds.x, winBounds.y,
2252 winBounds.x + winBounds.width, winBounds.y + winBounds.height);
2253
2254 imageOrigin.x = 0;
2255 imageOrigin.y = 0;
2256
2257 if (!setMapArea(newMapArea)) {
2258 updateFinalImage();
2259 repaint();
2260 }
2261
2262 if (getCursor() == SwingUtil.PANNING_CURSOR)
2263 setCursor(SwingUtil.PAN_CURSOR);
2264 }
2265
2266 public void onRenderingPending() {
2267 LOGGER.debug("Pending rendering updates the preview...");
2268 updateFinalImage();
2269 XMapPane.this.repaint();
2270 }
2271
2272 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26