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" }))
def update_all_ranges(dc): i = 0 u = 0 p = 0 si = 0 su = 0 sp = 0 mi = 0 mu = 0 mp = 0 multiproducts = set() for layer in get_layers(): for prod in layer.products: if prod.multi_product: multiproducts.add(prod) else: stats = update_single_range(dc, prod) p += stats[0] u += stats[1] i += stats[2] sp += stats[3] su += stats[4] si += stats[5] for mprod in multiproducts: stats = update_multi_range(dc, mprod, follow_dependencies=False) mp += stats[0] mu += stats[1] mi += stats[2] return p, u, i, sp, su, si, mp, mu, mi
def get_capabilities(args): # TODO: Handle updatesequence request parameter for cache consistency. # Note: Only WMS v1.0.0 exists at this stage, so no version negotiation is necessary # 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) section = args.get("section") if section: section = section.lower() show_service_id = False show_service_provider = False show_ops_metadata = False show_contents = False show_themes = False if section is None: show_service_id = True show_service_provider = True show_ops_metadata = True show_contents = True show_themes = True else: sections = section.split(",") for s in sections: if s == "all": show_service_id = True show_service_provider = True show_ops_metadata = True show_contents = True show_themes = True elif s == "serviceidentification": show_service_id = True elif s == "serviceprovider": show_service_provider = True elif s == "operationsmetadata": show_ops_metadata = True elif s == "contents": show_contents = True elif s == "themes": show_themes = True else: raise WMTSException("Invalid section: %s" % section, WMTSException.INVALID_PARAMETER_VALUE, locator="Section parameter") return (render_template("wmts_capabilities.xml", service=service_cfg, platforms=platforms, base_url=base_url, show_service_id=show_service_id, show_service_provider=show_service_provider, show_ops_metadata=show_ops_metadata, show_contents=show_contents, show_themes=show_themes, webmerc_ss=WebMercScaleSet), 200, resp_headers({ "Content-Type": "application/xml", "Cache-Control": "no-cache,max-age=0" }))
def test_get_layers(): layers = get_layers() assert layers.platforms for p in layers: assert p.styles assert p.products for prd in p.products: assert layers.product_index[prd.name] == prd assert prd.title
def get_capabilities(args): if args.get("service") != "WMS": raise WMSException("Invalid service", locator="Service parameter") # TODO: Handle updatesequence request parameter for cache consistency. # Note: Only WMS v1.3.0 is fully supported at this stage, so no version negotiation is necessary # Extract layer metadata from Datacube. platforms = get_layers() return render_template("capabilities.xml", service=service_cfg, platforms=platforms), 200, resp_headers( {"Content-Type": "application/xml"})
def legend(layer, style): platforms = get_layers() product = platforms.product_index.get(layer) if not product: return ("Unknown Layer", 404, resp_headers({"Content-Type": "text/plain"})) img = create_legend_for_style(product, style) if not img: return ("Unknown Style", 404, resp_headers({"Content-Type": "text/plain"})) return img
def get_product_from_arg(args, argname="layers"): layers = args.get(argname, "").split(",") if len(layers) != 1: raise WMSException("Multi-layer requests not supported") layer = layers[0] platforms = get_layers() product = platforms.product_index.get(layer) if not product: raise WMSException("Layer %s is not defined" % layer, WMSException.LAYER_NOT_DEFINED, locator="Layer parameter") return product
def update_range(dc, product, multi=False, follow_dependencies=True): if multi: product = get_layers().product_index.get(product) else: product = dc.index.products.get_by_name(product) if product is None: raise Exception("Requested product not found.") if multi: return update_multi_range(dc, product, follow_dependencies=follow_dependencies) else: return update_single_range(dc, product)
def get_capabilities(args): # TODO: Handle updatesequence request parameter for cache consistency. # Note: Only WMS v1.3.0 is fully supported at this stage, so no version negotiation is necessary # Extract layer metadata from Datacube. platforms = get_layers(refresh=True) return render_template("wms_capabilities.xml", service=get_service_cfg(), platforms=platforms), 200, resp_headers({ "Content-Type": "application/xml", "Cache-Control": "no-cache,max-age=0" })
def add_all(dc): multi_products = set() for layer in get_layers(): for product_cfg in layer.products: product_name = product_cfg.product_name if product_cfg.multi_product: multi_products.add(product_cfg) else: print("Adding range for:", product_name) add_product_range(dc, product_name) for p in multi_products: print("Adding multiproduct range for:", p.name) add_multiproduct_range(dc, p, follow_dependencies=False)
def add_multiproduct_range(dc, product, follow_dependencies=True): if isinstance(product, str): product = get_layers().product_index.get(product) assert product is not None if follow_dependencies: for product_name in product.product_names: dc_prod = dc.index.products.get_by_name(product_name) add_product_range(dc, dc_prod) if not check_datasets_exist(dc, dc_prod): print("Could not find any datasets for: ", product_name) # Actually merge and store! create_multiprod_range_entry(dc, product, get_crses())
def get_capabilities(args): # TODO: Handle updatesequence request parameter for cache consistency. # Note: Only WMS v1.3.0 is fully supported at this stage, so no version negotiation is necessary # 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("wms_capabilities.xml", service=service_cfg, platforms=platforms, base_url=base_url), 200, resp_headers({ "Content-Type": "application/xml", "Cache-Control": "max-age=10" }))
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" }))
def get_coverage(args): decoder = WCS20GetCoverageKVPDecoder(args) try: coverage_id = decoder.coverage_id except KeyError: raise WCS2Exception("Missing coverageid parameter", locator="coverageid") svc_cfg = get_service_cfg(refresh=True) layers = get_layers(refresh=True) product = layers.product_index.get(coverage_id) if not product: raise WCS2Exception("Invalid coverage: %s" % coverage_id, WCS2Exception.COVERAGE_NOT_DEFINED, locator="COVERAGE parameter") if decoder.format: if decoder.format not in svc_cfg.wcs_formats: raise WCS2Exception("Unsupported format: %s" % decoder.format, WCS2Exception.INVALID_PARAMETER_VALUE, locator="FORMAT parameter") elif not svc_cfg.native_wcs_format: raise WCS2Exception("Missing parameter format 'format'", WCS2Exception.MISSING_PARAMETER_VALUE, locator="FORMAT parameter") fmt_cfg = svc_cfg.wcs_formats[decoder.format or svc_cfg.native_wcs_format] return ( '', 200, resp_headers({ "Content-Type": "application/xml", "Cache-Control": "no-cache, max-age=0" }) )
def desc_coverages(args): try: coverage_ids = [s.strip() for s in args['coverageid'].split(',')] except KeyError: raise WCS2Exception("Missing coverageid parameter", locator="coverageid") svc_cfg = get_service_cfg(refresh=True) layers = get_layers(refresh=True) products = [] for coverage_id in coverage_ids: product = layers.product_index.get(coverage_id) if product: products.append(product) else: raise WCS2Exception("Invalid coverage: %s" % coverage_id, WCS2Exception.COVERAGE_NOT_DEFINED, locator="Coverage parameter") # TODO: make a coverge object from each of the 'products' coverages = [ get_coverage_object(svc_cfg, product) for product in products ] encoder = WCS21XMLEncoder() return ( encoder.serialize( encoder.encode_coverage_descriptions(coverages) ), 200, resp_headers({ "Content-Type": "application/xml", "Cache-Control": "no-cache, max-age=0" }) )
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)