/[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 515 - (hide annotations)
Mon Nov 9 18:51:54 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: 66662 byte(s)
Styles with wronlgy spelled property names are now automatically corrected when loaded. This helps when upgrading atlases that have been created with GeoPublisher/AtlasStyler 1.2


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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26