/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2014 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wms; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.io.IOException; import java.nio.charset.Charset; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ExecutorService; import java.util.logging.Level; import java.util.logging.Logger; import org.geoserver.catalog.AttributeTypeInfo; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.DimensionInfo; import org.geoserver.catalog.DimensionPresentation; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.MetadataMap; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.catalog.PublishedInfo; import org.geoserver.catalog.PublishedType; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.StyleInfo; import org.geoserver.catalog.WMSLayerInfo; import org.geoserver.catalog.util.ReaderDimensionsAccessor; import org.geoserver.config.GeoServer; import org.geoserver.config.GeoServerInfo; import org.geoserver.config.JAIInfo; import org.geoserver.data.util.CoverageUtils; import org.geoserver.ows.kvp.TimeParser; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.platform.ServiceException; import org.geoserver.wms.WMSInfo.WMSInterpolation; import org.geoserver.wms.WatermarkInfo.Position; import org.geoserver.wms.dimension.DimensionDefaultValueSelectionStrategy; import org.geoserver.wms.dimension.DimensionDefaultValueSelectionStrategyFactory; import org.geoserver.wms.dimension.DimensionFilterBuilder; import org.geoserver.wms.featureinfo.GetFeatureInfoOutputFormat; import org.geoserver.wms.map.RenderedImageMapOutputFormat; import org.geoserver.wms.map.RenderedImageMapResponse; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.data.FeatureSource; import org.geotools.data.Query; import org.geotools.data.ows.Layer; import org.geotools.data.ows.OperationType; import org.geotools.data.ows.WMSCapabilities; import org.geotools.factory.CommonFactoryFinder; import org.geotools.factory.GeoTools; import org.geotools.feature.FeatureCollection; import org.geotools.feature.visitor.CalcResult; import org.geotools.feature.visitor.MaxVisitor; import org.geotools.feature.visitor.MinVisitor; import org.geotools.feature.visitor.UniqueVisitor; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.geotools.referencing.CRS.AxisOrder; import org.geotools.styling.Style; import org.geotools.util.Converters; import org.geotools.util.DateRange; import org.geotools.util.NumberRange; import org.geotools.util.Range; import org.geotools.util.Version; import org.geotools.util.logging.Logging; import org.opengis.feature.type.Name; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import org.opengis.filter.PropertyIsBetween; import org.opengis.filter.expression.PropertyName; import org.opengis.parameter.GeneralParameterDescriptor; import org.opengis.parameter.GeneralParameterValue; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterValue; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; /** * A facade providing access to the WMS configuration details * * @author Gabriel Roldan */ public class WMS implements ApplicationContextAware { public static final Version VERSION_1_1_1 = new Version("1.1.1"); public static final Version VERSION_1_3_0 = new Version("1.3.0"); public static final String JPEG_COMPRESSION = "jpegCompression"; public static final int JPEG_COMPRESSION_DEFAULT = 25; public static final String PNG_COMPRESSION = "pngCompression"; public static final int PNG_COMPRESSION_DEFAULT = 25; public static final String MAX_ALLOWED_FRAMES = "maxAllowedFrames"; public static final int MAX_ALLOWED_FRAMES_DEFAULT = Integer.MAX_VALUE; public static final String MAX_RENDERING_TIME = "maxAnimatorRenderingTime"; public static final String MAX_RENDERING_SIZE = "maxRenderingSize"; public static final String FRAMES_DELAY = "framesDelay"; public static final int FRAMES_DELAY_DEFAULT = 1000; public static final String DISPOSAL_METHOD = "disposalMethod"; public static final String LOOP_CONTINUOUSLY = "loopContinuously"; public static final Boolean LOOP_CONTINUOUSLY_DEFAULT = Boolean.FALSE; public static final String SCALEHINT_MAPUNITS_PIXEL = "scalehintMapunitsPixel"; public static final Boolean SCALEHINT_MAPUNITS_PIXEL_DEFAULT = Boolean.FALSE; public static final String DYNAMIC_STYLING_DISABLED = "dynamicStylingDisabled"; static final Logger LOGGER = Logging.getLogger(WMS.class); public static final String WEB_CONTAINER_KEY = "WMS"; /** * SVG renderers. */ public static final String SVG_SIMPLE = "Simple"; public static final String SVG_BATIK = "Batik"; /** * KML reflector mode */ public static String KML_REFLECTOR_MODE = "kmlReflectorMode"; /** * KML reflector mode values */ public static final String KML_REFLECTOR_MODE_REFRESH = "refresh"; public static final String KML_REFLECTOR_MODE_SUPEROVERLAY = "superoverlay"; public static final String KML_REFLECTOR_MODE_DOWNLOAD = "download"; public static final String KML_REFLECTOR_MODE_DEFAULT = KML_REFLECTOR_MODE_REFRESH; /** * KML superoverlay sub-mode */ public static final String KML_SUPEROVERLAY_MODE = "kmlSuperoverlayMode"; public static final String KML_SUPEROVERLAY_MODE_AUTO = "auto"; public static final String KML_SUPEROVERLAY_MODE_RASTER = "raster"; public static final String KML_SUPEROVERLAY_MODE_OVERVIEW = "overview"; public static final String KML_SUPEROVERLAY_MODE_HYBRID = "hybrid"; public static final String KML_SUPEROVERLAY_MODE_CACHED = "cached"; public static final String KML_SUPEROVERLAY_MODE_DEFAULT = KML_SUPEROVERLAY_MODE_AUTO; public static final String KML_KMLATTR = "kmlAttr"; public static final boolean KML_KMLATTR_DEFAULT = true; public static final String KML_KMLPLACEMARK = "kmlPlacemark"; public static final boolean KML_KMLPLACEMARK_DEFAULT = false; public static final String KML_KMSCORE = "kmlKmscore"; public static final int KML_KMSCORE_DEFAULT = 40; /** * Enable continuous map wrapping (global sys var) */ public static Boolean ENABLE_MAP_WRAPPING = null; /** * Continuous map wrapping key */ public static String MAP_WRAPPING_KEY = "mapWrapping"; /** * Enable advanced projection handling */ public static Boolean ENABLE_ADVANCED_PROJECTION = null; /** * Advanced projection key */ public static String ADVANCED_PROJECTION_KEY = "advancedProjectionHandling"; /** * GIF disposal methods */ public static final String DISPOSAL_METHOD_NONE = "none"; public static final String DISPOSAL_METHOD_NOT_DISPOSE = "doNotDispose"; public static final String DISPOSAL_METHOD_BACKGROUND = "backgroundColor"; public static final String DISPOSAL_METHOD_PREVIOUS = "previous"; public static final String DISPOSAL_METHOD_DEFAULT = DISPOSAL_METHOD_NONE; public static final String[] DISPOSAL_METHODS = { DISPOSAL_METHOD_NONE, DISPOSAL_METHOD_NOT_DISPOSE, DISPOSAL_METHOD_BACKGROUND, DISPOSAL_METHOD_PREVIOUS }; /** * the WMS Animator animatorExecutor service */ private ExecutorService animatorExecutorService; private static final FilterFactory ff = CommonFactoryFinder.getFilterFactory(null); private final GeoServer geoserver; private ApplicationContext applicationContext; private DimensionDefaultValueSelectionStrategyFactory defaultDimensionValueFactory; public WMS(GeoServer geoserver) { this.geoserver = geoserver; } public Catalog getCatalog() { return geoserver.getCatalog(); } public WMSInfo getServiceInfo() { return geoserver.getService(WMSInfo.class); } public Style getStyleByName(String styleName) throws IOException { StyleInfo styleInfo = getCatalog().getStyleByName(styleName); return styleInfo == null ? null : styleInfo.getStyle(); } public LayerInfo getLayerByName(String layerName) { return getCatalog().getLayerByName(layerName); } public LayerGroupInfo getLayerGroupByName(String layerGroupName) { return getCatalog().getLayerGroupByName(layerGroupName); } public boolean isEnabled() { WMSInfo serviceInfo = getServiceInfo(); return serviceInfo.isEnabled(); } /** * /** Returns a supported version according to the version negotiation rules in section 6.2.4 * of the WMS 1.3.0 spec. * <p> * Calls through to {@link #negotiateVersion(Version)}. * </p> * * @param requestedVersion * The version, may be bull. * */ public static Version negotiateVersion(final String requestedVersion) { return negotiateVersion(requestedVersion != null ? new Version(requestedVersion) : null); } /** * Returns a supported version according to the version negotiation rules in section 6.2.4 of * the WMS 1.3.0 spec. * <p> * For instance: <u> * <li>request version not provided? -> higher version supported * <li>requested version supported? -> that same version * <li>requested version < lowest supported version? -> lowest supported * <li>requested version > lowest supported version? -> higher supported version that's lower * than the requested version </u> * </p> * * @param requestedVersion * the request version, or {@code null} if unspecified * */ public static Version negotiateVersion(final Version requestedVersion) { if (null == requestedVersion) { return VERSION_1_3_0; } if (VERSION_1_1_1.equals(requestedVersion)) { return VERSION_1_1_1; } if (VERSION_1_3_0.equals(requestedVersion)) { return VERSION_1_3_0; } if (requestedVersion.compareTo(VERSION_1_3_0) < 0) { return VERSION_1_1_1; } return VERSION_1_3_0; } public String getVersion() { WMSInfo serviceInfo = getServiceInfo(); List<Version> versions = serviceInfo.getVersions(); String version; if (versions.size() > 0) { version = versions.get(0).toString(); } else { // shouldn't a version be set? version = "1.1.1"; } return version; } public GeoServer getGeoServer() { return this.geoserver; } /** * @param animatorExecutorService the animatorExecutorService to set */ public void setAnimatorExecutorService(ExecutorService animatorExecutorService) { this.animatorExecutorService = animatorExecutorService; } /** * @return the animatorExecutorService */ public ExecutorService getAnimatorExecutorService() { return animatorExecutorService; } public WMSInterpolation getInterpolation() { return getServiceInfo().getInterpolation(); } public boolean isDynamicStylingDisabled() { return getServiceInfo().isDynamicStylingDisabled(); } public JAIInfo.PngEncoderType getPNGEncoderType() { JAIInfo jaiInfo = getJaiInfo(); return jaiInfo.getPngEncoderType(); } public Boolean getJPEGNativeAcceleration() { JAIInfo jaiInfo = getJaiInfo(); return Boolean.valueOf(jaiInfo.isJpegAcceleration()); } private JAIInfo getJaiInfo() { GeoServer geoServer = getGeoServer(); GeoServerInfo global = geoServer.getGlobal(); return global.getJAI(); } public Charset getCharSet() { GeoServer geoServer2 = getGeoServer(); String charset = geoServer2.getSettings().getCharset(); return Charset.forName(charset); } public String getProxyBaseUrl() { GeoServer geoServer = getGeoServer(); return geoServer.getSettings().getProxyBaseUrl(); } public long getUpdateSequence() { GeoServerInfo global = getGeoServer().getGlobal(); return global.getUpdateSequence(); } public int getWatermarkTransparency() { WatermarkInfo watermark = getServiceInfo().getWatermark(); return watermark.getTransparency(); } public int getWatermarkPosition() { WatermarkInfo watermark = getServiceInfo().getWatermark(); Position position = watermark.getPosition(); return position.getCode(); } public boolean isGlobalWatermarking() { WatermarkInfo watermark = getServiceInfo().getWatermark(); return watermark.isEnabled(); } public String getGlobalWatermarkingURL() { WatermarkInfo watermark = getServiceInfo().getWatermark(); return watermark.getURL(); } public FeatureTypeInfo getFeatureTypeInfo(final Name name) { Catalog catalog = getCatalog(); FeatureTypeInfo resource = catalog.getResourceByName(name, FeatureTypeInfo.class); return resource; } public CoverageInfo getCoverageInfo(final Name name) { Catalog catalog = getCatalog(); CoverageInfo resource = catalog.getResourceByName(name, CoverageInfo.class); return resource; } public WMSLayerInfo getWMSLayerInfo(final Name name) { Catalog catalog = getCatalog(); WMSLayerInfo resource = catalog.getResourceByName(name, WMSLayerInfo.class); return resource; } public ResourceInfo getResourceInfo(final Name name) { Catalog catalog = getCatalog(); ResourceInfo resource = catalog.getResourceByName(name, ResourceInfo.class); return resource; } public List<LayerInfo> getLayers() { Catalog catalog = getCatalog(); return catalog.getLayers(); } public String getNamespaceByPrefix(final String prefix) { Catalog catalog = getCatalog(); NamespaceInfo namespaceInfo = catalog.getNamespaceByPrefix(prefix); return namespaceInfo == null ? null : namespaceInfo.getURI(); } public List<LayerGroupInfo> getLayerGroups() { Catalog catalog = getCatalog(); List<LayerGroupInfo> layerGroups = catalog.getLayerGroups(); return layerGroups; } /** * Informs the user that this WMS supports SLD. We don't currently handle sld, still needs to be * rolled in from geotools, so this now must be false. * * //djb: we support it now * * @return false */ public boolean supportsSLD() { return true; // djb: we support it now } /** * Informs the user that this WMS supports User Layers * <p> * We support this both remote wfs and inlineFeature * </p> * * @return true */ public boolean supportsUserLayer() { return true; } /** * Informs the user that this WMS supports User Styles * * @return true */ public boolean supportsUserStyle() { return true; } /** * Informs the user that this WMS supports Remote WFS. * * @return true */ public boolean supportsRemoteWFS() { return true; } public void setSvgRenderer(String svgRendererHint) { WMSInfo serviceInfo = getServiceInfo(); serviceInfo.getMetadata().put("svgRenderer", svgRendererHint); getGeoServer().save(serviceInfo); } public String getSvgRenderer() { WMSInfo serviceInfo = getServiceInfo(); String svgRendererHint = (String) serviceInfo.getMetadata().get("svgRenderer"); return svgRendererHint; } public boolean isSvgAntiAlias() { WMSInfo serviceInfo = getServiceInfo(); Boolean svgAntiAlias = Converters.convert(serviceInfo.getMetadata().get("svgAntiAlias"), Boolean.class); return svgAntiAlias == null ? true : svgAntiAlias.booleanValue(); } public int getPngCompression() { WMSInfo serviceInfo = getServiceInfo(); return getMetadataPercentage(serviceInfo.getMetadata(), PNG_COMPRESSION, PNG_COMPRESSION_DEFAULT); } public int getJpegCompression() { WMSInfo serviceInfo = getServiceInfo(); return getMetadataPercentage(serviceInfo.getMetadata(), JPEG_COMPRESSION, JPEG_COMPRESSION_DEFAULT); } /** * Checks if continuous map wrapping is enabled or not * * */ public boolean isContinuousMapWrappingEnabled() { // for backwards compatibility we set the config value to the sys variable one if set, but // once set, the config wins Boolean enabled = getMetadataValue(MAP_WRAPPING_KEY, ENABLE_MAP_WRAPPING, Boolean.class); return enabled; } /** * Checks if advanced projection handling is enabled or not * * */ public boolean isAdvancedProjectionHandlingEnabled() { // for backwards compatibility we set the config value to the sys variable one if set, but // once set, the config wins Boolean enabled = getMetadataValue(ADVANCED_PROJECTION_KEY, ENABLE_ADVANCED_PROJECTION, Boolean.class); return enabled; } public int getMaxAllowedFrames() { return getMetadataValue(MAX_ALLOWED_FRAMES, MAX_ALLOWED_FRAMES_DEFAULT, Integer.class); } public Long getMaxAnimatorRenderingTime() { return getMetadataValue(MAX_RENDERING_TIME, null, Long.class); } public Long getMaxRenderingSize() { return getMetadataValue( MAX_RENDERING_SIZE, null, Long.class); } public Integer getFramesDelay() { return getMetadataValue(FRAMES_DELAY, FRAMES_DELAY_DEFAULT, Integer.class); } public String getDisposalMethod() { return getMetadataValue(DISPOSAL_METHOD, DISPOSAL_METHOD_DEFAULT, String.class); } public Boolean getLoopContinuously() { return getMetadataValue(LOOP_CONTINUOUSLY, LOOP_CONTINUOUSLY_DEFAULT, Boolean.class); } public Boolean getScalehintUnitPixel(){ return getMetadataValue(SCALEHINT_MAPUNITS_PIXEL, SCALEHINT_MAPUNITS_PIXEL_DEFAULT, Boolean.class); } int getMetadataPercentage(MetadataMap metadata, String key, int defaultValue) { Integer parsedValue = Converters.convert(metadata.get(key), Integer.class); if (parsedValue == null) return defaultValue; int value = parsedValue.intValue(); if (value < 0 || value > 100) { LOGGER.warning("Invalid percertage value for '" + key + "', it should be between 0 and 100"); return defaultValue; } return value; } <T> T getMetadataValue(String key, T defaultValue, Class<T> clazz) { if (getServiceInfo() == null) { return defaultValue; } MetadataMap metadata = getServiceInfo().getMetadata(); T parsedValue = Converters.convert(metadata.get(key), clazz); if (parsedValue == null) return defaultValue; return parsedValue; } public int getNumDecimals() { return getGeoServer().getSettings().getNumDecimals(); } public String getNameSpacePrefix(final String nsUri) { Catalog catalog = getCatalog(); NamespaceInfo ns = catalog.getNamespaceByURI(nsUri); return ns == null ? null : ns.getPrefix(); } public int getMaxBuffer() { return getServiceInfo().getMaxBuffer(); } public int getMaxRequestMemory() { return getServiceInfo().getMaxRequestMemory(); } public int getMaxRenderingTime() { return getServiceInfo().getMaxRenderingTime(); } public int getMaxRenderingErrors() { return getServiceInfo().getMaxRenderingErrors(); } public String getKmlReflectorMode() { String value = (String) getServiceInfo().getMetadata().get(KML_REFLECTOR_MODE); return value != null ? value : KML_REFLECTOR_MODE_DEFAULT; } public String getKmlSuperoverlayMode() { String value = (String) getServiceInfo().getMetadata().get(KML_SUPEROVERLAY_MODE); return value != null ? value : KML_SUPEROVERLAY_MODE_DEFAULT; } public boolean getKmlKmAttr() { Boolean kmAttr = Converters.convert(getServiceInfo().getMetadata().get(KML_KMLATTR), Boolean.class); return kmAttr == null ? KML_KMLATTR_DEFAULT : kmAttr.booleanValue(); } public boolean getKmlPlacemark() { Boolean kmAttr = Converters.convert(getServiceInfo().getMetadata().get(KML_KMLPLACEMARK), Boolean.class); return kmAttr == null ? KML_KMLPLACEMARK_DEFAULT : kmAttr.booleanValue(); } public int getKmScore() { return getMetadataPercentage(getServiceInfo().getMetadata(), KML_KMSCORE, KML_KMSCORE_DEFAULT); } /** * Returns all allowed map output formats. */ public Collection<GetMapOutputFormat> getAllowedMapFormats() { List<GetMapOutputFormat> result = new ArrayList<GetMapOutputFormat>(); for (GetMapOutputFormat producer: WMSExtensions.findMapProducers(applicationContext)) { if (isAllowedGetMapFormat(producer)) { result.add(producer); } } return result; } /** * Returns all map output formats. */ public Collection<GetMapOutputFormat> getAvailableMapFormats() { return WMSExtensions.findMapProducers(applicationContext); } /** * Grabs the list of allowed MIME-Types for the GetMap operation from the set of * {@link GetMapOutputFormat}s registered in the application context. * * @param applicationContext * The application context where to grab the GetMapOutputFormats from. * @see GetMapOutputFormat#getContentType() */ public Set<String> getAvailableMapFormatNames() { final Collection<GetMapOutputFormat> producers; producers = WMSExtensions.findMapProducers(applicationContext); final Set<String> formats = new HashSet<String>(); for (GetMapOutputFormat producer : producers) { formats.addAll(producer.getOutputFormatNames()); } return formats; } /** * @return all allowed GetMap format names */ public Set<String> getAllowedMapFormatNames() { final Collection<GetMapOutputFormat> producers; producers = WMSExtensions.findMapProducers(applicationContext); final Set<String> formats = new HashSet<String>(); for (GetMapOutputFormat producer : producers) { if (isAllowedGetMapFormat(producer)==false) { continue; // skip this producer, its mime type is not allowed } formats.addAll(producer.getOutputFormatNames()); } return formats; } /** * Checks is a getMap mime type is allowed * * @param format * */ public boolean isAllowedGetMapFormat(GetMapOutputFormat format) { if (getServiceInfo().isGetMapMimeTypeCheckingEnabled()==false) return true; Set<String> mimeTypes = getServiceInfo().getGetMapMimeTypes(); return mimeTypes.contains(format.getMimeType()); } /** * Checks is a getFeatureInfo mime type is allowed * * @param format * */ public boolean isAllowedGetFeatureInfoFormat(GetFeatureInfoOutputFormat format) { if (getServiceInfo().isGetFeatureInfoMimeTypeCheckingEnabled()==false) return true; Set<String> mimeTypes = getServiceInfo().getGetFeatureInfoMimeTypes(); return mimeTypes.contains(format.getContentType()); } /** * create a {@link ServiceException} for an unallowed * GetFeatureInfo format * * @param requestFormat * */ public ServiceException unallowedGetFeatureInfoFormatException(String requestFormat) { ServiceException e = new ServiceException("Getting feature info using " + requestFormat + " is not allowed", "ForbiddenFormat"); e.setCode("ForbiddenFormat"); return e; } /** * create a {@link ServiceException} for an unallowed * GetMap format * * @param requestFormat * */ public ServiceException unallowedGetMapFormatException(String requestFormat) { ServiceException e = new ServiceException("Creating maps using " + requestFormat + " is not allowed", "ForbiddenFormat"); e.setCode("ForbiddenFormat"); return e; } public Set<String> getAvailableLegendGraphicsFormats() { List<GetLegendGraphicOutputFormat> formats; formats = WMSExtensions.findLegendGraphicFormats(applicationContext); Set<String> mimeTypes = new HashSet<String>(); for (GetLegendGraphicOutputFormat format : formats) { mimeTypes.add(format.getContentType()); } return mimeTypes; } /** * Returns all {@link ExtendedCapabilitiesProvider} extensions. */ public List<ExtendedCapabilitiesProvider> getAvailableExtendedCapabilitiesProviders() { return WMSExtensions.findExtendedCapabilitiesProviders(applicationContext); } /** * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) */ public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; // get the default dimension value selector factory, picking the one with // the highest priority (this allows for plugin overrides) defaultDimensionValueFactory = GeoServerExtensions.extensions( DimensionDefaultValueSelectionStrategyFactory.class).get(0); // enable/disable map wrapping if (ENABLE_MAP_WRAPPING == null) { String wrapping = GeoServerExtensions.getProperty("ENABLE_MAP_WRAPPING", applicationContext); // default to true, but allow switching off if (wrapping == null) ENABLE_MAP_WRAPPING = true; else ENABLE_MAP_WRAPPING = Boolean.valueOf(wrapping); } // enable/disable advanced reprojection handling if (ENABLE_ADVANCED_PROJECTION == null) { String projection = GeoServerExtensions.getProperty("ENABLE_ADVANCED_PROJECTION", applicationContext); // default to true, but allow switching off if (projection == null) ENABLE_ADVANCED_PROJECTION = true; else ENABLE_ADVANCED_PROJECTION = Boolean.valueOf(projection); } } /** * @param requestFormat * @return a {@link GetFeatureInfoOutputFormat} that can handle the requested mime type or * {@code null} if none if found */ public GetFeatureInfoOutputFormat getFeatureInfoOutputFormat(String requestFormat) { List<GetFeatureInfoOutputFormat> outputFormats; outputFormats = WMSExtensions.findFeatureInfoFormats(applicationContext); for (GetFeatureInfoOutputFormat format : outputFormats) { if (format.canProduce(requestFormat)) { return format; } } return null; } /** * @return a list of all getFeatureInfo content types */ public List<String> getAvailableFeatureInfoFormats() { List<String> mimeTypes = new ArrayList<String>(); for (GetFeatureInfoOutputFormat format : WMSExtensions.findFeatureInfoFormats(applicationContext)) { mimeTypes.add(format.getContentType()); } return mimeTypes; } /** * @return a list of all allowed getFeature info content types */ public List<String> getAllowedFeatureInfoFormats() { List<String> mimeTypes = new ArrayList<String>(); for (GetFeatureInfoOutputFormat format : WMSExtensions.findFeatureInfoFormats(applicationContext)) { if (isAllowedGetFeatureInfoFormat(format)==false) { continue; // skip this format } mimeTypes.add(format.getContentType()); } return mimeTypes; } /** * @param mimeType * the mime type to look a GetMapOutputFormat for * @return the GetMapOutputFormat that can handle {@code mimeType}, or {@code null} if none is * found */ public GetMapOutputFormat getMapOutputFormat(final String mimeType) { GetMapOutputFormat outputFormat; outputFormat = WMSExtensions.findMapProducer(mimeType, applicationContext); return outputFormat; } /** * * @param outputFormat * desired output format mime type * @return the GetLegendGraphicOutputFormat that can handle {@code mimeType}, or {@code null} if * none is found */ public GetLegendGraphicOutputFormat getLegendGraphicOutputFormat(final String outputFormat) { GetLegendGraphicOutputFormat format; format = WMSExtensions.findLegendGraphicFormat(outputFormat, applicationContext); return format; } /** * Returns a version object for the specified version string. * <p> * Calls through to {@link #version(String, boolean)} with exact set to <code>false</false>. * </p> */ public static Version version(String version) { return version(version, false); } /** * Returns a version object for the specified version string optionally returning null when the * version string does not match one of the available WMS versions. * * @param version * The version string. * @param exact * If set to false, a version object will always be returned. If set to true only a * version matching on of the available wms versions will be returned. * */ public static Version version(String version, boolean exact) { if (version == null || 0 == version.trim().length()) { return null; } if (VERSION_1_1_1.toString().equals(version)) { return VERSION_1_1_1; } else if (VERSION_1_3_0.toString().equals(version)) { return VERSION_1_3_0; } return exact ? null : new Version(version); } /** * Transforms a crs identifier to its internal representation based on the specified WMS * version. * <p> * In version 1.3 of WMS geographic coordinate systems are to be ordered y/x or * latitude/longitude. The only possible way to represent this internally is to use the explicit * epsg namespace "urn:x-ogc:def:crs:EPSG:". This method essentially replaces the traditional * "EPSG:" namespace with the explicit. * </p> */ public static String toInternalSRS(String srs, Version version) { if (VERSION_1_3_0.equals(version)) { if (srs != null && srs.toUpperCase().startsWith("EPSG:")) { srs = srs.toUpperCase().replace("EPSG:", "urn:ogc:def:crs:EPSG:"); } } return srs; } /** * Returns true if the layer can be queried */ public boolean isQueryable(LayerInfo layer) { try { if (layer.getResource() instanceof WMSLayerInfo) { WMSLayerInfo info = (WMSLayerInfo) layer.getResource(); Layer wl = info.getWMSLayer(null); if (!wl.isQueryable()) { return false; } WMSCapabilities caps = info.getStore().getWebMapServer(null).getCapabilities(); OperationType featureInfo = caps.getRequest().getGetFeatureInfo(); if (featureInfo == null || !featureInfo.getFormats() .contains("application/vnd.ogc.gml")) { return false; } } return layer.isQueryable(); } catch (IOException e) { LOGGER.log(Level.INFO, "Failed to determine if the layer is queryable, assuming it's not", e); return false; } } /** * Returns true if the layer is opaque */ public boolean isOpaque(LayerInfo layer) { return layer.isOpaque(); } public Integer getCascadedHopCount(LayerInfo layer) { if (!(layer.getResource() instanceof WMSLayerInfo)) { return null; } WMSLayerInfo wmsLayerInfo = (WMSLayerInfo) layer.getResource(); Layer wmsLayer; int cascaded = 1; try { wmsLayer = wmsLayerInfo.getWMSLayer(null); cascaded = 1 + wmsLayer.getCascaded(); } catch (IOException e) { LOGGER.log(Level.INFO, "Unable to determina WMSLayer cascaded hop count", e); } return cascaded; } public boolean isQueryable(LayerGroupInfo layerGroup) { if (layerGroup.isQueryDisabled()) return false; boolean queryable = false; for (PublishedInfo published : layerGroup.getLayers()) { if (published instanceof LayerInfo) { queryable |= isQueryable((LayerInfo) published); } else { queryable |= isQueryable((LayerGroupInfo) published); } } return queryable; } /** * Returns the read parameters for the specified layer, merging some well known request * parameters into the read parameters if possible * * @param request * @param mapLayerInfo * @param layerFilter * @param reader * */ public GeneralParameterValue[] getWMSReadParameters(final GetMapRequest request, final MapLayerInfo mapLayerInfo, final Filter layerFilter, final List<Object> times, final List<Object> elevations, final GridCoverage2DReader reader, boolean readGeom) throws IOException { // setup the scene final ParameterValueGroup readParametersDescriptor = reader.getFormat().getReadParameters(); CoverageInfo coverage = mapLayerInfo.getCoverage(); MetadataMap metadata = coverage.getMetadata(); GeneralParameterValue[] readParameters = CoverageUtils.getParameters( readParametersDescriptor, coverage.getParameters(), readGeom); ReaderDimensionsAccessor dimensions = new ReaderDimensionsAccessor(reader); // pass down time final DimensionInfo timeInfo = metadata.get(ResourceInfo.TIME, DimensionInfo.class); // add the descriptors for custom dimensions final List<GeneralParameterDescriptor> parameterDescriptors = new ArrayList<GeneralParameterDescriptor>(readParametersDescriptor.getDescriptor().descriptors()); Set<ParameterDescriptor<List>> dynamicParameters = reader.getDynamicParameters(); parameterDescriptors.addAll(dynamicParameters); if (timeInfo != null && timeInfo.isEnabled()) { // handle "default" List<Object> fixedTimes = new ArrayList<Object>(times); for (int i = 0; i < fixedTimes.size(); i++) { if (fixedTimes.get(i) == null) { fixedTimes.set(i, getDefaultTime(coverage)); } } // pass down the parameters readParameters = CoverageUtils.mergeParameter(parameterDescriptors, readParameters, fixedTimes, "TIME", "Time"); } // pass down elevation final DimensionInfo elevationInfo = metadata.get(ResourceInfo.ELEVATION, DimensionInfo.class); if (elevationInfo != null && elevationInfo.isEnabled()) { // handle "default" List<Object> fixedElevations = new ArrayList<Object>(elevations); for (int i = 0; i < fixedElevations.size(); i++) { if (fixedElevations.get(i) == null) { fixedElevations.set(i, getDefaultElevation(coverage)); } } readParameters = CoverageUtils.mergeParameter(parameterDescriptors, readParameters, fixedElevations, "ELEVATION", "Elevation"); } if (layerFilter != null && readParameters != null) { // test for default [empty is replaced with INCLUDE filter] ]filter for (int i = 0; i < readParameters.length; i++) { GeneralParameterValue param = readParameters[i]; GeneralParameterDescriptor pd = param.getDescriptor(); if (pd.getName().getCode().equalsIgnoreCase("FILTER")) { final ParameterValue pv = (ParameterValue) pd.createValue(); // if something different from the default INCLUDE filter is specified if (layerFilter != Filter.INCLUDE) { // override the default filter pv.setValue(layerFilter); readParameters[i] = pv; } break; } } } // custom dimensions List<String> customDomains = new ArrayList(dimensions.getCustomDomains()); for (String domain : new ArrayList<String>(customDomains)) { List<String> values = request.getCustomDimension(domain); if (values != null) { readParameters = CoverageUtils.mergeParameter(parameterDescriptors, readParameters, dimensions.convertDimensionValue(domain, values), domain); customDomains.remove(domain); } } // see if we have any custom domain for which we have to set the default value if(!customDomains.isEmpty()) { for (String name : customDomains) { final DimensionInfo customInfo = metadata.get(ResourceInfo.CUSTOM_DIMENSION_PREFIX + name, DimensionInfo.class); if (customInfo != null && customInfo.isEnabled()) { Object val = dimensions.convertDimensionValue(name, getDefaultCustomDimensionValue(name, coverage, String.class)); readParameters = CoverageUtils.mergeParameter( parameterDescriptors, readParameters, val, name); } } } return readParameters; } public Collection<RenderedImageMapResponse> getAvailableMapResponses() { return WMSExtensions.findMapResponses(applicationContext); } /** * Query and returns the times for the given layer, in the given time range * @throws IOException */ public TreeSet<Object> queryCoverageTimes(CoverageInfo coverage, DateRange queryRange, int maxAnimationSteps) throws IOException { // grab the time metadata DimensionInfo time = coverage.getMetadata().get(ResourceInfo.TIME, DimensionInfo.class); if (time == null || !time.isEnabled()) { throw new ServiceException("Layer " + coverage.getPrefixedName() + " does not have time support enabled"); } GridCoverage2DReader reader = null; try { reader = (GridCoverage2DReader) coverage.getGridCoverageReader(null, null); } catch (Throwable t) { throw new ServiceException("Unable to acquire a reader for this coverage " + coverage.prefixedName(), t); } if (reader == null) { throw new ServiceException("Unable to acquire a reader for this coverage " + coverage.prefixedName()); } ReaderDimensionsAccessor dimensions = new ReaderDimensionsAccessor(reader); return dimensions.getTimeDomain(queryRange, maxAnimationSteps); } /** * Query and returns the times for the given layer, in the given time range */ public TreeSet<Object> queryFeatureTypeTimes(FeatureTypeInfo typeInfo, DateRange range, int maxItems) throws IOException { return queryFeatureTypeDimension(typeInfo, range, maxItems, ResourceInfo.TIME); } /** * Returns the list of time values for the specified typeInfo based on the dimension * representation: all values for {@link DimensionPresentation#LIST}, otherwise min and max * * @param typeInfo * * @throws IOException */ public TreeSet<Date> getFeatureTypeTimes(FeatureTypeInfo typeInfo) throws IOException { // grab the time metadata DimensionInfo time = typeInfo.getMetadata().get(ResourceInfo.TIME, DimensionInfo.class); if (time == null || !time.isEnabled()) { throw new ServiceException("Layer " + typeInfo.prefixedName() + " does not have time support enabled"); } FeatureCollection collection = getDimensionCollection(typeInfo, time); TreeSet<Date> result = new TreeSet<Date>(); if (time.getPresentation() == DimensionPresentation.LIST) { final UniqueVisitor visitor = new UniqueVisitor(time.getAttribute()); collection.accepts(visitor, null); @SuppressWarnings("unchecked") Set<Date> values = visitor.getUnique(); if (values.size() <= 0) { result = null; } else { // we might get null values out of the visitor, strip them values.remove(null); result.addAll(values); } } else { final MinVisitor min = new MinVisitor(time.getAttribute()); collection.accepts(min, null); CalcResult minResult = min.getResult(); // check calcresult first to avoid potential IllegalStateException if no features are in collection if (minResult != CalcResult.NULL_RESULT) { result.add((Date) min.getMin()); final MaxVisitor max = new MaxVisitor(time.getAttribute()); collection.accepts(max, null); result.add((Date) max.getMax()); } } return result; } /** * Query and returns the elevations for the given layer, in the given time range * @throws IOException */ public TreeSet<Object> queryCoverageElevations(CoverageInfo coverage, NumberRange queryRange, int maxAnimationSteps) throws IOException { // grab the metadata DimensionInfo elevation = coverage.getMetadata().get(ResourceInfo.ELEVATION, DimensionInfo.class); if (elevation == null || !elevation.isEnabled()) { throw new ServiceException("Layer " + coverage.prefixedName() + " does not have elevation support enabled"); } GridCoverage2DReader reader = null; try { reader = (GridCoverage2DReader) coverage.getGridCoverageReader(null, null); } catch (Throwable t) { throw new ServiceException("Unable to acquire a reader for this coverage " + coverage.prefixedName(), t); } if (reader == null) { throw new ServiceException("Unable to acquire a reader for this coverage " + coverage.prefixedName()); } ReaderDimensionsAccessor dimensions = new ReaderDimensionsAccessor(reader); return dimensions.getElevationDomain(queryRange, maxAnimationSteps); } /** * Returns the list of elevation values for the specified typeInfo based on the dimension * representation: all values for {@link DimensionPresentation#LIST}, otherwise min and max * * @param typeInfo * * @throws IOException */ public TreeSet<Double> getFeatureTypeElevations(FeatureTypeInfo typeInfo) throws IOException { // grab the time metadata DimensionInfo elevation = typeInfo.getMetadata().get(ResourceInfo.ELEVATION, DimensionInfo.class); if (elevation == null || !elevation.isEnabled()) { throw new ServiceException("Layer " + typeInfo.prefixedName() + " does not have elevation support enabled"); } FeatureCollection collection = getDimensionCollection(typeInfo, elevation); TreeSet<Double> result = new TreeSet<Double>(); if (elevation.getPresentation() == DimensionPresentation.LIST || (elevation.getPresentation() == DimensionPresentation.DISCRETE_INTERVAL && elevation .getResolution() == null)) { final UniqueVisitor visitor = new UniqueVisitor(elevation.getAttribute()); collection.accepts(visitor, null); @SuppressWarnings("unchecked") Set<Object> values = visitor.getUnique(); if (values.size() <= 0) { result = null; } else { for (Object value : values) { result.add(((Number) value).doubleValue()); } } } else { final MinVisitor min = new MinVisitor(elevation.getAttribute()); collection.accepts(min, null); // check calcresult first to avoid potential IllegalStateException if no features are in collection CalcResult calcResult = min.getResult(); if (calcResult != CalcResult.NULL_RESULT) { result.add(((Number) min.getMin()).doubleValue()); final MaxVisitor max = new MaxVisitor(elevation.getAttribute()); collection.accepts(max, null); result.add(((Number) max.getMax()).doubleValue()); } } return result; } /** * Query and returns the times for the given layer, in the given time range */ public TreeSet<Object> queryFeatureTypeElevations(FeatureTypeInfo typeInfo, NumberRange range, int maxItems) throws IOException { return queryFeatureTypeDimension(typeInfo, range, maxItems, ResourceInfo.ELEVATION); } /** * Query and returns the dimension values for the given layer, in the given range */ TreeSet<Object> queryFeatureTypeDimension(FeatureTypeInfo typeInfo, Range range, int maxItems, String dimensionName) throws IOException { // grab the metadata DimensionInfo di = typeInfo.getMetadata().get(dimensionName, DimensionInfo.class); if (di == null || !di.isEnabled()) { throw new ServiceException("Layer " + typeInfo.prefixedName() + " does not have " + dimensionName + " support enabled"); } // filter by date range FeatureSource fs = getFeatureSource(typeInfo); // build query to grab the time values final Query query = new Query(fs.getSchema().getName().getLocalPart()); query.setPropertyNames(Arrays.asList(di.getAttribute())); final PropertyName attribute = ff.property(di.getAttribute()); final PropertyIsBetween rangeFilter = ff.between(attribute, ff.literal(range.getMinValue()), ff.literal(range.getMaxValue())); query.setFilter(rangeFilter); query.setMaxFeatures(maxItems); FeatureCollection collection = fs.getFeatures(query); // collect all unique values (can't do ranges now, we don't have a multi-attribute unique visitor) UniqueVisitor visitor = new UniqueVisitor(attribute); collection.accepts(visitor, null); TreeSet<Object> result = new TreeSet<>(visitor.getUnique()); return result; } /** * Returns the default value for time dimension. * * @param resourceInfo * */ public Object getDefaultTime(ResourceInfo resourceInfo) { // check the time metadata DimensionInfo time = resourceInfo.getMetadata().get(ResourceInfo.TIME, DimensionInfo.class); if (time == null || !time.isEnabled()) { throw new ServiceException("Layer " + resourceInfo.prefixedName() + " does not have time support enabled"); } DimensionDefaultValueSelectionStrategy strategy = this.getDefaultValueStrategy(resourceInfo, ResourceInfo.TIME, time); return strategy.getDefaultValue(resourceInfo, ResourceInfo.TIME, time, Date.class); } /** * Returns the default value for elevation dimension. * * @param resourceInfo * */ public Object getDefaultElevation(ResourceInfo resourceInfo) { DimensionInfo elevation = getDimensionInfo(resourceInfo, ResourceInfo.ELEVATION); DimensionDefaultValueSelectionStrategy strategy = this.getDefaultValueStrategy(resourceInfo, ResourceInfo.ELEVATION, elevation); return strategy.getDefaultValue(resourceInfo, ResourceInfo.ELEVATION, elevation, Double.class); } /** * Looks up the elevation configuration, throws an exception if not found * @param resourceInfo * @param dimensionName * */ public DimensionInfo getDimensionInfo(ResourceInfo resourceInfo, String dimensionName) { DimensionInfo info = resourceInfo.getMetadata().get(dimensionName, DimensionInfo.class); if (info == null || !info.isEnabled()) { throw new ServiceException("Layer " + resourceInfo.prefixedName() + " does not have " + dimensionName + " support enabled"); } return info; } /** * Returns the default value for the given custom dimension. * * @param <T> * @param dimensionName * @param resourceInfo * @param clz * */ public <T> T getDefaultCustomDimensionValue(String dimensionName, ResourceInfo resourceInfo, Class<T> clz){ DimensionInfo customDim = resourceInfo.getMetadata().get(ResourceInfo.CUSTOM_DIMENSION_PREFIX+dimensionName, DimensionInfo.class); if (customDim == null || !customDim.isEnabled()) { throw new ServiceException("Layer " + resourceInfo.prefixedName() + " does not have support enabled for dimension "+dimensionName); } DimensionDefaultValueSelectionStrategy strategy = this.getDefaultValueStrategy(resourceInfo, ResourceInfo.CUSTOM_DIMENSION_PREFIX+dimensionName, customDim); // custom dimensions have no range support return (T) strategy.getDefaultValue(resourceInfo, ResourceInfo.CUSTOM_DIMENSION_PREFIX+dimensionName, customDim, clz); } public DimensionDefaultValueSelectionStrategy getDefaultValueStrategy(ResourceInfo resource, String dimensionName, DimensionInfo dimensionInfo){ if (defaultDimensionValueFactory != null) { return defaultDimensionValueFactory.getStrategy(resource, dimensionName, dimensionInfo); } else { return null; } } /** * Returns the collection of all values of the dimension attribute, eventually sorted if the * native capabilities allow for it * * @param typeInfo * @param dimension * * @throws IOException */ FeatureCollection getDimensionCollection(FeatureTypeInfo typeInfo, DimensionInfo dimension) throws IOException { FeatureSource source = getFeatureSource(typeInfo); // build query to grab the dimension values final Query dimQuery = new Query(source.getSchema().getName().getLocalPart()); dimQuery.setPropertyNames(Arrays.asList(dimension.getAttribute())); return source.getFeatures(dimQuery); } /** * Returns the feature source for the given feature type */ FeatureSource getFeatureSource(FeatureTypeInfo typeInfo) { // grab the feature source FeatureSource source = null; try { source = typeInfo.getFeatureSource(null, GeoTools.getDefaultHints()); } catch (IOException e) { throw new ServiceException( "Could not get the feauture source to list time info for layer " + typeInfo.prefixedName(), e); } return source; } /** * Builds a filter for the current time and elevation, should the layer support them. Only one * among time and elevation can be multi-valued * * @param layerFilter * @param currentTime * @param currentElevation * @param mapLayerInfo * */ public Filter getTimeElevationToFilter(List<Object> times, List<Object> elevations, FeatureTypeInfo typeInfo) throws IOException { DimensionInfo timeInfo = typeInfo.getMetadata().get(ResourceInfo.TIME, DimensionInfo.class); DimensionInfo elevationInfo = typeInfo.getMetadata().get(ResourceInfo.ELEVATION, DimensionInfo.class); DimensionFilterBuilder builder = new DimensionFilterBuilder(ff); // handle time support if (timeInfo != null && timeInfo.isEnabled() && times != null) { List<Object> defaultedTimes = new ArrayList<Object>(times.size()); for (Object datetime : times) { if (datetime == null) { // this is "default" datetime = getDefaultTime(typeInfo); } defaultedTimes.add(datetime); } builder.appendFilters(timeInfo.getAttribute(), timeInfo.getEndAttribute(), defaultedTimes); } // handle elevation support if (elevationInfo != null && elevationInfo.isEnabled() && elevations != null) { List<Object> defaultedElevations = new ArrayList<Object>(elevations.size()); for (Object elevation : elevations) { if (elevation == null) { // this is "default" elevation = getDefaultElevation(typeInfo); } defaultedElevations.add(elevation); } builder.appendFilters(elevationInfo.getAttribute(), elevationInfo.getEndAttribute(), defaultedElevations); } Filter result = builder.getFilter(); return result; } /** * Returns the max rendering time taking into account the server limits and the request options * @param request * @return */ public int getMaxRenderingTime(GetMapRequest request) { int localMaxRenderingTime = 0; Object timeoutOption = request.getFormatOptions().get("timeout"); if (timeoutOption != null) { try { localMaxRenderingTime = Integer.parseInt(timeoutOption.toString()); } catch (NumberFormatException e) { RenderedImageMapOutputFormat.LOGGER.log(Level.WARNING,"Could not parse format_option \"timeout\": "+timeoutOption, e); } } int maxRenderingTime = getMaxRenderingTime(localMaxRenderingTime); return maxRenderingTime; } /** * Returns the max rendering time for animations taking into account the server limits and the request options * @param request * @return */ public int getMaxAnimationRenderingTime(GetMapRequest request) { int localMaxRenderingTime = 0; Object timeoutOption = request.getFormatOptions().get("timeout"); if (timeoutOption != null) { try { localMaxRenderingTime = Integer.parseInt(timeoutOption.toString()); } catch (NumberFormatException e) { RenderedImageMapOutputFormat.LOGGER.log(Level.WARNING,"Could not parse format_option \"timeout\": "+timeoutOption, e); } } Long maxRenderingTime = getMaxAnimatorRenderingTime(); if(maxRenderingTime == null) { return localMaxRenderingTime; } else if(localMaxRenderingTime == 0) { return maxRenderingTime.intValue(); } else { return Math.min(maxRenderingTime.intValue(), localMaxRenderingTime); } } /** * Timeout on the smallest nonzero value of the WMS timeout and the timeout format option * If both are zero then there is no timeout * * @param localMaxRenderingTime * */ private int getMaxRenderingTime(int localMaxRenderingTime) { int maxRenderingTime = getMaxRenderingTime() * 1000; if (maxRenderingTime == 0) { maxRenderingTime = localMaxRenderingTime; } else if (localMaxRenderingTime != 0) { maxRenderingTime = Math.min(maxRenderingTime, localMaxRenderingTime); } return maxRenderingTime; } /** * Converts a coordinate expressed on the device space back to real world coordinates. Stolen * from LiteRenderer but without the need of a Graphics object * * @param x * horizontal coordinate on device space * @param y * vertical coordinate on device space * @param map * The map extent * @param width * image width * @param height * image height * * @return The correspondent real world coordinate * * @throws RuntimeException */ public static Coordinate pixelToWorld(double x, double y, ReferencedEnvelope map, double width, double height) { // set up the affine transform and calculate scale values AffineTransform at = worldToScreenTransform(map, width, height); Point2D result = null; try { result = at.inverseTransform(new java.awt.geom.Point2D.Double(x, y), new java.awt.geom.Point2D.Double()); } catch (NoninvertibleTransformException e) { throw new RuntimeException(e); } Coordinate c = new Coordinate(result.getX(), result.getY()); return c; } /** * Sets up the affine transform. Stolen from liteRenderer code. * * @param mapExtent * the map extent * @param width * the screen size * @param height * * @return a transform that maps from real world coordinates to the screen */ public static AffineTransform worldToScreenTransform(ReferencedEnvelope mapExtent, double width, double height) { //the transformation depends on an x/y ordering, if we have a lat/lon crs swap it CoordinateReferenceSystem crs = mapExtent.getCoordinateReferenceSystem(); boolean swap = crs != null && CRS.getAxisOrder(crs) == AxisOrder.NORTH_EAST; if (swap) { mapExtent = new ReferencedEnvelope(mapExtent.getMinY(), mapExtent.getMaxY(), mapExtent.getMinX(), mapExtent.getMaxX(), null); } double scaleX = width / mapExtent.getWidth(); double scaleY = height / mapExtent.getHeight(); double tx = -mapExtent.getMinX() * scaleX; double ty = (mapExtent.getMinY() * scaleY) + height; AffineTransform at = new AffineTransform(scaleX, 0.0d, 0.0d, -scaleY, tx, ty); //if we swapped concatenate a transform that swaps back if (swap) { at.concatenate(new AffineTransform(0, 1, 1, 0, 0, 0)); } return at; } public static WMS get() { return GeoServerExtensions.bean(WMS.class); } /** * Checks if the layer can be drawn, that is, if it's raster, or vector with a geometry attribute * @param lyr * @return */ public static boolean isWmsExposable(LayerInfo lyr) { if (lyr.getType() == PublishedType.RASTER || lyr.getType() == PublishedType.WMS) { return true; } if (lyr.getType() == PublishedType.VECTOR) { final ResourceInfo resource = lyr.getResource(); try { for (AttributeTypeInfo att : ((FeatureTypeInfo) resource).attributes()) { if (att.getBinding() != null && Geometry.class.isAssignableFrom(att.getBinding())) { return true; } } } catch (Exception e) { LOGGER.log(Level.SEVERE, "An error occurred trying to determine if" + " the layer is geometryless", e); } } return false; } }