Skip to content

refactor savemap #7

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 7 commits into from
Mar 21, 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: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ The downloaded tile cache gets large very quickly -128kB per tile- so a ESP32 wi
void setResolution(uint16_t w, uint16_t h);
```

- If no resolution is set, a 320 by 240 map will be returned by `fetchMap`.

#### Resize cache

```c++
bool resizeTilesCache(uint8_t numberOfTiles);
```
**Note**: Each tile is 128 kB.

- The cache is cleared before resizing.
- Each tile is 128 kB.

#### Free the memory used by the tile cache

Expand All @@ -47,11 +51,14 @@ bool fetchMap(LGFX_Sprite &map, double longitude, double latitude, uint8_t zoom)
#### Save a map to SD card

```c++
bool saveMap(const char *filename, LGFX_Sprite &display, String &result, uint8_t sdPin = SS)
bool saveMap(const char *filename, LGFX_Sprite &map, String &result,
uint8_t sdPin = SS, uint32_t frequency = 4000000)
```
`filename` should start with `/` for example `/map.bmp`
`sdPin` is optional and used to set a `SS/CS` pin for the SD slot.
`result` returns something like `SD Card mount failed!` or `Screenshot saved`.

- `filename` should start with `/` for example `/map.bmp` or `/images/map.bmp`
- `result` returns something like `SD Card mount failed` or `Screenshot saved`.
- `sdPin` is optional and used to set a `SS/CS` pin for the SD slot.
- `frequency` is optional and used to set the SD speed.

## License differences between this library and the map data

Expand Down
98 changes: 55 additions & 43 deletions src/OpenStreetMap-esp32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -412,88 +412,100 @@ bool OpenStreetMap::downloadAndDecodeTile(CachedTile &tile, uint32_t x, uint32_t
return true;
}

bool OpenStreetMap::saveMap(const char *filename, LGFX_Sprite &map, String &result, uint8_t sdPin)
bool OpenStreetMap::saveMap(const char *filename, LGFX_Sprite &map, String &result, uint8_t sdPin, uint32_t frequency)
{
log_i("Saving map, this may take a while...");

if (!map.getBuffer())
{
result = "No data in map!";
result = "No data in map";
return false;
}

if (!SD.begin(sdPin))
if (!SD.begin(sdPin, SPI, frequency))
{
result = "SD Card mount failed!";
result = "SD Card mount failed";
return false;
}

File file = SD.open(filename, FILE_WRITE);
if (!file)
{
result = "Failed to open file!";
result = "Failed to open file";
SD.end();
return false;
}

// BMP header (54 bytes)
uint16_t bfType = 0x4D42; // "BM"
uint32_t bfSize = 54 + map.width() * map.height() * 3; // Header + pixel data (3 bytes per pixel for RGB888)
uint16_t bfReserved = 0;
uint32_t bfOffBits = 54; // Offset to pixel data
// BMP Header (54 bytes)
uint16_t bfType = 0x4D42; // "BM"
uint32_t biSizeImage = map.width() * map.height() * 3; // 3 bytes per pixel (RGB888)
uint32_t bfSize = 54 + biSizeImage; // Total file size
uint32_t bfOffBits = 54; // Offset to pixel data

uint32_t biSize = 40; // Info header size
int32_t biWidth = map.width();
int32_t biHeight = -map.height(); // Negative to flip vertically
int32_t biHeight = -map.height(); // Negative to store in top-down order
uint16_t biPlanes = 1;
uint16_t biBitCount = 24; // RGB888 format
uint32_t biCompression = 0;
uint32_t biSizeImage = map.width() * map.height() * 3; // 3 bytes per pixel
int32_t biXPelsPerMeter = 0;
int32_t biYPelsPerMeter = 0;
uint32_t biClrUsed = 0;
uint32_t biClrImportant = 0;

// Write BMP header
file.write(reinterpret_cast<const uint8_t *>(&bfType), sizeof(bfType));
file.write(reinterpret_cast<const uint8_t *>(&bfSize), sizeof(bfSize));
file.write(reinterpret_cast<const uint8_t *>(&bfReserved), sizeof(bfReserved));
file.write(reinterpret_cast<const uint8_t *>(&bfOffBits), sizeof(bfOffBits));

file.write(reinterpret_cast<const uint8_t *>(&biSize), sizeof(biSize));
file.write(reinterpret_cast<const uint8_t *>(&biWidth), sizeof(biWidth));
file.write(reinterpret_cast<const uint8_t *>(&biHeight), sizeof(biHeight));
file.write(reinterpret_cast<const uint8_t *>(&biPlanes), sizeof(biPlanes));
file.write(reinterpret_cast<const uint8_t *>(&biBitCount), sizeof(biBitCount));
file.write(reinterpret_cast<const uint8_t *>(&biCompression), sizeof(biCompression));
file.write(reinterpret_cast<const uint8_t *>(&biSizeImage), sizeof(biSizeImage));
file.write(reinterpret_cast<const uint8_t *>(&biXPelsPerMeter), sizeof(biXPelsPerMeter));
file.write(reinterpret_cast<const uint8_t *>(&biYPelsPerMeter), sizeof(biYPelsPerMeter));
file.write(reinterpret_cast<const uint8_t *>(&biClrUsed), sizeof(biClrUsed));
file.write(reinterpret_cast<const uint8_t *>(&biClrImportant), sizeof(biClrImportant));
// Write BMP header (Ensuring little-endian format)
auto writeLE = [&](uint32_t value, uint8_t size)
{
for (uint8_t i = 0; i < size; i++)
file.write(static_cast<uint8_t>(value >> (8 * i)));
};

writeLE(bfType, 2);
writeLE(bfSize, 4);
writeLE(0, 2); // bfReserved
writeLE(0, 2);
writeLE(bfOffBits, 4);

writeLE(biSize, 4);
writeLE(biWidth, 4);
writeLE(biHeight, 4);
writeLE(biPlanes, 2);
writeLE(biBitCount, 2);
writeLE(biCompression, 4);
writeLE(biSizeImage, 4);
writeLE(biXPelsPerMeter, 4);
writeLE(biYPelsPerMeter, 4);
writeLE(biClrUsed, 4);
writeLE(biClrImportant, 4);

MemoryBuffer rowBuffer(map.width() * 3);
if (!rowBuffer.isAllocated())
{
result = "Row buffer allocation failed";
file.close();
SD.end();
return false;
}

uint8_t *buf = rowBuffer.get();
for (int y = 0; y < map.height(); y++)
{
for (int x = 0; x < map.width(); x++)
{
uint16_t rgb565Color = map.readPixel(x, y); // Read pixel color (RGB565 format)
uint8_t red5 = (rgb565Color >> 11) & 0x1F;
uint8_t green6 = (rgb565Color >> 5) & 0x3F;
uint8_t blue5 = rgb565Color & 0x1F;

// Convert RGB565 to RGB888
uint8_t red8 = (red5 * 255) / 31;
uint8_t green8 = (green6 * 255) / 63;
uint8_t blue8 = (blue5 * 255) / 31;

file.write(blue8);
file.write(green8);
file.write(red8);
uint16_t rgb565Color = map.readPixel(x, y);
uint8_t red8 = ((rgb565Color >> 11) & 0x1F) * 255 / 31;
uint8_t green8 = ((rgb565Color >> 5) & 0x3F) * 255 / 63;
uint8_t blue8 = (rgb565Color & 0x1F) * 255 / 31;

buf[x * 3] = blue8;
buf[x * 3 + 1] = green8;
buf[x * 3 + 2] = red8;
}
file.write(buf, rowBuffer.size()); // Write entire row at once
}

file.close();
SD.end();
result = "Screenshot saved";
return true;
}
}
2 changes: 1 addition & 1 deletion src/OpenStreetMap-esp32.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class OpenStreetMap
bool resizeTilesCache(uint8_t numberOfTiles);
void freeTilesCache();
bool fetchMap(LGFX_Sprite &sprite, double longitude, double latitude, uint8_t zoom);
bool saveMap(const char *filename, LGFX_Sprite &map, String &result, uint8_t sdPin = SS);
bool saveMap(const char *filename, LGFX_Sprite &map, String &result, uint8_t sdPin = SS, uint32_t frequency = 4000000);

private:
static OpenStreetMap *currentInstance;
Expand Down