/* (c) 2014-2015 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wms.worldwind; import it.geosolutions.jaiext.range.RangeFactory; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.RenderedImage; import java.awt.image.renderable.ParameterBlock; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteOrder; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import javax.imageio.ImageWriter; import javax.imageio.spi.ImageWriterSpi; import javax.imageio.stream.ImageOutputStream; import javax.media.jai.Interpolation; import javax.media.jai.JAI; import javax.media.jai.TiledImage; import org.geoserver.catalog.MetadataMap; import org.geoserver.data.util.CoverageUtils; import org.geoserver.platform.ServiceException; import org.geoserver.wms.GetMapRequest; import org.geoserver.wms.MapLayerInfo; import org.geoserver.wms.MapProducerCapabilities; import org.geoserver.wms.WMS; import org.geoserver.wms.WMSMapContent; import org.geoserver.wms.map.RenderedImageMapResponse; import org.geoserver.wms.worldwind.util.BilWCSUtils; import org.geoserver.wms.worldwind.util.RecodeRaster; import org.geotools.coverage.CoverageFactoryFinder; import org.geotools.coverage.grid.GeneralGridEnvelope; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.coverage.grid.io.AbstractGridFormat; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.geometry.GeneralEnvelope; import org.geotools.image.ImageWorker; import org.geotools.referencing.CRS; import org.geotools.resources.coverage.CoverageUtilities; import org.geotools.util.logging.Logging; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.TransformException; import org.vfny.geoserver.wcs.WcsException; import com.sun.media.imageioimpl.plugins.raw.RawImageWriterSpi; /** * Map producer for producing Raw bil images out of an elevation model. * Modeled after the GeoTIFFMapResponse, relying on Geotools and the * RawImageWriterSpi * @author Tishampati Dhar * @since 2.0.x * */ public final class BilMapResponse extends RenderedImageMapResponse { /** A logger for this class. */ private static final Logger LOGGER = Logging.getLogger(BilMapResponse.class); /** the only MIME type this map producer supports */ static final String MIME_TYPE = "image/bil"; private static final String[] OUTPUT_FORMATS = {MIME_TYPE,"application/bil", "application/bil8","application/bil16", "application/bil32" }; /** GridCoverageFactory. - Where do we use this again ?*/ private final static GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null); /** Raw Image Writer **/ private final static ImageWriterSpi writerSPI = new RawImageWriterSpi(); /** * Constructor for a {@link BilMapResponse}. * * @param wms * that is asking us to encode the image. */ public BilMapResponse(final WMS wms) { super(OUTPUT_FORMATS,wms); } @Override public void formatImageOutputStream(RenderedImage image, OutputStream outStream, WMSMapContent mapContent) throws ServiceException, IOException { //TODO: Write reprojected terrain tile // TODO Get request tile size final GetMapRequest request = mapContent.getRequest(); String bilEncoding = (String) request.getFormat(); int height = request.getHeight(); int width = request.getWidth(); if ((height>512)||(width>512)){ throw new ServiceException("Cannot get WMS bil" + " tiles bigger than 512x512, try WCS"); } List<MapLayerInfo> reqlayers = request.getLayers(); //Can't fetch bil for more than 1 layer if (reqlayers.size() > 1) { throw new ServiceException("Cannot combine layers into BIL output"); } // Get BIL layer configuration. This configuration is set by the server administrator // using the BIL layer config panel. MapLayerInfo mapLayerInfo = reqlayers.get(0); MetadataMap metadata = mapLayerInfo.getResource().getMetadata(); String defaultDataType = (String) metadata.get(BilConfig.DEFAULT_DATA_TYPE); String byteOrder = (String) metadata.get(BilConfig.BYTE_ORDER); Double outNoData = null; Object noDataParam = metadata.get(BilConfig.NO_DATA_OUTPUT); if (noDataParam instanceof Number) { outNoData = ((Number) noDataParam).doubleValue(); } else if (noDataParam instanceof String) { try { outNoData = Double.parseDouble((String) noDataParam); } catch (NumberFormatException e) { LOGGER.warning("Can't parse output no data attribute: " + e.getMessage()); // TODO localize } } GridCoverage2DReader coverageReader = (GridCoverage2DReader) mapLayerInfo.getCoverageReader(); GeneralEnvelope destinationEnvelope = new GeneralEnvelope(mapContent.getRenderingArea()); /* * Try to use a gridcoverage style render */ GridCoverage2D subCov = null; try { subCov = getFinalCoverage(request, mapLayerInfo, mapContent, coverageReader, destinationEnvelope); } catch (Exception e) { LOGGER.severe("Could not get a subcoverage"); } if (subCov == null) { LOGGER.fine("Creating coverage from a blank image"); BufferedImage emptyImage = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY); DataBuffer data = emptyImage.getRaster().getDataBuffer(); for (int i = 0; i < data.getSize(); ++i) { data.setElem(i, 32768); // 0x0080 in file (2^15) } subCov = factory.create("uselessname", emptyImage, destinationEnvelope); } if(subCov!=null) { image = subCov.getRenderedImage(); if(image!=null) { int dtype = image.getData().getDataBuffer().getDataType(); RenderedImage transformedImage = image; // Perform NoData translation final double[] inNoDataValues = CoverageUtilities.getBackgroundValues( (GridCoverage2D) subCov); if (inNoDataValues != null && outNoData != null) { // TODO should support multiple no-data values final double inNoData = inNoDataValues[0]; if (inNoData != outNoData) { ParameterBlock param = new ParameterBlock().addSource(image); param = param.add(inNoData); param = param.add(outNoData); transformedImage = JAI.create(RecodeRaster.OPERATION_NAME, param, null); } } // Perform format conversion. If the requested encoding does not specify the format // (i.e. application/bil or image/bil), then convert to the default encoding configured // by the server administrator. Operator is not created if no conversion is necessary. if (defaultDataType != null && ((bilEncoding.equals("application/bil") || bilEncoding.equals("image/bil")))) { bilEncoding = defaultDataType; } ImageWorker worker = new ImageWorker(transformedImage); Double nod = inNoDataValues != null ? (outNoData != null ? outNoData : inNoDataValues[0]) : null; worker.setNoData(nod != null ? RangeFactory.create(nod, nod) : null); if((bilEncoding.equals("application/bil32")) && (dtype != DataBuffer.TYPE_FLOAT)) { transformedImage = worker.format(DataBuffer.TYPE_FLOAT).getRenderedImage(); } else if((bilEncoding.equals("application/bil16")) && (dtype != DataBuffer.TYPE_SHORT)) { transformedImage = worker.format(DataBuffer.TYPE_SHORT).getRenderedImage(); } else if((bilEncoding.equals("application/bil8")) && (dtype != DataBuffer.TYPE_BYTE)) { transformedImage = worker.format(DataBuffer.TYPE_BYTE).getRenderedImage(); } TiledImage tiled = new TiledImage(transformedImage,width,height); final ImageOutputStream imageOutStream = ImageIO.createImageOutputStream(outStream); final ImageWriter writer = writerSPI.createWriterInstance(); // Set byte order out of output stream based on layer configuration. if (ByteOrder.LITTLE_ENDIAN.toString().equals(byteOrder)) { imageOutStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); } else if (ByteOrder.BIG_ENDIAN.toString().equals(byteOrder)) { imageOutStream.setByteOrder(ByteOrder.BIG_ENDIAN); } writer.setOutput(imageOutStream); writer.write(tiled); imageOutStream.flush(); imageOutStream.close(); } else { throw new ServiceException("Cannot render to BIL"); } } else { throw new ServiceException("You requested a bil of size:"+ height+"x"+width+",but you can't have it!!"); } } /** * getFinalCoverage - message the RenderedImage into Bil * * @param request CoverageRequest * @param meta CoverageInfo * @param mapContent Context for GetMap request. * @param coverageReader reader * * @return GridCoverage2D * * @throws Exception an error occurred */ private static GridCoverage2D getFinalCoverage(GetMapRequest request, MapLayerInfo meta, WMSMapContent mapContent, GridCoverage2DReader coverageReader, GeneralEnvelope destinationEnvelope) throws WcsException, IOException, IndexOutOfBoundsException, FactoryException, TransformException { // This is the final Response CRS final String responseCRS = request.getSRS(); // - then create the Coordinate Reference System final CoordinateReferenceSystem targetCRS = CRS.decode(responseCRS); // This is the CRS of the requested Envelope final String requestCRS = request.getSRS(); // - then create the Coordinate Reference System final CoordinateReferenceSystem sourceCRS = CRS.decode(requestCRS); // This is the CRS of the Coverage Envelope final CoordinateReferenceSystem cvCRS = ((GeneralEnvelope) coverageReader .getOriginalEnvelope()).getCoordinateReferenceSystem(); // this is the destination envelope in the coverage crs final GeneralEnvelope destinationEnvelopeInSourceCRS = CRS.transform(destinationEnvelope, cvCRS); /** * Reading Coverage on Requested Envelope */ Rectangle destinationSize = null; destinationSize = new Rectangle(0,0,request.getHeight(),request.getWidth()); /** * Checking for supported Interpolation Methods */ Interpolation interpolation = Interpolation.getInstance(Interpolation.INTERP_BILINEAR); // ///////////////////////////////////////////////////////// // // Reading the coverage // // ///////////////////////////////////////////////////////// Map<Object,Object> parameters = new HashMap<Object,Object>(); parameters.put(AbstractGridFormat.READ_GRIDGEOMETRY2D.getName().toString(), new GridGeometry2D(new GeneralGridEnvelope(destinationSize), destinationEnvelopeInSourceCRS)); final GridCoverage2D coverage = coverageReader.read(CoverageUtils.getParameters( coverageReader.getFormat().getReadParameters(), parameters, true)); if (coverage == null) { LOGGER.log(Level.FINE, "Failed to read coverage - continuing"); return null; } /** * Band Select */ /* Coverage bandSelectedCoverage = null; bandSelectedCoverage = WCSUtils.bandSelect(request.getParameters(), coverage); */ /** * Crop */ final GridCoverage2D croppedGridCoverage = BilWCSUtils.crop(coverage, (GeneralEnvelope) coverage.getEnvelope(), cvCRS, destinationEnvelopeInSourceCRS, Boolean.TRUE); /** * Scale/Resampling (if necessary) */ //GridCoverage2D subCoverage = null; GridCoverage2D subCoverage = croppedGridCoverage; final GeneralGridEnvelope newGridrange = new GeneralGridEnvelope(destinationSize); subCoverage = BilWCSUtils.scale(croppedGridCoverage, newGridrange, croppedGridCoverage, cvCRS, destinationEnvelopeInSourceCRS); /** * Reproject */ subCoverage = BilWCSUtils.reproject(subCoverage, sourceCRS, targetCRS, interpolation); return subCoverage; } /** * This is not really an image map */ @Override public MapProducerCapabilities getCapabilities(String outputFormat) { // FIXME become more capable return new MapProducerCapabilities(false, false, false, false, null); } static { RecodeRaster.register(JAI.getDefaultInstance()); } }