mapcachetools 1.0.7 → 2.1.0
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.
- package/LICENSE +16 -0
- package/README.md +95 -19
- package/bin/mapcache_download.js +325 -0
- package/package.json +41 -13
- package/REALKEY.txt +0 -1
- package/images/Screenshot from 2022-09-03 02-19-12.png +0 -0
- package/mapcache_download.js +0 -379
- package/site/css/leaflet.css +0 -640
- package/site/css/styles.css +0 -8
- package/site/index.html +0 -43
- package/site/js/jquery/jquery-3.4.1.min.js +0 -2
- package/site/js/jquery/jquery-ui.min.css +0 -7
- package/site/js/jquery/jquery-ui.min.js +0 -13
- package/site/js/leaflet-src.esm.js +0 -13986
- package/site/js/leaflet-src.esm.js.map +0 -1
- package/site/js/leaflet-src.js +0 -14080
- package/site/js/leaflet-src.js.map +0 -1
- package/site/js/leaflet.js +0 -5
- package/site/js/leaflet.js.map +0 -1
- package/site/js/leaflet.rotatedMarker.js +0 -57
package/LICENSE
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Mohammad Said Hefny
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
|
package/README.md
CHANGED
|
@@ -1,46 +1,122 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Map Cache Tools
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
You can use these images with a library like [Leafet](https://leafletjs.com/ "Leafet") to make your own map server easily without the need to download a local OpenStreetMap server.
|
|
3
|
+
A robust Node.js tool to download map tiles (satellite or terrain RGB) for a specified geographic area and zoom range, storing them as PNG images in a folder. Ideal for offline map applications, such as integration with [Leaflet](https://leafletjs.com/) or 3D rendering projects like WebGL-based terrain visualization. Features include parallel downloads, retry handling, progress tracking, and support for multiple map providers.
|
|
5
4
|
|
|
6
|
-
[](https://github.com/HefnySco/mapcache/blob/master/images/Screenshot%20from%202022-09-03%2002-19-12.png?raw=true "Downloaded Images of Map")
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
Watch a demo video showcasing the tool:
|
|
7
|
+
[](https://youtu.be/aSb6xNOQqok)
|
|
9
8
|
|
|
10
|
-
Download area from 58.5500977,-4.5231876 to 58.5501,-4.5231976 with maximum zoom in of 20 and minimum zoom in of 0 i.e. whole world zoom, and store images in folder called image_folder
|
|
11
9
|
|
|
10
|
+
## Features
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
- **Supported Providers**: OpenStreetMap (OSM), Mapbox Satellite, and Mapbox Terrain RGB (for elevation data).
|
|
13
|
+
- **Unified PNG Format**: All tiles are saved as PNG, with Mapbox Satellite tiles converted from JPG for consistency.
|
|
14
|
+
- **Parallel Downloads**: Configurable concurrency to optimize speed while respecting provider rate limits.
|
|
15
|
+
- **Retry Mechanism**: Automatically retries failed downloads (default: 3 attempts) with exponential backoff.
|
|
16
|
+
- **Progress Tracking**: Displays real-time progress with percentage completion.
|
|
17
|
+
- **File Management**: Skips existing files unless `--force` is used, with automatic output folder creation.
|
|
18
|
+
- **Flexible Zoom**: Download tiles for a range of zoom levels (min `--zout` to max `--zin`).
|
|
19
|
+
- **Integration**: Outputs tiles compatible with 3D mapping projects (e.g., Three.js terrain rendering) and Leaflet-based map servers.
|
|
14
20
|
|
|
15
|
-
##
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
1. Clone the repository:
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/HefnySco/mapcache.git
|
|
26
|
+
cd mapcache
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
2. Install dependencies:
|
|
30
|
+
```bash
|
|
31
|
+
npm install
|
|
32
|
+
```
|
|
33
|
+
Note: The `sharp` library is required for JPG-to-PNG conversion. Ensure system dependencies are installed:
|
|
34
|
+
- Ubuntu/Debian: `sudo apt-get install libvips-dev`
|
|
35
|
+
- macOS: `brew install libvips`
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
Download map tiles for a geographic area defined by two coordinates (`lat1,lng1` to `lat2,lng2`), a zoom range, and an output folder. Run the script with:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
node downloader.js --lat1=<lat> --lng1=<lng> --lat2=<lat> --lng2=<lng> --zin=<max_zoom> --folder=<output_folder> [options]
|
|
43
|
+
```
|
|
16
44
|
|
|
17
|
-
|
|
45
|
+
### Example: Download OSM Tiles
|
|
46
|
+
Download OpenStreetMap tiles for a small area with zoom levels 0 to 14:
|
|
47
|
+
```bash
|
|
48
|
+
node downloader.js --lat1=40.7128 --lng1=-74.0060 --lat2=40.7228 --lng2=-73.9960 --zin=14 --zout=0 --folder=./site/cachedMaps
|
|
49
|
+
```
|
|
18
50
|
|
|
51
|
+
### Example: Download Mapbox Satellite Tiles
|
|
52
|
+
Download Mapbox satellite tiles (saved as PNG, requires a Mapbox access token):
|
|
19
53
|
```bash
|
|
20
|
-
|
|
54
|
+
node downloader.js --lat1=40.7128 --lng1=-74.0060 --lat2=40.7228 --lng2=-73.9960 --zin=14 --folder=./site/cachedMaps --provider=1 --token=pk.eyJ1IjoibWhlZm55IiwiYSI6ImNrZW84Nm9rYTA2ZWgycm9mdmNscmFxYzcifQ.c-zxDjXCthXmRsErPzKhbQ
|
|
55
|
+
```
|
|
21
56
|
|
|
57
|
+
### Example: Download Mapbox Terrain RGB Tiles
|
|
58
|
+
Download Mapbox terrain RGB tiles for elevation data:
|
|
59
|
+
```bash
|
|
60
|
+
node downloader.js --lat1=40.7128 --lng1=-74.0060 --lat2=40.7228 --lng2=-73.9960 --zin=14 --folder=./site/cachedMaps --provider=2 --token=pk.eyJ1IjoibWhlZm55IiwiYSI6ImNrZW84Nm9rYTA2ZWgycm9mdmNscmFxYzcifQ.c-zxDjXCthXmRsErPzKhbQ
|
|
22
61
|
```
|
|
23
62
|
|
|
24
|
-
|
|
63
|
+
### Options
|
|
64
|
+
- `--lat1, --lng1`: Starting coordinates (required).
|
|
65
|
+
- `--lat2, --lng2`: Ending coordinates (required).
|
|
66
|
+
- `--zin`: Maximum zoom level (required, e.g., 18).
|
|
67
|
+
- `--zout`: Minimum zoom level (default: 0).
|
|
68
|
+
- `--folder`: Output folder for tiles (required, e.g., `./site/cachedMaps`).
|
|
69
|
+
- `--provider`: Map provider (0=OSM, 1=Mapbox Satellite, 2=Mapbox Terrain RGB; default: 0).
|
|
70
|
+
- `--token`: Mapbox access token (required for provider 1 or 2).
|
|
71
|
+
- `--concurrency`: Number of parallel downloads (default: 5).
|
|
72
|
+
- `--retries`: Retry attempts for failed downloads (default: 3).
|
|
73
|
+
- `--force`: Redownload existing files (default: false).
|
|
74
|
+
- `--help, -h`: Display help message.
|
|
75
|
+
- `--version, -v`: Display script version.
|
|
76
|
+
|
|
77
|
+
## Serving Tiles Locally
|
|
78
|
+
|
|
79
|
+
Use a local HTTP server to serve downloaded tiles for testing with Leaflet or other map libraries:
|
|
25
80
|
|
|
26
81
|
```bash
|
|
27
|
-
|
|
82
|
+
cd site
|
|
83
|
+
npm install -g http-server
|
|
84
|
+
http-server
|
|
28
85
|
```
|
|
29
86
|
|
|
30
|
-
|
|
87
|
+
Access the tiles at `http://localhost:8080/cachedMaps`. The folder `./site/cachedMaps` contains tiles named as:
|
|
88
|
+
- OSM: `osm_<x>_<y>_<zoom>.png`
|
|
89
|
+
- Mapbox Satellite: `sat_<x>_<y>_<zoom>.png`
|
|
90
|
+
- Mapbox Terrain RGB: `terrain_<x>_<y>_<zoom>.png`
|
|
91
|
+
|
|
92
|
+
## Integration with 3D Mapping Projects
|
|
31
93
|
|
|
32
|
-
|
|
94
|
+
The downloaded tiles are saved as PNG, compatible with 3D mapping applications like Three.js for terrain rendering. For example, Mapbox terrain RGB tiles can be used to generate heightmaps:
|
|
33
95
|
|
|
96
|
+
```javascript
|
|
97
|
+
const terrainData = await textureLoader.loadAsync('terrain_<x>_<y>_<zoom>.png');
|
|
98
|
+
const data = getPixelData(terrainData); // Extract RGB values
|
|
99
|
+
const height = -10000 + ((r * 65536 + g * 256 + b) * 0.1); // Mapbox terrain RGB formula
|
|
100
|
+
```
|
|
34
101
|
|
|
102
|
+
Ensure the output folder matches your project's tile cache directory, and use the same zoom levels (e.g., 14) for consistency with your rendering logic.
|
|
103
|
+
|
|
104
|
+
## Map Providers
|
|
35
105
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
106
|
+
- **OpenStreetMap (OSM)**: Free, no token required. Tiles are natively PNG. [Website](https://www.openstreetmap.org/).
|
|
107
|
+
- **Mapbox Satellite**: High-quality satellite imagery, converted from JPG to PNG for consistency. Requires a Mapbox access token. [Website](https://api.mapbox.com/).
|
|
108
|
+
- **Mapbox Terrain RGB**: Elevation data encoded in RGB PNG tiles. Requires a Mapbox access token. [Website](https://docs.mapbox.com/data/tilesets/reference/mapbox-terrain-rgb-v1/).
|
|
39
109
|
|
|
110
|
+
Obtain a Mapbox token from [Mapbox Account](https://account.mapbox.com/).
|
|
40
111
|
|
|
112
|
+
## Notes
|
|
41
113
|
|
|
114
|
+
- **Rate Limits**: Mapbox enforces rate limits (e.g., 750 requests/min for satellite, 200 for terrain RGB on free tiers). Adjust `--concurrency` to avoid being throttled.
|
|
115
|
+
- **File Overwrites**: Use `--force=true` to redownload tiles if they are outdated or corrupted.
|
|
116
|
+
- **File Size**: Mapbox Satellite tiles are converted to PNG, which may increase file size compared to JPG. Consider disk space for large areas.
|
|
117
|
+
- **Tile Validation**: For critical applications, validate PNG tiles post-download to ensure integrity (e.g., using `sharp` metadata).
|
|
42
118
|
|
|
43
|
-
### Disclaimer
|
|
44
|
-
Please make sure to review any terms and conditions of map providers you want to use. Author assumes no liability for any incidental, consequential or other liability from the use of this product.
|
|
45
119
|
|
|
120
|
+
## Disclaimer
|
|
46
121
|
|
|
122
|
+
Please review the terms and conditions of your chosen map provider (e.g., [OpenStreetMap](https://www.openstreetmap.org/copyright), [Mapbox](https://www.mapbox.com/tos)). The author assumes no liability for any incidental, consequential, or other damages arising from the use of this tool.
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Enhanced Map Tile Downloader with Terrain Support and Single Extension
|
|
3
|
+
- Downloads map tiles (satellite or terrain RGB) for a given geographic bounding box and zoom range.
|
|
4
|
+
- Saves all tiles as PNG for consistency, converting Mapbox Satellite tiles from JPG to PNG.
|
|
5
|
+
- Supports OpenStreetMap (default) and Mapbox (with token) for satellite and terrain RGB tiles.
|
|
6
|
+
- Parallel downloads with configurable concurrency to respect rate limits.
|
|
7
|
+
- Retry mechanism for failed downloads.
|
|
8
|
+
- Creates output folder if it doesn't exist.
|
|
9
|
+
- Progress reporting with total count and completion percentage.
|
|
10
|
+
- Better input validation and default values.
|
|
11
|
+
- Force redownload option.
|
|
12
|
+
- Improved error handling and logging.
|
|
13
|
+
|
|
14
|
+
Usage: node downloader.js --lat1=<lat> --lng1=<lng> --lat2=<lat> --lng2=<lng> --zin=<max_zoom> [options]
|
|
15
|
+
|
|
16
|
+
Run with --help for full options.
|
|
17
|
+
*/
|
|
18
|
+
"use strict";
|
|
19
|
+
|
|
20
|
+
const fs = require("fs");
|
|
21
|
+
const path = require("path");
|
|
22
|
+
const axios = require("axios");
|
|
23
|
+
const sharp = require("sharp"); // Added for JPG-to-PNG conversion
|
|
24
|
+
const v_pjson = require("../package.json"); // Assuming this exists; otherwise, hardcode version.
|
|
25
|
+
const c_args = require("../helpers/hlp_args.js"); // Assuming this provides getArgs().
|
|
26
|
+
const c_colors = require("../helpers/js_colors.js").Colors; // Assuming this provides color codes.
|
|
27
|
+
|
|
28
|
+
const EARTH_RADIUS = 6378137;
|
|
29
|
+
const MAX_LATITUDE = 85.0511287798;
|
|
30
|
+
const R_MINOR = 6356752.314245179; // Not used, but kept for completeness.
|
|
31
|
+
|
|
32
|
+
// Default values
|
|
33
|
+
const DEFAULT_PROVIDER = 0; // 0: OSM, 1: Mapbox Satellite, 2: Mapbox Terrain RGB
|
|
34
|
+
const DEFAULT_CONCURRENCY = 5; // Safe default to avoid rate limits
|
|
35
|
+
const DEFAULT_RETRIES = 3;
|
|
36
|
+
const DEFAULT_ZOUT = 0;
|
|
37
|
+
const DEFAULT_FORCE = false;
|
|
38
|
+
|
|
39
|
+
// Global variables
|
|
40
|
+
let map_provider = DEFAULT_PROVIDER;
|
|
41
|
+
let TOKEN = ""; // Required for Mapbox
|
|
42
|
+
let myArgs = c_args.getArgs();
|
|
43
|
+
let totalTiles = 0;
|
|
44
|
+
let downloadedTiles = 0;
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* Projection functions (unchanged)
|
|
48
|
+
*/
|
|
49
|
+
function project(p_lat, p_lng) {
|
|
50
|
+
const d = Math.PI / 180,
|
|
51
|
+
max = MAX_LATITUDE,
|
|
52
|
+
lat = Math.max(Math.min(max, p_lat), -max),
|
|
53
|
+
sin = Math.sin(lat * d);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
x: EARTH_RADIUS * p_lng * d,
|
|
57
|
+
y: (EARTH_RADIUS * Math.log((1 + sin) / (1 - sin))) / 2,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function unproject(point) {
|
|
62
|
+
const d = 180 / Math.PI;
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
lat: (2 * Math.atan(Math.exp(point.y / EARTH_RADIUS)) - Math.PI / 2) * d,
|
|
66
|
+
lng: (point.x * d) / EARTH_RADIUS,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function zoomScale(zoom) {
|
|
71
|
+
return 256 * Math.pow(2, zoom);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function transform(point, scale) {
|
|
75
|
+
scale = scale || 1;
|
|
76
|
+
point.x = scale * (2.495320233665337e-8 * point.x + 0.5);
|
|
77
|
+
point.y = scale * (-2.495320233665337e-8 * point.y + 0.5);
|
|
78
|
+
return point;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function fn_convertFromLngLatToPoints(lat1, lng1, lat2, lng2, zoom) {
|
|
82
|
+
// Normalize bounds
|
|
83
|
+
if (lng1 > lng2) [lng1, lng2] = [lng2, lng1];
|
|
84
|
+
if (lat1 > lat2) [lat1, lat2] = [lat2, lat1];
|
|
85
|
+
|
|
86
|
+
let point1 = project(lat1, lng1);
|
|
87
|
+
let point2 = project(lat2, lng2);
|
|
88
|
+
|
|
89
|
+
let scaledZoom = zoomScale(zoom);
|
|
90
|
+
point1 = transform(point1, scaledZoom);
|
|
91
|
+
point2 = transform(point2, scaledZoom);
|
|
92
|
+
|
|
93
|
+
// Floor to tile indices
|
|
94
|
+
point1.x = Math.floor(point1.x / 256);
|
|
95
|
+
point1.y = Math.floor(point1.y / 256);
|
|
96
|
+
point2.x = Math.floor(point2.x / 256);
|
|
97
|
+
point2.y = Math.floor(point2.y / 256);
|
|
98
|
+
|
|
99
|
+
// Ensure point1 is top-left, point2 bottom-right
|
|
100
|
+
if (point1.y > point2.y) [point1.y, point2.y] = [point2.y, point1.y];
|
|
101
|
+
|
|
102
|
+
return [point1, point2];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* ============================================================
|
|
106
|
+
Enhanced Download Function with Retry
|
|
107
|
+
============================================================ */
|
|
108
|
+
|
|
109
|
+
const download_image = async (url, image_path, isMapboxSatellite = false, retries = myArgs.retries || DEFAULT_RETRIES) => {
|
|
110
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
111
|
+
try {
|
|
112
|
+
const response = await axios({
|
|
113
|
+
url,
|
|
114
|
+
responseType: "arraybuffer", // Changed to arraybuffer for sharp compatibility
|
|
115
|
+
timeout: 10000, // 10s timeout
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Convert to PNG if Mapbox Satellite (JPG), otherwise save directly
|
|
119
|
+
const buffer = isMapboxSatellite
|
|
120
|
+
? await sharp(response.data).png().toBuffer()
|
|
121
|
+
: response.data;
|
|
122
|
+
|
|
123
|
+
await fs.promises.writeFile(image_path, buffer);
|
|
124
|
+
return; // Success
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.log(c_colors.BError + `Attempt ${attempt} failed for ${url}: ${error.message}` + c_colors.Reset);
|
|
127
|
+
if (attempt === retries) {
|
|
128
|
+
throw error; // Final failure
|
|
129
|
+
}
|
|
130
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); // Exponential backoff
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/* ============================================================
|
|
136
|
+
Generate Tile URL and Filename
|
|
137
|
+
============================================================ */
|
|
138
|
+
|
|
139
|
+
function getTileUrlAndFilename(i, j, zoom) {
|
|
140
|
+
let url, typePrefix, isMapboxSatellite = false;
|
|
141
|
+
if (map_provider === 1) {
|
|
142
|
+
// Mapbox Satellite
|
|
143
|
+
url = `https://api.mapbox.com/styles/v1/mapbox/satellite-v9/tiles/${zoom}/${i}/${j}?access_token=${TOKEN}`;
|
|
144
|
+
typePrefix = "sat";
|
|
145
|
+
isMapboxSatellite = true;
|
|
146
|
+
} else if (map_provider === 2) {
|
|
147
|
+
// Mapbox Terrain RGB
|
|
148
|
+
url = `https://api.mapbox.com/v4/mapbox.terrain-rgb/${zoom}/${i}/${j}.png?access_token=${TOKEN}`;
|
|
149
|
+
typePrefix = "terrain";
|
|
150
|
+
} else {
|
|
151
|
+
// OpenStreetMap
|
|
152
|
+
url = `https://tile.openstreetmap.org/${zoom}/${i}/${j}.png`;
|
|
153
|
+
typePrefix = "osm";
|
|
154
|
+
}
|
|
155
|
+
const filename = `${typePrefix}_${i}_${j}_${zoom}.png`; // Always PNG
|
|
156
|
+
return { url, filename, isMapboxSatellite };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* ============================================================
|
|
160
|
+
Download Tiles for a Zoom Level (Parallel with Concurrency)
|
|
161
|
+
============================================================ */
|
|
162
|
+
|
|
163
|
+
async function fn_download_tiles_for_zoom(point1, point2, zoom, folder, force) {
|
|
164
|
+
const tiles = [];
|
|
165
|
+
for (let i = point1.x; i <= point2.x; i++) {
|
|
166
|
+
for (let j = point1.y; j <= point2.y; j++) {
|
|
167
|
+
tiles.push({ i, j });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
totalTiles += tiles.length;
|
|
172
|
+
console.log(c_colors.BSuccess + `Queueing ${tiles.length} tiles for zoom ${zoom}...` + c_colors.Reset);
|
|
173
|
+
|
|
174
|
+
// Process in batches to limit concurrency
|
|
175
|
+
const concurrency = myArgs.concurrency || DEFAULT_CONCURRENCY;
|
|
176
|
+
for (let batchStart = 0; batchStart < tiles.length; batchStart += concurrency) {
|
|
177
|
+
const batch = tiles.slice(batchStart, batchStart + concurrency);
|
|
178
|
+
await Promise.all(
|
|
179
|
+
batch.map(async ({ i, j }) => {
|
|
180
|
+
const { url, filename, isMapboxSatellite } = getTileUrlAndFilename(i, j, zoom);
|
|
181
|
+
const image_path = path.join(folder, filename);
|
|
182
|
+
|
|
183
|
+
if (!force && fs.existsSync(image_path)) {
|
|
184
|
+
console.log(c_colors.FgCyan + `Skipping existing: ${filename}` + c_colors.Reset);
|
|
185
|
+
downloadedTiles++;
|
|
186
|
+
updateProgress();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
await download_image(url, image_path, isMapboxSatellite);
|
|
192
|
+
console.log(c_colors.BSuccess + `Downloaded: ${filename}` + c_colors.Reset);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error(c_colors.BError + `Failed to download ${filename} after retries.` + c_colors.Reset);
|
|
195
|
+
} finally {
|
|
196
|
+
downloadedTiles++;
|
|
197
|
+
updateProgress();
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* ============================================================
|
|
205
|
+
Progress Updater
|
|
206
|
+
============================================================ */
|
|
207
|
+
|
|
208
|
+
function updateProgress() {
|
|
209
|
+
const progress = ((downloadedTiles / totalTiles) * 100).toFixed(2);
|
|
210
|
+
console.log(c_colors.FgYellow + `Progress: ${downloadedTiles}/${totalTiles} (${progress}%)` + c_colors.Reset);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* ============================================================
|
|
214
|
+
Input Validation and Argument Handling
|
|
215
|
+
============================================================ */
|
|
216
|
+
|
|
217
|
+
function fn_handle_arguments() {
|
|
218
|
+
myArgs = c_args.getArgs();
|
|
219
|
+
let error = false;
|
|
220
|
+
|
|
221
|
+
if (myArgs.help || myArgs.h || myArgs.version || myArgs.v) {
|
|
222
|
+
console.log(
|
|
223
|
+
c_colors.BSuccess +
|
|
224
|
+
"MAP Tile Downloader v" +
|
|
225
|
+
c_colors.FgYellow +
|
|
226
|
+
(v_pjson.version || "1.0.0") +
|
|
227
|
+
c_colors.Reset
|
|
228
|
+
);
|
|
229
|
+
console.log(c_colors.BSuccess + "Usage: node downloader.js --lat1=<lat> --lng1=<lng> --lat2=<lat> --lng2=<lng> --zin=<max_zoom> [options]" + c_colors.Reset);
|
|
230
|
+
console.log(c_colors.BSuccess + "--lat1, --lng1: Start coordinates (required)" + c_colors.Reset);
|
|
231
|
+
console.log(c_colors.BSuccess + "--lat2, --lng2: End coordinates (required)" + c_colors.Reset);
|
|
232
|
+
console.log(c_colors.BSuccess + "--zout: Min zoom (default: 0)" + c_colors.Reset);
|
|
233
|
+
console.log(c_colors.BSuccess + "--zin: Max zoom (required, e.g., 18)" + c_colors.Reset);
|
|
234
|
+
console.log(c_colors.BSuccess + "--folder: Output folder (required, e.g., ./out)" + c_colors.Reset);
|
|
235
|
+
console.log(c_colors.BSuccess + "--provider: 0=OSM (default), 1=Mapbox Satellite, 2=Mapbox Terrain RGB" + c_colors.Reset);
|
|
236
|
+
console.log(c_colors.BSuccess + "--token: Mapbox access token (required if provider=1 or 2)" + c_colors.Reset);
|
|
237
|
+
console.log(c_colors.BSuccess + "--concurrency: Parallel downloads (default: 5)" + c_colors.Reset);
|
|
238
|
+
console.log(c_colors.BSuccess + "--retries: Retry attempts per tile (default: 3)" + c_colors.Reset);
|
|
239
|
+
console.log(c_colors.BSuccess + "--force: Redownload existing files (default: false)" + c_colors.Reset);
|
|
240
|
+
process.exit(0);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Required args
|
|
244
|
+
["lat1", "lng1", "lat2", "lng2", "zin", "folder"].forEach(arg => {
|
|
245
|
+
if (!myArgs[arg]) {
|
|
246
|
+
error = true;
|
|
247
|
+
console.log(c_colors.BError + `Missing required argument: --${arg}` + c_colors.Reset);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Validate lat/lng
|
|
252
|
+
["lat1", "lat2"].forEach(arg => {
|
|
253
|
+
if (myArgs[arg] && (myArgs[arg] < -90 || myArgs[arg] > 90)) {
|
|
254
|
+
error = true;
|
|
255
|
+
console.log(c_colors.BError + `Invalid latitude ${arg}: ${myArgs[arg]} (must be -90 to 90)` + c_colors.Reset);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
["lng1", "lng2"].forEach(arg => {
|
|
259
|
+
if (myArgs[arg] && (myArgs[arg] < -180 || myArgs[arg] > 180)) {
|
|
260
|
+
error = true;
|
|
261
|
+
console.log(c_colors.BError + `Invalid longitude ${arg}: ${myArgs[arg]} (must be -180 to 180)` + c_colors.Reset);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Provider and token
|
|
266
|
+
map_provider = myArgs.provider ? parseInt(myArgs.provider) : DEFAULT_PROVIDER;
|
|
267
|
+
if (map_provider === 1 || map_provider === 2) {
|
|
268
|
+
TOKEN = myArgs.token;
|
|
269
|
+
if (!TOKEN) {
|
|
270
|
+
error = true;
|
|
271
|
+
console.log(c_colors.BError + "Missing --token for Mapbox provider (satellite or terrain)." + c_colors.Reset);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Zooms
|
|
276
|
+
myArgs.zout = myArgs.zout ? parseInt(myArgs.zout) : DEFAULT_ZOUT;
|
|
277
|
+
myArgs.zin = parseInt(myArgs.zin);
|
|
278
|
+
if (myArgs.zout > myArgs.zin || myArgs.zout < 0 || myArgs.zin > 22) {
|
|
279
|
+
error = true;
|
|
280
|
+
console.log(c_colors.BError + "Invalid zoom range: zout <= zin, 0-22." + c_colors.Reset);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Force
|
|
284
|
+
myArgs.force = myArgs.force === "true" || myArgs.force === true ? true : DEFAULT_FORCE;
|
|
285
|
+
|
|
286
|
+
if (error) process.exit(1);
|
|
287
|
+
|
|
288
|
+
// Create folder if needed
|
|
289
|
+
const folder = myArgs.folder;
|
|
290
|
+
if (!fs.existsSync(folder)) {
|
|
291
|
+
fs.mkdirSync(folder, { recursive: true });
|
|
292
|
+
console.log(c_colors.BSuccess + `Created output folder: ${folder}` + c_colors.Reset);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* ============================================================
|
|
297
|
+
Main Execution
|
|
298
|
+
============================================================ */
|
|
299
|
+
|
|
300
|
+
async function main() {
|
|
301
|
+
fn_handle_arguments();
|
|
302
|
+
const folder = myArgs.folder;
|
|
303
|
+
const force = myArgs.force;
|
|
304
|
+
|
|
305
|
+
// Calculate total tiles across all zooms for progress
|
|
306
|
+
for (let zoom = myArgs.zout; zoom <= myArgs.zin; zoom++) {
|
|
307
|
+
const [point1, point2] = fn_convertFromLngLatToPoints(myArgs.lat1, myArgs.lng1, myArgs.lat2, myArgs.lng2, zoom);
|
|
308
|
+
totalTiles += (point2.x - point1.x + 1) * (point2.y - point1.y + 1);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
console.log(c_colors.BSuccess + `Total tiles to process: ${totalTiles}` + c_colors.Reset);
|
|
312
|
+
|
|
313
|
+
// Download per zoom
|
|
314
|
+
for (let zoom = myArgs.zout; zoom <= myArgs.zin; zoom++) {
|
|
315
|
+
const [point1, point2] = fn_convertFromLngLatToPoints(myArgs.lat1, myArgs.lng1, myArgs.lat2, myArgs.lng2, zoom);
|
|
316
|
+
await fn_download_tiles_for_zoom(point1, point2, zoom, folder, force);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
console.log(c_colors.BSuccess + "Download complete!" + c_colors.Reset);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
main().catch(error => {
|
|
323
|
+
console.error(c_colors.BError + `Unexpected error: ${error.message}` + c_colors.Reset);
|
|
324
|
+
process.exit(1);
|
|
325
|
+
});
|
package/package.json
CHANGED
|
@@ -1,24 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mapcachetools",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "
|
|
6
|
-
"scripts": {
|
|
7
|
-
"start": "node ./bin/downloadmaps.js"
|
|
8
|
-
},
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "A Node.js tool to download and cache map tiles (OSM, Mapbox Satellite, Mapbox Terrain RGB) as PNG images for offline use in mapping applications. Use at your own risk and comply with provider terms.",
|
|
5
|
+
"main": "bin/mapcache_download.js",
|
|
9
6
|
"bin": {
|
|
10
|
-
"
|
|
7
|
+
"mapcache-download": "./bin/mapcache_download.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node ./bin/mapcache_download.js",
|
|
11
|
+
"test": "jest",
|
|
12
|
+
"prepublishOnly": "npm test"
|
|
11
13
|
},
|
|
12
14
|
"keywords": [
|
|
13
15
|
"map",
|
|
14
|
-
"
|
|
15
|
-
"cache"
|
|
16
|
+
"tile",
|
|
17
|
+
"cache",
|
|
18
|
+
"openstreetmap",
|
|
19
|
+
"mapbox",
|
|
20
|
+
"terrain",
|
|
21
|
+
"offline",
|
|
22
|
+
"leaflet",
|
|
23
|
+
"threejs"
|
|
16
24
|
],
|
|
17
25
|
"homepage": "https://github.com/HefnySco/mapcache",
|
|
18
|
-
"
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/HefnySco/mapcache.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/HefnySco/mapcache/issues"
|
|
32
|
+
},
|
|
33
|
+
"author": "Mohammad Said Hefny <rcmobilestuff@gmail.com> (http://www.andruav.com)",
|
|
19
34
|
"license": "ISC",
|
|
20
35
|
"dependencies": {
|
|
21
|
-
"axios": "^
|
|
22
|
-
"
|
|
23
|
-
}
|
|
36
|
+
"axios": "^1.7.7",
|
|
37
|
+
"sharp": "^0.34.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"eslint": "^8.57.0",
|
|
41
|
+
"jest": "^30.2.0"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=14.0.0"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"bin",
|
|
48
|
+
"helpers",
|
|
49
|
+
"README.md",
|
|
50
|
+
"LICENSE"
|
|
51
|
+
]
|
|
24
52
|
}
|
package/REALKEY.txt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const TOKEN = "pk.eyJ1IjoiaHNhYWQiLCJhIjoiY2tqZnIwNXRuMndvdTJ4cnV0ODQ4djZ3NiJ9.LKojA3YMrG34L93jRThEGQ"
|
|
Binary file
|