class TestMetaGridLevelMetaTiles(object): def __init__(self): self.meta_grid = MetaGrid(TileGrid(), meta_size=(2, 2)) def test_full_grid_0(self): bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34) abbox, tile_grid, meta_tiles = \ self.meta_grid.get_affected_level_tiles(bbox, 0) meta_tiles = list(meta_tiles) assert_almost_equal_bbox(bbox, abbox) eq_(len(meta_tiles), 1) eq_(meta_tiles[0], (0, 0, 0)) def test_full_grid_2(self): bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34) abbox, tile_grid, meta_tiles = \ self.meta_grid.get_affected_level_tiles(bbox, 2) meta_tiles = list(meta_tiles) assert_almost_equal_bbox(bbox, abbox) eq_(tile_grid, (2, 2)) eq_(len(meta_tiles), 4) eq_(meta_tiles[0], (0, 2, 2)) eq_(meta_tiles[1], (2, 2, 2)) eq_(meta_tiles[2], (0, 0, 2)) eq_(meta_tiles[3], (2, 0, 2))
class TestMetaGridLevelMetaTilesGeodetic(object): def __init__(self): self.meta_grid = MetaGrid(TileGrid(is_geodetic=True), meta_size=(2, 2)) def test_full_grid_2(self): bbox = (-180.0, -90.0, 180.0, 90) abbox, tile_grid, meta_tiles = \ self.meta_grid.get_affected_level_tiles(bbox, 2) meta_tiles = list(meta_tiles) assert_almost_equal_bbox(bbox, abbox) eq_(tile_grid, (2, 1)) eq_(len(meta_tiles), 2) eq_(meta_tiles[0], (0, 0, 2)) eq_(meta_tiles[1], (2, 0, 2)) def test_partial_grid_3(self): bbox = (0.0, 5.0, 45, 40) abbox, tile_grid, meta_tiles = \ self.meta_grid.get_affected_level_tiles(bbox, 3) meta_tiles = list(meta_tiles) assert_almost_equal_bbox((0.0, 0.0, 90.0, 90.0), abbox) eq_(tile_grid, (1, 1)) eq_(len(meta_tiles), 1) eq_(meta_tiles[0], (4, 2, 3))
class TestMetaGridLevelMetaTilesGeodetic(object): def __init__(self): self.meta_grid = MetaGrid(TileGrid(is_geodetic=True), meta_size=(2, 2)) def test_full_grid_2(self): bbox = (-180.0, -90.0, 180.0, 90) abbox, tile_grid, meta_tiles = \ self.meta_grid.get_affected_level_tiles(bbox, 2) meta_tiles = list(meta_tiles) assert_almost_equal_bbox(bbox, abbox) eq_(tile_grid, (2, 1)) eq_(len(meta_tiles), 2) eq_(meta_tiles[0], (0, 0, 2)) eq_(meta_tiles[1], (2, 0, 2)) def test_partial_grid_3(self): bbox = (0.0, 5.0, 45, 40) abbox, tile_grid, meta_tiles = \ self.meta_grid.get_affected_level_tiles(bbox, 3) meta_tiles = list(meta_tiles) assert_almost_equal_bbox((0.0, 0.0, 90.0, 90.0), abbox) eq_(tile_grid, (1, 1)) eq_(len(meta_tiles), 1) eq_(meta_tiles[0], (4, 2, 3))
class TestMetaGridLevelMetaTiles(object): def __init__(self): self.meta_grid = MetaGrid(TileGrid(), meta_size=(2, 2)) def test_full_grid_0(self): bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34) abbox, tile_grid, meta_tiles = \ self.meta_grid.get_affected_level_tiles(bbox, 0) meta_tiles = list(meta_tiles) assert_almost_equal_bbox(bbox, abbox) eq_(len(meta_tiles), 1) eq_(meta_tiles[0], (0, 0, 0)) def test_full_grid_2(self): bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34) abbox, tile_grid, meta_tiles = \ self.meta_grid.get_affected_level_tiles(bbox, 2) meta_tiles = list(meta_tiles) assert_almost_equal_bbox(bbox, abbox) eq_(tile_grid, (2, 2)) eq_(len(meta_tiles), 4) eq_(meta_tiles[0], (0, 2, 2)) eq_(meta_tiles[1], (2, 2, 2)) eq_(meta_tiles[2], (0, 0, 2)) eq_(meta_tiles[3], (2, 0, 2))
class TileWalker(object): def __init__(self, task, worker_pool, handle_stale=False, handle_uncached=False, work_on_metatiles=True, skip_geoms_for_last_levels=0, progress_logger=None, seed_progress=None): self.tile_mgr = task.tile_manager self.task = task self.worker_pool = worker_pool self.handle_stale = handle_stale self.handle_uncached = handle_uncached self.work_on_metatiles = work_on_metatiles self.skip_geoms_for_last_levels = skip_geoms_for_last_levels self.progress_logger = progress_logger num_seed_levels = len(task.levels) self.report_till_level = task.levels[int(num_seed_levels * 0.8)] meta_size = self.tile_mgr.meta_grid.meta_size if self.tile_mgr.meta_grid else (1, 1) self.tiles_per_metatile = meta_size[0] * meta_size[1] self.grid = MetaGrid(self.tile_mgr.grid, meta_size=meta_size, meta_buffer=0) self.count = 0 self.seed_progress = seed_progress or SeedProgress() def walk(self): assert self.handle_stale or self.handle_uncached bbox = self.task.coverage.extent.bbox_for(self.tile_mgr.grid.srs) if self.seed_progress.already_processed(): # nothing to seed self.seed_progress.step_forward() else: try: self._walk(bbox, self.task.levels) except StopProcess: pass self.report_progress(self.task.levels[0], self.task.coverage.bbox) def _walk(self, cur_bbox, levels, all_subtiles=False): """ :param cur_bbox: the bbox to seed in this call :param levels: list of levels to seed :param all_subtiles: seed all subtiles and do not check for intersections with bbox/geom """ current_level, levels = levels[0], levels[1:] bbox_, tiles, subtiles = self.grid.get_affected_level_tiles(cur_bbox, current_level) total_subtiles = tiles[0] * tiles[1] if len(levels) < self.skip_geoms_for_last_levels: # do not filter in last levels all_subtiles = True subtiles = self._filter_subtiles(subtiles, all_subtiles) if current_level <= self.report_till_level: self.report_progress(current_level, cur_bbox) if not self.seed_progress.running(): self.report_progress(current_level, cur_bbox) self.tile_mgr.cleanup() raise StopProcess() for i, (subtile, sub_bbox, intersection) in enumerate(subtiles): if subtile is None: # no intersection self.seed_progress.step_forward(total_subtiles) continue if levels: # recurse to next level sub_bbox = limit_sub_bbox(cur_bbox, sub_bbox) if intersection == CONTAINS: all_subtiles = True else: all_subtiles = False with self.seed_progress.step_down(i, total_subtiles): if self.seed_progress.already_processed(): self.seed_progress.step_forward() else: self._walk(sub_bbox, levels, all_subtiles=all_subtiles) if not self.work_on_metatiles: # collect actual tiles handle_tiles = self.grid.tile_list(subtile) else: handle_tiles = [subtile] if self.handle_uncached: handle_tiles = [t for t in handle_tiles if t is not None and not self.tile_mgr.is_cached(t)] elif self.handle_stale: handle_tiles = [t for t in handle_tiles if t is not None and self.tile_mgr.is_stale(t)] if handle_tiles: self.count += 1 self.worker_pool.process(handle_tiles, self.seed_progress) if not levels: self.seed_progress.step_forward(total_subtiles) if len(levels) >= 4: # call cleanup to close open caches # for connection based caches self.tile_mgr.cleanup() def report_progress(self, level, bbox): if self.progress_logger: self.progress_logger.log_progress(self.seed_progress, level, bbox, self.count * self.tiles_per_metatile) def _filter_subtiles(self, subtiles, all_subtiles): """ Return an iterator with all sub tiles. Yields (None, None, None) for non-intersecting tiles, otherwise (subtile, subtile_bbox, intersection). """ for subtile in subtiles: if subtile is None: yield None, None, None else: sub_bbox = self.grid.meta_tile(subtile).bbox if all_subtiles: intersection = CONTAINS else: intersection = self.task.intersects(sub_bbox) if intersection: yield subtile, sub_bbox, intersection else: yield None, None, None
class TileWalker(object): def __init__(self, task, worker_pool, handle_stale=False, handle_uncached=False, work_on_metatiles=True, skip_geoms_for_last_levels=0, progress_logger=None): self.tile_mgr = task.tile_manager self.task = task self.worker_pool = worker_pool self.handle_stale = handle_stale self.handle_uncached = handle_uncached self.work_on_metatiles = work_on_metatiles self.skip_geoms_for_last_levels = skip_geoms_for_last_levels self.progress_logger = progress_logger num_seed_levels = len(task.levels) self.report_till_level = task.levels[int(num_seed_levels * 0.8)] meta_size = self.tile_mgr.meta_grid.meta_size if self.tile_mgr.meta_grid else ( 1, 1) self.tiles_per_metatile = meta_size[0] * meta_size[1] self.grid = MetaGrid(self.tile_mgr.grid, meta_size=meta_size, meta_buffer=0) self.progress = 0.0 self.eta = ETA() self.count = 0 def walk(self): assert self.handle_stale or self.handle_uncached bbox = self.task.coverage.extent.bbox_for(self.tile_mgr.grid.srs) self._walk(bbox, self.task.levels) self.report_progress(self.task.levels[0], self.task.coverage.bbox) def _walk(self, cur_bbox, levels, progess_str='', progress=1.0, all_subtiles=False): """ :param cur_bbox: the bbox to seed in this call :param levels: list of levels to seed :param all_subtiles: seed all subtiles and do not check for intersections with bbox/geom """ current_level, levels = levels[0], levels[1:] bbox_, tiles, subtiles = self.grid.get_affected_level_tiles( cur_bbox, current_level) total_subtiles = tiles[0] * tiles[1] if len(levels) < self.skip_geoms_for_last_levels: # do not filter in last levels all_subtiles = True subtiles = self._filter_subtiles(subtiles, all_subtiles) if current_level <= self.report_till_level: self.report_progress(current_level, cur_bbox) progress = progress / total_subtiles for i, (subtile, sub_bbox, intersection) in enumerate(subtiles): if subtile is None: # no intersection self.progress += progress continue if levels: # recurse to next level sub_bbox = limit_sub_bbox(cur_bbox, sub_bbox) cur_progess_str = progess_str + status_symbol( i, total_subtiles) if intersection == CONTAINS: all_subtiles = True else: all_subtiles = False self._walk(sub_bbox, levels, cur_progess_str, all_subtiles=all_subtiles, progress=progress) if not self.work_on_metatiles: # collect actual tiles handle_tiles = self.grid.tile_list(subtile) else: handle_tiles = [subtile] if self.handle_uncached: handle_tiles = [ t for t in handle_tiles if t is not None and not self.tile_mgr.is_cached(t) ] elif self.handle_stale: handle_tiles = [ t for t in handle_tiles if t is not None and self.tile_mgr.is_stale(t) ] if handle_tiles: self.count += 1 self.worker_pool.process( handle_tiles, (progess_str, self.progress, self.eta)) if not levels: self.progress += progress if len(levels) >= 4: # call cleanup to close open caches # for connection based caches self.tile_mgr.cleanup() self.eta.update(self.progress) def report_progress(self, level, bbox): if self.progress_logger: self.progress_logger.log_progress( self.progress, level, bbox, self.count * self.tiles_per_metatile, self.eta) def _filter_subtiles(self, subtiles, all_subtiles): """ Return an iterator with all sub tiles. Yields (None, None, None) for non-intersecting tiles, otherwise (subtile, subtile_bbox, intersection). """ for subtile in subtiles: if subtile is None: yield None, None, None else: sub_bbox = self.grid.meta_tile(subtile).bbox if all_subtiles: intersection = CONTAINS else: intersection = self.task.intersects(sub_bbox) if intersection: yield subtile, sub_bbox, intersection else: yield None, None, None
class TileWalker(object): """ TileWalker traverses through all tiles in a tile grid and calls worker_pool.process for each (meta) tile. It traverses the tile grid (pyramid) depth-first. Intersection with coverages are checked before handling subtiles in the next level, allowing to determine if all subtiles should be seeded or skipped. """ def __init__(self, task, worker_pool, handle_stale=False, handle_uncached=False, work_on_metatiles=True, skip_geoms_for_last_levels=0, progress_logger=None, seed_progress=None): self.tile_mgr = task.tile_manager self.task = task self.worker_pool = worker_pool self.handle_stale = handle_stale self.handle_uncached = handle_uncached self.work_on_metatiles = work_on_metatiles self.skip_geoms_for_last_levels = skip_geoms_for_last_levels self.progress_logger = progress_logger num_seed_levels = len(task.levels) if num_seed_levels >= 4: self.report_till_level = task.levels[num_seed_levels - 2] else: self.report_till_level = task.levels[num_seed_levels - 1] meta_size = self.tile_mgr.meta_grid.meta_size if self.tile_mgr.meta_grid else ( 1, 1) self.tiles_per_metatile = meta_size[0] * meta_size[1] self.grid = MetaGrid(self.tile_mgr.grid, meta_size=meta_size, meta_buffer=0) self.count = 0 self.seed_progress = seed_progress or SeedProgress() # It is possible that we 'walk' through the same tile multiple times # when seeding irregular tile grids[0]. limit_sub_bbox prevents that we # recurse into the same area multiple times, but it is still possible # that a tile is processed multiple times. Locking prevents that a tile # is seeded multiple times, but it is possible that we count the same tile # multiple times (in dry-mode, or while the tile is in the process queue). # Tile counts can be off by 280% with sqrt2 grids. # We keep a small cache of already processed tiles to skip most duplicates. # A simple cache of 64 tile coordinates for each level already brings the # difference down to ~8%, which is good enough and faster than a more # sophisticated FIFO cache with O(1) lookup, or even caching all tiles. # [0] irregular tile grids: where one tile does not have exactly 4 subtiles # Typically when you use res_factor, or a custom res list. self.seeded_tiles = {l: deque(maxlen=64) for l in task.levels} def walk(self): assert self.handle_stale or self.handle_uncached bbox = self.task.coverage.extent.bbox_for(self.tile_mgr.grid.srs) if self.seed_progress.already_processed(): # nothing to seed self.seed_progress.step_forward() else: try: self._walk(bbox, self.task.levels) except StopProcess: pass self.report_progress(self.task.levels[0], self.task.coverage.bbox) def _walk(self, cur_bbox, levels, current_level=0, all_subtiles=False): """ :param cur_bbox: the bbox to seed in this call :param levels: list of levels to seed :param all_subtiles: seed all subtiles and do not check for intersections with bbox/geom """ bbox_, tiles, subtiles = self.grid.get_affected_level_tiles( cur_bbox, current_level) total_subtiles = tiles[0] * tiles[1] if len(levels) < self.skip_geoms_for_last_levels: # do not filter in last levels all_subtiles = True subtiles = self._filter_subtiles(subtiles, all_subtiles) if current_level in levels and current_level <= self.report_till_level: self.report_progress(current_level, cur_bbox) if not self.seed_progress.running(): if current_level in levels: self.report_progress(current_level, cur_bbox) self.tile_mgr.cleanup() raise StopProcess() process = False if current_level in levels: levels = levels[1:] process = True for i, (subtile, sub_bbox, intersection) in enumerate(subtiles): if subtile is None: # no intersection self.seed_progress.step_forward(total_subtiles) continue if levels: # recurse to next level sub_bbox = limit_sub_bbox(cur_bbox, sub_bbox) if intersection == CONTAINS: all_subtiles = True else: all_subtiles = False with self.seed_progress.step_down(i, total_subtiles): if self.seed_progress.already_processed(): self.seed_progress.step_forward() else: self._walk(sub_bbox, levels, current_level=current_level + 1, all_subtiles=all_subtiles) if not process: continue # check if subtile was already processed. see comment in __init__ if subtile in self.seeded_tiles[current_level]: if not levels: self.seed_progress.step_forward(total_subtiles) continue self.seeded_tiles[current_level].appendleft(subtile) if not self.work_on_metatiles: # collect actual tiles handle_tiles = self.grid.tile_list(subtile) else: handle_tiles = [subtile] if self.handle_uncached: handle_tiles = [ t for t in handle_tiles if t is not None and not self.tile_mgr.is_cached(t) ] elif self.handle_stale: handle_tiles = [ t for t in handle_tiles if t is not None and self.tile_mgr.is_stale(t) ] if handle_tiles: self.count += 1 self.worker_pool.process(handle_tiles, self.seed_progress) if not levels: self.seed_progress.step_forward(total_subtiles) if len(levels) >= 4: # call cleanup to close open caches # for connection based caches self.tile_mgr.cleanup() def report_progress(self, level, bbox): if self.progress_logger: self.progress_logger.log_progress( self.seed_progress, level, bbox, self.count * self.tiles_per_metatile) def _filter_subtiles(self, subtiles, all_subtiles): """ Return an iterator with all sub tiles. Yields (None, None, None) for non-intersecting tiles, otherwise (subtile, subtile_bbox, intersection). """ for subtile in subtiles: if subtile is None: yield None, None, None else: sub_bbox = self.grid.meta_tile(subtile).bbox if all_subtiles: intersection = CONTAINS else: intersection = self.task.intersects(sub_bbox) if intersection: yield subtile, sub_bbox, intersection else: yield None, None, None
class TileWalker(object): """ TileWalker traverses through all tiles in a tile grid and calls worker_pool.process for each (meta) tile. It traverses the tile grid (pyramid) depth-first. Intersection with coverages are checked before handling subtiles in the next level, allowing to determine if all subtiles should be seeded or skipped. """ def __init__(self, task, worker_pool, handle_stale=False, handle_uncached=False, work_on_metatiles=True, skip_geoms_for_last_levels=0, progress_logger=None, seed_progress=None): self.tile_mgr = task.tile_manager self.task = task self.worker_pool = worker_pool self.handle_stale = handle_stale self.handle_uncached = handle_uncached self.work_on_metatiles = work_on_metatiles self.skip_geoms_for_last_levels = skip_geoms_for_last_levels self.progress_logger = progress_logger num_seed_levels = len(task.levels) if num_seed_levels >= 4: self.report_till_level = task.levels[num_seed_levels-2] else: self.report_till_level = task.levels[num_seed_levels-1] meta_size = self.tile_mgr.meta_grid.meta_size if self.tile_mgr.meta_grid else (1, 1) self.tiles_per_metatile = meta_size[0] * meta_size[1] self.grid = MetaGrid(self.tile_mgr.grid, meta_size=meta_size, meta_buffer=0) self.count = 0 self.seed_progress = seed_progress or SeedProgress() # It is possible that we 'walk' through the same tile multiple times # when seeding irregular tile grids[0]. limit_sub_bbox prevents that we # recurse into the same area multiple times, but it is still possible # that a tile is processed multiple times. Locking prevents that a tile # is seeded multiple times, but it is possible that we count the same tile # multiple times (in dry-mode, or while the tile is in the process queue). # Tile counts can be off by 280% with sqrt2 grids. # We keep a small cache of already processed tiles to skip most duplicates. # A simple cache of 64 tile coordinates for each level already brings the # difference down to ~8%, which is good enough and faster than a more # sophisticated FIFO cache with O(1) lookup, or even caching all tiles. # [0] irregular tile grids: where one tile does not have exactly 4 subtiles # Typically when you use res_factor, or a custom res list. self.seeded_tiles = {l: deque(maxlen=64) for l in task.levels} def walk(self): assert self.handle_stale or self.handle_uncached bbox = self.task.coverage.extent.bbox_for(self.tile_mgr.grid.srs) if self.seed_progress.already_processed(): # nothing to seed self.seed_progress.step_forward() else: try: self._walk(bbox, self.task.levels) except StopProcess: pass self.report_progress(self.task.levels[0], self.task.coverage.bbox) def _walk(self, cur_bbox, levels, current_level=0, all_subtiles=False): """ :param cur_bbox: the bbox to seed in this call :param levels: list of levels to seed :param all_subtiles: seed all subtiles and do not check for intersections with bbox/geom """ bbox_, tiles, subtiles = self.grid.get_affected_level_tiles(cur_bbox, current_level) total_subtiles = tiles[0] * tiles[1] if len(levels) < self.skip_geoms_for_last_levels: # do not filter in last levels all_subtiles = True subtiles = self._filter_subtiles(subtiles, all_subtiles) if current_level in levels and current_level <= self.report_till_level: self.report_progress(current_level, cur_bbox) if not self.seed_progress.running(): if current_level in levels: self.report_progress(current_level, cur_bbox) self.tile_mgr.cleanup() raise StopProcess() process = False; if current_level in levels: levels = levels[1:] process = True for i, (subtile, sub_bbox, intersection) in enumerate(subtiles): if subtile is None: # no intersection self.seed_progress.step_forward(total_subtiles) continue if levels: # recurse to next level sub_bbox = limit_sub_bbox(cur_bbox, sub_bbox) if intersection == CONTAINS: all_subtiles = True else: all_subtiles = False with self.seed_progress.step_down(i, total_subtiles): if self.seed_progress.already_processed(): self.seed_progress.step_forward() else: self._walk(sub_bbox, levels, current_level=current_level+1, all_subtiles=all_subtiles) if not process: continue # check if subtile was already processed. see comment in __init__ if subtile in self.seeded_tiles[current_level]: if not levels: self.seed_progress.step_forward(total_subtiles) continue self.seeded_tiles[current_level].appendleft(subtile) if not self.work_on_metatiles: # collect actual tiles handle_tiles = self.grid.tile_list(subtile) else: handle_tiles = [subtile] if self.handle_uncached: handle_tiles = [t for t in handle_tiles if t is not None and not self.tile_mgr.is_cached(t)] elif self.handle_stale: handle_tiles = [t for t in handle_tiles if t is not None and self.tile_mgr.is_stale(t)] if handle_tiles: self.count += 1 self.worker_pool.process(handle_tiles, self.seed_progress) if not levels: self.seed_progress.step_forward(total_subtiles) if len(levels) >= 4: # call cleanup to close open caches # for connection based caches self.tile_mgr.cleanup() def report_progress(self, level, bbox): if self.progress_logger: self.progress_logger.log_progress(self.seed_progress, level, bbox, self.count * self.tiles_per_metatile) def _filter_subtiles(self, subtiles, all_subtiles): """ Return an iterator with all sub tiles. Yields (None, None, None) for non-intersecting tiles, otherwise (subtile, subtile_bbox, intersection). """ for subtile in subtiles: if subtile is None: yield None, None, None else: sub_bbox = self.grid.meta_tile(subtile).bbox if all_subtiles: intersection = CONTAINS else: intersection = self.task.intersects(sub_bbox) if intersection: yield subtile, sub_bbox, intersection else: yield None, None, None