/[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 545 - (hide annotations)
Sat Nov 21 18:22:36 2009 UTC (15 years, 3 months ago) by alfonx
File size: 69638 byte(s)
Improved RasterPositionLabel. Earlier it was just looking for the top grid layer.. now it checks for the first grid layer under the mouse position. If there are multiple grid layers with different BBOXes this makes sense.
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     localRenderer.setContext(getContext());
393     requestStartRendering();
394    
395     }
396    
397     @Override
398     public void layerChanged(final MapLayerListEvent event) {
399     localRenderer.setContext(getContext());
400     requestStartRendering();
401     }
402    
403     @Override
404     public void layerMoved(final MapLayerListEvent event) {
405     localRenderer.setContext(getContext());
406     requestStartRendering();
407     }
408    
409     @Override
410     public void layerRemoved(final MapLayerListEvent event) {
411     if (event.getLayer() != null)
412     event.getLayer().removeMapLayerListener(localMapLayerListener);
413     localRenderer.setContext(getContext());
414     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     localRenderer.setContext(getContext()); // betters for SLD changes?!
427     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     while (maxAllowedExtend != null && !maxAllowedExtend.contains(newArea)
766     && newArea != null && !newArea.isNull()
767     && !Double.isNaN(newArea.getMinX())
768     && !Double.isNaN(newArea.getMaxX())
769     && !Double.isNaN(newArea.getMinY())
770     && !Double.isNaN(newArea.getMaxY())) {
771     /*
772     * If a maxExtend is set, we have to honour that...
773     */
774    
775     // Exceeds top? Move down and maybe cut
776     if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {
777     final double divY = newArea.getMaxY()
778     - maxAllowedExtend.getMaxY();
779     // LOGGER.debug("Moving area down by " + divY);
780    
781     newArea = new Envelope(new Coordinate(newArea.getMinX(),
782     newArea.getMinY() - divY), new Coordinate(newArea
783     .getMaxX(), newArea.getMaxY() - divY));
784    
785     if (newArea.getMinY() < maxAllowedExtend.getMinY()) {
786     // LOGGER.debug("Now it exeeds the bottom border.. cut!");
787     // And cut the bottom if it moved out of the area
788     newArea = new Envelope(new Coordinate(newArea.getMinX(),
789     maxAllowedExtend.getMinY()), new Coordinate(newArea
790     .getMaxX(), newArea.getMaxY()));
791    
792     // LOGGER.debug("and fix aspect ratio");
793    
794 alfonx 544 newArea = JTSUtil.fixAspectRatio(getVisibleRect(),
795     new ReferencedEnvelope(newArea, env
796     .getCoordinateReferenceSystem()), false);
797 alfonx 530 }
798 alfonx 509 }
799    
800 alfonx 530 // Exceeds bottom? Move up and maybe cut
801     if (newArea.getMinY() < maxAllowedExtend.getMinY()) {
802     final double divY = newArea.getMinY()
803     - maxAllowedExtend.getMinY();
804     // LOGGER.debug("Moving area up by " + divY);
805    
806     newArea = new Envelope(new Coordinate(newArea.getMinX(),
807     newArea.getMinY() - divY), new Coordinate(newArea
808     .getMaxX(), newArea.getMaxY() - divY));
809    
810     if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {
811     // LOGGER.debug("Now it exeeds the top border.. cut!");
812     // And cut the bottom if it moved out of the area
813     newArea = new Envelope(new Coordinate(newArea.getMinX(),
814     newArea.getMinY()), new Coordinate(newArea
815     .getMaxX(), maxAllowedExtend.getMaxY()));
816    
817     // LOGGER.debug("and fix aspect ratio");
818    
819 alfonx 544 newArea = JTSUtil.fixAspectRatio(getVisibleRect(),
820     new ReferencedEnvelope(newArea, env
821     .getCoordinateReferenceSystem()), false);
822 alfonx 509 }
823     }
824    
825 alfonx 530 // Exceeds to the right? move and maybe cut
826     if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {
827 alfonx 509
828 alfonx 530 // Move left..
829     final double divX = newArea.getMaxX()
830     - maxAllowedExtend.getMaxX();
831     // LOGGER.debug("Moving area left by " + divX);
832 alfonx 509
833 alfonx 530 newArea = new Envelope(new Coordinate(newArea.getMinX() - divX,
834     newArea.getMinY()), new Coordinate(newArea.getMaxX()
835     - divX, newArea.getMaxY()));
836 alfonx 509
837 alfonx 530 if (newArea.getMinX() < maxAllowedExtend.getMinX()) {
838     // LOGGER.debug("Now it exeeds the left border.. cut!");
839     // And cut the left if it moved out of the area
840     newArea = new Envelope(new Coordinate(maxAllowedExtend
841     .getMinX(), newArea.getMinY()), new Coordinate(
842     newArea.getMaxX(), newArea.getMaxY()));
843 alfonx 509
844 alfonx 530 // LOGGER.debug("and fix aspect ratio");
845    
846 alfonx 544 newArea = JTSUtil.fixAspectRatio(getVisibleRect(),
847     new ReferencedEnvelope(newArea, env
848     .getCoordinateReferenceSystem()), false);
849 alfonx 530 }
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 544 newArea = JTSUtil.fixAspectRatio(getVisibleRect(),
874     new ReferencedEnvelope(newArea, env
875     .getCoordinateReferenceSystem()), false);
876 alfonx 530 }
877     }
878 alfonx 509 }
879 mojays 2
880 alfonx 544 return new ReferencedEnvelope(newArea, env
881     .getCoordinateReferenceSystem());
882 alfonx 509 }
883 alfonx 414
884 alfonx 509 /**
885 alfonx 530 * Should be called when the {@link JMapPane} is not needed no more to help
886     * the GarbageCollector
887     *
888     * Removes all {@link JMapPaneListener}s that are registered
889     *
890     * @author <a href="mailto:[email protected]">Stefan Alfons
891     * Kr&uuml;ger</a>
892 alfonx 509 */
893 alfonx 530 public void dispose() {
894     if (isDisposed())
895     return;
896 alfonx 513
897 alfonx 530 setPainting(false);
898 alfonx 513
899 alfonx 530 resizeTimer.stop();
900     startRenderThreadsTimer.stop();
901    
902     disposed = true;
903    
904     if (bgExecuter != null) {
905     bgExecuter.cancelTask();
906     bgExecuter.dispose();
907 alfonx 504 }
908 alfonx 509
909 alfonx 530 if (localExecuter != null) {
910     int i = 0;
911     localExecuter.cancelTask();
912     while (i++ < 10 && localExecuter.isRunning()) {
913     try {
914 alfonx 543 Thread.sleep(200);
915 alfonx 530 } catch (final InterruptedException e) {
916 alfonx 543 LOGGER
917     .warn(
918     "while XMapPane we are waiting for the localExcutor to stop",
919     e);
920 alfonx 530 }
921     }
922     if (localExecuter.isRunning()) {
923 alfonx 543 LOGGER
924     .warn("localExecutor Thread still running after 2s! Continuing anyways...");
925 alfonx 530 }
926     localExecuter.dispose();
927     }
928 alfonx 539
929 alfonx 530 disposeImages();
930 alfonx 509
931 alfonx 543 // Remove all mapPaneListeners that have registered with us
932 alfonx 530 mapPaneListeners.clear();
933 alfonx 509
934 alfonx 530 removeMouseMotionListener(zoomMapPaneMouseListener);
935     removeMouseListener(zoomMapPaneMouseListener);
936 alfonx 509
937 alfonx 530 if (localContext != null)
938     getContext().clearLayerList();
939     if (bgContext != null)
940     getBgContext().clearLayerList();
941    
942     removeAll();
943 alfonx 509 }
944    
945     /**
946 alfonx 530 * Draws a rectangle in XOR mode from the origin at {@link #startPos} to the
947     * given point. All in screen coordinates.
948 alfonx 509 */
949 alfonx 530 protected void drawRectangle(final Graphics graphics, final Point startPos,
950     final Point e) {
951 alfonx 509
952 alfonx 530 if (!isWellDefined())
953 alfonx 509 return;
954 alfonx 307
955 alfonx 530 // undraw last box/draw new box
956     final int left = Math.min(startPos.x, e.x);
957     final int right = Math.max(startPos.x, e.x);
958     final int top = Math.max(startPos.y, e.y);
959     final int bottom = Math.min(startPos.y, e.y);
960     final int width = right - left;
961     final int height = top - bottom;
962    
963     if (width == 0 && height == 0)
964 alfonx 509 return;
965 alfonx 414
966 alfonx 530 graphics.setXORMode(Color.WHITE);
967     graphics.drawRect(left, bottom, width, height);
968 alfonx 144 }
969 mojays 2
970 alfonx 509 /**
971 alfonx 530 * Diretly paints scaled preview into the {@link SelectableXMapPane}. Used
972     * to give the user something to look at while we are rendering. Method
973     * should be called after {@link #setMapArea(Envelope)} has been set to the
974     * new mapArea and transform has been reset.<br>
975     *
976     * @param g
977     * Graphics2D to paint the preview into
978     *
979     * @param state
980     * Max be {@link #ZOOM_IN} or {@link #ZOOM_OUT}
981 alfonx 509 */
982 alfonx 530 protected boolean drawScaledPreviewImage_Zoom(final Graphics2D graphics) {
983 alfonx 509
984 alfonx 543 // if (1 == 1)return false;
985    
986 alfonx 530 if (quickPreviewHint == 0)
987     return false;
988 alfonx 509
989 alfonx 530 graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
990     RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
991     graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
992     RenderingHints.VALUE_ANTIALIAS_OFF);
993     graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
994     RenderingHints.VALUE_RENDER_SPEED);
995 alfonx 509
996 alfonx 530 if (oldMapArea == null)
997     return false;
998 mojays 2
999 alfonx 530 final Rectangle visibleArea = getVisibleRect();
1000 alfonx 509
1001 alfonx 530 // Calculate the oldMapArea in the current WindowCoordinates:
1002     final Envelope oldMapWindow = tranformGeoToWindow(oldMapArea.getMinX(),
1003     oldMapArea.getMinY(), oldMapArea.getMaxX(), oldMapArea
1004 alfonx 539 .getMaxY());
1005 alfonx 509
1006 alfonx 530 final int xx1 = (int) Math.round(oldMapWindow.getMinX());
1007     final int yy1 = (int) Math.round(oldMapWindow.getMinY());
1008     final int xx2 = (int) Math.round(oldMapWindow.getMaxX());
1009     final int yy2 = (int) Math.round(oldMapWindow.getMaxY());
1010 alfonx 509
1011 alfonx 530 graphics.drawImage(getPreFinalImage(), xx1, yy1, xx2, yy2,
1012     (int) visibleArea.getMinX(), (int) visibleArea.getMinY(),
1013     (int) visibleArea.getMaxX(), (int) visibleArea.getMaxY(),
1014     getMapBackgroundColor(), null);
1015 alfonx 509
1016 alfonx 530 final Rectangle painedArea = new Rectangle(xx1, yy1, xx2 - xx1, yy2
1017     - yy1);
1018 alfonx 529
1019 alfonx 530 SwingUtil.clearAround(graphics, painedArea, visibleArea);
1020 alfonx 529
1021 alfonx 533 addGadgets(graphics, true);
1022 alfonx 509
1023 alfonx 530 quickPreviewHint = 0;
1024 alfonx 509
1025 alfonx 533 repaintTimer.restart();
1026    
1027 alfonx 530 graphics.dispose();
1028 alfonx 509
1029 alfonx 530 // Something has been drawn
1030     return true;
1031     }
1032 mojays 2
1033 alfonx 530 public MapContext getBgContext() {
1034     return bgContext;
1035     }
1036 mojays 2
1037 alfonx 530 /**
1038     * Lazyly initializes a {@link BufferedImage} for the background renderer.
1039     */
1040     private Image getBgImage() {
1041     //
1042     // if (bgImage == null) {
1043     // bgImage = createImage(getBounds().width, getBounds().height);
1044     // }
1045    
1046     return bgImage;
1047     }
1048    
1049     public MapContext getContext() {
1050     if (localContext == null) {
1051     setLocalContext(new DefaultMapContext());
1052 alfonx 144 }
1053 alfonx 530 return localContext;
1054     }
1055 mojays 2
1056 alfonx 530 private BufferedImage getFinalImage() {
1057     //
1058     if (finalImage == null) {
1059     // Rectangle curPaintArea = getVisibleRect();
1060 alfonx 540 finalImage = new BufferedImage(getVisibleRect().width,
1061     getVisibleRect().height, IMAGETYPE);
1062 alfonx 530
1063 alfonx 509 requestStartRendering();
1064 alfonx 144 }
1065 alfonx 530 return finalImage;
1066     }
1067 alfonx 509
1068 alfonx 530 public RenderingHints getJava2dHints() {
1069     return java2dHints;
1070     }
1071    
1072 alfonx 509 /**
1073 alfonx 530 * Lazyly initializes a {@link BufferedImage} for the background renderer.
1074 alfonx 509 */
1075 alfonx 530 private BufferedImage getLocalImage() {
1076 alfonx 509
1077 alfonx 530 if (localImage == null) {
1078 alfonx 540 localImage = new BufferedImage(getVisibleRect().width,
1079     getVisibleRect().height, IMAGETYPE_withAlpha);
1080 alfonx 530 }
1081 alfonx 509
1082 alfonx 530 return localImage;
1083     }
1084    
1085     /**
1086     * Returns a copy of the mapArea
1087     *
1088     * @return
1089     */
1090 alfonx 533 public ReferencedEnvelope getMapArea() {
1091 alfonx 530 if (mapArea == null) {
1092     ReferencedEnvelope mapArea_ = null;
1093     try {
1094     mapArea_ = localContext.getLayerBounds();
1095     } catch (final IOException e) {
1096 alfonx 544 LOGGER.warn("localContext.getLayerBounds()", e);
1097 alfonx 509 }
1098    
1099 alfonx 544 if (mapArea_ == null && bgContext != null) {
1100     try {
1101     mapArea_ = bgContext.getLayerBounds();
1102     } catch (final IOException e) {
1103     LOGGER.warn("bgContext.getLayerBounds()", e);
1104     }
1105     }
1106    
1107 alfonx 530 if (mapArea_ != null) {
1108     mapArea = bestAllowedMapArea(mapArea_);
1109 alfonx 544 requestStartRendering();
1110 alfonx 530 }
1111 alfonx 509 }
1112    
1113 alfonx 530 if (mapArea == null)
1114     return null;
1115 alfonx 509
1116 alfonx 539 // TODO is needed at all, this should go to setMapArea maybe
1117 alfonx 533 if (localContext.getCoordinateReferenceSystem() == null)
1118     try {
1119     localContext.setCoordinateReferenceSystem(GeoImportUtil
1120     .getDefaultCRS());
1121 alfonx 539 } catch (Exception e) {
1122     throw new RuntimeException("setting context CRS:", e);
1123 alfonx 533 }
1124    
1125     return new ReferencedEnvelope(mapArea, localContext
1126     .getCoordinateReferenceSystem());
1127 alfonx 530 }
1128 alfonx 509
1129 alfonx 530 /**
1130     * Returns the background {@link Color} of the map pane. Default is white.
1131     **/
1132     public Color getMapBackgroundColor() {
1133     return mapBackgroundColor;
1134     }
1135 alfonx 509
1136     /**
1137 alfonx 530 * Get the BufferedImage to use as a flaoting icon in the lower right
1138     * corner.
1139     *
1140     * @return <code>null</code> if the feature is deactivated.
1141 alfonx 509 */
1142 alfonx 530 public BufferedImage getMapImage() {
1143     return mapImage;
1144     }
1145 alfonx 509
1146 alfonx 530 /**
1147     * Returns the evelope of the viewable area. The JMapPane will never show
1148     * anything outside of this extend. If this has been set to
1149     * <code>null</code> via {@link #setMaxExtend(Envelope)}, it tries to return
1150     * quickly the context's bounds. It it takes to long to determine the
1151     * context bounds, <code>null</code> is returned.
1152     *
1153     * @param maxExtend
1154     * <code>null</code> to not have this restriction.
1155     */
1156 alfonx 509
1157 alfonx 530 public Envelope getMaxExtend() {
1158     if (maxExtend == null) {
1159     final ReferencedEnvelope layerBounds = GTUtil
1160     .getVisibleLayoutBounds(localContext);
1161     if (layerBounds == null) {
1162     // TODO Last fallback could be the CRS valid area
1163     return null;
1164     }
1165 alfonx 509
1166 alfonx 530 // Kartenbereich um 10% vergroessern
1167 alfonx 540 return JTSUtil.fixAspectRatio(getVisibleRect(), JTSUtil
1168 alfonx 530 .expandEnvelope(layerBounds, 0.1), true);
1169 alfonx 509 }
1170 alfonx 530 return maxExtend;
1171     }
1172 alfonx 509
1173     /**
1174 alfonx 530 * Retuns the maximum allowed zoom scale. This is the smaller number value
1175     * of the two. Defaults to {@link Double}.MIN_VALUE
1176     *
1177     * @author <a href="mailto:[email protected]">Stefan Alfons
1178     * Kr&uuml;ger</a>
1179 alfonx 509 */
1180 alfonx 530 public Double getMaxZoomScale() {
1181     return maxZoomScale;
1182     }
1183 alfonx 509
1184 alfonx 530 /**
1185     * Retuns the minimum allowed zoom scale. This is the bigger number value of
1186     * the two. Defaults to {@link Double}.MAX_VALUE
1187     *
1188     * @author <a href="mailto:[email protected]">Stefan Alfons
1189     * Kr&uuml;ger</a>
1190     */
1191     public Double getMinZoomScale() {
1192     return minZoomScale;
1193     }
1194 alfonx 509
1195 alfonx 530 private Image getPreFinalImage() {
1196     // if (preFinalImage == null) {
1197     //
1198     // // Rectangle curPaintArea = getVisibleRect();
1199     // // preFinalImage = new BufferedImage(curPaintArea.width,
1200     // // curPaintArea.height, BufferedImage.TYPE_INT_RGB);
1201     //
1202     // preFinalImage = createImage(getBounds().width, getBounds().height);
1203     //
1204     // requestStartRendering();
1205     // }
1206     return preFinalImage;
1207     }
1208 alfonx 509
1209 alfonx 530 public Map<Object, Object> getRendererHints() {
1210 alfonx 533 // Clear label cache
1211     labelCache.clear();
1212     rendererHints.put(StreamingRenderer.LABEL_CACHE_KEY, labelCache);
1213    
1214 alfonx 530 return rendererHints;
1215     }
1216 alfonx 509
1217     /**
1218     * Liefert eine affine Transformation, um von den Fenster-Koordinaten in die
1219     * Karten-Koordinaten (Lat/Lon) umzurechnen.
1220     *
1221     * @return eine Kopie der aktuellen Transformation; <code>null</code> wenn
1222     * noch keine Karte angezeigt wird
1223     */
1224     public AffineTransform getScreenToWorld() {
1225     if (screenToWorld == null)
1226     resetTransforms();
1227     // nur Kopie der Transformation zurueckgeben!
1228     if (screenToWorld == null)
1229     return null;
1230     return new AffineTransform(screenToWorld);
1231 alfonx 144 }
1232 mojays 2
1233 alfonx 530 public int getState() {
1234     return state;
1235     }
1236    
1237     /**
1238     * Liefert den statisch eingestellten Cursor, der unabhaengig von der
1239     * eingestellten MapPane-Aktion (Zoom, Auswahl, ...) verwendet wird.
1240     *
1241     * @return {@code null}, wenn kein statischer Cursor verwendet, sondern der
1242     * Cursor automatisch je nach MapPane-Aktion eingestellt wird.
1243     */
1244     public Cursor getStaticCursor() {
1245     return this.staticCursor;
1246     }
1247    
1248 alfonx 509 public AffineTransform getWorldToScreenTransform() {
1249     if (worldToScreen == null) {
1250     resetTransforms();
1251     }
1252     // nur Kopie der Transformation zurueckgeben!
1253     return new AffineTransform(worldToScreen);
1254 alfonx 144 }
1255 mojays 2
1256 alfonx 530 /**
1257     * A flag indicating if dispose() has already been called. If true, then
1258     * further use of this {@link SelectableXMapPane} is undefined.
1259     */
1260     private boolean isDisposed() {
1261     return disposed;
1262     }
1263    
1264     /**
1265     * Returns whether a layer is regarded or ignored on {@link #SELECT_TOP},
1266     * {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP} actions. Returns
1267     * <code>true</code> if the selectability has not been defined.
1268     *
1269     * @param layer
1270     * a layer
1271     */
1272     public boolean isMapLayerSelectable(final MapLayer layer) {
1273     final Boolean selectable = mapLayerSelectable.get(layer);
1274     return selectable == null ? true : selectable;
1275     }
1276    
1277     /**
1278     * Return <code>true</code> if a CRS and a {@link #mapArea} are set and the
1279     * {@link XMapPane} is visible and has bounds set.
1280     */
1281     public boolean isWellDefined() {
1282     try {
1283     if (getContext() == null)
1284     return false;
1285     if (getContext().getLayerCount() <= 0)
1286     return false;
1287 alfonx 544 if (getVisibleRect().getWidth() == 0)
1288 alfonx 530 return false;
1289 alfonx 544 if (getVisibleRect().getHeight() == 0)
1290 alfonx 530 return false;
1291 alfonx 544 // if (getMapArea() == null)
1292     // return false;
1293 alfonx 530 } catch (final Exception e) {
1294     return false;
1295 alfonx 509 }
1296 alfonx 530 return true;
1297 alfonx 144 }
1298 mojays 2
1299 alfonx 530 public void mouseDragged(final Point startPos, final Point lastPos,
1300     final MouseEvent event) {
1301    
1302     if ((getState() == XMapPane.PAN)
1303     || ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0)) {
1304    
1305     if (getCursor() != SwingUtil.PANNING_CURSOR) {
1306     setCursor(SwingUtil.PANNING_CURSOR);
1307    
1308     // While panning, we deactivate the rendering. So the tasts are
1309     // ready to start when the panning os done.
1310     if (bgExecuter != null)
1311     bgExecuter.cancelTask();
1312     if (localExecuter != null)
1313     localExecuter.cancelTask();
1314     }
1315    
1316     if (lastPos.x > 0 && lastPos.y > 0) {
1317     final int dx = event.getX() - lastPos.x;
1318     final int dy = event.getY() - lastPos.y;
1319    
1320     // TODO Stop dragging when the drag would not be valid...
1321     // boolean dragValid = true;
1322     // // check if this panning results in a valid mapArea
1323     // {
1324     // Rectangle winBounds = xMapPane.getBounds();
1325     // winBounds.translate(xMapPane.imageOrigin.x,
1326     // -xMapPane.imageOrigin.y);
1327     // Envelope newMapAreaBefore = xMapPane.tranformWindowToGeo(
1328     // winBounds.x, winBounds.y, winBounds.x
1329     // + winBounds.width, winBounds.y
1330     // + winBounds.height);
1331     //
1332     //
1333     // winBounds = xMapPane.getBounds();
1334     // Point testIng = new Point(xMapPane.imageOrigin);
1335     // testIng.translate(dx, dy);
1336     // winBounds.translate(testIng.x, -testIng.y);
1337     // Envelope newMapAreaAfter = xMapPane.tranformWindowToGeo(
1338     // winBounds.x, winBounds.y, winBounds.x
1339     // + winBounds.width, winBounds.y
1340     // + winBounds.height);
1341     //
1342     // // If the last drag doesn't change the MapArea anymore cancel
1343     // it.
1344     // if (xMapPane.bestAllowedMapArea(newMapAreaAfter).equals(
1345     // xMapPane.bestAllowedMapArea(newMapAreaBefore))){
1346     // dragValid = false;
1347     // return;
1348     // }
1349     // }
1350    
1351     imageOrigin.translate(dx, dy);
1352     updateFinalImage();
1353     repaint();
1354     }
1355    
1356     } else if ((getState() == XMapPane.ZOOM_IN)
1357     || (getState() == XMapPane.ZOOM_OUT)
1358     || (getState() == XMapPane.SELECT_ALL)
1359     || (getState() == XMapPane.SELECT_TOP)
1360     // || (getState() == XMapPane.SELECT_ONE_FROM_TOP)
1361     ) {
1362     final Graphics graphics = getGraphics();
1363    
1364     drawRectangle(graphics, startPos, event.getPoint());
1365    
1366     if ((lastPos.x > 0) && (lastPos.y > 0)) {
1367     drawRectangle(graphics, startPos, lastPos);
1368     }
1369    
1370     graphics.dispose();
1371    
1372     }
1373    
1374 alfonx 144 }
1375 mojays 2
1376 alfonx 509 /**
1377 alfonx 530 * Called by the {@link RenderingExecutor} when rendering was cancelled.
1378     */
1379     public void onRenderingCancelled() {
1380 alfonx 544 LOGGER.debug("Rendering cancelled");
1381 alfonx 530 repaintTimer.stop();
1382     }
1383    
1384     /**
1385     * Called by the {@link RenderingExecutor} when rendering has been
1386     * completed.
1387     */
1388     public void onRenderingCompleted() {
1389     repaintTimer.stop();
1390     updateFinalImage();
1391     repaint();
1392 alfonx 533 if (renderingErrors.size() > 0)
1393     renderingErrors.remove(0);
1394 alfonx 530 }
1395    
1396     /**
1397     * Called by the {@linkplain XMapPane.RenderingTask} when rendering failed.
1398     * Publishes a {@linkplain MapPaneEvent} of type {@code
1399     * MapPaneEvent.Type.RENDERING_STOPPED} to listeners.
1400 alfonx 509 *
1401 alfonx 530 * @param renderingError
1402     * The error that occured during rendering
1403     *
1404     * @see MapPaneListener#onRenderingStopped(org.geotools.swing.event.MapPaneEvent)
1405 alfonx 509 */
1406 alfonx 530 public void onRenderingFailed(final Exception renderingError) {
1407 alfonx 533 this.renderingErrors.add(renderingError);
1408     if (renderingErrors.size() > 3)
1409     renderingErrors.remove(0);
1410 alfonx 530 repaintTimer.stop();
1411     LOGGER.warn("Rendering failed", renderingError);
1412     updateFinalImage();
1413     repaint();
1414 mojays 2
1415 alfonx 530 }
1416    
1417     public void onRenderingPending() {
1418     // LOGGER.debug("Pending rendering updates the preview...");
1419     updateFinalImage();
1420     repaint();
1421     }
1422    
1423     @Override
1424     protected void paintComponent(final Graphics g) {
1425 alfonx 544
1426 alfonx 530 if (!acceptsRepaintCalls)
1427     return;
1428    
1429     // Maybe update the cursor and maybe stop the repainting timer
1430     updateCursor();
1431    
1432 alfonx 533 // super.paintComponent(g); // candidate for removal
1433 alfonx 530
1434     boolean paintedSomething = false;
1435    
1436     if (mapImageInvalid) { /* if the map changed then redraw */
1437    
1438     mapImageInvalid = false; // Reset for next round
1439    
1440     // If the new mapArea and the oldMapArea intersect, we can draw some
1441     // quick scaled preview to make the user feel that something is
1442     // happening.
1443     if (mapAreaChanged && oldMapArea != null
1444     && getMapArea().intersects(oldMapArea)
1445 alfonx 544 & !getMapArea().equals(oldMapArea) && !paneResized) {
1446 alfonx 530
1447     mapAreaChanged = false;
1448    
1449     if (getMapArea().covers(oldMapArea)) {
1450     setQuickPreviewHint(ZOOM_OUT);
1451     paintedSomething = drawScaledPreviewImage_Zoom((Graphics2D) g);
1452     } else if (oldMapArea.covers(getMapArea())) {
1453     setQuickPreviewHint(ZOOM_IN);
1454     paintedSomething = drawScaledPreviewImage_Zoom((Graphics2D) g);
1455     }
1456    
1457 alfonx 509 }
1458 alfonx 530
1459 alfonx 544 // if (paneResized) {
1460     // paneResized = false;
1461     // disposeImages();
1462     // }
1463     //
1464     // // Start the Threads and Timers to render the image
1465     // requestStartRendering();
1466 alfonx 530
1467 alfonx 509 }
1468 mojays 2
1469 alfonx 530 if (!paintedSomething) {
1470 mojays 2
1471 alfonx 530 g.drawImage(getFinalImage(), 0, 0, null);
1472 alfonx 529
1473 alfonx 530 g.dispose(); // cand. for removal
1474 alfonx 509
1475 alfonx 530 paintedSomething = true; // cand. for removal
1476     }
1477 alfonx 529
1478 alfonx 530 }
1479 alfonx 509
1480 alfonx 530 /**
1481     * Heavily works on releasing all resources related to the four
1482     * {@link Image}s used to cache the results of the different renderers.<br>
1483     * In November 2009 i had some memory leaking problems with {@link XMapPane}
1484     * . The resources of the buffered images were never released. It seem to be
1485     * important to call the GC right after flushing the images.<br>
1486     * Hence this method may take a while, because it calls the GC up to four
1487     * times.
1488     */
1489     private void disposeImages() {
1490    
1491     // System.out.println("vorher = "
1492     // + new MbDecimalFormatter().format(LangUtil.gcTotal()));
1493     // bi.flush();
1494     // return bi = null;
1495     // System.out.println("nacher = "
1496     // + new MbDecimalFormatter().format(LangUtil.gcTotal()));
1497     //
1498     // System.out.println("\n");
1499    
1500     if (preFinalImage != null) {
1501     preFinalImage.flush();
1502     preFinalImage = null;
1503     LangUtil.gc();
1504 alfonx 509 }
1505 alfonx 530 if (finalImage != null) {
1506     finalImage.flush();
1507     finalImage = null;
1508     LangUtil.gc();
1509     }
1510     if (localImage != null) {
1511     localImage.flush();
1512     localImage = null;
1513     LangUtil.gc();
1514     }
1515     if (bgImage != null) {
1516     bgImage.flush();
1517     bgImage = null;
1518     LangUtil.gc();
1519     }
1520 alfonx 509
1521 alfonx 144 }
1522 mojays 2
1523 alfonx 530 /**
1524     * Performs a {@value #PAN} action. During panning, the displacement is
1525     * stored in {@link #imageOrigin} object. Calling {@link #performPan()} will
1526     * reset the offset and call {@link #setMapArea(Envelope)}.
1527     */
1528     public void performPan() {
1529    
1530 alfonx 540 Rectangle winBounds = getVisibleRect();
1531 alfonx 543
1532 alfonx 530 winBounds.translate(-imageOrigin.x, -imageOrigin.y);
1533     final Envelope newMapArea = tranformWindowToGeo(winBounds.x,
1534     winBounds.y, winBounds.x + winBounds.width, winBounds.y
1535     + winBounds.height);
1536    
1537     imageOrigin.x = 0;
1538     imageOrigin.y = 0;
1539    
1540     if (!setMapArea(newMapArea)) {
1541     /**
1542     * If setMapArea returns true, the finalImage is updated anyways.
1543     * This if-case exists to ensure that we repaint a correct image
1544     * even if the new panning area has been denied.
1545     */
1546     updateFinalImage();
1547     repaint();
1548     }
1549    
1550     if (getCursor() == SwingUtil.PANNING_CURSOR)
1551     setCursor(SwingUtil.PAN_CURSOR);
1552     }
1553    
1554     //
1555     // /**
1556     // * Nuetzlich wenn die Componente gedruckt (z.B. wenn ein Screenshot
1557     // gemacht
1558     // * wird) wird. Dann werden wird der Hintergrund auf WEISS gesetzt.
1559     // *
1560     // * @author <a href="mailto:[email protected]">Stefan Alfons
1561     // * Kr&uuml;ger</a>
1562     // */
1563     // @Override
1564     // public void print(final Graphics g) {
1565     // final Color orig = getBackground();
1566     // setBackground(Color.WHITE);
1567     //
1568     // // wrap in try/finally so that we always restore the state
1569     // try {
1570     // super.print(g);
1571     // } finally {
1572     // setBackground(orig);
1573     // }
1574     // }
1575    
1576     /**
1577     * Entfernt einen Listener von der Map.
1578     *
1579     * @param l
1580     * zu entfernender Listener
1581     */
1582     public void removeMapPaneListener(final JMapPaneListener l) {
1583     mapPaneListeners.remove(l);
1584     }
1585    
1586     /**
1587     * Cancels all running renderers and sets the flag to start new ones. <br>
1588     *
1589     * @see #startRenderThreadsTimer
1590     */
1591     private void requestStartRendering() {
1592     if (bgExecuter != null)
1593     bgExecuter.cancelTask();
1594     if (localExecuter != null)
1595     localExecuter.cancelTask();
1596    
1597 alfonx 544 mapImageInvalid = true;
1598     if (paneResized) {
1599     paneResized = false;
1600     disposeImages();
1601     }
1602     requestStartRendering = true;
1603    
1604 alfonx 530 }
1605    
1606 alfonx 539 /**
1607     * Calculate the affine transforms used to convert between world and pixel
1608     * coordinates. The calculations here are very basic and assume a cartesian
1609     * reference system.
1610     * <p>
1611     * Tne transform is calculated such that {@code envelope} will be centred in
1612     * the display
1613     *
1614     * @param envelope
1615     * the current map extent (world coordinates)
1616     * @param paintArea
1617     * the current map pane extent (screen units)
1618     */
1619     private void resetTransforms() {
1620     ReferencedEnvelope refMapEnv = new ReferencedEnvelope(mapArea,
1621     getContext().getCoordinateReferenceSystem());
1622 alfonx 540
1623 alfonx 544 // System.out
1624     // .println("paintArea in resetTeansofrms = " + getVisibleRect());
1625     if (!isWellDefined())
1626     return;
1627 alfonx 543
1628 alfonx 539 worldToScreen = RendererUtilities.worldToScreenTransform(refMapEnv,
1629 alfonx 544 getVisibleRect());
1630 alfonx 530
1631 alfonx 539 try {
1632     screenToWorld = worldToScreen.createInverse();
1633 alfonx 530
1634 alfonx 539 } catch (NoninvertibleTransformException ex) {
1635 alfonx 544 LOGGER
1636     .error("can't invert worldToScreen to get screenToWorld!",
1637     ex);
1638 alfonx 539 }
1639     }
1640 alfonx 536
1641 alfonx 509 public void setBgContext(final MapContext context) {
1642 mojays 2
1643 alfonx 509 // Remove the default listener from the old context
1644     if (this.bgContext != null) {
1645     this.bgContext.removeMapLayerListListener(bgContextListener);
1646    
1647     // adding listener to all layers
1648 alfonx 530 for (final MapLayer mapLayer : bgContext.getLayers()) {
1649 alfonx 509 mapLayer.removeMapLayerListener(bgMapLayerListener);
1650     }
1651 alfonx 144 }
1652 mojays 2
1653 alfonx 509 this.bgContext = context;
1654 mojays 2
1655 alfonx 509 if (context != null) {
1656 alfonx 544 // setMapArea(bgContext.getAreaOfInterest());
1657 alfonx 509
1658     this.bgContext.addMapLayerListListener(bgContextListener);
1659    
1660     // adding listener to all layers
1661 alfonx 530 for (final MapLayer mapLayer : bgContext.getLayers()) {
1662 alfonx 509 mapLayer.addMapLayerListener(bgMapLayerListener);
1663 alfonx 144 }
1664 alfonx 509 }
1665 alfonx 544
1666     requestStartRendering();
1667 alfonx 509 }
1668 mojays 2
1669 alfonx 530 public void setJava2dHints(final RenderingHints java2dHints) {
1670     this.java2dHints = java2dHints;
1671     }
1672    
1673     public void setLocalContext(final MapContext context) {
1674     // Remove the default listener from the old context
1675     if (this.localContext != null) {
1676     this.localContext.removeMapLayerListListener(localContextListener);
1677    
1678     // adding listener to all layers
1679     for (final MapLayer mapLayer : localContext.getLayers()) {
1680     mapLayer.removeMapLayerListener(localMapLayerListener);
1681 alfonx 509 }
1682 alfonx 530 }
1683 alfonx 509
1684 alfonx 530 this.localContext = context;
1685    
1686     if (context != null) {
1687    
1688 alfonx 544 // setMapArea(localContext.getAreaOfInterest());
1689 alfonx 530
1690     localRenderer.setContext(localContext);
1691    
1692     this.localContext.addMapLayerListListener(localContextListener);
1693    
1694     // adding listener to all layers
1695     for (final MapLayer mapLayer : localContext.getLayers()) {
1696     mapLayer.addMapLayerListener(localMapLayerListener);
1697 alfonx 144 }
1698     }
1699 mojays 2
1700 alfonx 544 requestStartRendering();
1701    
1702 alfonx 509 }
1703    
1704 alfonx 544 public boolean setMapArea(final Envelope newMapArea) {
1705     return setMapArea(new ReferencedEnvelope(newMapArea, getContext()
1706     .getCoordinateReferenceSystem()));
1707     }
1708    
1709 alfonx 509 /**
1710     * @param newMapArea
1711     * @return <code>true</code> if the mapArea has been changed and a repaint
1712     * has been triggered.
1713     */
1714 alfonx 544 public boolean setMapArea(final ReferencedEnvelope newMapArea) {
1715     //
1716     // if (getVisibleRect().getWidth() == 0 || (!isVisible()) ||
1717     // getVisibleRect().getHeight() == 0) {
1718     // // The window is not yet defined.. so we can not really determine the
1719     // asepect ratio for the mapPane. We store this as a candidate mappane
1720     // and handle it when the component resizes or becomes visible)
1721     // mapAreaCondidate = newMapArea;
1722     // }
1723 alfonx 509
1724     if (newMapArea == null
1725     || bestAllowedMapArea(newMapArea).equals(mapArea)) {
1726     // No change.. no need to repaint
1727     return false;
1728 alfonx 144 }
1729 mojays 2
1730 alfonx 513 // Testing, whether NaN or Infinity are used in the newMapArea
1731     if (newMapArea.isNull() || Double.isInfinite(newMapArea.getMaxX())
1732     || Double.isInfinite(newMapArea.getMaxY())
1733     || Double.isInfinite(newMapArea.getMinX())
1734     || Double.isInfinite(newMapArea.getMinY())) {
1735     // No change.. ugly new values
1736     LOGGER.warn("setMapArea has been called with newArea = "
1737     + newMapArea);
1738     return false;
1739     }
1740    
1741 alfonx 539 final Envelope candNew = bestAllowedMapArea(newMapArea);
1742    
1743 alfonx 513 // Testing, whether the difference if just minimal
1744 alfonx 509 if (mapArea != null) {
1745 alfonx 530 final double tolX = mapArea.getWidth() / 1000.;
1746     final double tolY = mapArea.getHeight() / 1000.;
1747 alfonx 509 if ((candNew.getMinX() - tolX < mapArea.getMinX())
1748     && (mapArea.getMinX() < candNew.getMinX() + tolX)
1749     && (candNew.getMaxX() - tolX < mapArea.getMaxX())
1750     && (mapArea.getMaxX() < candNew.getMaxX() + tolX)
1751 mojays 2
1752 alfonx 509 && (candNew.getMinY() - tolY < mapArea.getMinY())
1753     && (mapArea.getMinY() < candNew.getMinY() + tolY)
1754     && (candNew.getMaxY() - tolY < mapArea.getMaxY())
1755     && (mapArea.getMaxY() < candNew.getMaxY() + tolY)
1756 mojays 2
1757 alfonx 509 ) {
1758     // The two mapAreas only differ my 1/1000th.. ignore
1759 alfonx 505
1760 alfonx 509 return false;
1761     }
1762     }
1763 mojays 2
1764 alfonx 539 // New map are is accepted:
1765 alfonx 509 oldMapArea = mapArea;
1766 alfonx 539 mapArea = candNew;
1767     resetTransforms();
1768 mojays 2
1769 alfonx 509 if (localContext != null) {
1770     localContext.setAreaOfInterest(mapArea, localContext
1771     .getCoordinateReferenceSystem());
1772 alfonx 144 }
1773 alfonx 509 if (bgContext != null) {
1774     bgContext.setAreaOfInterest(mapArea, localContext
1775     .getCoordinateReferenceSystem());
1776     }
1777 alfonx 544
1778 alfonx 509 mapAreaChanged = true;
1779 alfonx 513
1780 alfonx 544 repaint(100);
1781    
1782     requestStartRendering();
1783    
1784 alfonx 509 return true;
1785     }
1786 mojays 2
1787 alfonx 509 /**
1788 alfonx 530 * Set the background color of the map.
1789 alfonx 509 *
1790 alfonx 530 * @param if <code>null</code>, white is used.
1791 alfonx 509 */
1792 alfonx 530 public void setMapBackgroundColor(Color bgColor) {
1793     if (bgColor == null)
1794     bgColor = Color.WHITE;
1795     this.mapBackgroundColor = bgColor;
1796 alfonx 509 }
1797 mojays 2
1798 alfonx 509 /**
1799 alfonx 530 * Set the BufferedImage to use as a flaoting icon in the lower right corner
1800     *
1801     * @param mapImageIcon
1802     * <code>null</code> is allowed and deactivates this icon.
1803 alfonx 529 */
1804 alfonx 530 public void setMapImage(final BufferedImage mapImage) {
1805     this.mapImage = mapImage;
1806     }
1807 alfonx 529
1808     /**
1809 alfonx 530 * Sets whether a layer is regarded or ignored on {@link #SELECT_TOP},
1810     * {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP} actions.
1811     *
1812     * @param layer
1813     * a layer
1814     * @param selectable
1815     * if {@code false} the layer is ignored during the upper
1816     * mentioned actions. If <code>null</code>, the default (true)
1817     * will be used.
1818 alfonx 529 */
1819 alfonx 530 public void setMapLayerSelectable(final MapLayer layer,
1820     final Boolean selectable) {
1821     if (selectable == null)
1822     mapLayerSelectable.remove(layer);
1823     else
1824     mapLayerSelectable.put(layer, selectable);
1825 alfonx 144 }
1826 mojays 2
1827 alfonx 509 /**
1828 alfonx 530 * Defines an evelope of the viwable area. The JMapPane will never show
1829     * anything outside of this extend.
1830 alfonx 509 *
1831 alfonx 530 * @param maxExtend
1832     * <code>null</code> to not have this restriction.
1833 alfonx 509 */
1834 alfonx 530 public void setMaxExtend(final Envelope maxExtend) {
1835     this.maxExtend = maxExtend;
1836 alfonx 144 }
1837 mojays 2
1838 alfonx 509 /**
1839 alfonx 530 * Set the maximum allowed zoom scale. This is the smaller number value of
1840     * the two. If <code>null</code> is passed, Double.MINVALUE are used which
1841     * mean there is no restriction.
1842 alfonx 509 *
1843 alfonx 530 * @author <a href="mailto:[email protected]">Stefan Alfons
1844     * Kr&uuml;ger</a>
1845 alfonx 509 */
1846 alfonx 530 public void setMaxZoomScale(final Double maxZoomScale) {
1847     this.maxZoomScale = maxZoomScale == null ? Double.MIN_VALUE
1848     : maxZoomScale;
1849 alfonx 144 }
1850 mojays 2
1851 alfonx 530 // /** Stored the time used for the last real rendering in ms. **/
1852     // private long lastRenderingDuration = Long.MAX_VALUE;
1853    
1854 alfonx 509 /**
1855 alfonx 530 * Set the minimum (nearest) allowed zoom scale. This is the bigger number
1856     * value of the two. If <code>null</code> is passed, Double.MAXVALUE are
1857     * used which mean there is no restriction.
1858 alfonx 509 *
1859 alfonx 530 *
1860     * @author <a href="mailto:[email protected]">Stefan Alfons
1861     * Kr&uuml;ger</a>
1862 alfonx 509 */
1863 alfonx 530 public void setMinZoomScale(final Double minZoomScale) {
1864     this.minZoomScale = minZoomScale == null ? Double.MAX_VALUE
1865     : minZoomScale;
1866 alfonx 144 }
1867 mojays 2
1868 alfonx 509 /**
1869     *
1870 alfonx 530 * @param b
1871 alfonx 509 */
1872 alfonx 530 public void setPainting(final boolean b) {
1873     acceptsRepaintCalls = b;
1874     }
1875 mojays 2
1876 alfonx 530 // /**
1877     // * Returns in milli seconds the time the last rending of the
1878     // * {@link SelectableXMapPane} took. #Long.MAX_VALUE if the JMapPane has
1879     // not
1880     // * been rendered yet.
1881     // */
1882     // public long getLastRenderingDuration() {
1883     // return lastRenderingDuration;
1884     // }
1885 mojays 2
1886 alfonx 530 public void setQuickPreviewHint(final int quickPreviewHint) {
1887     this.quickPreviewHint = quickPreviewHint;
1888 mojays 2
1889 alfonx 530 }
1890 mojays 2
1891 alfonx 530 private void setRendererHints(final Map<Object, Object> rendererHints) {
1892 alfonx 533 if (rendererHints != null)
1893     this.rendererHints = rendererHints;
1894 alfonx 144 }
1895 alfonx 513
1896 alfonx 509 /**
1897 alfonx 530 * Enables/Disables the ZOOM Mouse Listener. Upates the Cursor and stops the
1898     * repaint Timer if
1899     *
1900     * @param state
1901 alfonx 509 */
1902 alfonx 530 public void setState(final int state) {
1903     this.state = state;
1904 mojays 2
1905 alfonx 530 zoomMapPaneMouseListener.setEnabled((state == ZOOM_IN
1906     || state == ZOOM_OUT || state == PAN));
1907 alfonx 509
1908 alfonx 530 // Je nach Aktion den Cursor umsetzen
1909     updateCursor();
1910 alfonx 509 }
1911 alfonx 504
1912 alfonx 509 /**
1913 alfonx 530 * Standardmaessig wird der Cursor automatisch je nach MapPane-Aktion (Zoom,
1914     * Auswahl, ...) gesetzt. Mit dieser Methode kann ein statischer Cursor
1915     * gesetzt werden, der unabhaengig von der aktuellen MapPanes-Aktion
1916     * beibehalten wird. Um diesen statischen Cursor wieder zu entfernen, kann
1917     * {@code null} als Parameter uebergeben werden
1918 alfonx 509 *
1919 alfonx 530 * @param cursor
1920     * Cursor
1921 alfonx 509 */
1922 alfonx 530 public void setStaticCursor(final Cursor cursor) {
1923     this.staticCursor = cursor;
1924     if (cursor != null)
1925     super.setCursor(cursor);
1926 alfonx 509 }
1927 alfonx 431
1928 alfonx 509 /**
1929     * Starts rendering on one or two threads
1930     */
1931     private void startRendering() {
1932    
1933 alfonx 544 if (!isWellDefined() || !acceptsRepaintCalls) {
1934     requestStartRendering = true;
1935 alfonx 509 return;
1936 alfonx 544 }
1937 alfonx 509
1938 alfonx 529 if (bgExecuter != null) {
1939 alfonx 509 // Stop all renderers
1940     bgExecuter.cancelTask();
1941 alfonx 524 }
1942 alfonx 509
1943 alfonx 529 if (localExecuter != null) {
1944 alfonx 509 localExecuter.cancelTask();
1945 alfonx 524 }
1946 alfonx 529
1947 alfonx 530 final Rectangle curPaintArea = getVisibleRect();
1948 mojays 2
1949 alfonx 509 /**
1950     * We have to set new renderer
1951     */
1952    
1953     if (getBgContext() != null) {
1954 alfonx 533 bgRenderer.setJava2DHints(getJava2dHints());
1955     bgRenderer.setRendererHints(getRendererHints());
1956    
1957 alfonx 529 // bgExecuter = new RenderingExecutor();
1958     // LOGGER.debug("starting bg renderer:");
1959     // // /* System.out.println("rendering"); */
1960     // final GTRenderer createGTRenderer = GTUtil.createGTRenderer(
1961     // bgContext, getRendererHints());
1962     // createGTRenderer.setJava2DHints(getJava2dHints());
1963     // bgExecuter.submit(getBgContext().getAreaOfInterest(),
1964     // curPaintArea,
1965     // (Graphics2D) getBgImage().getGraphics(), createGTRenderer);
1966 alfonx 509 }
1967    
1968     if (getContext() != null) {
1969 alfonx 529 // localExecuter = new RenderingExecutor(this, 150l);
1970 alfonx 530 // LOGGER.debug("starting local renderer:");
1971    
1972 alfonx 533 localRenderer.setJava2DHints(getJava2dHints());
1973     localRenderer.setRendererHints(getRendererHints());
1974 alfonx 529
1975 alfonx 544 final boolean submitted = localExecuter.submit(getMapArea(),
1976 alfonx 533 curPaintArea, (Graphics2D) getLocalImage().getGraphics(),
1977 alfonx 544 localRenderer
1978     // , getWorldToScreenTransform()
1979     );
1980 alfonx 530 if (submitted)
1981     repaintTimer.restart();
1982     else
1983 alfonx 544 requestStartRendering = true; // Try to start rendering
1984     // again in
1985 alfonx 530 // a moment
1986 alfonx 509 }
1987    
1988 alfonx 514 updateCursor();
1989 alfonx 144 }
1990 mojays 2
1991 alfonx 509 /**
1992 alfonx 530 * Transformiert einen Geo-Koordinaten-Bereich in Fenster-Koordinaten.
1993 alfonx 509 *
1994 alfonx 530 * @param ox
1995     * X-Koordinate der VON-Position
1996     * @param oy
1997     * Y-Koordinate der VON-Position
1998     * @param px
1999     * X-Koordinate der BIS-Position
2000     * @param py
2001     * Y-Koordinate der BIS-Position
2002     * @param winToGeotransform
2003     * Eine Window to Geo transform. If <code>null</code>,
2004     * {@link #getScreenToWorld()} is used.
2005 alfonx 509 */
2006 alfonx 530 public Envelope tranformGeoToWindow(final double ox, final double oy,
2007 alfonx 539 final double px, final double py) {
2008     final AffineTransform at = getWorldToScreenTransform();
2009 alfonx 530 Point2D geoO;
2010 alfonx 539 // try {
2011     geoO = at.transform(new Point2D.Double(ox, oy), null);
2012     final Point2D geoP = at.transform(new Point2D.Double(px, py), null);
2013     return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP.getY());
2014     // } catch (final NoninvertibleTransformException e) {
2015     // LOGGER.error(e);
2016     // return new Envelope(ox, oy, px, py);
2017     // }
2018 alfonx 144 }
2019 mojays 2
2020 alfonx 509 /**
2021 alfonx 530 * Transformiert einen Fenster-Koordinaten-Bereich in Geo-Koordinaten.
2022 alfonx 509 *
2023 alfonx 530 * @param ox
2024     * X-Koordinate der VON-Position
2025     * @param oy
2026     * Y-Koordinate der VON-Position
2027     * @param px
2028     * X-Koordinate der BIS-Position
2029     * @param py
2030     * Y-Koordinate der BIS-Position
2031 alfonx 509 */
2032 alfonx 530 public Envelope tranformWindowToGeo(final int ox, final int oy,
2033     final int px, final int py) {
2034     final AffineTransform at = getScreenToWorld();
2035     final Point2D geoO = at.transform(new Point2D.Double(ox, oy), null);
2036     final Point2D geoP = at.transform(new Point2D.Double(px, py), null);
2037 alfonx 543
2038 alfonx 539 // Mmmmm... don't really understand why its x,x,y,y
2039 alfonx 543 // return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(),
2040     // geoP.getY());
2041     return new Envelope(new Coordinate(geoO.getX(), geoO.getY()),
2042     new Coordinate(geoP.getX(), geoP.getY()));
2043 alfonx 144 }
2044 mojays 2
2045 alfonx 509 /**
2046 alfonx 530 * Will update the cursor. If all rendering is finished also stops the
2047     * {@link #repaintTimer}
2048 alfonx 509 */
2049 alfonx 530 public void updateCursor() {
2050 mojays 2
2051 alfonx 530 // if the renderers have stopped, also stop the timer that is updating
2052     // the final image
2053     if (bgExecuter != null && bgExecuter.isRunning()
2054     || localExecuter != null && localExecuter.isRunning()) {
2055     setCursor(WAIT_CURSOR);
2056     return;
2057     } else {
2058     // Allow one last rendering
2059     if (repaintTimer.isRunning()) {
2060 alfonx 544 // System.out.println("one last rendering....");
2061 alfonx 530 repaintTimer.stop();
2062     updateFinalImage();
2063     repaint();
2064     }
2065 alfonx 144 }
2066 mojays 2
2067 alfonx 530 // wenn manueller Cursor gesetzt ist, dann diesen verwenden (unabhaengig
2068     // von der aktuellen Aktion
2069     if (this.staticCursor != null) {
2070     setCursor(staticCursor);
2071     return;
2072     }
2073     if (getCursor() == SwingUtil.PANNING_CURSOR) {
2074     // This cursor will reset itself
2075     return;
2076     }
2077 mojays 2
2078 alfonx 530 // Set the cursor depending on what tool is in use...
2079     switch (state) {
2080     case SELECT_TOP:
2081     case SELECT_ONE_FROM_TOP:
2082     case SELECT_ALL:
2083     setCursor(SwingUtil.CROSSHAIR_CURSOR);
2084     break;
2085     case ZOOM_IN:
2086     setCursor(SwingUtil.ZOOMIN_CURSOR);
2087     break;
2088     case ZOOM_OUT:
2089     setCursor(SwingUtil.ZOOMOUT_CURSOR);
2090     break;
2091     case PAN:
2092     setCursor(SwingUtil.PAN_CURSOR);
2093     break;
2094     default:
2095     setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
2096     break;
2097     }
2098 alfonx 144 }
2099 mojays 2
2100 alfonx 509 /**
2101 alfonx 530 * The renderers are all rendering into their own {@link Image}s. This
2102     * method combines all images to one {@link #finalImage}. The
2103     * {@link #repaintTimer} is calling this method regularely to update the
2104     * {@link #finalImage} even if the renderers are still working.
2105 alfonx 509 */
2106 alfonx 530 synchronized protected Image updateFinalImage() {
2107 mojays 2
2108 alfonx 530 // Render the two map images first, into the preFinalImage
2109     if (bgExecuter != null) {
2110     final Graphics2D preFinalG = (Graphics2D) getPreFinalImage()
2111     .getGraphics();
2112     preFinalG.setBackground(getMapBackgroundColor());
2113 mojays 2
2114 alfonx 530 preFinalG.drawImage(getBgImage(), 0, 0, getMapBackgroundColor(),
2115     null);
2116 mojays 2
2117 alfonx 530 // // Draw the local layers image
2118     preFinalG.drawImage(getLocalImage(), 0, 0, null);
2119     preFinalG.dispose();
2120 mojays 2
2121 alfonx 76 } else {
2122 alfonx 530 preFinalImage = getLocalImage();
2123 alfonx 76 }
2124 mojays 2
2125 alfonx 530 final Graphics2D finalG = getFinalImage().createGraphics();
2126     finalG.setBackground(getMapBackgroundColor());
2127     finalG.drawImage(getPreFinalImage(), imageOrigin.x, imageOrigin.y,
2128     getMapBackgroundColor(), null);
2129 mojays 2
2130 alfonx 530 final int finalImageHeight = getFinalImage().getHeight(null);
2131     final int finalImageWidth = getFinalImage().getWidth(null);
2132 alfonx 414
2133 alfonx 530 final Rectangle painedArea = new Rectangle(imageOrigin.x,
2134     imageOrigin.y, finalImageWidth, finalImageHeight);
2135     SwingUtil.clearAround(finalG, painedArea, getVisibleRect());
2136 alfonx 414
2137 alfonx 533 addGadgets(finalG, false);
2138 alfonx 414
2139 alfonx 530 finalG.dispose();
2140 alfonx 414
2141 alfonx 530 return finalImage;
2142 alfonx 76 }
2143    
2144 alfonx 144 /**
2145 alfonx 530 * Paints some optional stuff into the given {@link Graphics2D}. Usually
2146     * called as the last layer when {@link #updateFinalImage()}
2147 alfonx 533 *
2148     * @param forceWait
2149     * if <code>true</code>, a Wait-message will be painted even
2150     * though the rendering threads may not yet have started. If
2151     * <code>false</code>, it will only depend on
2152     * {@link #localExecuter.isRunning} and #bgExecuter.isRunning
2153 alfonx 144 */
2154 alfonx 533 private void addGadgets(final Graphics2D graphics, boolean forceWait) {
2155 mojays 2
2156 alfonx 530 // Paint a logo to the bottom right if available
2157     if (mapImage != null) {
2158 alfonx 540 Rectangle visibleRect = getVisibleRect();
2159     graphics.drawImage(mapImage, visibleRect.width
2160     - mapImage.getWidth() - 10, getVisibleRect().height
2161 alfonx 530 - mapImage.getHeight() - 10, null);
2162     }
2163 alfonx 539
2164 alfonx 533 int y = 17;
2165 mojays 2
2166 alfonx 530 // If the rendering process is still running, indicate this is the image
2167 alfonx 533 if (forceWait || bgExecuter != null && bgExecuter.isRunning()
2168 alfonx 530 || localExecuter != null && localExecuter.isRunning()) {
2169 mojays 2
2170 alfonx 539 y += 8;
2171    
2172 alfonx 530 final Color c = graphics.getColor();
2173     graphics.setFont(waitFont);
2174 alfonx 533
2175     graphics.setColor(getMapBackgroundColor());
2176     graphics.drawString(waitMsg, 5, y);
2177     graphics.setColor(getMapBackgroundColor());
2178 alfonx 539 graphics.drawString(waitMsg, 7, y + 2);
2179 alfonx 530 graphics.setColor(Color.BLACK);
2180 alfonx 539 graphics.drawString(waitMsg, 6, y + 1);
2181 mojays 2
2182 alfonx 530 graphics.setColor(c);
2183 alfonx 539
2184 alfonx 533 y += 24;
2185 alfonx 418 }
2186 alfonx 414
2187 alfonx 533 if (renderingErrors != null) {
2188    
2189     final Color c = graphics.getColor();
2190     graphics.setFont(errorFont);
2191    
2192     for (Exception ex : renderingErrors) {
2193    
2194     if (ex instanceof java.lang.IllegalArgumentException
2195     && ex.getMessage().equals(
2196     "Argument \"sourceCRS\" should not be null."))
2197     continue;
2198    
2199     String errStr = ex.getLocalizedMessage();
2200    
2201 alfonx 544 if (errStr == null)
2202     errStr = ex.getMessage();
2203     if (errStr == null)
2204     errStr = "unknown error: " + ex.getClass().getSimpleName();
2205    
2206 alfonx 533 graphics.setColor(Color.WHITE);
2207     graphics.drawString(errStr, 5, y);
2208     graphics.setColor(Color.RED);
2209     graphics.drawString(errStr, 6, y + 1);
2210    
2211     y += 19;
2212     }
2213    
2214     graphics.setColor(c);
2215     }
2216    
2217 alfonx 505 }
2218    
2219     /**
2220 alfonx 533 * Sets the {@link #mapArea} to best possibly present the given features. If
2221     * only one single point is given, the window is moved over the point.
2222 alfonx 509 *
2223 alfonx 530 * @param features
2224     * if <code>null</code> or size==0, the function doesn nothing.
2225 alfonx 509 */
2226 alfonx 530 public void zoomTo(
2227     final FeatureCollection<SimpleFeatureType, SimpleFeature> features) {
2228 alfonx 509
2229 alfonx 530 final CoordinateReferenceSystem mapCRS = getContext()
2230     .getCoordinateReferenceSystem();
2231     final CoordinateReferenceSystem fCRS = features.getSchema()
2232     .getGeometryDescriptor().getCoordinateReferenceSystem();
2233 alfonx 509
2234 alfonx 530 double width = mapArea.getWidth();
2235     double height = mapArea.getHeight();
2236     final double ratio = height / width;
2237 alfonx 509
2238 alfonx 530 if (features == null || features.size() == 0) {
2239     // feature count == 0 Zoom to the full extend
2240 alfonx 509 return;
2241 alfonx 530 } else if (features.size() == 1) {
2242 alfonx 529
2243 alfonx 530 // feature count == 1 Just move the window to the point and zoom 'a
2244     // bit'
2245     final SimpleFeature singleFeature = features.iterator().next();
2246 alfonx 509
2247 alfonx 530 if (((Geometry) singleFeature.getDefaultGeometry())
2248     .getCoordinates().length > 1) {
2249     // System.out.println("Zoomed to only pne poylgon");
2250     // Poly
2251     // TODO max width vs. height
2252     width = features.getBounds().getWidth() * 3;
2253     height = ratio * width;
2254     } else {
2255     // System.out.println("Zoomed in a bit becasue only one point");
2256     // width *= .9;
2257     // height *= .9;
2258     }
2259 alfonx 509
2260 alfonx 530 Coordinate centre = features.getBounds().centre();
2261     if (!mapCRS.equals(fCRS)) {
2262     // only to calculations if the CRS differ
2263 alfonx 529 try {
2264 alfonx 530 MathTransform fToMap;
2265     fToMap = CRS.findMathTransform(fCRS, mapCRS);
2266     // centre is transformed to the mapCRS
2267     centre = JTS.transform(centre, null, fToMap);
2268     } catch (final FactoryException e) {
2269     LOGGER.error("Looking for a Math transform", e);
2270     } catch (final TransformException e) {
2271     LOGGER.error("Looking for a Math transform", e);
2272 alfonx 529 }
2273     }
2274 alfonx 509
2275 alfonx 530 final Coordinate newLeftBottom = new Coordinate(centre.x - width
2276     / 2., centre.y - height / 2.);
2277     final Coordinate newTopRight = new Coordinate(
2278     centre.x + width / 2., centre.y + height / 2.);
2279 alfonx 509
2280 alfonx 530 final Envelope newMapArea = new Envelope(newLeftBottom, newTopRight);
2281 alfonx 509
2282 alfonx 530 setMapArea(newMapArea);
2283 alfonx 529
2284 alfonx 530 } else {
2285     final ReferencedEnvelope fBounds = features.getBounds();
2286 alfonx 529
2287 alfonx 530 Envelope bounds;
2288     if (!mapCRS.equals(fCRS)) {
2289     bounds = JTSUtil.transformEnvelope(fBounds, fCRS, mapCRS);
2290     } else {
2291     bounds = fBounds;
2292     }
2293     // BB umrechnen von Layer-CRS in Map-CRS
2294 alfonx 529
2295 alfonx 530 // Expand a bit
2296     bounds.expandBy(bounds.getWidth() / 6., bounds.getHeight() / 6.);
2297 alfonx 509
2298 alfonx 530 setMapArea(bounds);
2299     }
2300 alfonx 509 }
2301    
2302     /**
2303 alfonx 530 * Zooms towards a point.
2304     *
2305     * @param center
2306     * position in window coordinates
2307     * @param zoomFactor
2308     * > 1 for zoom in, < 1 for zoom out. Default is 1.33
2309 alfonx 509 */
2310 alfonx 530 public void zoomTo(final Point center) {
2311     zoomTo(center, null);
2312 alfonx 509 }
2313    
2314     /**
2315     * Zooms towards a point.
2316     *
2317     * @param center
2318     * position in window coordinates
2319     * @param zoomFaktor
2320     * > 1 for zoom in, < 1 for zoom out. Default is 1.33.
2321     */
2322     public void zoomTo(Point center, Double zoomFaktor) {
2323     if (zoomFaktor == null || zoomFaktor == 0.)
2324     zoomFaktor = 2.;
2325    
2326 alfonx 530 final Point2D gcenter = getScreenToWorld().transform(center, null);
2327 alfonx 509 center = null;
2328 alfonx 513
2329     if (Double.isNaN(gcenter.getX()) || Double.isNaN(gcenter.getY())
2330     || Double.isInfinite(gcenter.getX())
2331     || Double.isInfinite(gcenter.getY())
2332    
2333 alfonx 509 ) {
2334     // Not inside valid CRS area! cancel
2335     return;
2336     }
2337    
2338     final Envelope mapArea = getMapArea();
2339 alfonx 513
2340 alfonx 530 final Envelope newMapArea = new Envelope(mapArea);
2341 alfonx 513 newMapArea.expandBy((mapArea.getWidth() * zoomFaktor - mapArea
2342     .getWidth()) / 2., (mapArea.getHeight() * zoomFaktor - mapArea
2343     .getHeight()) / 2.);
2344 alfonx 509
2345 alfonx 543 // TODO we actually want that
2346     // // Move the newMapArea above the new center
2347     // newMapArea.translate(gcenter.getX() - mapArea.centre().x, gcenter
2348     // .getY()
2349     // - mapArea.centre().y);
2350    
2351 alfonx 509 setMapArea(newMapArea);
2352     }
2353    
2354 mojays 2 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26