{"id":29322404,"url":"https://github.com/iwatkot/pydtmdl","last_synced_at":"2026-04-12T21:03:45.867Z","repository":{"id":301810320,"uuid":"1010379774","full_name":"iwatkot/pydtmdl","owner":"iwatkot","description":"Python library to download DTM data from various providers.","archived":false,"fork":false,"pushed_at":"2025-12-30T13:37:02.000Z","size":101,"stargazers_count":4,"open_issues_count":0,"forks_count":6,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-03T06:38:32.309Z","etag":null,"topics":["dem","digital-terrain-model","download-dem","download-dtm","download-elevation-data","dtm","elevation","elevation-data","maps4fs"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iwatkot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-29T00:11:52.000Z","updated_at":"2025-12-30T13:37:05.000Z","dependencies_parsed_at":"2026-03-12T05:38:40.525Z","dependency_job_id":null,"html_url":"https://github.com/iwatkot/pydtmdl","commit_stats":null,"previous_names":["iwatkot/pydtmdl"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/iwatkot/pydtmdl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iwatkot%2Fpydtmdl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iwatkot%2Fpydtmdl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iwatkot%2Fpydtmdl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iwatkot%2Fpydtmdl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iwatkot","download_url":"https://codeload.github.com/iwatkot/pydtmdl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iwatkot%2Fpydtmdl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31526666,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T16:28:08.000Z","status":"ssl_error","status_checked_at":"2026-04-07T16:28:06.951Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dem","digital-terrain-model","download-dem","download-dtm","download-elevation-data","dtm","elevation","elevation-data","maps4fs"],"created_at":"2025-07-07T14:13:05.632Z","updated_at":"2026-04-12T21:03:45.839Z","avatar_url":"https://github.com/iwatkot.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\" markdown\u003e\n\n[![Maps4FS](https://img.shields.io/badge/maps4fs-gray?style=for-the-badge)](https://github.com/iwatkot/maps4fs)\n[![Maps4FS](https://img.shields.io/badge/atlasfs-green?style=for-the-badge)](https://github.com/iwatkot/atlasfs)\n[![Maps4FS](https://img.shields.io/badge/maps4fs-windows-purple?style=for-the-badge)](https://github.com/iwatkot/maps4fswindows)\n[![PYDTMDL](https://img.shields.io/badge/pydtmdl-blue?style=for-the-badge)](https://github.com/iwatkot/pydtmdl)\n[![PYGDMDL](https://img.shields.io/badge/pygmdl-teal?style=for-the-badge)](https://github.com/iwatkot/pygmdl)  \n[![Maps4FS API](https://img.shields.io/badge/maps4fs-api-green?style=for-the-badge)](https://github.com/iwatkot/maps4fsapi)\n[![Maps4FS UI](https://img.shields.io/badge/maps4fs-ui-blue?style=for-the-badge)](https://github.com/iwatkot/maps4fsui)\n[![Maps4FS Data](https://img.shields.io/badge/maps4fs-data-orange?style=for-the-badge)](https://github.com/iwatkot/maps4fsdata)\n[![Maps4FS ChromaDocs](https://img.shields.io/badge/maps4fs-chromadocs-orange?style=for-the-badge)](https://github.com/iwatkot/maps4fschromadocs)  \n[![Maps4FS Upgrader](https://img.shields.io/badge/maps4fs-upgrader-yellow?style=for-the-badge)](https://github.com/iwatkot/maps4fsupgrader)\n[![Maps4FS Stats](https://img.shields.io/badge/maps4fs-stats-red?style=for-the-badge)](https://github.com/iwatkot/maps4fsstats)\n[![Maps4FS Bot](https://img.shields.io/badge/maps4fs-bot-teal?style=for-the-badge)](https://github.com/iwatkot/maps4fsbot)\n[![Maps4FS Locale](https://img.shields.io/badge/maps4fs-locale-purple?style=for-the-badge)](https://github.com/iwatkot/maps4fslocale)\n\n\u003c/div\u003e\n\n\u003cdiv align=\"center\" markdown\u003e\n\u003cimg src=\"https://github.com/iwatkot/pydtmdl/releases/download/0.0.1/pydtmdl.png\"\u003e\n\u003c/a\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"#quick-start\"\u003eQuick Start\u003c/a\u003e •\n    \u003ca href=\"#overview\"\u003eOverview\u003c/a\u003e • \n    \u003ca href=\"#what-is-a-dtm\"\u003eWhat is a DTM?\u003c/a\u003e •\n    \u003ca href=\"#supported-dtm-providers\"\u003eSupported DTM providers\u003c/a\u003e •\n    \u003ca href=\"#licensing-and-data-usage\"\u003eLicensing and Data Usage\u003c/a\u003e •\n    \u003ca href=\"#contributing\"\u003eContributing\u003c/a\u003e\n\u003c/p\u003e\n\n[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/iwatkot/pydtmdl)](https://github.com/iwatkot/pydtmdl/releases)\n[![PyPI - Version](https://img.shields.io/pypi/v/pydtmdl)](https://pypi.org/project/pydtmdl)\n[![GitHub issues](https://img.shields.io/github/issues/iwatkot/pydtmdl)](https://github.com/iwatkot/pydtmdl/issues)\n[![PyPI - Downloads](https://img.shields.io/pypi/dm/pydtmdl)](https://pypi.org/project/pydtmdl)\n[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)\n[![Build Status](https://github.com/iwatkot/pydtmdl/actions/workflows/checks.yml/badge.svg)](https://github.com/iwatkot/pydtmdl/actions)\n[![GitHub Repo stars](https://img.shields.io/github/stars/iwatkot/pydtmdl)](https://github.com/iwatkot/pydtmdl/stargazers)\u003cbr\u003e\n\n\u003c/div\u003e\n\n## Quick Start\nInstall the package using pip:\n\n```bash\npip install pydtmdl\n```\n\nThen, you can use it in your Python scripts:\n\n```python\nfrom pydtmdl import DTMProvider\n\n# Prepare coordinates of the center point and size (in meters).\ncoords = 45.285460396731374, 20.237491178279715  # Center point of the region of interest.\nsize = 2048  # Size of the region in meters (2048x2048 m).\n\n# Get the best provider for the given coordinates.\nbest_provider = DTMProvider.get_best(coords)\nprint(f\"Best provider: {best_provider.name()}\")\n\n# Create an instance of the provider with the given coordinates and size.\nprovider = best_provider(coords, size=size)\n\n# Get the DTM data as a numpy array.\nnp_data = provider.image\n\n# Production-oriented rectangular extraction with structured metadata.\nresult = DTMProvider.extract_area(\n    center=coords,\n    width_m=4096,\n    height_m=2048,\n    rotation_deg=30,\n    provider_code=best_provider.code(),\n    fallback_provider_code=\"srtm30\",\n)\nprint(result.metadata.model_dump())\n```\n\nPositive `rotation_deg` values rotate the requested ROI clockwise around its center.\n\nThe legacy square API remains available. If you need machine-readable metadata, cache identifiers,\nfallback reporting, or rotated ROI extraction, use `get_result()` on a provider instance or the\nhigh-level `DTMProvider.extract_area(...)` helper.\n\n## Overview\n`pydtmdl` is a Python library designed to provide access to Digital Terrain Models (DTMs) from various providers. It supports multiple providers, each with its own resolution and data format. The library allows users to easily retrieve DTM data for specific geographic coordinates and sizes.  \n\nNote, that some providers may require additional settings, such as API keys or selection of a specific dataset. More details can be found in the demo script and in the providers source code.  \n\nThe library will retrieve all the required tiles, merge them, window them and return the result as a numpy array. If additional processing is required, such as normalization or resizing, it can be done using OpenCV or other libraries (example code is provided in the demo script).\n\nFor application backends and worker pipelines, the library also exposes structured result metadata\nincluding cache information, output file paths, provider fallback details, and machine-readable\nerror types.\n\n## What is a DTM?\n\nFirst of all, it's important to understand what a DTM is.  \nThere are two main types of elevation models: Digital Terrain Model (DTM) and Digital Surface Model (DSM). The DTM represents the bare earth surface without any objects like buildings or vegetation. The DSM, on the other hand, represents the earth's surface including all objects.\n\n![DTM vs DSM, example 1](https://github.com/user-attachments/assets/0bf691f3-6737-4663-86ca-c17a525ecda4)\n\n![DTM vs DSM, example 2](https://github.com/user-attachments/assets/3ae1082c-1117-4073-ac98-a2bc1e22c1ba)\n\nThe library is focused on the DTM data and the DSM sources are not supported and will not be added in the future. The reason for this is that the DTM data is more suitable for terrain generation in games, as it provides a more accurate representation of the earth's surface without any objects.\n\n## Supported DTM providers\n\n![coverage map](https://github.com/user-attachments/assets/be5c5ce1-7318-4352-97eb-efba7ec587cd)\n\nIn addition to SRTM 30m, which provides global coverage, the map above highlights all countries and/or regions where higher resolution coverage is provided by one of the DTM providers.\n\n| Provider Name                      | Resolution   | Developer                                   |\n| ---------------------------------- | ------------ | ------------------------------------------- |\n| 🌎 SRTM30                          | 30 meters    | [iwatkot](https://github.com/iwatkot)       |\n| 🌎 ArcticDEM                       | 2 meters     | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🌎 REMA Antarctica                 | 2 meters     | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇺🇸 USGS                            | 1-90 meters  | [ZenJakey](https://github.com/ZenJakey)     |\n| 🏴󠁧󠁢󠁥󠁮󠁧󠁿 England                         | 1 meter      | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🏴󠁧󠁢󠁳󠁣󠁴󠁿 Scotland                        | 0.25-1 meter | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🏴󠁧󠁢󠁷󠁬󠁳󠁿󠁧󠁢󠁷󠁬󠁳󠁿 Wales                           | 1 meter      | [garnwenshared](https://github.com/garnshared) |\n| 🇩🇪 Hessen, Germany                 | 1 meter      | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇩🇪 Niedersachsen, Germany          | 1 meter      | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇩🇪 Bayern, Germany                 | 1 meter      | [H4rdB4se](https://github.com/H4rdB4se)     |\n| 🇩🇪 Nordrhein-Westfalen, Germany    | 1 meter      | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇩🇪 Mecklenburg-Vorpommern, Germany | 1-25 meter   | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇩🇪 Baden-Württemberg, Germany      | 1 meter      | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇩🇪 Sachsen-Anhalt, Germany         | 1 meter      | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇩🇪 Thüringen, Germany              | 1 meter      | [H4rdB4se](https://github.com/H4rdB4se)     |\n| 🇨🇦 Canada                          | 1 meter      | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇧🇪 Flanders, Belgium               | 1 meter      | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇫🇷 France                          | 1 meter      | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇮🇹 Italy                           | 10 meter     | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇳🇴 Norway                          | 1 meter      | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇪🇸 Spain                           | 5 meter      | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇫🇮 Finland                         | 2 meter      | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇩🇰 Denmark                         | 0.4 meter    | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇸🇪 Sweden                          | 1 meter      | [GustavPersson](https://github.com/GustavPersson) |\n| 🇨🇭 Switzerland                     | 0.5-2 meter  | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇨🇿 Czech Republic                  | 5 meter      | [kbrandwijk](https://github.com/kbrandwijk) |\n| 🇨🇿 Czech Republic                  | 2 meter      | [VidhosticeSDK](https://github.com/VidhosticeSDK) |\n| 🇱🇹 Lithuania                       | 1 meter      | [Tox3](https://github.com/Tox3) |\n\n## Licensing and Data Usage\n\n⚠️ **Important**: This library provides access to DTM data from various third-party providers. **PyDTMDL does not own, host, or distribute this data**. Each DTM provider has its own licensing terms and usage restrictions.\n\n**It is your responsibility to:**\n- Check the license and terms of use for each DTM provider you use\n- Ensure compliance with the provider's licensing requirements\n- Verify that your use case (commercial, research, personal, etc.) is permitted\n- Provide proper attribution when required by the data provider\n- Respect any usage limits or restrictions imposed by the provider\n\nThe library itself is licensed under the GNU Affero General Public License v3 (AGPL-3.0), but this **does not grant you any rights** to the DTM data accessed through the library. The data licenses are separate and must be obtained directly from the respective providers.\n\n**By using this library, you acknowledge that you are solely responsible for ensuring compliance with all applicable data licenses and terms of use.**\n\nFor information about data licensing from specific providers, please refer to their official websites and documentation.\n\n## Contributing\n\nContributions are welcome! If you want to add your own DTM provider, please follow this guide.  \nYou can also contribute by reporting issues, suggesting improvements, or helping with documentation.\n\n### What a DTM provider does?\n\nA DTM provider is a service that provides elevation data for a given location. While there's plenty of DTM providers available, only the ones that provide a free and open access to their data can be used in this library.  \n\nThe base provider class, [DTMProvider](pydtmdl/base/dtm.py), handles all the heavy lifting: merging tiles, reprojecting to EPSG:4326, and extracting the region of interest. Individual DTM providers only need to implement the `download_tiles()` method to fetch the raw data.\n\nThe process for generating elevation data is:\n\n1. Download all DTM tiles for the desired map area (implemented by each DTM provider)\n2. Merge multiple tiles if necessary (handled by base class)\n3. Reproject to EPSG:4326 if needed (handled by base class)\n4. Extract the map area from the tile (handled by base class)\n\n### Provider Types\n\nThere are three main approaches to implementing a DTM provider:\n\n1. **Custom implementation** - Inherit from `DTMProvider` directly for unique APIs\n2. **WCS-based** - Inherit from both `WCSProvider` and `DTMProvider` for OGC WCS services\n3. **WMS-based** - Inherit from both `WMSProvider` and `DTMProvider` for OGC WMS services\n\n### Example 1: Custom Provider (SRTM)\n\n➡️ Existing providers can be found in the [pydtmdl/providers/](pydtmdl/providers/) folder.\n\n**Step 1:** Define the provider metadata.\n\n```python\nfrom pydtmdl.base.dtm import DTMProvider\n\nclass SRTM30Provider(DTMProvider):\n    \"\"\"Provider of Shuttle Radar Topography Mission (SRTM) 30m data.\"\"\"\n\n    _code = \"srtm30\"\n    _name = \"SRTM 30 m\"\n    _region = \"Global\"\n    _icon = \"🌎\"\n    _resolution = 30.0\n    _url = \"https://elevation-tiles-prod.s3.amazonaws.com/skadi/{latitude_band}/{tile_name}.hgt.gz\"\n```\n\n**Step 2 (optional):** Define custom settings if your provider requires authentication or configuration.\n\n```python\nfrom pydtmdl.base.dtm import DTMProviderSettings\n\nclass SwedenProviderSettings(DTMProviderSettings):\n    \"\"\"Settings for the Sweden provider.\"\"\"\n    username: str = \"\"\n    password: str = \"\"\n\nclass SwedenProvider(DTMProvider):\n    _settings = SwedenProviderSettings\n    _instructions = \"ℹ️ This provider requires username and password...\"\n```\n\nAccess settings in your code:\n```python\nusername = self.user_settings.username\npassword = self.user_settings.password\n```\n\n**Step 3:** Implement the `download_tiles()` method.\n\n```python\ndef download_tiles(self) -\u003e list[str]:\n    \"\"\"Download SRTM tiles.\"\"\"\n    north, south, east, west = self.get_bbox()\n    \n    tiles = []\n    for pair in [(north, east), (south, west), (south, east), (north, west)]:\n        tile_parameters = self.get_tile_parameters(*pair)\n        tile_name = tile_parameters[\"tile_name\"]\n        tile_path = os.path.join(self.hgt_directory, f\"{tile_name}.hgt\")\n        \n        if not os.path.isfile(tile_path):\n            # Download and decompress tile\n            compressed_path = os.path.join(self.gz_directory, f\"{tile_name}.hgt.gz\")\n            if not self.download_tile(compressed_path, **tile_parameters):\n                raise FileNotFoundError(f\"Tile {tile_name} not found.\")\n            # ... decompress logic ...\n        \n        tiles.append(tile_path)\n    return list(set(tiles))\n```\n\n### Example 2: WCS Provider (England)\n\nFor WCS-based providers, inherit from both `WCSProvider` and `DTMProvider`. The base class handles coordinate transformation automatically using the `transform_bbox()` utility from [pydtmdl/utils.py](pydtmdl/utils.py).\n\n```python\nfrom pydtmdl.base.dtm import DTMProvider\nfrom pydtmdl.base.wcs import WCSProvider\n\nclass England1MProvider(WCSProvider, DTMProvider):\n    \"\"\"Provider of England data.\"\"\"\n    \n    _code = \"england1m\"\n    _name = \"England DGM1\"\n    _region = \"UK\"\n    _icon = \"🏴󠁧󠁢󠁥󠁮󠁧󠁿\"\n    _resolution = 1.0\n    _extents = [(55.877, 49.851, 2.084, -7.105)]\n    \n    _url = \"https://environment.data.gov.uk/geoservices/datasets/.../wcs\"\n    _wcs_version = \"2.0.1\"\n    _source_crs = \"EPSG:27700\"  # British National Grid\n    _tile_size = 1000\n    \n    def get_wcs_parameters(self, tile):\n        return {\n            \"identifier\": [\"dataset_id\"],\n            \"subsets\": [(\"E\", str(tile[1]), str(tile[3])), (\"N\", str(tile[0]), str(tile[2]))],\n            \"format\": \"tiff\",\n        }\n```\n\nThe `WCSProvider` base class automatically:\n- Transforms your bbox from EPSG:4326 to `_source_crs` \n- Tiles the area based on `_tile_size`\n- Downloads each tile using your `get_wcs_parameters()` method\n- Returns the list of downloaded files\n\n### Example 3: Custom API with Authentication (Sweden)\n\nFor providers with custom APIs requiring authentication:\n\n```python\nclass SwedenProvider(DTMProvider):\n    _settings = SwedenProviderSettings  # Define custom settings\n    \n    def download_tiles(self):\n        \"\"\"Download tiles from STAC API.\"\"\"\n        download_urls = self.get_download_urls()\n        return self.download_tif_files(download_urls, self.shared_tiff_path)\n    \n    def _get_auth_headers(self) -\u003e dict[str, str]:\n        \"\"\"Generate auth headers from user settings.\"\"\"\n        credentials = f\"{self.user_settings.username}:{self.user_settings.password}\"\n        encoded = base64.b64encode(credentials.encode()).decode()\n        return {\"Authorization\": f\"Basic {encoded}\"}\n    \n    def get_download_urls(self) -\u003e list[str]:\n        \"\"\"Query STAC API for tile URLs within bbox.\"\"\"\n        bbox = self.get_bbox()\n        # ... API logic using self._get_auth_headers() ...\n        return urls\n```\n\n### Unified Download Methods\n\n⚠️ **Important**: All DTM providers **must use** the unified download methods provided by the base `DTMProvider` class. Do not implement your own download logic.\n\nThe base class provides three unified download methods with built-in retry logic, error handling, and progress tracking:\n\n#### 1. `download_tif_files(urls, output_path, headers=None, timeout=60)`\nFor downloading multiple GeoTIFF files from a list of URLs.\n\n```python\ndef download_tiles(self) -\u003e list[str]:\n    download_urls = self.get_download_urls()\n    return self.download_tif_files(download_urls, self.shared_tiff_path)\n```\n\n**Use for**: Simple URL-based downloads (SRTM, Scotland, Wales, etc.)\n\n#### 2. `download_file(url, output_path, headers=None, method='GET', data=None, timeout=60)`\nFor downloading a single file with flexible HTTP methods (GET/POST).\n\n```python\ndef download_tiles(self) -\u003e list[str]:\n    url = self.formatted_url(**tile_parameters)\n    output_path = os.path.join(self._tile_directory, \"tile.tif\")\n    self.download_file(url, output_path, method=\"POST\", data=polygon_data)\n    return [output_path]\n```\n\n**Use for**: Single file downloads or POST requests (Bavaria, custom APIs)\n\n#### 3. `download_tiles_with_fetcher(tiles, output_path, data_fetcher, file_name_generator=None)`\nFor OGC Web Services (WCS/WMS) or any service requiring custom data fetching.\n\n```python\ndef download_tiles(self) -\u003e list[str]:\n    bbox = self.get_bbox()\n    bbox = transform_bbox(bbox, self._source_crs)\n    tiles = tile_bbox(bbox, self._tile_size)\n    \n    wcs = WebCoverageService(self._url, version=self._wcs_version)\n    \n    def wcs_fetcher(tile):\n        return wcs.getCoverage(**self.get_wcs_parameters(tile))\n    \n    return self.download_tiles_with_fetcher(tiles, self.shared_tiff_path, wcs_fetcher)\n```\n\n**Use for**: WCS/WMS providers (automatically handled by `WCSProvider`/`WMSProvider` base classes)\n\n#### Why Use Unified Methods?\n\n- ✅ **Built-in retry logic** - Automatic retries with configurable attempts and delays\n- ✅ **Error handling** - Consistent error messages and logging\n- ✅ **Progress tracking** - Visual progress bars with tqdm\n- ✅ **File caching** - Skips already downloaded files\n- ✅ **Timeout support** - Configurable timeouts for slow connections\n- ✅ **Authentication** - Support for custom headers (API keys, Basic Auth, etc.)\n\n**If you need functionality not provided by these methods, extend the base class methods rather than implementing your own. This ensures all providers benefit from improvements and bug fixes.**\n\n### Other Helper Methods\n\nThe base `DTMProvider` class also provides:\n\n- `get_bbox()` - Returns `(north, south, east, west)` in EPSG:4326\n- `unzip_img_from_tif(file_name, output_path)` - Extracts .img or .tif from zip files\n- `_tile_directory` - Temporary directory for your provider's tiles\n- `_max_retries` - Number of retry attempts (default: 5)\n- `_retry_pause` - Seconds between retries (default: 5)\n\nFor coordinate transformation, use the utility function from [pydtmdl/utils.py](pydtmdl/utils.py):\n```python\nfrom pydtmdl.utils import transform_bbox\nbbox = self.get_bbox()\ntransformed_bbox = transform_bbox(bbox, \"EPSG:25832\")\n```\n\n### Requirements\n\n- Providers must be free and openly accessible\n- If authentication is required, users must provide their own credentials via settings\n- The `download_tiles()` method must return a list of file paths to GeoTIFF files\n- All tiles should contain valid elevation data readable by `rasterio`\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiwatkot%2Fpydtmdl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiwatkot%2Fpydtmdl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiwatkot%2Fpydtmdl/lists"}