ne-loader 0.2.1__py3-none-any.whl

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.
ne_loader/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # Natural Earth Loader package
ne_loader/cacher.py ADDED
@@ -0,0 +1,35 @@
1
+ """Resolve the cache directory used for downloaded Natural Earth data."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Optional, Union
6
+
7
+ from platformdirs import user_cache_dir
8
+
9
+
10
+ PathLike = Union[str, Path]
11
+
12
+
13
+ def get_cache_dir(path_override: Optional[PathLike] = None) -> Path:
14
+ """Return the directory used to cache Natural Earth downloads.
15
+
16
+ Cache directory in order of precedence:
17
+ 1. Explicit function argument 'path_override'
18
+ 2. ``NATURAL_EARTH_CACHE_DIR`` environment variable.
19
+ 3. Platform-specific user cache directory.
20
+
21
+ Args:
22
+ path_override: Optional cache directory
23
+ Returns:
24
+ A ``pathlib.Path`` pointing to the cache directory.
25
+
26
+ """
27
+ if path_override:
28
+ return Path(path_override).expanduser()
29
+
30
+ env_path: Optional[str] = os.getenv("NATURAL_EARTH_CACHE_DIR")
31
+ if env_path:
32
+ return Path(env_path).expanduser()
33
+
34
+ default_cache_dir: str = user_cache_dir(appname="ne-loader", appauthor="erictunn")
35
+ return Path(default_cache_dir)
ne_loader/cli.py ADDED
@@ -0,0 +1,33 @@
1
+ """Provides basic command line functionality."""
2
+
3
+ import argparse
4
+
5
+ import geopandas as gpd
6
+
7
+ from . import map_loader
8
+
9
+
10
+ def main() -> None:
11
+ """Run the Natural Earth loader command-line interface."""
12
+ parser: argparse.ArgumentParser = argparse.ArgumentParser(
13
+ description="Download and load Natural Earth data."
14
+ )
15
+ parser.add_argument("category", choices=["cultural", "physical"], help="Data category")
16
+ parser.add_argument("name", help="Dataset name (e.g., admin_0_countries)")
17
+ parser.add_argument("--res", default="10m", help="Resolution (default: 10m)")
18
+ parser.add_argument("--out", help="Output file to save as GeoJSON (optional)")
19
+ args: argparse.Namespace = parser.parse_args()
20
+
21
+ gdf: gpd.GeoDataFrame = map_loader.get_natural_earth(
22
+ args.category,
23
+ args.name,
24
+ args.res,
25
+ )
26
+ if args.out:
27
+ gdf.to_file(args.out, driver="GeoJSON")
28
+ print(f"Saved to {args.out}")
29
+ else:
30
+ print(gdf.head())
31
+
32
+ if __name__ == "__main__":
33
+ main()
@@ -0,0 +1,129 @@
1
+ """Handles downloading and fetching of NE data."""
2
+
3
+ import logging
4
+ import zipfile
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import geopandas as gpd
9
+ import requests
10
+
11
+ from .cacher import PathLike, get_cache_dir
12
+
13
+
14
+ def build_ne_filename(name: str, res: str = "10m", suffix: str = ".zip") -> str:
15
+ """Build a Natural Earth dataset filename."""
16
+ return f"ne_{res}_{name}{suffix}"
17
+
18
+
19
+ def build_ne_url(category: str, name: str, res: str = "10m") -> str:
20
+ """Build the download URL for a Natural Earth vector dataset."""
21
+ return (
22
+ f"https://naciscdn.org/naturalearth/{res}/{category}/"
23
+ f"{build_ne_filename(name, res)}"
24
+ )
25
+
26
+
27
+ def build_ne_zip_path(data_dir: PathLike, name: str, res: str = "10m") -> Path:
28
+ """Build the local cache path for a Natural Earth zip file."""
29
+ return Path(data_dir) / build_ne_filename(name, res)
30
+
31
+
32
+ def build_ne_extract_dir(data_dir: PathLike, name: str, res: str = "10m") -> Path:
33
+ """Build the local extraction directory for a Natural Earth dataset."""
34
+ return Path(data_dir) / build_ne_filename(name, res, suffix="")
35
+
36
+
37
+ def build_ne_shp_path(data_dir: PathLike, name: str, res: str = "10m") -> Path:
38
+ """Build the local shapefile path for an extracted Natural Earth dataset."""
39
+ extract_dir: Path = build_ne_extract_dir(data_dir, name, res)
40
+ return extract_dir / build_ne_filename(name, res, suffix=".shp")
41
+
42
+
43
+ def get_natural_earth(
44
+ category: str,
45
+ name: str,
46
+ res: str = "10m",
47
+ dir_override: Optional[PathLike] = None,
48
+ ) -> gpd.GeoDataFrame:
49
+ """Download, cache, and load a Natural Earth vector dataset.
50
+
51
+ Args:
52
+ category: Natural Earth data category, e.g. "cultural" or
53
+ "physical".
54
+ name: Dataset name without the ``ne_{res}_`` prefix, e.g.
55
+ "admin_0_countries".
56
+ res: Natural Earth resolution. "10m", "50m" and "110m" are accepted.
57
+ However, not all datasets will have all 3 resolutions available.
58
+ dir_override: Optional cache directory override. This takes precedence over the
59
+ ``NATURAL_EARTH_CACHE_DIR`` environment variable.
60
+
61
+ Returns:
62
+ A GeoPandas ``GeoDataFrame`` loaded from the cached shapefile.
63
+
64
+ """
65
+ logger: logging.Logger = logging.getLogger(__name__)
66
+
67
+ data_dir: Path = get_cache_dir(dir_override)
68
+ data_dir.mkdir(parents=True, exist_ok=True)
69
+
70
+ url: str = build_ne_url(category, name, res)
71
+ zip_path: Path = build_ne_zip_path(data_dir, name, res)
72
+ extract_dir: Path = build_ne_extract_dir(data_dir, name, res)
73
+ shp_file: Path = build_ne_shp_path(data_dir, name, res)
74
+
75
+ _download_ne_data(
76
+ url=url,
77
+ extract_dir=extract_dir,
78
+ name=name,
79
+ res=res,
80
+ zip_path=zip_path,
81
+ shp_file=shp_file,
82
+ logger=logger,
83
+ )
84
+
85
+ return gpd.read_file(shp_file)
86
+
87
+
88
+ def _download_ne_data(
89
+ url: str,
90
+ extract_dir: Path,
91
+ name: str,
92
+ res: str,
93
+ zip_path: Path,
94
+ shp_file: Path,
95
+ logger: logging.Logger,
96
+ ) -> None:
97
+ """Download and extract a dataset when the expected shapefile is absent."""
98
+ if shp_file.exists():
99
+ return
100
+
101
+ print(f"Downloading {name} ({res})...")
102
+
103
+ try:
104
+ response: requests.Response = requests.get(url, stream=True, timeout=10)
105
+ response.raise_for_status()
106
+
107
+ with zip_path.open("wb") as zip_file:
108
+ chunk: bytes
109
+ for chunk in response.iter_content(chunk_size=8192):
110
+ zip_file.write(chunk)
111
+
112
+ with zipfile.ZipFile(zip_path, "r") as zip_ref:
113
+ zip_ref.extractall(extract_dir)
114
+ zip_path.unlink()
115
+
116
+ except requests.exceptions.HTTPError as error:
117
+ logger.error(
118
+ "A HTTP error occurred while attempting to fetch data: %s\n"
119
+ "This may cause an error when attempting to load the data.",
120
+ error,
121
+ )
122
+ print(f"A HTTP error occurred while attempting to fetch data: {error}")
123
+ except requests.exceptions.RequestException as error:
124
+ logger.error(
125
+ "A request error occurred while attempting to fetch data: %s\n"
126
+ "This may cause an error when attempting to load the data.",
127
+ error,
128
+ )
129
+ print(f"A request error occurred while attempting to fetch data: {error}")
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.4
2
+ Name: ne-loader
3
+ Version: 0.2.1
4
+ Summary: A simple loader for Natural Earth map data.
5
+ Author-email: Eric Tunn <erictunn@icloud.com>
6
+ Project-URL: Homepage, https://github.com/erictunn/ne-loader
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: geopandas>=0.12
10
+ Requires-Dist: platformdirs>=4
11
+ Requires-Dist: requests>=2.25
12
+
13
+ # NE Loader
14
+
15
+ A simple, robust Python package to download and load Natural Earth map data using GeoPandas.
16
+
17
+ ## Features
18
+
19
+ - Download and cache Natural Earth datasets
20
+ - Load shapefiles as GeoDataFrames
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install ne-loader
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ```python
31
+ from ne_loader import map_loader
32
+ world = map_loader.get_natural_earth('cultural', 'admin_0_countries')
33
+ ```
34
+
35
+ ## CLI
36
+
37
+ ```bash
38
+ ne-loader --help
39
+ ```
40
+
41
+ To set an environment override for the NE data save path:
42
+
43
+ ```bash
44
+ export NATURAL_EARTH_CACHE_DIR="..."
45
+ ```
46
+
47
+ ## Tests
48
+
49
+ ```bash
50
+ python3 -m pytest tests/test_map_loader.py
51
+ ```
52
+
53
+ ## License
54
+
55
+ MIT
@@ -0,0 +1,9 @@
1
+ ne_loader/__init__.py,sha256=IyFStSqzurEnHsWbr6oaINAptDDkZdLzR6XC20o5ADc,31
2
+ ne_loader/cacher.py,sha256=UDslouaxYNIacEUb9Tk9cnFtxeeREBDrzvsSoMWT_AY,1015
3
+ ne_loader/cli.py,sha256=ViZzCzTPS9Lgf-N5zkc_ugVYOZyE-6xBtjf6Bd3zzMM,1021
4
+ ne_loader/map_loader.py,sha256=mW2PmRjNpAlfSbE5QJCt7lPCJ_5cXt0WlY6W6uJLu-s,4256
5
+ ne_loader-0.2.1.dist-info/METADATA,sha256=ycbwPkB4DMotS-SWyThAGym0a7ZjIK6HvYeUaNavewc,978
6
+ ne_loader-0.2.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
7
+ ne_loader-0.2.1.dist-info/entry_points.txt,sha256=lfC4UxVdYCouf-dRbZ8W3L6MMn_1LTDiedybSGDb9Ws,49
8
+ ne_loader-0.2.1.dist-info/top_level.txt,sha256=lnHLytilCj9bNbVduIlhzJunYyjDkUpl8aYutmjG--I,10
9
+ ne_loader-0.2.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ne-loader = ne_loader.cli:main
@@ -0,0 +1 @@
1
+ ne_loader