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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26