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

Legend:
Removed from v.516  
changed lines
  Added in v.555

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26