Skip to content

Commit e2b516f

Browse files
Add code to use multiple tile formats and providers (#96)
* Add code to use multiple tile formats and providers * Add example declaration for multiple tile providers * Add a modded DejaVu9 font with a percent sign © * Use taskYIELD() to get better utilisation when downloading * Fetch task priority to 1 * Update README * Add provider getters
1 parent 9b0ba73 commit e2b516f

File tree

6 files changed

+421
-67
lines changed

6 files changed

+421
-67
lines changed

README.md

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ A composed map can be pushed to the screen, saved to SD or used for further comp
1717
Downloaded tiles are cached in psram for reuse.
1818

1919
This library should work on any ESP32 type with psram and a LovyanGFX compatible display.
20-
OSM tiles are quite large -128kB per tile- so psram is required.
20+
OSM tiles are quite large at 128kB or insane large at 512kB per tile, so psram is required.
2121

2222
This project is not endorsed by or affiliated with the OpenStreetMap Foundation.
2323
Use of any OSMF provided service is governed by the [OSMF Terms of Use](https://osmfoundation.org/wiki/Terms_of_Use).
@@ -40,43 +40,103 @@ framework = arduino
4040
lib_deps =
4141
celliesprojects/OpenStreetMap-esp32@^1.0.6
4242
lovyan03/LovyanGFX@^1.2.7
43-
https://github.com/bitbank2/PNGdec@^1.1.3
43+
bitbank2/PNGdec@^1.1.3
4444
```
4545

46+
## Functions
47+
4648
### Set map size
4749

4850
```c++
49-
void setSize(uint16_t w, uint16_t h);
51+
void setSize(uint16_t w, uint16_t h)
5052
```
5153
52-
- If no size is set a 320px by 240px map will be returned by `fetchMap`.
54+
- If no size is set a 320px by 240px map will be returned.
55+
- The tile cache should be freed with `freeTilesCache()` after setting a new bigger map size.
5356
54-
### Resize cache
57+
### Get the number of tiles needed to cache a map
5558
5659
```c++
57-
bool resizeTilesCache(uint16_t numberOfTiles);
60+
uint16_t tilesNeeded(uint16_t w, uint16_t h)
5861
```
5962

60-
- If the cache is not resized before the first call to `fetchMap`, it will auto initialize with space for 10 tiles on the first call.
61-
- Each tile allocates 128 kB psram.
62-
- The cache content is cleared before resizing.
63+
This returns the number of tiles required to cache the given map size.
6364

64-
### Free the memory used by the tile cache
65+
### Resize the tiles cache
6566

6667
```c++
67-
void freeTilesCache();
68+
bool resizeTilesCache(uint16_t numberOfTiles)
6869
```
6970
71+
- If the cache is not resized before the first call to `fetchMap`, the cache will be auto initialized.
72+
- The cache content is cleared before resizing.
73+
- Each 256px tile allocates **128kB** psram.
74+
- Each 512px tile allocates **512kB** psram.
75+
76+
**Don't over-allocate the cache**
77+
When resizing the cache, keep in mind that the map sprite also uses psram.
78+
The PNG decoders -~50kB for each core- also live in psram.
79+
Use the above `tilesNeeded` function to calculate a safe and sane cache size.
80+
7081
### Fetch a map
7182
7283
```c++
73-
bool fetchMap(LGFX_Sprite &map, double longitude, double latitude, uint8_t zoom);
84+
bool fetchMap(LGFX_Sprite &map, double longitude, double latitude, uint8_t zoom)
7485
```
7586

7687
- Overflowing `longitude` are wrapped and normalized to +-180°.
7788
- Overflowing `latitude` are clamped to +-90°.
7889
- Valid range for the `zoom` level is 1-18.
7990

91+
### Free the memory used by the tile cache
92+
93+
```c++
94+
void freeTilesCache()
95+
```
96+
97+
### Switch to a different tile provider
98+
99+
```c++
100+
bool setTileProvider(int index)
101+
```
102+
103+
This function will switch to a tile provider (if) that is user defined in `src/TileProvider.hpp`.
104+
105+
- Returns `true` and clears the cache on success.
106+
- Returns `false` -and the current tile provider is unchanged- if no provider at the index is defined.
107+
108+
### Get the number of defined providers
109+
110+
`OSM_TILEPROVIDERS` gives the number of defined providers.
111+
112+
Example use:
113+
114+
```c++
115+
const int numberOfProviders = OSM_TILEPROVIDERS;
116+
```
117+
118+
In the default setup there is only one provider defined.
119+
See `src/TileProvider.hpp` for example setups for [https://www.thunderforest.com/](https://www.thunderforest.com/) that only require an API key and commenting/uncommenting 2 lines.
120+
Registration and a hobby tier are available for free.
121+
122+
### Get the provider name
123+
124+
```c++
125+
char *getProviderName()
126+
```
127+
128+
### Get the minimum zoom level
129+
130+
```c++
131+
int getMinZoom()
132+
```
133+
134+
### Get the maximum zoom level
135+
136+
```c++
137+
int getMaxZoom()
138+
```
139+
80140
## Example code
81141

82142
### Example returning the default 320x240 map

src/CachedTile.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ struct CachedTile
5050
free();
5151
}
5252

53-
bool allocate()
53+
bool allocate(int tileSize)
5454
{
55-
buffer = static_cast<uint16_t *>(heap_caps_malloc(256 * 256 * sizeof(uint16_t), MALLOC_CAP_SPIRAM));
55+
buffer = static_cast<uint16_t *>(heap_caps_malloc(tileSize * tileSize * sizeof(uint16_t), MALLOC_CAP_SPIRAM));
5656
return buffer != nullptr;
5757
}
5858

src/OpenStreetMap-esp32.cpp

Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -87,26 +87,26 @@ void OpenStreetMap::computeRequiredTiles(double longitude, double latitude, uint
8787
const int32_t targetTileY = static_cast<int32_t>(exactTileY);
8888

8989
// Compute the offset inside the tile for the given coordinates
90-
const int16_t targetOffsetX = (exactTileX - targetTileX) * OSM_TILESIZE;
91-
const int16_t targetOffsetY = (exactTileY - targetTileY) * OSM_TILESIZE;
90+
const int16_t targetOffsetX = (exactTileX - targetTileX) * currentProvider->tileSize;
91+
const int16_t targetOffsetY = (exactTileY - targetTileY) * currentProvider->tileSize;
9292

9393
// Compute the offset for tiles covering the map area to keep the location centered
9494
const int16_t tilesOffsetX = mapWidth / 2 - targetOffsetX;
9595
const int16_t tilesOffsetY = mapHeight / 2 - targetOffsetY;
9696

9797
// Compute number of colums required
98-
const float colsLeft = 1.0 * tilesOffsetX / OSM_TILESIZE;
99-
const float colsRight = float(mapWidth - (tilesOffsetX + OSM_TILESIZE)) / OSM_TILESIZE;
98+
const float colsLeft = 1.0 * tilesOffsetX / currentProvider->tileSize;
99+
const float colsRight = float(mapWidth - (tilesOffsetX + currentProvider->tileSize)) / currentProvider->tileSize;
100100
numberOfColums = ceil(colsLeft) + 1 + ceil(colsRight);
101101

102-
startOffsetX = tilesOffsetX - (ceil(colsLeft) * OSM_TILESIZE);
102+
startOffsetX = tilesOffsetX - (ceil(colsLeft) * currentProvider->tileSize);
103103

104104
// Compute number of rows required
105-
const float rowsTop = 1.0 * tilesOffsetY / OSM_TILESIZE;
106-
const float rowsBottom = float(mapHeight - (tilesOffsetY + OSM_TILESIZE)) / OSM_TILESIZE;
105+
const float rowsTop = 1.0 * tilesOffsetY / currentProvider->tileSize;
106+
const float rowsBottom = float(mapHeight - (tilesOffsetY + currentProvider->tileSize)) / currentProvider->tileSize;
107107
const uint32_t numberOfRows = ceil(rowsTop) + 1 + ceil(rowsBottom);
108108

109-
startOffsetY = tilesOffsetY - (ceil(rowsTop) * OSM_TILESIZE);
109+
startOffsetY = tilesOffsetY - (ceil(rowsTop) * currentProvider->tileSize);
110110

111111
log_v(" Need %i * %i tiles. First tile offset is %d,%d",
112112
numberOfColums, numberOfRows, startOffsetX, startOffsetY);
@@ -171,17 +171,11 @@ bool OpenStreetMap::isTileCachedOrBusy(uint32_t x, uint32_t y, uint8_t z)
171171

172172
void OpenStreetMap::freeTilesCache()
173173
{
174-
for (auto &tile : tilesCache)
175-
tile.free();
176-
177-
tilesCache.clear();
174+
std::vector<CachedTile>().swap(tilesCache);
178175
}
179176

180177
bool OpenStreetMap::resizeTilesCache(uint16_t numberOfTiles)
181178
{
182-
if (tilesCache.size() == numberOfTiles)
183-
return true;
184-
185179
if (!numberOfTiles)
186180
{
187181
log_e("Invalid cache size: %d", numberOfTiles);
@@ -193,7 +187,7 @@ bool OpenStreetMap::resizeTilesCache(uint16_t numberOfTiles)
193187

194188
for (auto &tile : tilesCache)
195189
{
196-
if (!tile.allocate())
190+
if (!tile.allocate(currentProvider->tileSize))
197191
{
198192
log_e("Tile cache allocation failed!");
199193
freeTilesCache();
@@ -211,7 +205,7 @@ void OpenStreetMap::updateCache(const tileList &requiredTiles, uint8_t zoom)
211205
if (!jobs.empty())
212206
{
213207
runJobs(jobs);
214-
log_i("Updated %i tiles in %lu ms - %i ms/tile", jobs.size(), millis() - startMS, (millis() - startMS) / jobs.size());
208+
log_d("Updated %i tiles in %lu ms - %i ms/tile", jobs.size(), millis() - startMS, (millis() - startMS) / jobs.size());
215209
}
216210
}
217211

@@ -270,8 +264,8 @@ bool OpenStreetMap::composeMap(LGFX_Sprite &mapSprite, const tileList &requiredT
270264
continue;
271265
}
272266

273-
int drawX = startOffsetX + (tileIndex % numberOfColums) * OSM_TILESIZE;
274-
int drawY = startOffsetY + (tileIndex / numberOfColums) * OSM_TILESIZE;
267+
int drawX = startOffsetX + (tileIndex % numberOfColums) * currentProvider->tileSize;
268+
int drawY = startOffsetY + (tileIndex / numberOfColums) * currentProvider->tileSize;
275269

276270
auto it = std::find_if(tilesCache.begin(), tilesCache.end(),
277271
[&](const CachedTile &tile)
@@ -280,7 +274,7 @@ bool OpenStreetMap::composeMap(LGFX_Sprite &mapSprite, const tileList &requiredT
280274
});
281275

282276
if (it != tilesCache.end())
283-
mapSprite.pushImage(drawX, drawY, OSM_TILESIZE, OSM_TILESIZE, it->buffer);
277+
mapSprite.pushImage(drawX, drawY, currentProvider->tileSize, currentProvider->tileSize, it->buffer);
284278
else
285279
log_w("Tile (z=%d, x=%d, y=%d) not found in cache", zoom, tileX, tileY);
286280

@@ -293,8 +287,8 @@ bool OpenStreetMap::composeMap(LGFX_Sprite &mapSprite, const tileList &requiredT
293287
mapSprite.setTextColor(TFT_WHITE, TFT_BLACK);
294288
else
295289
mapSprite.setTextColor(TFT_BLACK);
296-
mapSprite.drawRightString(" Map data from OpenStreetMap.org ",
297-
mapSprite.width(), mapSprite.height() - 10, &DejaVu9);
290+
mapSprite.drawRightString(currentProvider->attribution,
291+
mapSprite.width(), mapSprite.height() - 10, &DejaVu9Modded);
298292
mapSprite.setTextColor(TFT_WHITE, TFT_BLACK);
299293

300294
return true;
@@ -308,7 +302,7 @@ bool OpenStreetMap::fetchMap(LGFX_Sprite &mapSprite, double longitude, double la
308302
return false;
309303
}
310304

311-
if (!zoom || zoom > OSM_MAX_ZOOM)
305+
if (zoom < currentProvider->minZoom || zoom > currentProvider->maxZoom)
312306
{
313307
log_e("Invalid zoom level: %d", zoom);
314308
return false;
@@ -320,14 +314,10 @@ bool OpenStreetMap::fetchMap(LGFX_Sprite &mapSprite, double longitude, double la
320314
return false;
321315
}
322316

323-
if (!tilesCache.capacity())
317+
if (!tilesCache.capacity() && !resizeTilesCache(tilesNeeded(mapWidth, mapHeight)))
324318
{
325-
log_w("Cache not initialized, setting up a default cache...");
326-
if (!resizeTilesCache(OSM_DEFAULT_CACHE_ITEMS))
327-
{
328-
log_e("Could not allocate tile cache");
329-
return false;
330-
}
319+
log_e("Could not allocate tile cache");
320+
return false;
331321
}
332322

333323
longitude = fmod(longitude + 180.0, 360.0) - 180.0;
@@ -366,7 +356,7 @@ bool OpenStreetMap::fillBuffer(WiFiClient *stream, MemoryBuffer &buffer, size_t
366356
result = "Timeout: " + String(OSM_TILE_TIMEOUT_MS) + " ms";
367357
return false;
368358
}
369-
vTaskDelay(pdMS_TO_TICKS(1));
359+
taskYIELD();
370360
continue;
371361
}
372362

@@ -382,7 +372,7 @@ bool OpenStreetMap::fillBuffer(WiFiClient *stream, MemoryBuffer &buffer, size_t
382372
lastReadTime = millis();
383373
}
384374
else
385-
vTaskDelay(pdMS_TO_TICKS(1));
375+
taskYIELD();
386376
}
387377
return true;
388378
}
@@ -432,22 +422,25 @@ std::optional<std::unique_ptr<MemoryBuffer>> OpenStreetMap::urlToBuffer(const ch
432422

433423
void OpenStreetMap::PNGDraw(PNGDRAW *pDraw)
434424
{
435-
uint16_t *destRow = currentInstance->currentTileBuffer + (pDraw->y * OSM_TILESIZE);
425+
uint16_t *destRow = currentInstance->currentTileBuffer + (pDraw->y * currentInstance->currentProvider->tileSize);
436426
getPNGCurrentCore()->getLineAsRGB565(pDraw, destRow, PNG_RGB565_BIG_ENDIAN, 0xffffffff);
437427
}
438428

439429
bool OpenStreetMap::fetchTile(CachedTile &tile, uint32_t x, uint32_t y, uint8_t zoom, String &result)
440430
{
441-
char url[64];
442-
snprintf(url, sizeof(url), "https://tile.openstreetmap.org/%u/%u/%u.png",
443-
static_cast<unsigned int>(zoom),
444-
static_cast<unsigned int>(x),
445-
static_cast<unsigned int>(y));
446-
447-
const auto buffer = urlToBuffer(url, result);
431+
String url = currentProvider->urlTemplate;
432+
url.replace("{x}", String(x));
433+
url.replace("{y}", String(y));
434+
url.replace("{z}", String(zoom));
435+
if (currentProvider->requiresApiKey && strstr(url.c_str(), "{apiKey}"))
436+
url.replace("{apiKey}", currentProvider->apiKey);
437+
438+
const auto buffer = urlToBuffer(url.c_str(), result);
448439
if (!buffer)
449440
return false;
450441

442+
url.clear();
443+
451444
PNG *png = getPNGCurrentCore();
452445
const int16_t rc = png->openRAM(buffer.value()->get(), buffer.value()->size(), PNGDraw);
453446
if (rc != PNG_SUCCESS)
@@ -456,7 +449,7 @@ bool OpenStreetMap::fetchTile(CachedTile &tile, uint32_t x, uint32_t y, uint8_t
456449
return false;
457450
}
458451

459-
if (png->getWidth() != OSM_TILESIZE || png->getHeight() != OSM_TILESIZE)
452+
if (png->getWidth() != currentProvider->tileSize || png->getHeight() != currentProvider->tileSize)
460453
{
461454
result = "Unexpected tile size: w=" + String(png->getWidth()) + " h=" + String(png->getHeight());
462455
return false;
@@ -468,7 +461,7 @@ bool OpenStreetMap::fetchTile(CachedTile &tile, uint32_t x, uint32_t y, uint8_t
468461
const int decodeResult = png->decode(0, PNG_FAST_PALETTE);
469462
if (decodeResult != PNG_SUCCESS)
470463
{
471-
result = "Decoding " + String(url) + " failed with code: " + String(decodeResult);
464+
result = "Decoding " + url + " failed with code: " + String(decodeResult);
472465
tile.valid = false;
473466
return false;
474467
}
@@ -550,3 +543,25 @@ bool OpenStreetMap::startTileWorkerTasks()
550543
log_i("Started %d tile worker task(s)", numberOfWorkers);
551544
return true;
552545
}
546+
547+
uint16_t OpenStreetMap::tilesNeeded(uint16_t mapWidth, uint16_t mapHeight)
548+
{
549+
const int tileSize = currentProvider->tileSize;
550+
int tilesX = (mapWidth + tileSize - 1) / tileSize + 1;
551+
int tilesY = (mapHeight + tileSize - 1) / tileSize + 1;
552+
return tilesX * tilesY;
553+
}
554+
555+
bool OpenStreetMap::setTileProvider(int index)
556+
{
557+
if (index < 0 || index >= OSM_TILEPROVIDERS)
558+
{
559+
log_e("invalid provider index");
560+
return false;
561+
}
562+
563+
currentProvider = &tileProviders[index];
564+
freeTilesCache();
565+
log_i("provider changed to '%s'", currentProvider->name);
566+
return true;
567+
}

0 commit comments

Comments
 (0)