The code is below. Take a look maybe you will find it useful. Beside the major change in functionality I made several minor changes (like making some fields private or using different logger class) to adapt it to my app. So you can simply ignore those.
The major changes are in class *WMSCoverageReader*. The field mapRequest is now of type List<MapRequest> because of the need to call WMS server several times to get several images and bind them together. There are two new fields: *maxWidth* and *maxHeight*. Those are assigned in the constructor and hold the maximum sizes of the image that the given WMS service can return. As for the methods, only two are hugely different: *getMap* and *initMapRequest*. In general *initMapRequest* now initiates not a single request but several (if there is such need) to get smaller parts of the whole image if its size exceeds the maximum values. Those images are fetched 'glued' together in *getMap* method. The changes will be more clear if you simply compare your code and mine The class *WMSLayer* was changed only because it referenced the field *mapRequest* of the coverage reader class. So I had to adapt it a bit. But no logic was changed there. And here is the code, as promised. WMSCoverageReader import java.awt.Color; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.imageio.ImageIO; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader; import org.geotools.coverage.grid.io.AbstractGridFormat; import org.geotools.data.ows.Layer; import org.geotools.data.wms.WebMapServer; import org.geotools.data.wms.request.GetFeatureInfoRequest; import org.geotools.data.wms.request.GetMapRequest; import org.geotools.data.wms.response.GetFeatureInfoResponse; import org.geotools.data.wms.response.GetMapResponse; import org.geotools.geometry.DirectPosition2D; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.ows.ServiceException; import org.geotools.referencing.CRS; import org.geotools.renderer.lite.RendererUtilities; import org.opengis.coverage.grid.Format; import org.opengis.geometry.Envelope; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.parameter.GeneralParameterValue; import org.opengis.parameter.ParameterValue; import org.opengis.referencing.ReferenceIdentifier; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.cs.CartesianCS; import org.opengis.referencing.operation.TransformException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A grid coverage readers backing onto a WMS server by issuing GetMap */ /* package */class WMSCoverageReader extends AbstractGridCoverage2DReader { private static final Logger LOGGER = LoggerFactory.getLogger(WMSCoverageReader.class); private static final GridCoverageFactory gcf = new GridCoverageFactory(); /** The WMS server. */ WebMapServer wms; /** * The layer */ List<Layer> layers = new ArrayList<Layer>(); /** * The chosen SRS name */ private String srsName; /** * The format to use for requests */ private String format; /** * The last GetMap request */ private List<List<GetMapRequest>> mapRequests = new ArrayList<>(); private GetMapRequest lastMapRequest; /** * The last GetMap response */ private GridCoverage2D grid; /** * The set of SRS common to all layers */ Set<String> validSRS; /** * The cached layer bounds */ ReferencedEnvelope bounds; /** * The last request envelope */ private ReferencedEnvelope requestedEnvelope; /** * Last request width */ private int width; /** * Last request height */ private int height; /** * Last request CRS (used for reprojected GetFeatureInfo) */ CoordinateReferenceSystem requestCRS; /** The maximum available width of the image returned by WMS server. */ private int maxWidth; /** The maximum available height of the image returned by WMS server. */ private int maxHeight; /** * Builds a new WMS coverage reader * * @param wms * the webmap server * @param layer * the layer that will be used by this coverage reader */ public WMSCoverageReader(WebMapServer wms, Layer layer) { this.wms = wms; // init the reader this.addLayer(layer); // best guess at the format with a preference for PNG (since it's normally transparent) List<String> formats = wms.getCapabilities().getRequest().getGetMap().getFormats(); format = formats.iterator().next(); for (String format : formats) { if ("image/png".equals(format) || "image/png24".equals(format) || "png".equals(format) || "png24".equals(format) || "image/png; mode=24bit".equals(format)) { this.format = format; break; } } maxWidth = wms.getCapabilities().getService().getMaxWidth(); maxHeight = wms.getCapabilities().getService().getMaxHeight(); } void addLayer(Layer layer) { layers.add(layer); if (srsName == null) { // initialize from first layer for (String srs : layer.getBoundingBoxes().keySet()) { try { // check it's valid, if not we crap out and move to the next CRS.decode(srs); srsName = srs; break; } catch (Exception e) { // it's fine, we could not decode that code } } if (srsName == null) { if (layer.getSrs().contains("EPSG:4326")) { // otherwise we try 4326 srsName = "EPSG:4326"; } else { // if not even that works we just take the first... srsName = layer.getSrs().iterator().next(); } } validSRS = layer.getSrs(); } else { Set<String> intersection = new HashSet<String>(validSRS); intersection.retainAll(layer.getSrs()); // can we reuse what we have? if (!intersection.contains(srsName)) { if (intersection.size() == 0) { throw new IllegalArgumentException("The layer being appended does " + "not have any SRS in common with the ones already " + "included in the WMS request, cannot be merged"); } else if (intersection.contains("EPSG:4326")) { srsName = "EPSG:4326"; } else { // if not even that works we just take the first... srsName = intersection.iterator().next(); } validSRS = intersection; } } CoordinateReferenceSystem crs = null; try { crs = CRS.decode(srsName); } catch (Exception e) { LOGGER.warn("Bounds unavailable for layer: {}", layer); } this.crs = crs; // update the cached bounds and the reader original envelope this.updateBounds(); } /** * Issues GetFeatureInfo against a point using the params of the last GetMap request * * @param pos * @return * @throws IOException */ public InputStream getFeatureInfo(DirectPosition2D pos, String infoFormat, int featureCount, GetMapRequest getmap) throws IOException { GetFeatureInfoRequest request = wms.createGetFeatureInfoRequest(getmap); request.setFeatureCount(1); request.setQueryLayers(new LinkedHashSet<Layer>(layers)); request.setInfoFormat(infoFormat); request.setFeatureCount(featureCount); try { AffineTransform tx = RendererUtilities.worldToScreenTransform(requestedEnvelope, new Rectangle(width, height)); Point2D dest = new Point2D.Double(); Point2D src = new Point2D.Double(pos.x, pos.y); tx.transform(src, dest); request.setQueryPoint((int) dest.getX(), (int) dest.getY()); } catch (Exception e) { throw (IOException) new IOException("Failed to grab feature info").initCause(e); } try { LOGGER.debug("Issuing request: {}", request.getFinalURL()); GetFeatureInfoResponse response = wms.issueRequest(request); return response.getInputStream(); } catch (IOException e) { throw e; } catch (Throwable t) { throw (IOException) new IOException("Failed to grab feature info").initCause(t); } } @SuppressWarnings("rawtypes") @Override public GridCoverage2D read(GeneralParameterValue[] parameters) throws IllegalArgumentException, IOException { // try to get request params from the request Envelope requestedEnvelope = null; int width = -1; int height = -1; Color backgroundColor = null; if (parameters != null) { for (GeneralParameterValue param : parameters) { final ReferenceIdentifier name = param.getDescriptor().getName(); if (name.equals(AbstractGridFormat.READ_GRIDGEOMETRY2D.getName())) { final GridGeometry2D gg = (GridGeometry2D) ((ParameterValue) param).getValue(); requestedEnvelope = gg.getEnvelope(); // the range high value is the highest pixel included in the raster, // the actual width and height is one more than that width = gg.getGridRange().getHigh(0) + 1; height = gg.getGridRange().getHigh(1) + 1; } else if (name.equals(AbstractGridFormat.BACKGROUND_COLOR.getName())) { backgroundColor = (Color) ((ParameterValue) param).getValue(); } } } // fill in a reasonable default if we did not manage to get the params if (requestedEnvelope == null) { requestedEnvelope = this.getOriginalEnvelope(); width = 640; height = (int) Math.round(requestedEnvelope.getSpan(1) / requestedEnvelope.getSpan(0) * 640); } // if the structure did not change reuse the same response if (grid != null && grid.getGridGeometry().getGridRange2D().getWidth() == width && grid.getGridGeometry().getGridRange2D().getHeight() == height && grid.getEnvelope().equals(requestedEnvelope)) { return grid; } try { grid = this.getMap(this.reference(requestedEnvelope), width, height, backgroundColor); } catch (TransformException e) { throw new IOException(e); } return grid; } /** * Execute the GetMap request * * @throws TransformException * @throws MismatchedDimensionException */ private GridCoverage2D getMap(ReferencedEnvelope requestedEnvelope, int width, int height, Color backgroundColor) throws IOException, MismatchedDimensionException, TransformException { ReferencedEnvelope gridEnvelope = this.initMapRequest(requestedEnvelope, width, height, backgroundColor); // issue the request and wrap response in a grid coverage BufferedImage image = new BufferedImage(this.width, this.height, BufferedImage.TYPE_4BYTE_ABGR); int y = 0; for (List<GetMapRequest> mapRequestRow : mapRequests) { int x = 0, rowHeight = 0; for (GetMapRequest mapRequest : mapRequestRow) { InputStream is = null; try { LOGGER.debug("Issuing request: {}", mapRequest.getFinalURL()); GetMapResponse response = wms.issueRequest(mapRequest); lastMapRequest = mapRequest; try { is = response.getInputStream(); BufferedImage partImage = ImageIO.read(is); if (partImage == null) { throw new IOException("GetMap failed: " + mapRequest.getFinalURL()); } image.getGraphics().drawImage(partImage, x, y, null); x += partImage.getWidth(); rowHeight = Math.max(rowHeight, partImage.getHeight()); } finally { response.dispose(); } } catch (ServiceException e) { throw (IOException) new IOException("GetMap failed").initCause(e); } } y += rowHeight; } return gcf.create(layers.get(0).getTitle(), image, gridEnvelope); } public GetMapRequest getLastMapRequest() { return lastMapRequest; } /** * Sets up a max request with the provided parameters, making sure it is compatible with the layers own native SRS * list * * @param bbox * @param width * @param height * @return * @throws IOException * @throws TransformException * @throws MismatchedDimensionException */ /* package */ReferencedEnvelope initMapRequest(ReferencedEnvelope bbox, int width, int height, Color backgroundColor) throws IOException, MismatchedDimensionException, TransformException { // we need Cartesian coordinate system to have X and Y axes in the normal order (X - horizontal, Y - vertical) // this is because we will divide the print area later if neccessary requestCRS = bbox.getCoordinateReferenceSystem(); if (!(bbox.getCoordinateReferenceSystem().getCoordinateSystem() instanceof CartesianCS)) { requestCRS = CRS.getProjectedCRS(bbox.getCoordinateReferenceSystem());// this has the Cartesian system } String requestSrs = CRS.toSRS(requestCRS); LOGGER.debug("Using the cartesian CRS for computations of the print areas: {}.", requestCRS); ReferencedEnvelope gridEnvelope = new ReferencedEnvelope(CRS.transform(bbox, requestCRS)); requestedEnvelope = gridEnvelope; this.width = width; this.height = height; mapRequests.clear();// just in case // for some silly reason GetMapRequest will list the layers in the opposite order... List<Layer> reversed = new ArrayList<Layer>(layers); Collections.reverse(reversed); int remainingHeight = height, y = 0; while (remainingHeight > 0) { List<GetMapRequest> mapRequestsRow = new ArrayList<>(); int currentHeight = remainingHeight > maxHeight ? maxHeight : remainingHeight; int remainingWidth = width, x = 0; while (remainingWidth > 0) { int currentWidth = remainingWidth > maxWidth ? maxWidth : remainingWidth; LOGGER.debug("Adding request."); GetMapRequest mapRequest = wms.createGetMapRequest(); for (Layer layer : reversed) { mapRequest.addLayer(layer); } mapRequest.setDimensions(currentWidth, currentHeight); mapRequest.setFormat(format); if (backgroundColor == null) { mapRequest.setTransparent(true); } else { String rgba = Integer.toHexString(backgroundColor.getRGB()); String rgb = rgba.substring(2, rgba.length()); mapRequest.setBGColour("0x" + rgb.toUpperCase()); mapRequest.setTransparent(backgroundColor.getAlpha() < 255); } double xMin = gridEnvelope.getMinX() + gridEnvelope.getWidth() * (x / (double) width); double xMax = xMin + gridEnvelope.getWidth() * (currentWidth / (double) width); double yMin = gridEnvelope.getMinY() + gridEnvelope.getHeight() * (y / (double) height); double yMax = yMin + gridEnvelope.getHeight() * (currentHeight / (double) height); mapRequest.setBBox(new ReferencedEnvelope(xMin, xMax, yMin, yMax, gridEnvelope.getCoordinateReferenceSystem())); mapRequest.setSRS(requestSrs); mapRequestsRow.add(mapRequest); x += maxWidth; remainingWidth -= maxWidth; } mapRequests.add(mapRequestsRow); remainingHeight -= maxHeight; y += maxHeight; } Collections.reverse(mapRequests); return gridEnvelope; } public GetMapRequest getRequestByPosition(DirectPosition2D pos) { double xRatio = (pos.x - requestedEnvelope.getMinX()) / requestedEnvelope.getWidth(); double yRatio = (pos.y - requestedEnvelope.getMinY()) / requestedEnvelope.getHeight(); int xOnImage = (int) (width * xRatio); int yOnImage = (int) (height * (1 - yRatio)); int xTileNumber = xOnImage / maxWidth + 1; int yTileNumber = yOnImage / maxHeight + 1; return mapRequests.get(yTileNumber).get(xTileNumber); } @Override public Format getFormat() { // this reader has not backing format return null; } /** * Returns the layer bounds * * @return */ public void updateBounds() { ReferencedEnvelope result = this.reference(layers.get(0).getEnvelope(crs)); for (int i = 1; i < layers.size(); i++) { ReferencedEnvelope layerEnvelope = this.reference(layers.get(i).getEnvelope(crs)); result.expandToInclude(layerEnvelope); } bounds = result; originalEnvelope = new GeneralEnvelope(result); } /** * Converts a {@link Envelope} into a {@link ReferencedEnvelope} * * @param envelope * @return */ private ReferencedEnvelope reference(Envelope envelope) { ReferencedEnvelope env = new ReferencedEnvelope(envelope.getCoordinateReferenceSystem()); env.expandToInclude(envelope.getMinimum(0), envelope.getMinimum(1)); env.expandToInclude(envelope.getMaximum(0), envelope.getMaximum(1)); return env; } /** * Converts a {@link GeneralEnvelope} into a {@link ReferencedEnvelope} * * @param ge * @return */ private ReferencedEnvelope reference(GeneralEnvelope ge) { return new ReferencedEnvelope(ge.getMinimum(0), ge.getMaximum(0), ge.getMinimum(1), ge.getMaximum(1), ge.getCoordinateReferenceSystem()); } @Override public String[] getMetadataNames() { return new String[] { REPROJECTING_READER }; } @Override public String getMetadataValue(String name) { if (REPROJECTING_READER.equals(name)) { return "true"; } return super.getMetadataValue(name); } } WMSLayer import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.List; import org.geotools.data.ows.Layer; import org.geotools.data.wms.WebMapServer; import org.geotools.data.wms.request.GetMapRequest; import org.geotools.factory.CommonFactoryFinder; import org.geotools.geometry.DirectPosition2D; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.map.GridReaderLayer; import org.geotools.referencing.CRS; import org.geotools.renderer.lite.RendererUtilities; import org.geotools.styling.FeatureTypeStyle; import org.geotools.styling.RasterSymbolizer; import org.geotools.styling.Rule; import org.geotools.styling.Style; import org.geotools.styling.StyleFactory; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; /** * Wraps a WMS layer into a {@link MapLayer} for interactive rendering usage TODO: expose a * GetFeatureInfo that returns a feature collection TODO: expose the list of named styles and allow * choosing which style to use * * @author Andrea Aime - OpenGeo * * * @source $URL$ */ public class WMSLayer extends GridReaderLayer { /** * The default raster style */ static Style STYLE; static { StyleFactory factory = CommonFactoryFinder.getStyleFactory(null); RasterSymbolizer symbolizer = factory.createRasterSymbolizer(); Rule rule = factory.createRule(); rule.symbolizers().add(symbolizer); FeatureTypeStyle type = factory.createFeatureTypeStyle(); type.rules().add(rule); STYLE = factory.createStyle(); STYLE.featureTypeStyles().add(type); } /** * Builds a new WMS layer * * @param wms * @param layer */ public WMSLayer(WebMapServer wms, Layer layer) { super( new WMSCoverageReader(wms, layer), STYLE ); } @Override public WMSCoverageReader getReader(){ return (WMSCoverageReader) reader; } @Override public synchronized ReferencedEnvelope getBounds() { WMSCoverageReader wmsReader = this.getReader(); if( wmsReader != null ){ return wmsReader.bounds; } return super.getBounds(); } /** * Retrieves the feature info as text (assuming "text/plain" is a supported feature info format) * * @param pos * the position to be checked, in real world coordinates * @return * @throws IOException */ public String getFeatureInfoAsText(DirectPosition2D pos, int featureCount) throws IOException { BufferedReader br = null; try { GetMapRequest mapRequest = this.getReader().getRequestByPosition(pos); InputStream is = this.getReader().getFeatureInfo(pos, "text/plain", featureCount, mapRequest); br = new BufferedReader(new InputStreamReader(is)); String line; StringBuilder sb = new StringBuilder(); while ((line = br.readLine()) != null) { sb.append(line).append("\n"); } return sb.toString(); } catch (IOException e) { throw e; } catch (Throwable t) { throw (IOException) new IOException("Failed to grab feature info").initCause(t); } finally { if (br != null) { br.close(); } } } /** * Retrieves the feature info as a generic input stream, it's the duty of the caller to * interpret the contents and ensure the stream is closed feature info format) * * @param pos * the position to be checked, in real world coordinates * @param infoFormat * The INFO_FORMAT parameter in the GetFeatureInfo request * @return * @throws IOException */ public InputStream getFeatureInfo(DirectPosition2D pos, String infoFormat, int featureCount) throws IOException { GetMapRequest mapRequest = this.getReader().getRequestByPosition(pos); return this.getReader().getFeatureInfo(pos, infoFormat, featureCount, mapRequest); } /** * Allows to run a standalone GetFeatureInfo request, without the need to have previously run a * GetMap request on this layer. Mostly useful for stateless users that rebuild the map context * for each rendering operation (e.g., GeoServer) * * @param pos * @param infoFormat * The INFO_FORMAT parameter in the GetFeatureInfo request * @return * @throws IOException */ public InputStream getFeatureInfo(ReferencedEnvelope bbox, int width, int height, int x, int y, String infoFormat, int featureCount) throws IOException { try { this.getReader().initMapRequest(bbox, width, height, null); // we need to convert x/y from the screen to the original coordinates, and then to the ones // that will be used to make the request AffineTransform at = RendererUtilities.worldToScreenTransform(bbox, new Rectangle(width, height)); Point2D screenPos = new Point2D.Double(x, y); Point2D worldPos = new Point2D.Double(x, y); at.inverseTransform(screenPos, worldPos); DirectPosition2D fromPos = new DirectPosition2D(worldPos.getX(), worldPos.getY()); DirectPosition2D toPos = new DirectPosition2D(); MathTransform mt = CRS.findMathTransform(bbox.getCoordinateReferenceSystem(), this.getReader().requestCRS, true); mt.transform(fromPos, toPos); GetMapRequest mapRequest = this.getReader().getRequestByPosition(toPos); return this.getReader().getFeatureInfo(toPos, infoFormat, featureCount, mapRequest); } catch(IOException e) { throw e; } catch(Throwable t) { throw (IOException) new IOException("Unexpected issue during GetFeatureInfo execution").initCause(t); } } /** * Returns the {@link WebMapServer} used by this layer * * @return */ public WebMapServer getWebMapServer() { return this.getReader().wms; } /** * Returns the WMS {@link Layer} used by this layer * * @return */ public List<Layer> getWMSLayers() { return this.getReader().layers; } /** * Returns the CRS used to make requests to the remote WMS * * @return */ public CoordinateReferenceSystem getCoordinateReferenceSystem() { return reader.getCoordinateReferenceSystem(); } /** * Returns last GetMap request performed by this layer * * @return */ public GetMapRequest getLastGetMap() { return this.getReader().getLastMapRequest(); } /** * Allows to add another WMS layer into the GetMap requests * * @param layer */ public void addLayer(Layer layer) { this.getReader().addLayer(layer); } /** * Returns true if the specified CRS can be used directly to perform WMS requests. Natively * supported crs will provide the best rendering quality as no client side reprojection is * necessary, the image coming from the WMS server will be used as-is * * @param crs * @return */ public boolean isNativelySupported(CoordinateReferenceSystem crs) { try { String code = CRS.lookupIdentifier(crs, false); return code != null && this.getReader().validSRS.contains(code); } catch (Throwable t) { return false; } } } -- View this message in context: http://osgeo-org.1560.x6.nabble.com/Problem-with-printing-WMS-tp5181673p5183180.html Sent from the geotools-gt2-users mailing list archive at Nabble.com. ------------------------------------------------------------------------------ New Year. New Location. New Benefits. New Data Center in Ashburn, VA. GigeNET is offering a free month of service with a new server in Ashburn. Choose from 2 high performing configs, both with 100TB of bandwidth. Higher redundancy.Lower latency.Increased capacity.Completely compliant. http://p.sf.net/sfu/gigenet _______________________________________________ GeoTools-GT2-Users mailing list GeoTools-GT2-Users@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/geotools-gt2-users