Skip to content

Fix tile fetcher #118

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 12, 2025
Merged
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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,23 @@ If you encounter a problem or want to request support for a new provider, please
char *getProviderName()
```

### Set the render mode

```c++
void setRenderMode(RenderMode mode)
```

Available modes:

- `RenderMode::ACCURATE` (default)
Downloads map tiles **without a timeout**, ensuring a complete map with **no missing tiles** in most cases.
Best suited for reliability and full-quality rendering.

- `RenderMode::FAST`
Downloads map tiles **with a timeout**.
This mode can produce the map **more quickly**, but some **tiles may be missing** if a request times out.
Ideal when operating under time constraints.

## Example code

### Example returning the default 320x240 map
Expand Down
63 changes: 0 additions & 63 deletions src/HTTPClientRAII.hpp

This file was deleted.

5 changes: 5 additions & 0 deletions src/MemoryBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,8 @@ bool MemoryBuffer::isAllocated()
{
return buffer_.get() != nullptr;
}

MemoryBuffer MemoryBuffer::empty()
{
return MemoryBuffer(0);
}
53 changes: 1 addition & 52 deletions src/MemoryBuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,66 +27,15 @@
#include <Arduino.h>
#include <memory>

/**
* @class MemoryBuffer
* @brief A class that handles memory allocation and deallocation for a buffer.
*
* This class provides an RAII approach to manage a dynamically allocated buffer. It ensures that memory is
* allocated during object creation and automatically freed when the object goes out of scope.
*
* @note It is recommended to use the `MemoryBuffer` class when dealing with dynamic memory allocation,
* to avoid memory leaks and ensure proper memory management.
*
* Example use:
* ```cpp
* {
* MemoryBuffer buffer(512);
* if (buffer.isAllocated()) { // Check if allocated!
* // Access buffer here...
* } else {
* // Handle error (e.g., log error, retry later)
* }
* } // buffer automatically freed
*
* ```
*/
class MemoryBuffer
{
public:
/**
* @brief Constructs a `MemoryBuffer` object and allocates memory of the specified size.
*
* The constructor allocates memory of the specified size for the buffer. If allocation fails,
* the buffer will not be valid.
*
* @param size The size of the buffer in bytes.
*
* @example
* // Example usage of the constructor
* MemoryBuffer buffer(512); // Allocates a buffer of 512 bytes
*/
explicit MemoryBuffer(size_t size);

/**
* @brief Returns a pointer to the allocated memory buffer.
*
* @return A pointer to the allocated memory, or `nullptr` if memory allocation failed.
*/
uint8_t *get();

/**
* @brief Returns the size of the allocated buffer.
*
* @return The size of the allocated buffer in bytes.
*/
size_t size() const;

/**
* @brief Checks whether memory allocation was successful.
*
* @return `true` if memory was successfully allocated, `false` if the buffer is `nullptr`.
*/
bool isAllocated();
static MemoryBuffer empty();

private:
size_t size_;
Expand Down
99 changes: 11 additions & 88 deletions src/OpenStreetMap-esp32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ void OpenStreetMap::updateCache(const tileList &requiredTiles, uint8_t zoom, Til
if (!jobs.empty())
{
runJobs(jobs);
log_i("Updated %i tiles in %lu ms - %i ms/tile", jobs.size(), millis() - startMS, (millis() - startMS) / jobs.size());
log_d("Finished %i jobs in %lu ms - %i ms/job", jobs.size(), millis() - startMS, (millis() - startMS) / jobs.size());
}
}

Expand Down Expand Up @@ -345,84 +345,6 @@ bool OpenStreetMap::fetchMap(LGFX_Sprite &mapSprite, double longitude, double la
return true;
}

bool OpenStreetMap::fillBuffer(WiFiClient *stream, MemoryBuffer &buffer, size_t contentSize, String &result)
{
size_t readSize = 0;
unsigned long lastReadTime = millis();
while (readSize < contentSize)
{
const size_t availableData = stream->available();
if (!availableData)
{
if (millis() - lastReadTime >= OSM_TILE_TIMEOUT_MS)
{
result = "Timeout: " + String(OSM_TILE_TIMEOUT_MS) + " ms";
return false;
}
taskYIELD();
continue;
}

const size_t remaining = contentSize - readSize;
const size_t toRead = std::min(availableData, remaining);
if (toRead == 0)
continue;

const int bytesRead = stream->readBytes(buffer.get() + readSize, toRead);
if (bytesRead > 0)
{
readSize += bytesRead;
lastReadTime = millis();
}
else
taskYIELD();
}
return true;
}

std::unique_ptr<MemoryBuffer> OpenStreetMap::urlToBuffer(const char *url, String &result)
{
HTTPClientRAII http;
if (!http.begin(url))
{
result = "Failed to initialize HTTP client";
return nullptr;
}

const int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK)
{
result = "HTTP Error: " + String(httpCode);
return nullptr;
}

const size_t contentSize = http.getSize();
if (contentSize < 1)
{
result = "Empty or chunked response";
return nullptr;
}

WiFiClient *stream = http.getStreamPtr();
if (!stream)
{
result = "Failed to get HTTP stream";
return nullptr;
}

auto buffer = std::make_unique<MemoryBuffer>(contentSize);
if (!buffer->isAllocated())
{
result = "Failed to allocate buffer";
return nullptr;
}

if (!fillBuffer(stream, *buffer, contentSize, result))
return nullptr;

return buffer;
}

void OpenStreetMap::PNGDraw(PNGDRAW *pDraw)
{
uint16_t *destRow = currentInstance->currentTileBuffer + (pDraw->y * currentInstance->currentProvider->tileSize);
Expand All @@ -431,20 +353,19 @@ void OpenStreetMap::PNGDraw(PNGDRAW *pDraw)

bool OpenStreetMap::fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, uint32_t x, uint32_t y, uint8_t zoom, String &result)
{

String url = currentProvider->urlTemplate;
url.replace("{x}", String(x));
url.replace("{y}", String(y));
url.replace("{z}", String(zoom));
if (currentProvider->requiresApiKey && strstr(url.c_str(), "{apiKey}"))
url.replace("{apiKey}", currentProvider->apiKey);

const std::unique_ptr<MemoryBuffer> buffer = fetcher.fetchToBuffer(url, result);
if (!buffer)
MemoryBuffer buffer = fetcher.fetchToBuffer(url, result, renderMode);
if (!buffer.isAllocated())
return false;

PNG *png = getPNGCurrentCore();
const int16_t rc = png->openRAM(buffer->get(), buffer->size(), PNGDraw);
const int16_t rc = png->openRAM(buffer.get(), buffer.size(), PNGDraw);
if (rc != PNG_SUCCESS)
{
result = "PNG Decoder Error: " + String(rc);
Expand Down Expand Up @@ -488,21 +409,18 @@ void OpenStreetMap::tileFetcherTask(void *param)
String result;
if (!osm->fetchTile(fetcher, *job.tile, job.x, job.y, job.z, result))
{
log_e("Tile fetch failed: %s", result.c_str());
job.tile->valid = false;
const size_t tileByteCount = osm->currentProvider->tileSize * osm->currentProvider->tileSize * 2;
memset(job.tile->buffer, 0, tileByteCount);
job.tile->valid = false;
log_e("Tile fetch failed: %s", result.c_str());
}
else
{
job.tile->valid = true;
log_d("core %i fetched tile z=%u x=%lu, y=%lu in %lu ms", xPortGetCoreID(), job.z, job.x, job.y, millis() - startMS);
}

job.tile->busy = false;
--osm->pendingJobs;
if (!uxQueueMessagesWaiting(osm->jobQueue))
fetcher.close();
}
log_d("task on core %i exiting", xPortGetCoreID());
xTaskNotifyGive(osm->ownerTask);
Expand Down Expand Up @@ -577,3 +495,8 @@ bool OpenStreetMap::setTileProvider(int index)
log_i("provider changed to '%s'", currentProvider->name);
return true;
}

void OpenStreetMap::setRenderMode(RenderMode mode)
{
renderMode = mode;
}
6 changes: 3 additions & 3 deletions src/OpenStreetMap-esp32.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
#include "CachedTile.hpp"
#include "TileJob.hpp"
#include "MemoryBuffer.hpp"
#include "HTTPClientRAII.hpp"
#include "ReusableTileFetcher.hpp"
#include "fonts/DejaVu9-modded.h"
#include "RenderMode.hpp"

constexpr uint16_t OSM_BGCOLOR = lgfx::color565(32, 32, 128);
constexpr uint16_t OSM_TILE_TIMEOUT_MS = 1000;
Expand Down Expand Up @@ -94,6 +94,7 @@ class OpenStreetMap
bool fetchMap(LGFX_Sprite &sprite, double longitude, double latitude, uint8_t zoom);
inline void freeTilesCache();

void setRenderMode(RenderMode mode);
bool setTileProvider(int index);
const char *getProviderName() { return currentProvider->name; };
int getMinZoom() const { return currentProvider->minZoom; };
Expand All @@ -109,15 +110,14 @@ class OpenStreetMap
void runJobs(const std::vector<TileJob> &jobs);
CachedTile *findUnusedTile(const tileList &requiredTiles, uint8_t zoom);
CachedTile *isTileCached(uint32_t x, uint32_t y, uint8_t z);
std::unique_ptr<MemoryBuffer> urlToBuffer(const char *url, String &result);
bool fetchTile(ReusableTileFetcher &fetcher, CachedTile &tile, uint32_t x, uint32_t y, uint8_t zoom, String &result);
bool fillBuffer(WiFiClient *stream, MemoryBuffer &buffer, size_t contentSize, String &result);
bool composeMap(LGFX_Sprite &mapSprite, TileBufferList &tilePointers);
static void tileFetcherTask(void *param);
static void PNGDraw(PNGDRAW *pDraw);

static inline thread_local OpenStreetMap *currentInstance = nullptr;
static inline thread_local uint16_t *currentTileBuffer = nullptr;
RenderMode renderMode = RenderMode::ACCURATE;
const TileProvider *currentProvider = &tileProviders[0];
std::vector<CachedTile> tilesCache;

Expand Down
7 changes: 7 additions & 0 deletions src/RenderMode.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

enum class RenderMode
{
FAST,
ACCURATE
};
Loading