Coverage for gws-app/gws/gis/cache/store.py: 97%
76 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""Filesystem tile store, MapProxy 'mp' directory layout."""
3import os
5import gws
6import gws.lib.osx as osx
9class Object(gws.TileStore):
10 """Filesystem tile store."""
12 def __init__(self, base_dir: str, max_age: int, extension: str):
13 """Create a store.
15 Args:
16 base_dir: Base directory.
17 max_age: Max. age of stored tiles in seconds.
18 extension: File extension of tile files, without a dot.
19 """
20 self.baseDir = base_dir
21 self.maxAge = max_age
22 self.extension = extension
24 def stats(self) -> gws.TileStoreStats:
25 return self._stats()
27 def stats_for_level(self, z: int) -> gws.TileStoreStats:
28 return self._stats(z)
30 def path(self, mt: gws.MapTile) -> str:
31 x, y, z = mt
32 s = 10000
33 return f'{self._level_dir(z)}/{(x // s):04d}/{(x % s):04d}/{(y // s):04d}/{(y % s):04d}.{self.extension}'
35 def has(self, mt: gws.MapTile) -> bool:
36 p = self.path(mt)
37 age = osx.file_age(p)
38 return 0 <= age < self.maxAge
40 def read(self, mt: gws.MapTile) -> bytes | None:
41 if not self.has(mt):
42 return None
43 try:
44 with open(self.path(mt), 'rb') as fp:
45 return fp.read()
46 except OSError:
47 return None
49 def write(self, mt: gws.MapTile, blob: bytes):
50 p = self.path(mt)
51 try:
52 osx.mkdir(os.path.dirname(p))
53 gws.u.write_file_b(p, blob)
54 except OSError as exc:
55 gws.log.warning(f'tile store: write failed {p!r}: {exc}')
57 def drop(self):
58 osx.rmdir(self.baseDir)
60 def drop_level(self, z: int):
61 osx.rmdir(self._level_dir(z))
63 def drop_range(self, mtr: gws.MapTileRange):
64 x0, y0, x1, y1, z = mtr
65 dir = self._level_dir(z)
66 if not gws.u.is_dir(dir):
67 return
69 for de in osx.find_entries(dir):
70 if not de.is_file() or de.name.endswith('.tmp'):
71 continue
72 x, y = self._tile_xy(de.path)
73 if x0 <= x <= x1 and y0 <= y <= y1:
74 osx.unlink(de.path)
76 ##
78 def _level_dir(self, z: int) -> str:
79 return f'{self.baseDir}/{z:02d}'
81 def _tile_xy(self, path: str) -> tuple[int, int]:
82 a, b, c, d = path.rsplit('.', 1)[0].split('/')[-4:]
83 return int(a) * 10000 + int(b), int(c) * 10000 + int(d)
85 def _stats(self, z: int | None = None) -> gws.TileStoreStats:
86 stats = gws.TileStoreStats(count=0, size=0, range=None)
87 dir = self._level_dir(z) if z is not None else self.baseDir
88 if not gws.u.is_dir(dir):
89 return stats
91 last_time = gws.u.stime() - self.maxAge
93 for de in osx.find_entries(dir):
94 if not de.is_file() or de.name.endswith('.tmp'):
95 continue
96 s = de.stat()
97 if s.st_size == 0 or s.st_mtime < last_time:
98 continue
99 stats.count += 1
100 stats.size += s.st_size
101 if z is None:
102 continue
104 x, y = self._tile_xy(de.path)
105 if stats.range is None:
106 stats.range = (x, y, x, y, z)
107 else:
108 stats.range = (
109 min(stats.range[0], x),
110 min(stats.range[1], y),
111 max(stats.range[2], x),
112 max(stats.range[3], y),
113 z,
114 )
116 return stats