Beispiel #1
0
def handle_wcs(nocase_args):
    operation = nocase_args.get("request", "").upper()
    if not operation:
        raise WCS1Exception("No operation specified", locator="Request parameter")
    elif operation == "GETCAPABILITIES":
        return get_capabilities(nocase_args)
    elif operation == "DESCRIBECOVERAGE":
        return desc_coverages(nocase_args)
    elif operation == "GETCOVERAGE":
        return get_coverage(nocase_args)
    else:
        raise WCS1Exception("Unrecognised operation: %s" % operation, locator="Request parameter")
Beispiel #2
0
def desc_coverages(args):
    # Note: Only WCS v1.0.0 is fully supported at this stage, so no version negotiation is necessary
    # Extract layer metadata from Datacube.
    platforms = get_layers(refresh=True)

    coverages = args.get("coverage")
    products = []
    if coverages:
        coverages = coverages.split(",")
        for c in coverages:
            p = platforms.product_index.get(c)
            if p:
                products.append(p)
            else:
                raise WCS1Exception("Invalid coverage: %s" % c,
                                    WCS1Exception.COVERAGE_NOT_DEFINED,
                                    locator="Coverage parameter")
    else:
        for plat in platforms:
            for p in plat.products:
                products.append(p)

    return (render_template("wcs_desc_coverage.xml",
                            service=get_service_cfg(),
                            products=products), 200,
            resp_headers({
                "Content-Type": "application/xml",
                "Cache-Control": "max-age=10"
            }))
Beispiel #3
0
def get_capabilities(args):
    # TODO: Handle updatesequence request parameter for cache consistency.
    # Note: Only WCS v1.0.0 is fully supported at this stage, so no version negotiation is necessary
    section = args.get("section")
    if section:
        section = section.lower()
    show_service = False
    show_capability = False
    show_content_metadata = False
    if section is None or section == "/":
        show_service = True
        show_capability = True
        show_content_metadata = True
    elif section == "/wcs_capabilities/service":
        show_service = True
    elif section == "/wcs_capabilities/capability":
        show_capability = True
    elif section == "/wcs_capabilities/contentmetadata":
        show_content_metadata = True
    else:
        raise WCS1Exception("Invalid section: %s" % section,
                            WCS1Exception.INVALID_PARAMETER_VALUE,
                            locator="Section parameter")

    # Extract layer metadata from Datacube.
    platforms = get_layers(refresh=True)
    service_cfg = get_service_cfg()
    url = args.get('Host', args['url_root'])
    base_url = get_service_base_url(service_cfg.allowed_urls, url)
    return (
        render_template("wcs_capabilities.xml",
                        show_service=show_service,
                        show_capability=show_capability,
                        show_content_metadata=show_content_metadata,
                        service=service_cfg,
                        platforms=platforms,
                        base_url=base_url),
        200,
        resp_headers({
            "Content-Type": "application/xml",
            "Cache-Control": "no-cache, max-age=0"
        }))
Beispiel #4
0
def ogc_impl():
    nocase_args = lower_get_args()
    nocase_args['referer'] = request.headers.get('Referer', None)
    nocase_args['origin'] = request.headers.get('Origin', None)
    nocase_args['requestid'] = request.environ.get("FLASK_REQUEST_ID")
    service = nocase_args.get("service", "").upper()
    svc_cfg = get_service_cfg()
    try:
        if service == "WMS":
            # WMS operation Map
            if svc_cfg.wms:
                return handle_wms(nocase_args)
            else:
                raise WMSException("Invalid service",
                                   locator="Service parameter")
        elif service == "WCS":
            # WCS operation Map
            if svc_cfg.wcs:
                return handle_wcs(nocase_args)
            else:
                raise WCS1Exception("Invalid service",
                                    locator="Service parameter")
        else:
            # Should we return a WMS or WCS exception if there is no service specified?
            # Defaulting to WMS because that's what we already have.
            raise WMSException("Invalid service", locator="Service parameter")
    except OGCException as e:
        return e.exception_response()
    except Exception as e:
        tb = sys.exc_info()[2]
        if service == "WCS":
            eclass = WCS1Exception
        else:
            eclass = WMSException
        ogc_e = eclass("Unexpected server error: %s" % str(e),
                       http_response=500)
        return ogc_e.exception_response(traceback=traceback.extract_tb(tb))
def get_coverage_data(req):
    #pylint: disable=too-many-locals, protected-access
    dc = get_cube()
    datasets = []
    for t in req.times:
        # IF t was passed to the datasets method instead of the stacker
        # constructor, we could use the one stacker.
        stacker = DataStacker(req.product, req.geobox, t, bands=req.bands)
        t_datasets = stacker.datasets(dc.index)
        if not t_datasets:
            # No matching data for this date
            continue
        datasets.extend(t_datasets)
    if not datasets:
        # TODO: Return an empty coverage file with full metadata?
        extents = dc.load(dask_chunks={},
                          product=req.product.product.name,
                          geopolygon=req.geobox.extent,
                          time=stacker._time)
        svc = get_service_cfg()
        x_range = (req.minx, req.maxx)
        y_range = (req.miny, req.maxy)
        xname = svc.published_CRSs[req.request_crsid]["horizontal_coord"]
        yname = svc.published_CRSs[req.request_crsid]["vertical_coord"]
        if xname in extents:
            xvals = extents[xname]
        else:
            xvals = numpy.linspace(x_range[0], x_range[1], num=req.width)
        if yname in extents:
            yvals = extents[yname]
        else:
            yvals = numpy.linspace(y_range[0], y_range[1], num=req.height)
        if svc.published_CRSs[req.request_crsid]["vertical_coord_first"]:
            nparrays = {
                band: ((yname, xname),
                       numpy.full((len(yvals), len(xvals)),
                                  req.product.nodata_dict[band]))
                for band in req.bands
            }
        else:
            nparrays = {
                band: ((xname, yname),
                       numpy.full((len(xvals), len(yvals)),
                                  req.product.nodata_dict[band]))
                for band in req.bands
            }
        data = xarray.Dataset(nparrays, coords={
            xname: xvals,
            yname: yvals,
        }).astype("int16")
        release_cube(dc)
        return data

    if req.product.max_datasets_wcs > 0 and len(
            datasets) > req.product.max_datasets_wcs:
        raise WCS1Exception(
            "This request processes too much data to be served in a reasonable amount of time."
            "Please reduce the bounds of your request and try again."
            "(max: %d, this request requires: %d)" %
            (req.product.max_datasets_wcs, len(datasets)))

    if req.format["multi-time"] and len(req.times) > 1:
        # Group by solar day
        group_by = datacube.api.query.query_group_by(time=req.times,
                                                     group_by='solar_day')
        datasets = dc.group_datasets(datasets, group_by)

    stacker = DataStacker(req.product,
                          req.geobox,
                          req.times[0],
                          bands=req.bands)
    output = stacker.data(datasets, skip_corrections=True)
    release_cube(dc)
    return output
    def __init__(self, args):
        self.args = args
        layers = get_layers()
        svc_cfg = get_service_cfg()

        # Argument: Coverage (required)
        if "coverage" not in args:
            raise WCS1Exception("No coverage specified",
                                WCS1Exception.MISSING_PARAMETER_VALUE,
                                locator="COVERAGE parameter")
        self.product_name = args["coverage"]
        self.product = layers.product_index.get(self.product_name)
        if not self.product:
            raise WCS1Exception("Invalid coverage: %s" % self.product_name,
                                WCS1Exception.COVERAGE_NOT_DEFINED,
                                locator="COVERAGE parameter")

        # Argument: FORMAT (required)
        if "format" not in args:
            raise WCS1Exception("No FORMAT parameter supplied",
                                WCS1Exception.MISSING_PARAMETER_VALUE,
                                locator="FORMAT parameter")
        if args["format"] not in svc_cfg.wcs_formats:
            raise WCS1Exception("Unsupported format: %s" % args["format"],
                                WCS1Exception.INVALID_PARAMETER_VALUE,
                                locator="FORMAT parameter")
        self.format = svc_cfg.wcs_formats[args["format"]]

        # Argument: (request) CRS (required)
        if "crs" not in args:
            raise WCS1Exception("No request CRS specified",
                                WCS1Exception.MISSING_PARAMETER_VALUE,
                                locator="CRS parameter")
        self.request_crsid = args["crs"]
        if self.request_crsid not in svc_cfg.published_CRSs:
            raise WCS1Exception("%s is not a supported CRS" %
                                self.request_crsid,
                                WCS1Exception.INVALID_PARAMETER_VALUE,
                                locator="CRS parameter")
        self.request_crs = geometry.CRS(self.request_crsid)

        # Argument: response_crs (optional)
        if "response_crs" in args:
            self.response_crsid = args["response_crs"]
            if self.response_crsid not in svc_cfg.published_CRSs:
                raise WCS1Exception("%s is not a supported CRS" %
                                    self.request_crsid,
                                    WCS1Exception.INVALID_PARAMETER_VALUE,
                                    locator="RESPONSE_CRS parameter")
            self.response_crs = geometry.CRS(self.response_crsid)
        else:
            self.response_crsid = self.request_crsid
            self.response_crs = self.request_crs

        # Arguments: One of BBOX or TIME is required
        #if "bbox" not in args and "time" not in args:
        #    raise WCS1Exception("At least one of BBOX or TIME parameters must be supplied",
        #                        WCS1Exception.MISSING_PARAMETER_VALUE,
        #                        locator="BBOX or TIME parameter"
        #                        )

        # Argument: BBOX (technically not required if TIME supplied, but
        #       it's not clear to me what that would mean.)
        # For WCS 1.0.0 all bboxes will be specified as minx, miny, maxx, maxy
        if "bbox" not in args:
            raise WCS1Exception("No BBOX parameter supplied",
                                WCS1Exception.MISSING_PARAMETER_VALUE,
                                locator="BBOX or TIME parameter")
        try:
            self.minx, self.miny, self.maxx, self.maxy = map(
                float, args['bbox'].split(','))
        except:
            raise WCS1Exception("Invalid BBOX parameter",
                                WCS1Exception.INVALID_PARAMETER_VALUE,
                                locator="BBOX parameter")

        # Argument: TIME
        if self.product.wcs_sole_time:
            self.times = [parse(self.product.wcs_sole_time).date()]
        elif "time" not in args:
            #      CEOS treats no supplied time argument as all time.
            # I'm really not sure what the right thing to do is, but QGIS wants us to do SOMETHING
            self.times = [self.product.ranges["times"][-1]]
        else:
            # TODO: the min/max/res format option?
            # It's a bit underspeced. I'm not sure what the "res" would look like.
            times = args["time"].split(",")
            self.times = []
            if times == "now":
                pass
            else:
                for t in times:
                    try:
                        time = parse(t).date()
                        if time not in self.product.ranges["time_set"]:
                            raise WCS1Exception(
                                "Time value '%s' not a valid date for coverage %s"
                                % (t, self.product_name),
                                WCS1Exception.INVALID_PARAMETER_VALUE,
                                locator="TIME parameter")
                        self.times.append(time)
                    except ValueError:
                        raise WCS1Exception(
                            "Time value '%s' not a valid ISO-8601 date" % t,
                            WCS1Exception.INVALID_PARAMETER_VALUE,
                            locator="TIME parameter")
                self.times.sort()

            if len(times) == 0:
                raise WCS1Exception("No valid ISO-8601 dates",
                                    WCS1Exception.INVALID_PARAMETER_VALUE,
                                    locator="TIME parameter")
            elif len(times) > 1 and not self.format["multi-time"]:
                raise WCS1Exception(
                    "Cannot select more than one time slice with the %s format"
                    % self.format["name"],
                    WCS1Exception.INVALID_PARAMETER_VALUE,
                    locator="TIME and FORMAT parameters")

        # Range constraint parameter: MEASUREMENTS
        # No default is set in the DescribeCoverage, so it is required
        # But QGIS wants us to work without one, so take default from config
        if "measurements" in args:
            bands = args["measurements"]
            self.bands = []
            for b in bands.split(","):
                try:
                    self.bands.append(self.product.band_idx.band(b))
                except ProductLayerException:
                    raise WCS1Exception("Invalid measurement '%s'" % b,
                                        WCS1Exception.INVALID_PARAMETER_VALUE,
                                        locator="MEASUREMENTS parameter")
            if not bands:
                raise WCS1Exception("No measurements supplied",
                                    WCS1Exception.INVALID_PARAMETER_VALUE,
                                    locator="MEASUREMENTS parameter")
        elif "styles" in args and args["styles"]:
            # Use style bands.
            # Non-standard protocol extension.
            #
            # As we have correlated WCS and WMS service implementations,
            # we can accept a style from WMS, and return the bands used for it.
            styles = args["styles"].split(",")
            if len(styles) != 1:
                raise WCS1Exception("Multiple style parameters not supported")
            style = self.product.style_index.get(styles[0])
            if style:
                self.bands = style.needed_bands
            else:
                self.bands = self.product.wcs_default_bands
        else:
            self.bands = self.product.wcs_default_bands

        # Argument: EXCEPTIONS (optional - defaults to XML)
        if "exceptions" in args and args[
                "exceptions"] != "application/vnd.ogc.se_xml":
            raise WCS1Exception("Unsupported exception format: " %
                                args["exceptions"],
                                WCS1Exception.INVALID_PARAMETER_VALUE,
                                locator="EXCEPTIONS parameter")

        # Argument: INTERPOLATION (optional only nearest-neighbour currently supported.)
        #      If 'none' is supported in future, validation of width/height/res will need to change.
        if "interpolation" in args and args[
                "interpolation"] != "nearest neighbor":
            raise WCS1Exception("Unsupported interpolation method: " %
                                args["interpolation"],
                                WCS1Exception.INVALID_PARAMETER_VALUE,
                                locator="INTERPOLATION parameter")

        if "width" in args:
            if "height" not in args:
                raise WCS1Exception(
                    "WIDTH parameter supplied without HEIGHT parameter",
                    WCS1Exception.MISSING_PARAMETER_VALUE,
                    locator="WIDTH/HEIGHT parameters")
            if "resx" in args or "resy" in args:
                raise WCS1Exception(
                    "Specify WIDTH/HEIGHT parameters OR RESX/RESY parameters - not both",
                    WCS1Exception.MISSING_PARAMETER_VALUE,
                    locator="RESX/RESY/WIDTH/HEIGHT parameters")
            try:
                self.height = int(args["height"])
                if self.height < 1:
                    raise ValueError()
            except ValueError:
                raise WCS1Exception(
                    "HEIGHT parameter must be a positive integer",
                    WCS1Exception.INVALID_PARAMETER_VALUE,
                    locator="HEIGHT parameter")
            try:
                self.width = int(args["width"])
                if self.width < 1:
                    raise ValueError()
            except ValueError:
                raise WCS1Exception(
                    "WIDTH parameter must be a positive integer",
                    WCS1Exception.INVALID_PARAMETER_VALUE,
                    locator="WIDTH parameter")
            self.resx = (self.maxx - self.minx) / self.width
            self.resy = (self.maxy - self.miny) / self.height
        elif "resx" in args:
            if "resy" not in args:
                raise WCS1Exception(
                    "RESX parameter supplied without RESY parameter",
                    WCS1Exception.MISSING_PARAMETER_VALUE,
                    locator="RESX/RESY parameters")
            if "height" in args:
                raise WCS1Exception(
                    "Specify WIDTH/HEIGHT parameters OR RESX/RESY parameters - not both",
                    WCS1Exception.MISSING_PARAMETER_VALUE,
                    locator="RESX/RESY/WIDTH/HEIGHT parameters")
            try:
                self.resx = float(args["resx"])
                if self.resx <= 0.0:
                    raise ValueError(0)
            except ValueError:
                raise WCS1Exception("RESX parameter must be a positive number",
                                    WCS1Exception.INVALID_PARAMETER_VALUE,
                                    locator="RESX parameter")
            try:
                self.resy = float(args["resy"])
                if self.resy <= 0.0:
                    raise ValueError(0)
            except ValueError:
                raise WCS1Exception("RESY parameter must be a positive number",
                                    WCS1Exception.INVALID_PARAMETER_VALUE,
                                    locator="RESY parameter")
            self.width = (self.maxx - self.minx) / self.resx
            self.height = (self.maxy - self.miny) / self.resy
            self.width = int(self.width + 0.5)
            self.height = int(self.height + 0.5)
        elif "height" in args:
            raise WCS1Exception(
                "HEIGHT parameter supplied without WIDTH parameter",
                WCS1Exception.MISSING_PARAMETER_VALUE,
                locator="WIDTH/HEIGHT parameters")
        elif "resy" in args:
            raise WCS1Exception(
                "RESY parameter supplied without RESX parameter",
                WCS1Exception.MISSING_PARAMETER_VALUE,
                locator="RESX/RESY parameters")
        else:
            raise WCS1Exception(
                "You must specify either the WIDTH/HEIGHT parameters or RESX/RESY",
                WCS1Exception.MISSING_PARAMETER_VALUE,
                locator="RESX/RESY/WIDTH/HEIGHT parameters")

        self.extent = geometry.polygon([(self.minx, self.miny),
                                        (self.minx, self.maxy),
                                        (self.maxx, self.maxy),
                                        (self.maxx, self.miny),
                                        (self.minx, self.miny)],
                                       self.request_crs)

        xscale = (self.maxx - self.minx) / self.width
        yscale = (self.miny - self.maxy) / self.height
        trans_aff = Affine.translation(self.minx, self.maxy)
        scale_aff = Affine.scale(xscale, yscale)
        self.affine = trans_aff * scale_aff
        self.geobox = geometry.GeoBox(self.width, self.height, self.affine,
                                      self.request_crs)