/[schmitzm]/branches/2.0-RC2/src/skrueger/geotools/XMapPane.java
ViewVC logotype

Annotation of /branches/2.0-RC2/src/skrueger/geotools/XMapPane.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 539 - (hide annotations)
Fri Nov 20 19:10:05 2009 UTC (15 years, 3 months ago) by alfonx
Original Path: branches/1.0-gt2-2.6/src/skrueger/geotools/XMapPane.java
File size: 69760 byte(s)
Last commit before i crack the selection mechanism
* RenderingExceutor will also report Exceptions that crash the Thread

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26