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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26