@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);
 }
  @Override
  protected URI getTileUri(
      URI commonUri,
      Transformer transformer,
      double minGeoX,
      double minGeoY,
      double maxGeoX,
      double maxGeoY,
      long w,
      long h)
      throws URISyntaxException, UnsupportedEncodingException {
    if (matrixIds != null) {
      PJsonArray topLeftCorner = matrix.getJSONArray(TOP_LEFT_CORNER);
      float factor = 1 / (matrix.getFloat(RESOLUTION) * w);
      int row = (int) Math.round((topLeftCorner.getDouble(1) - maxGeoY) * factor);
      int col = (int) Math.round((minGeoX - topLeftCorner.getDouble(0)) * factor);
      if (WMTSRequestEncoding.REST == requestEncoding) {
        String path = commonUri.getPath();
        for (int i = 0; i < dimensions.size(); i++) {
          String d = dimensions.getString(i);
          path = path.replace("{" + d + "}", dimensionsParams.getString(d.toUpperCase()));
        }
        path = path.replace("{TileMatrixSet}", matrixSet);
        path = path.replace("{TileMatrix}", matrix.getString("identifier"));
        path = path.replace("{TileRow}", String.valueOf(row));
        path = path.replace("{TileCol}", String.valueOf(col));

        return new URI(
            commonUri.getScheme(),
            commonUri.getUserInfo(),
            commonUri.getHost(),
            commonUri.getPort(),
            path,
            commonUri.getQuery(),
            commonUri.getFragment());
      } else {
        String query = "SERVICE=WMTS";
        query += "&REQUEST=GetTile";
        query += "&VERSION=" + version;
        query += "&LAYER=" + layer;
        query += "&STYLE=" + style;
        query += "&TILEMATRIXSET=" + matrixSet;
        query += "&TILEMATRIX=" + matrix.getString("identifier");
        query += "&TILEROW=" + row;
        query += "&TILECOL=" + col;
        query += "&FORMAT=" + format;
        if (dimensions != null) {
          for (int i = 0; i < dimensions.size(); i++) {
            String d = dimensions.getString(i);
            query += "&" + d + "=" + dimensionsParams.getString(d.toUpperCase());
          }
        }
        return new URI(
            commonUri.getScheme(),
            commonUri.getUserInfo(),
            commonUri.getHost(),
            commonUri.getPort(),
            commonUri.getPath(),
            query,
            commonUri.getFragment());
      }
    } else {
      double targetResolution = (maxGeoX - minGeoX) / w;
      WMTSLayerInfo.ResolutionInfo resolution =
          tileCacheLayerInfo.getNearestResolution(targetResolution);

      int col =
          (int)
              Math.round(
                  Math.floor(
                      ((maxGeoX + minGeoX) / 2 - tileOrigin.getDouble(0))
                          / (resolution.value * w)));
      int row =
          (int)
              Math.round(
                  Math.floor(
                      (tileOrigin.getDouble(1) - (maxGeoY + minGeoY) / 2)
                          / (resolution.value * h)));

      StringBuilder path = new StringBuilder();
      if (!commonUri.getPath().endsWith("/")) {
        path.append('/');
      }
      if (requestEncoding == WMTSRequestEncoding.REST) {
        path.append(version);
        path.append('/').append(layer);
        path.append('/').append(style);
        // Add dimensions
        if (dimensions != null) {
          for (int i = 0; i < dimensions.size(); i++) {
            path.append('/').append(dimensionsParams.getString(dimensions.getString(i)));
          }
        }
        path.append('/').append(matrixSet);
        path.append('/').append(resolution.index + zoomOffset);
        path.append('/').append(row);
        path.append('/').append(col);

        path.append('.').append(tileCacheLayerInfo.getExtension());

        return new URI(
            commonUri.getScheme(),
            commonUri.getUserInfo(),
            commonUri.getHost(),
            commonUri.getPort(),
            commonUri.getPath() + path,
            commonUri.getQuery(),
            commonUri.getFragment());
      } else {
        String query = "SERVICE=WMTS";
        query += "&REQUEST=GetTile";
        query += "&VERSION=" + version;
        query += "&LAYER=" + layer;
        query += "&STYLE=" + style;
        query += "&TILEMATRIXSET=" + matrixSet;
        String tileMatrix = "" + (resolution.index + zoomOffset);
        if (capabilitiesInfo != null && capabilitiesInfo.getTileMatrices().containsKey(matrixSet)) {
          final WMTSServiceInfo.TileMatrixSet tileMatrixSet =
              capabilitiesInfo.getTileMatrices().get(matrixSet);
          if (!tileMatrixSet.limits.containsKey(tileMatrix)) {
            // try to find a tileMatrix from capabilities that seems to match parameters
            final WMTSServiceInfo.TileMatrixLimit limit =
                tileMatrixSet.limits.get(matrixSet + ":" + tileMatrix);
            if (limit != null) {
              tileMatrix = limit.id;
            } else {
              for (WMTSServiceInfo.TileMatrixLimit l : tileMatrixSet.limits.values()) {
                if (l.id.endsWith(":" + tileMatrix)) {
                  tileMatrix = l.id;
                  break;
                }
              }
            }
          }
        }
        query += "&TILEMATRIX=" + tileMatrix;
        query += "&TILEROW=" + row;
        query += "&TILECOL=" + col;
        query += "&FORMAT=" + (formatSuffix.equals("png") ? "image/png" : "image/jpeg");
        if (dimensions != null) {
          for (int i = 0; i < dimensions.size(); i++) {
            String d = dimensions.getString(i);
            query += "&" + d + "=" + dimensionsParams.getString(d.toUpperCase());
          }
        }
        return new URI(
            commonUri.getScheme(),
            commonUri.getUserInfo(),
            commonUri.getHost(),
            commonUri.getPort(),
            commonUri.getPath(),
            query,
            commonUri.getFragment());
      }
    }
  }