/[schmitzm]/branches/2.4.x/src/skrueger/geotools/RenderingExecutor.java
ViewVC logotype

Diff of /branches/2.4.x/src/skrueger/geotools/RenderingExecutor.java

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

revision 524 by alfonx, Wed Nov 18 09:56:47 2009 UTC revision 544 by alfonx, Sat Nov 21 17:13:31 2009 UTC
# Line 1  Line 1 
1  package skrueger.geotools;  package skrueger.geotools;
2    
 /*  
  *    GeoTools - The Open Source Java GIS Toolkit  
  *    http://geotools.org  
  *  
  *    (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)  
  *  
  *    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.  
  */  
   
   
 import gtmig.org.geotools.swing.XMapPane;  
   
 import java.awt.AlphaComposite;  
 import java.awt.Color;  
 import java.awt.Composite;  
3  import java.awt.Graphics2D;  import java.awt.Graphics2D;
4  import java.awt.Rectangle;  import java.awt.Rectangle;
5  import java.util.concurrent.Callable;  import java.awt.geom.AffineTransform;
 import java.util.concurrent.CountDownLatch;  
 import java.util.concurrent.ExecutorService;  
 import java.util.concurrent.Executors;  
 import java.util.concurrent.Future;  
 import java.util.concurrent.ScheduledExecutorService;  
 import java.util.concurrent.ScheduledFuture;  
 import java.util.concurrent.TimeUnit;  
 import java.util.concurrent.atomic.AtomicBoolean;  
6    
7  import org.geotools.geometry.jts.ReferencedEnvelope;  import org.geotools.geometry.jts.ReferencedEnvelope;
8  import org.geotools.renderer.GTRenderer;  import org.geotools.renderer.GTRenderer;
9  import org.geotools.renderer.RenderListener;  import org.geotools.renderer.RenderListener;
 import org.geotools.swing.JMapPane;  
10  import org.opengis.feature.simple.SimpleFeature;  import org.opengis.feature.simple.SimpleFeature;
11    
12  /**  /**
13   * This class is used by {@code JMapPane} to handle the scheduling and running of   * This class is used by {@link XMapPane} to start and stop the rendering a
14   * rendering tasks on a background thread. It functions as a single thread, non-   * {@link Thread} for rendering.
  * queueing executor, ie. only one rendering task can run at any given time and,  
  * while it is running, any other submitted tasks will be rejected.  
  * <p>  
  * Whether a rendering task is accepted or rejected can be tested on submission:  
  * <pre><code>  
  *     ReferencedEnvelope areaToDraw = ...  
  *     Graphics2D graphicsToDrawInto = ...  
  *     boolean accepted = renderingExecutor.submit(areaToDraw, graphicsToDrawInto);  
  * </code></pre>  
  *  
  * The status of the executor can also be checked at any time like this:  
  * <pre><code>  
  *     boolean busy = renderingExecutor.isRunning();  
  * </code></pre>  
  *  
  * While a rendering task is running it is regularly polled to see if it has completed  
  * and, if so, whether it finished normally, was cancelled or failed. The interval between  
  * polling can be adjusted which might be useful to tune the executor for particular  
  * applications:  
  * <pre><code>  
  *     RenderingExecutor re = new RenderingExecutor( mapPane );  
  *     re.setPollingInterval( 150 );  // 150 milliseconds  
  * </code></pre>  
  *  
  * @author Michael Bedward  
  * @since 2.7  
  * @source $URL: http://svn.osgeo.org/geotools/branches/2.6.x/modules/unsupported/swing/src/main/java/org/geotools/swing/RenderingExecutor.java $  
  * @version $Id: RenderingExecutor.java 34285 2009-10-30 10:48:49Z mbedward $  
  *  
  * @see JMapPane  
15   */   */
16  public class RenderingExecutor {  class RenderingExecutor {
17    
18      private final XMapPane mapPane;          /**
19      private final ExecutorService taskExecutor;           * Instance to a {@link RenderThread} doing any work. It's volatile so the
20      private final ScheduledExecutorService watchExecutor;           * correct value will always be visible to any {@link Thread}
21             **/
22  //    /** The default interval (milliseconds) for polling the result of a rendering task */          private volatile RenderThread renderThread;
23  //    public static final long DEFAULT_POLLING_INTERVAL = 300L;  
24            private final XMapPane mapPane;
25      private long pollingInterval;  
26            public RenderingExecutor(XMapPane mapPane) {
27      /*                  this.mapPane = mapPane;
28       * This latch is used to avoid a race between the cancellation of          }
29       * a current task and the submittal of a new task  
30       */          /**
31      private CountDownLatch cancelLatch;           * Submit a new rendering task. If no rendering task is presently running
32             * this new job will be accepted; otherwise it will be rejected and it
33      /**           * returns <code>false</code>.
34       * Constants to indicate the result of a rendering task           *
35       */           * @param envelope
36      public enum TaskResult {           *            the map area (world coordinates) to be rendered.
37          PENDING,           * @param graphics
38          COMPLETED,           *            the graphics object to draw on.
39          CANCELLED,           * @param paintArea
40          FAILED;           *            size of the area to paint the world into.
41      }           * @param worldToScreen
42             *            the {@link AffineTransform} from world coordinates to screen
43      private long numFeatures;           *            coordinates.
44             * @param renderer
45      /**           *            the {@link GTRenderer} to use.
46       * A rendering task           *
47       */           * @return true if the rendering task was accepted; false if it was rejected
48      private class Task implements Callable<TaskResult>, RenderListener {           */
49            public synchronized boolean submit(ReferencedEnvelope envelope,
50          private final ReferencedEnvelope envelope;                          Rectangle paintArea, Graphics2D graphics,
51          private final Rectangle paintArea;                          final GTRenderer renderer
52          private final Graphics2D graphics;  //                      , AffineTransform worldToScreen
53                            ) {
54          private boolean cancelled;                  if (renderThread == null || !renderThread.isAlive()) {
55          private boolean failed;                          // System.out.println("is vacant... starting thread!");
56                  final private GTRenderer renderer;                          renderThread = null;
57    
58          /**                          renderThread = new RenderThread(paintArea, graphics, renderer,
59           * Constructor. Creates a new rendering task  //                                      worldToScreen,
60           *                                          envelope);
61           * @param envelope map area to render (world coordinates)                          renderThread.start();
62           * @param paintArea drawing area (image or display coordinates)a  
63           * @param graphics graphics object used to draw into the image or display                          return true;
64           */                  } else {
65          public Task(final ReferencedEnvelope envelope, final Rectangle paintArea, final Graphics2D graphics, GTRenderer renderer) {                          // System.out.println("is busy... requesting stop!");
66              this.envelope = envelope;                          renderThread.getRenderer().stopRendering();
67              this.paintArea = paintArea;                          return false;
68              this.graphics = graphics;                  }
69              this.cancelled = false;          }
70              this.renderer = renderer;  
71              failed = false;          /**
72          }           * For every new rendering job submitted and accepted, an instance of this
73             * {@link Thread} will be started.
74          /**           *
75           * Called by the executor to run this rendering task.           */
76           *          class RenderThread extends Thread {
77           * @return result of the task: completed, cancelled or failed  
78           * @throws Exception                  private final GTRenderer renderer;
79           */  
80          public TaskResult call() throws Exception {                  public RenderThread(final Rectangle paintArea,
81              if (!cancelled) {                                  final Graphics2D graphics, GTRenderer renderer,
82                    //                              AffineTransform worldToScreen,
83                  try {                                  ReferencedEnvelope mapEnv) {
84                      renderer.addRenderListener(this);                          super(new RenderRun(paintArea, graphics, renderer,
85                                                                mapEnv
86                      Composite composite = graphics.getComposite();  //                                      ,                                       worldToScreen
87                      //graphics.setComposite(AlphaComposite.Src);                                          ));
88                      //graphics.setBackground(Color.WHITE);                          this.renderer = renderer;
89                      graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));  
90                      graphics.fill(paintArea);                          setName("Render" + getName());
91                      graphics.setComposite(composite);  
92                            // System.out.println("starting render thread " + getName());
93                    }
94                      numFeatures = 0;  
95                      renderer.paint(graphics, mapPane.getVisibleRect(), envelope, mapPane.getWorldToScreenTransform());                  public GTRenderer getRenderer() {
96                            return renderer;
97                  } finally {                  }
98                      renderer.removeRenderListener(this);  
99                  }          }
100              }  
101            /**
102              if (cancelled) {           * This {@link Runnable} will actually start the rendering
103                  return TaskResult.CANCELLED;           */
104              } else if (failed) {          class RenderRun implements Runnable, RenderListener {
105                  return TaskResult.FAILED;                  private final Rectangle paintArea;
106              } else {                  private final Graphics2D graphics;
107                  return TaskResult.COMPLETED;  //              private final AffineTransform worldToScreen;
108              }                  private final GTRenderer renderer;
109          }                  private final ReferencedEnvelope mapEnv;
110    
111          /**                  public RenderRun(Rectangle paintArea, Graphics2D graphics,
112           * Cancel the rendering task if it is running. If called before                                  GTRenderer renderer, ReferencedEnvelope mapEnv
113           * being run the task will be abandoned.  //                              ,
114           */  //                              AffineTransform worldToScreen
115          public synchronized void cancel() {                                  ) {
116              if (isRunning()) {                          this.paintArea = paintArea;
117                  cancelled = true;                          this.graphics = graphics;
118                  renderer.stopRendering();                          this.renderer = renderer;
119              }                          this.mapEnv = mapEnv;
120          }  //                      this.worldToScreen = worldToScreen;
121                    }
122          /**  
123           * Called by the renderer when each feature is drawn.                  @Override
124           *                  public void run() {
125           * @param feature the feature just drawn                          try {
126           */                                  renderer.addRenderListener(this);
127          public void featureRenderer(SimpleFeature feature) {                                  System.out.println("start rendering...");
128              // @todo update a progress listener                                  // try {
129              numFeatures++ ;                                  // Thread.sleep(1000);
130          }                                  // } catch (InterruptedException e) {
131                                    // e.printStackTrace();
132          /**                                  // }
133           * Called by the renderer on error  
134           *                                  // Clear the graphics context
135           * @param e cause of the error                                  graphics.setBackground(mapPane.getMapBackgroundColor());
136           */                                  graphics.clearRect(paintArea.x, paintArea.y, paintArea.width,
137          public void errorOccurred(Exception e) {                                                  paintArea.height);
138                  renderingException = e;  
139                  graphics.setColor(Color.white);                                  renderer.paint(graphics, paintArea, mapEnv);
140                  graphics.drawString(e.getMessage(), 11, 11);  
141                  graphics.drawString(e.getMessage(), 9, 9);                                  // Kill the reference to this Thread so #isRunning will say
142                  graphics.setColor(Color.black);                                  // false directly
143                  graphics.drawString(e.getMessage(), 10, 10);                                  renderThread = null;
144              failed = true;                                  mapPane.onRenderingCompleted();
145          }                          } catch (Exception e) {
146                                    mapPane.onRenderingFailed(e);
147      }                          } finally {
148                                    renderer.removeRenderListener(this);
149      private AtomicBoolean taskRunning;                          }
150      private Task task;                  }
151      private Future<TaskResult> taskResult;  
152      private ScheduledFuture<?> watcher;                  @Override
153          private Exception renderingException;                  public void errorOccurred(Exception e) {
154                            mapPane.onRenderingFailed(e);
155      /**                  }
156       * Constructor. Creates a new executor to service the specified map pane.  
157       *                  @Override
158       * @param mapPane the map pane to be serviced                  public void featureRenderer(SimpleFeature feature) {
159       * @param l                  }
160       */  
161          public RenderingExecutor(final XMapPane mapPane, long pollingInterval) {          }
162          taskRunning = new AtomicBoolean(false);  
163          this.mapPane = mapPane;          /**
164          taskExecutor = Executors.newSingleThreadExecutor();           * Ask to stop the rendering. May be called often.
165          watchExecutor = Executors.newSingleThreadScheduledExecutor();           */
166          this.pollingInterval = pollingInterval;          public void cancelTask() {
167          cancelLatch = new CountDownLatch(0);                  if (renderThread != null && renderThread.isAlive()) {
168      }                          // System.out.println("request stop for thread " +task.getName());
169                            renderThread.getRenderer().stopRendering();
170      /**                  }
171       * Get the interval for polling the result of a rendering task          }
172       *  
173       * @return polling interval in milliseconds          /**
174       */           * @return <code>true</code> if the {@link Thread} is busy rendering.
175      public long getPollingInterval() {           */
176          return pollingInterval;          public boolean isRunning() {
177      }                  return (renderThread != null && renderThread.isAlive());
178            }
179      /**  
180       * Set the interval for polling the result of a rendering task          /**
181       *           * Will stop rendering and remove the reference to the {@link Thread}.
182       * @param interval interval in milliseconds (values {@code <=} 0 are ignored)           */
183       */          public void dispose() {
184      public void setPollingInterval(long interval) {                  if (renderThread != null) {
185          if (interval > 0) {                          renderThread.renderer.stopRendering();
186              pollingInterval = interval;                          renderThread = null;
187          }                  }
188      }          }
   
     /**  
      * Submit a new rendering task. If no rendering task is presently running  
      * this new task will be accepted; otherwise it will be rejected (ie. there  
      * is no task queue).  
      *  
      * @param envelope the map area (world coordinates) to be rendered  
      * @param graphics the graphics object to draw on  
      *  
      * @return true if the rendering task was accepted; false if it was  
      *         rejected  
      */  
     public synchronized boolean submit(ReferencedEnvelope envelope, Rectangle paintArea, Graphics2D graphics, final GTRenderer renderer) {  
         if (!isRunning()) {  
 //            try {  
 //                // wait for any cancelled task to finish its shutdown  
 //                cancelLatch.await();  
 //            } catch (InterruptedException ex) {  
 //                return false;  
 //            }  
   
             task = new Task(envelope, paintArea, graphics, renderer);  
             taskRunning.set(true);  
             taskResult = taskExecutor.submit(task);  
             watcher = watchExecutor.scheduleAtFixedRate(new Runnable() {  
   
                 public void run() {  
                     pollTaskResult();  
                 }  
             }, pollingInterval, pollingInterval, TimeUnit.MILLISECONDS);  
   
             return true;  
         }  
   
         return false;  
     }  
   
     /**  
      * Cancel the current rendering task if one is running  
      */  
     public synchronized void cancelTask() {  
         if (isRunning()) {  
             task.cancel();  
               
             // Cancelling to often... can that be the reason?  
             if (cancelLatch.getCount() < 1) {  
                 cancelLatch = new CountDownLatch(1);  
             }  
         }  
     }  
   
     private void pollTaskResult() {  
         if (!taskResult.isDone()) {  
             return;  
         }  
   
         TaskResult result = TaskResult.PENDING;  
   
         try {  
             result = taskResult.get();  
         } catch (Exception ex) {  
             throw new IllegalStateException("When getting rendering result", ex);  
         }  
   
         watcher.cancel(false);  
         taskRunning.set(false);  
   
         switch (result) {  
             case CANCELLED:  
                 cancelLatch.countDown();  
                 mapPane.onRenderingCancelled();  
                 break;  
   
             case COMPLETED:  
                 mapPane.onRenderingCompleted();  
                 break;  
   
             case FAILED:  
                 mapPane.onRenderingFailed(renderingException);  
                 break;  
                   
             case PENDING:  
                 mapPane.onRenderingPending();  
                 break;  
                   
         }  
     }  
   
     public synchronized boolean isRunning() {  
         return taskRunning.get();  
     }  
   
     @Override  
     protected void finalize() throws Throwable {  
         if (this.isRunning()) {  
             taskExecutor.shutdownNow();  
             watchExecutor.shutdownNow();  
         }  
     }  
       
     public void dispose() {  
         taskExecutor.shutdownNow();  
         watchExecutor.shutdownNow();  
     }  
 }  
189    
190    }

Legend:
Removed from v.524  
changed lines
  Added in v.544

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26