def snap_bounds(bounds=None, pyramid=None, zoom=None): """ Snaps bounds to tiles boundaries of specific zoom level. Parameters ---------- bounds : bounds to be snapped pyramid : TilePyramid zoom : int Returns ------- Bounds(left, bottom, right, top) """ if not isinstance(bounds, (tuple, list)): raise TypeError("bounds must be either a tuple or a list") if len(bounds) != 4: raise ValueError("bounds has to have exactly four values") if not isinstance(pyramid, BufferedTilePyramid): raise TypeError("pyramid has to be a BufferedTilePyramid") bounds = Bounds(*bounds) lb = pyramid.tile_from_xy(bounds.left, bounds.bottom, zoom, on_edge_use="rt").bounds rt = pyramid.tile_from_xy(bounds.right, bounds.top, zoom, on_edge_use="lb").bounds return Bounds(lb.left, lb.bottom, rt.right, rt.top)
def bounds_from_opts(wkt_geometry=None, point=None, bounds=None, zoom=None, raw_conf=None): """ Loads the process pyramid of a raw configuration. Parameters ---------- raw_conf : dict Raw mapchete configuration as dictionary. Returns ------- BufferedTilePyramid """ if wkt_geometry: return Bounds(*wkt.loads(wkt_geometry).bounds) elif point: x, y = point zoom_levels = get_zoom_levels( process_zoom_levels=raw_conf["zoom_levels"], init_zoom_levels=zoom) tp = raw_conf_process_pyramid(raw_conf) return Bounds(*tp.tile_from_xy(x, y, max(zoom_levels)).bounds) else: return validate_bounds(bounds) if bounds is not None else bounds
def bounds_from_opts(wkt_geometry=None, point=None, point_crs=None, zoom=None, bounds=None, bounds_crs=None, raw_conf=None): """ Return process bounds depending on given inputs. Parameters ---------- wkt_geometry : string WKT geometry used to generate bounds. point : iterable x and y coordinates of point whose corresponding process tile bounds shall be returned. point_crs : str or CRS CRS of point (default: process pyramid CRS) zoom : int Mandatory zoom level if point is provided. bounds : iterable Bounding coordinates to be used bounds_crs : str or CRS CRS of bounds (default: process pyramid CRS) raw_conf : dict Raw mapchete configuration as dictionary. Returns ------- BufferedTilePyramid """ if wkt_geometry: return Bounds(*wkt.loads(wkt_geometry).bounds) elif point: x, y = point tp = raw_conf_process_pyramid(raw_conf) if point_crs: reproj = reproject_geometry(Point(x, y), src_crs=point_crs, dst_crs=tp.crs) x = reproj.x y = reproj.y zoom_levels = get_zoom_levels( process_zoom_levels=raw_conf["zoom_levels"], init_zoom_levels=zoom) return Bounds(*tp.tile_from_xy(x, y, max(zoom_levels)).bounds) elif bounds: bounds = validate_bounds(bounds) if bounds_crs: tp = raw_conf_process_pyramid(raw_conf) bounds = Bounds(*reproject_geometry( box(*bounds), src_crs=bounds_crs, dst_crs=tp.crs).bounds) return bounds else: return
def clip_bounds(bounds=None, clip=None): """ Clips bounds by clip. Parameters ---------- bounds : bounds to be clipped clip : clip bounds Returns ------- Bounds(left, bottom, right, top) """ bounds = Bounds(*bounds) clip = Bounds(*clip) return Bounds(max(bounds.left, clip.left), max(bounds.bottom, clip.bottom), min(bounds.right, clip.right), min(bounds.top, clip.top))
def init_bounds(self): """ Process bounds this process is currently initialized with. This gets triggered by using the ``bounds`` kwarg. If not set, it will be equal to self.bounds. """ if self._raw["init_bounds"] is None: return self.bounds else: return Bounds(*_validate_bounds(self._raw["init_bounds"]))
def bounds_at_zoom(self, zoom=None): """ Return process bounds for zoom level. Parameters ---------- zoom : integer or list Returns ------- process bounds : tuple left, bottom, right, top """ return () if self.area_at_zoom(zoom).is_empty else Bounds( *self.area_at_zoom(zoom).bounds)
def snap_bounds(bounds=None, pyramid=None, zoom=None): """ Snaps bounds to tiles boundaries of specific zoom level. Parameters ---------- bounds : bounds to be snapped pyramid : TilePyramid zoom : int Returns ------- Bounds(left, bottom, right, top) """ bounds = validate_bounds(bounds) pyramid = validate_bufferedtilepyramid(pyramid) lb = pyramid.tile_from_xy(bounds.left, bounds.bottom, zoom, on_edge_use="rt").bounds rt = pyramid.tile_from_xy(bounds.right, bounds.top, zoom, on_edge_use="lb").bounds return Bounds(lb.left, lb.bottom, rt.right, rt.top)
def validate_bounds(bounds): """ Return validated bounds. Bounds must be a list or tuple with exactly four elements. Parameters ---------- bounds : list or tuple Returns ------- Bounds Raises ------ TypeError if type is invalid. """ if not isinstance(bounds, (tuple, list)): raise TypeError("bounds must be either a tuple or a list: %s" % str(bounds)) if len(bounds) != 4: raise ValueError("bounds has to have exactly four values: %s" % str(bounds)) return Bounds(*bounds)
def bounds(self): """Process bounds as defined in the configuration.""" if self._raw["bounds"] is None: return self.process_pyramid.bounds else: return Bounds(*_validate_bounds(self._raw["bounds"]))
def __init__(self, input_config, zoom=None, area=None, area_crs=None, bounds=None, bounds_crs=None, single_input_file=None, mode="continue", debug=False, **kwargs): """Initialize configuration.""" logger.debug(f"parsing {input_config}") # get dictionary representation of input_config and # (0) map deprecated params to new structure self._raw = _map_to_new_config(_config_to_dict(input_config)) self._raw["init_zoom_levels"] = zoom self._raw["init_bounds"] = bounds self._raw["init_bounds_crs"] = bounds_crs self._raw["init_area"] = area self._raw["init_area_crs"] = area_crs self._cache_area_at_zoom = {} self._cache_full_process_area = None if mode not in ["memory", "continue", "readonly", "overwrite"]: raise MapcheteConfigError("unknown mode %s" % mode) self.mode = mode # (1) assert mandatory params are available try: validate_values(self._raw, _MANDATORY_PARAMETERS) except Exception as e: raise MapcheteConfigError(e) # (2) check user process self.config_dir = self._raw["config_dir"] self.process_name = self.process_path = self._raw["process"] if self.mode != "readonly": logger.debug("validating process code") self.process_func # (3) set process and output pyramids logger.debug("initializing pyramids") try: process_metatiling = self._raw["pyramid"].get("metatiling", 1) # output metatiling defaults to process metatiling if not set # explicitly output_metatiling = self._raw["output"].get( "metatiling", process_metatiling) # we cannot properly handle output tiles which are bigger than # process tiles if output_metatiling > process_metatiling: raise ValueError( "output metatiles must be smaller than process metatiles") # these two BufferedTilePyramid instances will help us with all # the tile geometries etc. self.process_pyramid = BufferedTilePyramid( self._raw["pyramid"]["grid"], metatiling=process_metatiling, pixelbuffer=self._raw["pyramid"].get("pixelbuffer", 0)) self.output_pyramid = BufferedTilePyramid( self._raw["pyramid"]["grid"], metatiling=output_metatiling, pixelbuffer=self._raw["output"].get("pixelbuffer", 0)) except Exception as e: logger.exception(e) raise MapcheteConfigError(e) # (4) set approach how to handle inputs # don't inititalize inputs on readonly mode or if only overviews are going to be # built self._init_inputs = False if (self.mode == "readonly" or (not len( set(self.baselevels["zooms"]).intersection( set(self.init_zoom_levels))) if self.baselevels else False) ) else True # (5) prepare process parameters per zoom level without initializing # input and output classes logger.debug("preparing process parameters") self._params_at_zoom = _raw_at_zoom(self._raw, self.init_zoom_levels) # (6) determine process area and process boundaries both from config as well # as from initialization. # First, the area and bounds parameters from the configuration are checked. If # both are provided, the intersection will be taken into account. If none are, # the process pyramid area is assumed. # Second, they can be overrided by the area and bounds kwargs when constructing # the configuration. # To finally determine the process tiles, the intersection of process area and the # union of all inputs is considered. self.area = self._get_process_area( area=self._raw.get("area"), bounds=self._raw.get("bounds"), area_fallback=box(*self.process_pyramid.bounds), bounds_fallback=self.process_pyramid.bounds, area_crs=area_crs, bounds_crs=bounds_crs) logger.debug(f"process area: {self.area}") self.bounds = Bounds(*self.area.bounds) logger.debug(f"process bounds: {self.bounds}") self.init_area = self._get_process_area( area=self._raw.get("init_area"), bounds=self._raw.get("init_bounds"), area_fallback=self.area, bounds_fallback=self.bounds, area_crs=area_crs, bounds_crs=bounds_crs) logger.debug(f"init area: {self.init_area}") self.init_bounds = Bounds(*self.init_area.bounds) logger.debug(f"init bounds: {self.init_bounds}") # (7) the delimiters are used by some input drivers self._delimiters = dict(zoom=self.init_zoom_levels, bounds=self.init_bounds, process_bounds=self.bounds, effective_bounds=self.effective_bounds) # (8) initialize output logger.debug("initializing output") self.output # (9) initialize input items # depending on the inputs this action takes the longest and is done # in the end to let all other actions fail earlier if necessary logger.debug("initializing input") self.input # (10) some output drivers such as the GeoTIFF single file driver also needs the # process area to prepare logger.debug("prepare output") self.output.prepare(process_area=self.area_at_zoom())