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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26