Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tiles
.DS_Store
42 changes: 35 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,27 @@ Generate offline map tiles for your Meshtastic T-Deck device! This tool download
pip install pillow requests
```

2. **Generate tiles for your city:**
2. **Get a free MapTiler API key** at https://maptiler.com/ and export the required environment variables:
```bash
export MAPTILER_KEY=your_key_here
# Required — sent in the HTTP User-Agent so tile providers can contact you
export TDECK_MAPS_CONTACT="you@example.com"
# Required — sent as HTTP Referer (any URL or project identifier works)
export TDECK_MAPS_REFERER="https://github.com/you/tdeck-maps"
```

Both `TDECK_MAPS_CONTACT` and `TDECK_MAPS_REFERER` must be set — every tile
provider policy (MapTiler, OSM, Nominatim) requires an identifiable User-Agent
with a way to reach you. `--contact` on the CLI overrides `TDECK_MAPS_CONTACT`.

3. **Generate tiles for your city:**
```bash
python meshtastic_tiles.py --city "San Francisco" --min-zoom 8 --max-zoom 12
```

3. **Copy the `tiles` folder to your T-Deck's SD card**
4. **Copy the `tiles` folder to your T-Deck's SD card**

4. **Configure Meshtastic to use offline tiles**
5. **Configure Meshtastic to use offline tiles**

## 📋 Features

Expand Down Expand Up @@ -122,10 +135,17 @@ python meshtastic_tiles.py --coords --north 40.8 --south 40.6 --east -74.0 --wes

## 🗺️ Map Sources

Choose different map types with the `--source` option:
All default sources go through **MapTiler** (free tier, requires an API key). OSM's own tile servers
explicitly prohibit bulk downloads — see [OSM Tile Usage Policy](https://operations.osmfoundation.org/policies/tiles/) —
so this tool no longer uses them by default. `osm-direct` is available as an opt-in and will likely get you
`403 Blocked`.

Pass the key via `--maptiler-key` or the `MAPTILER_KEY` env var.

```bash
# Standard street map (default)
export MAPTILER_KEY=your_key_here

# Standard street map (default, via MapTiler)
python meshtastic_tiles.py --city "Denver" --source osm

# Satellite imagery
Expand All @@ -134,10 +154,18 @@ python meshtastic_tiles.py --city "Denver" --source satellite
# Topographic/terrain
python meshtastic_tiles.py --city "Denver" --source terrain

# Cycling-focused
python meshtastic_tiles.py --city "Denver" --source cycle
# Raw OSM servers (policy-restricted — expect blocks):
python meshtastic_tiles.py --city "Denver" --source osm-direct --i-understand-osm-policy
```

### Why we require an API key now

The OSM tile policy forbids bulk downloading from `tile.openstreetmap.org`. Previous versions of this
script violated that policy and eventually got `403 Access Blocked`. Using MapTiler (or any other tile
provider that permits offline caching) is the correct fix. Every request also carries an identifying
`User-Agent` (contact email from `TDECK_MAPS_CONTACT` or `--contact`) and `Referer` (from
`TDECK_MAPS_REFERER`), as every major provider requires.

## 🔍 Zoom Levels Guide

Choose zoom levels based on your needs and storage capacity:
Expand Down
77 changes: 55 additions & 22 deletions maps.html
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,12 @@ <h1>🗺️ Meshtastic Tile Generator</h1>
<div id="map"></div>
<div class="map-controls">
<select id="mapStyle">
<option value="osm">OpenStreetMap</option>
<option value="osm">OpenStreetMap (MapTiler)</option>
<option value="satellite">Satellite</option>
<option value="terrain">Terrain</option>
</select>
<input type="text" id="previewKey" placeholder="MapTiler key (for preview)" style="margin-top:4px;width:100%;">
<small style="color:#ccc;">Preview needs a MapTiler key. Get one at maptiler.com.</small>
</div>
<div class="selection-info hidden" id="selectionInfo">
<div>Click and drag to select area</div>
Expand Down Expand Up @@ -282,22 +284,30 @@ <h3>🔍 Zoom Levels</h3>
<div class="control-group">
<h3>🗺️ Map Source</h3>
<select id="tileSource">
<option value="osm">OpenStreetMap</option>
<option value="satellite">Satellite</option>
<option value="terrain">Terrain</option>
<option value="cycle">Cycle</option>
<option value="osm">OpenStreetMap (via MapTiler)</option>
<option value="satellite">Satellite (via MapTiler)</option>
<option value="terrain">Terrain (via MapTiler)</option>
<option value="osm-direct">OSM direct — policy-restricted</option>
</select>
<div class="form-row" style="margin-top:0.5rem;">
<label>MapTiler key:</label>
<input type="text" id="maptilerKey" placeholder="required for MapTiler sources">
</div>
<div class="form-row">
<label>Contact:</label>
<input type="text" id="contact" value="ekarwatowski@gmail.com" placeholder="email or URL for User-Agent">
</div>
</div>

<div class="control-group">
<h3>⚙️ Advanced Options</h3>
<div class="form-row">
<label>Delay:</label>
<input type="number" id="delay" value="0.2" step="0.1" min="0.1">
<input type="number" id="delay" value="0.5" step="0.1" min="0.1">
</div>
<div class="form-row">
<label>Workers:</label>
<input type="number" id="workers" value="3" min="1" max="10">
<input type="number" id="workers" value="2" min="1" max="10">
</div>
<div class="form-row">
<label>Output:</label>
Expand Down Expand Up @@ -346,20 +356,24 @@ <h3>🌟 Quick Presets</h3>
let startPoint = null;
let currentTileLayer = null;

// Map tile layers
const tileLayers = {
osm: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
satellite: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
terrain: 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'
};

// Map tile layers (preview-only; requires MapTiler key)
function tileUrl(style) {
const key = document.getElementById('previewKey')?.value || '';
const layers = {
osm: `https://api.maptiler.com/maps/openstreetmap/{z}/{x}/{y}.png?key=${key}`,
satellite: `https://api.maptiler.com/tiles/satellite-v2/{z}/{x}/{y}.jpg?key=${key}`,
terrain: `https://api.maptiler.com/maps/topo-v2/{z}/{x}/{y}.png?key=${key}`,
};
return layers[style] || layers.osm;
}

// Initialize map
function initMap() {
map = L.map('map').setView([39.7392, -104.9903], 8); // Denver

// Add default tile layer
currentTileLayer = L.tileLayer(tileLayers.osm, {
attribution: '© OpenStreetMap contributors'
currentTileLayer = L.tileLayer(tileUrl('osm'), {
attribution: '© OpenStreetMap contributors · © MapTiler'
}).addTo(map);

// Add selection functionality
Expand Down Expand Up @@ -442,8 +456,8 @@ <h3>🌟 Quick Presets</h3>
map.removeLayer(currentTileLayer);
}

currentTileLayer = L.tileLayer(tileLayers[style], {
attribution: style === 'osm' ? '© OpenStreetMap contributors' : '© Map tiles'
currentTileLayer = L.tileLayer(tileUrl(style), {
attribution: '© OpenStreetMap contributors · © MapTiler'
}).addTo(map);
}

Expand Down Expand Up @@ -507,13 +521,29 @@ <h3>🌟 Quick Presets</h3>
const delay = document.getElementById('delay').value;
const workers = document.getElementById('workers').value;
const outputDir = document.getElementById('outputDir').value;

const maptilerKey = document.getElementById('maptilerKey').value.trim();
const contact = document.getElementById('contact').value.trim();

if (!north || !south || !east || !west) {
document.getElementById('commandOutput').textContent = 'Select an area on the map first...';
return;
}

const command = `python meshtastic_tiles.py --coords --north ${north} --south ${south} --east ${east} --west ${west} --min-zoom ${minZoom} --max-zoom ${maxZoom} --source ${source} --delay ${delay} --max-workers ${workers} --output-dir ${outputDir}`;

const parts = [
'python meshtastic_tiles.py --coords',
`--north ${north} --south ${south} --east ${east} --west ${west}`,
`--min-zoom ${minZoom} --max-zoom ${maxZoom}`,
`--source ${source}`,
`--delay ${delay} --max-workers ${workers}`,
`--output-dir ${outputDir}`,
];
if (contact) parts.push(`--contact ${contact}`);
if (source !== 'osm-direct') {
parts.push(maptilerKey ? `--maptiler-key ${maptilerKey}` : '--maptiler-key "$MAPTILER_KEY"');
} else {
parts.push('--i-understand-osm-policy');
}
const command = parts.join(' ');

document.getElementById('commandOutput').textContent = command;
}
Expand Down Expand Up @@ -589,6 +619,9 @@ <h3>🌟 Quick Presets</h3>
document.getElementById('delay').addEventListener('input', generateCommand);
document.getElementById('workers').addEventListener('input', generateCommand);
document.getElementById('outputDir').addEventListener('input', generateCommand);
document.getElementById('maptilerKey').addEventListener('input', generateCommand);
document.getElementById('contact').addEventListener('input', generateCommand);
document.getElementById('previewKey').addEventListener('change', changeMapStyle);

// Manual coordinate input listeners
['north', 'south', 'east', 'west'].forEach(id => {
Expand Down
Loading