/[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 556 by alfonx, Tue Nov 24 12:56:09 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()                  if (newMapArea == null)
1716                                                  - divX, newArea.getMaxY()));                          return false;
1717                    if (getMapContext()
1718                                    .getCoordinateReferenceSystem() == null) return false;
1719                    return setMapArea(new ReferencedEnvelope(newMapArea, getMapContext()
1720                                    .getCoordinateReferenceSystem()));
1721            }
1722    
1723                                  if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {          /**
1724                                          // LOGGER.debug("Now it exeeds the right border.. cut!");           * @param newMapArea
1725                                          // And cut the left if it moved out of the area           * @return <code>true</code> if the mapArea has been changed and a repaint
1726                                          newArea = new Envelope(new Coordinate(newArea.getMinX(),           *         has been triggered.
1727                                                          newArea.getMinY()), new Coordinate(maxAllowedExtend           */
1728                                                          .getMaxX(), newArea.getMaxY()));          public boolean setMapArea(final ReferencedEnvelope newMapArea) {
1729    
1730                                          // LOGGER.debug("and fix aspect ratio");                  if (newMapArea == null
1731                                    || bestAllowedMapArea(newMapArea).equals(mapArea)) {
1732                            // No change.. no need to repaint
1733                            return false;
1734                    }
1735    
1736                                          newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,                  // Testing, whether NaN or Infinity are used in the newMapArea
1737                                                          false);                  if (newMapArea.isNull() || Double.isInfinite(newMapArea.getMaxX())
1738                                  }                                  || Double.isInfinite(newMapArea.getMaxY())
1739                                    || Double.isInfinite(newMapArea.getMinX())
1740                                    || Double.isInfinite(newMapArea.getMinY())) {
1741                            // No change.. ugly new values
1742                            LOGGER.warn("setMapArea has been called with newArea = "
1743                                            + newMapArea);
1744                            return false;
1745                    }
1746    
1747                    final Envelope candNew = bestAllowedMapArea(newMapArea);
1748    
1749                    // Testing, whether the difference if just minimal
1750                    if (mapArea != null) {
1751                            final double tolX = mapArea.getWidth() / 1000.;
1752                            final double tolY = mapArea.getHeight() / 1000.;
1753                            if ((candNew.getMinX() - tolX < mapArea.getMinX())
1754                                            && (mapArea.getMinX() < candNew.getMinX() + tolX)
1755                                            && (candNew.getMaxX() - tolX < mapArea.getMaxX())
1756                                            && (mapArea.getMaxX() < candNew.getMaxX() + tolX)
1757    
1758                                            && (candNew.getMinY() - tolY < mapArea.getMinY())
1759                                            && (mapArea.getMinY() < candNew.getMinY() + tolY)
1760                                            && (candNew.getMaxY() - tolY < mapArea.getMaxY())
1761                                            && (mapArea.getMaxY() < candNew.getMaxY() + tolY)
1762    
1763                            ) {
1764                                    // The two mapAreas only differ my 1/1000th.. ignore
1765    
1766                                    return false;
1767                          }                          }
1768                    }
1769    
1770                    // New map are is accepted:
1771                    oldMapArea = mapArea;
1772                    mapArea = candNew;
1773                    resetTransforms();
1774    
1775                    if (localContext != null) {
1776                            localContext.setAreaOfInterest(mapArea, localContext
1777                                            .getCoordinateReferenceSystem());
1778                  }                  }
1779                    if (bgContext != null) {
1780                            bgContext.setAreaOfInterest(mapArea, localContext
1781                                            .getCoordinateReferenceSystem());
1782                    }
1783    
1784                    mapAreaChanged = true;
1785    
1786                  return newArea;                  repaint(200); // Do not remove it!
1787    
1788                    requestStartRendering();
1789    
1790                    return true;
1791          }          }
1792    
1793          /**          /**
1794           * 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  
1795           *           *
1796           * @author <a href="mailto:[email protected]">Stefan Alfons           * @param if <code>null</code>, white is used.
          *         Kr&uuml;ger</a>  
1797           */           */
1798          public Double getMinZoomScale() {          public void setMapBackgroundColor(Color bgColor) {
1799                  return minZoomScale;                  if (bgColor == null)
1800                            bgColor = Color.WHITE;
1801                    this.mapBackgroundColor = bgColor;
1802          }          }
1803    
1804          /**          /**
1805           * 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  
1806           *           *
1807           * @author <a href="mailto:[email protected]">Stefan Alfons           * @param mapImageIcon
1808           *         Kr&uuml;ger</a>           *            <code>null</code> is allowed and deactivates this icon.
1809           */           */
1810          public Double getMaxZoomScale() {          public void setMapImage(final BufferedImage mapImage) {
1811                  return maxZoomScale;                  this.mapImage = mapImage;
1812            }
1813    
1814            /**
1815             * Sets whether a layer is regarded or ignored on {@link #SELECT_TOP},
1816             * {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP} actions.
1817             *
1818             * @param layer
1819             *            a layer
1820             * @param selectable
1821             *            if {@code false} the layer is ignored during the upper
1822             *            mentioned actions. If <code>null</code>, the default (true)
1823             *            will be used.
1824             */
1825            public void setMapLayerSelectable(final MapLayer layer,
1826                            final Boolean selectable) {
1827                    if (selectable == null)
1828                            mapLayerSelectable.remove(layer);
1829                    else
1830                            mapLayerSelectable.put(layer, selectable);
1831            }
1832    
1833            /**
1834             * Defines an evelope of the viwable area. The JMapPane will never show
1835             * anything outside of this extend.
1836             *
1837             * @param maxExtend
1838             *            <code>null</code> to not have this restriction.
1839             */
1840            public void setMaxExtend(final Envelope maxExtend) {
1841                    this.maxExtend = maxExtend;
1842          }          }
1843    
1844          /**          /**
# Line 1881  public class XMapPane extends JPanel imp Line 1854  public class XMapPane extends JPanel imp
1854                                  : maxZoomScale;                                  : maxZoomScale;
1855          }          }
1856    
1857            // /** Stored the time used for the last real rendering in ms. **/
1858            // private long lastRenderingDuration = Long.MAX_VALUE;
1859    
1860          /**          /**
1861           * Set the minimum (nearest) allowed zoom scale. This is the bigger number           * Set the minimum (nearest) allowed zoom scale. This is the bigger number
1862           * 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 1872  public class XMapPane extends JPanel imp
1872          }          }
1873    
1874          /**          /**
1875           * Defines an evelope of the viwable area. The JMapPane will never show           * If <code>true</code>, allow the {@link XMapPane} to process #repaint()
1876           * anything outside of this extend.           * requests. Otherwise the map will not paint anything and not start any
1877           *           * rendering {@link Thread}s.
          * @param maxExtend  
          *            <code>null</code> to not have this restriction.  
1878           */           */
1879          public void setMaxExtend(Envelope maxExtend) {          public void setPainting(final boolean b) {
1880                  this.maxExtend = maxExtend;                  acceptsRepaintCalls = b;
1881                    if (acceptsRepaintCalls == true)
1882                            repaint();
1883            }
1884    
1885            private void setRendererHints(final Map<Object, Object> rendererHints) {
1886                    if (rendererHints != null)
1887                            this.rendererHints = rendererHints;
1888          }          }
1889    
1890          /**          /**
1891           * Returns the evelope of the viewable area. The JMapPane will never show           * Enables/Disables the ZOOM Mouse Listener. Upates the Cursor and stops the
1892           * 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.  
1893           *           *
1894           * @param maxExtend           * @param state
          *            <code>null</code> to not have this restriction.  
1895           */           */
1896            public void setState(final int state) {
1897                    this.state = state;
1898    
1899          public Envelope getMaxExtend() {                  zoomMapPaneMouseListener.setEnabled((state == ZOOM_IN
1900                  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;  
                         }  
1901    
1902                          // Kartenbereich um 10% vergroessern                  // Je nach Aktion den Cursor umsetzen
1903                          return JTSUtil.fixAspectRatio(this.getBounds(), JTSUtil                  updateCursor();
                                         .expandEnvelope(layerBounds, 0.1), true);  
                 }  
                 return maxExtend;  
1904          }          }
1905    
1906          /**          /**
1907           * Set the background color of the map.           * Standardmaessig wird der Cursor automatisch je nach MapPane-Aktion (Zoom,
1908             * Auswahl, ...) gesetzt. Mit dieser Methode kann ein statischer Cursor
1909             * gesetzt werden, der unabhaengig von der aktuellen MapPanes-Aktion
1910             * beibehalten wird. Um diesen statischen Cursor wieder zu entfernen, kann
1911             * {@code null} als Parameter uebergeben werden
1912           *           *
1913           * @param if <code>null</code>, white is used.           * @param cursor
1914             *            Cursor
1915           */           */
1916          public void setMapBackgroundColor(Color bgColor) {          public void setStaticCursor(final Cursor cursor) {
1917                  if (bgColor == null)                  this.staticCursor = cursor;
1918                          bgColor = Color.WHITE;                  if (cursor != null)
1919                  this.mapBackgroundColor = bgColor;                          super.setCursor(cursor);
1920          }          }
1921    
1922          /**          /**
1923           * Returns the background {@link Color} of the map pane. Default is white.           * Starts rendering on one or two threads
1924           **/           */
1925          public Color getMapBackgroundColor() {          private void startRendering() {
1926                  return mapBackgroundColor;  
1927                    if (!isWellDefined() || !acceptsRepaintCalls) {
1928                            // if we are not ready to start rendering, try it again the next
1929                            // time the timer is chacking the flag.
1930                            requestStartRendering = true;
1931                            return;
1932                    }
1933    
1934                    if (bgExecuter != null) {
1935                            // Stop all renderers
1936                            bgExecuter.cancelTask();
1937                    }
1938    
1939                    localExecuter.cancelTask();
1940    
1941                    final Rectangle curPaintArea = getVisibleRect();
1942    
1943                    /**
1944                     * We have to set new renderer
1945                     */
1946    
1947                    if (getBgContext() != null) {
1948                            bgRenderer.setJava2DHints(getJava2dHints());
1949                            bgRenderer.setRendererHints(getRendererHints());
1950    
1951                            // bgExecuter = new RenderingExecutor();
1952                            // LOGGER.debug("starting bg renderer:");
1953                            // // /* System.out.println("rendering"); */
1954                            // final GTRenderer createGTRenderer = GTUtil.createGTRenderer(
1955                            // bgContext, getRendererHints());
1956                            // createGTRenderer.setJava2DHints(getJava2dHints());
1957                            // bgExecuter.submit(getBgContext().getAreaOfInterest(),
1958                            // curPaintArea,
1959                            // (Graphics2D) getBgImage().getGraphics(), createGTRenderer);
1960                    }
1961    
1962                    if (getMapContext() != null) {
1963                            // localExecuter = new RenderingExecutor(this, 150l);
1964                            // LOGGER.debug("starting local renderer:");
1965    
1966                            localRenderer.setJava2DHints(getJava2dHints());
1967                            localRenderer.setRendererHints(getRendererHints());
1968    
1969                            final boolean submitted = localExecuter.submit(getMapArea(),
1970                                            curPaintArea, (Graphics2D) getLocalImage().getGraphics(),
1971                                            localRenderer);
1972                            if (submitted)
1973                                    repaintTimer.restart();
1974                            else
1975                                    requestStartRendering = true; // Try to start rendering
1976                            // again in
1977                            // a moment
1978                    }
1979    
1980                    updateCursor();
1981          }          }
1982    
1983          /**          /**
1984             * Transformiert einen Geo-Koordinaten-Bereich in Fenster-Koordinaten.
1985           *           *
1986           * @param b           * @param ox
1987             *            X-Koordinate der VON-Position
1988             * @param oy
1989             *            Y-Koordinate der VON-Position
1990             * @param px
1991             *            X-Koordinate der BIS-Position
1992             * @param py
1993             *            Y-Koordinate der BIS-Position
1994             * @param winToGeotransform
1995             *            Eine Window to Geo transform. If <code>null</code>,
1996             *            {@link #getScreenToWorld()} is used.
1997           */           */
1998          public void setPainting(boolean b) {          public Envelope tranformGeoToWindow(final double ox, final double oy,
1999                  acceptsRepaintCalls = b;                          final double px, final double py) {
2000                    final AffineTransform at = getWorldToScreenTransform();
2001                    Point2D geoO;
2002                    // try {
2003                    geoO = at.transform(new Point2D.Double(ox, oy), null);
2004                    final Point2D geoP = at.transform(new Point2D.Double(px, py), null);
2005                    return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP.getY());
2006                    // } catch (final NoninvertibleTransformException e) {
2007                    // LOGGER.error(e);
2008                    // return new Envelope(ox, oy, px, py);
2009                    // }
2010          }          }
2011    
2012          /**          /**
2013           * Fuegt der Map einen Listener hinzu.           * Transformiert einen Fenster-Koordinaten-Bereich in Geo-Koordinaten.
2014           *           *
2015           * @param l           * @param ox
2016           *            neuer Listener           *            X-Koordinate der VON-Position
2017             * @param oy
2018             *            Y-Koordinate der VON-Position
2019             * @param px
2020             *            X-Koordinate der BIS-Position
2021             * @param py
2022             *            Y-Koordinate der BIS-Position
2023           */           */
2024          public void addMapPaneListener(JMapPaneListener l) {          public Envelope tranformWindowToGeo(final int ox, final int oy,
2025                  mapPaneListeners.add(l);                          final int px, final int py) {
2026                    final AffineTransform at = getScreenToWorld();
2027                    final Point2D geoO = at.transform(new Point2D.Double(ox, oy), null);
2028                    final Point2D geoP = at.transform(new Point2D.Double(px, py), null);
2029    
2030                    // Mmmmm... don't really understand why its x,x,y,y
2031                    // return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(),
2032                    // geoP.getY());
2033                    return new Envelope(new Coordinate(geoO.getX(), geoO.getY()),
2034                                    new Coordinate(geoP.getX(), geoP.getY()));
2035          }          }
2036    
2037          /**          /**
2038           * Liste der angeschlossenen Listener, die auf Aktionen des MapPanes           * Will update the cursor. If all rendering is finished also stops the
2039           * lauschen.           * {@link #repaintTimer}
2040           */           */
2041          protected Vector<JMapPaneListener> mapPaneListeners = new Vector<JMapPaneListener>();          public void updateCursor() {
2042    
2043          /**                  // if the renderers have stopped, also stop the timer that is updating
2044           * A flag indicating if dispose() was already called. If true, then further                  // the final image
2045           * use of this {@link SelectableXMapPane} is undefined.                  if (bgExecuter != null && bgExecuter.isRunning()
2046           */                                  || localExecuter.isRunning()) {
2047          private boolean disposed = false;                          setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
2048                            return;
2049                    } else {
2050                            // Allow one last rendering
2051                            if (repaintTimer.isRunning()) {
2052                                    // System.out.println("one last rendering....");
2053                                    repaintTimer.stop();
2054                                    updateFinalImage();
2055                                    repaint();
2056                            }
2057                    }
2058    
2059                    // wenn manueller Cursor gesetzt ist, dann diesen verwenden (unabhaengig
2060                    // von der aktuellen Aktion
2061                    if (this.staticCursor != null) {
2062                            setCursor(staticCursor);
2063                            return;
2064                    }
2065                    if (getCursor() == SwingUtil.PANNING_CURSOR) {
2066                            // This cursor will reset itself
2067                            return;
2068                    }
2069    
2070                    // Set the cursor depending on what tool is in use...
2071                    switch (state) {
2072                    case SELECT_TOP:
2073                    case SELECT_ONE_FROM_TOP:
2074                    case SELECT_ALL:
2075                            setCursor(SwingUtil.CROSSHAIR_CURSOR);
2076                            break;
2077                    case ZOOM_IN:
2078                            setCursor(SwingUtil.ZOOMIN_CURSOR);
2079                            break;
2080                    case ZOOM_OUT:
2081                            setCursor(SwingUtil.ZOOMOUT_CURSOR);
2082                            break;
2083                    case PAN:
2084                            setCursor(SwingUtil.PAN_CURSOR);
2085                            break;
2086                    default:
2087                            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
2088                            break;
2089                    }
2090            }
2091    
2092          /**          /**
2093           * Entfernt einen Listener von der Map.           * The renderers are all rendering into their own {@link Image}s. This
2094           *           * method combines all images to one {@link #finalImage}. The
2095           * @param l           * {@link #repaintTimer} is calling this method regularely to update the
2096           *            zu entfernender Listener           * {@link #finalImage} even if the renderers are still working.
2097           */           */
2098          public void removeMapPaneListener(JMapPaneListener l) {          synchronized protected Image updateFinalImage() {
                 mapPaneListeners.remove(l);  
         }  
2099    
2100          /** Stored the time used for the last real rendering in ms. **/                  // Render the two map images first, into the preFinalImage
2101          private long lastRenderingDuration = Long.MAX_VALUE;                  if (bgExecuter != null) {
2102                            final Graphics2D preFinalG = (Graphics2D) getPreFinalImage()
2103                                            .getGraphics();
2104                            preFinalG.setBackground(getMapBackgroundColor());
2105    
2106          // if null, no quick preview will be shown                          preFinalG.drawImage(getBgImage(), 0, 0, getMapBackgroundColor(),
2107          private int quickPreviewHint = 0;                                          null);
2108    
2109          /**                          // // Draw the local layers image
2110           * For every rendering thread started,                          preFinalG.drawImage(getLocalImage(), 0, 0, null);
2111           * {@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;  
2112    
2113          /**                  } else {
2114           * Returns in milli seconds the time the last rending of the                          preFinalImage = getLocalImage();
2115           * {@link SelectableXMapPane} took. #Long.MAX_VALUE if the JMapPane has not                  }
2116           * been rendered yet.  
2117           */                  final Graphics2D finalG = getFinalImage().createGraphics();
2118          public long getLastRenderingDuration() {                  finalG.setBackground(getMapBackgroundColor());
2119                  return lastRenderingDuration;                  finalG.drawImage(getPreFinalImage(), imageOrigin.x, imageOrigin.y,
2120                                    getMapBackgroundColor(), null);
2121    
2122                    final int finalImageHeight = getFinalImage().getHeight(null);
2123                    final int finalImageWidth = getFinalImage().getWidth(null);
2124    
2125                    final Rectangle painedArea = new Rectangle(imageOrigin.x,
2126                                    imageOrigin.y, finalImageWidth, finalImageHeight);
2127                    SwingUtil.clearAround(finalG, painedArea, getVisibleRect());
2128    
2129                    addGadgets(finalG, false);
2130    
2131                    finalG.dispose();
2132    
2133                    return finalImage;
2134          }          }
2135    
2136          /**          /**
2137           * Should be called when the {@link JMapPane} is not needed no more to help           * Paints some optional stuff into the given {@link Graphics2D}. Usually
2138           * the GarbageCollector           * called as the last layer when {@link #updateFinalImage()}
2139           *           *
2140           * Removes all {@link JMapPaneListener}s that are registered           * @param forceWait
2141           *           *            if <code>true</code>, a Wait-message will be painted even
2142           * @author <a href="mailto:[email protected]">Stefan Alfons           *            though the rendering threads may not yet have started. If
2143           *         Kr&uuml;ger</a>           *            <code>false</code>, it will only depend on
2144             *            {@link #localExecuter.isRunning} and #bgExecuter.isRunning
2145           */           */
2146          public void dispose() {          private void addGadgets(final Graphics2D graphics, boolean forceWait) {
                 if (isDisposed())  
                         return;  
                   
                 setPainting(false);  
2147    
2148                  disposed = true;                  // Paint a logo to the bottom right if available
2149                    if (mapImage != null) {
2150                  if (bgExecuter != null) {                          Rectangle visibleRect = getVisibleRect();
2151                          bgExecuter.cancelTask();                          graphics.drawImage(mapImage, visibleRect.width
2152                                            - mapImage.getWidth() - 10, getVisibleRect().height
2153                                            - mapImage.getHeight() - 10, null);
2154                  }                  }
2155    
2156                  if (localExecuter != null) {                  int y = 17;
2157                          localExecuter.cancelTask();  
2158                    // If the rendering process is still running, indicate this is the image
2159                    if (forceWait || bgExecuter != null && bgExecuter.isRunning()
2160                                    || localExecuter.isRunning()) {
2161    
2162                            y += 8;
2163    
2164                            final Color c = graphics.getColor();
2165                            graphics.setFont(waitFont);
2166    
2167                            graphics.setColor(getMapBackgroundColor());
2168                            graphics.drawString(waitMsg, 5, y);
2169                            graphics.setColor(getMapBackgroundColor());
2170                            graphics.drawString(waitMsg, 7, y + 2);
2171                            graphics.setColor(Color.BLACK);
2172                            graphics.drawString(waitMsg, 6, y + 1);
2173    
2174                            graphics.setColor(c);
2175    
2176                            y += 24;
2177                  }                  }
2178    
2179                  startRenderThreadsTimer.stop();                  if (!renderingErrors.isEmpty() && isShowExceptions()) {
 //              repainterTimer.stop();  
2180    
2181                  if (bgImage != null)                          final Color c = graphics.getColor();
2182                          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();  
2183    
2184                  // if (dragWaitCursorListener != null)                          for (Exception ex : renderingErrors) {
                 // this.removeMouseListener(dragWaitCursorListener);  
                 // if (mouseWheelZoomListener != null)  
                 // this.removeMouseWheelListener(mouseWheelZoomListener);  
2185    
2186                  // Alle mapPaneListener entfernen                                  String errStr = ex.getLocalizedMessage();
                 mapPaneListeners.clear();  
2187    
2188                  removeMouseMotionListener(zoomMapPaneMouseListener);                                  if (errStr == null)
2189                  removeMouseListener(zoomMapPaneMouseListener);                                          errStr = ex.getMessage();
2190                                    if (errStr == null)
2191                                            errStr = "unknown error: " + ex.getClass().getSimpleName();
2192    
2193                  if (localContext != null)                                  graphics.setColor(Color.WHITE);
2194                          getContext().clearLayerList();                                  graphics.drawString(errStr, 5, y);
2195                  if (bgContext != null)                                  graphics.setColor(Color.RED);
2196                          getBgContext().clearLayerList();                                  graphics.drawString(errStr, 6, y + 1);
2197    
2198                                    y += 19;
2199                            }
2200    
2201                            graphics.setColor(c);
2202                    }
2203    
                 removeAll();  
2204          }          }
2205    
2206          /**          /**
2207           * A flag indicating if dispose() has already been called. If true, then           * Sets the {@link #mapArea} to best possibly present the given features. If
2208           * further use of this {@link SelectableXMapPane} is undefined.           * only one single point is given, the window is moved over the point.
2209             *
2210             * @param features
2211             *            if <code>null</code> or size==0, the function doesn nothing.
2212           */           */
2213          private boolean isDisposed() {          public void zoomTo(
2214                  return disposed;                          final FeatureCollection<SimpleFeatureType, SimpleFeature> features) {
         }  
2215    
2216          public void setQuickPreviewHint(int quickPreviewHint) {                  // if (!isWellDefined()) return;
                 this.quickPreviewHint = quickPreviewHint;  
2217    
2218          }                  final CoordinateReferenceSystem mapCRS = getMapContext()
2219                                    .getCoordinateReferenceSystem();
2220                    final CoordinateReferenceSystem fCRS = features.getSchema()
2221                                    .getGeometryDescriptor().getCoordinateReferenceSystem();
2222    
2223          public void setJava2dHints(RenderingHints java2dHints) {                  ReferencedEnvelope _mapArea;
2224                  this.java2dHints = java2dHints;                  if (mapArea == null)
2225                            _mapArea = features.getBounds();
2226                    else
2227                            _mapArea = getMapArea();
2228                    double width = _mapArea.getWidth();
2229                    double height = _mapArea.getHeight();
2230                    final double ratio = height / width;
2231    
2232                    if (features == null || features.size() == 0) {
2233                            // feature count == 0 Zoom to the full extend
2234                            return;
2235                    } else if (features.size() == 1) {
2236    
2237                            // feature count == 1 Just move the window to the point and zoom 'a
2238                            // bit'
2239                            final SimpleFeature singleFeature = features.iterator().next();
2240    
2241                            if (((Geometry) singleFeature.getDefaultGeometry())
2242                                            .getCoordinates().length > 1) {
2243                                    // System.out.println("Zoomed to only pne poylgon");
2244                                    // Poly
2245                                    // TODO max width vs. height
2246                                    width = features.getBounds().getWidth() * 3;
2247                                    height = ratio * width;
2248                            } else {
2249                                    // System.out.println("Zoomed in a bit becasue only one point");
2250                                    // width *= .9;
2251                                    // height *= .9;
2252                            }
2253    
2254                            Coordinate centre = features.getBounds().centre();
2255                            if (!mapCRS.equals(fCRS)) {
2256                                    // only to calculations if the CRS differ
2257                                    try {
2258                                            MathTransform fToMap;
2259                                            fToMap = CRS.findMathTransform(fCRS, mapCRS);
2260                                            // centre is transformed to the mapCRS
2261                                            centre = JTS.transform(centre, null, fToMap);
2262                                    } catch (final FactoryException e) {
2263                                            LOGGER.error("Looking for a Math transform", e);
2264                                    } catch (final TransformException e) {
2265                                            LOGGER.error("Looking for a Math transform", e);
2266                                    }
2267                            }
2268    
2269                            final Coordinate newLeftBottom = new Coordinate(centre.x - width
2270                                            / 2., centre.y - height / 2.);
2271                            final Coordinate newTopRight = new Coordinate(
2272                                            centre.x + width / 2., centre.y + height / 2.);
2273    
2274                            final Envelope newMapArea = new Envelope(newLeftBottom, newTopRight);
2275    
2276                            setMapArea(newMapArea);
2277    
2278                    } else {
2279                            final ReferencedEnvelope fBounds = features.getBounds();
2280    
2281                            ReferencedEnvelope bounds;
2282                            if (!mapCRS.equals(fCRS)) {
2283                                    bounds = JTSUtil.transformEnvelope(fBounds, mapCRS);
2284                            } else {
2285                                    bounds = fBounds;
2286                            }
2287                            // BB umrechnen von Layer-CRS in Map-CRS
2288    
2289                            // Expand a bit
2290                            bounds.expandBy(bounds.getWidth() / 6., bounds.getHeight() / 6.);
2291    
2292                            setMapArea(bounds);
2293                    }
2294          }          }
2295    
2296          public RenderingHints getJava2dHints() {          /**
2297                  return java2dHints;           * Zooms towards a point.
2298             *
2299             * @param center
2300             *            position in window coordinates
2301             * @param zoomFactor
2302             *            > 1 for zoom in, < 1 for zoom out. Default is 1.33
2303             */
2304            public void zoomTo(final Point center) {
2305                    zoomTo(center, null);
2306          }          }
2307    
2308          /**          /**
# Line 2106  public class XMapPane extends JPanel imp Line 2317  public class XMapPane extends JPanel imp
2317                  if (zoomFaktor == null || zoomFaktor == 0.)                  if (zoomFaktor == null || zoomFaktor == 0.)
2318                          zoomFaktor = 2.;                          zoomFaktor = 2.;
2319    
2320                  Point2D gcenter = getScreenToWorld().transform(center, null);                  final Point2D gcenter = getScreenToWorld().transform(center, null);
2321                  center = null;                  center = null;
2322    
2323                  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 2331  public class XMapPane extends JPanel imp
2331    
2332                  final Envelope mapArea = getMapArea();                  final Envelope mapArea = getMapArea();
2333    
2334                  Envelope newMapArea = new Envelope(mapArea);                  final Envelope newMapArea = new Envelope(mapArea);
2335                  newMapArea.expandBy((mapArea.getWidth() * zoomFaktor - mapArea                  newMapArea.expandBy((mapArea.getWidth() * zoomFaktor - mapArea
2336                                  .getWidth()) / 2., (mapArea.getHeight() * zoomFaktor - mapArea                                  .getWidth()) / 2., (mapArea.getHeight() * zoomFaktor - mapArea
2337                                  .getHeight()) / 2.);                                  .getHeight()) / 2.);
2338    
2339                  // Move the newMapArea above the new center                  // TODO we actually want that
2340                  newMapArea.translate(gcenter.getX() - mapArea.centre().x, gcenter                  // // Move the newMapArea above the new center
2341                                  .getY()                  // newMapArea.translate(gcenter.getX() - mapArea.centre().x, gcenter
2342                                  - mapArea.centre().y);                  // .getY()
2343                    // - mapArea.centre().y);
2344    
2345                  setMapArea(newMapArea);                  setMapArea(newMapArea);
2346          }          }
2347    
2348          /**          /**
2349           * Zooms towards a point.           * Shall non-fatal rendering exceptions be reported in the mappane or be
2350           *           * 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.  
2351           */           */
2352          protected void drawRectangle(final Graphics graphics, Point startPos,          public void setShowExceptions(boolean showExceptions) {
2353                          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);  
2354          }          }
2355    
2356          /**          /**
2357           * Finalizes a PAN action           * Shall exceptions be reported in the mappane?
2358           */           */
2359          public void performPan() {          public boolean isShowExceptions() {
2360                  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();  
2361          }          }
2362    
2363  }  }

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26