/[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 511 by alfonx, Thu Nov 5 17:41:33 2009 UTC branches/1.0-gt2-2.6/src/skrueger/geotools/XMapPane.java revision 558 by alfonx, Tue Nov 24 13:01: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;
 import org.geotools.swing.RenderingExecutor;  
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;
51  import org.geotools.swing.event.MapPaneListener;  import org.geotools.swing.event.MapPaneListener;
# Line 82  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;
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;
   
         public static final long DEFAULT_REPAINTING_DELAY = 200;  
   
         protected skrueger.geotools.RenderingExecutor bgExecuter;  
   
         /**  
          * the map context to render  
          */  
         MapContext localContext;  
         /**  
          * the map context to render  
          */  
         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;  
   
204          /**          /**
205           * the size of the pane last time we drew           * Flag fuer Modus
206             * "SimpleFeature-Auswahl auf dem obersten (sichtbaren) Layer".
207             *
208             * @see #setState(int)
209             * @see #setState(int)
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 233  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 247  public class XMapPane extends JPanel imp Line 253  public class XMapPane extends JPanel imp
253    
254                  // aktuelle Geo-Position ueber Transformation des JMapPane berechnen                  // aktuelle Geo-Position ueber Transformation des JMapPane berechnen
255                  if (e != null && e.getSource() instanceof XMapPane) {                  if (e != null && e.getSource() instanceof XMapPane) {
256                            
257                          final XMapPane xMapPane = (XMapPane) e.getSource();                          final XMapPane xMapPane = (XMapPane) e.getSource();
258                            
259                          if (!xMapPane.isWellDefined()) return null;                          if (!xMapPane.isWellDefined())
260                                                            return null;
261                          AffineTransform at = xMapPane.getScreenToWorld();  
262                            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 262  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;  
   
         /**  
          * While threads are working, we call {@link XMapPane#updateFinalImage()}  
          * regularly and repaint(). This {@link Timer} is stoppen when all renderers  
          * finished.  
          */  
         final private Timer repainterTimer;  
   
         /**  
          * 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) DEFAULT_REPAINTING_DELAY,  
                                 new ActionListener() {  
   
                                         @Override  
                                         public void actionPerformed(ActionEvent e) {  
                                                 updateFinalImage();  
                                                 XMapPane.this.repaint(DEFAULT_REPAINTING_DELAY);  
                                         }  
                                 });  
                 repainterTimer.setInitialDelay(1000);  
                 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;  
                         }  
284    
285                          Coordinate centre = features.getBounds().centre();                  @Override
286                          if (!mapCRS.equals(fCRS)) {                  public void layerChanged(final MapLayerListEvent event) {
287                                  // only to calculations if the CRS differ                          requestStartRendering();
288                                  try {                  }
                                         MathTransform fToMap;  
                                         fToMap = CRS.findMathTransform(fCRS, mapCRS);  
                                         // centre is transformed to the mapCRS  
                                         centre = JTS.transform(centre, null, fToMap);  
                                 } catch (FactoryException e) {  
                                         LOGGER.error("Looking for a Math transform", e);  
                                 } catch (TransformException e) {  
                                         LOGGER.error("Looking for a Math transform", e);  
                                 }  
                         }  
   
                         Coordinate newLeftBottom = new Coordinate(centre.x - width / 2.,  
                                         centre.y - height / 2.);  
                         Coordinate newTopRight = new Coordinate(centre.x + width / 2.,  
                                         centre.y + height / 2.);  
   
                         Envelope newMapArea = new Envelope(newLeftBottom, newTopRight);  
   
                         setMapArea(newMapArea);  
   
                 } else {  
                         ReferencedEnvelope fBounds = features.getBounds();  
   
                         Envelope bounds;  
                         if (!mapCRS.equals(fCRS)) {  
                                 bounds = JTSUtil.transformEnvelope(fBounds, fCRS, mapCRS);  
                         } else {  
                                 bounds = fBounds;  
                         }  
                         // BB umrechnen von Layer-CRS in Map-CRS  
   
                         // 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 updateCursorAndRepaintTimer() {          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();  
                         requestStartRendering();  
                 }  
         };  
   
         /**  
          * Listens to changes of the "background" {@link MapContext} and triggers  
          * repaints where needed.  
          */  
         private MapLayerListListener bgContextListener = new MapLayerListListener() {  
   
                 @Override  
                 public void layerAdded(final MapLayerListEvent event) {  
                         event.getLayer().addMapLayerListener(bgMapLayerListener);  
   
                         if (localContext.getLayers().length == 0  
                                         && bgContext.getLayers().length == 1) { // the first one and  
                                 // localContext is  
                                 // empty  
                                 if (!setMapArea(localContext.getAreaOfInterest()))  
                                         requestStartRendering();  
                                 return;  
                         }  
   
                         // We need to redraw, even in case that the mapArea didn't change  
                         // mapImageInvalid = true;  
                         // repaint();  
412                          requestStartRendering();                          requestStartRendering();
   
413                  }                  }
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 826  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 = null;
                         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.debug("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.debug("  maparea changed");
632                                                            paneResized = true;
633                                                    } else
634                                                            LOGGER.debug("  maparea NOT changed");
635                                            }
636                                    });
637                    resizeTimer.setRepeats(false);
638    
639                  // Testen, ob der Unterschied nur im minimalen Rundungsfehlerbereich                  this.addComponentListener(new ComponentAdapter() {
                 // liegt  
                 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)  
640    
641                                          && (candNew.getMinY() - tolY < mapArea.getMinY())                          private Rectangle oldVisibleRect;
                                         && (mapArea.getMinY() < candNew.getMinY() + tolY)  
                                         && (candNew.getMaxY() - tolY < mapArea.getMaxY())  
                                         && (mapArea.getMaxY() < candNew.getMaxY() + tolY)  
642    
643                          ) {                          @Override
644                                  // The two mapAreas only differ my 1/1000th.. ignore                          public void componentResized(final ComponentEvent e) {
645    
646                                  return false;                                  // Seems to be called twice with the same size..
647                                    if (oldVisibleRect != null
648                                                    && oldVisibleRect.equals(getVisibleRect())) {
649                                            LOGGER.debug("skipping resize.");
650                                            return;
651                                    }
652    
653                                    LOGGER.debug("resized: " + getVisibleRect());
654                                    resizeTimer.restart();
655                                    oldVisibleRect = getVisibleRect();
656                          }                          }
                 }  
657    
658                  oldMapArea = mapArea;                  });
659    
660                    /*
661                     * Setting up the repaintTimer. Not started automatically.
662                     */
663                    repaintTimer = new Timer(REPEATING_REPAINT_DELAY, new ActionListener() {
664    
665                  System.out.println("requested setting to \\t" + newMapArea);                          @Override
666                  this.mapArea = bestAllowedMapArea(newMapArea);                          public void actionPerformed(final ActionEvent e) {
667                  System.out.println("set to \\t\\ŧ" + newMapArea);                                  if ((!localExecuter.isRunning())
668                                                    && (bgExecuter != null && !bgExecuter.isRunning())) {
669                                            repaintTimer.stop();
670                                    } else {
671                                            updateFinalImage();
672                                            XMapPane.this.repaint(100);
673    
674                                    }
675                            }
676                    });
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    
                 if (localContext != null) {  
                         localContext.setAreaOfInterest(mapArea, localContext  
                                         .getCoordinateReferenceSystem());  
                 }  
                 if (bgContext != null) {  
                         bgContext.setAreaOfInterest(mapArea, localContext  
                                         .getCoordinateReferenceSystem());  
                 }  
                 resetTransforms();  
                 mapImageInvalid = true;  
                 mapAreaChanged = true;  
                 repaint();  
                 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                  updateCursorAndRepaintTimer();                          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 skrueger.geotools.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                  updateCursorAndRepaintTimer();                                  && !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 1242  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 1257  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          }          }
           
         final static Font waitFont = new Font("Arial", Font.BOLD, 30);  
   
         /**  
          * Paints some optinal stuff into the given {@link Graphics2D}. Usually called as the last paint on the mapImage.  
          */  
         private void addGadgets(Graphics2D graphics) {  
   
                 if (mapImage != null)  
                         graphics.drawImage(mapImage,  
                                         getBounds().width - mapImage.getWidth() - 10,  
                                         getBounds().height - mapImage.getHeight() - 10, this);  
1051    
1052                  // If still rendering, paint a gray shadow or so...          public MapContext getBgContext() {
1053                  if (bgExecuter != null && bgExecuter.isRunning()                  return bgContext;
                                 || localExecuter != null && localExecuter.isRunning()) {  
                         graphics.setColor(Color.BLACK);  
                           
                         graphics.setFont(waitFont);  
                         graphics.drawString("Wait...", 40, 70);  
                 }  
                   
1054          }          }
1055    
1056          /**          /**
1057           * Accumulates all three images           * Lazyly initializes a {@link BufferedImage} for the background renderer.
          *  
          * @return  
1058           */           */
1059          synchronized protected BufferedImage updateFinalImage() {          private Image getBgImage() {
   
                 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());  
                           
                         preFinalG.drawImage(getBgImage(), 0, 0,  
                                         getMapBackgroundColor(), null);  
                         // // Draw the local layers image  
                         preFinalG.drawImage(getLocalImage(), 0, 0, null);  
                         preFinalG.dispose();  
                 }  
                   
                 finalG.drawImage(getPreFinalImage(), imageOrigin.x, imageOrigin.y,  
                                 getMapBackgroundColor(), null);  
   
                 // System.out.println(new Date().getTime() - startTime +  
                 // "ms for update");  
1060                  //                  //
1061                  // if (finalImageFrame == null) {                  // if (bgImage == null) {
1062                  // finalImageFrame = new JFrame();                  // bgImage = createImage(getBounds().width, getBounds().height);
                 // finalImageFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);  
                 // SwingUtil.setRelativeFramePosition(finalImageFrame, 0, 0);  
1063                  // }                  // }
                 // finalImageFrame.setContentPane(new JLabel(new  
                 // ImageIcon(finalImage)));  
                 // finalImageFrame.pack();  
                 // finalImageFrame.setVisible(true);  
   
                 final int finalImageHeight = getFinalImage().getHeight(null);  
                 final int finalImageWidth = getFinalImage().getWidth(null);  
   
                 Rectangle painedArea = new Rectangle(imageOrigin.x, imageOrigin.y,  
                                 finalImageWidth, finalImageHeight);  
                 SwingUtil.clearAround(finalG, painedArea, getVisibleRect());  
   
                 addGadgets(finalG);  
 //              finalG.drawImage(getGadgetsImage(), 0, 0, null);  
   
                 finalG.dispose();  
1064    
1065                  return finalImage;                  return bgImage;
1066          }          }
1067    
1068          /*          public MapContext getMapContext() {
1069           * Set alpha composite. For example, pass in 1.0f to have 100% opacity pass                  if (localContext == null) {
1070           * in 0.25f to have 25% opacity.                          setLocalContext(new DefaultMapContext());
1071           */                  }
1072          private AlphaComposite makeComposite(float alpha) {                  return localContext;
                 int type = AlphaComposite.SRC_OVER;  
                 return (AlphaComposite.getInstance(type, alpha));  
1073          }          }
1074    
1075          private Image getFinalImage() {          private BufferedImage getFinalImage() {
1076                    //
1077                  if (finalImage == null) {                  if (finalImage == null) {
1078                          finalImage = null;                          // Rectangle curPaintArea = getVisibleRect();
1079                          Rectangle curPaintArea = getVisibleRect();                          finalImage = new BufferedImage(getVisibleRect().width,
1080                          finalImage = new BufferedImage(curPaintArea.width,                                          getVisibleRect().height, IMAGETYPE);
                                         curPaintArea.height, BufferedImage.TYPE_INT_RGB);  
1081    
1082                          requestStartRendering();                          requestStartRendering();
1083                  }                  }
1084                  return finalImage;                  return finalImage;
1085          }          }
           
         private Image getPreFinalImage() {  
                   
                 if (preFinalImage == null) {  
                         preFinalImage = null;  
                         Rectangle curPaintArea = getVisibleRect();  
1086    
1087                          preFinalImage = new BufferedImage(curPaintArea.width,          public RenderingHints getJava2dHints() {
1088                                          curPaintArea.height, BufferedImage.TYPE_INT_RGB);                  return java2dHints;
                           
                         requestStartRendering();  
                 }  
                 return preFinalImage;  
1089          }          }
1090    
1091          /**          /**
1092           * While dragging, the {@link #updateFinalImage()} method is translating the           * Lazyly initializes a {@link BufferedImage} for the background renderer.
          * cached images while setting it together.  
          **/  
         Point imageOrigin = new Point(0, 0);  
   
         /**  
          * Starts rendering on one or two threads  
1093           */           */
1094          private void startRendering() {          private BufferedImage getLocalImage() {
1095    
1096                  if (!isWellDefined())                  if (localImage == null) {
1097                          return;                          localImage = new BufferedImage(getVisibleRect().width,
1098                                            getVisibleRect().height, IMAGETYPE_withAlpha);
1099                    }
1100    
1101                  if (bgExecuter != null)                  return localImage;
1102                          // Stop all renderers          }
                         bgExecuter.cancelTask();  
1103    
1104                  if (localExecuter != null)          /**
1105                          localExecuter.cancelTask();           * Returns a copy of the mapArea
1106                  //           *
1107                  //           * @return
1108                  // LOGGER.debug("stopping any running renderes:");           */
1109                  // int count = 0;          public ReferencedEnvelope getMapArea() {
1110                  // while (bgExecuter.isRunning() || localExecuter.isRunning()) {                  if (mapArea == null) {
1111                  // LOGGER.debug("waiting for threads to stop");                          ReferencedEnvelope mapArea_ = null;
1112                  //                          try {
1113                  // bgExecuter.cancelTask();                                  mapArea_ = localContext.getLayerBounds();
1114                  // localExecuter.cancelTask();                          } catch (final IOException e) {
1115                  //                                  LOGGER.warn("localContext.getLayerBounds()", e);
1116                  // count++;                          }
                 //  
                 // try {  
                 // Thread.sleep(100);  
                 // } catch (InterruptedException e) {  
                 // LOGGER.error(e);  
                 // }  
                 //  
                 // if (count > 100) {  
                 // throw new RuntimeException(  
                 // "Unable to stop rendering thread for 10secs");  
                 // }  
                 // }  
                 // LOGGER.debug(" threads stopped after " + count / 10. + "secs");  
1117    
1118                  Rectangle curPaintArea = getVisibleRect();                          if (mapArea_ == null && bgContext != null) {
1119                                    try {
1120                                            mapArea_ = bgContext.getLayerBounds();
1121                                    } catch (final IOException e) {
1122                                            LOGGER.warn("bgContext.getLayerBounds()", e);
1123                                    }
1124                            }
1125    
1126                  // allow a single pixel margin at the right and bottom edges                          if (mapArea_ != null) {
1127                  curPaintArea.width -= 1;                                  mapArea = bestAllowedMapArea(mapArea_);
1128                  curPaintArea.height -= 1;                                  requestStartRendering();
1129                            }
1130                    }
1131    
1132                  labelCache.clear();                  if (mapArea == null)
1133                            return null;
1134    
1135                  /**                  // 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 new ReferencedEnvelope(mapArea, localContext
1145                   * We have to set new renderer                                  .getCoordinateReferenceSystem());
1146                   */          }
1147    
1148                  if (getBgContext() != null) {          /**
1149                          bgExecuter = new skrueger.geotools.RenderingExecutor(this);           * Returns the background {@link Color} of the map pane. If not set, the
1150                          LOGGER.debug("starting bg renderer:");           * methods looks for a parent component and will use its background color.
1151                          // /* System.out.println("rendering"); */           * If no parent component is available, WHITE is returned.
1152                          final GTRenderer createGTRenderer = GTUtil.createGTRenderer(           **/
1153                                          localContext, getRendererHints());          public Color getMapBackgroundColor() {
1154                          createGTRenderer.setJava2DHints(getJava2dHints());                  if (mapBackgroundColor == null){
1155                          bgExecuter.submit(getBgContext().getAreaOfInterest(), curPaintArea,                          if (getParent() != null) return getParent().getBackground();
1156                                          (Graphics2D) getBgImage().getGraphics(), createGTRenderer);                          else return Color.WHITE;
                 }  
   
                 if (getContext() != null) {  
                         localExecuter = new skrueger.geotools.RenderingExecutor(this);  
                         LOGGER.debug("starting local renderer:");  
                         final GTRenderer createGTRenderer = GTUtil.createGTRenderer(  
                                         localContext, getRendererHints());  
                         createGTRenderer.setJava2DHints(getJava2dHints());  
                         localExecuter.submit(getContext().getAreaOfInterest(),  
                                         curPaintArea, (Graphics2D) getLocalImage().getGraphics(),  
                                         createGTRenderer);  
1157                  }                  }
1158                    return mapBackgroundColor;
1159            }
1160    
1161                  updateCursorAndRepaintTimer();          /**
1162             * Get the BufferedImage to use as a flaoting icon in the lower right
1163                  // start regular repaints until all renderers are done.           * corner.
1164                  repainterTimer.setRepeats(true);           *
1165                  repainterTimer.restart();           * @return <code>null</code> if the feature is deactivated.
1166             */
1167            public BufferedImage getMapImage() {
1168                    return mapImage;
1169          }          }
1170    
1171          /**          /**
1172           * Lazyly initializes a {@link BufferedImage} for the background renderer.           * Returns the evelope of the viewable area. The JMapPane will never show
1173             * anything outside of this extend. If this has been set to
1174             * <code>null</code> via {@link #setMaxExtend(Envelope)}, it tries to return
1175             * quickly the context's bounds. It it takes to long to determine the
1176             * context bounds, <code>null</code> is returned.
1177             *
1178             * @param maxExtend
1179             *            <code>null</code> to not have this restriction.
1180           */           */
         private BufferedImage getBgImage() {  
1181    
1182                  if (bgImage == null) {          public Envelope getMaxExtend() {
1183                          LOGGER.debug("creating a new background image");                  if (maxExtend == null) {
1184                            // Commented-out because it takes soo much time!
1185                            //
1186                            // long start = System.currentTimeMillis();
1187                            // final ReferencedEnvelope layerBounds = GTUtil
1188                            // .getVisibleLayoutBounds(localContext);
1189                            //                      
1190                            // LOGGER.info(
1191                            // (System.currentTimeMillis()-start)+"m to get maxExtend");
1192                            //                      
1193                            // if (layerBounds == null) {
1194                            // // TODO Last fallback could be the CRS valid area
1195                            // return null;
1196                            // }
1197                            //
1198                            // // Kartenbereich um 10% vergroessern
1199                            // return JTSUtil.fixAspectRatio(getVisibleRect(), JTSUtil
1200                            // .expandEnvelope(layerBounds, 0.1), true);
1201                    }
1202                    return maxExtend;
1203            }
1204    
1205                          Rectangle curPaintArea = getVisibleRect();          /**
1206                          // allow a single pixel margin at the right and bottom edges           * Retuns the maximum allowed zoom scale. This is the smaller number value
1207                          curPaintArea.width -= 1;           * of the two. Defaults to {@link Double}.MIN_VALUE
1208                          curPaintArea.height -= 1;           *
1209             * @author <a href="mailto:[email protected]">Stefan Alfons
1210             *         Kr&uuml;ger</a>
1211             */
1212            public Double getMaxZoomScale() {
1213                    return maxZoomScale;
1214            }
1215    
1216                          bgImage = new BufferedImage(curPaintArea.width + 1,          /**
1217                                          curPaintArea.height + 1, BufferedImage.TYPE_INT_ARGB);           * Retuns the minimum allowed zoom scale. This is the bigger number value of
1218                  }           * the two. Defaults to {@link Double}.MAX_VALUE
1219             *
1220             * @author <a href="mailto:[email protected]">Stefan Alfons
1221             *         Kr&uuml;ger</a>
1222             */
1223            public Double getMinZoomScale() {
1224                    return minZoomScale;
1225            }
1226    
1227                  return bgImage;          private Image getPreFinalImage() {
1228                    // if (preFinalImage == null) {
1229                    //                      
1230                    // // Rectangle curPaintArea = getVisibleRect();
1231                    // // preFinalImage = new BufferedImage(curPaintArea.width,
1232                    // // curPaintArea.height, BufferedImage.TYPE_INT_RGB);
1233                    //                      
1234                    // preFinalImage = createImage(getBounds().width, getBounds().height);
1235                    //
1236                    // requestStartRendering();
1237                    // }
1238                    return preFinalImage;
1239          }          }
1240    
1241          /** An (transparent) image to paint over the map in the lower right corner **/          public Map<Object, Object> getRendererHints() {
1242          private BufferedImage mapImage = null;                  // Clear label cache
1243                    labelCache.clear();
1244                    rendererHints.put(StreamingRenderer.LABEL_CACHE_KEY, labelCache);
1245    
1246          private boolean acceptsRepaintCalls = true;                  return rendererHints;
1247            }
1248    
1249          /**          /**
1250           * Get the BufferedImage to use as a flaoting icon in the lower right           * Liefert eine affine Transformation, um von den Fenster-Koordinaten in die
1251           * corner.           * Karten-Koordinaten (Lat/Lon) umzurechnen.
1252           *           *
1253           * @return <code>null</code> if the feature is deactivated.           * @return eine Kopie der aktuellen Transformation; <code>null</code> wenn
1254             *         noch keine Karte angezeigt wird
1255           */           */
1256          public BufferedImage getMapImage() {          public AffineTransform getScreenToWorld() {
1257                  return mapImage;                  if (screenToWorld == null)
1258                            resetTransforms();
1259                    // nur Kopie der Transformation zurueckgeben!
1260                    if (screenToWorld == null)
1261                            return null;
1262                    return new AffineTransform(screenToWorld);
1263            }
1264    
1265            public int getState() {
1266                    return state;
1267          }          }
1268    
1269          /**          /**
1270           * Set the BufferedImage to use as a flaoting icon in the lower right corner           * Liefert den statisch eingestellten Cursor, der unabhaengig von der
1271             * eingestellten MapPane-Aktion (Zoom, Auswahl, ...) verwendet wird.
1272           *           *
1273           * @param mapImageIcon           * @return {@code null}, wenn kein statischer Cursor verwendet, sondern der
1274           *            <code>null</code> is allowed and deactivates this icon.           *         Cursor automatisch je nach MapPane-Aktion eingestellt wird.
1275           */           */
1276          public void setMapImage(BufferedImage mapImage) {          public Cursor getStaticCursor() {
1277                  this.mapImage = mapImage;                  return this.staticCursor;
1278  //              gadgetsImage = null;          }
1279    
1280            public AffineTransform getWorldToScreenTransform() {
1281                    if (worldToScreen == null) {
1282                            resetTransforms();
1283                    }
1284                    // nur Kopie der Transformation zurueckgeben!
1285                    return new AffineTransform(worldToScreen);
1286          }          }
1287    
1288          /**          /**
1289           * Lazyly initializes a {@link BufferedImage} for the background renderer.           * A flag indicating if dispose() has already been called. If true, then
1290             * further use of this {@link SelectableXMapPane} is undefined.
1291           */           */
1292          private BufferedImage getLocalImage() {          private boolean isDisposed() {
1293                    return disposed;
1294                  if (localImage == null) {          }
                         LOGGER.debug("creating a new local image");  
1295    
1296                          Rectangle curPaintArea = getVisibleRect();          /**
1297                          // allow a single pixel margin at the right and bottom edges           * Returns whether a layer is regarded or ignored on {@link #SELECT_TOP},
1298                          curPaintArea.width -= 1;           * {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP} actions. Returns
1299                          curPaintArea.height -= 1;           * <code>true</code> if the selectability has not been defined.
1300             *
1301             * @param layer
1302             *            a layer
1303             */
1304            public boolean isMapLayerSelectable(final MapLayer layer) {
1305                    final Boolean selectable = mapLayerSelectable.get(layer);
1306                    return selectable == null ? true : selectable;
1307            }
1308    
1309                          localImage = new BufferedImage(curPaintArea.width + 1,          /**
1310                                          curPaintArea.height + 1, BufferedImage.TYPE_INT_ARGB);           * Return <code>true</code> if a CRS and a {@link #mapArea} are set and the
1311             * {@link XMapPane} is visible and has bounds set.
1312             */
1313            public boolean isWellDefined() {
1314                    try {
1315                            if (getMapContext() == null)
1316                                    return false;
1317                            if (getMapContext().getLayerCount() <= 0)
1318                                    return false;
1319                            if (getVisibleRect().getWidth() == 0)
1320                                    return false;
1321                            if (getVisibleRect().getHeight() == 0)
1322                                    return false;
1323                            // if (getMapArea() == null)
1324                            // return false;
1325                    } catch (final Exception e) {
1326                            return false;
1327                  }                  }
1328                    return true;
                 return localImage;  
1329          }          }
 //  
 //      /**  
 //       * Lazyly initializes a {@link BufferedImage} for the background renderer.  
 //       */  
 //      private BufferedImage getGadgetsImage() {  
 //  
 //              if (gadgetsImage == null) {  
 //                      LOGGER.debug("creating a new gadgets image");  
 //  
 //                      Rectangle curPaintArea = getVisibleRect();  
 //                      // allow a single pixel margin at the right and bottom edges  
 //                      curPaintArea.width -= 1;  
 //                      curPaintArea.height -= 1;  
 //  
 //                      gadgetsImage = new BufferedImage(curPaintArea.width + 1,  
 //                                      curPaintArea.height + 1, BufferedImage.TYPE_INT_ARGB);  
 //  
 //                      if (mapImage != null)  
 //                              gadgetsImage.getGraphics().drawImage(mapImage,  
 //                                              curPaintArea.width - mapImage.getWidth() - 10,  
 //                                              curPaintArea.height - mapImage.getHeight() - 10, this);  
 //                        
 //              }  
 //  
 //              return gadgetsImage;  
 //      }  
1330    
1331          /**          /**
1332           * Called by the {@linkplain XMapPane.RenderingTask} when rendering has been           * Called from the listeners while the mouse is dragging, this method either
1333           * completed Publishes a {@linkplain MapPaneEvent} of type {@code           * paints a translated (moved/panned) version of the image, or a rectangle.
          * MapPaneEvent.Type.RENDERING_STOPPED} to listeners.  
1334           *           *
1335           * @see MapPaneListener#onRenderingStopped(org.geotools.swing.event.MapPaneEvent)           * @param startPos
1336             *            in screen coordinates
1337             * @param lastPos
1338             *            in screen coordinates
1339             * @param event
1340             *            the {@link MouseEvent} to read the mouse buttons from
1341           */           */
1342          public void onRenderingCompleted() {          public void mouseDragged(final Point startPos, final Point lastPos,
1343                  System.out.println("onRenderingCompleted");                          final MouseEvent event) {
1344    
1345                  updateFinalImage();                  if ((getState() == XMapPane.PAN)
1346                                    || ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0)) {
1347    
1348                  repaint();                          // Panning needs a panning coursor
1349                            if (getCursor() != SwingUtil.PANNING_CURSOR) {
1350                                    setCursor(SwingUtil.PANNING_CURSOR);
1351    
1352                                    // While panning, we deactivate the rendering. So the tasks are
1353                                    // ready to start when the panning is finished.
1354                                    if (bgExecuter != null && bgExecuter.isRunning())
1355                                            bgExecuter.cancelTask();
1356                                    if (localExecuter.isRunning())
1357                                            localExecuter.cancelTask();
1358                            }
1359    
1360                            if (lastPos.x > 0 && lastPos.y > 0) {
1361                                    final int dx = event.getX() - lastPos.x;
1362                                    final int dy = event.getY() - lastPos.y;
1363    
1364                                    // TODO Stop dragging when the drag would not be valid...
1365                                    // boolean dragValid = true;
1366                                    // // check if this panning results in a valid mapArea
1367                                    // {
1368                                    // Rectangle winBounds = xMapPane.getBounds();
1369                                    // winBounds.translate(xMapPane.imageOrigin.x,
1370                                    // -xMapPane.imageOrigin.y);
1371                                    // Envelope newMapAreaBefore = xMapPane.tranformWindowToGeo(
1372                                    // winBounds.x, winBounds.y, winBounds.x
1373                                    // + winBounds.width, winBounds.y
1374                                    // + winBounds.height);
1375                                    //                                      
1376                                    //
1377                                    // winBounds = xMapPane.getBounds();
1378                                    // Point testIng = new Point(xMapPane.imageOrigin);
1379                                    // testIng.translate(dx, dy);
1380                                    // winBounds.translate(testIng.x, -testIng.y);
1381                                    // Envelope newMapAreaAfter = xMapPane.tranformWindowToGeo(
1382                                    // winBounds.x, winBounds.y, winBounds.x
1383                                    // + winBounds.width, winBounds.y
1384                                    // + winBounds.height);
1385                                    //
1386                                    // // If the last drag doesn't change the MapArea anymore cancel
1387                                    // it.
1388                                    // if (xMapPane.bestAllowedMapArea(newMapAreaAfter).equals(
1389                                    // xMapPane.bestAllowedMapArea(newMapAreaBefore))){
1390                                    // dragValid = false;
1391                                    // return;
1392                                    // }
1393                                    // }
1394    
1395                                    imageOrigin.translate(dx, dy);
1396                                    updateFinalImage();
1397                                    repaint();
1398                            }
1399    
1400                    } else if ((getState() == XMapPane.ZOOM_IN)
1401                                    || (getState() == XMapPane.ZOOM_OUT)
1402                                    || (getState() == XMapPane.SELECT_ALL)
1403                                    || (getState() == XMapPane.SELECT_TOP)) {
1404    
1405                            // Draws a rectangle
1406                            final Graphics graphics = getGraphics();
1407                            drawRectangle(graphics, startPos, event.getPoint());
1408                            if ((lastPos.x > 0) && (lastPos.y > 0))
1409                                    drawRectangle(graphics, startPos, lastPos);
1410                            graphics.dispose();
1411                    }
1412          }          }
1413    
1414          /**          /**
1415           * 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)  
1416           */           */
1417          public void onRenderingCancelled() {          public void onRenderingCancelled() {
1418                  LOGGER.debug("Rendering cancelled");                  LOGGER.debug("Rendering cancelled");
1419                    repaintTimer.stop();
1420            }
1421    
1422            /**
1423             * Called by the {@link RenderingExecutor} when rendering has been
1424             * completed.
1425             */
1426            public void onRenderingCompleted() {
1427                    LOGGER.debug("complete");
1428    
1429                    repaintTimer.stop();
1430    
1431                    // We "forget" about an exception every time we complete a rendering
1432                    // thread successfully
1433                    if (renderingErrors.size() > 0)
1434                            renderingErrors.remove(0);
1435    
1436                    updateFinalImage();
1437                    repaint();
1438          }          }
1439    
1440          /**          /**
# Line 1621  public class XMapPane extends JPanel imp Line 1447  public class XMapPane extends JPanel imp
1447           *           *
1448           * @see MapPaneListener#onRenderingStopped(org.geotools.swing.event.MapPaneEvent)           * @see MapPaneListener#onRenderingStopped(org.geotools.swing.event.MapPaneEvent)
1449           */           */
1450          public void onRenderingFailed(Exception renderingError) {          public void onRenderingFailed(final Exception renderingError) {
1451                  // MapPaneEvent ev = new MapPaneEvent(this,  
1452                  // MapPaneEvent.Type.RENDERING_STOPPED);                  // Store the exceptions so we can show it to the user:
1453                  // publishEvent(ev);                  if (!(renderingError instanceof java.lang.IllegalArgumentException && renderingError
1454                                    .getMessage().equals(
1455                                                    "Argument \"sourceCRS\" should not be null.")))
1456                            this.renderingErrors.add(renderingError);
1457                    if (renderingErrors.size() > 3)
1458                            renderingErrors.remove(0);
1459    
1460                    repaintTimer.stop();
1461    
1462                  LOGGER.warn("Rendering failed", renderingError);                  LOGGER.warn("Rendering failed", renderingError);
1463                  updateFinalImage();                  updateFinalImage();
1464                  repaint();                  repaint();
1465    
1466          }          }
1467    
1468            @Override
1469            protected void paintComponent(final Graphics g) {
1470    
1471                    // Maybe update the cursor and maybe stop the repainting timer
1472                    updateCursor();
1473    
1474                    if (!acceptsRepaintCalls)
1475                            return;
1476    
1477                    if (!isWellDefined())
1478                            return;
1479    
1480                    // super.paintComponent(g); // candidate for removal
1481    
1482                    boolean paintedSomething = false;
1483    
1484                    if (mapImageInvalid) { /* if the map changed then redraw */
1485    
1486                            mapImageInvalid = false; // Reset for next round
1487    
1488                            // If the new mapArea and the oldMapArea intersect, we can draw some
1489                            // quick scaled preview to make the user feel that something is
1490                            // happening.
1491                            if (mapAreaChanged && oldMapArea != null
1492                                            && getMapArea().intersects(oldMapArea)
1493                                            & !getMapArea().equals(oldMapArea) && !paneResized) {
1494    
1495                                    mapAreaChanged = false;
1496    
1497                                    if (getMapArea().covers(oldMapArea)) {
1498                                            quickPreviewHint = ZOOM_OUT;
1499                                            paintedSomething = drawScaledPreviewImage_Zoom((Graphics2D) g);
1500                                    } else if (oldMapArea.covers(getMapArea())) {
1501                                            quickPreviewHint = ZOOM_IN;
1502                                            paintedSomething = drawScaledPreviewImage_Zoom((Graphics2D) g);
1503                                    }
1504                            }
1505                    }
1506    
1507                    if (!paintedSomething) {
1508    
1509                            g.drawImage(getFinalImage(), 0, 0, null);
1510    
1511                            g.dispose(); // cand. for removal
1512    
1513                            paintedSomething = true; // cand. for removal
1514                    }
1515    
1516            }
1517    
1518          /**          /**
1519           * Called when a rendering request has been rejected. This will be common,           * Heavily works on releasing all resources related to the four
1520           * 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>
1521           * base implementation does nothing. It is provided for sub-classes to           * In November 2009 i had some memory leaking problems with {@link XMapPane}
1522           * override if required.           * . The resources of the buffered images were never released. It seem to be
1523             * important to call the GC right after flushing the images.<br>
1524             * Hence this method may take a while, because it calls the GC up to four
1525             * times.
1526           */           */
1527          public void onRenderingRejected() {          private void disposeImages() {
                 LOGGER.warn("Rendering rejected");  
                 repaint();  
         }  
1528    
1529          @Override                  // System.out.println("vorher = "
1530          public void propertyChange(final PropertyChangeEvent evt) {                  // + new MbDecimalFormatter().format(LangUtil.gcTotal()));
1531                  final String prop = evt.getPropertyName();                  // bi.flush();
1532                    // return bi = null;
1533                    // System.out.println("nacher = "
1534                    // + new MbDecimalFormatter().format(LangUtil.gcTotal()));
1535                    //
1536                    // System.out.println("\n");
1537    
1538                  if (prop.equalsIgnoreCase("crs")) {                  if (preFinalImage != null) {
1539                          localContext.setAreaOfInterest(localContext.getAreaOfInterest(),                          preFinalImage.flush();
1540                                          (CoordinateReferenceSystem) evt.getNewValue());                          preFinalImage = null;
1541                            LangUtil.gc();
1542                    }
1543                    if (finalImage != null) {
1544                            finalImage.flush();
1545                            finalImage = null;
1546                            LangUtil.gc();
1547                    }
1548                    if (localImage != null) {
1549                            localImage.flush();
1550                            localImage = null;
1551                            LangUtil.gc();
1552                    }
1553                    if (bgImage != null) {
1554                            bgImage.flush();
1555                            bgImage = null;
1556                            LangUtil.gc();
1557                  }                  }
1558    
1559          }          }
1560    
         // xulu.sn  
1561          /**          /**
1562           * Korrigiert den {@link Envelope} aka {@code mapArea} auf die beste           * Performs a {@value #PAN} action. During panning, the displacement is
1563           * erlaubte Flaeche damit die Massstabsbeschaenkungen noch eingehalten           * stored in {@link #imageOrigin} object. Calling {@link #performPan()} will
1564           * 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>  
1565           */           */
1566          public Envelope bestAllowedMapArea(Envelope env) {          public void performPan() {
                 if (getWidth() == 0)  
                         return env;  
                 if (env == null)  
                         return null;  
1567    
1568                  Envelope newArea = null;                  Rectangle winBounds = getVisibleRect();
1569    
1570                  /**                  winBounds.translate(-imageOrigin.x, -imageOrigin.y);
1571                   * Correct the aspect Ratio before we check the rest. Otherwise we might                  final Envelope newMapArea = tranformWindowToGeo(winBounds.x,
1572                   * easily fail. We allow to grow here, because we don't check against                                  winBounds.y, winBounds.x + winBounds.width, winBounds.y
1573                   * the maxExtend                                                  + winBounds.height);
                  */  
                 Rectangle curPaintArea = getVisibleRect();  
1574    
1575                  env = JTSUtil.fixAspectRatio(curPaintArea, env, true);                  imageOrigin.x = 0;
1576                    imageOrigin.y = 0;
1577    
1578                  final double scale = env.getWidth() / getWidth();                  if (!setMapArea(newMapArea)) {
1579                  final double centerX = env.getMinX() + env.getWidth() / 2.;                          /**
1580                  final double centerY = env.getMinY() + env.getHeight() / 2.;                           * If setMapArea returns true, the finalImage is updated anyways.
1581                  double newWidth2 = 0;                           * This if-case exists to ensure that we repaint a correct image
1582                  double newHeight2 = 0;                           * even if the new panning area has been denied.
1583                  if (scale < getMaxZoomScale()) {                           */
1584                          // ****************************************************************************                          updateFinalImage();
1585                          // 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;  
1586                  }                  }
1587    
1588                  if (newArea == null) {                  if (getCursor() == SwingUtil.PANNING_CURSOR)
1589                            setCursor(SwingUtil.PAN_CURSOR);
1590            }
1591    
1592                          final Coordinate ll = new Coordinate(centerX - newWidth2, centerY          /**
1593                                          - newHeight2);           * Entfernt einen Listener von der Map.
1594                          final Coordinate ur = new Coordinate(centerX + newWidth2, centerY           *
1595                                          + newHeight2);           * @param l
1596             *            zu entfernender Listener
1597             */
1598            public void removeMapPaneListener(final JMapPaneListener l) {
1599                    mapPaneListeners.remove(l);
1600            }
1601    
1602                          newArea = new Envelope(ll, ur);          /**
1603             * Cancels all running renderers and sets the flag to start new ones. <br>
1604             *
1605             * @see #startRenderThreadsTimer
1606             */
1607            private void requestStartRendering() {
1608                    if (bgExecuter != null)
1609                            bgExecuter.cancelTask();
1610    
1611                    localExecuter.cancelTask();
1612    
1613                    mapImageInvalid = true;
1614                    if (paneResized) {
1615                            paneResized = false;
1616                            disposeImages();
1617                  }                  }
1618                    requestStartRendering = true;
1619    
1620                  Envelope maxAllowedExtend = getMaxExtend();          }
                 while (maxAllowedExtend != null && !maxAllowedExtend.contains(newArea)  
                                 && newArea != null && !newArea.isNull()  
                                 && !Double.isNaN(newArea.getMinX())  
                                 && !Double.isNaN(newArea.getMaxX())  
                                 && !Double.isNaN(newArea.getMinY())  
                                 && !Double.isNaN(newArea.getMaxY())) {  
                         /*  
                          * If a maxExtend is set, we have to honour that...  
                          */  
1621    
1622                          // Exceeds top? Move down and maybe cut          /**
1623                          if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {           * Calculate the affine transforms used to convert between world and pixel
1624                                  double divY = newArea.getMaxY() - maxAllowedExtend.getMaxY();           * coordinates. The calculations here are very basic and assume a cartesian
1625                                  // LOGGER.debug("Moving area down by " + divY);           * reference system.
1626             * <p>
1627             * Tne transform is calculated such that {@code envelope} will be centred in
1628             * the display
1629             *
1630             * @param envelope
1631             *            the current map extent (world coordinates)
1632             * @param paintArea
1633             *            the current map pane extent (screen units)
1634             */
1635            private void resetTransforms() {
1636                    ReferencedEnvelope refMapEnv = new ReferencedEnvelope(mapArea,
1637                                    getMapContext().getCoordinateReferenceSystem());
1638    
1639                                  newArea = new Envelope(new Coordinate(newArea.getMinX(),                  // System.out
1640                                                  newArea.getMinY() - divY), new Coordinate(newArea                  // .println("paintArea in resetTeansofrms = " + getVisibleRect());
1641                                                  .getMaxX(), newArea.getMaxY() - divY));                  if (!isWellDefined())
1642                            return;
1643    
1644                                  if (newArea.getMinY() < maxAllowedExtend.getMinY()) {                  worldToScreen = RendererUtilities.worldToScreenTransform(refMapEnv,
1645                                          // LOGGER.debug("Now it exeeds the bottom border.. cut!");                                  getVisibleRect());
                                         // And cut the bottom if it moved out of the area  
                                         newArea = new Envelope(new Coordinate(newArea.getMinX(),  
                                                         maxAllowedExtend.getMinY()), new Coordinate(newArea  
                                                         .getMaxX(), newArea.getMaxY()));  
1646    
1647                                          // LOGGER.debug("and fix aspect ratio");                  try {
1648                            screenToWorld = worldToScreen.createInverse();
1649    
1650                                          newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,                  } catch (NoninvertibleTransformException ex) {
1651                                                          false);                          LOGGER
1652                                  }                                          .error("can't invert worldToScreen to get screenToWorld!",
1653                                                            ex);
1654                    }
1655            }
1656    
1657            public void setBgContext(final MapContext context) {
1658    
1659                    // Remove the default listener from the old context
1660                    if (this.bgContext != null) {
1661                            this.bgContext.removeMapLayerListListener(bgContextListener);
1662    
1663                            // adding listener to all layers
1664                            for (final MapLayer mapLayer : bgContext.getLayers()) {
1665                                    mapLayer.removeMapLayerListener(bgMapLayerListener);
1666                          }                          }
1667                    }
1668    
1669                          // Exceeds bottom? Move up and maybe cut                  this.bgContext = context;
                         if (newArea.getMinY() < maxAllowedExtend.getMinY()) {  
                                 double divY = newArea.getMinY() - maxAllowedExtend.getMinY();  
                                 // LOGGER.debug("Moving area up by " + divY);  
1670    
1671                                  newArea = new Envelope(new Coordinate(newArea.getMinX(),                  if (context != null) {
1672                                                  newArea.getMinY() - divY), new Coordinate(newArea                          // setMapArea(bgContext.getAreaOfInterest());
                                                 .getMaxX(), newArea.getMaxY() - divY));  
1673    
1674                                  if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {                          this.bgContext.addMapLayerListListener(bgContextListener);
                                         // LOGGER.debug("Now it exeeds the top border.. cut!");  
                                         // And cut the bottom if it moved out of the area  
                                         newArea = new Envelope(new Coordinate(newArea.getMinX(),  
                                                         newArea.getMinY()), new Coordinate(newArea  
                                                         .getMaxX(), maxAllowedExtend.getMaxY()));  
1675    
1676                                          // LOGGER.debug("and fix aspect ratio");                          // adding listener to all layers
1677                            for (final MapLayer mapLayer : bgContext.getLayers()) {
1678                                    mapLayer.addMapLayerListener(bgMapLayerListener);
1679                            }
1680                    }
1681    
1682                                          newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,                  requestStartRendering();
1683                                                          false);          }
1684                                  }  
1685            public void setJava2dHints(final RenderingHints java2dHints) {
1686                    this.java2dHints = java2dHints;
1687            }
1688    
1689            public void setLocalContext(final MapContext context) {
1690                    // Remove the default listener from the old context
1691                    if (this.localContext != null) {
1692                            this.localContext.removeMapLayerListListener(localContextListener);
1693    
1694                            // adding listener to all layers
1695                            for (final MapLayer mapLayer : localContext.getLayers()) {
1696                                    mapLayer.removeMapLayerListener(localMapLayerListener);
1697                          }                          }
1698                    }
1699    
1700                          // Exceeds to the right? move and maybe cut                  this.localContext = context;
                         if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {  
1701    
1702                                  // Move left..                  if (context != null) {
                                 double divX = newArea.getMaxX() - maxAllowedExtend.getMaxX();  
                                 // LOGGER.debug("Moving area left by " + divX);  
1703    
1704                                  newArea = new Envelope(new Coordinate(newArea.getMinX() - divX,                          // setMapArea(localContext.getAreaOfInterest());
                                                 newArea.getMinY()), new Coordinate(newArea.getMaxX()  
                                                 - divX, newArea.getMaxY()));  
1705    
1706                                  if (newArea.getMinX() < maxAllowedExtend.getMinX()) {                          localRenderer.setContext(localContext);
                                         // LOGGER.debug("Now it exeeds the left border.. cut!");  
                                         // And cut the left if it moved out of the area  
                                         newArea = new Envelope(new Coordinate(maxAllowedExtend  
                                                         .getMinX(), newArea.getMinY()), new Coordinate(  
                                                         newArea.getMaxX(), newArea.getMaxY()));  
1707    
1708                                          // LOGGER.debug("and fix aspect ratio");                          this.localContext.addMapLayerListListener(localContextListener);
1709    
1710                                          newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,                          // adding listener to all layers
1711                                                          false);                          for (final MapLayer mapLayer : localContext.getLayers()) {
1712                                  }                                  mapLayer.addMapLayerListener(localMapLayerListener);
1713                          }                          }
1714                    }
1715    
1716                          // Exceeds to the left? move and maybe cut                  requestStartRendering();
                         if (newArea.getMinX() < maxAllowedExtend.getMinX()) {  
1717    
1718                                  // Move right..          }
                                 double divX = newArea.getMinX() - maxAllowedExtend.getMinX();  
                                 // LOGGER.debug("Moving area right by " + divX);  
1719    
1720                                  newArea = new Envelope(new Coordinate(newArea.getMinX() - divX,          public boolean setMapArea(final Envelope newMapArea) {
1721                                                  newArea.getMinY()), new Coordinate(newArea.getMaxX()                  if (newMapArea == null)
1722                                                  - divX, newArea.getMaxY()));                          return false;
1723                    if (getMapContext()
1724                                    .getCoordinateReferenceSystem() == null) return false;
1725                    return setMapArea(new ReferencedEnvelope(newMapArea, getMapContext()
1726                                    .getCoordinateReferenceSystem()));
1727            }
1728    
1729                                  if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {          /**
1730                                          // LOGGER.debug("Now it exeeds the right border.. cut!");           * @param newMapArea
1731                                          // And cut the left if it moved out of the area           * @return <code>true</code> if the mapArea has been changed and a repaint
1732                                          newArea = new Envelope(new Coordinate(newArea.getMinX(),           *         has been triggered.
1733                                                          newArea.getMinY()), new Coordinate(maxAllowedExtend           */
1734                                                          .getMaxX(), newArea.getMaxY()));          public boolean setMapArea(final ReferencedEnvelope newMapArea) {
1735    
1736                                          // LOGGER.debug("and fix aspect ratio");                  if (newMapArea == null
1737                                    || bestAllowedMapArea(newMapArea).equals(mapArea)) {
1738                            // No change.. no need to repaint
1739                            return false;
1740                    }
1741    
1742                                          newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,                  // Testing, whether NaN or Infinity are used in the newMapArea
1743                                                          false);                  if (newMapArea.isNull() || Double.isInfinite(newMapArea.getMaxX())
1744                                  }                                  || Double.isInfinite(newMapArea.getMaxY())
1745                                    || Double.isInfinite(newMapArea.getMinX())
1746                                    || Double.isInfinite(newMapArea.getMinY())) {
1747                            // No change.. ugly new values
1748                            LOGGER.warn("setMapArea has been called with newArea = "
1749                                            + newMapArea);
1750                            return false;
1751                    }
1752    
1753                    final Envelope candNew = bestAllowedMapArea(newMapArea);
1754    
1755                    // Testing, whether the difference if just minimal
1756                    if (mapArea != null) {
1757                            final double tolX = mapArea.getWidth() / 1000.;
1758                            final double tolY = mapArea.getHeight() / 1000.;
1759                            if ((candNew.getMinX() - tolX < mapArea.getMinX())
1760                                            && (mapArea.getMinX() < candNew.getMinX() + tolX)
1761                                            && (candNew.getMaxX() - tolX < mapArea.getMaxX())
1762                                            && (mapArea.getMaxX() < candNew.getMaxX() + tolX)
1763    
1764                                            && (candNew.getMinY() - tolY < mapArea.getMinY())
1765                                            && (mapArea.getMinY() < candNew.getMinY() + tolY)
1766                                            && (candNew.getMaxY() - tolY < mapArea.getMaxY())
1767                                            && (mapArea.getMaxY() < candNew.getMaxY() + tolY)
1768    
1769                            ) {
1770                                    // The two mapAreas only differ my 1/1000th.. ignore
1771    
1772                                    return false;
1773                          }                          }
1774                    }
1775    
1776                    // New map are is accepted:
1777                    oldMapArea = mapArea;
1778                    mapArea = candNew;
1779                    resetTransforms();
1780    
1781                    if (localContext != null) {
1782                            localContext.setAreaOfInterest(mapArea, localContext
1783                                            .getCoordinateReferenceSystem());
1784                    }
1785                    if (bgContext != null) {
1786                            bgContext.setAreaOfInterest(mapArea, localContext
1787                                            .getCoordinateReferenceSystem());
1788                  }                  }
1789    
1790                  return newArea;                  mapAreaChanged = true;
1791    
1792                    repaint(200); // Do not remove it!
1793    
1794                    requestStartRendering();
1795    
1796                    return true;
1797          }          }
1798    
1799          /**          /**
1800           * 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  
1801           *           *
1802           * @author <a href="mailto:[email protected]">Stefan Alfons           * @param if <code>null</code>, white is used.
          *         Kr&uuml;ger</a>  
1803           */           */
1804          public Double getMinZoomScale() {          public void setMapBackgroundColor(Color bgColor) {
1805                  return minZoomScale;                  this.mapBackgroundColor = bgColor;
1806          }          }
1807    
1808          /**          /**
1809           * 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  
1810           *           *
1811           * @author <a href="mailto:[email protected]">Stefan Alfons           * @param mapImageIcon
1812           *         Kr&uuml;ger</a>           *            <code>null</code> is allowed and deactivates this icon.
1813           */           */
1814          public Double getMaxZoomScale() {          public void setMapImage(final BufferedImage mapImage) {
1815                  return maxZoomScale;                  this.mapImage = mapImage;
1816            }
1817    
1818            /**
1819             * Sets whether a layer is regarded or ignored on {@link #SELECT_TOP},
1820             * {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP} actions.
1821             *
1822             * @param layer
1823             *            a layer
1824             * @param selectable
1825             *            if {@code false} the layer is ignored during the upper
1826             *            mentioned actions. If <code>null</code>, the default (true)
1827             *            will be used.
1828             */
1829            public void setMapLayerSelectable(final MapLayer layer,
1830                            final Boolean selectable) {
1831                    if (selectable == null)
1832                            mapLayerSelectable.remove(layer);
1833                    else
1834                            mapLayerSelectable.put(layer, selectable);
1835            }
1836    
1837            /**
1838             * Defines an evelope of the viwable area. The JMapPane will never show
1839             * anything outside of this extend.
1840             *
1841             * @param maxExtend
1842             *            <code>null</code> to not have this restriction.
1843             */
1844            public void setMaxExtend(final Envelope maxExtend) {
1845                    this.maxExtend = maxExtend;
1846          }          }
1847    
1848          /**          /**
# Line 1862  public class XMapPane extends JPanel imp Line 1858  public class XMapPane extends JPanel imp
1858                                  : maxZoomScale;                                  : maxZoomScale;
1859          }          }
1860    
1861            // /** Stored the time used for the last real rendering in ms. **/
1862            // private long lastRenderingDuration = Long.MAX_VALUE;
1863    
1864          /**          /**
1865           * Set the minimum (nearest) allowed zoom scale. This is the bigger number           * Set the minimum (nearest) allowed zoom scale. This is the bigger number
1866           * 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 1877  public class XMapPane extends JPanel imp Line 1876  public class XMapPane extends JPanel imp
1876          }          }
1877    
1878          /**          /**
1879           * Defines an evelope of the viwable area. The JMapPane will never show           * If <code>true</code>, allow the {@link XMapPane} to process #repaint()
1880           * anything outside of this extend.           * requests. Otherwise the map will not paint anything and not start any
1881           *           * rendering {@link Thread}s.
          * @param maxExtend  
          *            <code>null</code> to not have this restriction.  
1882           */           */
1883          public void setMaxExtend(Envelope maxExtend) {          public void setPainting(final boolean b) {
1884                  this.maxExtend = maxExtend;                  acceptsRepaintCalls = b;
1885                    if (acceptsRepaintCalls == true)
1886                            repaint();
1887            }
1888    
1889            private void setRendererHints(final Map<Object, Object> rendererHints) {
1890                    if (rendererHints != null)
1891                            this.rendererHints = rendererHints;
1892          }          }
1893    
1894          /**          /**
1895           * Returns the evelope of the viewable area. The JMapPane will never show           * Enables/Disables the ZOOM Mouse Listener. Upates the Cursor and stops the
1896           * 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.  
1897           *           *
1898           * @param maxExtend           * @param state
          *            <code>null</code> to not have this restriction.  
1899           */           */
1900            public void setState(final int state) {
1901                    this.state = state;
1902    
1903          public Envelope getMaxExtend() {                  zoomMapPaneMouseListener.setEnabled((state == ZOOM_IN
1904                  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;  
                         }  
1905    
1906                          // Kartenbereich um 10% vergroessern                  // Je nach Aktion den Cursor umsetzen
1907                          return JTSUtil.fixAspectRatio(this.getBounds(), JTSUtil                  updateCursor();
                                         .expandEnvelope(layerBounds, 0.1), true);  
                 }  
                 return maxExtend;  
1908          }          }
1909    
1910          /**          /**
1911           * Set the background color of the map.           * Standardmaessig wird der Cursor automatisch je nach MapPane-Aktion (Zoom,
1912             * Auswahl, ...) gesetzt. Mit dieser Methode kann ein statischer Cursor
1913             * gesetzt werden, der unabhaengig von der aktuellen MapPanes-Aktion
1914             * beibehalten wird. Um diesen statischen Cursor wieder zu entfernen, kann
1915             * {@code null} als Parameter uebergeben werden
1916           *           *
1917           * @param if <code>null</code>, white is used.           * @param cursor
1918             *            Cursor
1919           */           */
1920          public void setMapBackgroundColor(Color bgColor) {          public void setStaticCursor(final Cursor cursor) {
1921                  if (bgColor == null)                  this.staticCursor = cursor;
1922                          bgColor = Color.WHITE;                  if (cursor != null)
1923                  this.mapBackgroundColor = bgColor;                          super.setCursor(cursor);
1924          }          }
1925    
1926          /**          /**
1927           * Returns the background {@link Color} of the map pane. Default is white.           * Starts rendering on one or two threads
1928           **/           */
1929          public Color getMapBackgroundColor() {          private void startRendering() {
1930                  return mapBackgroundColor;  
1931                    if (!isWellDefined() || !acceptsRepaintCalls) {
1932                            // if we are not ready to start rendering, try it again the next
1933                            // time the timer is chacking the flag.
1934                            requestStartRendering = true;
1935                            return;
1936                    }
1937    
1938                    if (bgExecuter != null) {
1939                            // Stop all renderers
1940                            bgExecuter.cancelTask();
1941                    }
1942    
1943                    localExecuter.cancelTask();
1944    
1945                    final Rectangle curPaintArea = getVisibleRect();
1946    
1947                    /**
1948                     * We have to set new renderer
1949                     */
1950    
1951                    if (getBgContext() != null) {
1952                            bgRenderer.setJava2DHints(getJava2dHints());
1953                            bgRenderer.setRendererHints(getRendererHints());
1954    
1955                            // bgExecuter = new RenderingExecutor();
1956                            // LOGGER.debug("starting bg renderer:");
1957                            // // /* System.out.println("rendering"); */
1958                            // final GTRenderer createGTRenderer = GTUtil.createGTRenderer(
1959                            // bgContext, getRendererHints());
1960                            // createGTRenderer.setJava2DHints(getJava2dHints());
1961                            // bgExecuter.submit(getBgContext().getAreaOfInterest(),
1962                            // curPaintArea,
1963                            // (Graphics2D) getBgImage().getGraphics(), createGTRenderer);
1964                    }
1965    
1966                    if (getMapContext() != null) {
1967                            // localExecuter = new RenderingExecutor(this, 150l);
1968                            // LOGGER.debug("starting local renderer:");
1969    
1970                            localRenderer.setJava2DHints(getJava2dHints());
1971                            localRenderer.setRendererHints(getRendererHints());
1972    
1973                            final boolean submitted = localExecuter.submit(getMapArea(),
1974                                            curPaintArea, (Graphics2D) getLocalImage().getGraphics(),
1975                                            localRenderer);
1976                            if (submitted)
1977                                    repaintTimer.restart();
1978                            else
1979                                    requestStartRendering = true; // Try to start rendering
1980                            // again in
1981                            // a moment
1982                    }
1983    
1984                    updateCursor();
1985          }          }
1986    
1987          /**          /**
1988             * Transformiert einen Geo-Koordinaten-Bereich in Fenster-Koordinaten.
1989           *           *
1990           * @param b           * @param ox
1991             *            X-Koordinate der VON-Position
1992             * @param oy
1993             *            Y-Koordinate der VON-Position
1994             * @param px
1995             *            X-Koordinate der BIS-Position
1996             * @param py
1997             *            Y-Koordinate der BIS-Position
1998             * @param winToGeotransform
1999             *            Eine Window to Geo transform. If <code>null</code>,
2000             *            {@link #getScreenToWorld()} is used.
2001           */           */
2002          public void setPainting(boolean b) {          public Envelope tranformGeoToWindow(final double ox, final double oy,
2003                  acceptsRepaintCalls = b;                          final double px, final double py) {
2004                    final AffineTransform at = getWorldToScreenTransform();
2005                    Point2D geoO;
2006                    // try {
2007                    geoO = at.transform(new Point2D.Double(ox, oy), null);
2008                    final Point2D geoP = at.transform(new Point2D.Double(px, py), null);
2009                    return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP.getY());
2010                    // } catch (final NoninvertibleTransformException e) {
2011                    // LOGGER.error(e);
2012                    // return new Envelope(ox, oy, px, py);
2013                    // }
2014          }          }
2015    
2016          /**          /**
2017           * Fuegt der Map einen Listener hinzu.           * Transformiert einen Fenster-Koordinaten-Bereich in Geo-Koordinaten.
2018           *           *
2019           * @param l           * @param ox
2020           *            neuer Listener           *            X-Koordinate der VON-Position
2021             * @param oy
2022             *            Y-Koordinate der VON-Position
2023             * @param px
2024             *            X-Koordinate der BIS-Position
2025             * @param py
2026             *            Y-Koordinate der BIS-Position
2027           */           */
2028          public void addMapPaneListener(JMapPaneListener l) {          public Envelope tranformWindowToGeo(final int ox, final int oy,
2029                  mapPaneListeners.add(l);                          final int px, final int py) {
2030                    final AffineTransform at = getScreenToWorld();
2031                    final Point2D geoO = at.transform(new Point2D.Double(ox, oy), null);
2032                    final Point2D geoP = at.transform(new Point2D.Double(px, py), null);
2033    
2034                    // Mmmmm... don't really understand why its x,x,y,y
2035                    // return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(),
2036                    // geoP.getY());
2037                    return new Envelope(new Coordinate(geoO.getX(), geoO.getY()),
2038                                    new Coordinate(geoP.getX(), geoP.getY()));
2039          }          }
2040    
2041          /**          /**
2042           * Liste der angeschlossenen Listener, die auf Aktionen des MapPanes           * Will update the cursor. If all rendering is finished also stops the
2043           * lauschen.           * {@link #repaintTimer}
2044           */           */
2045          protected Vector<JMapPaneListener> mapPaneListeners = new Vector<JMapPaneListener>();          public void updateCursor() {
2046    
2047          /**                  // if the renderers have stopped, also stop the timer that is updating
2048           * A flag indicating if dispose() was already called. If true, then further                  // the final image
2049           * use of this {@link SelectableXMapPane} is undefined.                  if (bgExecuter != null && bgExecuter.isRunning()
2050           */                                  || localExecuter.isRunning()) {
2051          private boolean disposed = false;                          setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
2052                            return;
2053                    } else {
2054                            // Allow one last rendering
2055                            if (repaintTimer.isRunning()) {
2056                                    // System.out.println("one last rendering....");
2057                                    repaintTimer.stop();
2058                                    updateFinalImage();
2059                                    repaint();
2060                            }
2061                    }
2062    
2063                    // wenn manueller Cursor gesetzt ist, dann diesen verwenden (unabhaengig
2064                    // von der aktuellen Aktion
2065                    if (this.staticCursor != null) {
2066                            setCursor(staticCursor);
2067                            return;
2068                    }
2069                    if (getCursor() == SwingUtil.PANNING_CURSOR) {
2070                            // This cursor will reset itself
2071                            return;
2072                    }
2073    
2074                    // Set the cursor depending on what tool is in use...
2075                    switch (state) {
2076                    case SELECT_TOP:
2077                    case SELECT_ONE_FROM_TOP:
2078                    case SELECT_ALL:
2079                            setCursor(SwingUtil.CROSSHAIR_CURSOR);
2080                            break;
2081                    case ZOOM_IN:
2082                            setCursor(SwingUtil.ZOOMIN_CURSOR);
2083                            break;
2084                    case ZOOM_OUT:
2085                            setCursor(SwingUtil.ZOOMOUT_CURSOR);
2086                            break;
2087                    case PAN:
2088                            setCursor(SwingUtil.PAN_CURSOR);
2089                            break;
2090                    default:
2091                            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
2092                            break;
2093                    }
2094            }
2095    
2096          /**          /**
2097           * Entfernt einen Listener von der Map.           * The renderers are all rendering into their own {@link Image}s. This
2098           *           * method combines all images to one {@link #finalImage}. The
2099           * @param l           * {@link #repaintTimer} is calling this method regularely to update the
2100           *            zu entfernender Listener           * {@link #finalImage} even if the renderers are still working.
2101           */           */
2102          public void removeMapPaneListener(JMapPaneListener l) {          synchronized protected Image updateFinalImage() {
                 mapPaneListeners.remove(l);  
         }  
2103    
2104          /** Stored the time used for the last real rendering in ms. **/                  // Render the two map images first, into the preFinalImage
2105          private long lastRenderingDuration = Long.MAX_VALUE;                  if (bgExecuter != null) {
2106                            final Graphics2D preFinalG = (Graphics2D) getPreFinalImage()
2107                                            .getGraphics();
2108                            preFinalG.setBackground(getMapBackgroundColor());
2109    
2110          // if null, no quick preview will be shown                          preFinalG.drawImage(getBgImage(), 0, 0, getMapBackgroundColor(),
2111          private int quickPreviewHint = 0;                                          null);
2112    
2113          /**                          // // Draw the local layers image
2114           * For every rendering thread started,                          preFinalG.drawImage(getLocalImage(), 0, 0, null);
2115           * {@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;  
2116    
2117          /**                  } else {
2118           * Returns in milli seconds the time the last rending of the                          preFinalImage = getLocalImage();
2119           * {@link SelectableXMapPane} took. #Long.MAX_VALUE if the JMapPane has not                  }
2120           * been rendered yet.  
2121           */                  final Graphics2D finalG = getFinalImage().createGraphics();
2122          public long getLastRenderingDuration() {                  finalG.setBackground(getMapBackgroundColor());
2123                  return lastRenderingDuration;                  finalG.drawImage(getPreFinalImage(), imageOrigin.x, imageOrigin.y,
2124                                    getMapBackgroundColor(), null);
2125    
2126                    final int finalImageHeight = getFinalImage().getHeight(null);
2127                    final int finalImageWidth = getFinalImage().getWidth(null);
2128    
2129                    final Rectangle painedArea = new Rectangle(imageOrigin.x,
2130                                    imageOrigin.y, finalImageWidth, finalImageHeight);
2131                    SwingUtil.clearAround(finalG, painedArea, getVisibleRect());
2132    
2133                    addGadgets(finalG, false);
2134    
2135                    finalG.dispose();
2136    
2137                    return finalImage;
2138          }          }
2139    
2140          /**          /**
2141           * Should be called when the {@link JMapPane} is not needed no more to help           * Paints some optional stuff into the given {@link Graphics2D}. Usually
2142           * the GarbageCollector           * called as the last layer when {@link #updateFinalImage()}
          *  
          * Removes all {@link JMapPaneListener}s that are registered  
2143           *           *
2144           * @author <a href="mailto:[email protected]">Stefan Alfons           * @param forceWait
2145           *         Kr&uuml;ger</a>           *            if <code>true</code>, a Wait-message will be painted even
2146             *            though the rendering threads may not yet have started. If
2147             *            <code>false</code>, it will only depend on
2148             *            {@link #localExecuter.isRunning} and #bgExecuter.isRunning
2149           */           */
2150          public void dispose() {          private void addGadgets(final Graphics2D graphics, boolean forceWait) {
                 if (isDisposed())  
                         return;  
2151    
2152                  disposed = true;                  // Paint a logo to the bottom right if available
2153                    if (mapImage != null) {
2154                  if (bgExecuter != null) {                          Rectangle visibleRect = getVisibleRect();
2155                          bgExecuter.cancelTask();                          graphics.drawImage(mapImage, visibleRect.width
2156                                            - mapImage.getWidth() - 10, getVisibleRect().height
2157                                            - mapImage.getHeight() - 10, null);
2158                  }                  }
2159    
2160                  if (localExecuter != null) {                  int y = 17;
                         localExecuter.cancelTask();  
                 }  
2161    
2162                  startRenderThreadsTimer.stop();                  // If the rendering process is still running, indicate this is the image
2163                  repainterTimer.stop();                  if (forceWait || bgExecuter != null && bgExecuter.isRunning()
2164                                    || localExecuter.isRunning()) {
2165    
2166                  if (bgImage != null)                          y += 8;
                         bgImage.flush();  
                 if (localImage != null)  
                         localImage.flush();  
 //              if (gadgetsImage != null)  
 //                      gadgetsImage.flush();  
                 if (finalImage != null)  
                         finalImage.flush();  
                 if (preFinalImage != null)  
                         preFinalImage.flush();  
2167    
2168                  // if (dragWaitCursorListener != null)                          final Color c = graphics.getColor();
2169                  // this.removeMouseListener(dragWaitCursorListener);                          graphics.setFont(waitFont);
                 // if (mouseWheelZoomListener != null)  
                 // this.removeMouseWheelListener(mouseWheelZoomListener);  
2170    
2171                  // Alle mapPaneListener entfernen                          graphics.setColor(getMapBackgroundColor());
2172                  mapPaneListeners.clear();                          graphics.drawString(waitMsg, 5, y);
2173                            graphics.setColor(getMapBackgroundColor());
2174                            graphics.drawString(waitMsg, 7, y + 2);
2175                            graphics.setColor(Color.BLACK);
2176                            graphics.drawString(waitMsg, 6, y + 1);
2177    
2178                  removeMouseMotionListener(zoomMapPaneMouseListener);                          graphics.setColor(c);
                 removeMouseListener(zoomMapPaneMouseListener);  
2179    
2180                  if (localContext != null)                          y += 24;
2181                          getContext().clearLayerList();                  }
                 if (bgContext != null)  
                         getBgContext().clearLayerList();  
2182    
2183                  removeAll();                  if (!renderingErrors.isEmpty() && isShowExceptions()) {
         }  
2184    
2185          /**                          final Color c = graphics.getColor();
2186           * A flag indicating if dispose() has already been called. If true, then                          graphics.setFont(errorFont);
          * further use of this {@link SelectableXMapPane} is undefined.  
          */  
         private boolean isDisposed() {  
                 return disposed;  
         }  
2187    
2188          public void setQuickPreviewHint(int quickPreviewHint) {                          for (Exception ex : renderingErrors) {
                 this.quickPreviewHint = quickPreviewHint;  
2189    
2190          }                                  String errStr = ex.getLocalizedMessage();
2191    
2192          public void setJava2dHints(RenderingHints java2dHints) {                                  if (errStr == null)
2193                  this.java2dHints = java2dHints;                                          errStr = ex.getMessage();
2194          }                                  if (errStr == null)
2195                                            errStr = "unknown error: " + ex.getClass().getSimpleName();
2196    
2197                                    graphics.setColor(Color.WHITE);
2198                                    graphics.drawString(errStr, 5, y);
2199                                    graphics.setColor(Color.RED);
2200                                    graphics.drawString(errStr, 6, y + 1);
2201    
2202                                    y += 19;
2203                            }
2204    
2205                            graphics.setColor(c);
2206                    }
2207    
         public RenderingHints getJava2dHints() {  
                 return java2dHints;  
2208          }          }
2209    
2210          /**          /**
2211           * Zooms towards a point.           * Sets the {@link #mapArea} to best possibly present the given features. If
2212             * only one single point is given, the window is moved over the point.
2213           *           *
2214           * @param center           * @param features
2215           *            position in window coordinates           *            if <code>null</code> or size==0, the function doesn nothing.
          * @param zoomFaktor  
          *            > 1 for zoom in, < 1 for zoom out. Default is 1.33.  
2216           */           */
2217          public void zoomTo(Point center, Double zoomFaktor) {          public void zoomTo(
2218                  if (zoomFaktor == null || zoomFaktor == 0.)                          final FeatureCollection<SimpleFeatureType, SimpleFeature> features) {
                         zoomFaktor = 2.;  
2219    
2220                  Point2D gcenter = getScreenToWorld().transform(center, null);                  // if (!isWellDefined()) return;
2221                  center = null;  
2222                                    final CoordinateReferenceSystem mapCRS = getMapContext()
2223                  if (                                  .getCoordinateReferenceSystem();
2224                                  Double.isNaN(gcenter.getX()) || Double.isNaN(gcenter.getY())                  final CoordinateReferenceSystem fCRS = features.getSchema()
2225                                  ||                                  .getGeometryDescriptor().getCoordinateReferenceSystem();
2226                                  Double.isInfinite(gcenter.getX()) || Double.isInfinite(gcenter.getY())  
2227                                                    ReferencedEnvelope _mapArea;
2228                  ) {                  if (mapArea == null)
2229                          // Not inside valid CRS area! cancel                          _mapArea = features.getBounds();
2230                    else
2231                            _mapArea = getMapArea();
2232                    double width = _mapArea.getWidth();
2233                    double height = _mapArea.getHeight();
2234                    final double ratio = height / width;
2235    
2236                    if (features == null || features.size() == 0) {
2237                            // feature count == 0 Zoom to the full extend
2238                          return;                          return;
2239                  }                  } else if (features.size() == 1) {
2240    
2241                  final Envelope mapArea = getMapArea();                          // feature count == 1 Just move the window to the point and zoom 'a
2242                                            // bit'
2243                  Envelope newMapArea = new Envelope(mapArea);                          final SimpleFeature singleFeature = features.iterator().next();
                 newMapArea  
                                 .expandBy((mapArea.getWidth() * zoomFaktor - mapArea  
                                                 .getWidth()) / 2., (mapArea.getHeight()  
                                                 * zoomFaktor - mapArea.getHeight()) / 2.);  
   
                 // Move the newMapArea above the new center  
                 newMapArea.translate(gcenter.getX() - mapArea.centre().x, gcenter.getY()  
                                 - mapArea.centre().y);  
2244    
2245                  setMapArea(newMapArea);                          if (((Geometry) singleFeature.getDefaultGeometry())
2246                                            .getCoordinates().length > 1) {
2247                                    // System.out.println("Zoomed to only pne poylgon");
2248                                    // Poly
2249                                    // TODO max width vs. height
2250                                    width = features.getBounds().getWidth() * 3;
2251                                    height = ratio * width;
2252                            } else {
2253                                    // System.out.println("Zoomed in a bit becasue only one point");
2254                                    // width *= .9;
2255                                    // height *= .9;
2256                            }
2257    
2258                            Coordinate centre = features.getBounds().centre();
2259                            if (!mapCRS.equals(fCRS)) {
2260                                    // only to calculations if the CRS differ
2261                                    try {
2262                                            MathTransform fToMap;
2263                                            fToMap = CRS.findMathTransform(fCRS, mapCRS);
2264                                            // centre is transformed to the mapCRS
2265                                            centre = JTS.transform(centre, null, fToMap);
2266                                    } catch (final FactoryException e) {
2267                                            LOGGER.error("Looking for a Math transform", e);
2268                                    } catch (final TransformException e) {
2269                                            LOGGER.error("Looking for a Math transform", e);
2270                                    }
2271                            }
2272    
2273                            final Coordinate newLeftBottom = new Coordinate(centre.x - width
2274                                            / 2., centre.y - height / 2.);
2275                            final Coordinate newTopRight = new Coordinate(
2276                                            centre.x + width / 2., centre.y + height / 2.);
2277    
2278                            final Envelope newMapArea = new Envelope(newLeftBottom, newTopRight);
2279    
2280                            setMapArea(newMapArea);
2281    
2282                    } else {
2283                            final ReferencedEnvelope fBounds = features.getBounds();
2284    
2285                            ReferencedEnvelope bounds;
2286                            if (!mapCRS.equals(fCRS)) {
2287                                    bounds = JTSUtil.transformEnvelope(fBounds, mapCRS);
2288                            } else {
2289                                    bounds = fBounds;
2290                            }
2291                            // BB umrechnen von Layer-CRS in Map-CRS
2292    
2293                            // Expand a bit
2294                            bounds.expandBy(bounds.getWidth() / 6., bounds.getHeight() / 6.);
2295    
2296                            setMapArea(bounds);
2297                    }
2298          }          }
2299    
2300          /**          /**
# Line 2121  public class XMapPane extends JPanel imp Line 2305  public class XMapPane extends JPanel imp
2305           * @param zoomFactor           * @param zoomFactor
2306           *            > 1 for zoom in, < 1 for zoom out. Default is 1.33           *            > 1 for zoom in, < 1 for zoom out. Default is 1.33
2307           */           */
2308          public void zoomTo(Point center) {          public void zoomTo(final Point center) {
2309                  zoomTo(center, null);                  zoomTo(center, null);
2310          }          }
2311    
2312          public void mouseDragged(Point startPos, Point lastPos, MouseEvent event) {          /**
2313             * Zooms towards a point.
2314                  if ((getState() == XMapPane.PAN)           *
2315                                  || ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0)) {           * @param center
2316             *            position in window coordinates
2317                          if (getCursor() != SwingUtil.PANNING_CURSOR) {           * @param zoomFaktor
2318                                  setCursor(SwingUtil.PANNING_CURSOR);           *            > 1 for zoom in, < 1 for zoom out. Default is 1.33.
2319             */
2320                                  // While panning, we deactivate the rendering. So the tasts are          public void zoomTo(Point center, Double zoomFaktor) {
2321                                  // ready to start when the panning os done.                  if (zoomFaktor == null || zoomFaktor == 0.)
2322                                  if (bgExecuter != null)                          zoomFaktor = 2.;
                                         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;  
2323    
2324                                  // TODO Stop dragging when the drag would not be valid...                  final Point2D gcenter = getScreenToWorld().transform(center, null);
2325                                  // boolean dragValid = true;                  center = null;
                                 // // 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;  
                                 // }  
                                 // }  
2326    
2327                                  imageOrigin.translate(dx, dy);                  if (Double.isNaN(gcenter.getX()) || Double.isNaN(gcenter.getY())
2328                                  updateFinalImage();                                  || Double.isInfinite(gcenter.getX())
2329                                  repaint();                                  || Double.isInfinite(gcenter.getY())
                         }  
2330    
                 } else if ((getState() == XMapPane.ZOOM_IN)  
                                 || (getState() == XMapPane.ZOOM_OUT)  
                                 || (getState() == XMapPane.SELECT_ALL)  
                                 || (getState() == XMapPane.SELECT_TOP)  
                 // || (getState() == XMapPane.SELECT_ONE_FROM_TOP)  
2331                  ) {                  ) {
2332                          final Graphics graphics = getGraphics();                          // Not inside valid CRS area! cancel
2333                            return;
2334                          drawRectangle(graphics, startPos, event.getPoint());                  }
2335    
2336                          if ((lastPos.x > 0) && (lastPos.y > 0)) {                  final Envelope mapArea = getMapArea();
                                 drawRectangle(graphics, startPos, lastPos);  
                         }  
2337    
2338                  }                  final Envelope newMapArea = new Envelope(mapArea);
2339                    newMapArea.expandBy((mapArea.getWidth() * zoomFaktor - mapArea
2340                                    .getWidth()) / 2., (mapArea.getHeight() * zoomFaktor - mapArea
2341                                    .getHeight()) / 2.);
2342    
2343                    // TODO we actually want that
2344                    // // Move the newMapArea above the new center
2345                    // newMapArea.translate(gcenter.getX() - mapArea.centre().x, gcenter
2346                    // .getY()
2347                    // - mapArea.centre().y);
2348    
2349                    setMapArea(newMapArea);
2350          }          }
2351    
2352          /**          /**
2353           * Draws a rectangle in XOR mode from the origin at {@link #startPos} to the           * Shall non-fatal rendering exceptions be reported in the mappane or be
2354           * given point. All in screen coordinates.           * dropped quitely.
2355           */           */
2356          protected void drawRectangle(final Graphics graphics, Point startPos,          public void setShowExceptions(boolean showExceptions) {
2357                          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);  
2358          }          }
2359    
2360          /**          /**
2361           * Finalizes a PAN action           * Shall exceptions be reported in the mappane?
2362           */           */
2363          public void performPan() {          public boolean isShowExceptions() {
2364                  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);  
2365          }          }
2366    
2367  }  }

Legend:
Removed from v.511  
changed lines
  Added in v.558

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26