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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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

Legend:
Removed from v.521  
changed lines
  Added in v.553

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26