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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26