|
| 1 | +# Geoapify CLI Reverse Geocoder |
| 2 | + |
| 3 | +A fast and flexible Node.js CLI tool for **batch reverse geocoding** geographic coordinates from CSV files using the [Geoapify Reverse Geocoding API](https://www.geoapify.com/reverse-geocoding-api/). |
| 4 | + |
| 5 | +> 🧰 Use as a command-line utility or import as a Node.js module. |
| 6 | +
|
| 7 | +## 📚 Table of Contents |
| 8 | + |
| 9 | +* [🚀 Features](#features) |
| 10 | +* [📦 Installation & Dependencies](#installation--dependencies) |
| 11 | +* [📁 Usage](#usage) |
| 12 | +* [📄 CSV Format](#csv-format) |
| 13 | +* [📝 Output Formats](#output-formats) |
| 14 | +* [⚠️ Error Handling & Fallbacks](#error-handling--fallbacks) |
| 15 | +* [📊 Sample Logs](#sample-logs) |
| 16 | +* [🔐 API Access Requirements](#api-access-requirements) |
| 17 | + |
| 18 | +## Features |
| 19 | + |
| 20 | +* Read coordinates from CSV files with customizable column mapping (`lat`, `lon`, etc.) |
| 21 | +* Built-in rate limiting using `@geoapify/request-rate-limiter` (default: 5 requests/second) |
| 22 | +* Automatically retries failed requests with configurable retry limits |
| 23 | +* Supports multiple output formats: JSON, NDJSON, or console |
| 24 | +* Preserves the original row order in the output |
| 25 | +* Skips invalid or empty coordinates with clear warning logs |
| 26 | +* Modular architecture with reusable utility modules |
| 27 | +* Can be used via CLI or as a Node.js module |
| 28 | + |
| 29 | +## Installation & Dependencies |
| 30 | + |
| 31 | +### Requirements |
| 32 | + |
| 33 | +* **Node.js v18+** — Required for native `fetch()` support |
| 34 | +* **Geoapify API Key** — Get one at [https://www.geoapify.com](https://www.geoapify.com) |
| 35 | + |
| 36 | +### Dependencies |
| 37 | + |
| 38 | +This project uses the following NPM packages (already listed in `package.json`): |
| 39 | + |
| 40 | +* [`@geoapify/request-rate-limiter`](https://www.npmjs.com/package/@geoapify/request-rate-limiter) – Handles request throttling |
| 41 | +* [`commander`](https://www.npmjs.com/package/commander) – CLI argument parsing |
| 42 | +* [`csv-parser`](https://www.npmjs.com/package/csv-parser) – Efficient CSV file parsing |
| 43 | + |
| 44 | +### Install |
| 45 | + |
| 46 | +In the project root, run: |
| 47 | + |
| 48 | +```bash |
| 49 | +npm install |
| 50 | +``` |
| 51 | + |
| 52 | +## Usage |
| 53 | + |
| 54 | +You can use the reverse geocoder as a **command-line tool** or as a **Node.js module** in your own scripts. |
| 55 | + |
| 56 | +### Command Line Usage |
| 57 | + |
| 58 | +#### Basic Example |
| 59 | + |
| 60 | +```bash |
| 61 | +node reverse-geocoder.js -k YOUR_API_KEY -i coordinates.csv |
| 62 | +``` |
| 63 | + |
| 64 | +#### Full Example with All Options |
| 65 | + |
| 66 | +```bash |
| 67 | +node reverse-geocoder.js \ |
| 68 | + --api-key YOUR_API_KEY \ |
| 69 | + --input coordinates.csv \ |
| 70 | + --output results.ndjson \ |
| 71 | + --rate-limit 5 \ |
| 72 | + --output-format ndjson \ |
| 73 | + --max-retries 3 |
| 74 | +``` |
| 75 | + |
| 76 | +### CLI Options |
| 77 | + |
| 78 | +| Option | Short | Description | Default | |
| 79 | +| ----------------- | ----- | ------------------------------------------------ | ------- | |
| 80 | +| `--api-key` | `-k` | **(Required)** Your Geoapify API key | – | |
| 81 | +| `--input` | `-i` | **(Required)** Path to CSV file with coordinates | – | |
| 82 | +| `--output` | `-o` | Path to write reverse geocoding results | – | |
| 83 | +| `--rate-limit` | `-r` | Requests per second (RPS) | 5 | |
| 84 | +| `--output-format` | `-f` | Output format: `json`, `ndjson`, `console` | ndjson | |
| 85 | +| `--no-retry` | | Disable automatic retries for failed requests | false | |
| 86 | +| `--max-retries` | | Max retry attempts per failed request | 3 | |
| 87 | + |
| 88 | +### Programmatic Usage |
| 89 | + |
| 90 | +You can also import the geocoder into your own Node.js script: |
| 91 | + |
| 92 | +```js |
| 93 | +const { reverseGeocodeCoordinates } = require('./reverse-geocoder'); |
| 94 | + |
| 95 | +async function main() { |
| 96 | + const results = await reverseGeocodeCoordinates({ |
| 97 | + inputFile: 'coordinates.csv', |
| 98 | + outputFile: 'results.json', |
| 99 | + apiKey: 'YOUR_API_KEY', |
| 100 | + rateLimit: 5, |
| 101 | + outputFormat: 'json', |
| 102 | + retryFailedRequests: true, |
| 103 | + maxRetries: 3 |
| 104 | + }); |
| 105 | + |
| 106 | + console.log(`Geocoded ${results.length} coordinates`); |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +## CSV Format |
| 111 | + |
| 112 | +The input CSV file should contain latitude and longitude values for each point to be reverse geocoded. |
| 113 | + |
| 114 | +### Default Column Names |
| 115 | + |
| 116 | +By default, the script expects the following column headers: |
| 117 | + |
| 118 | +```csv |
| 119 | +lat,lon |
| 120 | +40.7128,-74.0060 |
| 121 | +34.0522,-118.2437 |
| 122 | +41.8781,-87.6298 |
| 123 | +``` |
| 124 | + |
| 125 | +### Custom Column Mapping |
| 126 | + |
| 127 | +If your file uses different column names (e.g., `latitude`, `longitude`), you can change the mapping in the configuration section of the script or modify the CLI version to support dynamic mapping. |
| 128 | + |
| 129 | +Only valid numeric values within the coordinate range will be processed: |
| 130 | + |
| 131 | +* Latitude: -90 to 90 |
| 132 | +* Longitude: -180 to 180 |
| 133 | + |
| 134 | +Invalid or empty coordinates will be skipped and logged with a warning. |
| 135 | + |
| 136 | +## Output Formats |
| 137 | + |
| 138 | +You can choose between three output formats using the `--output-format` (`-f`) option: `json`, `ndjson`, or `console`. |
| 139 | + |
| 140 | +### JSON Format |
| 141 | + |
| 142 | +A single JSON array containing all results. Recommended for smaller datasets or downstream systems expecting full structured data. |
| 143 | + |
| 144 | +```json |
| 145 | +[ |
| 146 | + { |
| 147 | + "success": true, |
| 148 | + "originalCoordinates": [40.7128, -74.0060], |
| 149 | + "result": { |
| 150 | + "formatted": "New York, NY 10007, United States of America", |
| 151 | + "city": "New York", |
| 152 | + "state": "New York", |
| 153 | + "country": "United States", |
| 154 | + "postcode": "10007", |
| 155 | + "lat": 40.7128, |
| 156 | + "lon": -74.0060 |
| 157 | + }, |
| 158 | + "originalRow": { "lat": "40.7128", "lon": "-74.0060" } |
| 159 | + } |
| 160 | +] |
| 161 | +``` |
| 162 | + |
| 163 | +### NDJSON Format (default) |
| 164 | + |
| 165 | +Each result is written on a separate line as a standalone JSON object. Ideal for streaming or processing large datasets. |
| 166 | + |
| 167 | +``` |
| 168 | +{"success":true,"originalCoordinates":[40.7128,-74.0060],"result":{...}} |
| 169 | +{"success":false,"originalCoordinates":[null,null],"error":"Invalid coordinates"} |
| 170 | +``` |
| 171 | + |
| 172 | +### Console Output |
| 173 | + |
| 174 | +If no `--output` path is specified and `--output-format` is set to `console`, results are printed to stdout. |
| 175 | + |
| 176 | +## Error Handling & Fallbacks |
| 177 | + |
| 178 | +The script includes robust mechanisms to handle common issues and ensure consistent results, even when individual lookups fail. |
| 179 | + |
| 180 | +### What’s Handled: |
| 181 | + |
| 182 | +* **Empty or invalid coordinates** |
| 183 | + Rows with missing or invalid `lat`/`lon` values are skipped and logged with a warning. |
| 184 | + |
| 185 | +* **Network or API errors** |
| 186 | + Failures due to timeouts, DNS errors, or API availability are retried (up to `--max-retries` times). |
| 187 | + |
| 188 | +* **No results from API** |
| 189 | + If no address is found for given coordinates, the result is logged with a descriptive error and included in the output with `"success": false`. |
| 190 | + |
| 191 | +* **Rate limiting (HTTP 429)** |
| 192 | + Automatically throttled using the `@geoapify/request-rate-limiter` to avoid exceeding Geoapify API limits. |
| 193 | + |
| 194 | +* **File read/write errors** |
| 195 | + Clear fatal errors are logged if input/output files are inaccessible or invalid. |
| 196 | + |
| 197 | +### Output Consistency: |
| 198 | + |
| 199 | +* Output files (JSON or NDJSON) always match the order of the original input rows. |
| 200 | +* Even failed entries preserve `originalRow` and `originalCoordinates` with an `error` field describing the issue. |
| 201 | +* Retry attempts are logged individually and limited by `--max-retries`. |
| 202 | + |
| 203 | +## Sample Logs |
| 204 | + |
| 205 | +The script logs each step with timestamps and clear status indicators. Below is an example log from a typical reverse geocoding run: |
| 206 | + |
| 207 | +``` |
| 208 | +[2025-07-14T12:00:00.000Z] INFO: Read 3 rows from coordinates.csv |
| 209 | +[2025-07-14T12:00:00.050Z] INFO: Starting reverse geocoding of 3 coordinates at 5 requests per second |
| 210 | +[2025-07-14T12:00:00.200Z] INFO: ✓ Geocoded: 40.7128,-74.0060 |
| 211 | +[2025-07-14T12:00:00.400Z] INFO: ✓ Geocoded: 34.0522,-118.2437 |
| 212 | +[2025-07-14T12:00:00.600Z] WARNING: ✗ Failed: Invalid coordinates - No results found |
| 213 | +[2025-07-14T12:00:00.700Z] INFO: Retry attempt 1/3: 1 failed requests... |
| 214 | +[2025-07-14T12:00:01.000Z] WARNING: ✗ Retry failed: Invalid coordinates - No results found |
| 215 | +[2025-07-14T12:00:01.100Z] INFO: Geocoding complete: 2 successful, 1 failed |
| 216 | +[2025-07-14T12:00:01.200Z] INFO: Results written to results.ndjson |
| 217 | +``` |
| 218 | + |
| 219 | +### Log Levels |
| 220 | + |
| 221 | +* `INFO` — Standard messages: file read, request success, summary |
| 222 | +* `WARNING` — Non-fatal issues: failed lookups, skipped rows, retries |
| 223 | +* `ERROR` — Critical problems that stop the script: file not found, invalid input, API key missing |
| 224 | + |
| 225 | +## API Access Requirements |
| 226 | + |
| 227 | +This tool uses the [Geoapify Reverse Geocoding API](https://apidocs.geoapify.com/docs/geocoding/reverse-geocoding/). |
| 228 | + |
| 229 | +### Requirements |
| 230 | + |
| 231 | +* **Geoapify API Key** — Required for authenticating all requests |
| 232 | + Get your free API key at [geoapify.com](https://www.geoapify.com/get-started/) |
| 233 | + |
| 234 | +* **Rate Limits**: |
| 235 | + |
| 236 | + * Free plan allows up to **5 requests per second** (soft limit) |
| 237 | + * Daily quota: up to **3,000 requests per day** on the Free plan |
| 238 | + * Use the `--rate-limit` option to adjust request frequency |
| 239 | + |
| 240 | +* **Node.js version** |
| 241 | + Requires **Node.js 18+** for native `fetch()` support |
| 242 | + |
| 243 | +### Best Practices |
| 244 | + |
| 245 | +* Avoid hardcoding your API key in public code repositories |
| 246 | +* Use environment variables or CLI flags (`--api-key`) to pass your key securely |
| 247 | +* If processing large datasets, consider: |
| 248 | + * Increasing `--rate-limit` with a paid plan |
| 249 | + * Splitting the workload into multiple batches |
| 250 | + * Scheduling jobs to avoid hitting daily limits |
| 251 | + |
0 commit comments