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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26