Exemple #1
0
  /**
   * Returns true if the request is GWC compatible
   *
   * @param mapContent
   * @return
   */
  public static boolean isRequestGWCCompatible(WMSMapContent mapContent, Layer layer, WMS wms) {
    List<Layer> layers = mapContent.layers();
    for (int i = 0; i < layers.size(); i++) {
      if (layers.get(i) == layer) {
        return isRequestGWCCompatible(mapContent.getRequest(), i, wms);
      }
    }
    LOGGER.warning("Could not find map layer " + layer.getTitle() + " in the map context");

    return false;
  }
 Integer srid(WMSMapContent map) {
   Integer srid = null;
   try {
     srid = CRS.lookupEpsgCode(map.getCoordinateReferenceSystem(), false);
     if (srid == null) {
       srid = Integer.parseInt(map.getRequest().getSRS().split(":")[1]);
     }
   } catch (Exception ex) {
     LOGGER.log(Level.WARNING, "Error determining srid", ex);
   }
   return srid;
 }
Exemple #3
0
  /**
   * Encodes the url of a GetMap request from a map context + map layer.
   *
   * <p>If the <tt>Layer</tt> argument is <code>null</code>, the request is made including all
   * layers in the <tt>mapContexT</tt>.
   *
   * <p>If the <tt>bbox</tt> argument is <code>null</code>. {@link
   * WMSMapContent#getAreaOfInterest()} is used for the bbox parameter.
   *
   * @param mapContent The map context.
   * @param layer The Map layer, may be <code>null</code>.
   * @param layerIndex The index of the layer in the request.
   * @param bbox The bounding box of the request, may be <code>null</code>.
   * @param kvp Additional or overiding kvp parameters, may be <code>null</code>
   * @param tile Flag controlling whether the request should be made against tile cache
   * @param geoserver
   * @return The full url for a getMap request.
   * @deprecated use {@link WMSRequests#getGetMapUrl(WMSMapContent, Layer, Envelope, String[])}
   */
  public static String getMapUrl(
      WMSMapContent mapContent,
      Layer layer,
      int layerIndex,
      Envelope bbox,
      String[] kvp,
      boolean tile,
      GeoServer geoserver) {

    if (tile) {
      org.geoserver.wms.GetMapRequest request = mapContent.getRequest();
      return WMSRequests.getTiledGetMapUrl(geoserver, request, layer, layerIndex, bbox, kvp);
    }

    return WMSRequests.getGetMapUrl(mapContent.getRequest(), layer, layerIndex, bbox, kvp);
  }
  /*
   * Adds a feature layer to the geopackage.
   */
  void addFeatureLayer(
      GeoPackage geopkg, FeatureLayer layer, MapLayerInfo mapLayer, WMSMapContent map)
      throws IOException {

    FeatureEntry e = new FeatureEntry();
    initEntry(e, layer, mapLayer, map);

    Filter filter = layer.getQuery().getFilter();
    GeometryDescriptor gd = mapLayer.getFeature().getFeatureType().getGeometryDescriptor();
    if (gd != null) {
      Envelope bnds = bounds(map);
      BBOX bboxFilter =
          filterFactory.bbox(
              gd.getLocalName(),
              bnds.getMinX(),
              bnds.getMinY(),
              bnds.getMaxX(),
              bnds.getMaxY(),
              map.getRequest().getSRS());
      filter = filterFactory.and(filter, bboxFilter);
    }

    LOGGER.fine("Creating feature entry" + e.getTableName());
    geopkg.add(e, layer.getSimpleFeatureSource(), filter);
  }
 String findBestFormat(WMSMapContent map) {
   // if request is a single coverage layer return jpeg, otherwise use just png
   List<MapLayerInfo> layers = map.getRequest().getLayers();
   if (layers.size() == 1 && layers.get(0).getType() == MapLayerInfo.TYPE_RASTER) {
     return JPEG_MIME_TYPE;
   }
   return PNG_MIME_TYPE;
 }
    public KMLGeometryTranslator(
        ContentHandler handler, int numDecimals, boolean useDummyZ, WMSMapContent context) {
      // super(handler, "kml", "http://earth.google.com/kml/2.0" );
      super(handler, null, null, numDecimals, useDummyZ, 3);
      coordWriter = new KMLCoordinateWriter(numDecimals, useDummyZ);

      String extrudeValue = (String) context.getRequest().getFormatOptions().get("extrude");
      if (extrudeValue != null) {
        extrude = Boolean.valueOf(extrudeValue).booleanValue();
      }

      String requestedAltitudeMode =
          (String) context.getRequest().getFormatOptions().get("altitudeMode");
      if (requestedAltitudeMode != null) {
        for (String mode : validAltitudeModes) {
          if (mode.equalsIgnoreCase(requestedAltitudeMode.trim())) {
            altitudeMode = mode;
          }
        }
      }
    }
  @Test
  public void testExternalImageSize() throws Exception {
    GetMapRequest req = createGetMapRequest(MockData.STREAMS);
    req.setWidth(256);
    req.setHeight(256);

    WMSMapContent mapContent = new WMSMapContent(req);
    mapContent.addLayer(createMapLayer(MockData.STREAMS, "big-local-image"));

    mapContent
        .getViewport()
        .setBounds(new ReferencedEnvelope(-180, 0, -90, 90, DefaultGeographicCRS.WGS84));
    mapContent.setMapHeight(256);
    mapContent.setMapWidth(256);

    KMLMapOutputFormat of = new KMLMapOutputFormat(getWMS());
    KMLMap map = of.produceMap(mapContent);

    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    new KMLEncoder().encode(map.getKml(), bout, null);

    Document document = dom(new ByteArrayInputStream(bout.toByteArray()));

    assertEquals("kml", document.getDocumentElement().getNodeName());
    assertEquals(1, document.getElementsByTagName("Style").getLength());

    XMLAssert.assertXpathExists("//kml:IconStyle/kml:scale", document);

    XPath xPath = XPathFactory.newInstance().newXPath();
    initXPath(xPath);

    Double scale =
        (Double)
            xPath.evaluate(
                "//kml:IconStyle/kml:scale", document.getDocumentElement(), XPathConstants.NUMBER);
    assertEquals(42d / 16d, scale, 0.01);
  }
  int[] findMinMaxZoom(GridSubset gridSubset, WMSMapContent map) {
    GridSet gridSet = gridSubset.getGridSet();
    Map formatOpts = map.getRequest().getFormatOptions();

    Integer minZoom = null;
    if (formatOpts.containsKey("min_zoom")) {
      minZoom = Integer.parseInt(formatOpts.get("min_zoom").toString());
    }
    if (minZoom == null) {
      minZoom = findClosestZoom(gridSet, map);
    }

    Integer maxZoom = null;
    if (formatOpts.containsKey("max_zoom")) {
      maxZoom = Integer.parseInt(formatOpts.get("max_zoom").toString());
    } else if (formatOpts.containsKey("num_zooms")) {
      maxZoom = minZoom + Integer.parseInt(formatOpts.get("num_zooms").toString());
    }

    if (maxZoom == null) {
      // walk down until we hit too many tiles
      maxZoom = findMaxZoomAuto(gridSubset, minZoom, map);
    }

    if (maxZoom < minZoom) {
      throw new ServiceException(
          format("maxZoom (%d) can not be less than minZoom (%d)", maxZoom, minZoom));
    }

    // end index
    if (maxZoom > gridSet.getNumLevels()) {
      LOGGER.warning(
          format(
              "Max zoom (%d) can't be greater than number of zoom levels (%d)",
              maxZoom, gridSet.getNumLevels()));
      maxZoom = gridSet.getNumLevels();
    }

    return new int[] {minZoom, maxZoom};
  }
  WMSMapContent createMapContext(QName layer, String style) throws Exception {

    // create a map context
    WMSMapContent mapContent = new WMSMapContent();
    mapContent.addLayer(createMapLayer(layer, style));
    mapContent.setMapHeight(256);
    mapContent.setMapWidth(256);

    GetMapRequest getMapRequest = createGetMapRequest(new QName[] {layer});
    getMapRequest.setWidth(256);
    getMapRequest.setHeight(256);

    mapContent.setRequest(getMapRequest);
    mapContent
        .getViewport()
        .setBounds(new ReferencedEnvelope(-180, 180, -90, 90, DefaultGeographicCRS.WGS84));
    return mapContent;
  }
  /**
   * Creates the {@link RenderedImage} corresponding to the tile at index {@code tileIdx} and uses a
   * {@link RenderedImageMapResponse} to encode it into the {@link #getResponseFormat() response
   * format}.
   *
   * @see org.geowebcache.layer.MetaTile#writeTileToStream(int, org.geowebcache.io.Resource)
   * @see RenderedImageMapResponse#write
   */
  @Override
  public boolean writeTileToStream(final int tileIdx, Resource target) throws IOException {

    checkNotNull(metaTileMap, "webMap is not set");
    if (!(metaTileMap instanceof RenderedImageMap)) {
      throw new IllegalArgumentException(
          "Only RenderedImageMaps are supported so far: " + metaTileMap.getClass().getName());
    }
    final RenderedImageMapResponse mapEncoder;
    {
      final GWC mediator = GWC.get();
      final Response responseEncoder = mediator.getResponseEncoder(responseFormat, metaTileMap);
      mapEncoder = (RenderedImageMapResponse) responseEncoder;
    }

    RenderedImage tile = metaTileMap.getImage();
    WMSMapContent tileContext = metaTileMap.getMapContext();

    if (this.tiles.length > 1 || (this.tiles.length == 1 && metaHasGutter())) {
      final Rectangle tileDim = this.tiles[tileIdx];
      tile = createTile(tileDim.x, tileDim.y, tileDim.width, tileDim.height);
      disposeLater(tile);
      {
        final WMSMapContent metaTileContext = metaTileMap.getMapContext();
        // do not create tileContext with metaTileContext.getLayers() as the layer list.
        // It is not needed at this stage and the constructor would force a
        // MapLayer.getBounds() that might fail
        tileContext = new WMSMapContent();
        tileContext.setRequest(metaTileContext.getRequest());
        tileContext.setBgColor(metaTileContext.getBgColor());
        tileContext.setMapWidth(tileDim.width);
        tileContext.setMapHeight(tileDim.height);
        tileContext.setPalette(metaTileContext.getPalette());
        tileContext.setTransparent(tileContext.isTransparent());
        long[][] tileIndexes = getTilesGridPositions();
        BoundingBox tileBounds = gridSubset.boundsFromIndex(tileIndexes[tileIdx]);
        ReferencedEnvelope tilebbox =
            new ReferencedEnvelope(metaTileContext.getCoordinateReferenceSystem());
        tilebbox.init(
            tileBounds.getMinX(), tileBounds.getMaxX(), tileBounds.getMinY(), tileBounds.getMaxY());
        tileContext.getViewport().setBounds(tilebbox);
      }
    }

    OutputStream outStream = target.getOutputStream();
    try {
      // call formatImageOuputStream instead of write to avoid disposition of rendered images
      // when processing a tile from a metatile and instead defer it to this class' dispose()
      // method
      mapEncoder.formatImageOutputStream(tile, outStream, tileContext);
      return true;
    } finally {
      outStream.close();
    }
  }
  @Override
  public WebMap produceMap(WMSMapContent map) throws ServiceException, IOException {
    GeoPackage geopkg = new GeoPackage();
    geopkg.init();

    GetMapRequest req = map.getRequest();

    List<Layer> layers = map.layers();
    List<MapLayerInfo> mapLayers = req.getLayers();

    Preconditions.checkState(
        layers.size() == mapLayers.size(),
        "Number of map layers not same as number of rendered layers");

    // list of layers to render directly and include as tiles
    List<MapLayerInfo> tileLayers = new ArrayList();

    // check mode, one of:
    // vector - render vector layers as feature entries and all else as tiles (default)
    // hybrid - render vector layers as feature entries, raster layers as raster entries, all
    //          others as tile entries
    // tiled - all layers as a single tile set
    Map formatOpts = req.getFormatOptions();
    Mode mode =
        formatOpts.containsKey("mode")
            ? Mode.valueOf(((String) formatOpts.get("mode")).toUpperCase())
            : Mode.VECTOR;

    if (mode == Mode.TILED) {
      // tiled mode means render all as map tile layer
      tileLayers.addAll(mapLayers);
    } else {

      // hybrid mode, dump as raw vector or raster unless the request specifically asks for a
      // layer to be rendered as tiles
      for (int i = 0; i < layers.size(); i++) {
        Layer layer = layers.get(i);
        MapLayerInfo mapLayer = mapLayers.get(i);

        if (layer instanceof FeatureLayer) {
          addFeatureLayer(geopkg, (FeatureLayer) layer, mapLayer, map);
        } else if (layer instanceof GridCoverageLayer) {
          if (mode == Mode.HYBRID) {
            addCoverageLayer(geopkg, (GridCoverageLayer) layer, mapLayer, map);
          } else {
            tileLayers.add(mapLayer);
          }
        } else {
          tileLayers.add(mapLayer);
        }
      }
    }

    addTileLayers(geopkg, tileLayers, map);

    geopkg.close();

    final File dbFile = geopkg.getFile();
    final BufferedInputStream bin = new BufferedInputStream(new FileInputStream(dbFile));

    RawMap result =
        new RawMap(map, bin, MIME_TYPE) {
          @Override
          public void writeTo(OutputStream out) throws IOException {
            String dbFilename = getAttachmentFileName();
            if (dbFilename != null) {
              dbFilename = dbFilename.substring(0, dbFilename.length() - 4) + ".gpkg";
            } else {
              // this shouldn't really ever happen, but fallback anyways
              dbFilename = "geoserver.gpkg";
            }

            IOUtils.copy(bin, out);
            out.flush();

            //               JD: disabling zip compression for now
            //                ZipOutputStream zout = new ZipOutputStream(out);
            //                zout.putNextEntry(new ZipEntry(dbFilename));
            //
            //                super.writeTo(zout);
            //                zout.closeEntry();
            //                zout.close();

            bin.close();
            try {
              dbFile.delete();
            } catch (Exception e) {
              LOGGER.log(Level.WARNING, "Error deleting file: " + dbFile.getAbsolutePath(), e);
            }
          }
        };

    result.setContentDispositionHeader(map, ".gpkg", true);
    return result;
  }
Exemple #12
0
  /**
   * Loads the feature collection based on the current styling and the scale denominator. If no
   * feature is going to be returned a null feature collection will be returned instead
   *
   * @param featureSource
   * @param layer
   * @param mapContent
   * @param wms
   * @param scaleDenominator
   * @return
   * @throws Exception
   */
  public static SimpleFeatureCollection loadFeatureCollection(
      SimpleFeatureSource featureSource,
      Layer layer,
      WMSMapContent mapContent,
      WMS wms,
      double scaleDenominator)
      throws Exception {
    SimpleFeatureType schema = featureSource.getSchema();

    Envelope envelope = mapContent.getRenderingArea();
    ReferencedEnvelope aoi =
        new ReferencedEnvelope(envelope, mapContent.getCoordinateReferenceSystem());
    CoordinateReferenceSystem sourceCrs = schema.getCoordinateReferenceSystem();

    boolean reprojectBBox =
        (sourceCrs != null)
            && !CRS.equalsIgnoreMetadata(aoi.getCoordinateReferenceSystem(), sourceCrs);
    if (reprojectBBox) {
      aoi = aoi.transform(sourceCrs, true);
    }

    Filter filter = createBBoxFilter(schema, aoi);

    // now build the query using only the attributes and the bounding
    // box needed
    Query q = new Query(schema.getTypeName());
    q.setFilter(filter);

    // now, if a definition query has been established for this layer,
    // be sure to respect it by combining it with the bounding box one.
    Query definitionQuery = layer.getQuery();

    if (definitionQuery != Query.ALL) {
      if (q == Query.ALL) {
        q = (Query) definitionQuery;
      } else {
        q = (Query) DataUtilities.mixQueries(definitionQuery, q, "KMLEncoder");
      }
    }

    // handle startIndex requested by client query
    q.setStartIndex(definitionQuery.getStartIndex());

    // check the regionating strategy
    RegionatingStrategy regionatingStrategy = null;
    String stratname = (String) mapContent.getRequest().getFormatOptions().get("regionateBy");
    if (("auto").equals(stratname)) {
      Catalog catalog = wms.getGeoServer().getCatalog();
      Name name = layer.getFeatureSource().getName();
      stratname =
          catalog
              .getFeatureTypeByName(name)
              .getMetadata()
              .get("kml.regionateStrategy", String.class);
      if (stratname == null || "".equals(stratname)) {
        stratname = "best_guess";
        LOGGER.log(
            Level.FINE,
            "No default regionating strategy has been configured in "
                + name
                + "; using automatic best-guess strategy.");
      }
    }

    if (stratname != null) {
      regionatingStrategy = findStrategyByName(stratname);

      // if a strategy was specified but we did not find it, let the user
      // know
      if (regionatingStrategy == null)
        throw new ServiceException("Unknown regionating strategy " + stratname);
    }

    // try to load less features by leveraging regionating strategy and the
    // SLD
    Filter regionatingFilter = Filter.INCLUDE;

    if (regionatingStrategy != null)
      regionatingFilter = regionatingStrategy.getFilter(mapContent, layer);

    Filter ruleFilter =
        summarizeRuleFilters(
            getLayerRules(featureSource.getSchema(), layer.getStyle()), scaleDenominator);
    Filter finalFilter = joinFilters(q.getFilter(), ruleFilter, regionatingFilter);
    if (finalFilter == Filter.EXCLUDE) {
      // if we don't have any feature to return
      return null;
    }
    q.setFilter(finalFilter);

    // make sure we output in 4326 since that's what KML mandates
    CoordinateReferenceSystem wgs84;
    try {
      wgs84 = CRS.decode("EPSG:4326");
    } catch (Exception e) {
      throw new RuntimeException(
          "Cannot decode EPSG:4326, the CRS subsystem must be badly broken...");
    }
    if (sourceCrs != null && !CRS.equalsIgnoreMetadata(wgs84, sourceCrs)) {
      return new ReprojectFeatureResults(featureSource.getFeatures(q), wgs84);
    }

    return featureSource.getFeatures(q);
  }
Exemple #13
0
 /**
  * Encodes the url for a GetLegendGraphic request from a map context + map layer.
  *
  * @param mapContent The map context.
  * @param layer The map layer.
  * @param kvp Additional or overidding kvp parameters, may be <code>null</code>
  *
  * @return A map containing all the key value pairs for a GetLegendGraphic request.
  * @deprecated use {@link WMSRequests#getGetLegendGraphicUrl(WMSMapContent, Layer, String[])
  */
 public static String getLegendGraphicUrl(WMSMapContent mapContent, Layer layer, String[] kvp) {
   return WMSRequests.getGetLegendGraphicUrl(mapContent.getRequest(), layer, kvp);
 }
Exemple #14
0
 /**
  * Encodes the url of a GetMap request from a map context + map layer.
  *
  * <p>If the <tt>Layer</tt> argument is <code>null</code>, the request is made including all
  * layers in the <tt>mapContexT</tt>.
  *
  * @param mapContent The map context.
  * @param layer The Map layer, may be <code>null</code>
  * @param layerIndex The index of the layer in the request.
  * @param kvp Additional or overidding kvp parameters, may be <code>null</code>
  * @param tile Flag controlling wether the request should be made against tile cache
  * @param geoserver
  * @return The full url for a getMap request.
  * @deprecated use {@link WMSRequests#getGetMapUrl(WMSMapContent, Layer, int, Envelope, String[])}
  */
 public static String getMapUrl(
     WMSMapContent mapContent, Layer layer, int layerIndex, boolean tile, GeoServer geoserver) {
   return getMapUrl(
       mapContent, layer, layerIndex, mapContent.getRenderingArea(), null, tile, geoserver);
 }
  void addTileLayers(GeoPackage geopkg, List<MapLayerInfo> mapLayers, WMSMapContent map)
      throws IOException {

    if (mapLayers.isEmpty()) {
      return;
    }

    // figure out a name for the file entry
    String tileEntryName = null;
    Map formatOpts = map.getRequest().getFormatOptions();
    if (formatOpts.containsKey("tileset_name")) {
      tileEntryName = (String) formatOpts.get("tileset_name");
    }
    if (tileEntryName == null) {
      tileEntryName = map.getTitle();
    }
    if (tileEntryName == null && mapLayers.size() == 1) {
      Iterator<MapLayerInfo> it = mapLayers.iterator();
      tileEntryName = it.next().getLayerInfo().getName();
    }

    GridSubset gridSubset = findBestGridSubset(map);
    int[] minmax = findMinMaxZoom(gridSubset, map);

    BoundingBox bbox = bbox(map);

    TileEntry e = new TileEntry();
    e.setTableName(tileEntryName);

    if (mapLayers.size() == 1) {
      ResourceInfo r = mapLayers.get(0).getResource();
      e.setIdentifier(r.getTitle());
      e.setDescription(r.getAbstract());
    }
    e.setBounds(
        new ReferencedEnvelope(
            findTileBounds(gridSubset, bbox, minmax[0]), map.getCoordinateReferenceSystem()));
    e.setSrid(srid(map));

    GridSet gridSet = gridSubset.getGridSet();
    for (int z = minmax[0]; z < minmax[1]; z++) {
      Grid g = gridSet.getGrid(z);

      TileMatrix m = new TileMatrix();
      m.setZoomLevel(z);
      m.setMatrixWidth((int) g.getNumTilesWide());
      m.setMatrixHeight((int) g.getNumTilesHigh());
      m.setTileWidth(gridSubset.getTileWidth());
      m.setTileHeight(gridSubset.getTileHeight());

      // TODO: not sure about this
      m.setXPixelSize(g.getResolution());
      m.setYPixelSize(g.getResolution());
      // m.setXPixelSize(gridSet.getPixelSize());
      // m.setYPixelSize(gridSet.getPixelSize());

      e.getTileMatricies().add(m);
    }

    // figure out the actual bounds of the tiles to be renderered
    LOGGER.fine("Creating tile entry" + e.getTableName());
    geopkg.create(e);

    // create a prototype getmap request
    GetMapRequest req = new GetMapRequest();
    OwsUtils.copy(map.getRequest(), req, GetMapRequest.class);
    req.setLayers(mapLayers);

    String imageFormat =
        formatOpts.containsKey("format") ? parseFormatFromOpts(formatOpts) : findBestFormat(map);

    req.setFormat(imageFormat);
    req.setWidth(gridSubset.getTileWidth());
    req.setHeight(gridSubset.getTileHeight());

    // count tiles as we generate them
    int ntiles = 0;

    // flag determining if tile row indexes we store in database should be inverted
    boolean flipy = Boolean.valueOf((String) formatOpts.get("flipy"));
    for (int z = minmax[0]; z < minmax[1]; z++) {
      long[] intersect = gridSubset.getCoverageIntersection(z, bbox);
      for (long x = intersect[0]; x <= intersect[2]; x++) {
        for (long y = intersect[1]; y <= intersect[3]; y++) {
          BoundingBox box = gridSubset.boundsFromIndex(new long[] {x, y, z});
          req.setBbox(new Envelope(box.getMinX(), box.getMaxX(), box.getMinY(), box.getMaxY()));

          Tile t = new Tile();
          t.setZoom(z);
          t.setColumn((int) x);
          t.setRow((int) (flipy ? gridSubset.getNumTilesHigh(z) - (y + 1) : y));

          WebMap result = webMapService.getMap(req);
          t.setData(toBytes(result));

          geopkg.add(e, t);

          // images we encode are actually kept around, we need to clean them up
          if (ntiles++ == TILE_CLEANUP_INTERVAL) {
            cleanUpImages();
            ntiles = 0;
          }
        }
      }
    }
  }
 ReferencedEnvelope bounds(WMSMapContent map) {
   return new ReferencedEnvelope(map.getRequest().getBbox(), map.getCoordinateReferenceSystem());
 }
  GridSubset findBestGridSubset(WMSMapContent map) {
    GetMapRequest req = map.getRequest();
    Map formatOpts = req.getFormatOptions();

    GridSetBroker gridSetBroker = gwc.getGridSetBroker();
    GridSet gridSet = null;

    // first check format options to see if explicitly specified
    if (formatOpts.containsKey("gridset")) {
      gridSet = gridSetBroker.get(formatOpts.get("gridset").toString());
    }

    // next check srs
    if (gridSet == null) {
      gridSet = gridSetBroker.get(req.getSRS().toUpperCase());
    }

    if (gridSet != null) {
      return GridSubsetFactory.createGridSubSet(gridSet);
    }

    CoordinateReferenceSystem crs = map.getCoordinateReferenceSystem();

    // look up epsg code
    Integer epsgCode = null;
    try {
      epsgCode = CRS.lookupEpsgCode(crs, false);
    } catch (Exception e) {
      throw new ServiceException("Unable to determine epsg code for " + crs, e);
    }
    if (epsgCode == null) {
      throw new ServiceException("Unable to determine epsg code for " + crs);
    }

    SRS srs = SRS.getSRS(epsgCode);

    // figure out the appropriate grid sub set
    Set<GridSubset> gridSubsets = new LinkedHashSet<GridSubset>();
    for (MapLayerInfo l : req.getLayers()) {
      TileLayer tl = gwc.getTileLayerByName(l.getName());
      if (tl == null) {
        throw new ServiceException("No tile layer for " + l.getName());
      }

      List<GridSubset> theseGridSubsets = tl.getGridSubsetsForSRS(srs);
      if (gridSubsets.isEmpty()) {
        gridSubsets.addAll(theseGridSubsets);
      } else {
        gridSubsets.retainAll(theseGridSubsets);
      }

      if (gridSubsets.isEmpty()) {
        throw new ServiceException(
            "No suitable " + epsgCode + " grid subset for " + req.getLayers());
      }
    }

    if (gridSubsets.size() > 1) {
      if (LOGGER.isLoggable(Level.WARNING)) {
        StringBuilder msg = new StringBuilder("Found multiple grid subsets: ");
        for (GridSubset gs : gridSubsets) {
          msg.append(gs.getName()).append(", ");
        }
        msg.setLength(msg.length() - 2);
        msg.append(". Choosing first.");
        LOGGER.warning(msg.toString());
      }
    }

    return gridSubsets.iterator().next();
  }