/[schmitzm]/trunk/src/skrueger/geotools/XMapPane.java
ViewVC logotype

Annotation of /trunk/src/skrueger/geotools/XMapPane.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 540 - (hide annotations)
Fri Nov 20 19:34:50 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)
* Fixed the "jump left" problem in XMapPane
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 alfonx 540 newArea = JTSUtil.fixAspectRatio(getVisibleRect(), newArea,
798 alfonx 530 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 alfonx 540 newArea = JTSUtil.fixAspectRatio(getVisibleRect(), newArea,
822 alfonx 530 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 alfonx 540 newArea = JTSUtil.fixAspectRatio(getVisibleRect(), newArea,
848 alfonx 530 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 alfonx 540 newArea = JTSUtil.fixAspectRatio(getVisibleRect(), newArea,
874 alfonx 530 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 540 // if (1 == 1)return false;
983 alfonx 539
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 alfonx 540 finalImage = new BufferedImage(getVisibleRect().width,
1059     getVisibleRect().height, IMAGETYPE);
1060 alfonx 530
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 alfonx 540 localImage = new BufferedImage(getVisibleRect().width,
1077     getVisibleRect().height, IMAGETYPE_withAlpha);
1078 alfonx 530 }
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 alfonx 540 return JTSUtil.fixAspectRatio(getVisibleRect(), JTSUtil
1159 alfonx 530 .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 alfonx 540 Rectangle winBounds = getVisibleRect();
1523    
1524 alfonx 530 winBounds.translate(-imageOrigin.x, -imageOrigin.y);
1525     final Envelope newMapArea = tranformWindowToGeo(winBounds.x,
1526     winBounds.y, winBounds.x + winBounds.width, winBounds.y
1527     + winBounds.height);
1528    
1529     imageOrigin.x = 0;
1530     imageOrigin.y = 0;
1531    
1532     if (!setMapArea(newMapArea)) {
1533     /**
1534     * If setMapArea returns true, the finalImage is updated anyways.
1535     * This if-case exists to ensure that we repaint a correct image
1536     * even if the new panning area has been denied.
1537     */
1538     updateFinalImage();
1539     repaint();
1540     }
1541    
1542     if (getCursor() == SwingUtil.PANNING_CURSOR)
1543     setCursor(SwingUtil.PAN_CURSOR);
1544     }
1545    
1546     //
1547     // /**
1548     // * Nuetzlich wenn die Componente gedruckt (z.B. wenn ein Screenshot
1549     // gemacht
1550     // * wird) wird. Dann werden wird der Hintergrund auf WEISS gesetzt.
1551     // *
1552     // * @author <a href="mailto:[email protected]">Stefan Alfons
1553     // * Kr&uuml;ger</a>
1554     // */
1555     // @Override
1556     // public void print(final Graphics g) {
1557     // final Color orig = getBackground();
1558     // setBackground(Color.WHITE);
1559     //
1560     // // wrap in try/finally so that we always restore the state
1561     // try {
1562     // super.print(g);
1563     // } finally {
1564     // setBackground(orig);
1565     // }
1566     // }
1567    
1568     /**
1569     * Entfernt einen Listener von der Map.
1570     *
1571     * @param l
1572     * zu entfernender Listener
1573     */
1574     public void removeMapPaneListener(final JMapPaneListener l) {
1575     mapPaneListeners.remove(l);
1576     }
1577    
1578     /**
1579     * Cancels all running renderers and sets the flag to start new ones. <br>
1580     *
1581     * @see #startRenderThreadsTimer
1582     */
1583     private void requestStartRendering() {
1584     if (bgExecuter != null)
1585     bgExecuter.cancelTask();
1586     if (localExecuter != null)
1587     localExecuter.cancelTask();
1588     requestStartRendering = true;
1589    
1590     }
1591    
1592 alfonx 539 //
1593     // /**
1594     // * Berechnet die Transformation zwischen Fenster- und Karten-Koordinaten
1595     // * neu.
1596     // */
1597     // protected void resetTransforms() {
1598     // if (getMapArea() == null || getWidth() == 0 || getHeight() == 0)
1599     // return;
1600     //
1601     // // We store the last Transform
1602     // oldScreenToWorld = screenToWorld;
1603     //
1604     // this.screenToWorld = new AffineTransform(
1605     // // Genauso wie die Fenster-Koordinaten, werden die Longitude-Koordinaten
1606     // // nach rechts (Osten) hin groesser
1607     // // --> positive Verschiebung
1608     // getMapArea().getWidth() / getWidth(),
1609     // // keine Verzerrung
1610     // 0.0, 0.0,
1611     // // Waehrend die Fenster-Koordinaten nach unten hin groesser
1612     // // werden,
1613     // // werden Latitude-Koordinaten nach Sueden hin keiner
1614     // // --> negative Verschiebung
1615     // -getMapArea().getHeight() / getHeight(),
1616     // // Die Longitude-Koordinaten werden nach Osten hin groesser
1617     // // --> obere linke Ecke des Fensters hat also den Minimalwert
1618     // getMapArea().getMinX(),
1619     // // Die Latitude-Koordinaten werden nach Norden hin groesser
1620     // // --> obere linke Ecke des Fensters hat also den Maximalwert
1621     // getMapArea().getMaxY());
1622     //
1623     // try {
1624     // this.worldToScreen = screenToWorld.createInverse();
1625     // } catch (final NoninvertibleTransformException e) {
1626     // LOGGER.error(e);
1627     // }
1628     // }
1629 alfonx 530
1630 alfonx 539 /**
1631     * Calculate the affine transforms used to convert between world and pixel
1632     * coordinates. The calculations here are very basic and assume a cartesian
1633     * reference system.
1634     * <p>
1635     * Tne transform is calculated such that {@code envelope} will be centred in
1636     * the display
1637     *
1638     * @param envelope
1639     * the current map extent (world coordinates)
1640     * @param paintArea
1641     * the current map pane extent (screen units)
1642     */
1643     private void resetTransforms() {
1644     ReferencedEnvelope refMapEnv = new ReferencedEnvelope(mapArea,
1645     getContext().getCoordinateReferenceSystem());
1646 alfonx 540
1647     Rectangle paintArea = getVisibleRect(); // NOT USE GET BOUNDS!
1648 alfonx 539
1649     //
1650     // double xscale = paintArea.getWidth() / refEnv.getWidth();
1651     // double yscale = paintArea.getHeight() / refEnv.getHeight();
1652     //
1653     // double scale = Math.min(xscale, yscale);
1654     //
1655     // double xoff = refEnv.getMedian(0) * scale - paintArea.getCenterX();
1656     // double yoff = refEnv.getMedian(1) * scale + paintArea.getCenterY();
1657 alfonx 530
1658 alfonx 539 worldToScreen = RendererUtilities.worldToScreenTransform(refMapEnv,
1659     paintArea);
1660 alfonx 530
1661 alfonx 539 // worldToScreen = new AffineTransform(scale, 0, 0, -scale, -xoff,
1662     // yoff);
1663     try {
1664     screenToWorld = worldToScreen.createInverse();
1665 alfonx 530
1666 alfonx 539 } catch (NoninvertibleTransformException ex) {
1667     ex.printStackTrace();
1668     }
1669     }
1670 alfonx 536
1671 alfonx 509 public void setBgContext(final MapContext context) {
1672 mojays 2
1673 alfonx 509 // Remove the default listener from the old context
1674     if (this.bgContext != null) {
1675     this.bgContext.removeMapLayerListListener(bgContextListener);
1676    
1677     // adding listener to all layers
1678 alfonx 530 for (final MapLayer mapLayer : bgContext.getLayers()) {
1679 alfonx 509 mapLayer.removeMapLayerListener(bgMapLayerListener);
1680     }
1681 alfonx 144 }
1682 mojays 2
1683 alfonx 509 this.bgContext = context;
1684 mojays 2
1685 alfonx 509 if (context != null) {
1686     setMapArea(bgContext.getAreaOfInterest());
1687    
1688     this.bgContext.addMapLayerListListener(bgContextListener);
1689    
1690     // adding listener to all layers
1691 alfonx 530 for (final MapLayer mapLayer : bgContext.getLayers()) {
1692 alfonx 509 mapLayer.addMapLayerListener(bgMapLayerListener);
1693 alfonx 144 }
1694 alfonx 509 }
1695     mapImageInvalid = true;
1696     repaint();
1697     }
1698 mojays 2
1699 alfonx 530 public void setJava2dHints(final RenderingHints java2dHints) {
1700     this.java2dHints = java2dHints;
1701     }
1702    
1703 alfonx 509 /**
1704     *
1705 alfonx 530 * @param context
1706 alfonx 509 */
1707 alfonx 530 public void setLocalContext(final MapContext context) {
1708     // Remove the default listener from the old context
1709     if (this.localContext != null) {
1710     this.localContext.removeMapLayerListListener(localContextListener);
1711    
1712     // adding listener to all layers
1713     for (final MapLayer mapLayer : localContext.getLayers()) {
1714     mapLayer.removeMapLayerListener(localMapLayerListener);
1715 alfonx 509 }
1716 alfonx 530 }
1717 alfonx 509
1718 alfonx 530 this.localContext = context;
1719    
1720     if (context != null) {
1721    
1722     setMapArea(localContext.getAreaOfInterest());
1723    
1724     localRenderer.setContext(localContext);
1725    
1726     this.localContext.addMapLayerListListener(localContextListener);
1727    
1728     // adding listener to all layers
1729     for (final MapLayer mapLayer : localContext.getLayers()) {
1730     mapLayer.addMapLayerListener(localMapLayerListener);
1731 alfonx 144 }
1732     }
1733 mojays 2
1734 alfonx 530 mapImageInvalid = true;
1735     repaint();
1736 alfonx 509 }
1737    
1738     /**
1739     * @param newMapArea
1740     * @return <code>true</code> if the mapArea has been changed and a repaint
1741     * has been triggered.
1742     */
1743     public boolean setMapArea(final Envelope newMapArea) {
1744    
1745     if (newMapArea == null
1746     || bestAllowedMapArea(newMapArea).equals(mapArea)) {
1747     // No change.. no need to repaint
1748     return false;
1749 alfonx 144 }
1750 mojays 2
1751 alfonx 513 // Testing, whether NaN or Infinity are used in the newMapArea
1752     if (newMapArea.isNull() || Double.isInfinite(newMapArea.getMaxX())
1753     || Double.isInfinite(newMapArea.getMaxY())
1754     || Double.isInfinite(newMapArea.getMinX())
1755     || Double.isInfinite(newMapArea.getMinY())) {
1756     // No change.. ugly new values
1757     LOGGER.warn("setMapArea has been called with newArea = "
1758     + newMapArea);
1759     return false;
1760     }
1761    
1762 alfonx 539 final Envelope candNew = bestAllowedMapArea(newMapArea);
1763    
1764 alfonx 513 // Testing, whether the difference if just minimal
1765 alfonx 509 if (mapArea != null) {
1766 alfonx 530 final double tolX = mapArea.getWidth() / 1000.;
1767     final double tolY = mapArea.getHeight() / 1000.;
1768 alfonx 509 if ((candNew.getMinX() - tolX < mapArea.getMinX())
1769     && (mapArea.getMinX() < candNew.getMinX() + tolX)
1770     && (candNew.getMaxX() - tolX < mapArea.getMaxX())
1771     && (mapArea.getMaxX() < candNew.getMaxX() + tolX)
1772 mojays 2
1773 alfonx 509 && (candNew.getMinY() - tolY < mapArea.getMinY())
1774     && (mapArea.getMinY() < candNew.getMinY() + tolY)
1775     && (candNew.getMaxY() - tolY < mapArea.getMaxY())
1776     && (mapArea.getMaxY() < candNew.getMaxY() + tolY)
1777 mojays 2
1778 alfonx 509 ) {
1779     // The two mapAreas only differ my 1/1000th.. ignore
1780 alfonx 505
1781 alfonx 509 return false;
1782     }
1783     }
1784 mojays 2
1785 alfonx 539 // New map are is accepted:
1786 alfonx 509 oldMapArea = mapArea;
1787 alfonx 539 mapArea = candNew;
1788     resetTransforms();
1789 mojays 2
1790 alfonx 509 if (localContext != null) {
1791     localContext.setAreaOfInterest(mapArea, localContext
1792     .getCoordinateReferenceSystem());
1793 alfonx 144 }
1794 alfonx 509 if (bgContext != null) {
1795     bgContext.setAreaOfInterest(mapArea, localContext
1796     .getCoordinateReferenceSystem());
1797     }
1798     mapImageInvalid = true;
1799     mapAreaChanged = true;
1800     repaint();
1801 alfonx 513
1802 alfonx 539 LOGGER.debug("New maparea = " + mapArea);
1803 alfonx 509 return true;
1804     }
1805 mojays 2
1806 alfonx 509 /**
1807 alfonx 530 * Set the background color of the map.
1808 alfonx 509 *
1809 alfonx 530 * @param if <code>null</code>, white is used.
1810 alfonx 509 */
1811 alfonx 530 public void setMapBackgroundColor(Color bgColor) {
1812     if (bgColor == null)
1813     bgColor = Color.WHITE;
1814     this.mapBackgroundColor = bgColor;
1815 alfonx 509 }
1816 mojays 2
1817 alfonx 509 /**
1818 alfonx 530 * Set the BufferedImage to use as a flaoting icon in the lower right corner
1819     *
1820     * @param mapImageIcon
1821     * <code>null</code> is allowed and deactivates this icon.
1822 alfonx 529 */
1823 alfonx 530 public void setMapImage(final BufferedImage mapImage) {
1824     this.mapImage = mapImage;
1825     }
1826 alfonx 529
1827     /**
1828 alfonx 530 * Sets whether a layer is regarded or ignored on {@link #SELECT_TOP},
1829     * {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP} actions.
1830     *
1831     * @param layer
1832     * a layer
1833     * @param selectable
1834     * if {@code false} the layer is ignored during the upper
1835     * mentioned actions. If <code>null</code>, the default (true)
1836     * will be used.
1837 alfonx 529 */
1838 alfonx 530 public void setMapLayerSelectable(final MapLayer layer,
1839     final Boolean selectable) {
1840     if (selectable == null)
1841     mapLayerSelectable.remove(layer);
1842     else
1843     mapLayerSelectable.put(layer, selectable);
1844 alfonx 144 }
1845 mojays 2
1846 alfonx 509 /**
1847 alfonx 530 * Defines an evelope of the viwable area. The JMapPane will never show
1848     * anything outside of this extend.
1849 alfonx 509 *
1850 alfonx 530 * @param maxExtend
1851     * <code>null</code> to not have this restriction.
1852 alfonx 509 */
1853 alfonx 530 public void setMaxExtend(final Envelope maxExtend) {
1854     this.maxExtend = maxExtend;
1855 alfonx 144 }
1856 mojays 2
1857 alfonx 509 /**
1858 alfonx 530 * Set the maximum allowed zoom scale. This is the smaller number value of
1859     * the two. If <code>null</code> is passed, Double.MINVALUE are used which
1860     * mean there is no restriction.
1861 alfonx 509 *
1862 alfonx 530 * @author <a href="mailto:[email protected]">Stefan Alfons
1863     * Kr&uuml;ger</a>
1864 alfonx 509 */
1865 alfonx 530 public void setMaxZoomScale(final Double maxZoomScale) {
1866     this.maxZoomScale = maxZoomScale == null ? Double.MIN_VALUE
1867     : maxZoomScale;
1868 alfonx 144 }
1869 mojays 2
1870 alfonx 530 // /** Stored the time used for the last real rendering in ms. **/
1871     // private long lastRenderingDuration = Long.MAX_VALUE;
1872    
1873 alfonx 509 /**
1874 alfonx 530 * Set the minimum (nearest) allowed zoom scale. This is the bigger number
1875     * value of the two. If <code>null</code> is passed, Double.MAXVALUE are
1876     * used which mean there is no restriction.
1877 alfonx 509 *
1878 alfonx 530 *
1879     * @author <a href="mailto:[email protected]">Stefan Alfons
1880     * Kr&uuml;ger</a>
1881 alfonx 509 */
1882 alfonx 530 public void setMinZoomScale(final Double minZoomScale) {
1883     this.minZoomScale = minZoomScale == null ? Double.MAX_VALUE
1884     : minZoomScale;
1885 alfonx 144 }
1886 mojays 2
1887 alfonx 509 /**
1888     *
1889 alfonx 530 * @param b
1890 alfonx 509 */
1891 alfonx 530 public void setPainting(final boolean b) {
1892     acceptsRepaintCalls = b;
1893     }
1894 mojays 2
1895 alfonx 530 // /**
1896     // * Returns in milli seconds the time the last rending of the
1897     // * {@link SelectableXMapPane} took. #Long.MAX_VALUE if the JMapPane has
1898     // not
1899     // * been rendered yet.
1900     // */
1901     // public long getLastRenderingDuration() {
1902     // return lastRenderingDuration;
1903     // }
1904 mojays 2
1905 alfonx 530 public void setQuickPreviewHint(final int quickPreviewHint) {
1906     this.quickPreviewHint = quickPreviewHint;
1907 mojays 2
1908 alfonx 530 }
1909 mojays 2
1910 alfonx 530 private void setRendererHints(final Map<Object, Object> rendererHints) {
1911 alfonx 533 if (rendererHints != null)
1912     this.rendererHints = rendererHints;
1913 alfonx 144 }
1914 alfonx 513
1915 alfonx 509 /**
1916 alfonx 530 * Enables/Disables the ZOOM Mouse Listener. Upates the Cursor and stops the
1917     * repaint Timer if
1918     *
1919     * @param state
1920 alfonx 509 */
1921 alfonx 530 public void setState(final int state) {
1922     this.state = state;
1923 mojays 2
1924 alfonx 530 zoomMapPaneMouseListener.setEnabled((state == ZOOM_IN
1925     || state == ZOOM_OUT || state == PAN));
1926 alfonx 509
1927 alfonx 530 // Je nach Aktion den Cursor umsetzen
1928     updateCursor();
1929 alfonx 509 }
1930 alfonx 504
1931 alfonx 509 /**
1932 alfonx 530 * Standardmaessig wird der Cursor automatisch je nach MapPane-Aktion (Zoom,
1933     * Auswahl, ...) gesetzt. Mit dieser Methode kann ein statischer Cursor
1934     * gesetzt werden, der unabhaengig von der aktuellen MapPanes-Aktion
1935     * beibehalten wird. Um diesen statischen Cursor wieder zu entfernen, kann
1936     * {@code null} als Parameter uebergeben werden
1937 alfonx 509 *
1938 alfonx 530 * @param cursor
1939     * Cursor
1940 alfonx 509 */
1941 alfonx 530 public void setStaticCursor(final Cursor cursor) {
1942     this.staticCursor = cursor;
1943     if (cursor != null)
1944     super.setCursor(cursor);
1945 alfonx 509 }
1946 alfonx 431
1947 alfonx 509 /**
1948     * Starts rendering on one or two threads
1949     */
1950     private void startRendering() {
1951    
1952     if (!isWellDefined())
1953     return;
1954    
1955 alfonx 529 if (bgExecuter != null) {
1956 alfonx 509 // Stop all renderers
1957     bgExecuter.cancelTask();
1958 alfonx 524 }
1959 alfonx 509
1960 alfonx 529 if (localExecuter != null) {
1961 alfonx 509 localExecuter.cancelTask();
1962 alfonx 524 }
1963 alfonx 529
1964 alfonx 530 final Rectangle curPaintArea = getVisibleRect();
1965 mojays 2
1966 alfonx 509 /**
1967     * We have to set new renderer
1968     */
1969    
1970     if (getBgContext() != null) {
1971 alfonx 533 bgRenderer.setJava2DHints(getJava2dHints());
1972     bgRenderer.setRendererHints(getRendererHints());
1973    
1974 alfonx 529 // bgExecuter = new RenderingExecutor();
1975     // LOGGER.debug("starting bg renderer:");
1976     // // /* System.out.println("rendering"); */
1977     // final GTRenderer createGTRenderer = GTUtil.createGTRenderer(
1978     // bgContext, getRendererHints());
1979     // createGTRenderer.setJava2DHints(getJava2dHints());
1980     // bgExecuter.submit(getBgContext().getAreaOfInterest(),
1981     // curPaintArea,
1982     // (Graphics2D) getBgImage().getGraphics(), createGTRenderer);
1983 alfonx 509 }
1984    
1985     if (getContext() != null) {
1986 alfonx 529 // localExecuter = new RenderingExecutor(this, 150l);
1987 alfonx 530 // LOGGER.debug("starting local renderer:");
1988    
1989 alfonx 533 localRenderer.setJava2DHints(getJava2dHints());
1990     localRenderer.setRendererHints(getRendererHints());
1991 alfonx 529
1992 alfonx 533 ReferencedEnvelope areaOfInterest = getMapArea();
1993     final boolean submitted = localExecuter.submit(areaOfInterest,
1994     curPaintArea, (Graphics2D) getLocalImage().getGraphics(),
1995     localRenderer, getWorldToScreenTransform());
1996 alfonx 530 if (submitted)
1997     repaintTimer.restart();
1998     else
1999     requestStartRendering = true; // Try to start rendering again in
2000     // a moment
2001 alfonx 509 }
2002    
2003 alfonx 514 updateCursor();
2004 alfonx 144 }
2005 mojays 2
2006 alfonx 509 /**
2007 alfonx 530 * Transformiert einen Geo-Koordinaten-Bereich in Fenster-Koordinaten.
2008 alfonx 509 *
2009 alfonx 530 * @param ox
2010     * X-Koordinate der VON-Position
2011     * @param oy
2012     * Y-Koordinate der VON-Position
2013     * @param px
2014     * X-Koordinate der BIS-Position
2015     * @param py
2016     * Y-Koordinate der BIS-Position
2017     * @param winToGeotransform
2018     * Eine Window to Geo transform. If <code>null</code>,
2019     * {@link #getScreenToWorld()} is used.
2020 alfonx 509 */
2021 alfonx 530 public Envelope tranformGeoToWindow(final double ox, final double oy,
2022 alfonx 539 final double px, final double py) {
2023     final AffineTransform at = getWorldToScreenTransform();
2024 alfonx 530 Point2D geoO;
2025 alfonx 539 // try {
2026     geoO = at.transform(new Point2D.Double(ox, oy), null);
2027     final Point2D geoP = at.transform(new Point2D.Double(px, py), null);
2028     return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP.getY());
2029     // } catch (final NoninvertibleTransformException e) {
2030     // LOGGER.error(e);
2031     // return new Envelope(ox, oy, px, py);
2032     // }
2033 alfonx 144 }
2034 mojays 2
2035 alfonx 509 /**
2036 alfonx 530 * Transformiert einen Fenster-Koordinaten-Bereich in Geo-Koordinaten.
2037 alfonx 509 *
2038 alfonx 530 * @param ox
2039     * X-Koordinate der VON-Position
2040     * @param oy
2041     * Y-Koordinate der VON-Position
2042     * @param px
2043     * X-Koordinate der BIS-Position
2044     * @param py
2045     * Y-Koordinate der BIS-Position
2046 alfonx 509 */
2047 alfonx 530 public Envelope tranformWindowToGeo(final int ox, final int oy,
2048     final int px, final int py) {
2049     final AffineTransform at = getScreenToWorld();
2050     final Point2D geoO = at.transform(new Point2D.Double(ox, oy), null);
2051     final Point2D geoP = at.transform(new Point2D.Double(px, py), null);
2052 alfonx 539
2053     // Mmmmm... don't really understand why its x,x,y,y
2054     // return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP.getY());
2055     return new Envelope( new Coordinate(geoO.getX(), geoO.getY()), new Coordinate(geoP.getX(), geoP.getY()));
2056 alfonx 144 }
2057 mojays 2
2058 alfonx 509 /**
2059 alfonx 530 * Will update the cursor. If all rendering is finished also stops the
2060     * {@link #repaintTimer}
2061 alfonx 509 */
2062 alfonx 530 public void updateCursor() {
2063 mojays 2
2064 alfonx 530 // if the renderers have stopped, also stop the timer that is updating
2065     // the final image
2066     if (bgExecuter != null && bgExecuter.isRunning()
2067     || localExecuter != null && localExecuter.isRunning()) {
2068     setCursor(WAIT_CURSOR);
2069     return;
2070     } else {
2071     // Allow one last rendering
2072     if (repaintTimer.isRunning()) {
2073 alfonx 533 System.out.println("one last rendering....");
2074 alfonx 530 repaintTimer.stop();
2075     updateFinalImage();
2076     repaint();
2077     }
2078 alfonx 144 }
2079 mojays 2
2080 alfonx 530 // wenn manueller Cursor gesetzt ist, dann diesen verwenden (unabhaengig
2081     // von der aktuellen Aktion
2082     if (this.staticCursor != null) {
2083     setCursor(staticCursor);
2084     return;
2085     }
2086     if (getCursor() == SwingUtil.PANNING_CURSOR) {
2087     // This cursor will reset itself
2088     return;
2089     }
2090 mojays 2
2091 alfonx 530 // Set the cursor depending on what tool is in use...
2092     switch (state) {
2093     case SELECT_TOP:
2094     case SELECT_ONE_FROM_TOP:
2095     case SELECT_ALL:
2096     setCursor(SwingUtil.CROSSHAIR_CURSOR);
2097     break;
2098     case ZOOM_IN:
2099     setCursor(SwingUtil.ZOOMIN_CURSOR);
2100     break;
2101     case ZOOM_OUT:
2102     setCursor(SwingUtil.ZOOMOUT_CURSOR);
2103     break;
2104     case PAN:
2105     setCursor(SwingUtil.PAN_CURSOR);
2106     break;
2107     default:
2108     setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
2109     break;
2110     }
2111 alfonx 144 }
2112 mojays 2
2113 alfonx 509 /**
2114 alfonx 530 * The renderers are all rendering into their own {@link Image}s. This
2115     * method combines all images to one {@link #finalImage}. The
2116     * {@link #repaintTimer} is calling this method regularely to update the
2117     * {@link #finalImage} even if the renderers are still working.
2118 alfonx 509 */
2119 alfonx 530 synchronized protected Image updateFinalImage() {
2120 mojays 2
2121 alfonx 530 // Render the two map images first, into the preFinalImage
2122     if (bgExecuter != null) {
2123     final Graphics2D preFinalG = (Graphics2D) getPreFinalImage()
2124     .getGraphics();
2125     preFinalG.setBackground(getMapBackgroundColor());
2126 mojays 2
2127 alfonx 530 preFinalG.drawImage(getBgImage(), 0, 0, getMapBackgroundColor(),
2128     null);
2129 mojays 2
2130 alfonx 530 // // Draw the local layers image
2131     preFinalG.drawImage(getLocalImage(), 0, 0, null);
2132     preFinalG.dispose();
2133 mojays 2
2134 alfonx 76 } else {
2135 alfonx 530 preFinalImage = getLocalImage();
2136 alfonx 76 }
2137 mojays 2
2138 alfonx 530 final Graphics2D finalG = getFinalImage().createGraphics();
2139     finalG.setBackground(getMapBackgroundColor());
2140     finalG.drawImage(getPreFinalImage(), imageOrigin.x, imageOrigin.y,
2141     getMapBackgroundColor(), null);
2142 mojays 2
2143 alfonx 530 final int finalImageHeight = getFinalImage().getHeight(null);
2144     final int finalImageWidth = getFinalImage().getWidth(null);
2145 alfonx 414
2146 alfonx 530 final Rectangle painedArea = new Rectangle(imageOrigin.x,
2147     imageOrigin.y, finalImageWidth, finalImageHeight);
2148     SwingUtil.clearAround(finalG, painedArea, getVisibleRect());
2149 alfonx 414
2150 alfonx 533 addGadgets(finalG, false);
2151 alfonx 414
2152 alfonx 530 finalG.dispose();
2153 alfonx 414
2154 alfonx 530 return finalImage;
2155 alfonx 76 }
2156    
2157 alfonx 144 /**
2158 alfonx 530 * Paints some optional stuff into the given {@link Graphics2D}. Usually
2159     * called as the last layer when {@link #updateFinalImage()}
2160 alfonx 533 *
2161     * @param forceWait
2162     * if <code>true</code>, a Wait-message will be painted even
2163     * though the rendering threads may not yet have started. If
2164     * <code>false</code>, it will only depend on
2165     * {@link #localExecuter.isRunning} and #bgExecuter.isRunning
2166 alfonx 144 */
2167 alfonx 533 private void addGadgets(final Graphics2D graphics, boolean forceWait) {
2168 mojays 2
2169 alfonx 530 // Paint a logo to the bottom right if available
2170     if (mapImage != null) {
2171 alfonx 540 Rectangle visibleRect = getVisibleRect();
2172     graphics.drawImage(mapImage, visibleRect.width
2173     - mapImage.getWidth() - 10, getVisibleRect().height
2174 alfonx 530 - mapImage.getHeight() - 10, null);
2175     }
2176 alfonx 539
2177 alfonx 533 int y = 17;
2178 mojays 2
2179 alfonx 530 // If the rendering process is still running, indicate this is the image
2180 alfonx 533 if (forceWait || bgExecuter != null && bgExecuter.isRunning()
2181 alfonx 530 || localExecuter != null && localExecuter.isRunning()) {
2182 mojays 2
2183 alfonx 539 y += 8;
2184    
2185 alfonx 530 final Color c = graphics.getColor();
2186     graphics.setFont(waitFont);
2187 alfonx 533
2188     graphics.setColor(getMapBackgroundColor());
2189     graphics.drawString(waitMsg, 5, y);
2190     graphics.setColor(getMapBackgroundColor());
2191 alfonx 539 graphics.drawString(waitMsg, 7, y + 2);
2192 alfonx 530 graphics.setColor(Color.BLACK);
2193 alfonx 539 graphics.drawString(waitMsg, 6, y + 1);
2194 mojays 2
2195 alfonx 530 graphics.setColor(c);
2196 alfonx 539
2197 alfonx 533 y += 24;
2198 alfonx 418 }
2199 alfonx 414
2200 alfonx 533 if (renderingErrors != null) {
2201    
2202     final Color c = graphics.getColor();
2203     graphics.setFont(errorFont);
2204    
2205     for (Exception ex : renderingErrors) {
2206    
2207     if (ex instanceof java.lang.IllegalArgumentException
2208     && ex.getMessage().equals(
2209     "Argument \"sourceCRS\" should not be null."))
2210     continue;
2211    
2212     String errStr = ex.getLocalizedMessage();
2213    
2214     graphics.setColor(Color.WHITE);
2215     graphics.drawString(errStr, 5, y);
2216     graphics.setColor(Color.RED);
2217     graphics.drawString(errStr, 6, y + 1);
2218    
2219     y += 19;
2220     }
2221    
2222     graphics.setColor(c);
2223     }
2224    
2225 alfonx 505 }
2226    
2227     /**
2228 alfonx 533 * Sets the {@link #mapArea} to best possibly present the given features. If
2229     * only one single point is given, the window is moved over the point.
2230 alfonx 509 *
2231 alfonx 530 * @param features
2232     * if <code>null</code> or size==0, the function doesn nothing.
2233 alfonx 509 */
2234 alfonx 530 public void zoomTo(
2235     final FeatureCollection<SimpleFeatureType, SimpleFeature> features) {
2236 alfonx 509
2237 alfonx 530 final CoordinateReferenceSystem mapCRS = getContext()
2238     .getCoordinateReferenceSystem();
2239     final CoordinateReferenceSystem fCRS = features.getSchema()
2240     .getGeometryDescriptor().getCoordinateReferenceSystem();
2241 alfonx 509
2242 alfonx 530 double width = mapArea.getWidth();
2243     double height = mapArea.getHeight();
2244     final double ratio = height / width;
2245 alfonx 509
2246 alfonx 530 if (features == null || features.size() == 0) {
2247     // feature count == 0 Zoom to the full extend
2248 alfonx 509 return;
2249 alfonx 530 } else if (features.size() == 1) {
2250 alfonx 529
2251 alfonx 530 // feature count == 1 Just move the window to the point and zoom 'a
2252     // bit'
2253     final SimpleFeature singleFeature = features.iterator().next();
2254 alfonx 509
2255 alfonx 530 if (((Geometry) singleFeature.getDefaultGeometry())
2256     .getCoordinates().length > 1) {
2257     // System.out.println("Zoomed to only pne poylgon");
2258     // Poly
2259     // TODO max width vs. height
2260     width = features.getBounds().getWidth() * 3;
2261     height = ratio * width;
2262     } else {
2263     // System.out.println("Zoomed in a bit becasue only one point");
2264     // width *= .9;
2265     // height *= .9;
2266     }
2267 alfonx 509
2268 alfonx 530 Coordinate centre = features.getBounds().centre();
2269     if (!mapCRS.equals(fCRS)) {
2270     // only to calculations if the CRS differ
2271 alfonx 529 try {
2272 alfonx 530 MathTransform fToMap;
2273     fToMap = CRS.findMathTransform(fCRS, mapCRS);
2274     // centre is transformed to the mapCRS
2275     centre = JTS.transform(centre, null, fToMap);
2276     } catch (final FactoryException e) {
2277     LOGGER.error("Looking for a Math transform", e);
2278     } catch (final TransformException e) {
2279     LOGGER.error("Looking for a Math transform", e);
2280 alfonx 529 }
2281     }
2282 alfonx 509
2283 alfonx 530 final Coordinate newLeftBottom = new Coordinate(centre.x - width
2284     / 2., centre.y - height / 2.);
2285     final Coordinate newTopRight = new Coordinate(
2286     centre.x + width / 2., centre.y + height / 2.);
2287 alfonx 509
2288 alfonx 530 final Envelope newMapArea = new Envelope(newLeftBottom, newTopRight);
2289 alfonx 509
2290 alfonx 530 setMapArea(newMapArea);
2291 alfonx 529
2292 alfonx 530 } else {
2293     final ReferencedEnvelope fBounds = features.getBounds();
2294 alfonx 529
2295 alfonx 530 Envelope bounds;
2296     if (!mapCRS.equals(fCRS)) {
2297     bounds = JTSUtil.transformEnvelope(fBounds, fCRS, mapCRS);
2298     } else {
2299     bounds = fBounds;
2300     }
2301     // BB umrechnen von Layer-CRS in Map-CRS
2302 alfonx 529
2303 alfonx 530 // Expand a bit
2304     bounds.expandBy(bounds.getWidth() / 6., bounds.getHeight() / 6.);
2305 alfonx 509
2306 alfonx 530 setMapArea(bounds);
2307     }
2308 alfonx 509 }
2309    
2310     /**
2311 alfonx 530 * Zooms towards a point.
2312     *
2313     * @param center
2314     * position in window coordinates
2315     * @param zoomFactor
2316     * > 1 for zoom in, < 1 for zoom out. Default is 1.33
2317 alfonx 509 */
2318 alfonx 530 public void zoomTo(final Point center) {
2319     zoomTo(center, null);
2320 alfonx 509 }
2321    
2322     /**
2323     * Zooms towards a point.
2324     *
2325     * @param center
2326     * position in window coordinates
2327     * @param zoomFaktor
2328     * > 1 for zoom in, < 1 for zoom out. Default is 1.33.
2329     */
2330     public void zoomTo(Point center, Double zoomFaktor) {
2331     if (zoomFaktor == null || zoomFaktor == 0.)
2332     zoomFaktor = 2.;
2333    
2334 alfonx 530 final Point2D gcenter = getScreenToWorld().transform(center, null);
2335 alfonx 509 center = null;
2336 alfonx 513
2337     if (Double.isNaN(gcenter.getX()) || Double.isNaN(gcenter.getY())
2338     || Double.isInfinite(gcenter.getX())
2339     || Double.isInfinite(gcenter.getY())
2340    
2341 alfonx 509 ) {
2342     // Not inside valid CRS area! cancel
2343     return;
2344     }
2345    
2346     final Envelope mapArea = getMapArea();
2347 alfonx 513
2348 alfonx 530 final Envelope newMapArea = new Envelope(mapArea);
2349 alfonx 513 newMapArea.expandBy((mapArea.getWidth() * zoomFaktor - mapArea
2350     .getWidth()) / 2., (mapArea.getHeight() * zoomFaktor - mapArea
2351     .getHeight()) / 2.);
2352 alfonx 539
2353     // TODO we actually want that
2354     // // Move the newMapArea above the new center
2355     // newMapArea.translate(gcenter.getX() - mapArea.centre().x, gcenter
2356     // .getY()
2357     // - mapArea.centre().y);
2358 alfonx 509
2359     setMapArea(newMapArea);
2360     }
2361    
2362 mojays 2 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26