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

Legend:
Removed from v.514  
changed lines
  Added in v.551

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26