GeoPakTile 2.1.0__tar.gz → 2.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {geopaktile-2.1.0 → geopaktile-2.1.2}/PKG-INFO +1 -1
- {geopaktile-2.1.0 → geopaktile-2.1.2}/pyproject.toml +50 -50
- {geopaktile-2.1.0 → geopaktile-2.1.2}/src/geopaktile/geopaktile.py +1 -3
- {geopaktile-2.1.0 → geopaktile-2.1.2}/src/geopaktile/gpkg_to_cog.py +76 -5
- {geopaktile-2.1.0 → geopaktile-2.1.2}/LICENSE.md +0 -0
- {geopaktile-2.1.0 → geopaktile-2.1.2}/README.md +0 -0
- {geopaktile-2.1.0 → geopaktile-2.1.2}/src/geopaktile/__init__.py +0 -0
- {geopaktile-2.1.0 → geopaktile-2.1.2}/src/geopaktile/__main__.py +0 -0
- {geopaktile-2.1.0 → geopaktile-2.1.2}/src/geopaktile/xml_parser.py +0 -0
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "GeoPakTile"
|
|
3
|
-
version = "2.1.
|
|
4
|
-
description = "Create a Geopackage XYZ Tile layer."
|
|
5
|
-
readme = "README.md"
|
|
6
|
-
authors = [
|
|
7
|
-
{ name = "apwebber", email = "39490780+apwebber@users.noreply.github.com" }
|
|
8
|
-
]
|
|
9
|
-
license = "MIT"
|
|
10
|
-
license-files = ["LICENSE.md"]
|
|
11
|
-
keywords = ["GIS", "Geopackage", "Tiles", "XYZ", "gpkg"]
|
|
12
|
-
requires-python = ">=3.10"
|
|
13
|
-
classifiers = [
|
|
14
|
-
"Development Status :: 4 - Beta",
|
|
15
|
-
|
|
16
|
-
# Specify the Python versions you support here.
|
|
17
|
-
"Programming Language :: Python :: 3.10",
|
|
18
|
-
"Programming Language :: Python :: 3.11",
|
|
19
|
-
"Programming Language :: Python :: 3.12",
|
|
20
|
-
"Programming Language :: Python :: 3.13",
|
|
21
|
-
"Programming Language :: Python :: 3.14",
|
|
22
|
-
]
|
|
23
|
-
dependencies = [
|
|
24
|
-
"typing_extensions>=4.0; python_version < '3.11'",
|
|
25
|
-
"morecantile>=6.2.0",
|
|
26
|
-
"numpy>=2.2.6",
|
|
27
|
-
"requests>=2.34.2",
|
|
28
|
-
"pillow>=12.3.0",
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
[project.urls]
|
|
32
|
-
Homepage = "https://github.com/apwebber/GeoPakTile"
|
|
33
|
-
Documentation = "https://github.com/apwebber/GeoPakTile"
|
|
34
|
-
Repository = "https://github.com/apwebber/GeoPakTile.git"
|
|
35
|
-
Issues = "https://github.com/apwebber/GeoPakTile/issues"
|
|
36
|
-
|
|
37
|
-
[project.scripts]
|
|
38
|
-
geopaktile = "geopaktile:main"
|
|
39
|
-
|
|
40
|
-
[build-system]
|
|
41
|
-
requires = ["uv_build>=0.11.2,<0.12.0"]
|
|
42
|
-
build-backend = "uv_build"
|
|
43
|
-
|
|
44
|
-
[dependency-groups]
|
|
45
|
-
dev = [
|
|
46
|
-
"pytest",
|
|
47
|
-
"diskcache",
|
|
48
|
-
"rasterio",
|
|
49
|
-
"rio-cogeo>=6.0.0",
|
|
50
|
-
]
|
|
1
|
+
[project]
|
|
2
|
+
name = "GeoPakTile"
|
|
3
|
+
version = "2.1.2"
|
|
4
|
+
description = "Create a Geopackage XYZ Tile layer."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "apwebber", email = "39490780+apwebber@users.noreply.github.com" }
|
|
8
|
+
]
|
|
9
|
+
license = "MIT"
|
|
10
|
+
license-files = ["LICENSE.md"]
|
|
11
|
+
keywords = ["GIS", "Geopackage", "Tiles", "XYZ", "gpkg"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
|
|
16
|
+
# Specify the Python versions you support here.
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"typing_extensions>=4.0; python_version < '3.11'",
|
|
25
|
+
"morecantile>=6.2.0",
|
|
26
|
+
"numpy>=2.2.6",
|
|
27
|
+
"requests>=2.34.2",
|
|
28
|
+
"pillow>=12.3.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/apwebber/GeoPakTile"
|
|
33
|
+
Documentation = "https://github.com/apwebber/GeoPakTile"
|
|
34
|
+
Repository = "https://github.com/apwebber/GeoPakTile.git"
|
|
35
|
+
Issues = "https://github.com/apwebber/GeoPakTile/issues"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
geopaktile = "geopaktile:main"
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["uv_build>=0.11.2,<0.12.0"]
|
|
42
|
+
build-backend = "uv_build"
|
|
43
|
+
|
|
44
|
+
[dependency-groups]
|
|
45
|
+
dev = [
|
|
46
|
+
"pytest",
|
|
47
|
+
"diskcache",
|
|
48
|
+
"rasterio",
|
|
49
|
+
"rio-cogeo>=6.0.0",
|
|
50
|
+
]
|
|
@@ -159,6 +159,7 @@ class GeoPakTile:
|
|
|
159
159
|
)
|
|
160
160
|
VALUES (?, ?, ?, ?)
|
|
161
161
|
"""
|
|
162
|
+
tiles = list(tiles)
|
|
162
163
|
|
|
163
164
|
zs = {t[0] for t in tiles}
|
|
164
165
|
for z in zs:
|
|
@@ -170,9 +171,6 @@ class GeoPakTile:
|
|
|
170
171
|
x = int(x)
|
|
171
172
|
y = int(y)
|
|
172
173
|
|
|
173
|
-
if self.has_tile(z, x, y):
|
|
174
|
-
continue
|
|
175
|
-
|
|
176
174
|
batch.append((z, x, y, sqlite3.Binary(tile_bytes)))
|
|
177
175
|
if len(batch) >= batch_size:
|
|
178
176
|
self._cursor.executemany(insert_sql, batch)
|
|
@@ -68,6 +68,35 @@ class _Plan:
|
|
|
68
68
|
resampled: bool
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
def _connect_readonly(path: str | Path) -> sqlite3.Connection:
|
|
72
|
+
r"""Open a GeoPackage read-only, trying resolve() first, then absolute().
|
|
73
|
+
|
|
74
|
+
resolve() can fail on network drives: it may raise, or turn a mapped
|
|
75
|
+
drive letter into a UNC path (\\server\share) whose file:// URI SQLite
|
|
76
|
+
rejects. absolute() keeps the path as given, which often works instead.
|
|
77
|
+
"""
|
|
78
|
+
path = Path(path)
|
|
79
|
+
errors: list[str] = []
|
|
80
|
+
tried: set[str] = set()
|
|
81
|
+
for method in (Path.resolve, Path.absolute):
|
|
82
|
+
con = None
|
|
83
|
+
try:
|
|
84
|
+
uri = method(path).as_uri() + "?mode=ro"
|
|
85
|
+
if uri in tried:
|
|
86
|
+
continue
|
|
87
|
+
tried.add(uri)
|
|
88
|
+
con = sqlite3.connect(uri, uri=True)
|
|
89
|
+
con.execute("PRAGMA schema_version") # make SQLite actually open the file
|
|
90
|
+
return con
|
|
91
|
+
except (OSError, RuntimeError, ValueError, sqlite3.Error) as exc:
|
|
92
|
+
if con is not None:
|
|
93
|
+
con.close()
|
|
94
|
+
errors.append(f"{method.__name__}(): {exc}")
|
|
95
|
+
raise sqlite3.OperationalError(
|
|
96
|
+
f"Could not open GeoPackage {path}: " + "; ".join(errors)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
71
100
|
def _quote_ident(name: str) -> str:
|
|
72
101
|
return '"' + name.replace('"', '""') + '"'
|
|
73
102
|
|
|
@@ -84,7 +113,7 @@ def _as_int(value: float, what: str) -> int:
|
|
|
84
113
|
"Zoom levels may not be exact multiples of each other.",
|
|
85
114
|
stacklevel=3,
|
|
86
115
|
)
|
|
87
|
-
return round(value)
|
|
116
|
+
return int(round(value))
|
|
88
117
|
|
|
89
118
|
|
|
90
119
|
def gpkg_tiles_to_cog(
|
|
@@ -102,6 +131,7 @@ def gpkg_tiles_to_cog(
|
|
|
102
131
|
compress: str = "DEFLATE",
|
|
103
132
|
blocksize: int | None = None,
|
|
104
133
|
overwrite: bool = False,
|
|
134
|
+
tmp_dir: str | Path | None = None,
|
|
105
135
|
) -> Path:
|
|
106
136
|
"""Convert a GeoPackage tile layer to a COG, using zoom levels as overviews.
|
|
107
137
|
|
|
@@ -128,6 +158,9 @@ def gpkg_tiles_to_cog(
|
|
|
128
158
|
compress: COG compression, e.g. "DEFLATE", "ZSTD", "LZW", "WEBP", "JPEG".
|
|
129
159
|
blocksize: COG internal tile size. Defaults to the GeoPackage tile width.
|
|
130
160
|
overwrite: Replace an existing output file.
|
|
161
|
+
tmp_dir: Directory for the temporary per-zoom mosaics, which can need
|
|
162
|
+
about as much space as the output. Defaults to the output file's
|
|
163
|
+
directory, so they go on the same drive as the COG.
|
|
131
164
|
|
|
132
165
|
Returns:
|
|
133
166
|
Path to the written COG.
|
|
@@ -146,8 +179,7 @@ def gpkg_tiles_to_cog(
|
|
|
146
179
|
if cog_path.exists() and not overwrite:
|
|
147
180
|
raise FileExistsError(f"{cog_path} exists; pass overwrite=True")
|
|
148
181
|
|
|
149
|
-
|
|
150
|
-
con = sqlite3.connect(uri, uri=True)
|
|
182
|
+
con = _connect_readonly(gpkg_path)
|
|
151
183
|
try:
|
|
152
184
|
# --- Tile table, tile matrix set and CRS ----------------------------
|
|
153
185
|
tables = [
|
|
@@ -293,7 +325,9 @@ def gpkg_tiles_to_cog(
|
|
|
293
325
|
if blocksize is None:
|
|
294
326
|
blocksize = base.tile_width
|
|
295
327
|
|
|
296
|
-
|
|
328
|
+
if tmp_dir is None:
|
|
329
|
+
tmp_dir = cog_path.absolute().parent # absolute(): resolve() can fail on network drives
|
|
330
|
+
with tempfile.TemporaryDirectory(prefix="gpkg2cog_", dir=tmp_dir) as tmp:
|
|
297
331
|
tmpdir = Path(tmp)
|
|
298
332
|
level_files: list[Path] = []
|
|
299
333
|
|
|
@@ -505,4 +539,41 @@ def _pyramid_vrt(
|
|
|
505
539
|
]
|
|
506
540
|
parts.append(" </VRTRasterBand>")
|
|
507
541
|
parts.append("</VRTDataset>")
|
|
508
|
-
return "\n".join(parts)
|
|
542
|
+
return "\n".join(parts)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
if __name__ == "__main__":
|
|
546
|
+
import argparse
|
|
547
|
+
|
|
548
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
549
|
+
parser.add_argument("gpkg")
|
|
550
|
+
parser.add_argument("cog")
|
|
551
|
+
parser.add_argument("--table")
|
|
552
|
+
parser.add_argument(
|
|
553
|
+
"--bbox", type=float, nargs=4, metavar=("MINX", "MINY", "MAXX", "MAXY")
|
|
554
|
+
)
|
|
555
|
+
parser.add_argument("--bbox-crs")
|
|
556
|
+
parser.add_argument("--min-zoom", type=int)
|
|
557
|
+
parser.add_argument("--max-zoom", type=int)
|
|
558
|
+
parser.add_argument("--resample-unaligned", action="store_true")
|
|
559
|
+
parser.add_argument("--resampling", default="bilinear")
|
|
560
|
+
parser.add_argument("--bands", default="RGBA", choices=sorted(SUPPORTED_BANDS))
|
|
561
|
+
parser.add_argument("--compress", default="DEFLATE")
|
|
562
|
+
parser.add_argument("--overwrite", action="store_true")
|
|
563
|
+
parser.add_argument("--tmp-dir", help="directory for temporary files")
|
|
564
|
+
args = parser.parse_args()
|
|
565
|
+
gpkg_tiles_to_cog(
|
|
566
|
+
args.gpkg,
|
|
567
|
+
args.cog,
|
|
568
|
+
args.table,
|
|
569
|
+
bbox=args.bbox,
|
|
570
|
+
bbox_crs=args.bbox_crs,
|
|
571
|
+
min_zoom=args.min_zoom,
|
|
572
|
+
max_zoom=args.max_zoom,
|
|
573
|
+
resample_unaligned=args.resample_unaligned,
|
|
574
|
+
resampling=args.resampling,
|
|
575
|
+
bands=args.bands,
|
|
576
|
+
compress=args.compress,
|
|
577
|
+
overwrite=args.overwrite,
|
|
578
|
+
tmp_dir=args.tmp_dir,
|
|
579
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|