/** fix the resolution to something compatible with the resolutions available in tilecache. */
  private Transformer fixTiledTransformer(Transformer transformer) {
    double resolution;

    // if clientResolution is passed from client use it explicitly if available otherwise calculate
    // nearest resolution
    if (this.context.getCurrentPageParams().has("clientResolution")) {
      float clientResolution = this.context.getCurrentPageParams().getFloat("clientResolution");
      boolean hasServerResolution = false;
      for (double serverResolution : this.tileCacheLayerInfo.getResolutions()) {
        if (serverResolution == clientResolution) {
          hasServerResolution = true;
        }
      }
      if (!hasServerResolution) {
        return null;
      } else {
        resolution = clientResolution;
      }
    } else {
      double targetResolution = (transformer.getGeoW() / transformer.getStraightBitmapW());
      TileCacheLayerInfo.ResolutionInfo resolutionInfo =
          tileCacheLayerInfo.getNearestResolution(targetResolution);
      resolution = resolutionInfo.value;
    }
    // TODO Gucken was mit diesem Transformer gemacht wird!!!
    transformer = transformer.clone();
    transformer.setResolution(resolution);
    return transformer;
  }
 @Override
 protected void renderTiles(
     TileRenderer formatter,
     Transformer transformer,
     URI commonUri,
     ParallelMapTileLoader parallelMapTileLoader)
     throws IOException, URISyntaxException {
   if (matrixIds != null) {
     double diff = Double.POSITIVE_INFINITY;
     double targetResolution = transformer.getGeoW() / transformer.getStraightBitmapW();
     for (int i = 0; i < matrixIds.size(); i++) {
       PJsonObject matrixId = matrixIds.getJSONObject(i);
       float resolution = matrixId.getFloat(RESOLUTION);
       double delta = Math.abs(1 - resolution / targetResolution);
       if (delta < diff) {
         diff = delta;
         matrix = matrixId;
       }
     }
     float resolution = matrix.getFloat(RESOLUTION);
     PJsonArray tileSize = matrix.getJSONArray(TILE_SIZE);
     PJsonArray topLeftCorner = matrix.getJSONArray(TOP_LEFT_CORNER);
     PJsonArray matrixSize = matrix.getJSONArray(MATRIX_SIZE);
     tileCacheLayerInfo =
         new TileCacheLayerInfo(
             String.valueOf(resolution),
             tileSize.getInt(0),
             tileSize.getInt(1),
             topLeftCorner.getFloat(0),
             topLeftCorner.getFloat(1) - tileSize.getInt(1) * matrixSize.getInt(1) * resolution,
             topLeftCorner.getFloat(0) + tileSize.getInt(0) * matrixSize.getInt(0) * resolution,
             topLeftCorner.getFloat(1),
             format);
   }
   super.renderTiles(formatter, transformer, commonUri, parallelMapTileLoader);
 }
  protected void renderTiles(
      TileRenderer formater,
      Transformer transformer,
      URI commonUri,
      ParallelMapTileLoader parallelMapTileLoader)
      throws IOException, URISyntaxException {
    final List<URI> urls = new ArrayList<URI>(1);
    final double offsetX;
    final double offsetY;
    final long bitmapTileW;
    final long bitmapTileH;
    int nbTilesW = 0;

    double minGeoX = transformer.getRotatedMinGeoX();
    double minGeoY = transformer.getRotatedMinGeoY();
    double maxGeoX = transformer.getRotatedMaxGeoX();
    double maxGeoY = transformer.getRotatedMaxGeoY();

    if (tileCacheLayerInfo != null) {
      // tiled
      transformer = fixTiledTransformer(transformer);

      if (transformer == null) {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Resolution out of bounds.");
        }
        urls.add(null);
      }

      bitmapTileW = tileCacheLayerInfo.getWidth();
      bitmapTileH = tileCacheLayerInfo.getHeight();
      final double tileGeoWidth = transformer.getResolution() * bitmapTileW;
      final double tileGeoHeight = transformer.getResolution() * bitmapTileH;

      // TODO I would like to do this sort of thing by extension points for plugins

      // the tileMinGeoSize is not calculated the same way in TileCache
      // and KaMap, so they are treated differently here.
      final double tileMinGeoX;
      final double tileMinGeoY;
      if (this instanceof TileCacheMapReader) {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("TileCacheMapReader min geo x and y calculation used");
        }
        tileMinGeoX =
            (float)
                    (Math.floor((minGeoX - tileCacheLayerInfo.getMinX()) / tileGeoWidth)
                        * tileGeoWidth)
                + tileCacheLayerInfo.getMinX();
        tileMinGeoY =
            (float)
                    (Math.floor((minGeoY - tileCacheLayerInfo.getMinY()) / tileGeoHeight)
                        * tileGeoHeight)
                + tileCacheLayerInfo.getMinY();
      } else if (this instanceof KaMapCacheMapReader || this instanceof KaMapMapReader) {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Kamap min geo x and y calculation used");
        }
        tileMinGeoX = (float) (Math.floor((minGeoX) / tileGeoWidth) * tileGeoWidth);
        tileMinGeoY = (float) (Math.floor((minGeoY) / tileGeoHeight) * tileGeoHeight);
      } else if (this instanceof WMTSMapReader) {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("WMTS min geo x and y calculation used");
        }

        tileMinGeoX =
            (Math.floor((minGeoX - tileCacheLayerInfo.getMinX()) / tileGeoWidth) * tileGeoWidth)
                + tileCacheLayerInfo.getMinX();
        tileMinGeoY =
            tileCacheLayerInfo.getMaxY()
                - ((Math.ceil((tileCacheLayerInfo.getMaxY() - minGeoY) / tileGeoHeight))
                    * tileGeoHeight);
      } else {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Default min geo x and y calculation used");
        }
        tileMinGeoX =
            (float)
                    (Math.floor((minGeoX - tileCacheLayerInfo.getMinX()) / tileGeoWidth)
                        * tileGeoWidth)
                + tileCacheLayerInfo.getMinX();
        tileMinGeoY =
            (float)
                    (Math.floor((minGeoY - tileCacheLayerInfo.getMinY()) / tileGeoHeight)
                        * tileGeoHeight)
                + tileCacheLayerInfo.getMinY();
      }

      // Linksverschiebung des Kachelstartpunktes in Pixel
      offsetX = (minGeoX - tileMinGeoX) / transformer.getResolution();
      // Höhenverschiebung (nach unten) des Kachelstartpunktes in Pixel
      offsetY = (minGeoY - tileMinGeoY) / transformer.getResolution();
      for (double geoY = tileMinGeoY; geoY < maxGeoY; geoY += tileGeoHeight) {
        nbTilesW = 0;
        for (double geoX = tileMinGeoX; geoX < maxGeoX; geoX += tileGeoWidth) {
          nbTilesW++;

          if (tileCacheLayerInfo.isVisible(geoX, geoY, geoX + tileGeoWidth, geoY + tileGeoHeight)) {
            double exactLeftX = 958760.7160000019;
            double exactBottomY = 6389320.947999999;
            boolean positionCorrectX = geoX <= (exactLeftX + 0.25) && geoX >= (exactLeftX - 0.25);
            boolean positionCorrectY =
                geoY <= (exactBottomY + 0.25) && geoY >= (exactBottomY - 0.25);
            boolean positionCorrect = positionCorrectX && positionCorrectY;
            LOGGER.debug(positionCorrect ? "Position Correct" : "Position Incorrect");
            urls.add(
                getTileUri(
                    commonUri,
                    transformer,
                    geoX,
                    geoY,
                    geoX + tileGeoWidth,
                    geoY + tileGeoHeight,
                    bitmapTileW,
                    bitmapTileH));
          } else {
            if (LOGGER.isDebugEnabled()) {
              LOGGER.debug(
                  "Tile out of bounds: "
                      + getTileUri(
                          commonUri,
                          transformer,
                          geoX,
                          geoY,
                          geoX + tileGeoWidth,
                          geoY + tileGeoHeight,
                          bitmapTileW,
                          bitmapTileH));
            }
            urls.add(null);
          }
        }
      }

    } else {
      // single tile
      nbTilesW = 1;
      offsetX = 0;
      offsetY = 0;
      bitmapTileW = transformer.getRotatedBitmapW();
      bitmapTileH = transformer.getRotatedBitmapH();
      urls.add(
          getTileUri(
              commonUri,
              transformer,
              minGeoX,
              minGeoY,
              maxGeoX,
              maxGeoY,
              bitmapTileW,
              bitmapTileH));
    }

    // TODO Hier wird falsch scaliert
    formater.render(
        transformer,
        urls,
        parallelMapTileLoader,
        context,
        opacity,
        nbTilesW,
        offsetX,
        offsetY,
        bitmapTileW,
        bitmapTileH);
  }