def __init__(self, cfg, parent_layer=None, **kwargs):
        super().__init__(cfg, **kwargs)
        self.global_cfg = kwargs["global_cfg"]
        self.parent_layer = parent_layer

        if "title" not in cfg:
            raise ConfigException(
                "Layer without title found under parent layer %s" %
                str(parent_layer))
        self.title = cfg["title"]
        if "abstract" in cfg:
            self.abstract = cfg["abstract"]
        elif parent_layer:
            self.abstract = parent_layer.abstract
        else:
            raise ConfigException(
                "No abstract supplied for top-level layer %s" % self.title)
        # Accumulate keywords
        self.keywords = set()
        if self.parent_layer:
            for word in self.parent_layer.keywords:
                self.keywords.add(word)
        else:
            for word in self.global_cfg.keywords:
                self.keywords.add(word)
        for word in cfg.get("keywords", []):
            self.keywords.add(word)
        # Inherit or override attribution
        if "attribution" in cfg:
            self.attribution = AttributionCfg.parse(cfg.get("attribution"))
        elif parent_layer:
            self.attribution = self.parent_layer.attribution
        else:
            self.attribution = self.global_cfg.attribution
 def parse_flags(self, cfg):
     if cfg:
         self.parse_pq_names(cfg)
         self.pq_band = cfg["band"]
         if "fuse_func" in cfg:
             self.pq_fuse_func = FunctionWrapper(self, cfg["fuse_func"])
         else:
             self.pq_fuse_func = None
         self.pq_ignore_time = cfg.get("ignore_time", False)
         self.ignore_info_flags = cfg.get("ignore_info_flags", [])
         self.pq_manual_merge = cfg.get("manual_merge", False)
     else:
         self.pq_names = []
         self.pq_low_res_names = []
         self.pq_name = None
         self.pq_band = None
         self.pq_ignore_time = False
         self.ignore_info_flags = []
         self.pq_manual_merge = False
     self.declare_unready("pq_products")
     self.declare_unready("pq_product")
     self.declare_unready("flags_def")
     self.declare_unready("info_mask")
     self.pq_products = []
     self.pq_low_res_products = []
    def parse_wcs(self, cfg):
        if cfg is None:
            self.wcs = False
            return
        else:
            self.wcs = True
        # Native CRS
        self.cfg_native_crs = cfg.get("native_crs")
        self.declare_unready("native_CRS")
        self.declare_unready("native_CRS_def")

        # Rectified Grids
        self.declare_unready("origin_x")
        self.declare_unready("origin_y")
        self.cfg_native_resolution = cfg.get("native_resolution")
        self.declare_unready("resolution_x")
        self.declare_unready("resolution_y")
        self.declare_unready("grid_high_x")
        self.declare_unready("grid_high_y")
        self.declare_unready("grids")
        # Band management
        self.wcs_raw_default_bands = cfg["default_bands"]
        self.declare_unready("wcs_default_bands")

        # Native format
        if "native_format" in cfg:
            self.native_format = cfg["native_format"]
            if self.native_format not in self.global_cfg.wcs_formats_by_name:
                raise ConfigException(
                    f"WCS native format {self.native_format} for layer {self.name} is not in supported formats list"
                )
        else:
            self.native_format = self.global_cfg.native_wcs_format
Esempio n. 4
0
    def __init__(self, cfg, global_cfg, dc, parent_layer=None):
        self.global_cfg = global_cfg
        self.parent_layer = parent_layer

        if "title" not in cfg:
            raise ConfigException("Layer without title found under parent layer %s" % str(parent_layer))
        self.title = cfg["title"]
        try:
            if "abstract" in cfg:
                self.abstract = cfg["abstract"]
            elif parent_layer:
                self.abstract = parent_layer.abstract
            else:
                raise ConfigException("No abstract supplied for top-level layer %s" % self.title)
            # Accumulate keywords
            self.keywords = set()
            if self.parent_layer:
                for word in self.parent_layer.keywords:
                    self.keywords.add(word)
            else:
                for word in self.global_cfg.keywords:
                    self.keywords.add(word)
            for word in cfg.get("keywords", []):
                self.keywords.add(word)
            # Inherit or override attribution
            if "attribution" in cfg:
                self.attribution = AttributionCfg.parse(cfg.get("attribution"))
            elif parent_layer:
                self.attribution = self.parent_layer.attribution
            else:
                self.attribution = self.global_cfg.attribution

        except KeyError:
            raise ConfigException("Required entry missing in layer %s" % self.title)
        super().__init__({})
def parse_ows_layer(cfg, global_cfg, parent_layer=None):
    if cfg.get("name", None):
        if cfg.get("multi_product", False):
            return OWSMultiProductLayer(cfg, global_cfg, parent_layer)
        else:
            return OWSProductLayer(cfg, global_cfg, parent_layer)
    else:
        return OWSFolder(cfg, global_cfg, parent_layer)
Esempio n. 6
0
 def parse_resource_limits(self, cfg):
     wms_cfg = cfg.get("wms", {})
     wcs_cfg = cfg.get("wcs", {})
     self.zoom_fill = wms_cfg.get("zoomed_out_fill_colour",
                                  [150, 180, 200, 160])
     self.min_zoom = wms_cfg.get("min_zoom_factor", 300.0)
     self.max_datasets_wms = wms_cfg.get("max_datasets", 0)
     self.max_datasets_wcs = wcs_cfg.get("max_datasets", 0)
 def parse_feature_info(self, cfg):
     self.feature_info_include_utc_dates = cfg.get("include_utc_dates",
                                                   False)
     custom = cfg.get("include_custom", {})
     self.feature_info_custom_includes = {
         k: FunctionWrapper(self, v)
         for k, v in custom.items()
     }
 def parse_resource_limits(self, cfg):
     wms_cfg = cfg.get("wms", {})
     wcs_cfg = cfg.get("wcs", {})
     self.zoom_fill = wms_cfg.get("zoomed_out_fill_colour",
                                  [150, 180, 200, 160])
     self.min_zoom = wms_cfg.get("min_zoom_factor", 300.0)
     self.max_datasets_wms = wms_cfg.get("max_datasets", 0)
     self.max_datasets_wcs = wcs_cfg.get("max_datasets", 0)
     self.wms_cache_rules = CacheControlRules(
         wms_cfg.get("dataset_cache_rules"), self, self.max_datasets_wms)
     self.wcs_cache_rules = CacheControlRules(
         wcs_cfg.get("dataset_cache_rules"), self, self.max_datasets_wcs)
Esempio n. 9
0
 def parse_image_processing(self, cfg):
     emf_cfg = cfg["extent_mask_func"]
     if isinstance(emf_cfg, Mapping) or isinstance(emf_cfg, str):
         self.extent_mask_func = [ FunctionWrapper(self, emf_cfg) ]
     else:
         self.extent_mask_func = list([ FunctionWrapper(self, emf) for emf in emf_cfg ])
     raw_afb = cfg.get("always_fetch_bands", [])
     self.always_fetch_bands = list([ self.band_idx.band(b) for b in raw_afb ])
     self.solar_correction = cfg.get("apply_solar_corrections", False)
     self.data_manual_merge = cfg.get("manual_merge", False)
     if cfg.get("fuse_func"):
         self.fuse_func = FunctionWrapper(self, cfg["fuse_func"])
     else:
         self.fuse_func = None
Esempio n. 10
0
 def __init__(self, cfg):
     self.title = cfg.get("title")
     self.url = cfg.get("url")
     logo = cfg.get("logo")
     if not logo:
         self.logo_width = None
         self.logo_height = None
         self.logo_url = None
         self.logo_fmt = None
     else:
         self.logo_width = logo.get("width")
         self.logo_height = logo.get("height")
         self.logo_url = logo.get("url")
         self.logo_fmt = logo.get("format")
Esempio n. 11
0
    def __init__(self, refresh=False):
        if not self.initialised or refresh:
            self.initialised = True
            cfg = read_config()
            try:
                self.parse_global(cfg["global"])
            except KeyError as e:
                raise ConfigException(
                    "Missing required config entry in 'global' section: %s" %
                    str(e))

            if self.wms:
                self.parse_wms(cfg.get("wms", {}))
            else:
                self.parse_wms({})

            if self.wcs:
                try:
                    self.parse_wcs(cfg["wcs"])
                except KeyError as e:
                    raise ConfigException(
                        "Missing required config entry in 'wcs' section (with WCS enabled): %s"
                        % str(e))
            else:
                self.parse_wcs(None)
            try:
                self.parse_layers(cfg["layers"])
            except KeyError as e:
                raise ConfigException(
                    "Missing required config entry in 'layers' section")
        super().__init__({})
    def parse_pq_names(self, cfg):
        if "dataset" in cfg:
            raise ConfigException(
                f"The 'dataset' entry in the flags section is no longer supported.  Please refer to the documentation for the correct format (layer {self.name})"
            )
        if "product" in cfg:
            pq_names = (cfg["product"], )
        else:
            pq_names = (self.product_name, )

        if "low_res_product" in cfg:
            pq_low_res_names = (cfg.get("low_res_product"), )
        else:
            pq_low_res_names = self.low_res_product_names
        if "products" in cfg:
            raise ConfigException(
                f"'products' entry in flags section of non-multi-product layer {self.name} - use 'product' only"
            )
        if "low_res_products" in cfg:
            raise ConfigException(
                f"'low_res_products' entry in flags section of non-multi-product layer {self.name}- use 'low_res_product' only"
            )
        return {
            "pq_names": pq_names,
            "pq_low_res_names": pq_low_res_names,
        }
Esempio n. 13
0
    def parse_wcs(self, cfg):
        if self.wcs:
            if not isinstance(cfg, Mapping):
                raise ConfigException("WCS section missing (and WCS is enabled)")
            self.default_geographic_CRS = cfg.get("default_geographic_CRS")
            if self.default_geographic_CRS not in self.published_CRSs:
                raise ConfigException("Configured default geographic CRS not listed in published CRSs.")
            if not self.published_CRSs[self.default_geographic_CRS]["geographic"]:
                raise ConfigException("Configured default geographic CRS not listed in published CRSs as geographic.")
            self.default_geographic_CRS_def = self.published_CRSs[self.default_geographic_CRS]
            self.wcs_formats = {}
            for fmt_name, fmt in cfg["formats"].items():
                self.wcs_formats[fmt_name] = {
                    "mime": fmt["mime"],
                    "extension": fmt["extension"],
                    "multi-time": fmt["multi-time"],
                    "name": fmt_name,
                }
                self.wcs_formats[fmt_name]["renderer"] = get_function(fmt["renderer"])
            if not self.wcs_formats:
                raise ConfigException("Must configure at least one wcs format to support WCS.")

            self.native_wcs_format = cfg["native_format"]
            if self.native_wcs_format not in self.wcs_formats:
                raise Exception("Configured native WCS format not a supported format.")
        else:
            self.default_geographic_CRS = None
            self.default_geographic_CRS_def = None
            self.wcs_formats = {}
            self.native_wcs_format = None
    def parse_pq_names(self, cfg):
        # pylint: disable=attribute-defined-outside-init
        if "dataset" in cfg:
            self.pq_name = cfg["dataset"]
            print("CFG WARNING:", "The preferred name for the 'dataset' entry",
                  "in the flags section is now 'product'.",
                  "Please update the configuration for layer", self.name)
        elif "product" in cfg:
            self.pq_name = cfg["product"]
        else:
            self.pq_name = self.product_name
        self.pq_names = [self.pq_name]

        if "low_res_product" in cfg:
            self.pq_low_res_name = cfg.get("low_res_product")
            self.pq_low_res_names = [self.pq_low_res_name]
        else:
            self.pq_low_res_names = self.low_res_product_names
        if "products" in cfg:
            raise ConfigException(
                f"'products' entry in flags section of non-multi-product layer {self.name} - use 'product' only"
            )
        if "low_res_products" in cfg:
            raise ConfigException(
                f"'low_res_products' entry in flags section of non-multi-product layer {self.name}- use 'low_res_product' only"
            )
Esempio n. 15
0
    def parse_flags(self, cfg, dc):
        if cfg:
            self.parse_pq_names(cfg)
            self.pq_band = cfg["band"]
            if "fuse_func" in cfg:
                self.pq_fuse_func = FunctionWrapper(self, cfg["fuse_func"])
            else:
                self.pq_fuse_func = None
            self.pq_ignore_time = cfg.get("ignore_time", False)
            self.ignore_info_flags = cfg.get("ignore_info_flags", [])
            self.pq_manual_merge = cfg.get("manual_merge", False)
        else:
            self.pq_names = []
            self.pq_name = None
            self.pq_band = None
            self.pq_ignore_time = False
            self.ignore_info_flags = []
            self.pq_manual_merge = False
        self.pq_products = []

        if self.pq_names:
            for pqn in self.pq_names:
                if pqn is not None:
                    pq_product = dc.index.products.get_by_name(pqn)
                    if pq_product is None:
                        raise ConfigException(
                            "Could not find pq_product %s for %s in database" %
                            (pqn, self.name))
                    self.pq_products.append(pq_product)

        self.info_mask = ~0
        if self.pq_products:
            self.pq_product = self.pq_products[0]
            meas = self.pq_product.lookup_measurements([self.pq_band])
            self.flags_def = meas[self.pq_band]["flags_definition"]
            for bitname in self.ignore_info_flags:
                bit = self.flags_def[bitname]["bits"]
                if not isinstance(bit, int):
                    continue
                flag = 1 << bit
                self.info_mask &= ~flag
        else:
            self.pq_product = None
Esempio n. 16
0
 def parse_global(self, cfg):
     self._response_headers = cfg.get("response_headers", {})
     self.wms = cfg.get("services", {}).get("wms", True)
     self.wmts = cfg.get("services", {}).get("wmts", True)
     self.wcs = cfg.get("services", {}).get("wcs", False)
     if not self.wms and not self.wmts and not self.wcs:
         raise ConfigException("At least one service must be active.")
     self.title = cfg["title"]
     self.allowed_urls = cfg["allowed_urls"]
     self.info_url = cfg["info_url"]
     self.abstract = cfg.get("abstract")
     self.contact_info = cfg.get("contact_info", {})
     self.keywords = cfg.get("keywords", [])
     self.fees = cfg.get("fees")
     self.access_constraints = cfg.get("access_constraints")
     # self.use_extent_views = cfg.get("use_extent_views", False)
     if not self.fees:
         self.fees = "none"
     if not self.access_constraints:
         self.access_constraints = "none"
     self.published_CRSs = {}
     for crs_str, crsdef in cfg["published_CRSs"].items():
         if crs_str.startswith("EPSG:"):
             gml_name = "http://www.opengis.net/def/crs/EPSG/0/" + crs_str[
                 5:]
         else:
             gml_name = crs_str
         self.published_CRSs[crs_str] = {
             "geographic": crsdef["geographic"],
             "horizontal_coord": crsdef.get("horizontal_coord",
                                            "longitude"),
             "vertical_coord": crsdef.get("vertical_coord", "latitude"),
             "vertical_coord_first": crsdef.get("vertical_coord_first",
                                                False),
             "gml_name": gml_name
         }
         if self.published_CRSs[crs_str]["geographic"]:
             if self.published_CRSs[crs_str][
                     "horizontal_coord"] != "longitude":
                 raise Exception(
                     "Published CRS {} is geographic"
                     "but has a horizontal coordinate that is not 'longitude'"
                     .format(crs_str))
             if self.published_CRSs[crs_str]["vertical_coord"] != "latitude":
                 raise Exception(
                     "Published CRS {} is geographic"
                     "but has a vertical coordinate that is not 'latitude'".
                     format(crs_str))
 def parse_wms(self, cfg):
     if not self.wms and not self.wmts:
         cfg = {}
     self.s3_bucket = cfg.get("s3_bucket", "")
     self.s3_url = cfg.get("s3_url", "")
     self.s3_aws_zone = cfg.get("s3_aws_zone", "")
     self.wms_max_width = cfg.get("max_width", 256)
     self.wms_max_height = cfg.get("max_height", 256)
     self.attribution = AttributionCfg.parse(cfg.get("attribution"))
     self.authorities = cfg.get("authorities", {})
    def parse_image_processing(self, cfg):
        emf_cfg = cfg["extent_mask_func"]
        if isinstance(emf_cfg, Mapping) or isinstance(emf_cfg, str):
            self.extent_mask_func = [FunctionWrapper(self, emf_cfg)]
        else:
            self.extent_mask_func = list(
                [FunctionWrapper(self, emf) for emf in emf_cfg])
        self.raw_afb = cfg.get("always_fetch_bands", [])
        self.declare_unready("always_fetch_bands")
        self.solar_correction = cfg.get("apply_solar_corrections", False)
        self.data_manual_merge = cfg.get("manual_merge", False)
        if self.solar_correction and not self.data_manual_merge:
            raise ConfigException("Solar correction requires manual_merge.")
        if self.data_manual_merge and not self.solar_correction:
            _LOG.warning(
                "Manual merge is only recommended where solar correction is required."
            )

        if cfg.get("fuse_func"):
            self.fuse_func = FunctionWrapper(self, cfg["fuse_func"])
        else:
            self.fuse_func = None
 def __init__(self, cfg):
     super().__init__(cfg)
     self.title = cfg.get("title")
     self.url = cfg.get("url")
     logo = cfg.get("logo")
     if not self.title and not self.url and not logo:
         raise ConfigException(
             "At least one of title, url and logo is required in an attribution definition"
         )
     if not logo:
         self.logo_width = None
         self.logo_height = None
         self.logo_url = None
         self.logo_fmt = None
     else:
         self.logo_width = logo.get("width")
         self.logo_height = logo.get("height")
         self.logo_url = logo.get("url")
         self.logo_fmt = logo.get("format")
         if not self.logo_url or not self.logo_fmt:
             raise ConfigException(
                 "url and format must both be specified in an attribution logo."
             )
 def parse_product_names(self, cfg):
     self.product_names = cfg["product_names"]
     self.product_name = self.product_names[0]
     self.low_res_product_names = cfg.get("low_res_product_names", [])
     if self.low_res_product_names:
         self.low_res_product_name = self.low_res_product_names[0]
     else:
         self.low_res_product_name = None
     if "product_name" in cfg:
         raise ConfigException(
             f"'product_name' entry in multi-product layer {self.name} - use 'product_names' only"
         )
     if "low_res_product_name" in cfg:
         raise ConfigException(
             f"'low_res_product_name' entry in multi-product layer {self.name} - use 'low_res_product_names' only"
         )
    def parse_product_names(self, cfg):
        self.product_name = cfg["product_name"]
        self.product_names = (self.product_name, )

        self.low_res_product_name = cfg.get("low_res_product_name")
        if self.low_res_product_name:
            self.low_res_product_names = (self.low_res_product_name, )
        else:
            self.low_res_product_names = tuple()
        if "product_names" in cfg:
            raise ConfigException(
                f"'product_names' entry in non-multi-product layer {self.name} - use 'product_name' only"
            )
        if "low_res_product_names" in cfg:
            raise ConfigException(
                f"'low_res_product_names' entry in non-multi-product layer {self.name} - use 'low_res_product_name' only"
            )
Esempio n. 22
0
    def parse_wcs(self, cfg, dc):
        if cfg is None:
            self.wcs = False
            return
        else:
            self.wcs = True
        # Native CRS
        try:
            self.native_CRS = self.product.definition["storage"]["crs"]
        except KeyError:
            self.native_CRS = None
        if not self.native_CRS:
            self.native_CRS = cfg.get("native_crs")
        if not self.native_CRS:
            raise ConfigException("No native CRS could be found for layer %s" % self.name)
        if self.native_CRS not in self.global_cfg.published_CRSs:
            raise ConfigException("Native CRS for product %s (%s) not in published CRSs" % (
                            self.product_name,
                            self.native_CRS))
        self.native_CRS_def = self.global_cfg.published_CRSs[self.native_CRS]
        # Prepare Rectified Grid
        native_bounding_box = self.bboxes[self.native_CRS]
        self.origin_x = native_bounding_box["left"]
        self.origin_y = native_bounding_box["bottom"]
        try:
            self.resolution_x, self.resolution_y = cfg["native_resolution"]
        except KeyError:
            raise ConfigException("No native resolution supplied for WCS enabled layer %s" % self.name)
        except ValueError:
            raise ConfigException("Invalid native resolution supplied for WCS enabled layer %s" % self.name)
        except TypeError:
            raise ConfigException("Invalid native resolution supplied for WCS enabled layer %s" % self.name)
        self.grid_high_x = int((native_bounding_box["right"] - native_bounding_box["left"]) / self.resolution_x)
        self.grid_high_y = int((native_bounding_box["top"] - native_bounding_box["bottom"]) / self.resolution_y)

        # Band management
        self.wcs_default_bands = [self.band_idx.band(b) for b in cfg["default_bands"]]
        # Cache some metadata from the datacube
        bands = dc.list_measurements().loc[self.product_name]
        self.bands = bands.index.values
        self.nodata_values = bands['nodata'].values
        self.nodata_dict = {a: b for a, b in zip(self.bands, self.nodata_values)}
Esempio n. 23
0
    def __init__(self, cfg, global_cfg, dc, parent_layer=None):
        super().__init__(cfg, global_cfg, dc, parent_layer)
        self.name = cfg["name"]
        self.hide = False
        try:
            self.parse_product_names(cfg)
            self.products = []
            for prod_name in self.product_names:
                if "__" in prod_name:
                    raise ConfigException(
                        "Product names cannot contain a double underscore '__'."
                    )
                product = dc.index.products.get_by_name(prod_name)
                if not product:
                    raise ConfigException(
                        "Could not find product %s in datacube" % prod_name)
                self.products.append(product)
            self.product = self.products[0]
            self.definition = self.product.definition

            self.time_resolution = cfg.get("time_resolution", TIMERES_RAW)
            if self.time_resolution not in TIMERES_VALS:
                raise ConfigException(
                    "Invalid time resolution value %s in named layer %s" %
                    (self.time_resolution, self.name))
        except KeyError:
            raise ConfigException(
                "Required product names entry missing in named layer %s" %
                self.name)
        self.dynamic = cfg.get("dynamic", False)
        self.force_range_update(dc)
        # TODO: sub-ranges
        self.band_idx = BandIndex(self.product, cfg.get("bands"), dc)
        try:
            self.parse_resource_limits(cfg.get("resource_limits", {}))
        except KeyError:
            raise ConfigException(
                "Missing required config items in resource limits for layer %s"
                % self.name)
        try:
            self.parse_flags(cfg.get("flags", {}), dc)
        except KeyError:
            raise ConfigException(
                "Missing required config items in flags section for layer %s" %
                self.name)
        try:
            self.parse_image_processing(cfg["image_processing"])
        except KeyError:
            raise ConfigException(
                "Missing required config items in image processing section for layer %s"
                % self.name)
        self.identifiers = cfg.get("identifiers", {})
        for auth in self.identifiers.keys():
            if auth not in self.global_cfg.authorities:
                raise ConfigException(
                    "Identifier with non-declared authority: %s" % repr(auth))
        try:
            self.parse_urls(cfg.get("urls", {}))
        except KeyError:
            raise ConfigException(
                "Missing required config items in urls section for layer %s" %
                self.name)
        self.parse_feature_info(cfg.get("feature_info", {}))

        self.feature_info_include_utc_dates = cfg.get("feature_info_url_dates",
                                                      False)
        try:
            self.parse_styling(cfg["styling"])
        except KeyError:
            raise ConfigException(
                "Missing required config items in styling section for layer %s"
                % self.name)

        if self.global_cfg.wcs:
            try:
                self.parse_wcs(cfg.get("wcs"), dc)
            except KeyError:
                raise ConfigException(
                    "Missing required config items in wcs section for layer %s"
                    % self.name)

        sub_prod_cfg = cfg.get("sub_products", {})
        self.sub_product_label = sub_prod_cfg.get("label")
        if "extractor" in sub_prod_cfg:
            self.sub_product_extractor = FunctionWrapper(
                self, sub_prod_cfg["extractor"])
        else:
            self.sub_product_extractor = None

        # And finally, add to the global product index.
        self.global_cfg.product_index[self.name] = self
        if not self.multi_product:
            self.global_cfg.native_product_index[self.product_name] = self
 def parse_urls(self, cfg):
     self.feature_list_urls = SuppURL.parse_list(cfg.get("features", []))
     self.data_urls = SuppURL.parse_list(cfg.get("data", []))
Esempio n. 25
0
    def parse_wcs(self, cfg, dc):
        if cfg is None:
            self.wcs = False
            return
        else:
            self.wcs = True
        # Native CRS
        try:
            self.native_CRS = self.product.definition["storage"]["crs"]
        except KeyError:
            self.native_CRS = None
        if not self.native_CRS:
            self.native_CRS = cfg.get("native_crs")
        elif cfg.get("native_crs") == self.native_CRS:
            _LOG.debug(
                "Native crs for layer %s is specified in ODC metadata and does not need to be specified in configuration",
                self.name)
        elif "native_crs" in cfg:
            _LOG.warning(
                "Native crs for layer %s is specified in config as %s - overridden to %s by ODC metadata",
                self.name, cfg['native_crs'], self.native_CRS)

        if not self.native_CRS:
            raise ConfigException(
                f"No native CRS could be found for layer {self.name}")
        if self.native_CRS not in self.global_cfg.published_CRSs:
            raise ConfigException(
                "Native CRS for product %s (%s) not in published CRSs" %
                (self.product_name, self.native_CRS))
        self.native_CRS_def = self.global_cfg.published_CRSs[self.native_CRS]
        # Prepare Rectified Grids
        try:
            native_bounding_box = self.bboxes[self.native_CRS]
        except KeyError:
            _LOG.warning(
                "Layer: %s No bounding box in ranges for native CRS %s - rerun update_ranges.py",
                self.name, self.native_CRS)
            self.hide = True
            return
        self.origin_x = native_bounding_box["left"]
        self.origin_y = native_bounding_box["bottom"]

        try:
            self.resolution_x = self.product.definition["storage"][
                "resolution"][self.native_CRS_def["horizontal_coord"]]
            self.resolution_y = self.product.definition["storage"][
                "resolution"][self.native_CRS_def["vertical_coord"]]
        except KeyError:
            self.resolution_x = None
            self.resolution_y = None

        if self.resolution_x is None:
            try:
                self.resolution_x, self.resolution_y = cfg["native_resolution"]
            except KeyError:
                raise ConfigException(
                    f"No native resolution supplied for WCS enabled layer {self.name}"
                )
            except ValueError:
                raise ConfigException(
                    f"Invalid native resolution supplied for WCS enabled layer {self.name}"
                )
            except TypeError:
                raise ConfigException(
                    f"Invalid native resolution supplied for WCS enabled layer {self.name}"
                )
        elif "native_resolution" in cfg:
            config_x, config_y = cfg["native_resolution"]
            if (math.isclose(config_x, self.resolution_x, rel_tol=1e-10) and
                    math.isclose(config_y, self.resolution_y, rel_tol=1e-10)):
                _LOG.debug(
                    "Native resolution for layer %s is specified in ODC metadata and does not need to be specified in configuration",
                    self.name)
            else:
                _LOG.warning(
                    "Native resolution for layer %s is specified in config as %s - overridden to (%.f, %.f) by ODC metadata",
                    self.name, repr(cfg['native_resolution']),
                    self.resolution_x, self.resolution_y)

        if (native_bounding_box["right"] -
                native_bounding_box["left"]) < self.resolution_x:
            ConfigException(
                "Native (%s) bounding box on layer %s has left %f, right %f (diff %d), but horizontal resolution is %f"
                % (self.native_CRS, self.name, native_bounding_box["left"],
                   native_bounding_box["right"], native_bounding_box["right"] -
                   native_bounding_box["left"], self.resolution_x))
        if (native_bounding_box["top"] -
                native_bounding_box["bottom"]) < self.resolution_x:
            ConfigException(
                "Native (%s) bounding box on layer %s has bottom %f, top %f (diff %d), but vertical resolution is %f"
                % (self.native_CRS, self.name, native_bounding_box["bottom"],
                   native_bounding_box["top"], native_bounding_box["top"] -
                   native_bounding_box["bottom"], self.resolution_y))
        self.grid_high_x = int(
            (native_bounding_box["right"] - native_bounding_box["left"]) /
            self.resolution_x)
        self.grid_high_y = int(
            (native_bounding_box["top"] - native_bounding_box["bottom"]) /
            self.resolution_y)

        if self.grid_high_x == 0:
            err_str = f"Grid High X is zero on layer {self.name}: native ({self.native_CRS}) extent: {native_bounding_box['left']},{native_bounding_box['right']}: x_res={self.resolution_x}"
            raise ConfigException(err_str)
        if self.grid_high_y == 0:
            err_str = f"Grid High y is zero on layer {self.name}: native ({self.native_CRS}) extent: {native_bounding_box['bottom']},{native_bounding_box['top']}: x_res={self.resolution_y}"
            raise ConfigException(err_str)
        self.grids = {}
        for crs, crs_def in self.global_cfg.published_CRSs.items():
            if crs == self.native_CRS:
                self.grids[crs] = {
                    "origin": (self.origin_x, self.origin_y),
                    "resolution": (self.resolution_x, self.resolution_y),
                }
            else:
                try:
                    bbox = self.bboxes[crs]
                except KeyError:
                    continue
                self.grids[crs] = {
                    "origin": (bbox["left"], bbox["bottom"]),
                    "resolution":
                    ((bbox["right"] - bbox["left"]) / self.grid_high_x,
                     (bbox["top"] - bbox["bottom"]) / self.grid_high_y)
                }

        # Band management
        self.wcs_default_bands = [
            self.band_idx.band(b) for b in cfg["default_bands"]
        ]
        # Cache some metadata from the datacube
        try:
            bands = dc.list_measurements().loc[self.product_name]
        except KeyError:
            raise ConfigException(
                "Datacube.list_measurements() not returning measurements for product %s"
                % self.product_name)
        self.bands = bands.index.values
        try:
            self.nodata_values = bands['nodata'].values
        except KeyError:
            raise ConfigException(
                "Datacube has no 'nodata' values for bands in product %s" %
                self.product_name)
        self.nodata_dict = {
            a: b
            for a, b in zip(self.bands, self.nodata_values)
        }

        # Native format
        if "native_format" in cfg:
            self.native_format = cfg["native_format"]
            if self.native_format not in self.global_cfg.wcs_formats_by_name:
                raise ConfigException(
                    "WCS native format for layer %s is not in supported formats list"
                    % self.product_name)
        else:
            self.native_format = self.global_cfg.native_wcs_format
    def parse_global(self, cfg):
        self._response_headers = cfg.get("response_headers", {})
        self.wms = cfg.get("services", {}).get("wms", True)
        self.wmts = cfg.get("services", {}).get("wmts", True)
        self.wcs = cfg.get("services", {}).get("wcs", False)
        if not self.wms and not self.wmts and not self.wcs:
            raise ConfigException("At least one service must be active.")
        self.title = cfg["title"]
        self.allowed_urls = cfg["allowed_urls"]
        self.info_url = cfg["info_url"]
        self.abstract = cfg.get("abstract")
        self.contact_info = cfg.get("contact_info", {})
        self.keywords = cfg.get("keywords", [])
        self.fees = cfg.get("fees")
        self.access_constraints = cfg.get("access_constraints")
        # self.use_extent_views = cfg.get("use_extent_views", False)
        if not self.fees:
            self.fees = "none"
        if not self.access_constraints:
            self.access_constraints = "none"

        def make_gml_name(name):
            if name.startswith("EPSG:"):
                return f"http://www.opengis.net/def/crs/EPSG/0/{name[5:]}"
            else:
                return name

        self.published_CRSs = {}
        self.internal_CRSs = {}
        CRS_aliases = {}
        for crs_str, crsdef in cfg["published_CRSs"].items():
            if "alias" in crsdef:
                CRS_aliases[crs_str] = crsdef
                continue
            self.internal_CRSs[crs_str] = {
                "geographic": crsdef["geographic"],
                "horizontal_coord": crsdef.get("horizontal_coord",
                                               "longitude"),
                "vertical_coord": crsdef.get("vertical_coord", "latitude"),
                "vertical_coord_first": crsdef.get("vertical_coord_first",
                                                   False),
                "gml_name": make_gml_name(crs_str),
                "alias_of": None
            }
            self.published_CRSs[crs_str] = self.internal_CRSs[crs_str]
            if self.published_CRSs[crs_str]["geographic"]:
                if self.published_CRSs[crs_str][
                        "horizontal_coord"] != "longitude":
                    raise ConfigException(
                        f"Published CRS {crs_str} is geographic"
                        "but has a horizontal coordinate that is not 'longitude'"
                    )
                if self.published_CRSs[crs_str]["vertical_coord"] != "latitude":
                    raise ConfigException(
                        f"Published CRS {crs_str} is geographic"
                        "but has a vertical coordinate that is not 'latitude'")
        for alias, alias_def in CRS_aliases.items():
            target_crs = alias_def["alias"]
            if target_crs not in self.published_CRSs:
                _LOG.warning(
                    "CRS %s defined as alias for %s, which is not a published CRS - skipping",
                    alias, target_crs)
                continue
            target_def = self.published_CRSs[target_crs]
            self.published_CRSs[alias] = target_def.copy()
            self.published_CRSs[alias]["gml_name"] = make_gml_name(alias)
            self.published_CRSs[alias]["alias_of"] = target_crs
    def __init__(self, cfg, global_cfg, parent_layer=None, **kwargs):
        name = cfg["name"]
        super().__init__(cfg,
                         global_cfg=global_cfg,
                         parent_layer=parent_layer,
                         keyvals={"layer": name},
                         **kwargs)
        self.name = name
        cfg = self._raw_cfg
        self.hide = False
        try:
            self.parse_product_names(cfg)
            if len(self.low_res_product_names) not in (
                    0, len(self.product_names)):
                raise ConfigException(
                    f"Lengths of product_names and low_res_product_names do not match in layer {self.name}"
                )
            for prod_name in self.product_names:
                if "__" in prod_name:
                    # I think this was for subproducts which are currently broken
                    raise ConfigException(
                        "Product names cannot contain a double underscore '__'."
                    )
        except IndexError:
            raise ConfigException(f"No products declared in layer {self.name}")
        except KeyError:
            raise ConfigException(
                "Required product names entry missing in named layer %s" %
                self.name)
        self.declare_unready("products")
        self.declare_unready("low_res_products")
        self.declare_unready("product")
        self.declare_unready("definition")

        self.time_resolution = cfg.get("time_resolution", TIMERES_RAW)
        if self.time_resolution not in TIMERES_VALS:
            raise ConfigException(
                "Invalid time resolution value %s in named layer %s" %
                (self.time_resolution, self.name))
        self.dynamic = cfg.get("dynamic", False)

        self.declare_unready("_ranges")
        self.declare_unready("bboxes")
        # TODO: sub-ranges
        self.band_idx = BandIndex(self, cfg.get("bands"))
        self.parse_resource_limits(cfg.get("resource_limits", {}))
        try:
            self.parse_flags(cfg.get("flags", {}))
            self.declare_unready("all_flag_band_names")
        except KeyError as e:
            raise ConfigException(
                f"Missing required config ({str(e)}) in flags section for layer {self.name}"
            )
        try:
            self.parse_image_processing(cfg["image_processing"])
        except KeyError as e:
            raise ConfigException(
                f"Missing required config ({str(e)}) in image processing section for layer {self.name}"
            )
        self.identifiers = cfg.get("identifiers", {})
        for auth in self.identifiers.keys():
            if auth not in self.global_cfg.authorities:
                raise ConfigException(
                    f"Identifier with non-declared authority: {auth} in layer {self.name}"
                )
        self.parse_urls(cfg.get("urls", {}))
        self.parse_feature_info(cfg.get("feature_info", {}))
        self.feature_info_include_utc_dates = cfg.get("feature_info_url_dates",
                                                      False)
        try:
            self.parse_styling(cfg["styling"])
        except KeyError as e:
            raise ConfigException(
                f"Missing required config item {e} in styling section for layer {self.name}"
            )

        if self.global_cfg.wcs:
            try:
                self.parse_wcs(cfg.get("wcs"))
            except KeyError as e:
                raise ConfigException(
                    f"Missing required config item {e} in wcs section for layer {self.name}"
                )


#       Sub-products have been broken for some time.
#        sub_prod_cfg = cfg.get("sub_products", {})
#        self.sub_product_label = sub_prod_cfg.get("label")
#        if "extractor" in sub_prod_cfg:
#            self.sub_product_extractor = FunctionWrapper(self, sub_prod_cfg["extractor"])
#        else:
#            self.sub_product_extractor = None
# And finally, add to the global product index.
        self.global_cfg.product_index[self.name] = self