/[schmitzm]/branches/1.0-gt2-2.6/src/skrueger/geotools/XMapPane.java
ViewVC logotype

Annotation of /branches/1.0-gt2-2.6/src/skrueger/geotools/XMapPane.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 513 - (hide annotations)
Mon Nov 9 11:17:34 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: 66838 byte(s)
Fixed an error in the zoom-out code of 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     public void updateCursorAndRepaintTimer() {
617    
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 System.out.println("requested setting to \\t" + newMapArea);
1065     this.mapArea = bestAllowedMapArea(newMapArea);
1066     System.out.println("set to \\t\\ŧ" + newMapArea);
1067    
1068     if (localContext != null) {
1069     localContext.setAreaOfInterest(mapArea, localContext
1070     .getCoordinateReferenceSystem());
1071 alfonx 144 }
1072 alfonx 509 if (bgContext != null) {
1073     bgContext.setAreaOfInterest(mapArea, localContext
1074     .getCoordinateReferenceSystem());
1075     }
1076     resetTransforms();
1077     mapImageInvalid = true;
1078     mapAreaChanged = true;
1079     repaint();
1080 alfonx 513
1081     LOGGER.debug("New maparea = " + mapArea);
1082 alfonx 509 return true;
1083     }
1084 mojays 2
1085 alfonx 509 public int getState() {
1086     return state;
1087 alfonx 144 }
1088 mojays 2
1089 alfonx 509 /**
1090     * Enables/Disables the ZOOM Mouse Listener. Upates the Cursor and stops the
1091     * repaint Timer if
1092     *
1093     * @param state
1094     */
1095     public void setState(final int state) {
1096     this.state = state;
1097 mojays 2
1098 alfonx 509 zoomMapPaneMouseListener.setEnabled((state == ZOOM_IN
1099     || state == ZOOM_OUT || state == PAN));
1100 mojays 2
1101 alfonx 509 // Je nach Aktion den Cursor umsetzen
1102     updateCursorAndRepaintTimer();
1103     }
1104 mojays 2
1105 alfonx 509 /** Cursor wenn kein Mausbutton gedrueckt wird. default oder SwingUtil.PAN **/
1106     protected static Cursor normalCursor = Cursor
1107     .getPredefinedCursor(Cursor.DEFAULT_CURSOR);
1108    
1109     public static final Cursor WAIT_CURSOR = Cursor
1110     .getPredefinedCursor(Cursor.WAIT_CURSOR);
1111    
1112     public static final int NONE = -123;
1113    
1114 mojays 512 private RenderingExecutor localExecuter;
1115 alfonx 509
1116     private BufferedImage finalImage;
1117    
1118     /**
1119     * A flag set it {@link #setMapArea(Envelope)} to indicated the
1120     * {@link #paintComponent(Graphics)} method, that the image on-screen is
1121     * associated with {@link #oldMapArea} and may hence be scaled for a quick
1122     * preview.<br/>
1123     * The flag is reset
1124     **/
1125     private boolean mapAreaChanged = false;
1126    
1127     private JFrame finalImageFrame;
1128    
1129     private volatile Boolean requestStartRendering = false;
1130     private BufferedImage preFinalImage;
1131    
1132     protected void paintComponent(final Graphics g) {
1133     // Maybe update the cursor
1134     updateCursorAndRepaintTimer();
1135    
1136     if (!acceptsRepaintCalls)
1137 alfonx 144 return;
1138 mojays 2
1139 alfonx 509 boolean paintedSomething = false;
1140 mojays 2
1141 alfonx 509 if (mapImageInvalid) { /* if the map changed then redraw */
1142 mojays 2
1143 alfonx 509 mapImageInvalid = false; // Reset for next round
1144 mojays 2
1145 alfonx 509 // If the new mapArea and the oldMapArea intersect, we can draw some
1146     // quick scaled preview to make the user feel that something is
1147     // happening.
1148     if (mapAreaChanged && oldMapArea != null
1149     && getMapArea().intersects(oldMapArea)
1150     & !getMapArea().equals(oldMapArea)) {
1151    
1152     mapAreaChanged = false;
1153    
1154     if (getMapArea().covers(oldMapArea)) {
1155     setQuickPreviewHint(ZOOM_OUT);
1156     paintedSomething = drawScaledPreviewImage_Zoom((Graphics2D) g);
1157     } else if (oldMapArea.covers(getMapArea())) {
1158     setQuickPreviewHint(ZOOM_IN);
1159     paintedSomething = drawScaledPreviewImage_Zoom((Graphics2D) g);
1160     }
1161    
1162     }
1163    
1164     if (paneResized) {
1165     paneResized = false;
1166     preFinalImage = null;
1167     finalImage = null;
1168     localImage = null;
1169     bgImage = null;
1170 alfonx 513 // gadgetsImage = null;
1171 alfonx 509 }
1172    
1173     // Start the Threads and Timers to render the image
1174     requestStartRendering();
1175    
1176 alfonx 144 }
1177 mojays 2
1178 alfonx 509 if (!paintedSomething) {
1179 mojays 2
1180 alfonx 509 // TODO Should just paint the getFinalImage(). Update should be
1181     // called by timer every 300ms, and the repaint() until all threads
1182     // are done.
1183     g.drawImage(getFinalImage(), 0, 0, this);
1184 mojays 2
1185 alfonx 509 paintedSomething = true;
1186     }
1187    
1188 alfonx 144 }
1189 mojays 2
1190 alfonx 509 /**
1191     * Cancels all running renderers and sets the flag to start new ones. <br/>
1192     *
1193     * @see #startRenderThreadsTimer
1194     */
1195     private void requestStartRendering() {
1196     if (bgExecuter != null)
1197     bgExecuter.cancelTask();
1198     if (localExecuter != null)
1199     localExecuter.cancelTask();
1200     requestStartRendering = true;
1201 alfonx 144 }
1202 mojays 2
1203 alfonx 509 /**
1204     * Transformiert einen Fenster-Koordinaten-Bereich in Geo-Koordinaten.
1205     *
1206     * @param ox
1207     * X-Koordinate der VON-Position
1208     * @param oy
1209     * Y-Koordinate der VON-Position
1210     * @param px
1211     * X-Koordinate der BIS-Position
1212     * @param py
1213     * Y-Koordinate der BIS-Position
1214     */
1215     public Envelope tranformWindowToGeo(int ox, int oy, int px, int py) {
1216     AffineTransform at = getScreenToWorld();
1217     Point2D geoO = at.transform(new Point2D.Double(ox, oy), null);
1218     Point2D geoP = at.transform(new Point2D.Double(px, py), null);
1219     return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP.getY());
1220 alfonx 144 }
1221 mojays 2
1222 alfonx 509 /**
1223     * Transformiert einen Geo-Koordinaten-Bereich in Fenster-Koordinaten.
1224     *
1225     * @param ox
1226     * X-Koordinate der VON-Position
1227     * @param oy
1228     * Y-Koordinate der VON-Position
1229     * @param px
1230     * X-Koordinate der BIS-Position
1231     * @param py
1232     * Y-Koordinate der BIS-Position
1233     * @param winToGeotransform
1234     * Eine Window to Geo transform. If <code>null</code>,
1235     * {@link #getScreenToWorld()} is used.
1236     */
1237     public Envelope tranformGeoToWindow(double ox, double oy, double px,
1238     double py, AffineTransform winToGeotransform) {
1239     AffineTransform at = winToGeotransform == null ? getScreenToWorld()
1240     : winToGeotransform;
1241     Point2D geoO;
1242     try {
1243     geoO = at.inverseTransform(new Point2D.Double(ox, oy), null);
1244     Point2D geoP = at
1245     .inverseTransform(new Point2D.Double(px, py), null);
1246     return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP
1247     .getY());
1248     } catch (NoninvertibleTransformException e) {
1249     LOGGER.error(e);
1250     return new Envelope(ox, oy, px, py);
1251     }
1252 alfonx 144 }
1253 mojays 2
1254 alfonx 509 /**
1255     * Diretly paints scaled preview into the {@link SelectableXMapPane}. Used
1256     * to give the user something to look at while we are rendering. Method
1257     * should be called after {@link #setMapArea(Envelope)} has been set to the
1258     * new mapArea and transform has been reset.<br/>
1259     * This method does nothing if the {@link #lastRenderingDuration} is smaller
1260     * then a trashhold.
1261     *
1262     * @param g
1263     * Graphics2D to paint the preview into
1264     *
1265     * @param state
1266     * Max be {@link #ZOOM_IN} or {@link #ZOOM_OUT}
1267     */
1268     protected boolean drawScaledPreviewImage_Zoom(Graphics2D graphics) {
1269 mojays 2
1270 alfonx 509 if (quickPreviewHint == 0)
1271     return false;
1272 mojays 2
1273 alfonx 509 graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
1274     RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
1275     graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1276     RenderingHints.VALUE_ANTIALIAS_OFF);
1277     graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
1278     RenderingHints.VALUE_RENDER_SPEED);
1279 mojays 2
1280 alfonx 509 if (oldMapArea == null)
1281     return false;
1282 mojays 2
1283 alfonx 509 Rectangle visibleArea = getVisibleRect();
1284 mojays 2
1285 alfonx 509 // Calculate the oldMapArea in the current WindowCoordinates:
1286     Envelope oldMapWindow = tranformGeoToWindow(oldMapArea.getMinX(),
1287     oldMapArea.getMinY(), oldMapArea.getMaxX(), oldMapArea
1288     .getMaxY(), null);
1289 alfonx 414
1290 alfonx 509 int xx1 = (int) Math.round(oldMapWindow.getMinX());
1291     int yy1 = (int) Math.round(oldMapWindow.getMinY());
1292     int xx2 = (int) Math.round(oldMapWindow.getMaxX());
1293     int yy2 = (int) Math.round(oldMapWindow.getMaxY());
1294 mojays 2
1295 alfonx 509 graphics.drawImage(getPreFinalImage(), xx1, yy1, xx2, yy2,
1296     (int) visibleArea.getMinX(), (int) visibleArea.getMinY(),
1297     (int) visibleArea.getMaxX(), (int) visibleArea.getMaxY(),
1298     getMapBackgroundColor(), null);
1299 mojays 2
1300 alfonx 509 Rectangle painedArea = new Rectangle(xx1, yy1, xx2 - xx1, yy2 - yy1);
1301 mojays 2
1302 alfonx 509 SwingUtil.clearAround(graphics, painedArea, visibleArea);
1303 alfonx 513
1304 alfonx 509 addGadgets(graphics);
1305 alfonx 513 // graphics.drawImage(getGadgetsImage(), 0,0, (int)
1306     // visibleArea.getMaxX(), (int) visibleArea.getMaxY(),null);
1307 mojays 2
1308 alfonx 509 quickPreviewHint = 0;
1309 mojays 2
1310 alfonx 509 graphics.dispose();
1311     // Something has been drawn
1312     return true;
1313 alfonx 144 }
1314 alfonx 513
1315 alfonx 509 final static Font waitFont = new Font("Arial", Font.BOLD, 30);
1316 mojays 2
1317 alfonx 509 /**
1318 alfonx 513 * Paints some optinal stuff into the given {@link Graphics2D}. Usually
1319     * called as the last paint on the mapImage.
1320 alfonx 509 */
1321     private void addGadgets(Graphics2D graphics) {
1322 mojays 2
1323 alfonx 509 if (mapImage != null)
1324 alfonx 513 graphics.drawImage(mapImage, getBounds().width
1325     - mapImage.getWidth() - 10, getBounds().height
1326     - mapImage.getHeight() - 10, this);
1327 alfonx 509
1328     // If still rendering, paint a gray shadow or so...
1329     if (bgExecuter != null && bgExecuter.isRunning()
1330     || localExecuter != null && localExecuter.isRunning()) {
1331     graphics.setColor(Color.BLACK);
1332 alfonx 513
1333 alfonx 509 graphics.setFont(waitFont);
1334     graphics.drawString("Wait...", 40, 70);
1335 alfonx 431 }
1336 alfonx 513
1337 alfonx 509 }
1338 alfonx 504
1339 alfonx 509 /**
1340     * Accumulates all three images
1341     *
1342     * @return
1343     */
1344     synchronized protected BufferedImage updateFinalImage() {
1345 mojays 2
1346 alfonx 509 final Graphics2D finalG = (Graphics2D) getFinalImage().getGraphics();
1347     finalG.setBackground(getMapBackgroundColor());
1348 alfonx 513
1349 alfonx 509 // Render the two map images first, into the preFinalImage
1350     {
1351 alfonx 513 final Graphics2D preFinalG = (Graphics2D) getPreFinalImage()
1352     .getGraphics();
1353 alfonx 509 preFinalG.setBackground(getMapBackgroundColor());
1354 alfonx 513
1355     preFinalG.drawImage(getBgImage(), 0, 0, getMapBackgroundColor(),
1356     null);
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 // System.out.println(new Date().getTime() - startTime +
1366     // "ms for update");
1367     //
1368     // if (finalImageFrame == null) {
1369     // finalImageFrame = new JFrame();
1370     // finalImageFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1371     // SwingUtil.setRelativeFramePosition(finalImageFrame, 0, 0);
1372     // }
1373     // finalImageFrame.setContentPane(new JLabel(new
1374     // ImageIcon(finalImage)));
1375     // finalImageFrame.pack();
1376     // finalImageFrame.setVisible(true);
1377 mojays 2
1378 alfonx 509 final int finalImageHeight = getFinalImage().getHeight(null);
1379     final int finalImageWidth = getFinalImage().getWidth(null);
1380 mojays 2
1381 alfonx 509 Rectangle painedArea = new Rectangle(imageOrigin.x, imageOrigin.y,
1382     finalImageWidth, finalImageHeight);
1383     SwingUtil.clearAround(finalG, painedArea, getVisibleRect());
1384 mojays 2
1385 alfonx 509 addGadgets(finalG);
1386 alfonx 513 // finalG.drawImage(getGadgetsImage(), 0, 0, null);
1387 mojays 2
1388 alfonx 509 finalG.dispose();
1389 mojays 2
1390 alfonx 509 return finalImage;
1391     }
1392 alfonx 431
1393 alfonx 509 /*
1394     * Set alpha composite. For example, pass in 1.0f to have 100% opacity pass
1395     * in 0.25f to have 25% opacity.
1396     */
1397     private AlphaComposite makeComposite(float alpha) {
1398     int type = AlphaComposite.SRC_OVER;
1399     return (AlphaComposite.getInstance(type, alpha));
1400     }
1401 mojays 2
1402 alfonx 509 private Image getFinalImage() {
1403 mojays 2
1404 alfonx 509 if (finalImage == null) {
1405     finalImage = null;
1406     Rectangle curPaintArea = getVisibleRect();
1407     finalImage = new BufferedImage(curPaintArea.width,
1408     curPaintArea.height, BufferedImage.TYPE_INT_RGB);
1409 mojays 2
1410 alfonx 509 requestStartRendering();
1411     }
1412     return finalImage;
1413     }
1414 alfonx 513
1415 alfonx 509 private Image getPreFinalImage() {
1416 alfonx 513
1417 alfonx 509 if (preFinalImage == null) {
1418     preFinalImage = null;
1419     Rectangle curPaintArea = getVisibleRect();
1420 mojays 2
1421 alfonx 509 preFinalImage = new BufferedImage(curPaintArea.width,
1422     curPaintArea.height, BufferedImage.TYPE_INT_RGB);
1423 alfonx 513
1424 alfonx 509 requestStartRendering();
1425     }
1426     return preFinalImage;
1427     }
1428 mojays 2
1429 alfonx 509 /**
1430     * While dragging, the {@link #updateFinalImage()} method is translating the
1431     * cached images while setting it together.
1432     **/
1433     Point imageOrigin = new Point(0, 0);
1434 mojays 2
1435 alfonx 509 /**
1436     * Starts rendering on one or two threads
1437     */
1438     private void startRendering() {
1439    
1440     if (!isWellDefined())
1441     return;
1442    
1443     if (bgExecuter != null)
1444     // Stop all renderers
1445     bgExecuter.cancelTask();
1446    
1447     if (localExecuter != null)
1448     localExecuter.cancelTask();
1449 alfonx 414 //
1450     //
1451 alfonx 509 // LOGGER.debug("stopping any running renderes:");
1452     // int count = 0;
1453     // while (bgExecuter.isRunning() || localExecuter.isRunning()) {
1454     // LOGGER.debug("waiting for threads to stop");
1455     //
1456     // bgExecuter.cancelTask();
1457     // localExecuter.cancelTask();
1458     //
1459     // count++;
1460     //
1461     // try {
1462     // Thread.sleep(100);
1463     // } catch (InterruptedException e) {
1464     // LOGGER.error(e);
1465 alfonx 414 // }
1466 alfonx 509 //
1467     // if (count > 100) {
1468     // throw new RuntimeException(
1469     // "Unable to stop rendering thread for 10secs");
1470 alfonx 414 // }
1471     // }
1472 alfonx 509 // LOGGER.debug(" threads stopped after " + count / 10. + "secs");
1473 mojays 2
1474 alfonx 509 Rectangle curPaintArea = getVisibleRect();
1475 mojays 2
1476 alfonx 509 // allow a single pixel margin at the right and bottom edges
1477     curPaintArea.width -= 1;
1478     curPaintArea.height -= 1;
1479    
1480     labelCache.clear();
1481    
1482     /**
1483     */
1484    
1485     /**
1486     * We have to set new renderer
1487     */
1488    
1489     if (getBgContext() != null) {
1490 alfonx 513 bgExecuter = new RenderingExecutor(this, 333l);
1491 alfonx 509 LOGGER.debug("starting bg renderer:");
1492     // /* System.out.println("rendering"); */
1493     final GTRenderer createGTRenderer = GTUtil.createGTRenderer(
1494     localContext, getRendererHints());
1495     createGTRenderer.setJava2DHints(getJava2dHints());
1496     bgExecuter.submit(getBgContext().getAreaOfInterest(), curPaintArea,
1497     (Graphics2D) getBgImage().getGraphics(), createGTRenderer);
1498     }
1499    
1500     if (getContext() != null) {
1501 alfonx 513 localExecuter = new RenderingExecutor(this, 150l);
1502 alfonx 509 LOGGER.debug("starting local renderer:");
1503     final GTRenderer createGTRenderer = GTUtil.createGTRenderer(
1504     localContext, getRendererHints());
1505     createGTRenderer.setJava2DHints(getJava2dHints());
1506     localExecuter.submit(getContext().getAreaOfInterest(),
1507     curPaintArea, (Graphics2D) getLocalImage().getGraphics(),
1508     createGTRenderer);
1509     }
1510    
1511     updateCursorAndRepaintTimer();
1512 alfonx 513 //
1513     // // start regular repaints until all renderers are done.
1514     // repainterTimer.setRepeats(true);
1515     // repainterTimer.restart();
1516 alfonx 509
1517 alfonx 144 }
1518 mojays 2
1519 alfonx 509 /**
1520     * Lazyly initializes a {@link BufferedImage} for the background renderer.
1521     */
1522     private BufferedImage getBgImage() {
1523 mojays 2
1524 alfonx 509 if (bgImage == null) {
1525     LOGGER.debug("creating a new background image");
1526    
1527     Rectangle curPaintArea = getVisibleRect();
1528     // allow a single pixel margin at the right and bottom edges
1529     curPaintArea.width -= 1;
1530     curPaintArea.height -= 1;
1531    
1532     bgImage = new BufferedImage(curPaintArea.width + 1,
1533     curPaintArea.height + 1, BufferedImage.TYPE_INT_ARGB);
1534 alfonx 144 }
1535 alfonx 509
1536     return bgImage;
1537 alfonx 144 }
1538 mojays 2
1539 alfonx 509 /** An (transparent) image to paint over the map in the lower right corner **/
1540     private BufferedImage mapImage = null;
1541    
1542     private boolean acceptsRepaintCalls = true;
1543    
1544     /**
1545     * Get the BufferedImage to use as a flaoting icon in the lower right
1546     * corner.
1547     *
1548     * @return <code>null</code> if the feature is deactivated.
1549     */
1550     public BufferedImage getMapImage() {
1551     return mapImage;
1552 alfonx 144 }
1553 mojays 2
1554 alfonx 509 /**
1555     * Set the BufferedImage to use as a flaoting icon in the lower right corner
1556     *
1557     * @param mapImageIcon
1558     * <code>null</code> is allowed and deactivates this icon.
1559     */
1560     public void setMapImage(BufferedImage mapImage) {
1561     this.mapImage = mapImage;
1562 alfonx 513 // gadgetsImage = null;
1563 alfonx 144 }
1564 mojays 2
1565 alfonx 509 /**
1566     * Lazyly initializes a {@link BufferedImage} for the background renderer.
1567     */
1568     private BufferedImage getLocalImage() {
1569 mojays 2
1570 alfonx 509 if (localImage == null) {
1571     LOGGER.debug("creating a new local image");
1572 mojays 2
1573 alfonx 509 Rectangle curPaintArea = getVisibleRect();
1574     // allow a single pixel margin at the right and bottom edges
1575     curPaintArea.width -= 1;
1576     curPaintArea.height -= 1;
1577 mojays 2
1578 alfonx 509 localImage = new BufferedImage(curPaintArea.width + 1,
1579     curPaintArea.height + 1, BufferedImage.TYPE_INT_ARGB);
1580 alfonx 144 }
1581 mojays 2
1582 alfonx 509 return localImage;
1583 alfonx 144 }
1584 mojays 2
1585 alfonx 513 //
1586     // /**
1587     // * Lazyly initializes a {@link BufferedImage} for the background renderer.
1588     // */
1589     // private BufferedImage getGadgetsImage() {
1590     //
1591     // if (gadgetsImage == null) {
1592     // LOGGER.debug("creating a new gadgets image");
1593     //
1594     // Rectangle curPaintArea = getVisibleRect();
1595     // // allow a single pixel margin at the right and bottom edges
1596     // curPaintArea.width -= 1;
1597     // curPaintArea.height -= 1;
1598     //
1599     // gadgetsImage = new BufferedImage(curPaintArea.width + 1,
1600     // curPaintArea.height + 1, BufferedImage.TYPE_INT_ARGB);
1601     //
1602     // if (mapImage != null)
1603     // gadgetsImage.getGraphics().drawImage(mapImage,
1604     // curPaintArea.width - mapImage.getWidth() - 10,
1605     // curPaintArea.height - mapImage.getHeight() - 10, this);
1606     //
1607     // }
1608     //
1609     // return gadgetsImage;
1610     // }
1611    
1612 alfonx 509 /**
1613     * Called by the {@linkplain XMapPane.RenderingTask} when rendering has been
1614     * completed Publishes a {@linkplain MapPaneEvent} of type {@code
1615     * MapPaneEvent.Type.RENDERING_STOPPED} to listeners.
1616     *
1617     * @see MapPaneListener#onRenderingStopped(org.geotools.swing.event.MapPaneEvent)
1618     */
1619     public void onRenderingCompleted() {
1620     System.out.println("onRenderingCompleted");
1621    
1622     updateFinalImage();
1623    
1624 alfonx 144 repaint();
1625 alfonx 509
1626 alfonx 144 }
1627 mojays 2
1628 alfonx 509 /**
1629     * Called by the {@linkplain XMapPane.RenderingTask} when rendering was
1630     * cancelled. Publishes a {@linkplain MapPaneEvent} of type {@code
1631     * MapPaneEvent.Type.RENDERING_STOPPED} to listeners.
1632     *
1633     * @see MapPaneListener#onRenderingStopped(org.geotools.swing.event.MapPaneEvent)
1634     */
1635     public void onRenderingCancelled() {
1636     LOGGER.debug("Rendering cancelled");
1637 alfonx 144 }
1638 mojays 2
1639 alfonx 509 /**
1640     * Called by the {@linkplain XMapPane.RenderingTask} when rendering failed.
1641     * Publishes a {@linkplain MapPaneEvent} of type {@code
1642     * MapPaneEvent.Type.RENDERING_STOPPED} to listeners.
1643     *
1644     * @param renderingError
1645     * The error that occured during rendering
1646     *
1647     * @see MapPaneListener#onRenderingStopped(org.geotools.swing.event.MapPaneEvent)
1648     */
1649     public void onRenderingFailed(Exception renderingError) {
1650     // MapPaneEvent ev = new MapPaneEvent(this,
1651     // MapPaneEvent.Type.RENDERING_STOPPED);
1652     // publishEvent(ev);
1653     LOGGER.warn("Rendering failed", renderingError);
1654     updateFinalImage();
1655 alfonx 144 repaint();
1656 mojays 2
1657 alfonx 144 }
1658 mojays 2
1659 alfonx 144 /**
1660 alfonx 509 * Called when a rendering request has been rejected. This will be common,
1661     * such as when the user pauses during drag-resizing fo the map pane. The
1662     * base implementation does nothing. It is provided for sub-classes to
1663     * override if required.
1664 alfonx 144 */
1665 alfonx 509 public void onRenderingRejected() {
1666     LOGGER.warn("Rendering rejected");
1667     repaint();
1668 alfonx 144 }
1669 mojays 2
1670 alfonx 509 @Override
1671     public void propertyChange(final PropertyChangeEvent evt) {
1672     final String prop = evt.getPropertyName();
1673    
1674     if (prop.equalsIgnoreCase("crs")) {
1675     localContext.setAreaOfInterest(localContext.getAreaOfInterest(),
1676     (CoordinateReferenceSystem) evt.getNewValue());
1677     }
1678 alfonx 144 }
1679    
1680     // xulu.sn
1681 alfonx 76 /**
1682     * Korrigiert den {@link Envelope} aka {@code mapArea} auf die beste
1683     * erlaubte Flaeche damit die Massstabsbeschaenkungen noch eingehalten
1684     * werden, FALLS der uebergeben Envelope nicht schon gueltig sein sollte.<br/>
1685     * Since 21. April 09: Before thecalculation starts, the aspect ratio is
1686     * corrected. This change implies, that setMapArea() will most of the time
1687     * not allow setting to a wrong aspectRatio.
1688     *
1689     * @author <a href="mailto:[email protected]">Stefan Alfons
1690     * Kr&uuml;ger</a>
1691     */
1692     public Envelope bestAllowedMapArea(Envelope env) {
1693     if (getWidth() == 0)
1694     return env;
1695     if (env == null)
1696 alfonx 509 return null;
1697 mojays 2
1698 alfonx 414 Envelope newArea = null;
1699    
1700 alfonx 76 /**
1701 alfonx 144 * Correct the aspect Ratio before we check the rest. Otherwise we might
1702 alfonx 504 * easily fail. We allow to grow here, because we don't check against
1703     * the maxExtend
1704 alfonx 76 */
1705 alfonx 509 Rectangle curPaintArea = getVisibleRect();
1706 mojays 2
1707 alfonx 509 env = JTSUtil.fixAspectRatio(curPaintArea, env, true);
1708    
1709 alfonx 307 final double scale = env.getWidth() / getWidth();
1710     final double centerX = env.getMinX() + env.getWidth() / 2.;
1711     final double centerY = env.getMinY() + env.getHeight() / 2.;
1712 alfonx 414 double newWidth2 = 0;
1713     double newHeight2 = 0;
1714 alfonx 76 if (scale < getMaxZoomScale()) {
1715     // ****************************************************************************
1716     // Wir zoomen weiter rein als erlaubt => Anpassen des envelope
1717     // ****************************************************************************
1718     newWidth2 = getMaxZoomScale() * getWidth() / 2.;
1719     newHeight2 = getMaxZoomScale() * getHeight() / 2.;
1720     } else if (scale > getMinZoomScale()) {
1721     // ****************************************************************************
1722     // Wir zoomen weiter raus als erlaubt => Anpassen des envelope
1723     // ****************************************************************************
1724     newWidth2 = getMinZoomScale() * getWidth() / 2.;
1725     newHeight2 = getMinZoomScale() * getHeight() / 2.;
1726     } else {
1727     // ****************************************************************************
1728     // Die mapArea / der Envelope ist ist gueltig! Keine Aenderungen
1729     // ****************************************************************************
1730 alfonx 414 newArea = env;
1731 alfonx 76 }
1732 mojays 2
1733 alfonx 414 if (newArea == null) {
1734 mojays 2
1735 alfonx 414 final Coordinate ll = new Coordinate(centerX - newWidth2, centerY
1736     - newHeight2);
1737     final Coordinate ur = new Coordinate(centerX + newWidth2, centerY
1738     + newHeight2);
1739    
1740     newArea = new Envelope(ll, ur);
1741     }
1742    
1743 alfonx 418 Envelope maxAllowedExtend = getMaxExtend();
1744 alfonx 504 while (maxAllowedExtend != null && !maxAllowedExtend.contains(newArea)
1745     && newArea != null && !newArea.isNull()
1746     && !Double.isNaN(newArea.getMinX())
1747     && !Double.isNaN(newArea.getMaxX())
1748     && !Double.isNaN(newArea.getMinY())
1749     && !Double.isNaN(newArea.getMaxY())) {
1750 alfonx 418 /*
1751     * If a maxExtend is set, we have to honour that...
1752     */
1753 alfonx 414
1754 alfonx 418 // Exceeds top? Move down and maybe cut
1755     if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {
1756     double divY = newArea.getMaxY() - maxAllowedExtend.getMaxY();
1757 alfonx 504 // LOGGER.debug("Moving area down by " + divY);
1758 alfonx 414
1759 alfonx 418 newArea = new Envelope(new Coordinate(newArea.getMinX(),
1760     newArea.getMinY() - divY), new Coordinate(newArea
1761     .getMaxX(), newArea.getMaxY() - divY));
1762 alfonx 414
1763 alfonx 418 if (newArea.getMinY() < maxAllowedExtend.getMinY()) {
1764 alfonx 504 // LOGGER.debug("Now it exeeds the bottom border.. cut!");
1765 alfonx 418 // And cut the bottom if it moved out of the area
1766 alfonx 414 newArea = new Envelope(new Coordinate(newArea.getMinX(),
1767 alfonx 418 maxAllowedExtend.getMinY()), new Coordinate(newArea
1768     .getMaxX(), newArea.getMaxY()));
1769 alfonx 414
1770 alfonx 504 // LOGGER.debug("and fix aspect ratio");
1771 alfonx 414
1772 alfonx 504 newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
1773     false);
1774 alfonx 414 }
1775 alfonx 418 }
1776 alfonx 414
1777 alfonx 418 // Exceeds bottom? Move up and maybe cut
1778     if (newArea.getMinY() < maxAllowedExtend.getMinY()) {
1779     double divY = newArea.getMinY() - maxAllowedExtend.getMinY();
1780 alfonx 504 // LOGGER.debug("Moving area up by " + divY);
1781 alfonx 414
1782 alfonx 418 newArea = new Envelope(new Coordinate(newArea.getMinX(),
1783     newArea.getMinY() - divY), new Coordinate(newArea
1784     .getMaxX(), newArea.getMaxY() - divY));
1785    
1786     if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {
1787 alfonx 504 // LOGGER.debug("Now it exeeds the top border.. cut!");
1788 alfonx 418 // And cut the bottom if it moved out of the area
1789 alfonx 414 newArea = new Envelope(new Coordinate(newArea.getMinX(),
1790 alfonx 418 newArea.getMinY()), new Coordinate(newArea
1791     .getMaxX(), maxAllowedExtend.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 right? move and maybe cut
1801     if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {
1802 alfonx 414
1803 alfonx 418 // Move left..
1804     double divX = newArea.getMaxX() - maxAllowedExtend.getMaxX();
1805 alfonx 504 // LOGGER.debug("Moving area left 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.getMinX() < maxAllowedExtend.getMinX()) {
1812 alfonx 504 // LOGGER.debug("Now it exeeds the left border.. cut!");
1813 alfonx 418 // And cut the left if it moved out of the area
1814 alfonx 504 newArea = new Envelope(new Coordinate(maxAllowedExtend
1815     .getMinX(), newArea.getMinY()), new Coordinate(
1816     newArea.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 alfonx 418 }
1824 alfonx 414
1825 alfonx 418 // Exceeds to the left? move and maybe cut
1826     if (newArea.getMinX() < maxAllowedExtend.getMinX()) {
1827 alfonx 414
1828 alfonx 418 // Move right..
1829     double divX = newArea.getMinX() - maxAllowedExtend.getMinX();
1830 alfonx 504 // LOGGER.debug("Moving area right by " + divX);
1831 alfonx 414
1832 alfonx 418 newArea = new Envelope(new Coordinate(newArea.getMinX() - divX,
1833     newArea.getMinY()), new Coordinate(newArea.getMaxX()
1834     - divX, newArea.getMaxY()));
1835 alfonx 414
1836 alfonx 418 if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {
1837 alfonx 504 // LOGGER.debug("Now it exeeds the right border.. cut!");
1838 alfonx 418 // And cut the left if it moved out of the area
1839     newArea = new Envelope(new Coordinate(newArea.getMinX(),
1840     newArea.getMinY()), new Coordinate(maxAllowedExtend
1841     .getMaxX(), newArea.getMaxY()));
1842 alfonx 414
1843 alfonx 504 // LOGGER.debug("and fix aspect ratio");
1844 alfonx 414
1845 alfonx 504 newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
1846     false);
1847 alfonx 414 }
1848     }
1849    
1850     }
1851    
1852     return newArea;
1853 alfonx 76 }
1854    
1855 alfonx 144 /**
1856     * Retuns the minimum allowed zoom scale. This is the bigger number value of
1857     * the two. Defaults to {@link Double}.MAX_VALUE
1858     *
1859     * @author <a href="mailto:[email protected]">Stefan Alfons
1860     * Kr&uuml;ger</a>
1861     */
1862     public Double getMinZoomScale() {
1863     return minZoomScale;
1864     }
1865 mojays 2
1866 alfonx 144 /**
1867     * Retuns the maximum allowed zoom scale. This is the smaller number value
1868     * of the two. Defaults to {@link Double}.MIN_VALUE
1869     *
1870     * @author <a href="mailto:[email protected]">Stefan Alfons
1871     * Kr&uuml;ger</a>
1872     */
1873     public Double getMaxZoomScale() {
1874     return maxZoomScale;
1875     }
1876 mojays 2
1877 alfonx 144 /**
1878     * Set the maximum allowed zoom scale. This is the smaller number value of
1879 alfonx 504 * the two. If <code>null</code> is passed, Double.MINVALUE are used which
1880     * mean there is no restriction.
1881 alfonx 144 *
1882     * @author <a href="mailto:[email protected]">Stefan Alfons
1883     * Kr&uuml;ger</a>
1884     */
1885 alfonx 307 public void setMaxZoomScale(final Double maxZoomScale) {
1886 alfonx 504 this.maxZoomScale = maxZoomScale == null ? Double.MIN_VALUE
1887     : maxZoomScale;
1888 alfonx 144 }
1889 mojays 2
1890 alfonx 144 /**
1891     * Set the minimum (nearest) allowed zoom scale. This is the bigger number
1892 alfonx 504 * value of the two. If <code>null</code> is passed, Double.MAXVALUE are
1893     * used which mean there is no restriction.
1894 alfonx 144 *
1895 alfonx 504 *
1896 alfonx 144 * @author <a href="mailto:[email protected]">Stefan Alfons
1897     * Kr&uuml;ger</a>
1898     */
1899 alfonx 307 public void setMinZoomScale(final Double minZoomScale) {
1900 alfonx 504 this.minZoomScale = minZoomScale == null ? Double.MAX_VALUE
1901     : minZoomScale;
1902 alfonx 144 }
1903 mojays 2
1904 alfonx 414 /**
1905     * Defines an evelope of the viwable area. The JMapPane will never show
1906     * anything outside of this extend.
1907     *
1908     * @param maxExtend
1909     * <code>null</code> to not have this restriction.
1910     */
1911     public void setMaxExtend(Envelope maxExtend) {
1912     this.maxExtend = maxExtend;
1913     }
1914    
1915     /**
1916     * Returns the evelope of the viewable area. The JMapPane will never show
1917 alfonx 418 * anything outside of this extend. If this has been set to
1918     * <code>null</code> via {@link #setMaxExtend(Envelope)}, it tries to return
1919     * quickly the context's bounds. It it takes to long to determine the
1920     * context bounds, <code>null</code> is returned.
1921 alfonx 414 *
1922     * @param maxExtend
1923     * <code>null</code> to not have this restriction.
1924     */
1925    
1926     public Envelope getMaxExtend() {
1927 alfonx 418 if (maxExtend == null) {
1928 alfonx 509 final ReferencedEnvelope layerBounds = GTUtil
1929     .getVisibleLayoutBounds(localContext);
1930     if (layerBounds == null) {
1931     // TODO Last fallback could be the CRS valid area
1932     return null;
1933 alfonx 418 }
1934 alfonx 509
1935     // Kartenbereich um 10% vergroessern
1936     return JTSUtil.fixAspectRatio(this.getBounds(), JTSUtil
1937     .expandEnvelope(layerBounds, 0.1), true);
1938 alfonx 418 }
1939 alfonx 414 return maxExtend;
1940     }
1941    
1942 alfonx 505 /**
1943 alfonx 509 * Set the background color of the map.
1944     *
1945 alfonx 505 * @param if <code>null</code>, white is used.
1946     */
1947     public void setMapBackgroundColor(Color bgColor) {
1948 alfonx 509 if (bgColor == null)
1949     bgColor = Color.WHITE;
1950 alfonx 505 this.mapBackgroundColor = bgColor;
1951     }
1952    
1953     /**
1954     * Returns the background {@link Color} of the map pane. Default is white.
1955 alfonx 509 **/
1956 alfonx 505 public Color getMapBackgroundColor() {
1957     return mapBackgroundColor;
1958     }
1959    
1960 alfonx 509 /**
1961     *
1962     * @param b
1963     */
1964     public void setPainting(boolean b) {
1965     acceptsRepaintCalls = b;
1966     }
1967    
1968     /**
1969     * Fuegt der Map einen Listener hinzu.
1970     *
1971     * @param l
1972     * neuer Listener
1973     */
1974     public void addMapPaneListener(JMapPaneListener l) {
1975     mapPaneListeners.add(l);
1976     }
1977    
1978     /**
1979     * Liste der angeschlossenen Listener, die auf Aktionen des MapPanes
1980     * lauschen.
1981     */
1982     protected Vector<JMapPaneListener> mapPaneListeners = new Vector<JMapPaneListener>();
1983    
1984     /**
1985     * A flag indicating if dispose() was already called. If true, then further
1986     * use of this {@link SelectableXMapPane} is undefined.
1987     */
1988     private boolean disposed = false;
1989    
1990     /**
1991     * Entfernt einen Listener von der Map.
1992     *
1993     * @param l
1994     * zu entfernender Listener
1995     */
1996     public void removeMapPaneListener(JMapPaneListener l) {
1997     mapPaneListeners.remove(l);
1998     }
1999    
2000     /** Stored the time used for the last real rendering in ms. **/
2001     private long lastRenderingDuration = Long.MAX_VALUE;
2002    
2003     // if null, no quick preview will be shown
2004     private int quickPreviewHint = 0;
2005    
2006     /**
2007     * For every rendering thread started,
2008     * {@link GTUtil#createGTRenderer(MapContext)} is called to create a new
2009     * renderer. These Java2D rendering hints are passed to the
2010     * {@link GTRenderer}. The java2dHints are the same for all renderers (bg
2011     * and local).
2012     */
2013     private RenderingHints java2dHints;
2014    
2015     /**
2016     * Returns in milli seconds the time the last rending of the
2017     * {@link SelectableXMapPane} took. #Long.MAX_VALUE if the JMapPane has not
2018     * been rendered yet.
2019     */
2020     public long getLastRenderingDuration() {
2021     return lastRenderingDuration;
2022     }
2023    
2024     /**
2025     * Should be called when the {@link JMapPane} is not needed no more to help
2026     * the GarbageCollector
2027     *
2028     * Removes all {@link JMapPaneListener}s that are registered
2029     *
2030     * @author <a href="mailto:[email protected]">Stefan Alfons
2031     * Kr&uuml;ger</a>
2032     */
2033     public void dispose() {
2034     if (isDisposed())
2035     return;
2036    
2037     disposed = true;
2038    
2039     if (bgExecuter != null) {
2040     bgExecuter.cancelTask();
2041     }
2042    
2043     if (localExecuter != null) {
2044     localExecuter.cancelTask();
2045     }
2046    
2047     startRenderThreadsTimer.stop();
2048 alfonx 513 // repainterTimer.stop();
2049 alfonx 509
2050     if (bgImage != null)
2051     bgImage.flush();
2052     if (localImage != null)
2053     localImage.flush();
2054 alfonx 513 // if (gadgetsImage != null)
2055     // gadgetsImage.flush();
2056 alfonx 509 if (finalImage != null)
2057     finalImage.flush();
2058     if (preFinalImage != null)
2059     preFinalImage.flush();
2060    
2061     // if (dragWaitCursorListener != null)
2062     // this.removeMouseListener(dragWaitCursorListener);
2063     // if (mouseWheelZoomListener != null)
2064     // this.removeMouseWheelListener(mouseWheelZoomListener);
2065    
2066     // Alle mapPaneListener entfernen
2067     mapPaneListeners.clear();
2068    
2069     removeMouseMotionListener(zoomMapPaneMouseListener);
2070     removeMouseListener(zoomMapPaneMouseListener);
2071    
2072     if (localContext != null)
2073     getContext().clearLayerList();
2074     if (bgContext != null)
2075     getBgContext().clearLayerList();
2076    
2077     removeAll();
2078     }
2079    
2080     /**
2081     * A flag indicating if dispose() has already been called. If true, then
2082     * further use of this {@link SelectableXMapPane} is undefined.
2083     */
2084     private boolean isDisposed() {
2085     return disposed;
2086     }
2087    
2088     public void setQuickPreviewHint(int quickPreviewHint) {
2089     this.quickPreviewHint = quickPreviewHint;
2090    
2091     }
2092    
2093     public void setJava2dHints(RenderingHints java2dHints) {
2094     this.java2dHints = java2dHints;
2095     }
2096    
2097     public RenderingHints getJava2dHints() {
2098     return java2dHints;
2099     }
2100    
2101     /**
2102     * Zooms towards a point.
2103     *
2104     * @param center
2105     * position in window coordinates
2106     * @param zoomFaktor
2107     * > 1 for zoom in, < 1 for zoom out. Default is 1.33.
2108     */
2109     public void zoomTo(Point center, Double zoomFaktor) {
2110     if (zoomFaktor == null || zoomFaktor == 0.)
2111     zoomFaktor = 2.;
2112    
2113     Point2D gcenter = getScreenToWorld().transform(center, null);
2114     center = null;
2115 alfonx 513
2116     if (Double.isNaN(gcenter.getX()) || Double.isNaN(gcenter.getY())
2117     || Double.isInfinite(gcenter.getX())
2118     || Double.isInfinite(gcenter.getY())
2119    
2120 alfonx 509 ) {
2121     // Not inside valid CRS area! cancel
2122     return;
2123     }
2124    
2125     final Envelope mapArea = getMapArea();
2126 alfonx 513
2127 alfonx 509 Envelope newMapArea = new Envelope(mapArea);
2128 alfonx 513 newMapArea.expandBy((mapArea.getWidth() * zoomFaktor - mapArea
2129     .getWidth()) / 2., (mapArea.getHeight() * zoomFaktor - mapArea
2130     .getHeight()) / 2.);
2131 alfonx 509
2132     // Move the newMapArea above the new center
2133 alfonx 513 newMapArea.translate(gcenter.getX() - mapArea.centre().x, gcenter
2134     .getY()
2135 alfonx 509 - mapArea.centre().y);
2136    
2137     setMapArea(newMapArea);
2138     }
2139    
2140     /**
2141     * Zooms towards a point.
2142     *
2143     * @param center
2144     * position in window coordinates
2145     * @param zoomFactor
2146     * > 1 for zoom in, < 1 for zoom out. Default is 1.33
2147     */
2148     public void zoomTo(Point center) {
2149     zoomTo(center, null);
2150     }
2151    
2152     public void mouseDragged(Point startPos, Point lastPos, MouseEvent event) {
2153    
2154     if ((getState() == XMapPane.PAN)
2155     || ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0)) {
2156    
2157     if (getCursor() != SwingUtil.PANNING_CURSOR) {
2158     setCursor(SwingUtil.PANNING_CURSOR);
2159    
2160     // While panning, we deactivate the rendering. So the tasts are
2161     // ready to start when the panning os done.
2162     if (bgExecuter != null)
2163     bgExecuter.cancelTask();
2164     if (localExecuter != null)
2165     localExecuter.cancelTask();
2166     }
2167    
2168     if (lastPos.x > 0 && lastPos.y > 0) {
2169     final int dx = event.getX() - lastPos.x;
2170     final int dy = event.getY() - lastPos.y;
2171    
2172     // TODO Stop dragging when the drag would not be valid...
2173     // boolean dragValid = true;
2174     // // check if this panning results in a valid mapArea
2175     // {
2176     // Rectangle winBounds = xMapPane.getBounds();
2177     // winBounds.translate(xMapPane.imageOrigin.x,
2178     // -xMapPane.imageOrigin.y);
2179     // Envelope newMapAreaBefore = xMapPane.tranformWindowToGeo(
2180     // winBounds.x, winBounds.y, winBounds.x
2181     // + winBounds.width, winBounds.y
2182     // + winBounds.height);
2183     //
2184     //
2185     // winBounds = xMapPane.getBounds();
2186     // Point testIng = new Point(xMapPane.imageOrigin);
2187     // testIng.translate(dx, dy);
2188     // winBounds.translate(testIng.x, -testIng.y);
2189     // Envelope newMapAreaAfter = xMapPane.tranformWindowToGeo(
2190     // winBounds.x, winBounds.y, winBounds.x
2191     // + winBounds.width, winBounds.y
2192     // + winBounds.height);
2193     //
2194     // // If the last drag doesn't change the MapArea anymore cancel
2195     // it.
2196     // if (xMapPane.bestAllowedMapArea(newMapAreaAfter).equals(
2197     // xMapPane.bestAllowedMapArea(newMapAreaBefore))){
2198     // dragValid = false;
2199     // return;
2200     // }
2201     // }
2202    
2203     imageOrigin.translate(dx, dy);
2204     updateFinalImage();
2205     repaint();
2206     }
2207    
2208     } else if ((getState() == XMapPane.ZOOM_IN)
2209     || (getState() == XMapPane.ZOOM_OUT)
2210     || (getState() == XMapPane.SELECT_ALL)
2211     || (getState() == XMapPane.SELECT_TOP)
2212     // || (getState() == XMapPane.SELECT_ONE_FROM_TOP)
2213     ) {
2214     final Graphics graphics = getGraphics();
2215    
2216     drawRectangle(graphics, startPos, event.getPoint());
2217    
2218     if ((lastPos.x > 0) && (lastPos.y > 0)) {
2219     drawRectangle(graphics, startPos, lastPos);
2220     }
2221    
2222     }
2223    
2224     }
2225    
2226     /**
2227     * Draws a rectangle in XOR mode from the origin at {@link #startPos} to the
2228     * given point. All in screen coordinates.
2229     */
2230     protected void drawRectangle(final Graphics graphics, Point startPos,
2231     Point e) {
2232     // undraw last box/draw new box
2233     final int left = Math.min(startPos.x, e.x);
2234     final int right = Math.max(startPos.x, e.x);
2235     final int top = Math.max(startPos.y, e.y);
2236     final int bottom = Math.min(startPos.y, e.y);
2237     final int width = right - left;
2238     final int height = top - bottom;
2239    
2240     if (width == 0 && height == 0)
2241     return;
2242    
2243     graphics.setXORMode(Color.WHITE);
2244     graphics.drawRect(left, bottom, width, height);
2245     }
2246    
2247     /**
2248     * Finalizes a PAN action
2249     */
2250     public void performPan() {
2251     Rectangle winBounds = getBounds();
2252     winBounds.translate(-imageOrigin.x, -imageOrigin.y);
2253     Envelope newMapArea = tranformWindowToGeo(winBounds.x, winBounds.y,
2254     winBounds.x + winBounds.width, winBounds.y + winBounds.height);
2255    
2256     imageOrigin.x = 0;
2257     imageOrigin.y = 0;
2258    
2259 alfonx 513 if (!setMapArea(newMapArea)) {
2260 alfonx 509 updateFinalImage();
2261     repaint();
2262     }
2263    
2264     if (getCursor() == SwingUtil.PANNING_CURSOR)
2265     setCursor(SwingUtil.PAN_CURSOR);
2266     }
2267    
2268 alfonx 513 public void onRenderingPending() {
2269     LOGGER.debug("Pending rendering updates the preview...");
2270     updateFinalImage();
2271     XMapPane.this.repaint();
2272     }
2273    
2274 mojays 2 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26