Initial commit of ESP component

This commit is contained in:
Eric
2025-09-08 20:47:04 -07:00
parent 5c4847c0d6
commit 165c2c07c7
7 changed files with 1371 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

6
CMakeLists.txt Executable file
View File

@@ -0,0 +1,6 @@
idf_component_register(
SRCS "map_tiles.cpp"
INCLUDE_DIRS "include"
REQUIRES lvgl esp_system
PRIV_REQUIRES vfs fatfs
)

279
README.md Executable file
View File

@@ -0,0 +1,279 @@
# Map Tiles Component for LVGL 9.x
A comprehensive map tiles component for ESP-IDF projects using LVGL 9.x. This component provides functionality to load and display map tiles with GPS coordinate conversion, designed for embedded applications requiring offline map display capabilities.
## Features
- **LVGL 9.x Compatible**: Fully compatible with LVGL 9.x image handling
- **GPS Coordinate Conversion**: Convert GPS coordinates to tile coordinates and vice versa
- **Dynamic Tile Loading**: Load map tiles on demand from file system
- **Configurable Grid Size**: Support for different grid sizes (3x3, 5x5, 7x7, etc.)
- **Multiple Tile Types**: Support for up to 8 different tile types (street, satellite, terrain, hybrid, etc.)
- **Memory Efficient**: Configurable memory allocation (SPIRAM or regular RAM)
- **Multiple Zoom Levels**: Support for different map zoom levels
- **Error Handling**: Comprehensive error handling and logging
- **C API**: Clean C API for easy integration
## Requirements
- ESP-IDF 5.0 or later
- LVGL 9.x
- File system support (FAT/SPIFFS/LittleFS)
- Map tiles in binary format (RGB565, 256x256 pixels)
## Installation
### Using ESP-IDF Component Manager
Add to your project's `main/idf_component.yml`:
```yaml
dependencies:
map_tiles:
git: "https://github.com/0015/map_tiles_component.git"
version: "^1.1.0"
```
### Manual Installation
1. Copy the `map_tiles` folder to your project's `components` directory
2. The component will be automatically included in your build
## Usage
### Basic Setup
```c
#include "map_tiles.h"
// Configure the map tiles with multiple tile types and custom grid size
const char* tile_folders[] = {"street_map", "satellite", "terrain", "hybrid"};
map_tiles_config_t config = {
.base_path = "/sdcard", // Base path to tile storage
.tile_folders = {tile_folders[0], tile_folders[1], tile_folders[2], tile_folders[3]},
.tile_type_count = 4, // Number of tile types
.default_zoom = 10, // Default zoom level
.use_spiram = true, // Use SPIRAM if available
.default_tile_type = 0, // Start with street map (index 0)
.grid_cols = 5, // Grid width (tiles)
.grid_rows = 5 // Grid height (tiles)
};
// Initialize map tiles
map_tiles_handle_t map_handle = map_tiles_init(&config);
if (!map_handle) {
ESP_LOGE(TAG, "Failed to initialize map tiles");
return;
}
```
### Loading Tiles
```c
// Set center position from GPS coordinates
map_tiles_set_center_from_gps(map_handle, 37.7749, -122.4194); // San Francisco
// Get grid dimensions
int grid_cols, grid_rows;
map_tiles_get_grid_size(map_handle, &grid_cols, &grid_rows);
int tile_count = map_tiles_get_tile_count(map_handle);
// Load tiles for the configured grid size
for (int row = 0; row < grid_rows; row++) {
for (int col = 0; col < grid_cols; col++) {
int index = row * grid_cols + col;
int tile_x, tile_y;
map_tiles_get_position(map_handle, &tile_x, &tile_y);
bool loaded = map_tiles_load_tile(map_handle, index,
tile_x + col, tile_y + row);
if (!loaded) {
ESP_LOGW(TAG, "Failed to load tile %d", index);
}
}
}
```
### Displaying Tiles with LVGL
```c
// Get grid dimensions and tile count
int grid_cols, grid_rows;
map_tiles_get_grid_size(map_handle, &grid_cols, &grid_rows);
int tile_count = map_tiles_get_tile_count(map_handle);
// Create image widgets for each tile
lv_obj_t** tile_images = malloc(tile_count * sizeof(lv_obj_t*));
for (int i = 0; i < tile_count; i++) {
tile_images[i] = lv_image_create(parent_container);
// Get the tile image descriptor
lv_image_dsc_t* img_dsc = map_tiles_get_image(map_handle, i);
if (img_dsc) {
lv_image_set_src(tile_images[i], img_dsc);
// Position the tile in the grid
int row = i / grid_cols;
int col = i % grid_cols;
lv_obj_set_pos(tile_images[i],
col * MAP_TILES_TILE_SIZE,
row * MAP_TILES_TILE_SIZE);
}
}
```
### Switching Tile Types
```c
// Switch to different tile types
map_tiles_set_tile_type(map_handle, 0); // Street map
map_tiles_set_tile_type(map_handle, 1); // Satellite
map_tiles_set_tile_type(map_handle, 2); // Terrain
map_tiles_set_tile_type(map_handle, 3); // Hybrid
// Get current tile type
int current_type = map_tiles_get_tile_type(map_handle);
// Get available tile types
int type_count = map_tiles_get_tile_type_count(map_handle);
for (int i = 0; i < type_count; i++) {
const char* folder = map_tiles_get_tile_type_folder(map_handle, i);
printf("Tile type %d: %s\n", i, folder);
}
```
### GPS Coordinate Conversion
```c
// Convert GPS to tile coordinates
double tile_x, tile_y;
map_tiles_gps_to_tile_xy(map_handle, 37.7749, -122.4194, &tile_x, &tile_y);
// Check if GPS position is within current tile grid
bool within_tiles = map_tiles_is_gps_within_tiles(map_handle, 37.7749, -122.4194);
// Get marker offset for precise positioning
int offset_x, offset_y;
map_tiles_get_marker_offset(map_handle, &offset_x, &offset_y);
```
### Memory Management
```c
// Clean up when done
map_tiles_cleanup(map_handle);
```
## Tile File Format
The component expects map tiles in a specific binary format:
- **File Structure**: `{base_path}/{map_tile}/{zoom}/{tile_x}/{tile_y}.bin`
- **Format**: 12-byte header + raw RGB565 pixel data
- **Size**: 256x256 pixels
- **Color Format**: RGB565 (16-bit per pixel)
### Example Tile Structure
```
/sdcard/
├── street_map/ // Tile type 0
│ ├── 10/
│ │ ├── 164/
│ │ │ ├── 395.bin
│ │ │ ├── 396.bin
│ │ │ └── ...
│ │ └── ...
│ └── ...
├── satellite/ // Tile type 1
│ ├── 10/
│ │ ├── 164/
│ │ │ ├── 395.bin
│ │ │ └── ...
│ │ └── ...
│ └── ...
├── terrain/ // Tile type 2
│ └── ...
└── hybrid/ // Tile type 3
└── ...
```
## Configuration Options
| Parameter | Type | Description | Default |
|-----------|------|-------------|---------|
| `base_path` | `const char*` | Base directory for tile storage | Required |
| `tile_folders` | `const char*[]` | Array of folder names for different tile types | Required |
| `tile_type_count` | `int` | Number of tile types (max 8) | Required |
| `default_zoom` | `int` | Initial zoom level | Required |
| `use_spiram` | `bool` | Use SPIRAM for tile buffers | `false` |
| `default_tile_type` | `int` | Initial tile type index | Required |
| `grid_cols` | `int` | Number of tile columns (max 10) | 5 |
| `grid_rows` | `int` | Number of tile rows (max 10) | 5 |
## API Reference
### Initialization
- `map_tiles_init()` - Initialize map tiles system
- `map_tiles_cleanup()` - Clean up resources
### Tile Management
- `map_tiles_load_tile()` - Load a specific tile
- `map_tiles_get_image()` - Get LVGL image descriptor
- `map_tiles_get_buffer()` - Get raw tile buffer
### Grid Management
- `map_tiles_get_grid_size()` - Get current grid dimensions
- `map_tiles_get_tile_count()` - Get total number of tiles in grid
### Coordinate Conversion
- `map_tiles_gps_to_tile_xy()` - Convert GPS to tile coordinates
- `map_tiles_set_center_from_gps()` - Set center from GPS
- `map_tiles_is_gps_within_tiles()` - Check if GPS is within current tiles
### Position Management
- `map_tiles_get_position()` - Get current tile position
- `map_tiles_set_position()` - Set tile position
- `map_tiles_get_marker_offset()` - Get marker offset
- `map_tiles_set_marker_offset()` - Set marker offset
### Tile Type Management
- `map_tiles_set_tile_type()` - Set active tile type
- `map_tiles_get_tile_type()` - Get current tile type
- `map_tiles_get_tile_type_count()` - Get number of available types
- `map_tiles_get_tile_type_folder()` - Get folder name for a type
### Zoom Control
- `map_tiles_set_zoom()` - Set zoom level
- `map_tiles_get_zoom()` - Get current zoom level
### Error Handling
- `map_tiles_set_loading_error()` - Set error state
- `map_tiles_has_loading_error()` - Check error state
## Performance Considerations
- **Memory Usage**: Each tile uses ~128KB (256×256×2 bytes)
- **Grid Size**: Larger grids use more memory (3x3=9 tiles, 5x5=25 tiles, 7x7=49 tiles)
- **SPIRAM**: Recommended for ESP32-S3 with PSRAM for better performance
- **File System**: Ensure adequate file system performance for tile loading
- **Tile Caching**: Component maintains tile buffers until cleanup
## Example Projects
See the `examples` directory for complete implementation examples:
- Basic map display
- GPS tracking with map updates
- Interactive map with touch controls
## License
This component is released under the MIT License. See LICENSE file for details.
## Contributing
Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests.
## Support
For questions and support, please open an issue on the GitHub repository.

286
examples/basic_map_display.c Executable file
View File

@@ -0,0 +1,286 @@
#include "map_tiles.h"
#include "lvgl.h"
#include "esp_log.h"
static const char* TAG = "map_example";
// Map tiles handle
static map_tiles_handle_t map_handle = NULL;
// LVGL objects for displaying tiles
static lv_obj_t* map_container = NULL;
static lv_obj_t** tile_images = NULL; // Dynamic array for configurable grid
static int grid_cols = 0, grid_rows = 0, tile_count = 0;
/**
* @brief Initialize the map display
*/
void map_display_init(void)
{
// Configure map tiles with multiple tile types and custom grid size
const char* tile_folders[] = {"street_map", "satellite", "terrain", "hybrid"};
map_tiles_config_t config = {
.base_path = "/sdcard",
.tile_folders = {tile_folders[0], tile_folders[1], tile_folders[2], tile_folders[3]},
.tile_type_count = 4,
.default_zoom = 10,
.use_spiram = true,
.default_tile_type = 0, // Start with street map
.grid_cols = 5, // 5x5 grid (configurable)
.grid_rows = 5
};
// Initialize map tiles
map_handle = map_tiles_init(&config);
if (!map_handle) {
ESP_LOGE(TAG, "Failed to initialize map tiles");
return;
}
// Get grid dimensions
map_tiles_get_grid_size(map_handle, &grid_cols, &grid_rows);
tile_count = map_tiles_get_tile_count(map_handle);
// Allocate tile images array
tile_images = malloc(tile_count * sizeof(lv_obj_t*));
if (!tile_images) {
ESP_LOGE(TAG, "Failed to allocate tile images array");
map_tiles_cleanup(map_handle);
return;
}
// Create map container
map_container = lv_obj_create(lv_screen_active());
lv_obj_set_size(map_container,
grid_cols * MAP_TILES_TILE_SIZE,
grid_rows * MAP_TILES_TILE_SIZE);
lv_obj_center(map_container);
lv_obj_set_style_pad_all(map_container, 0, 0);
lv_obj_set_style_border_width(map_container, 0, 0);
// Create image widgets for each tile
for (int i = 0; i < tile_count; i++) {
tile_images[i] = lv_image_create(map_container);
// Position tile in grid
int row = i / grid_cols;
int col = i % grid_cols;
lv_obj_set_pos(tile_images[i],
col * MAP_TILES_TILE_SIZE,
row * MAP_TILES_TILE_SIZE);
lv_obj_set_size(tile_images[i], MAP_TILES_TILE_SIZE, MAP_TILES_TILE_SIZE);
}
ESP_LOGI(TAG, "Map display initialized");
}
/**
* @brief Load and display map tiles for a GPS location
*
* @param lat Latitude in degrees
* @param lon Longitude in degrees
*/
void map_display_load_location(double lat, double lon)
{
if (!map_handle) {
ESP_LOGE(TAG, "Map not initialized");
return;
}
ESP_LOGI(TAG, "Loading map for GPS: %.6f, %.6f", lat, lon);
// Set center from GPS coordinates
map_tiles_set_center_from_gps(map_handle, lat, lon);
// Get current tile position
int base_tile_x, base_tile_y;
map_tiles_get_position(map_handle, &base_tile_x, &base_tile_y);
// Load tiles in a configurable grid
for (int row = 0; row < grid_rows; row++) {
for (int col = 0; col < grid_cols; col++) {
int index = row * grid_cols + col;
int tile_x = base_tile_x + col;
int tile_y = base_tile_y + row;
// Load the tile
bool loaded = map_tiles_load_tile(map_handle, index, tile_x, tile_y);
if (loaded) {
// Update the image widget
lv_image_dsc_t* img_dsc = map_tiles_get_image(map_handle, index);
if (img_dsc) {
lv_image_set_src(tile_images[index], img_dsc);
ESP_LOGD(TAG, "Loaded tile %d (%d, %d)", index, tile_x, tile_y);
}
} else {
ESP_LOGW(TAG, "Failed to load tile %d (%d, %d)", index, tile_x, tile_y);
// Set a placeholder or clear the image
lv_image_set_src(tile_images[index], NULL);
}
}
}
ESP_LOGI(TAG, "Map tiles loaded for location");
}
/**
* @brief Set the map tile type and reload tiles
*
* @param tile_type Tile type index (0=street, 1=satellite, 2=terrain, 3=hybrid)
* @param lat Current latitude
* @param lon Current longitude
*/
void map_display_set_tile_type(int tile_type, double lat, double lon)
{
if (!map_handle) {
ESP_LOGE(TAG, "Map not initialized");
return;
}
// Validate tile type
int max_types = map_tiles_get_tile_type_count(map_handle);
if (tile_type < 0 || tile_type >= max_types) {
ESP_LOGW(TAG, "Invalid tile type %d (valid range: 0-%d)", tile_type, max_types - 1);
return;
}
ESP_LOGI(TAG, "Setting tile type to %d (%s)", tile_type,
map_tiles_get_tile_type_folder(map_handle, tile_type));
// Set tile type
if (map_tiles_set_tile_type(map_handle, tile_type)) {
// Reload tiles for the new type
map_display_load_location(lat, lon);
}
}
/**
* @brief Set the zoom level and reload tiles
*
* @param zoom New zoom level
* @param lat Current latitude
* @param lon Current longitude
*/
void map_display_set_zoom(int zoom, double lat, double lon)
{
if (!map_handle) {
ESP_LOGE(TAG, "Map not initialized");
return;
}
ESP_LOGI(TAG, "Setting zoom to %d", zoom);
// Update zoom level
map_tiles_set_zoom(map_handle, zoom);
// Reload tiles for the new zoom level
map_display_load_location(lat, lon);
}
/**
* @brief Add a GPS marker to the map
*
* @param lat Latitude in degrees
* @param lon Longitude in degrees
*/
void map_display_add_marker(double lat, double lon)
{
if (!map_handle) {
ESP_LOGE(TAG, "Map not initialized");
return;
}
// Check if GPS position is within current tiles
if (!map_tiles_is_gps_within_tiles(map_handle, lat, lon)) {
ESP_LOGW(TAG, "GPS position outside current tiles, reloading map");
map_display_load_location(lat, lon);
return;
}
// Get marker offset within the current tile grid
int offset_x, offset_y;
map_tiles_get_marker_offset(map_handle, &offset_x, &offset_y);
// Create or update marker object
static lv_obj_t* marker = NULL;
if (!marker) {
marker = lv_obj_create(map_container);
lv_obj_set_size(marker, 10, 10);
lv_obj_set_style_bg_color(marker, lv_color_hex(0xFF0000), 0);
lv_obj_set_style_radius(marker, 5, 0);
lv_obj_set_style_border_width(marker, 1, 0);
lv_obj_set_style_border_color(marker, lv_color_hex(0xFFFFFF), 0);
}
// Position marker based on GPS offset
int center_tile_col = grid_cols / 2;
int center_tile_row = grid_rows / 2;
int marker_x = center_tile_col * MAP_TILES_TILE_SIZE + offset_x - 5;
int marker_y = center_tile_row * MAP_TILES_TILE_SIZE + offset_y - 5;
lv_obj_set_pos(marker, marker_x, marker_y);
ESP_LOGI(TAG, "GPS marker positioned at (%d, %d)", marker_x, marker_y);
}
/**
* @brief Clean up map display resources
*/
void map_display_cleanup(void)
{
if (tile_images) {
free(tile_images);
tile_images = NULL;
}
if (map_handle) {
map_tiles_cleanup(map_handle);
map_handle = NULL;
}
if (map_container) {
lv_obj_delete(map_container);
map_container = NULL;
}
grid_cols = grid_rows = tile_count = 0;
ESP_LOGI(TAG, "Map display cleaned up");
}
/**
* @brief Example usage in main application
*/
void app_main(void)
{
// Initialize LVGL and display driver first...
// Initialize map display
map_display_init();
// Load map for San Francisco
double lat = 37.7749;
double lon = -122.4194;
map_display_load_location(lat, lon);
// Add GPS marker
map_display_add_marker(lat, lon);
// Example: Change to satellite view (tile type 1)
// map_display_set_tile_type(1, lat, lon);
// Example: Change to terrain view (tile type 2)
// map_display_set_tile_type(2, lat, lon);
// Example: Change zoom level
// map_display_set_zoom(12, lat, lon);
// Example: Update GPS position
// map_display_add_marker(37.7849, -122.4094);
// NOTE: To use different grid sizes, modify the grid_cols and grid_rows
// in the config structure above. Examples:
// - 3x3 grid: .grid_cols = 3, .grid_rows = 3 (9 tiles, ~1.1MB RAM)
// - 5x5 grid: .grid_cols = 5, .grid_rows = 5 (25 tiles, ~3.1MB RAM)
// - 7x7 grid: .grid_cols = 7, .grid_rows = 7 (49 tiles, ~6.1MB RAM)
}

27
idf_component.yml Executable file
View File

@@ -0,0 +1,27 @@
version: "1.1.0"
maintainers:
- Eric Nam <thatprojectstudio@gmail.com>
description: "Map tiles component for LVGL 9.x - Load and display map tiles with GPS coordinate conversion"
url: "https://github.com/0015/map_tiles"
license: "MIT"
dependencies:
lvgl/lvgl: ">=9.3.0"
idf: ">=5.0"
targets:
- esp32
- esp32s2
- esp32s3
- esp32c3
- esp32c6
- esp32h2
- esp32p4
keywords:
- lvgl
- map
- tiles
- gps
- graphics
- ui

264
include/map_tiles.h Executable file
View File

@@ -0,0 +1,264 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "lvgl.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Map tiles component for LVGL 9.x
*
* This component provides functionality to load and display map tiles with GPS coordinate conversion.
* Tiles are assumed to be 256x256 pixels in RGB565 format, stored in binary files.
*/
// Constants
#define MAP_TILES_TILE_SIZE 256
#define MAP_TILES_DEFAULT_GRID_COLS 5
#define MAP_TILES_DEFAULT_GRID_ROWS 5
#define MAP_TILES_MAX_GRID_COLS 9
#define MAP_TILES_MAX_GRID_ROWS 9
#define MAP_TILES_MAX_TILES (MAP_TILES_MAX_GRID_COLS * MAP_TILES_MAX_GRID_ROWS)
#define MAP_TILES_BYTES_PER_PIXEL 2
#define MAP_TILES_COLOR_FORMAT LV_COLOR_FORMAT_RGB565
#define MAP_TILES_MAX_TYPES 8
#define MAP_TILES_MAX_FOLDER_NAME 32
/**
* @brief Configuration structure for map tiles
*/
typedef struct {
const char* base_path; /**< Base path where tiles are stored (e.g., "/sdcard") */
const char* tile_folders[MAP_TILES_MAX_TYPES]; /**< Array of folder names for different tile types */
int tile_type_count; /**< Number of tile types configured */
int grid_cols; /**< Number of tile columns (default: 5, max: 9) */
int grid_rows; /**< Number of tile rows (default: 5, max: 9) */
int default_zoom; /**< Default zoom level */
bool use_spiram; /**< Whether to use SPIRAM for tile buffers */
int default_tile_type; /**< Default tile type index (0 to tile_type_count-1) */
} map_tiles_config_t;
/**
* @brief Map tiles handle
*/
typedef struct map_tiles_t* map_tiles_handle_t;
/**
* @brief Initialize the map tiles system
*
* @param config Configuration structure
* @return map_tiles_handle_t Handle to the map tiles instance, NULL on failure
*/
map_tiles_handle_t map_tiles_init(const map_tiles_config_t* config);
/**
* @brief Set the zoom level
*
* @param handle Map tiles handle
* @param zoom_level Zoom level (typically 0-18)
*/
void map_tiles_set_zoom(map_tiles_handle_t handle, int zoom_level);
/**
* @brief Get the current zoom level
*
* @param handle Map tiles handle
* @return Current zoom level
*/
int map_tiles_get_zoom(map_tiles_handle_t handle);
/**
* @brief Set tile type
*
* @param handle Map tiles handle
* @param tile_type Tile type index (0 to configured tile_type_count-1)
* @return true if tile type was set successfully, false otherwise
*/
bool map_tiles_set_tile_type(map_tiles_handle_t handle, int tile_type);
/**
* @brief Get current tile type
*
* @param handle Map tiles handle
* @return Current tile type index, -1 if error
*/
int map_tiles_get_tile_type(map_tiles_handle_t handle);
/**
* @brief Get grid dimensions
*
* @param handle Map tiles handle
* @param cols Output grid columns (can be NULL)
* @param rows Output grid rows (can be NULL)
*/
void map_tiles_get_grid_size(map_tiles_handle_t handle, int* cols, int* rows);
/**
* @brief Get total tile count for current grid
*
* @param handle Map tiles handle
* @return Total number of tiles in the grid, 0 if error
*/
int map_tiles_get_tile_count(map_tiles_handle_t handle);
/**
* @brief Get tile type count
*
* @param handle Map tiles handle
* @return Number of configured tile types, 0 if error
*/
int map_tiles_get_tile_type_count(map_tiles_handle_t handle);
/**
* @brief Get tile type folder name
*
* @param handle Map tiles handle
* @param tile_type Tile type index
* @return Folder name for the tile type, NULL if invalid
*/
const char* map_tiles_get_tile_type_folder(map_tiles_handle_t handle, int tile_type);
/**
* @brief Load a specific tile dynamically
*
* @param handle Map tiles handle
* @param index Tile index (0 to total_tile_count-1)
* @param tile_x Tile X coordinate
* @param tile_y Tile Y coordinate
* @return true if tile loaded successfully, false otherwise
*/
bool map_tiles_load_tile(map_tiles_handle_t handle, int index, int tile_x, int tile_y);
/**
* @brief Convert GPS coordinates to tile coordinates
*
* @param handle Map tiles handle
* @param lat Latitude in degrees
* @param lon Longitude in degrees
* @param x Output tile X coordinate
* @param y Output tile Y coordinate
*/
void map_tiles_gps_to_tile_xy(map_tiles_handle_t handle, double lat, double lon, double* x, double* y);
/**
* @brief Convert tile coordinates to GPS coordinates
*
* @param handle Map tiles handle
* @param x Tile X coordinate
* @param y Tile Y coordinate
* @param lat Output latitude in degrees
* @param lon Output longitude in degrees
*/
void map_tiles_tile_xy_to_gps(map_tiles_handle_t handle, double x, double y, double* lat, double* lon);
/**
* @brief Get center GPS coordinates of current map view
*
* @param handle Map tiles handle
* @param lat Output latitude in degrees
* @param lon Output longitude in degrees
*/
void map_tiles_get_center_gps(map_tiles_handle_t handle, double* lat, double* lon);
/**
* @brief Set the tile center from GPS coordinates
*
* @param handle Map tiles handle
* @param lat Latitude in degrees
* @param lon Longitude in degrees
*/
void map_tiles_set_center_from_gps(map_tiles_handle_t handle, double lat, double lon);
/**
* @brief Check if GPS coordinates are within current tile grid
*
* @param handle Map tiles handle
* @param lat Latitude in degrees
* @param lon Longitude in degrees
* @return true if GPS position is within current tiles, false otherwise
*/
bool map_tiles_is_gps_within_tiles(map_tiles_handle_t handle, double lat, double lon);
/**
* @brief Get current tile position
*
* @param handle Map tiles handle
* @param tile_x Output tile X coordinate (can be NULL)
* @param tile_y Output tile Y coordinate (can be NULL)
*/
void map_tiles_get_position(map_tiles_handle_t handle, int* tile_x, int* tile_y);
/**
* @brief Set tile position
*
* @param handle Map tiles handle
* @param tile_x Tile X coordinate
* @param tile_y Tile Y coordinate
*/
void map_tiles_set_position(map_tiles_handle_t handle, int tile_x, int tile_y);
/**
* @brief Get marker offset within the current tile
*
* @param handle Map tiles handle
* @param offset_x Output X offset in pixels (can be NULL)
* @param offset_y Output Y offset in pixels (can be NULL)
*/
void map_tiles_get_marker_offset(map_tiles_handle_t handle, int* offset_x, int* offset_y);
/**
* @brief Set marker offset within the current tile
*
* @param handle Map tiles handle
* @param offset_x X offset in pixels
* @param offset_y Y offset in pixels
*/
void map_tiles_set_marker_offset(map_tiles_handle_t handle, int offset_x, int offset_y);
/**
* @brief Get tile image descriptor
*
* @param handle Map tiles handle
* @param index Tile index (0 to total_tile_count-1)
* @return Pointer to LVGL image descriptor, NULL if invalid
*/
lv_image_dsc_t* map_tiles_get_image(map_tiles_handle_t handle, int index);
/**
* @brief Get tile buffer
*
* @param handle Map tiles handle
* @param index Tile index (0 to total_tile_count-1)
* @return Pointer to tile buffer, NULL if invalid
*/
uint8_t* map_tiles_get_buffer(map_tiles_handle_t handle, int index);
/**
* @brief Set tile loading error state
*
* @param handle Map tiles handle
* @param error Error state
*/
void map_tiles_set_loading_error(map_tiles_handle_t handle, bool error);
/**
* @brief Check if there's a tile loading error
*
* @param handle Map tiles handle
* @return true if there's an error, false otherwise
*/
bool map_tiles_has_loading_error(map_tiles_handle_t handle);
/**
* @brief Clean up and free map tiles resources
*
* @param handle Map tiles handle
*/
void map_tiles_cleanup(map_tiles_handle_t handle);
#ifdef __cplusplus
}
#endif

509
map_tiles.cpp Executable file
View File

@@ -0,0 +1,509 @@
#include "map_tiles.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "esp_log.h"
#include "esp_heap_caps.h"
static const char* TAG = "map_tiles";
// Internal structure for map tiles instance
struct map_tiles_t {
// Configuration
char* base_path;
char* tile_folders[MAP_TILES_MAX_TYPES];
int tile_type_count;
int current_tile_type;
int grid_cols;
int grid_rows;
int tile_count;
int zoom;
bool use_spiram;
bool initialized;
// Tile management
int tile_x;
int tile_y;
int marker_offset_x;
int marker_offset_y;
bool tile_loading_error;
// Tile data - arrays will be allocated dynamically based on actual grid size
uint8_t** tile_bufs;
lv_image_dsc_t* tile_imgs;
};
map_tiles_handle_t map_tiles_init(const map_tiles_config_t* config)
{
if (!config || !config->base_path || config->tile_type_count <= 0 ||
config->tile_type_count > MAP_TILES_MAX_TYPES ||
config->default_tile_type < 0 || config->default_tile_type >= config->tile_type_count) {
ESP_LOGE(TAG, "Invalid configuration");
return NULL;
}
// Validate grid size - use defaults if not specified or invalid
int grid_cols = config->grid_cols;
int grid_rows = config->grid_rows;
if (grid_cols <= 0 || grid_cols > MAP_TILES_MAX_GRID_COLS) {
ESP_LOGW(TAG, "Invalid grid_cols %d, using default %d", grid_cols, MAP_TILES_DEFAULT_GRID_COLS);
grid_cols = MAP_TILES_DEFAULT_GRID_COLS;
}
if (grid_rows <= 0 || grid_rows > MAP_TILES_MAX_GRID_ROWS) {
ESP_LOGW(TAG, "Invalid grid_rows %d, using default %d", grid_rows, MAP_TILES_DEFAULT_GRID_ROWS);
grid_rows = MAP_TILES_DEFAULT_GRID_ROWS;
}
int tile_count = grid_cols * grid_rows;
// Validate that all tile folders are provided
for (int i = 0; i < config->tile_type_count; i++) {
if (!config->tile_folders[i]) {
ESP_LOGE(TAG, "Tile folder %d is NULL", i);
return NULL;
}
}
map_tiles_handle_t handle = (map_tiles_handle_t)calloc(1, sizeof(struct map_tiles_t));
if (!handle) {
ESP_LOGE(TAG, "Failed to allocate handle");
return NULL;
}
// Copy base path
handle->base_path = strdup(config->base_path);
if (!handle->base_path) {
ESP_LOGE(TAG, "Failed to allocate base path");
free(handle);
return NULL;
}
// Copy tile folder names
handle->tile_type_count = config->tile_type_count;
for (int i = 0; i < config->tile_type_count; i++) {
handle->tile_folders[i] = strdup(config->tile_folders[i]);
if (!handle->tile_folders[i]) {
ESP_LOGE(TAG, "Failed to allocate tile folder %d", i);
// Clean up previously allocated folders
for (int j = 0; j < i; j++) {
free(handle->tile_folders[j]);
}
free(handle->base_path);
free(handle);
return NULL;
}
}
handle->zoom = config->default_zoom;
handle->use_spiram = config->use_spiram;
handle->current_tile_type = config->default_tile_type;
handle->grid_cols = grid_cols;
handle->grid_rows = grid_rows;
handle->tile_count = tile_count;
handle->initialized = true;
handle->tile_loading_error = false;
// Initialize tile data - allocate arrays based on actual tile count
handle->tile_bufs = (uint8_t**)calloc(tile_count, sizeof(uint8_t*));
handle->tile_imgs = (lv_image_dsc_t*)calloc(tile_count, sizeof(lv_image_dsc_t));
if (!handle->tile_bufs || !handle->tile_imgs) {
ESP_LOGE(TAG, "Failed to allocate tile arrays");
// Clean up
if (handle->tile_bufs) free(handle->tile_bufs);
if (handle->tile_imgs) free(handle->tile_imgs);
for (int i = 0; i < handle->tile_type_count; i++) {
free(handle->tile_folders[i]);
}
free(handle->base_path);
free(handle);
return NULL;
}
ESP_LOGI(TAG, "Map tiles initialized with base path: %s, %d tile types, current type: %s, zoom: %d, grid: %dx%d",
handle->base_path, handle->tile_type_count,
handle->tile_folders[handle->current_tile_type], handle->zoom,
handle->grid_cols, handle->grid_rows);
return handle;
}
void map_tiles_set_zoom(map_tiles_handle_t handle, int zoom_level)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return;
}
handle->zoom = zoom_level;
ESP_LOGI(TAG, "Zoom level set to %d", zoom_level);
}
int map_tiles_get_zoom(map_tiles_handle_t handle)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return 0;
}
return handle->zoom;
}
bool map_tiles_set_tile_type(map_tiles_handle_t handle, int tile_type)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return false;
}
if (tile_type < 0 || tile_type >= handle->tile_type_count) {
ESP_LOGE(TAG, "Invalid tile type: %d (valid range: 0-%d)", tile_type, handle->tile_type_count - 1);
return false;
}
handle->current_tile_type = tile_type;
ESP_LOGI(TAG, "Tile type set to %d (%s)", tile_type, handle->tile_folders[tile_type]);
return true;
}
int map_tiles_get_tile_type(map_tiles_handle_t handle)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return -1;
}
return handle->current_tile_type;
}
int map_tiles_get_tile_type_count(map_tiles_handle_t handle)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return 0;
}
return handle->tile_type_count;
}
const char* map_tiles_get_tile_type_folder(map_tiles_handle_t handle, int tile_type)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return NULL;
}
if (tile_type < 0 || tile_type >= handle->tile_type_count) {
ESP_LOGE(TAG, "Invalid tile type: %d", tile_type);
return NULL;
}
return handle->tile_folders[tile_type];
}
bool map_tiles_load_tile(map_tiles_handle_t handle, int index, int tile_x, int tile_y)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return false;
}
if (index < 0 || index >= handle->tile_count) {
ESP_LOGE(TAG, "Invalid tile index: %d", index);
return false;
}
char path[256];
const char* folder = handle->tile_folders[handle->current_tile_type];
snprintf(path, sizeof(path), "%s/%s/%d/%d/%d.bin",
handle->base_path, folder, handle->zoom, tile_x, tile_y);
FILE *f = fopen(path, "rb");
if (!f) {
ESP_LOGW(TAG, "Tile not found: %s", path);
return false;
}
// Skip 12-byte header
fseek(f, 12, SEEK_SET);
// Allocate buffer if needed
if (!handle->tile_bufs[index]) {
uint32_t caps = handle->use_spiram ? (MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT) : MALLOC_CAP_DMA;
handle->tile_bufs[index] = (uint8_t*)heap_caps_malloc(
MAP_TILES_TILE_SIZE * MAP_TILES_TILE_SIZE * MAP_TILES_BYTES_PER_PIXEL, caps);
if (!handle->tile_bufs[index]) {
ESP_LOGE(TAG, "Tile %d: allocation failed", index);
fclose(f);
return false;
}
}
// Clear buffer
memset(handle->tile_bufs[index], 0,
MAP_TILES_TILE_SIZE * MAP_TILES_TILE_SIZE * MAP_TILES_BYTES_PER_PIXEL);
// Read tile data
size_t bytes_read = fread(handle->tile_bufs[index], 1,
MAP_TILES_TILE_SIZE * MAP_TILES_TILE_SIZE * MAP_TILES_BYTES_PER_PIXEL, f);
fclose(f);
if (bytes_read != MAP_TILES_TILE_SIZE * MAP_TILES_TILE_SIZE * MAP_TILES_BYTES_PER_PIXEL) {
ESP_LOGW(TAG, "Incomplete tile read: %zu bytes", bytes_read);
}
// Setup image descriptor
handle->tile_imgs[index].header.w = MAP_TILES_TILE_SIZE;
handle->tile_imgs[index].header.h = MAP_TILES_TILE_SIZE;
handle->tile_imgs[index].header.cf = MAP_TILES_COLOR_FORMAT;
handle->tile_imgs[index].header.stride = MAP_TILES_TILE_SIZE * MAP_TILES_BYTES_PER_PIXEL;
handle->tile_imgs[index].data = (const uint8_t*)handle->tile_bufs[index];
handle->tile_imgs[index].data_size = MAP_TILES_TILE_SIZE * MAP_TILES_TILE_SIZE * MAP_TILES_BYTES_PER_PIXEL;
handle->tile_imgs[index].reserved = NULL;
handle->tile_imgs[index].reserved_2 = NULL;
ESP_LOGD(TAG, "Loaded tile %d from %s", index, path);
return true;
}
void map_tiles_gps_to_tile_xy(map_tiles_handle_t handle, double lat, double lon, double* x, double* y)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return;
}
if (!x || !y) {
ESP_LOGE(TAG, "Invalid output parameters");
return;
}
double lat_rad = lat * M_PI / 180.0;
int n = 1 << handle->zoom;
*x = (lon + 180.0) / 360.0 * n;
*y = (1.0 - log(tan(lat_rad) + 1.0 / cos(lat_rad)) / M_PI) / 2.0 * n;
}
void map_tiles_tile_xy_to_gps(map_tiles_handle_t handle, double x, double y, double* lat, double* lon)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return;
}
if (!lat || !lon) {
ESP_LOGE(TAG, "Invalid output parameters");
return;
}
int n = 1 << handle->zoom;
*lon = x / n * 360.0 - 180.0;
double lat_rad = atan(sinh(M_PI * (1 - 2 * y / n)));
*lat = lat_rad * 180.0 / M_PI;
}
void map_tiles_get_center_gps(map_tiles_handle_t handle, double* lat, double* lon)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return;
}
if (!lat || !lon) {
ESP_LOGE(TAG, "Invalid output parameters");
return;
}
// Calculate center tile coordinates (center of the grid)
double center_x = handle->tile_x + handle->grid_cols / 2.0;
double center_y = handle->tile_y + handle->grid_rows / 2.0;
// Convert to GPS coordinates
map_tiles_tile_xy_to_gps(handle, center_x, center_y, lat, lon);
}
void map_tiles_set_center_from_gps(map_tiles_handle_t handle, double lat, double lon)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return;
}
double x, y;
map_tiles_gps_to_tile_xy(handle, lat, lon, &x, &y);
handle->tile_x = (int)x - handle->grid_cols / 2;
handle->tile_y = (int)y - handle->grid_rows / 2;
// Calculate pixel offset within the tile
handle->marker_offset_x = (int)((x - (int)x) * MAP_TILES_TILE_SIZE);
handle->marker_offset_y = (int)((y - (int)y) * MAP_TILES_TILE_SIZE);
ESP_LOGI(TAG, "GPS to tile: tile_x=%d, tile_y=%d, offset_x=%d, offset_y=%d",
handle->tile_x, handle->tile_y, handle->marker_offset_x, handle->marker_offset_y);
}
bool map_tiles_is_gps_within_tiles(map_tiles_handle_t handle, double lat, double lon)
{
if (!handle || !handle->initialized) {
return false;
}
double x, y;
map_tiles_gps_to_tile_xy(handle, lat, lon, &x, &y);
int gps_tile_x = (int)x;
int gps_tile_y = (int)y;
bool within_x = (gps_tile_x >= handle->tile_x && gps_tile_x < handle->tile_x + handle->grid_cols);
bool within_y = (gps_tile_y >= handle->tile_y && gps_tile_y < handle->tile_y + handle->grid_rows);
return within_x && within_y;
}
void map_tiles_get_position(map_tiles_handle_t handle, int* tile_x, int* tile_y)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return;
}
if (tile_x) *tile_x = handle->tile_x;
if (tile_y) *tile_y = handle->tile_y;
}
void map_tiles_set_position(map_tiles_handle_t handle, int tile_x, int tile_y)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return;
}
handle->tile_x = tile_x;
handle->tile_y = tile_y;
}
void map_tiles_get_marker_offset(map_tiles_handle_t handle, int* offset_x, int* offset_y)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return;
}
if (offset_x) *offset_x = handle->marker_offset_x;
if (offset_y) *offset_y = handle->marker_offset_y;
}
void map_tiles_set_marker_offset(map_tiles_handle_t handle, int offset_x, int offset_y)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return;
}
handle->marker_offset_x = offset_x;
handle->marker_offset_y = offset_y;
}
lv_image_dsc_t* map_tiles_get_image(map_tiles_handle_t handle, int index)
{
if (!handle || !handle->initialized || index < 0 || index >= handle->tile_count) {
return NULL;
}
return &handle->tile_imgs[index];
}
uint8_t* map_tiles_get_buffer(map_tiles_handle_t handle, int index)
{
if (!handle || !handle->initialized || index < 0 || index >= handle->tile_count) {
return NULL;
}
return handle->tile_bufs[index];
}
void map_tiles_set_loading_error(map_tiles_handle_t handle, bool error)
{
if (!handle || !handle->initialized) {
ESP_LOGE(TAG, "Handle not initialized");
return;
}
handle->tile_loading_error = error;
}
bool map_tiles_has_loading_error(map_tiles_handle_t handle)
{
if (!handle || !handle->initialized) {
return true;
}
return handle->tile_loading_error;
}
void map_tiles_cleanup(map_tiles_handle_t handle)
{
if (!handle) {
return;
}
if (handle->initialized) {
// Free tile buffers
if (handle->tile_bufs) {
for (int i = 0; i < handle->tile_count; i++) {
if (handle->tile_bufs[i]) {
heap_caps_free(handle->tile_bufs[i]);
handle->tile_bufs[i] = NULL;
}
}
free(handle->tile_bufs);
handle->tile_bufs = NULL;
}
// Free tile image descriptors array
if (handle->tile_imgs) {
free(handle->tile_imgs);
handle->tile_imgs = NULL;
}
handle->initialized = false;
ESP_LOGI(TAG, "Map tiles cleaned up");
}
// Free base path and folder names, then handle
if (handle->base_path) {
free(handle->base_path);
}
for (int i = 0; i < handle->tile_type_count; i++) {
if (handle->tile_folders[i]) {
free(handle->tile_folders[i]);
}
}
free(handle);
}
void map_tiles_get_grid_size(map_tiles_handle_t handle, int* cols, int* rows)
{
if (!handle || !handle->initialized || !cols || !rows) {
if (cols) *cols = 0;
if (rows) *rows = 0;
return;
}
*cols = handle->grid_cols;
*rows = handle->grid_rows;
}
int map_tiles_get_tile_count(map_tiles_handle_t handle)
{
if (!handle || !handle->initialized) {
return 0;
}
return handle->tile_count;
}