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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26