/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package org.esa.snap.rcp.layermanager.layersrc.wms; import com.bc.ceres.binding.ConversionException; import com.bc.ceres.binding.Property; import com.bc.ceres.binding.PropertyContainer; import com.bc.ceres.binding.PropertySet; import com.bc.ceres.binding.ValidationException; import com.bc.ceres.binding.dom.DomConverter; import com.bc.ceres.binding.dom.DomElement; import com.bc.ceres.glayer.Layer; import com.bc.ceres.glayer.LayerContext; import com.bc.ceres.glayer.LayerTypeRegistry; import com.bc.ceres.glayer.annotations.LayerTypeMetadata; import com.bc.ceres.glayer.support.ImageLayer; import com.bc.ceres.glevel.MultiLevelSource; import com.bc.ceres.glevel.support.DefaultMultiLevelModel; import com.bc.ceres.glevel.support.DefaultMultiLevelSource; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.datamodel.RasterDataNode; import org.geotools.data.ows.CRSEnvelope; import org.geotools.data.ows.StyleImpl; import org.geotools.data.wms.WebMapServer; import org.geotools.data.wms.request.GetMapRequest; import org.geotools.data.wms.response.GetMapResponse; import org.geotools.ows.ServiceException; import javax.imageio.ImageIO; import javax.media.jai.PlanarImage; import java.awt.Dimension; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.List; /** * Layer type for layer that displays images coming from an OGC WMS. * * @author Marco Peters * @since BEAM 4.6 */ @LayerTypeMetadata(name = "WmsLayerType", aliasNames = {"org.esa.snap.rcp.layermanager.layersrc.wms.WmsLayerType"}) public class WmsLayerType extends ImageLayer.Type { public static final String PROPERTY_NAME_RASTER = "raster"; public static final String PROPERTY_NAME_URL = "serverUrl"; public static final String PROPERTY_NAME_LAYER_INDEX = "layerIndex"; public static final String PROPERTY_NAME_CRS_ENVELOPE = "crsEnvelope"; public static final String PROPERTY_NAME_STYLE_NAME = "styleName"; public static final String PROPERTY_NAME_IMAGE_SIZE = "imageSize"; @Override public Layer createLayer(LayerContext ctx, PropertySet configuration) { final WebMapServer mapServer; try { mapServer = getWmsServer(configuration); } catch (Exception e) { final String message = String.format("Not able to access Web Mapping Server: %s", configuration.getValue(WmsLayerType.PROPERTY_NAME_URL)); throw new RuntimeException(message, e); } final int layerIndex = configuration.getValue(WmsLayerType.PROPERTY_NAME_LAYER_INDEX); final org.geotools.data.ows.Layer wmsLayer = getLayer(mapServer, layerIndex); final MultiLevelSource multiLevelSource = createMultiLevelSource(configuration, mapServer, wmsLayer); final ImageLayer.Type imageLayerType = LayerTypeRegistry.getLayerType(ImageLayer.Type.class); final PropertySet config = imageLayerType.createLayerConfig(ctx); config.setValue(ImageLayer.PROPERTY_NAME_MULTI_LEVEL_SOURCE, multiLevelSource); config.setValue(ImageLayer.PROPERTY_NAME_BORDER_SHOWN, false); config.setValue(ImageLayer.PROPERTY_NAME_PIXEL_BORDER_SHOWN, false); final ImageLayer wmsImageLayer = new ImageLayer(this, multiLevelSource, config); wmsImageLayer.setName(wmsLayer.getName()); return wmsImageLayer; } @Override public PropertySet createLayerConfig(LayerContext ctx) { final PropertyContainer template = new PropertyContainer(); template.addProperty(Property.create(PROPERTY_NAME_RASTER, RasterDataNode.class)); template.addProperty(Property.create(PROPERTY_NAME_URL, URL.class)); template.addProperty(Property.create(PROPERTY_NAME_LAYER_INDEX, Integer.class)); template.addProperty(Property.create(PROPERTY_NAME_STYLE_NAME, String.class)); template.addProperty(Property.create(PROPERTY_NAME_IMAGE_SIZE, Dimension.class)); template.addProperty(Property.create(PROPERTY_NAME_CRS_ENVELOPE, CRSEnvelope.class)); template.getDescriptor(PROPERTY_NAME_CRS_ENVELOPE).setDomConverter(new CRSEnvelopeDomConverter()); return template; } @SuppressWarnings({"unchecked"}) private static DefaultMultiLevelSource createMultiLevelSource(PropertySet configuration, WebMapServer wmsServer, org.geotools.data.ows.Layer layer) { DefaultMultiLevelSource multiLevelSource; final String styleName = configuration.getValue(WmsLayerType.PROPERTY_NAME_STYLE_NAME); final Dimension size = configuration.getValue(WmsLayerType.PROPERTY_NAME_IMAGE_SIZE); try { List<StyleImpl> styleList = layer.getStyles(); StyleImpl style = null; if (!styleList.isEmpty()) { style = styleList.get(0); for (StyleImpl currentstyle : styleList) { if (currentstyle.getName().equals(styleName)) { style = currentstyle; } } } CRSEnvelope crsEnvelope = configuration.getValue(WmsLayerType.PROPERTY_NAME_CRS_ENVELOPE); GetMapRequest mapRequest = wmsServer.createGetMapRequest(); mapRequest.addLayer(layer, style); mapRequest.setTransparent(true); mapRequest.setDimensions(size.width, size.height); mapRequest.setSRS(crsEnvelope.getEPSGCode()); // e.g. "EPSG:4326" = Geographic CRS mapRequest.setBBox(crsEnvelope); mapRequest.setFormat("image/png"); final PlanarImage image = PlanarImage.wrapRenderedImage(downloadWmsImage(mapRequest, wmsServer)); RasterDataNode raster = configuration.getValue(WmsLayerType.PROPERTY_NAME_RASTER); final int sceneWidth = raster.getRasterWidth(); final int sceneHeight = raster.getRasterHeight(); AffineTransform i2mTransform = Product.findImageToModelTransform(raster.getGeoCoding()); i2mTransform.scale((double) sceneWidth / image.getWidth(), (double) sceneHeight / image.getHeight()); final Rectangle2D bounds = DefaultMultiLevelModel.getModelBounds(i2mTransform, image); final DefaultMultiLevelModel multiLevelModel = new DefaultMultiLevelModel(1, i2mTransform, bounds); multiLevelSource = new DefaultMultiLevelSource(image, multiLevelModel); } catch (Exception e) { throw new IllegalStateException(String.format("Failed to access WMS: %s", configuration.getValue( WmsLayerType.PROPERTY_NAME_URL)), e); } return multiLevelSource; } private static org.geotools.data.ows.Layer getLayer(WebMapServer server, int layerIndex) { return server.getCapabilities().getLayerList().get(layerIndex); } private static WebMapServer getWmsServer(PropertySet configuration) throws IOException, ServiceException { return new WebMapServer((URL) configuration.getValue(WmsLayerType.PROPERTY_NAME_URL)); } private static BufferedImage downloadWmsImage(GetMapRequest mapRequest, WebMapServer wms) throws IOException, ServiceException { GetMapResponse mapResponse = wms.issueRequest(mapRequest); try (InputStream inputStream = mapResponse.getInputStream()) { return ImageIO.read(inputStream); } } private static class CRSEnvelopeDomConverter implements DomConverter { private static final String SRS_NAME = "srsName"; private static final String MIN_X = "minX"; private static final String MIN_Y = "minY"; private static final String MAX_X = "maxX"; private static final String MAX_Y = "maxY"; @Override public Class<?> getValueType() { return CRSEnvelope.class; } @Override public Object convertDomToValue(DomElement parentElement, Object value) throws ConversionException, ValidationException { try { String srsName = parentElement.getChild(SRS_NAME).getValue(); double minX = Double.parseDouble(parentElement.getChild(MIN_X).getValue()); double minY = Double.parseDouble(parentElement.getChild(MIN_Y).getValue()); double maxX = Double.parseDouble(parentElement.getChild(MAX_X).getValue()); double maxY = Double.parseDouble(parentElement.getChild(MAX_Y).getValue()); value = new CRSEnvelope(srsName, minX, minY, maxX, maxY); } catch (Exception e) { throw new IllegalArgumentException(e); } return value; } @Override public void convertValueToDom(Object value, DomElement parentElement) throws ConversionException { CRSEnvelope crsEnvelope = (CRSEnvelope) value; DomElement srsName = parentElement.createChild(SRS_NAME); srsName.setValue(crsEnvelope.getSRSName()); DomElement minX = parentElement.createChild(MIN_X); minX.setValue(Double.toString(crsEnvelope.getMinX())); DomElement minY = parentElement.createChild(MIN_Y); minY.setValue(Double.toString(crsEnvelope.getMinY())); DomElement maxX = parentElement.createChild(MAX_X); maxX.setValue(Double.toString(crsEnvelope.getMaxX())); DomElement maxY = parentElement.createChild(MAX_Y); maxY.setValue(Double.toString(crsEnvelope.getMaxY())); } } }