/[schmitzm]/branches/1.0-gt2-2.6/src/skrueger/geotools/XMapPane.java
ViewVC logotype

Contents of /branches/1.0-gt2-2.6/src/skrueger/geotools/XMapPane.java

Parent Directory Parent Directory | Revision Log Revision Log


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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26