/[schmitzm]/branches/2.0-RC1/src/skrueger/geotools/XMapPane.java
ViewVC logotype

Annotation of /branches/2.0-RC1/src/skrueger/geotools/XMapPane.java

Parent Directory Parent Directory | Revision Log Revision Log


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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26