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

Legend:
Removed from v.513  
changed lines
  Added in v.560

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26