Esempio n. 1
0
class BackendComponent(Component):
    file_storages = ExtensionPoint(FileStorageInterface)
    connected_storages = ExtensionPoint(ConnectedStorageInterface)
    packages = ExtensionPoint(PackageInterface)

    @property
    def storages(self):
        """ Helper for all storages.
        """
        return itertools.chain(self.file_storages, self.connected_storages)

    def get_file_storage_component(self, storage_type):
        storage_type = storage_type.upper()
        result_component = None
        for storage_component in self.file_storages:
            if storage_component.name.upper() == storage_type:
                if result_component is not None:
                    raise Exception("Ambigouus storage component")
                result_component = storage_component

        return result_component

    def get_connected_storage_component(self, storage_type):
        storage_type = storage_type.upper()
        result_component = None
        for storage_component in self.connected_storages:
            if storage_component.name.upper() == storage_type:
                if result_component is not None:
                    raise Exception("Ambigouus storage component")
                result_component = storage_component

        return result_component

    def get_storage_component(self, storage_type):
        file_storage = self.get_file_storage_component(storage_type)
        connected_storage = self.get_connected_storage_component(storage_type)

        if file_storage is not None and connected_storage is not None:
            raise Exception("Ambigouus storage component")

        return file_storage or connected_storage

    def get_package_component(self, format):
        format = format.upper()
        result_component = None
        for package_component in self.packages:
            if package_component.name.upper() == format:
                if result_component is not None:
                    raise Exception("Ambigouus package component")
                result_component = package_component

        return result_component
Esempio n. 2
0
class PDPComponent(Component):
    pdps = ExtensionPoint(PolicyDecisionPointInterface)

    def get_pdp(self, pdp_type):
        for pdp in self.pdps:
            if pdp.pdp_type == pdp_type:
                return pdp
        return None
Esempio n. 3
0
class MetadataComponent(Component):
    metadata_readers = ExtensionPoint(MetadataReaderInterface)
    metadata_writers = ExtensionPoint(MetadataWriterInterface)

    def get_reader_by_test(self, obj):
        for reader in self.metadata_readers:
            if reader.test(obj):
                return reader
        return None

    def get_reader_by_format(self, format):
        for reader in self.metadata_readers:
            if format in reader.formats:
                return reader
        return None

    def get_writer_by_format(self, format):
        for writer in self.metadata_writers:
            if format in writer.formats:
                return writer
        return None
Esempio n. 4
0
class WCS20GetCoverageHandler(WCSGetCoverageHandlerBase, Component):
    implements(ServiceHandlerInterface)
    implements(GetServiceHandlerInterface)
    implements(PostServiceHandlerInterface)

    encoding_extensions = ExtensionPoint(EncodingExtensionInterface)

    versions = ("2.0.0", "2.0.1")

    def get_decoder(self, request):
        if request.method == "GET":
            return WCS20GetCoverageKVPDecoder(request.GET)
        elif request.method == "POST":
            return WCS20GetCoverageXMLDecoder(request.body)

    def get_params(self, coverage, decoder, request):
        subsets = Subsets(decoder.subsets, crs=decoder.subsettingcrs)
        encoding_params = None
        for encoding_extension in self.encoding_extensions:
            if encoding_extension.supports(decoder.format, {}):
                encoding_params = encoding_extension.get_encoding_params(
                    request
                )

        scalefactor = decoder.scalefactor
        scales = list(
            chain(decoder.scaleaxes, decoder.scalesize, decoder.scaleextent)
        )

        # check scales validity: ScaleFactor and any other scale
        if scalefactor and scales:
            raise InvalidRequestException(
                "ScaleFactor and any other scale operation are mutually "
                "exclusive.", locator="scalefactor"
            )

        # check scales validity: Axis uniqueness
        axes = set()
        for scale in scales:
            if scale.axis in axes:
                raise InvalidRequestException(
                    "Axis '%s' is scaled multiple times." % scale.axis,
                    locator=scale.axis
                )
            axes.add(scale.axis)

        return WCS20CoverageRenderParams(
            coverage, subsets, decoder.rangesubset, decoder.format,
            decoder.outputcrs, decoder.mediatype, decoder.interpolation,
            scalefactor, scales, encoding_params or {}, request
        )
Esempio n. 5
0
class WPS10GetCapabilitiesHandler(Component):
    """ WPS 1.0 GetCapabilities service handler. """
    implements(ServiceHandlerInterface)
    implements(GetServiceHandlerInterface)
    implements(PostServiceHandlerInterface)
    implements(VersionNegotiationInterface)

    service = "WPS"
    versions = ("1.0.0", )
    request = "GetCapabilities"

    processes = ExtensionPoint(ProcessInterface)

    def handle(self, request):
        """ Handle HTTP request. """
        encoder = WPS10CapabilitiesXMLEncoder()
        return encoder.serialize(encoder.encode_capabilities(self.processes))
Esempio n. 6
0
class WPS10DescribeProcessHandler(Component):
    """ WPS 1.0 DescribeProcess service handler. """

    implements(ServiceHandlerInterface)
    implements(GetServiceHandlerInterface)
    implements(PostServiceHandlerInterface)

    service = "WPS"
    versions = ("1.0.0",)
    request = "DescribeProcess"

    processes = ExtensionPoint(ProcessInterface)

    @staticmethod
    def get_decoder(request):
        """ Get the WPS request decoder. """
        if request.method == "GET":
            return WPS10DescribeProcessKVPDecoder(request.GET)
        else:
            return WPS10DescribeProcessXMLDecoder(request.body)


    def handle(self, request):
        """ Handle HTTP request. """
        decoder = self.get_decoder(request)
        identifiers = set(decoder.identifiers)

        used_processes = []
        for process in self.processes:
            process_identifier = (
                getattr(process, 'identifier', None) or type(process).__name__
            )
            if process_identifier in identifiers:
                identifiers.remove(process_identifier)
                used_processes.append(process)

        for identifier in identifiers:
            raise NoSuchProcessError(identifier)

        encoder = WPS10ProcessDescriptionsXMLEncoder()
        return encoder.serialize(
            encoder.encode_process_descriptions(used_processes)
        )
Esempio n. 7
0
class MapServerWMSBaseComponent(Component):
    """ Base class for various WMS render components using MapServer.
    """

    connectors = ExtensionPoint(ConnectorInterface)
    layer_factories = ExtensionPoint(LayerFactoryInterface)
    style_applicators = ExtensionPoint(StyleApplicatorInterface)

    def render(self, layer_groups, request_values, **options):
        map_ = ms.Map()
        map_.setMetaData("ows_enable_request", "*")
        map_.setProjection("EPSG:4326")
        map_.imagecolor.setRGB(0, 0, 0)

        # set supported CRSs
        decoder = CRSsConfigReader(get_eoxserver_config())
        crss_string = " ".join(
            map(lambda crs: "EPSG:%d" % crs, decoder.supported_crss_wms)
        )
        map_.setMetaData("ows_srs", crss_string)
        map_.setMetaData("wms_srs", crss_string)

        self.check_parameters(map_, request_values)

        session = self.setup_map(layer_groups, map_, options)

        with session:
            request = ms.create_request(request_values)
            raw_result = map_.dispatch(request)

            result = result_set_from_raw_data(raw_result)
            return result, get_content_type(result)

    def check_parameters(self, map_, request_values):
        for key, value in request_values:
            if key.lower() == "format":
                if not map_.getOutputFormatByName(value):
                    raise InvalidFormat(value)
                break
        else:
            raise RenderException("Missing 'format' parameter")

    @property
    def suffixes(self):
        return list(
            chain(*[factory.suffixes for factory in self.layer_factories])
        )

    def get_connector(self, data_items):
        for connector in self.connectors:
            if connector.supports(data_items):
                return connector
        return None

    def get_layer_factory(self, suffix):
        result = None
        for factory in self.layer_factories:
            if suffix in factory.suffixes:
                if result:
                    pass  # TODO
                    #raise Exception("Found")
                result = factory
                return result
        return result

    def setup_map(self, layer_selection, map_, options):
        group_layers = SortedDict()
        session = ConnectorSession(options)

        # set up group layers before any "real" layers
        for collections, _, name, suffix in tuple(layer_selection.walk()):
            if not collections:
                continue

            # get a factory for the given suffix
            factory = self.get_layer_factory(suffix)
            if not factory:
                # raise or pass?
                continue

            # get the groups name, which is the name of the collection + the
            # suffix
            group_name = collections[-1].identifier + (suffix or "")

            # generate a group layer
            group_layer = factory.generate_group(group_name)
            group_layers[group_name] = group_layer

        # set up the actual layers for each coverage
        for collections, coverage, name, suffix in layer_selection.walk():
            # get a factory for the given coverage and suffix
            factory = self.get_layer_factory(suffix)

            group_layer = None
            group_name = None

            if collections:
                group_name = collections[-1].identifier + (suffix or "")
                group_layer = group_layers.get(group_name)

            if not coverage:
                # add an empty layer to not produce errors out of bounds.
                if name:
                    tmp_layer = ms.layerObj()
                    tmp_layer.name = (name + suffix) if suffix else name
                    layers_and_data_items = ((tmp_layer, ()),)
                else:
                    layers_and_data_items = ()

            elif not factory:
                tmp_layer = ms.layerObj()
                tmp_layer.name = name
                layers_and_data_items = ((tmp_layer, ()),)
            else:
                data_items = coverage.data_items.all()
                coverage.cached_data_items = data_items
                layers_and_data_items = tuple(factory.generate(
                    coverage, group_layer, suffix, options
                ))

            for layer, data_items in layers_and_data_items:
                connector = self.get_connector(data_items)

                if group_name:
                    layer.setMetaData("wms_layer_group", "/" + group_name)

                session.add(connector, coverage, data_items, layer)

        coverage_layers = [layer for _, layer, _ in session.coverage_layers]
        for layer in chain(group_layers.values(), coverage_layers):
            old_layer = map_.getLayerByName(layer.name)
            if old_layer:
                # remove the old layer and reinsert the new one, to
                # raise the layer to the top.
                # TODO: find a more efficient way to do this
                map_.removeLayer(old_layer.index)
            map_.insertLayer(layer)

        # apply any styles
        # TODO: move this to map/legendgraphic renderer only?
        for coverage, layer, data_items in session.coverage_layers:
            for applicator in self.style_applicators:
                applicator.apply(coverage, data_items, layer)

        return session

    def get_empty_layers(self, name):
        layer = ms.layerObj()
        layer.name = name
        layer.setMetaData("wms_enable_request", "getmap")
        return (layer,)
Esempio n. 8
0
class GDALDatasetMetadataReader(Component):
    implements(MetadataReaderInterface)

    additional_readers = ExtensionPoint(GDALDatasetMetadataReaderInterface)

    def test(self, obj):
        return open_gdal(obj) is not None

    def get_format_name(self, obj):
        ds = open_gdal(obj)
        if not ds:
            return None

        driver = ds.GetDriver()
        return "GDAL/" + driver.ShortName

    def read(self, obj):
        ds = open_gdal(obj)
        if ds is None:
            raise Exception("Could not parse from obj '%s'." % repr(obj))

        driver = ds.GetDriver()
        size = (ds.RasterXSize, ds.RasterYSize)
        values = {"size": size}

        # --= rectified datasets =--
        # NOTE: If the projection is a non-zero string then
        #       the geocoding is given by the Geo-Trasnformation
        #       matrix - not matter what are the values.
        if ds.GetProjection():
            values["coverage_type"] = "RectifiedDataset"
            values["projection"] = (ds.GetProjection(), "WKT")

            # get coordinates of all four image corners
            gt = ds.GetGeoTransform()

            def gtrans(x, y):
                return gt[0] + x * gt[1] + y * gt[2], gt[
                    3] + x * gt[4] + y * gt[5]

            vpix = [(0, 0), (0, size[1]), (size[0], 0), (size[0], size[1])]
            vx, vy = zip(*(gtrans(x, y) for x, y in vpix))

            # find the extent
            values["extent"] = (min(vx), min(vy), max(vx), max(vy))

        # --= tie-point encoded referenceable datasets =--
        # NOTE: If the GCP projection is a non-zero string and
        #       there are GCPs we are dealing with a tie-point geocoded
        #       referenceable dataset. The extent is given by the image
        #       footprint. The fooprint must not be wrapped arround
        #       the date-line!
        elif ds.GetGCPProjection() and ds.GetGCPCount() > 0:
            values["coverage_type"] = "ReferenceableDataset"
            projection = ds.GetGCPProjection()
            values["projection"] = (projection, "WKT")

            # parse the spatial reference to get the EPSG code
            sr = osr.SpatialReference(projection, "WKT")

            # NOTE: GeosGeometry can't handle non-EPSG geometry projections.
            if sr.GetAuthorityName(None) == "EPSG":
                srid = int(sr.GetAuthorityCode(None))

                # get the footprint
                rt_prm = rt.suggest_transformer(ds)
                fp_wkt = rt.get_footprint_wkt(ds, **rt_prm)
                footprint = GEOSGeometry(fp_wkt, srid)

                if isinstance(footprint, Polygon):
                    footprint = MultiPolygon(footprint)
                elif not isinstance(footprint, MultiPolygon):
                    raise TypeError("Got invalid geometry %s" %
                                    type(footprint).__name__)

                values["footprint"] = footprint
                values["extent"] = footprint.extent

        # --= dataset with no geocoding =--
        # TODO: Handling of other types of GDAL geocoding (e.g, RPC).
        else:
            pass

        reader = self._find_additional_reader(ds)
        if reader:
            additional_values = reader.read_ds(ds)
            for key, value in additional_values.items():
                values.setdefault(key, value)

        driver_metadata = driver.GetMetadata()
        frmt = driver_metadata.get("DMD_MIMETYPE")
        if frmt:
            values["format"] = frmt

        return values

    def _find_additional_reader(self, ds):
        for reader in self.additional_readers:
            if reader.test_ds(ds):
                return reader
        return None
Esempio n. 9
0
class WCSGetCapabilitiesHandlerBase(object):
    """ Base for Coverage description handlers.
    """

    service = "WCS"
    request = "GetCapabilities"

    index = 0

    renderers = ExtensionPoint(WCSCapabilitiesRendererInterface)

    def get_decoder(self, request):
        """ Interface method to get the correct decoder for this request.
        """

    def lookup_coverages(self, decoder):
        """ Default implementation of the coverage lookup. Simply returns all 
            coverages in no specific order.
        """
        return models.Coverage.objects.filter(visible=True) \
            .order_by("identifier")

    def get_params(self, coverages, decoder):
        """ Default method to return a render params object from the given 
            coverages/decoder.
        """

        return WCSCapabilitiesRenderParams(
            coverages,
            getattr(decoder, "version", None),
            getattr(decoder, "sections", None),
            getattr(decoder, "acceptlanguages", None),
            getattr(decoder, "acceptformats", None),
            getattr(decoder, "updatesequence", None),
        )

    def get_renderer(self, params):
        """ Default implementation for a renderer retrieval.
        """
        for renderer in self.renderers:
            if renderer.supports(params):
                return renderer

        raise OperationNotSupportedException(
            "No Capabilities renderer found for the given parameters.",
            self.request)

    def to_http_response(self, result_set):
        """ Default result to response conversion method.
        """
        return to_http_response(result_set)

    def handle(self, request):
        """ Default handler method.
        """

        # parse the parameters
        decoder = self.get_decoder(request)

        # get the coverages
        coverages = self.lookup_coverages(decoder)

        # create the render params
        params = self.get_params(coverages, decoder)
        params.http_request = request

        # get the renderer
        renderer = self.get_renderer(params)

        # dispatch the renderer and return the response
        result_set = renderer.render(params)
        return self.to_http_response(result_set)
Esempio n. 10
0
class WCSGetCoverageHandlerBase(object):
    """ Base for get coverage handlers.
    """

    service = "WCS"
    request = "GetCoverage"

    index = 10

    renderers = ExtensionPoint(WCSCoverageRendererInterface)

    def get_decoder(self, request):
        """ Interface method to get the correct decoder for this request.
        """

    def lookup_coverage(self, decoder):
        """ Default implementation of the coverage lookup. Returns the coverage
            model for the given request decoder or raises an exception if it is 
            not found.
        """
        coverage_id = decoder.coverage_id

        try:
            coverage = models.Coverage.objects.get(identifier=coverage_id)
        except models.Coverage.DoesNotExist:
            raise NoSuchCoverageException((coverage_id, ))

        return coverage

    def get_params(self, coverages, decoder, request):
        """ Interface method to return a render params object from the given 
            coverages/decoder.
        """

    def get_renderer(self, params):
        """ Default implementation for a renderer retrieval.
        """
        for renderer in self.renderers:
            if renderer.supports(params):
                return renderer

        raise OperationNotSupportedException(
            "No renderer found for coverage '%s'." % params.coverage,
            self.request)

    def to_http_response(self, result_set):
        """ Default result to response conversion method.
        """
        return to_http_response(result_set)

    def handle(self, request):
        """ Default handling method implementation.
        """

        # parse the request
        decoder = self.get_decoder(request)

        # get the coverage model
        coverage = self.lookup_coverage(decoder)

        # create the render params
        params = self.get_params(coverage, decoder, request)

        # get the renderer
        renderer = self.get_renderer(params)

        # render the coverage and return the response
        result_set = renderer.render(params)
        return self.to_http_response(result_set)
Esempio n. 11
0
class WCSDescribeCoverageHandlerBase(object):
    """ Base for Coverage description handlers.
    """

    service = "WCS"
    request = "DescribeCoverage"

    index = 1

    renderers = ExtensionPoint(WCSCoverageDescriptionRendererInterface)

    def get_decoder(self, request):
        """ Interface method to get the correct decoder for this request.
        """

    def lookup_coverages(self, decoder):
        """ Default implementation of the coverage lookup. Returns a sorted list
            of coverage models according to the decoders `coverage_ids` 
            attribute. Raises a `NoSuchCoverageException` if any of the given
            IDs was not found in the database.
        """
        ids = decoder.coverage_ids
        coverages = sorted(
            models.Coverage.objects.filter(identifier__in=ids),
            key=(lambda coverage: ids.index(coverage.identifier)))

        # check correct number
        if len(coverages) < len(ids):
            available_ids = set(
                [coverage.identifier for coverage in coverages])
            raise NoSuchCoverageException(set(ids) - available_ids)

        return coverages

    def get_params(self, coverages, decoder):
        """ Interface method to return a render params object from the given 
            coverages/decoder.
        """

    def get_renderer(self, params):
        """ Default implementation for a renderer retrieval.
        """
        for renderer in self.renderers:
            if renderer.supports(params):
                return renderer

        raise OperationNotSupportedException(
            "No suitable coverage description renderer found.", self.request)

    def to_http_response(self, result_set):
        """ Default result to response conversion method.
        """
        return to_http_response(result_set)

    def handle(self, request):
        """ Default request handling method implementation.
        """
        # parse the parameters
        decoder = self.get_decoder(request)

        # lookup the coverages
        coverages = self.lookup_coverages(decoder)

        # create the render parameters
        params = self.get_params(coverages, decoder)

        # find the correct renderer
        renderer = self.get_renderer(params)

        # render and return the response
        result_set = renderer.render(params)
        return self.to_http_response(result_set)
Esempio n. 12
0
class RectifiedCoverageMapServerRenderer(BaseRenderer):
    """ A coverage renderer for rectified coverages. Uses mapserver to process
        the request.
    """

    implements(WCSCoverageRendererInterface)

    # ReferenceableDatasets are not handled in WCS >= 2.0
    versions_full = (Version(1, 1), Version(1, 0))
    versions_partly = (Version(2, 0), )
    versions = versions_full + versions_partly

    handles_full = (models.RectifiedDataset, models.RectifiedStitchedMosaic,
                    models.ReferenceableDataset)

    handles_partly = (models.RectifiedDataset, models.RectifiedStitchedMosaic)
    handles = handles_full + handles_partly

    connectors = ExtensionPoint(ConnectorInterface)
    layer_factories = ExtensionPoint(LayerFactoryInterface)

    def supports(self, params):
        return ((params.version in self.versions_full
                 and issubclass(params.coverage.real_type, self.handles_full))
                or (params.version in self.versions_partly and issubclass(
                    params.coverage.real_type, self.handles_partly)))

    def render(self, params):
        # get coverage related stuff
        coverage = params.coverage

        # ReferenceableDataset are not supported in WCS < 2.0
        if issubclass(coverage.real_type, models.ReferenceableDataset):
            raise NoSuchCoverageException((coverage.identifier, ))

        data_items = self.data_items_for_coverage(coverage)

        range_type = coverage.range_type
        bands = list(range_type)

        subsets = params.subsets

        if subsets:
            subsets.srid  # this automatically checks the validity

        # create and configure map object
        map_ = self.create_map()

        # configure outputformat
        native_format = self.get_native_format(coverage, data_items)
        if get_format_by_mime(native_format) is None:
            native_format = "image/tiff"

        frmt = params.format or native_format

        if frmt is None:
            raise RenderException("Format could not be determined", "format")

        mime_type, frmt = split_format(frmt)

        imagemode = ms.gdalconst_to_imagemode(bands[0].data_type)
        time_stamp = datetime.now().strftime("%Y%m%d%H%M%S")
        basename = "%s_%s" % (coverage.identifier, time_stamp)
        of = create_outputformat(mime_type, frmt, imagemode, basename,
                                 getattr(params, "encoding_params", {}))

        map_.appendOutputFormat(of)
        map_.setOutputFormat(of)

        # TODO: use layer factory here
        layer = self.layer_for_coverage(coverage, native_format,
                                        params.version)

        map_.insertLayer(layer)

        for connector in self.connectors:
            if connector.supports(data_items):
                break
        else:
            raise OperationNotSupportedException(
                "Could not find applicable layer connector.", "coverage")

        try:
            connector.connect(coverage, data_items, layer, {})
            # create request object and dispatch it against the map
            request = ms.create_request(
                self.translate_params(params, range_type))
            request.setParameter("format", mime_type)
            raw_result = ms.dispatch(map_, request)

        finally:
            # perform any required layer related cleanup
            connector.disconnect(coverage, data_items, layer, {})

        result_set = result_set_from_raw_data(raw_result)

        if params.version == Version(2, 0):
            if getattr(params, "mediatype",
                       None) in ("multipart/mixed", "multipart/related"):
                encoder = WCS20EOXMLEncoder()
                is_mosaic = issubclass(coverage.real_type,
                                       models.RectifiedStitchedMosaic)

                if not is_mosaic:
                    tree = encoder.alter_rectified_dataset(
                        coverage, getattr(params, "http_request", None),
                        etree.parse(result_set[0].data_file).getroot(),
                        subsets.bounding_polygon(coverage)
                        if subsets else None)
                else:
                    tree = encoder.alter_rectified_stitched_mosaic(
                        coverage.cast(), getattr(params, "http_request", None),
                        etree.parse(result_set[0].data_file).getroot(),
                        subsets.bounding_polygon(coverage)
                        if subsets else None)

                result_set[0] = ResultBuffer(encoder.serialize(tree),
                                             encoder.content_type)

        # "default" response
        return result_set

    def translate_params(self, params, range_type):
        """ "Translate" parameters to be understandable by mapserver.
        """
        if params.version.startswith("2.0"):
            for key, value in params:
                if key == "interpolation":
                    interpolation = INTERPOLATION_TRANS.get(value)
                    if not interpolation:
                        raise InterpolationMethodNotSupportedException(
                            "Interpolation method '%s' is not supported." %
                            value)
                    yield key, value

                else:
                    yield key, value

            rangesubset = params.rangesubset
            if rangesubset:
                yield "rangesubset", ",".join(
                    map(str, rangesubset.get_band_indices(range_type, 1)))

            # TODO: this only works in newer MapServer implementations
            # (since 6.4?).
            SCALE_AVAILABLE = ms.msGetVersionInt() > 60401
            scalefactor = params.scalefactor
            if scalefactor is not None:
                if SCALE_AVAILABLE:
                    yield "scalefactor", str(scalefactor)
                else:
                    raise RenderException(
                        "'ScaleFactor' is not supported by MapServer in the "
                        "current version.", "scalefactor")

            for scale in params.scales:
                scaleaxes = []
                if isinstance(scale, ScaleSize):
                    yield "size", "%s(%d)" % (scale.axis, scale.size)
                elif isinstance(scale, ScaleExtent):
                    yield "size", "%s(%d)" % (scale.axis,
                                              scale.high - scale.low)
                elif isinstance(scale, ScaleAxis):
                    if SCALE_AVAILABLE:
                        scaleaxes.append(scale)
                    else:
                        raise RenderException(
                            "'ScaleAxes' is not supported by MapServer in the "
                            "current version.", "scaleaxes")

                if scaleaxes:
                    yield "scaleaxes", ",".join("%s(%f)" %
                                                (scale.axis, scale.scale)
                                                for scale in scaleaxes)

            if params.outputcrs is not None:
                srid = crss.parseEPSGCode(
                    params.outputcrs,
                    (crss.fromURL, crss.fromURN, crss.fromShortCode))
                if srid is None:
                    raise InvalidOutputCrsException(
                        "Failed to extract an EPSG code from the OutputCRS URI "
                        "'%s'." % params.outputcrs)
                yield "outputcrs", params.outputcrs

        else:
            for key, value in params:
                yield key, value
Esempio n. 13
0
class OpenSearch11SearchHandler(Component):
    search_extensions = ExtensionPoint(SearchExtensionInterface)
    result_formats = ExtensionPoint(ResultFormatInterface)

    def handle(self, request, collection_id=None, format_name=None):
        if request.method == "GET":
            request_parameters = request.GET
        elif request.method == "POST":
            request_parameters = request.POST
        else:
            raise Exception("Invalid request method '%s'." % request.method)

        decoder = OpenSearch11BaseDecoder(request_parameters)

        if collection_id:
            qs = models.Collection.objects.get(
                identifier=collection_id
            ).eo_objects.all()
        else:
            qs = models.Collection.objects.all()

        if decoder.search_terms:
            # TODO: search descriptions, summary etc once available
            qs = qs.filter(identifier__icontains=decoder.search_terms)

        namespaces = NameSpaceMap()
        all_parameters = {}
        for search_extension in self.search_extensions:
            # get all search extension related parameters and translate the name
            # to the actual parameter name
            params = dict(
                (parameter["type"], request_parameters[parameter["name"]])
                for parameter in search_extension.get_schema()
                if parameter["name"] in request_parameters
            )

            qs = search_extension.filter(qs, params)
            namespaces.add(search_extension.namespace)
            all_parameters[search_extension.namespace.prefix] = params

        total_count = len(qs)

        if decoder.start_index and not decoder.count:
            qs = qs[decoder.start_index:]
        elif decoder.start_index and decoder.count:
            qs = qs[decoder.start_index:decoder.start_index+decoder.count]
        elif decoder.count:
            qs = qs[:decoder.count]
        elif decoder.count == 0:
            if collection_id:
                qs = models.Collection.objects.none()
            else:
                qs = models.EOObject.objects.none()

        try:
            result_format = next(
                result_format
                for result_format in self.result_formats
                if result_format.name == format_name
            )
        except StopIteration:
            raise Http404("No such result format '%s'." % format_name)

        default_page_size = 100  # TODO: make this configurable

        search_context = SearchContext(
            total_count, decoder.start_index,
            decoder.count or default_page_size, len(qs),
            all_parameters, namespaces
        )

        return (
            result_format.encode(request, collection_id, qs, search_context),
            result_format.mimetype
        )
Esempio n. 14
0
class WCS20GetEOCoverageSetHandler(Component):
    implements(ServiceHandlerInterface)
    implements(GetServiceHandlerInterface)
    implements(PostServiceHandlerInterface)

    coverage_renderers = ExtensionPoint(WCSCoverageRendererInterface)
    package_writers = ExtensionPoint(PackageWriterInterface)

    service = "WCS"
    versions = ("2.0.0", "2.0.1")
    methods = ['GET', 'POST']
    request = "GetEOCoverageSet"

    index = 21

    def get_decoder(self, request):
        if request.method == "GET":
            return WCS20GetEOCoverageSetKVPDecoder(request.GET)
        elif request.method == "POST":
            return WCS20GetEOCoverageSetXMLDecoder(request.body)

    def get_params(self, coverage, decoder, request):
        return WCS20CoverageRenderParams(coverage,
                                         Subsets(decoder.subsets),
                                         http_request=request)

    def get_renderer(self, params):
        for renderer in self.coverage_renderers:
            if renderer.supports(params):
                return renderer

        raise InvalidRequestException(
            "Could not find renderer for coverage '%s'.")

    def get_pacakge_writer(self, format, params):
        for writer in self.package_writers:
            if writer.supports(format, params):
                return writer

        raise InvalidRequestException("Format '%s' is not supported." % format,
                                      locator="format")

    @property
    def constraints(self):
        reader = WCSEOConfigReader(get_eoxserver_config())
        return {"CountDefault": reader.paging_count_default}

    def handle(self, request):
        decoder = self.get_decoder(request)
        eo_ids = decoder.eo_ids

        format, format_params = decoder.format
        writer = self.get_pacakge_writer(format, format_params)

        containment = decoder.containment

        count_default = self.constraints["CountDefault"]
        count = decoder.count
        if count_default is not None:
            count = min(count, count_default)

        try:
            subsets = Subsets(decoder.subsets,
                              crs="http://www.opengis.net/def/crs/EPSG/0/4326",
                              allowed_types=Trim)
        except ValueError as e:
            raise InvalidSubsettingException(str(e))

        if len(eo_ids) == 0:
            raise

        # fetch a list of all requested EOObjects
        available_ids = models.EOObject.objects.filter(
            identifier__in=eo_ids).values_list("identifier", flat=True)

        # match the requested EOIDs against the available ones. If any are
        # requested, that are not available, raise and exit.
        failed = [eo_id for eo_id in eo_ids if eo_id not in available_ids]
        if failed:
            raise NoSuchDatasetSeriesOrCoverageException(failed)

        collections_qs = subsets.filter(
            models.Collection.objects.filter(identifier__in=eo_ids),
            containment="overlaps")

        # create a set of all indirectly referenced containers by iterating
        # recursively. The containment is set to "overlaps", to also include
        # collections that might have been excluded with "contains" but would
        # have matching coverages inserted.

        def recursive_lookup(super_collection, collection_set):
            sub_collections = models.Collection.objects.filter(
                collections__in=[super_collection.pk]).exclude(
                    pk__in=map(lambda c: c.pk, collection_set))
            sub_collections = subsets.filter(sub_collections, "overlaps")

            # Add all to the set
            collection_set |= set(sub_collections)

            for sub_collection in sub_collections:
                recursive_lookup(sub_collection, collection_set)

        collection_set = set(collections_qs)
        for collection in set(collection_set):
            recursive_lookup(collection, collection_set)

        collection_pks = map(lambda c: c.pk, collection_set)

        # Get all either directly referenced coverages or coverages that are
        # within referenced containers. Full subsetting is applied here.

        coverages_qs = models.Coverage.objects.filter(
            Q(identifier__in=eo_ids) | Q(collections__in=collection_pks))
        coverages_qs = subsets.filter(coverages_qs, containment=containment)

        # save a reference before limits are applied to obtain the full number
        # of matched coverages.
        coverages_no_limit_qs = coverages_qs

        # compute how many (if any) coverages can be retrieved. This depends on
        # the "count" parameter and default setting. Also, if we already
        # exceeded the count, limit the number of dataset series aswell
        """
        if inc_dss_section:
            num_collections = len(collection_set)
        else:
            num_collections = 0

        if num_collections < count and inc_cov_section:
            coverages_qs = coverages_qs.order_by("identifier")[:count - num_collections]
        elif num_collections == count or not inc_cov_section:
            coverages_qs = []
        else:
            coverages_qs = []
            collection_set = sorted(collection_set, key=lambda c: c.identifier)[:count]
        """

        # get a number of coverages that *would* have been included, but are not
        # because of the count parameter
        # count_all_coverages = coverages_no_limit_qs.count()

        # TODO: if containment is "within" we need to check all collections
        # again
        if containment == "within":
            collection_set = filter(lambda c: subsets.matches(c),
                                    collection_set)

        coverages = []
        dataset_series = []

        # finally iterate over everything that has been retrieved and get
        # a list of dataset series and coverages to be encoded into the response
        for eo_object in chain(coverages_qs, collection_set):
            if issubclass(eo_object.real_type, models.Coverage):
                coverages.append(eo_object.cast())

        fd, pkg_filename = tempfile.mkstemp()
        tmp = os.fdopen(fd)
        tmp.close()
        package = writer.create_package(pkg_filename, format, format_params)

        for coverage in coverages:
            params = self.get_params(coverage, decoder, request)
            renderer = self.get_renderer(params)
            result_set = renderer.render(params)
            all_filenames = set()
            for result_item in result_set:
                if not result_item.filename:
                    ext = mimetypes.guess_extension(result_item.content_type)
                    filename = coverage.identifier + ext
                else:
                    filename = result_item.filename
                if filename in all_filenames:
                    continue  # TODO: create new filename
                all_filenames.add(filename)
                location = "%s/%s" % (coverage.identifier, filename)
                writer.add_to_package(package, result_item.data_file,
                                      result_item.size, location)

        mime_type = writer.get_mime_type(package, format, format_params)
        ext = writer.get_file_extension(package, format, format_params)
        writer.cleanup(package)

        response = StreamingHttpResponse(tempfile_iterator(pkg_filename),
                                         mime_type)
        response["Content-Disposition"] = 'inline; filename="ows%s"' % ext
        response["Content-Length"] = str(os.path.getsize(pkg_filename))

        return response
Esempio n. 15
0
class WPS10ExcecuteHandler(Component):
    """ WPS 1.0 Execute service handler. """
    implements(ServiceHandlerInterface)
    implements(GetServiceHandlerInterface)
    implements(PostServiceHandlerInterface)

    service = "WPS"
    versions = ("1.0.0",)
    request = "Execute"

    processes = ExtensionPoint(ProcessInterface)
    async_backends = ExtensionPoint(AsyncBackendInterface)

    @staticmethod
    def get_decoder(request):
        """ Get request decoder matching the request format. """
        if request.method == "GET":
            return WPS10ExecuteKVPDecoder(
                parse_query_string(request.META['QUERY_STRING'])
            )
        elif request.method == "POST":
            # support for multipart items
            if request.META["CONTENT_TYPE"].startswith("multipart/"):
                _, data = next(mp.iterate(request.body))
                return WPS10ExecuteXMLDecoder(data)
            return WPS10ExecuteXMLDecoder(request.body)

    def get_process(self, identifier):
        """ Get process component matched by the identifier. """
        for process in self.processes:
            process_identifier = (
                getattr(process, 'identifier', None) or type(process).__name__
            )
            if process_identifier == identifier:
                return process
        raise NoSuchProcessError(identifier)

    def get_async_backend(self):
        """ Get available asynchronous back-end matched by the service version.
        """
        version_set = set(self.versions)
        for backend in self.async_backends:
            if set(backend.supported_versions) & version_set:
                return backend

    def handle(self, request):
        # pylint: disable=redefined-variable-type, too-many-locals
        """ Request handler. """
        logger = getLogger(__name__)
        # decode request
        decoder = self.get_decoder(request)

        # parse named requests parts used in case of cid references
        extra_parts = parse_named_parts(request)

        # get the process and convert input/output definitions to a common format
        process = self.get_process(decoder.identifier)
        logger.debug("Execute process %s", decoder.identifier)
        input_defs = parse_params(process.inputs)
        output_defs = parse_params(process.outputs)

        # get the unparsed (raw) inputs and the requested response parameters
        raw_inputs = check_invalid_inputs(decoder.inputs, input_defs)
        resp_form = check_invalid_outputs(decoder.response_form, output_defs)

        # resolve the special request input parameters
        raw_inputs = resolve_request_parameters(raw_inputs, input_defs, request)

        if resp_form.raw:
            encoder = WPS10ExecuteResponseRawEncoder(resp_form)
        else:
            encoder = WPS10ExecuteResponseXMLEncoder(
                process, resp_form, raw_inputs
            )

        if not resp_form.raw and resp_form.store_response:
            # asynchronous execution
            async_backend = self.get_async_backend()
            if not async_backend:
                raise StorageNotSupported(
                    "This service instance does not support asynchronous "
                    "execution!"
                )

            if not getattr(process, 'asynchronous', False):
                raise StorageNotSupported(
                    "This process does not allow asynchronous execution!",
                )

            if not resp_form.status:
                raise InvalidParameterValue(
                    "The status update cannot be blocked for an asynchronous "
                    "execute request!", "status"
                )

            # pass the control over the processing to the asynchronous back-end
            job_id = async_backend.execute(
                process, raw_inputs, resp_form, extra_parts, request=request,
            )

            # encode the StatusAccepted response
            encoder.status_location = async_backend.get_response_url(job_id)
            response = encoder.encode_accepted()

        else:
            # synchronous execution
            if not getattr(process, 'synchronous', True):
                raise InvalidParameterValue(
                    "This process does not allow synchronous execution!",
                    "storeExecuteResponse"
                )

            if resp_form.status:
                raise InvalidParameterValue(
                    "The status update cannot be provided for a synchronous "
                    "execute request!", "status"
                )

            # prepare inputs passed to the process execution subroutine
            inputs = {}
            inputs.update(decode_output_requests(resp_form, output_defs))
            inputs.update(decode_raw_inputs(
                raw_inputs, input_defs, InMemoryURLResolver(extra_parts, logger)
            ))
            if not resp_form.raw:
                encoder.inputs = inputs

            # execute the process
            outputs = process.execute(**inputs)

            # pack the outputs
            packed_outputs = pack_outputs(outputs, resp_form, output_defs)

            # encode the StatusSucceeded response
            response = encoder.encode_response(packed_outputs)

        return encoder.serialize(response)
Esempio n. 16
0
class ServiceComponent(Component):
    service_handlers = ExtensionPoint(ServiceHandlerInterface)
    exception_handlers = ExtensionPoint(ExceptionHandlerInterface)

    get_service_handlers = ExtensionPoint(GetServiceHandlerInterface)
    post_service_handlers = ExtensionPoint(PostServiceHandlerInterface)

    version_negotiation_handlers = ExtensionPoint(VersionNegotiationInterface)

    def __init__(self, *args, **kwargs):
        super(ServiceComponent, self).__init__(*args, **kwargs)

    def query_service_handler(self, request):
        """ Tries to find the correct service handler for a given request. The
        request ``method`` can either be "POST" (in which case the request body
        is parsed as XML) or "GET" (in which case the request is parsed
        as "KVP").

        If necessary a version negotiation is conducted, following OWS
        guidelines.

        :param request: a :class:`Django HttpRequest <django.http.HttpRequest>`
                        object
        :returns: the request handler component for the given request
        :raises ServiceNotSupportedException: if the service is not supported
                                              by any component
        :raises VersionNotSupportedException: if the specified version is not
                                              supported
        :raises OperationNotSupportedException: if the specified request
                                                operation is not supported
        """

        decoder = get_decoder(request)

        if request.method == "GET":
            handlers = self.get_service_handlers
        elif request.method == "POST":
            handlers = self.post_service_handlers
        elif request.method == "OPTIONS":
            return OptionsRequestHandler()
        else:
            raise HTTPMethodNotAllowedError(
                "The %s HTTP method is not allowed!" % request.method,
                ALLOWED_HTTP_METHODS)
            #handlers = self.service_handlers

        version = decoder.version
        if version is None:
            accepted_versions = decoder.acceptversions
            handlers = filter_handlers(handlers, decoder.service,
                                       accepted_versions, decoder.request)
            return self.version_negotiation(handlers, accepted_versions)

        # check that the service is supported
        handlers = filter(
            partial(handler_supports_service, service=decoder.service),
            handlers)
        if not handlers:
            raise ServiceNotSupportedException(decoder.service)

        # check that the required version is enabled
        handlers_ = filter(lambda h: decoder.version in h.versions, handlers)
        if not handlers_:
            # old style version negotiation shall always return capabilities
            if decoder.request == "GETCAPABILITIES":
                handlers = [
                    sorted(filter(
                        lambda h: decoder.request == h.request.upper(),
                        handlers),
                           key=lambda h: max(h.versions),
                           reverse=True)[0]
                ]
            else:
                raise VersionNotSupportedException(decoder.service,
                                                   decoder.version)
        else:
            handlers = handlers_

        # check that the required operation is supported and sort by the highest
        # version supported in descending manner
        handlers = sorted(filter(
            lambda h: decoder.request == h.request.upper(), handlers),
                          key=lambda h: max(h.versions),
                          reverse=True)

        if not handlers:
            operation = decoder.request
            raise OperationNotSupportedException(
                "Operation '%s' is not supported." % operation, operation)

        # return the handler with the highest version
        logger.debug("Handling '%s' request for '%s' service version '%s'." %
                     (handlers[0].request, handlers[0].service,
                      handlers[0].versions[0]))
        return handlers[0]

    def query_service_handlers(self,
                               service=None,
                               versions=None,
                               request=None,
                               method=None):
        """ Query the service handler components, filtering optionally by
            ``service``, ``versions``, ``request`` or ``method``.
        """
        method = method.upper() if method is not None else None

        if method == "GET":
            handlers = self.get_service_handlers
        elif method == "POST":
            handlers = self.post_service_handlers
        elif method is None:
            handlers = self.service_handlers
        else:
            return []

        handlers = filter_handlers(handlers, service, versions, request)
        return sort_handlers(handlers)

    def query_exception_handler(self, request):
        try:
            decoder = get_decoder(request)
            handlers = self.exception_handlers
            handlers = sorted(filter(
                partial(handler_supports_service, service=decoder.service),
                self.exception_handlers),
                              key=lambda h: max(h.versions),
                              reverse=True)

            # try to get the correctly versioned exception handler
            if decoder.version:
                for handler in handlers:
                    if decoder.version in handler.versions:
                        return handler
            else:
                # return the exception handler with the highest version,
                # if one is available
                return handlers[0]
        except:
            # swallow any exception here, because we *really* need a handler
            # to correctly show the exception.
            pass

        # last resort fallback is a plain OWS exception handler
        return OWS20ExceptionHandler()

    def version_negotiation(self, handlers, accepted_versions=None):
        version_to_handler = {}
        for handler in handlers:
            for version in handler.versions:
                version_to_handler.setdefault(version, handler)

        available_versions = sorted(version_to_handler.keys(), reverse=True)
        if not available_versions:
            raise VersionNegotiationException()

        if not accepted_versions:
            return version_to_handler[available_versions[0]]

        combinations = itertools.product(accepted_versions, available_versions)
        for accepted_version, available_version in combinations:
            if accepted_version == available_version:
                return version_to_handler[available_version]

        raise VersionNegotiationException()
Esempio n. 17
0
class RegistratorComponent(Component):
    registrators = ExtensionPoint(RegistratorInterface)
Esempio n. 18
0
class BackendComponent(Component):
    """ This :class:`Component <eoxserver.core.component.Component>` provides
    extension points and helpers to easily retrive Package and Storage
    components by their type names.
    """

    file_storages = ExtensionPoint(FileStorageInterface)
    connected_storages = ExtensionPoint(ConnectedStorageInterface)
    packages = ExtensionPoint(PackageInterface)

    @property
    def storages(self):
        """ Helper to retrieve components for all storage interfaces.
        """
        return itertools.chain(self.file_storages, self.connected_storages)

    def get_file_storage_component(self, storage_type):
        """ Retrieve a component implementing the
        :class:`eoxserver.backends.interfaces.FileStorageInterface` with the
        desired ``storage_type``.

        :param storage_type: the desired storage type
        :returns: the desired storage component or ``None``
        """

        storage_type = storage_type.upper()
        result_component = None
        for storage_component in self.file_storages:
            if storage_component.name.upper() == storage_type:
                if result_component is not None:
                    raise Exception("Ambigouus storage component")
                result_component = storage_component

        return result_component

    def get_connected_storage_component(self, storage_type):
        """ Retrieve a component implementing the
        :class:`eoxserver.backends.interfaces.ConnectedStorageInterface` with
        the desired ``storage_type``.

        :param storage_type: the desired storage type
        :returns: the desired storage component or ``None``
        """

        storage_type = storage_type.upper()
        result_component = None
        for storage_component in self.connected_storages:
            if storage_component.name.upper() == storage_type:
                if result_component is not None:
                    raise Exception("Ambigouus storage component")
                result_component = storage_component

        return result_component

    def get_storage_component(self, storage_type):
        """ Retrieve a component implementing the
        :class:`eoxserver.backends.interfaces.FileStorageInterface` or
        :class:`eoxserver.backends.interfaces.ConnectedStorageInterface` with
        the desired ``storage_type``.

        :param storage_type: the desired storage type
        :returns: the desired storage component or ``None``
        """

        file_storage = self.get_file_storage_component(storage_type)
        connected_storage = self.get_connected_storage_component(storage_type)

        if file_storage is not None and connected_storage is not None:
            raise Exception("Ambigouus storage component")

        return file_storage or connected_storage

    def get_package_component(self, format):
        """ Retrieve a component implementing the
        :class:`eoxserver.backends.interfaces.PackageInterface` with
        the desired ``format``.

        :param format: the desired package format
        :returns: the desired package component or ``None``
        """

        format = format.upper()
        result_component = None
        for package_component in self.packages:
            if package_component.name.upper() == format:
                if result_component is not None:
                    raise Exception("Ambigouus package component")
                result_component = package_component

        return result_component
Esempio n. 19
0
class MapServerWMSCapabilitiesRenderer(Component):
    """ WMS Capabilities renderer implementation using MapServer.
    """
    implements(WMSCapabilitiesRendererInterface)

    layer_factories = ExtensionPoint(LayerFactoryInterface)

    @property
    def suffixes(self):
        return list(
            chain(*[factory.suffixes for factory in self.layer_factories]))

    def render(self, collections, coverages, request_values, request):
        conf = CapabilitiesConfigReader(get_eoxserver_config())

        suffixes = self.suffixes

        http_service_url = get_http_service_url(request)

        map_ = Map()
        map_.setMetaData(
            {
                "enable_request":
                "*",
                "onlineresource":
                http_service_url,
                "service_onlineresource":
                conf.onlineresource,
                "updateSequence":
                conf.update_sequence,
                "name":
                conf.name,
                "title":
                conf.title,
                "abstract":
                conf.abstract,
                "accessconstraints":
                conf.access_constraints,
                "addresstype":
                "postal",
                "address":
                conf.delivery_point,
                "stateorprovince":
                conf.administrative_area,
                "city":
                conf.city,
                "postcode":
                conf.postal_code,
                "country":
                conf.country,
                "contactelectronicmailaddress":
                conf.electronic_mail_address,
                "contactfacsimiletelephone":
                conf.phone_facsimile,
                "contactvoicetelephone":
                conf.phone_voice,
                "contactperson":
                conf.individual_name,
                "contactorganization":
                conf.provider_name,
                "contactposition":
                conf.position_name,
                "fees":
                conf.fees,
                "keywordlist":
                ",".join(conf.keywords),
                "srs":
                " ".join(
                    crss.getSupportedCRS_WCS(
                        format_function=crss.asShortCode)),
            },
            namespace="ows")
        map_.setProjection("EPSG:4326")
        map_.setMetaData(
            {
                "getmap_formatlist":
                ",".join([f.mimeType for f in self.get_wms_formats()]),
                "getfeatureinfo_formatlist":
                "text/html,application/vnd.ogc.gml,text/plain",
            },
            namespace="wms")

        map_extent = None

        for collection in collections:
            group_name = None

            # calculate extent and timextent for every collection
            extent = collection.extent_wgs84
            # save overall map extent
            map_extent = self.join_extents(map_extent, extent)

            eo_objects = collection.eo_objects.filter(begin_time__isnull=False,
                                                      end_time__isnull=False)
            timeextent = ",".join(
                map(
                    lambda o:
                    ("/".join(map(isoformat, o.time_extent)) + "/PT1S"),
                    eo_objects))

            if len(suffixes) > 1:
                # create group layer, if there is more than one suffix for this
                # collection
                group_name = collection.identifier + "_group"
                group_layer = Layer(group_name)
                group_layer.setMetaData(
                    {
                        "title": group_name,
                        "abstract": group_name,
                        "extent": " ".join(map(str, extent)),
                    },
                    namespace="wms")

                minx, miny, maxx, maxy = extent
                group_layer.setExtent(minx, miny, maxx, maxy)

                # add default style
                default_class = Class("default")
                default_style = Style("default")
                default_class.insertStyle(default_style)
                group_layer.insertClass(default_class)

                map_.insertLayer(group_layer)

            for suffix in suffixes:
                layer_name = collection.identifier + (suffix or "")
                layer = Layer(layer_name)
                if group_name:
                    layer.setMetaData({"layer_group": "/" + group_name},
                                      namespace="wms")

                layer.setMetaData(
                    {
                        "title": layer_name,
                        "abstract": layer_name,
                        "extent": " ".join(map(str, extent)),
                        "timeextent": timeextent,
                    },
                    namespace="wms")
                map_.insertLayer(layer)

        for coverage in coverages:
            extent = coverage.extent_wgs84
            minx, miny, maxx, maxy = extent
            # save overall map extent
            map_extent = self.join_extents(map_extent, extent)

            layer_name = coverage.identifier
            layer = Layer(layer_name)
            layer.setMetaData(
                {
                    "title": layer_name,
                    "abstract": layer_name,
                    "extent": " ".join(map(str, extent)),
                },
                namespace="wms")
            minx, miny, maxx, maxy = extent
            layer.setExtent(minx, miny, maxx, maxy)

            map_.insertLayer(layer)

        # set the map_extent to a reasonable default value
        # in case there is no coverage or collection
        if map_extent is None:
            map_extent = (0.0, 0.0, 1.0, 1.0)

        map_minx, map_miny, map_maxx, map_maxy = map_extent
        map_.setExtent(map_minx, map_miny, map_maxx, map_maxy)

        request = create_request(request_values)
        raw_result = map_.dispatch(request)
        result = result_set_from_raw_data(raw_result)
        return result, get_content_type(result)

    def get_wms_formats(self):
        return getFormatRegistry().getSupportedFormatsWMS()

    def join_extents(self, e1=None, e2=None):
        if e1 and e2:
            e1_minx, e1_miny, e1_maxx, e1_maxy = e1
            e2_minx, e2_miny, e2_maxx, e2_maxy = e2
            return (min(e1_minx, e2_minx), min(e1_miny, e2_miny),
                    max(e1_maxx, e2_maxx), max(e1_maxy, e2_maxy))
        elif e1:
            return e1
        elif e2:
            return e2
        else:
            return None