Package org.geoserver.wcs2_0.response

Source Code of org.geoserver.wcs2_0.response.WCS20GetCapabilitiesTransformer

/* (c) 2014 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.wcs2_0.response;

import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.geoserver.ows.util.ResponseUtils.appendQueryString;
import static org.geoserver.ows.util.ResponseUtils.buildURL;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.opengis.wcs20.GetCapabilitiesType;

import org.geoserver.ExtendedCapabilitiesProvider;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.KeywordInfo;
import org.geoserver.config.ContactInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.config.ResourceErrorHandling;
import org.geoserver.config.SettingsInfo;
import org.geoserver.ows.URLMangler;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.ServiceException;
import org.geoserver.wcs.WCSInfo;
import org.geoserver.wcs.responses.CoverageResponseDelegate;
import org.geoserver.wcs.responses.CoverageResponseDelegateFinder;
import org.geoserver.wcs2_0.GetCoverage;
import org.geoserver.wcs2_0.WCS20Const;
import org.geoserver.wcs2_0.util.NCNameResourceCodec;
import org.geotools.referencing.CRS;
import org.geotools.util.logging.Logging;
import org.geotools.wcs.v2_0.WCS;
import org.geotools.xml.transform.TransformerBase;
import org.geotools.xml.transform.Translator;
import org.opengis.geometry.BoundingBox;
import org.vfny.geoserver.global.CoverageInfoLabelComparator;
import org.vfny.geoserver.wcs.WcsException;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.NamespaceSupport;

/**
* Transformer for GetCapabilities
*
* @author Emanuele Tajariol (etj) - GeoSolutions
* @author Simone Giannecchini, GeoSolutions
*/
public class WCS20GetCapabilitiesTransformer extends TransformerBase {

    private static final Logger LOGGER = Logging.getLogger(WCS20GetCapabilitiesTransformer.class);

    protected static final String CUR_VERSION = WCS20Const.V201;

    private WCSInfo wcs;

    private final boolean skipMisconfigured;

    private CoverageResponseDelegateFinder responseFactory;

    /** {@link Enum} that identifies the various sections.*/
    enum SECTIONS {
        ServiceIdentification, ServiceProvider, OperationsMetadata, ServiceMetadata, Contents, Languages, All;

        public static final Set<String> names;
        static  {
            Set<String> tmp = new HashSet<String>();
            for (SECTIONS section : SECTIONS.values()) {
                tmp.add(section.name());
            }
            names = Collections.unmodifiableSet(tmp);
        }

    };


    public WCS20GetCapabilitiesTransformer(GeoServer gs, CoverageResponseDelegateFinder responseFactory) {
        this.wcs = gs.getService(WCSInfo.class);
        this.skipMisconfigured = ResourceErrorHandling.SKIP_MISCONFIGURED_LAYERS.equals(
                gs.getGlobal().getResourceErrorHandling());
        this.responseFactory = responseFactory;
        setNamespaceDeclarationEnabled(false);
    }

    @Override
    public Translator createTranslator(ContentHandler handler) {
        return new WCS20GetCapabilitiesTranslator(handler);
    }


    private class WCS20GetCapabilitiesTranslator extends TranslatorSupport {
        /**
         * DOCUMENT ME!
         *
         * @uml.property name="request"
         * @uml.associationEnd multiplicity="(0 1)"
         */
        private GetCapabilitiesType request;
        private List<WCSExtendedCapabilitiesProvider> extensions;
        private org.geoserver.ExtendedCapabilitiesProvider.Translator translator;
        private TranslatorHelper helper;

        /**
         * Creates a new WFSCapsTranslator object.
         *
         * @param handler
         *            DOCUMENT ME!
         */
        public WCS20GetCapabilitiesTranslator(ContentHandler handler) {
            super(handler, null, null);
            this.helper = new TranslatorHelper();
            this.extensions = GeoServerExtensions.extensions(WCSExtendedCapabilitiesProvider.class);
            // register namespaces provided by extended capabilities
            NamespaceSupport namespaces = getNamespaceSupport();
            namespaces.declarePrefix("wcscrs", "http://www.opengis.net/wcs/service-extension/crs/1.0");
            namespaces.declarePrefix("int", "http://www.opengis.net/WCS_service-extension_interpolation/1.0");

            for (WCSExtendedCapabilitiesProvider cp : extensions) {
                cp.registerNamespaces(namespaces);
            }
            this.translator = new ExtendedCapabilitiesProvider.Translator() {
               
                @Override
                public void start(String element, Attributes attributes) {
                    WCS20GetCapabilitiesTranslator.this.start(element, attributes);
                }
               
                @Override
                public void start(String element) {
                    WCS20GetCapabilitiesTranslator.this.start(element);
                   
                }
               
                @Override
                public void end(String element) {
                    WCS20GetCapabilitiesTranslator.this.end(element);
                   
                }
               
                @Override
                public void chars(String text) {
                    WCS20GetCapabilitiesTranslator.this.chars(text);
                }
            };
        }

        /**
         * Encode the object.
         *
         * @param o
         *            The Object to encode.
         *
         * @throws IllegalArgumentException
         *             if the Object is not encodeable.
         */
        public void encode(Object o) throws IllegalArgumentException {
            if (!(o instanceof GetCapabilitiesType)) {
                throw new IllegalArgumentException("Not a GetCapabilitiesType: "+o!=null?o.toString():"null");
            }

            this.request = (GetCapabilitiesType) o;

            // check the update sequence
            final long updateSequence = wcs.getGeoServer().getGlobal().getUpdateSequence();
            long requestedUpdateSequence = -1;
            if (request.getUpdateSequence() != null) {
                try {
                    requestedUpdateSequence = Long.parseLong(request.getUpdateSequence());
                } catch (NumberFormatException e) {
                    throw new WcsException("Invalid update sequence number format, "
                            + "should be an integer", WcsException.WcsExceptionCode.InvalidUpdateSequence,
                            "updateSequence");
                }
                if (requestedUpdateSequence > updateSequence) {
                    throw new WcsException("Invalid update sequence value, it's higher "
                            + "than the current value, " + updateSequence,
                            WcsException.WcsExceptionCode.InvalidUpdateSequence, "updateSequence");
                }
            }

            // check and init sections
            // handle the sections directive
            boolean allSections;
            List<String> sections;
            if (request.getSections() == null) {
                sections = Collections.emptyList();
                allSections = true;
            } else {
                sections = request.getSections().getSection();
                allSections = sections.contains(SECTIONS.All.name());

                for (String section : sections) {
                    if(! SECTIONS.names.contains(section))
                        throw new WcsException("Unknown section " + section,
                                WcsException.WcsExceptionCode.InvalidParameterValue, "Sections");
                }
            }

            // Build the document

            final AttributesImpl attributes = WCS20Const.getDefaultNamespaces();
            attributes.addAttribute("", "version", "version", "", CUR_VERSION);
            attributes.addAttribute("", "updateSequence", "updateSequence", "", String.valueOf(updateSequence));
            helper.registerNamespaces(getNamespaceSupport(), attributes);
           
            // TODO: add a config to choose the canonical or local schema
            String location = buildSchemaLocation(request.getBaseUrl(), WCS.NAMESPACE, "http://schemas.opengis.net/wcs/2.0/wcsGetCapabilities.xsd");
           
            // final String locationDef = WCS.NAMESPACE + " " + buildSchemaURL(request.getBaseUrl(), "wcs/2.0/wcsGetCapabilities.xsd");//
            attributes.addAttribute("", "xsi:schemaLocation", "xsi:schemaLocation", "", location);

            start("wcs:Capabilities", attributes);

            // encode the actual capabilities contents taking into consideration
            // the sections
            if (requestedUpdateSequence < updateSequence) {
                if (allSections || sections.contains(SECTIONS.ServiceIdentification.name()))
                    handleServiceIdentification();
                if (allSections || sections.contains(SECTIONS.ServiceProvider.name()))
                    handleServiceProvider();
                if (allSections || sections.contains(SECTIONS.OperationsMetadata.name()))
                    handleOperationsMetadata();
                if (allSections || sections.contains(SECTIONS.ServiceMetadata.name()))
                    handleServiceMetadata(request);
                if (allSections || sections.contains(SECTIONS.Contents.name()))
                    handleContents();
                if (allSections || sections.contains(SECTIONS.Languages.name()))
                    handleLanguages();
            }

            end("wcs:Capabilities");
        }
       
        String buildSchemaLocation(String schemaBaseURL, String... locations) {
            for (WCSExtendedCapabilitiesProvider cp : extensions) {
                locations = helper.append(locations, cp.getSchemaLocations(schemaBaseURL));
            }

            return helper.buildSchemaLocation(locations);
        }
       
        private void handleServiceMetadata(GetCapabilitiesType ct) {
            start("wcs:ServiceMetadata");
           
            // formats are part of the document only starting version 2.0.1
            if(ct.getAcceptVersions() == null || ct.getAcceptVersions().getVersion() == null
                    || ct.getAcceptVersions().getVersion().isEmpty() || ct.getAcceptVersions().getVersion().contains("2.0.1")) {
                Set<String> formats = new TreeSet<String>();
                for (String format : responseFactory.getOutputFormats()) {
                    CoverageResponseDelegate delegate = responseFactory.encoderFor(format);
                    String mime = delegate.getMimeType(format);
                    try {
                        new URI(mime);
                        formats.add(mime);
                    } catch(URISyntaxException e) {
                        // skip it
                    }
                }
                for (String format : formats) {
                    element("wcs:formatSupported", format);
                }
            }
           
            // the CRS extension requires us to declare the full list of supported CRS
            start("wcs:Extension");
            // add the supported CRS
            Collection<String> codes;
            if(wcs.getSRS() == null || wcs.getSRS().isEmpty()) {
                codes = CRS.getSupportedCodes("EPSG");
            } else {
                codes = wcs.getSRS();
            }
            for (String code : codes) {
                if(!code.equals("WGS84(DD)")) {
                     element("wcscrs:crsSupported", "http://www.opengis.net/def/crs/EPSG/0/" + code);
                }
            }

            // add the supported interpolation methods
            element("int:interpolationSupported", "http://www.opengis.net/def/interpolation/OGC/1/nearest-neighbor");
            element("int:interpolationSupported", "http://www.opengis.net/def/interpolation/OGC/1/linear");
            element("int:interpolationSupported", "http://www.opengis.net/def/interpolation/OGC/1/cubic");
           
            end("wcs:Extension");
           
            end("wcs:ServiceMetadata");
        }

        /**
         * Handles the service identification of the capabilities document.
         *
         * @param config
         *            The OGC service to transform.
         *
         * @throws SAXException
         *             For any errors.
         */
        private void handleServiceIdentification() {
            start("ows:ServiceIdentification");

            element("ows:Title", wcs.getTitle());
            element("ows:Abstract", wcs.getAbstract());
           
            handleKeywords(wcs.getKeywords());

            element("ows:ServiceType", "urn:ogc:service:wcs"); // TODO: check this: some docs specify a "OGC WCS" string
            element("ows:ServiceTypeVersion", WCS20Const.V201);
            element("ows:ServiceTypeVersion", WCS20Const.V111);
            element("ows:ServiceTypeVersion", WCS20Const.V110);

            element("ows:Profile", "http://www.opengis.net/spec/WCS/2.0/conf/core");
            element("ows:Profile", "http://www.opengis.net/spec/WCS_protocol-binding_get-kvp/1.0.1"); // requirement #1 in OGC 09-147r3
            element("ows:Profile", "http://www.opengis.net/spec/WCS_protocol-binding_post-xml/1.0");

           
           
           
            // don't believe we support this one
            // element("ows:Profile", "http://www.opengis.net/spec/WCS_service-extension_crs/1.0/conf/crs-discrete-coverage");
            element("ows:Profile", "http://www.opengis.net/spec/WCS_service-extension_crs/1.0/conf/crs-gridded-coverage");
           
            // element("ows:Profile","http://www.opengis.net/spec/WCS_coverage-encoding/1.0/conf/coverage-encoding"); // TODO: check specs and URL
           
            // === GeoTiff encoding extension
            element("ows:Profile"," http://www.opengis.net/spec/WCS_geotiff-coverages/1.0/conf/geotiff-coverage");// TODO: check specs and URL
           
            // === GML encoding
            element("ows:Profile","http://www.opengis.net/spec/GMLCOV/1.0/conf/gml-coverage");
            element("ows:Profile","http://www.opengis.net/spec/GMLCOV/1.0/conf/special-format");
            element("ows:Profile","http://www.opengis.net/spec/GMLCOV/1.0/conf/multipart");
           
            // === Scaling Extension
            element("ows:Profile","http://www.opengis.net/spec/WCS_service-extension_scaling/1.0/conf/scaling");
           
            // === CRS Extension
            element("ows:Profile", "http://www.opengis.net/spec/WCS_service-extension_crs/1.0/conf/crs");
           
            // === Interpolation
            element("ows:Profile", "http://www.opengis.net/spec/WCS_service-extension_interpolation/1.0/conf/interpolation");
            element("ows:Profile", "http://www.opengis.net/spec/WCS_service-extension_interpolation/1.0/conf/interpolation-per-axis"); // TODO for time axis
            element("ows:Profile","http://www.opengis.net/spec/WCS_service-extension_interpolation/1.0/conf/nearest-neighbor");
            element("ows:Profile","http://www.opengis.net/spec/WCS_service-extension_interpolation/1.0/conf/linear");
            element("ows:Profile","http://www.opengis.net/spec/WCS_service-extension_interpolation/1.0/conf/cubic");
           
            // === Range Subsetting
            element("ows:Profile","http://www.opengis.net/spec/WCS_service-extension_range-subsetting/1.0/conf/record-subsetting");
            // TODO don't believe we support these
            // element("ows:Profile","http://www.opengis.net/spec/WCS_service-extension_array-subsetting/1.0/conf/array-subsetting");
            // element("ows:Profile","http://www.opengis.net/spec/WCS_service-extension_range-subsetting/1.0/conf/nested-subsetting");

            String fees = wcs.getFees();
            if ( isBlank(fees)) {
                fees = "NONE";
            }
            element("ows:Fees", fees);

            String accessConstraints = wcs.getAccessConstraints();
            if ( isBlank(accessConstraints)) {
                accessConstraints = "NONE";
            }
            element("ows:AccessConstraints", accessConstraints);
            end("ows:ServiceIdentification");
        }

        /**
         * Handles the service provider of the capabilities document.
         *
         * @param config
         *            The OGC service to transform.
         *
         * @throws SAXException
         *             For any errors.
         */
        private void handleServiceProvider() {
            start("ows:ServiceProvider");
            SettingsInfo settings = wcs.getGeoServer().getSettings();
            element("ows:ProviderName", settings.getContact().getContactOrganization());
            AttributesImpl attributes = new AttributesImpl();
            attributes.addAttribute("", "xlink:href", "xlink:href", "",
                settings.getOnlineResource() != null ? settings.getOnlineResource() : "");
            element("ows:ProviderSite", null, attributes);

            handleContact();

            end("ows:ServiceProvider");
        }

        /**
         * Handles the OperationMetadata portion of the document, printing out
         * the operations and where to bind to them.
         *
         * @param config
         *            The global wms.
         *
         * @throws SAXException
         *             For any problems.
         */
        private void handleOperationsMetadata() {
            start("ows:OperationsMetadata");
            handleOperation("GetCapabilities", null);
            handleOperation("DescribeCoverage", null);
            handleOperation("GetCoverage", null);

            // specify that we do support xml post encoding, clause 8.3.2.2 of
            // the WCS 1.1.1 spec
            AttributesImpl attributes = new AttributesImpl();
            attributes.addAttribute(null, "name", "name", null, "PostEncoding");
            start("ows:Constraint", attributes);
            start("ows:AllowedValues");
            element("ows:Value", "XML");
//            element("ows:Value", "text/xml");
//            element("ows:Value", "application/xml");
            end("ows:AllowedValues");
            end("ows:Constraint");
           
            if(extensions != null && extensions.size() > 0) {
                try {
                    for (WCSExtendedCapabilitiesProvider provider : extensions) {
                        provider.encodeExtendedOperations(translator, wcs, request);
                    }
                } catch (Exception e) {
                    throw new ServiceException("Extended capabilities provider threw error", e);
                }
            }

            end("ows:OperationsMetadata");
        }

        private void handleOperation(String capabilityName, Map<String, List<String>> parameters) {
            AttributesImpl attributes = new AttributesImpl();
            attributes.addAttribute(null, "name", "name", null, capabilityName);
            start("ows:Operation", attributes);

            final String url = appendQueryString(buildURL(request.getBaseUrl(), "wcs", null, URLMangler.URLType.SERVICE), "");

            start("ows:DCP");
            start("ows:HTTP");
            attributes = new AttributesImpl();
            attributes.addAttribute("", "xlink:href", "xlink:href", "", url);
            element("ows:Get", null, attributes);
            end("ows:HTTP");
            end("ows:DCP");

            attributes = new AttributesImpl();
            attributes.addAttribute("", "xlink:href", "xlink:href", "", url);
            start("ows:DCP");
            start("ows:HTTP");
            element("ows:Post", null, attributes);
            end("ows:HTTP");
            end("ows:DCP");

            if (parameters != null && !parameters.isEmpty()) {
                for (Map.Entry<String, List<String>> param : parameters.entrySet()) {
                    attributes = new AttributesImpl();
                    attributes.addAttribute("", "name", "name", "", param.getKey());
                    start("ows:Parameter", attributes);
                    start("ows:AllowedValues");
                    for (String value : param.getValue()) {
                        element("ows:Value", value);
                    }
                    end("ows:AllowedValues");
                    end("ows:Parameter");
                }
            }

            end("ows:Operation");
        }

        /**
         * DOCUMENT ME!
         *
         * @param kwords
         *            DOCUMENT ME!
         *
         * @throws SAXException
         *             DOCUMENT ME!
         */
        private void handleKeywords(List<KeywordInfo> kwords) {
            if( kwords != null && ! kwords.isEmpty()) {
                start("ows:Keywords");
                for (KeywordInfo kword : kwords) {
                    element("ows:Keyword", kword.getValue());
                }
                end("ows:Keywords");
            }
        }

        /**
         * Handles contacts.
         *
         * @param wcs
         *            the service.
         */
        private void handleContact() {
            final GeoServer gs = wcs.getGeoServer();
            start("ows:ServiceContact");

            ContactInfo contact = gs.getSettings().getContact();
            elementIfNotEmpty("ows:IndividualName", contact.getContactPerson());
            elementIfNotEmpty("ows:PositionName", contact.getContactPosition());

            start("ows:ContactInfo");
            start("ows:Phone");
            elementIfNotEmpty("ows:Voice", contact.getContactVoice());
            elementIfNotEmpty("ows:Facsimile", contact.getContactFacsimile());
            end("ows:Phone");
            start("ows:Address");
            elementIfNotEmpty("ows:DeliveryPoint", contact.getAddress());
            elementIfNotEmpty("ows:City", contact.getAddressCity());
            elementIfNotEmpty("ows:AdministrativeArea", contact.getAddressState());
            elementIfNotEmpty("ows:PostalCode", contact.getAddressPostalCode());
            elementIfNotEmpty("ows:Country", contact.getAddressCountry());
            elementIfNotEmpty("ows:ElectronicMailAddress", contact.getContactEmail());
            end("ows:Address");

            String or = gs.getSettings().getOnlineResource();
            if ( isNotBlank(or)) {
                AttributesImpl attributes = new AttributesImpl();
                attributes.addAttribute("", "xlink:href", "xlink:href", "", or);
                start("ows:OnlineResource", attributes);
                end("OnlineResource");
            }

            end("ows:ContactInfo");
            end("ows:ServiceContact");
        }

        private void handleWGS84BoundingBox(BoundingBox envelope) {
            start("ows:WGS84BoundingBox");
            element("ows:LowerCorner", new StringBuilder(Double.toString(envelope.getLowerCorner()
                    .getOrdinate(0))).append(" ").append(envelope.getLowerCorner().getOrdinate(1))
                    .toString());
            element("ows:UpperCorner", new StringBuilder(Double.toString(envelope.getUpperCorner()
                    .getOrdinate(0))).append(" ").append(envelope.getUpperCorner().getOrdinate(1))
                    .toString());
            end("ows:WGS84BoundingBox");
        }

        private void handleContents() {
            start("wcs:Contents");

            @SuppressWarnings("unchecked")
            final Set<CoverageInfo> coverages = new TreeSet<CoverageInfo>(new CoverageInfoLabelComparator());
            coverages.addAll(wcs.getGeoServer().getCatalog().getCoverages());

            // filter out disabled coverages
            for (Iterator<CoverageInfo> it = coverages.iterator(); it.hasNext();) {
                CoverageInfo cv = (CoverageInfo) it.next();
                if (!cv.enabled()) {
                    it.remove();
                }
            }
           
            for (CoverageInfo cv : coverages) {
                try {
                    mark();
                    handleCoverageSummary(cv);
                    commit();
                } catch (Exception e) {
                    if (skipMisconfigured) {
                        reset();
                        LOGGER.log(Level.SEVERE, "Skipping coverage " + cv.prefixedName()
                                + " as its capabilities generation failed", e);
                    } else {
                        throw new RuntimeException("Capabilities document generation failed on coverage "
                                + cv.prefixedName(), e);
                    }
                }
            }
           
            if(extensions != null && extensions.size() > 0) {
                start("wcs:Extension");
                try {
                    for (WCSExtendedCapabilitiesProvider provider : extensions) {
                        provider.encodeExtendedContents(translator, wcs, new ArrayList<CoverageInfo>(coverages), request);
                    }
                } catch (Exception e) {
                    throw new ServiceException("Extended capabilities provider threw error", e);
                }
                end("wcs:Extension");
            }

            end("wcs:Contents");
        }

        private void handleCoverageSummary(CoverageInfo cv) throws Exception {
            start("wcs:CoverageSummary");
            String covId = NCNameResourceCodec.encode(cv);
            element("wcs:CoverageId", covId);
            element("wcs:CoverageSubtype", "RectifiedGridCoverage")// TODO make this parametric

            handleWGS84BoundingBox(cv.getLatLonBoundingBox());
            handleBoundingBox(cv.boundingBox());

            end("wcs:CoverageSummary");
        }

        /**
         * Spits out the boundingbox for the current coverage taking into account the reprojection policy.
         *
         * @param boundingBox an instance of reference
         * @throws Exception in case we don't manage to retrieve the CRS EPSG code for this bbox (It should not happen!)
         */
        private void handleBoundingBox(BoundingBox boundingBox) throws Exception {
            // CRS for this bbox
            final AttributesImpl attributes = new AttributesImpl();
            attributes.addAttribute(
                    "",
                    "crs",
                    "crs",
                    "",
                    GetCoverage.SRS_STARTER+CRS.lookupIdentifier(boundingBox.getCoordinateReferenceSystem(), false));
           
            start("ows:BoundingBox",attributes);           
            // LowerCorner
            element("ows:LowerCorner", new StringBuilder(Double.toString(boundingBox.getLowerCorner()
                    .getOrdinate(0))).append(" ").append(boundingBox.getLowerCorner().getOrdinate(1))
                    .toString());
            // UpperCorner   
            element("ows:UpperCorner", new StringBuilder(Double.toString(boundingBox.getUpperCorner()
                    .getOrdinate(0))).append(" ").append(boundingBox.getUpperCorner().getOrdinate(1))
                    .toString());   
            end("ows:BoundingBox");
           
        }

        private void handleLanguages() {
//            start("ows:Languages");
//            // TODO
//            end("ows:Languages");
        }

        /**
         * Writes the element if and only if the content is not null and not
         * empty
         *
         * @param elementName
         * @param content
         */
        private void elementIfNotEmpty(String elementName, String content) {
            if ( isNotBlank(content) )
                element(elementName, content);
        }
    }

}
TOP

Related Classes of org.geoserver.wcs2_0.response.WCS20GetCapabilitiesTransformer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.