private TileRange createTileRange(
      WMTSource wmtSource, WMTRenderJob renderJob, Map<String, Tile> tileList) {
    TileRange range;
    TileSet tileset = new WMTTileSetWrapper(wmtSource);

    String value =
        CatalogPlugin.getDefault()
            .getPreferenceStore()
            .getString(PreferenceConstants.P_WMSCTILE_CACHING);
    if (value.equals(WMSCTileCaching.ONDISK.toString())) {
      String dir =
          CatalogPlugin.getDefault()
              .getPreferenceStore()
              .getString(PreferenceConstants.P_WMSCTILE_DISKDIR);
      WMTTileImageReadWriter tileReadWriter = new WMTTileImageReadWriter(dir);

      range =
          new TileRangeOnDisk(
              null,
              tileset,
              renderJob.getMapExtentTileCrs(),
              tileList,
              requestTileWorkQueue,
              writeTileWorkQueue,
              tileReadWriter);
    } else {
      range =
          new TileRangeInMemory(
              null, tileset, renderJob.getMapExtentTileCrs(), tileList, requestTileWorkQueue);
    }

    return range;
  }
  /**
   * Clears the area of the tile on the graphics
   *
   * @param graphics graphics to draw onto
   * @param style raster symbolizer
   * @throws FactoryException
   * @throws TransformException
   * @throws RenderException
   */
  private void renderBlankTile(Graphics2D graphics, WMTTile tile, WMTRenderJob renderJob)
      throws Exception {

    if (tile == null) {
      return;
    }

    // get the bounds of the tile and convert to necessary viewport projection
    Envelope bnds = renderJob.projectTileToMapCrs(tile.getExtent());

    // determine screen coordinates of tiles
    Point upperLeft = getContext().worldToPixel(new Coordinate(bnds.getMinX(), bnds.getMinY()));
    Point bottomRight = getContext().worldToPixel(new Coordinate(bnds.getMaxX(), bnds.getMaxY()));
    Rectangle tileSize = new Rectangle(upperLeft);
    tileSize.add(bottomRight);

    // render
    try {
      graphics.setBackground(new Color(255, 255, 255, 0)); // set the tile transparent for now
      graphics.clearRect(tileSize.x, tileSize.y, tileSize.width, tileSize.height);

      if (TESTING) {
        /* for testing draw border around tiles */
        graphics.setColor(Color.BLACK);
        graphics.drawLine(
            (int) tileSize.getMinX(),
            (int) tileSize.getMinY(),
            (int) tileSize.getMinX(),
            (int) tileSize.getMaxY());
        graphics.drawLine(
            (int) tileSize.getMinX(),
            (int) tileSize.getMinY(),
            (int) tileSize.getMaxX(),
            (int) tileSize.getMinY());
        graphics.drawLine(
            (int) tileSize.getMaxX(),
            (int) tileSize.getMinY(),
            (int) tileSize.getMaxX(),
            (int) tileSize.getMaxY());
        graphics.drawLine(
            (int) tileSize.getMinX(),
            (int) tileSize.getMaxY(),
            (int) tileSize.getMaxX(),
            (int) tileSize.getMaxY());
      }
    } catch (Throwable t) {
      WMTPlugin.log("Error Rendering Blank tile. Painting Tile: " + tile.getId(), t); // $NON-NLS-1$
    }
  }
  private Map<String, Tile> checkTooManyTiles(
      ILayer layer,
      WMTSource wmtSource,
      WMTLayerProperties layerProperties,
      WMTRenderJob renderJob,
      Map<String, Tile> tileList) {
    int tilesCount = tileList.size();

    if (tilesCount > WMTRenderJob.getTileLimitWarning()) {
      // too many tiles, let's use the recommended zoom-level (if it wasn't already used)
      Boolean selectionAutomatic = layerProperties.getSelectionAutomatic();

      if ((selectionAutomatic != null) && (selectionAutomatic == false)) {
        tileList.clear();
        tileList =
            wmtSource.cutExtentIntoTiles(
                renderJob, WMTRenderJob.getScaleFactor(), true, layerProperties);
        tilesCount = tileList.size();
      }

      // show a warning about this
      layer.setStatus(ILayer.WARNING);
      layer.setStatusMessage(Messages.Render_Warning_TooManyTiles);

      WMTPlugin.trace("[BasicWMTRender.render] Set WARNING_TOO_MANY_TILES"); // $NON-NLS-1$
    }

    if (tilesCount > WMTRenderJob.getTileLimitError()) {
      // this is just too much, cancel
      WMTPlugin.trace("[BasicWMTRender.render] Set ERROR_TOO_MANY_TILES"); // $NON-NLS-1$

      return Collections.emptyMap();
    }

    return tileList;
  }
  /**
   * @see net.refractions.udig.render.internal.wmsc.basic#renderTile(Graphics2D graphics, WMTTile
   *     tile, CoordinateReferenceSystem crs, RasterSymbolizer style)
   * @param graphics
   * @param tile
   * @param style
   * @throws FactoryException
   * @throws TransformException
   * @throws RenderException
   */
  private void renderTile(
      Graphics2D graphics, WMTTile tile, RasterSymbolizer style, WMTRenderJob renderJob)
      throws Exception {

    if (tile == null || tile.getBufferedImage() == null) {
      return;
    }

    // create a gridcoverage from the tile image
    GridCoverageFactory factory = new GridCoverageFactory();

    // get the tile bounds in the CRS the tiles were drawn in
    ReferencedEnvelope tileBndsMercatorRef =
        renderJob.projectTileToTileProjectedCrs(tile.getExtent());

    GridCoverage2D coverage =
        (GridCoverage2D)
            factory.create(
                "GridCoverage", tile.getBufferedImage(), tileBndsMercatorRef); // $NON-NLS-1$

    Envelope2D coveragebounds = coverage.getEnvelope2D();

    // bounds of tile
    ReferencedEnvelope bnds =
        new ReferencedEnvelope(
            coveragebounds.getMinX(),
            coveragebounds.getMaxX(),
            coveragebounds.getMinY(),
            coveragebounds.getMaxY(),
            renderJob.getCrsTilesProjected());

    // reproject tile bounds to map CRS
    bnds = renderJob.projectTileProjectedToMapCrs(bnds);

    // determine screen coordinates of tiles
    Point upperLeft = getContext().worldToPixel(new Coordinate(bnds.getMinX(), bnds.getMinY()));
    Point bottomRight = getContext().worldToPixel(new Coordinate(bnds.getMaxX(), bnds.getMaxY()));
    Rectangle tileSize = new Rectangle(upperLeft);
    tileSize.add(bottomRight);

    // render
    try {
      CoordinateReferenceSystem crs = getContext().getCRS();
      AffineTransform worldToScreen = RendererUtilities.worldToScreenTransform(bnds, tileSize, crs);
      GridCoverageRenderer paint = new GridCoverageRenderer(crs, bnds, tileSize, worldToScreen);

      paint.paint(graphics, coverage, style);

      if (TESTING) {
        //            if(true){
        /* for testing draw border around tiles */
        graphics.setColor(Color.BLACK);
        graphics.drawLine(
            (int) tileSize.getMinX(),
            (int) tileSize.getMinY(),
            (int) tileSize.getMinX(),
            (int) tileSize.getMaxY());
        graphics.drawLine(
            (int) tileSize.getMinX(),
            (int) tileSize.getMinY(),
            (int) tileSize.getMaxX(),
            (int) tileSize.getMinY());
        graphics.drawLine(
            (int) tileSize.getMaxX(),
            (int) tileSize.getMinY(),
            (int) tileSize.getMaxX(),
            (int) tileSize.getMaxY());
        graphics.drawLine(
            (int) tileSize.getMinX(),
            (int) tileSize.getMaxY(),
            (int) tileSize.getMaxX(),
            (int) tileSize.getMaxY());
        graphics.drawString(
            tile.getId(), ((int) tileSize.getMaxX() - 113), ((int) tileSize.getMaxY() - 113));
      }
    } catch (Throwable t) {
      WMTPlugin.log("Error Rendering tile. Painting Tile " + tile.getId(), t); // $NON-NLS-1$
    }
  }
  private void renderNotRenderedTiles(
      Graphics2D destination,
      IProgressMonitor monitor,
      WMTRenderJob renderJob,
      TileRange range,
      RasterSymbolizer style,
      int tileWorth,
      int thisid,
      Set<String> notRenderedTiles,
      Set<String> renderedTiles)
      throws Exception {
    checkCancelState(monitor, thisid, false);

    // set the listener on the tile range
    range.addListener(listener);

    // load the missing tiles by sending requests for them
    range.loadTiles(monitor);

    // block until all the missing tiles have come through (and draw them
    // as they are added to the blocking queue
    while (!notRenderedTiles.isEmpty()) {
      // check that the rendering is not canceled
      checkCancelState(monitor, thisid, true);

      if (testing) {
        System.out.println("BLOCKED: " + thisid); // $NON-NLS-1$
        System.out.println(
            "waiting on: " + notRenderedTiles.size() + " tiles"); // $NON-NLS-1$ //$NON-NLS-2$
      }

      Tile tile = null;
      try {
        Object element = null;

        /* get the next tile that is ready to render,
         * check after 1 sec if the rendering was canceled
         */
        while ((element = tilesToDraw_queue.poll(1000, TimeUnit.MILLISECONDS)) == null) {
          checkCancelState(monitor, thisid, true);
        }

        tile = (Tile) element;

        if (testing) {
          System.out.println("removed from queue: " + tile.getId()); // $NON-NLS-1$
        }
      } catch (InterruptedException ex) {
        if (testing) {
          System.out.println("InterruptedException trying to take: " + ex); // $NON-NLS-1$
        }
      }

      if (testing) {
        System.out.println("UNBLOCKED!!!: " + thisid); // $NON-NLS-1$
      }

      // check that the rendering is not canceled again after block
      checkCancelState(monitor, thisid, true);

      // check that the tile's bounds are within the current
      // context's bounds (if it's not, don't bother drawing it) and also
      // only draw tiles that haven't already been drawn (panning fast
      // can result in listeners being notified the same tile is ready multiple
      // times but we don't want to draw it more than once per render cycle)
      // ReferencedEnvelope viewbounds = getContext().getViewportModel().getBounds();

      ReferencedEnvelope viewbounds =
          renderJob.projectMapToTileCrs(context.getViewportModel().getBounds());

      if (tile != null
          && tile.getBufferedImage() != null
          && viewbounds != null
          && viewbounds.intersects(tile.getBounds())
          && !renderedTiles.contains(tile.getId())
          && notRenderedTiles.contains(tile.getId())) {
        try {
          renderedTiles.add(tile.getId());
          renderTile(destination, (WMTTile) tile, style, renderJob);

        } catch (Exception exc) {
          WMTPlugin.log(
              "[BasicWMTRender.render] renderTile failed (2): " + tile.getId(), exc); // $NON-NLS-1$
        }
        monitor.worked(tileWorth); // inc the monitor work by 1 tile
        setState(RENDERING); // tell renderer new data is ready
      }

      // remove the tile from the not rendered list regardless
      // of whether it was actually drawn (this is to prevent
      // this render cycle from blocking endlessly waiting for tiles
      // that either didn't return or had some error)
      notRenderedTiles.remove(tile.getId());
    }
  }
  public void render(Graphics2D destination, Envelope bounds, IProgressMonitor monitor)
      throws RenderException {
    WMTPlugin.trace("[BasicWMTRender.render] is called"); // $NON-NLS-1$

    if (monitor == null) {
      monitor = new NullProgressMonitor();
    }
    monitor.beginTask("Render WMT", 100); // $NON-NLS-1$
    setState(STARTING);

    ILayer layer = null;
    try {
      layer = getContext().getLayer();
      // assume everything will work fine
      layer.setStatus(ILayer.DONE);
      layer.setStatusMessage(""); // $NON-NLS-1$

      WMTSource wmtSource = getWmtSourceFromLayer(layer);

      if (wmtSource == null)
        throw new UnsupportedOperationException(Messages.Render_Error_NoSource);

      // Layer properties
      WMTLayerProperties layerProperties =
          new WMTLayerProperties((StyleBlackboard) layer.getStyleBlackboard());

      // Get map extent, which should be drawn
      ReferencedEnvelope mapExtent = getRenderBounds();
      if (mapExtent == null) {
        mapExtent = context.getViewportModel().getBounds();
      }

      // Scale
      double scale = getContext().getViewportModel().getScaleDenominator();
      WMTPlugin.trace("[BasicWMTRender.render] Scale: " + scale); // $NON-NLS-1$

      WMTRenderJob renderJob = null;
      try {
        renderJob = WMTRenderJob.createRenderJob(mapExtent, scale, wmtSource);
      } catch (Exception exc) {
        throw new UnsupportedOperationException(Messages.Render_Error_Projection);
      }

      // Find tiles
      Map<String, Tile> tileList =
          wmtSource.cutExtentIntoTiles(
              renderJob, WMTRenderJob.getScaleFactor(), false, layerProperties);

      // if we have nothing to display, return
      if (tileList.isEmpty()) {
        throw new UnsupportedOperationException(Messages.Render_Error_NoData);
      }

      // check if this are too many tiles
      if ((tileList = checkTooManyTiles(layer, wmtSource, layerProperties, renderJob, tileList))
          .isEmpty()) {
        throw new UnsupportedOperationException(Messages.Render_Error_TooManyTiles);
      }

      // Download and display tiles

      // look up the preference for caching tiles on-disk or in
      // memory and use the proper tilerange for that.
      TileRange range = createTileRange(wmtSource, renderJob, tileList);

      // create an empty raster symbolizer for rendering
      RasterSymbolizer style = styleBuilder.createRasterSymbolizer();

      // setup how much each tile is worth for the monitor work %
      int tileCount = range.getTileCount();
      int tileWorth = (tileCount / 100) * tileCount;

      int thisid = 0;
      if (testing) {
        staticid++;
        thisid = staticid;
      }

      // first render any tiles that are ready and render non-ready tiles with blank images
      Map<String, Tile> tiles = range.getTiles();
      Set<String> notRenderedTiles = new HashSet<String>();
      Set<String> renderedTiles = new HashSet<String>();

      renderReadyTiles(
          destination,
          monitor,
          renderJob,
          style,
          tileWorth,
          thisid,
          tiles,
          notRenderedTiles,
          renderedTiles);

      setState(RENDERING);

      // if the tilerange is not already completed, then load
      // the missing tiles
      if (!notRenderedTiles.isEmpty()) {
        renderNotRenderedTiles(
            destination,
            monitor,
            renderJob,
            range,
            style,
            tileWorth,
            thisid,
            notRenderedTiles,
            renderedTiles);
      }

      if (testing) {
        System.out.println("DONE!!!: " + thisid); // $NON-NLS-1$
      }
    } catch (UnsupportedOperationException doneExc) {
      setDone(monitor);

      layer.setStatus(ILayer.ERROR);
      layer.setStatusMessage(doneExc.getMessage());
      WMTPlugin.log("[BasicWMTRenderer.render] Error: ", doneExc); // $NON-NLS-1$

      return;
    } catch (CancellationException cancelExc) {
      return;
    } catch (Exception ex) {
      WMTPlugin.log("[BasicWMTRenderer.render] Unexpected Error: ", ex); // $NON-NLS-1$
    }

    setDone(monitor);
  }