/[schmitzm]/trunk/src/skrueger/geotools/XMapPane.java
ViewVC logotype

Diff of /trunk/src/skrueger/geotools/XMapPane.java

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26