diff --git a/README.md b/README.md index 49579e95..ae6ab789 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Expected SD card tile layout: ```text /maps/base/osm/{z}/{x}/{y}.png /maps/base/terrain/{z}/{x}/{y}.png -/maps/base/satellite/{z}/{x}/{y}.jpg +/maps/base/satellite/{z}/{x}/{y}.png /maps/contour/major-500/{z}/{x}/{y}.png /maps/contour/major-200/{z}/{x}/{y}.png /maps/contour/major-100/{z}/{x}/{y}.png diff --git a/README_CN.md b/README_CN.md index 3e0df8b6..35bb9b58 100644 --- a/README_CN.md +++ b/README_CN.md @@ -60,7 +60,7 @@ SD 卡瓦片目录结构示例: ```text /maps/base/osm/{z}/{x}/{y}.png /maps/base/terrain/{z}/{x}/{y}.png -/maps/base/satellite/{z}/{x}/{y}.jpg +/maps/base/satellite/{z}/{x}/{y}.png /maps/contour/major-500/{z}/{x}/{y}.png /maps/contour/major-200/{z}/{x}/{y}.png /maps/contour/major-100/{z}/{x}/{y}.png diff --git a/docs/audits/MAP_TILE_SOURCE_CACHE_OWNERSHIP_AUDIT.md b/docs/audits/MAP_TILE_SOURCE_CACHE_OWNERSHIP_AUDIT.md index fb82d9b9..5250b9fd 100644 --- a/docs/audits/MAP_TILE_SOURCE_CACHE_OWNERSHIP_AUDIT.md +++ b/docs/audits/MAP_TILE_SOURCE_CACHE_OWNERSHIP_AUDIT.md @@ -25,7 +25,7 @@ The first burn-down slice moves Trail Mate offline tile directory mapping behind | --- | --- | | OSM | `/maps/base/osm/{z}/{x}/{y}.png` | | Terrain | `/maps/base/terrain/{z}/{x}/{y}.png` | -| Satellite | `/maps/base/satellite/{z}/{x}/{y}.jpg` | +| Satellite | `/maps/base/satellite/{z}/{x}/{y}.png` | | Contour major 500 | `/maps/contour/major-500/{z}/{x}/{y}.png` | | Contour major 200 | `/maps/contour/major-200/{z}/{x}/{y}.png` | | Contour major 100 | `/maps/contour/major-100/{z}/{x}/{y}.png` | diff --git a/docs/map/SD_CARD_MAP_STRUCTURE_CN.md b/docs/map/SD_CARD_MAP_STRUCTURE_CN.md index 2873073e..672c4b7f 100644 --- a/docs/map/SD_CARD_MAP_STRUCTURE_CN.md +++ b/docs/map/SD_CARD_MAP_STRUCTURE_CN.md @@ -23,16 +23,16 @@ ├── terrain │ └── {z}/{x}/{y}.png └── satellite - └── {z}/{x}/{y}.jpg + └── {z}/{x}/{y}.png ``` ### 底图源与文件后缀 - `osm` -> `.png` - `terrain` -> `.png` -- `satellite` -> `.jpg` +- `satellite` -> `.png` -注意:后缀必须匹配,`satellite` 目录下放 `.png` 会导致读取失败。 +注意:所有底图源都必须使用 `.png` 后缀。 ## 3. 可选目录(等高线叠加) @@ -107,9 +107,9 @@ ## 7. 代码参考 -- 地图源与后缀映射:`platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp` -- 底图路径构造:`platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp` -- 等高线路径构造与 profile 选择:`platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp` +- 地图源与后缀映射:`modules/ui_map_runtime/src/map_tiles/map_tile_resolver.cpp` +- 底图路径构造:`modules/ui_map_runtime/src/map_tiles/map_tile_resolver.cpp` +- 等高线路径构造与 profile 选择:`modules/ui_map_runtime/src/map_tiles/map_tile_resolver.cpp` - 缩放范围:`platform/esp/arduino_common/include/ui/screens/gps/gps_constants.h` - Route 列表目录:`modules/ui_shared/src/ui/screens/tracker/tracker_page_components.cpp` - Track/Route 文件入口:`modules/ui_shared/src/ui/screens/tracker/tracker_page_components.cpp` diff --git a/docs/specification/MAP_TILE_SOURCE_CACHE_RUNTIME_SPEC.md b/docs/specification/MAP_TILE_SOURCE_CACHE_RUNTIME_SPEC.md index 6278f0df..68023755 100644 --- a/docs/specification/MAP_TILE_SOURCE_CACHE_RUNTIME_SPEC.md +++ b/docs/specification/MAP_TILE_SOURCE_CACHE_RUNTIME_SPEC.md @@ -22,7 +22,7 @@ from an input callback, LVGL timer, GTK callback, or page render callback. | --- | --- | --- | --- | | `MapTileRef` | Value Object | Identify layer/z/x/y | Filesystem path, LVGL object | | `MapTileLayer` | Layer key | Identify OSM/Terrain/Satellite/Contour layers | Renderer details | -| `MapTileFormat` | Format enum | Identify PNG/JPEG payload format | Decode ownership | +| `MapTileFormat` | Format enum | Identify PNG payload format | Decode ownership | | `MapTilePayload` | Data DTO | Optional byte payload returned by a source | Filesystem path ownership | | `IMapTileSource` | Port | Lookup/read tile payload by `MapTileRef` | LVGL rendering | | `IMapTileFileSystem` | Port | Filesystem operations needed by filesystem source | Tile path policy | @@ -36,7 +36,7 @@ from an input callback, LVGL timer, GTK callback, or page render callback. - `MapTileLayer::Osm` -> `/maps/base/osm/{z}/{x}/{y}.png` - `MapTileLayer::Terrain` -> `/maps/base/terrain/{z}/{x}/{y}.png` -- `MapTileLayer::Satellite` -> `/maps/base/satellite/{z}/{x}/{y}.jpg` +- `MapTileLayer::Satellite` -> `/maps/base/satellite/{z}/{x}/{y}.png` - `MapTileLayer::ContourMajor500` -> `/maps/contour/major-500/{z}/{x}/{y}.png` - `MapTileLayer::ContourMajor200` -> `/maps/contour/major-200/{z}/{x}/{y}.png` - `MapTileLayer::ContourMajor100` -> `/maps/contour/major-100/{z}/{x}/{y}.png` diff --git a/modules/ui_map_runtime/include/ui_map_runtime/map_tiles/map_tile_types.h b/modules/ui_map_runtime/include/ui_map_runtime/map_tiles/map_tile_types.h index a29b9d17..fd6179e2 100644 --- a/modules/ui_map_runtime/include/ui_map_runtime/map_tiles/map_tile_types.h +++ b/modules/ui_map_runtime/include/ui_map_runtime/map_tiles/map_tile_types.h @@ -29,7 +29,6 @@ enum class MapTileFormat : uint8_t { Unknown, Png, - Jpeg, }; enum class MapTileStatus : uint8_t diff --git a/modules/ui_map_runtime/src/map_tiles/map_tile_resolver.cpp b/modules/ui_map_runtime/src/map_tiles/map_tile_resolver.cpp index 13baa983..b209a658 100644 --- a/modules/ui_map_runtime/src/map_tiles/map_tile_resolver.cpp +++ b/modules/ui_map_runtime/src/map_tiles/map_tile_resolver.cpp @@ -55,7 +55,8 @@ const char* contourLayerDir(MapTileLayer layer) const char* extensionFor(MapTileLayer layer) { - return mapTileFormatForLayer(layer) == MapTileFormat::Jpeg ? "jpg" : "png"; + (void)layer; + return "png"; } bool writePath(char* out_path, @@ -190,7 +191,8 @@ MapTileLayer mapTileContourLayerForZoom(int zoom, bool* out_supported) MapTileFormat mapTileFormatForLayer(MapTileLayer layer) { - return layer == MapTileLayer::Satellite ? MapTileFormat::Jpeg : MapTileFormat::Png; + (void)layer; + return MapTileFormat::Png; } bool mapTileLayerIsContour(MapTileLayer layer) diff --git a/modules/ui_map_runtime/tests/test_filesystem_map_tile_source.cpp b/modules/ui_map_runtime/tests/test_filesystem_map_tile_source.cpp index 14eb7a3a..6b3ac6c9 100644 --- a/modules/ui_map_runtime/tests/test_filesystem_map_tile_source.cpp +++ b/modules/ui_map_runtime/tests/test_filesystem_map_tile_source.cpp @@ -114,7 +114,7 @@ void test_resolve_path() char path[160]{}; assert(source.resolvePath(ref, path, sizeof(path))); - assert(std::strcmp(path, "A:/maps/base/satellite/9/82/190.jpg") == 0); + assert(std::strcmp(path, "A:/maps/base/satellite/9/82/190.png") == 0); } } // namespace diff --git a/modules/ui_map_runtime/tests/test_map_tile_resolver.cpp b/modules/ui_map_runtime/tests/test_map_tile_resolver.cpp index 8a30f99e..5357e7d4 100644 --- a/modules/ui_map_runtime/tests/test_map_tile_resolver.cpp +++ b/modules/ui_map_runtime/tests/test_map_tile_resolver.cpp @@ -21,7 +21,7 @@ void test_base_paths() ref.layer = ui::map_tiles::MapTileLayer::Satellite; assert(resolver.resolvePath(ref, path, sizeof(path))); - assert(std::strcmp(path, "/sd/maps/base/satellite/12/656/1582.jpg") == 0); + assert(std::strcmp(path, "/sd/maps/base/satellite/12/656/1582.png") == 0); ui::map_tiles::MapTileResolver sd_root_resolver("/"); ref.layer = ui::map_tiles::MapTileLayer::Osm; @@ -65,6 +65,9 @@ void test_layer_helpers() ui::map_tiles::MapTileLayer::Terrain); assert(ui::map_tiles::mapTileLayerFromBaseSource(2) == ui::map_tiles::MapTileLayer::Satellite); + assert(ui::map_tiles::mapTileFormatForLayer( + ui::map_tiles::MapTileLayer::Satellite) == + ui::map_tiles::MapTileFormat::Png); bool supported = false; assert(ui::map_tiles::mapTileContourLayerForZoom(8, &supported) == diff --git a/platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp b/platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp index 0c841a21..9577be46 100644 --- a/platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp +++ b/platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp @@ -11,7 +11,6 @@ #include "platform/esp/arduino_common/storage/sd_card_runtime.h" #include "platform/esp/common/shared_spi_lock.h" #include "src/draw/lv_image_decoder_private.h" -#include "src/libs/tjpgd/tjpgd.h" #include "src/misc/cache/instance/lv_image_cache.h" #include "sys/clock.h" #include "ui/page/page_profile.h" @@ -330,8 +329,6 @@ const char* map_tile_format_name(ui::map_tiles::MapTileFormat format) { case ui::map_tiles::MapTileFormat::Png: return "png"; - case ui::map_tiles::MapTileFormat::Jpeg: - return "jpeg"; case ui::map_tiles::MapTileFormat::Unknown: default: return "unknown"; @@ -404,8 +401,6 @@ lv_color_format_t lvgl_source_format_for_tile(ui::map_tiles::MapTileFormat forma { case ui::map_tiles::MapTileFormat::Png: return LV_COLOR_FORMAT_RAW_ALPHA; - case ui::map_tiles::MapTileFormat::Jpeg: - return LV_COLOR_FORMAT_RAW; case ui::map_tiles::MapTileFormat::Unknown: default: return LV_COLOR_FORMAT_UNKNOWN; @@ -484,192 +479,6 @@ void release_tile_payload(ui::map_tiles::MapTileAsyncEvent& event) event.payload_size = 0; } -#if LV_USE_TJPGD -constexpr std::size_t kMapTileJpegWorkBufferBytes = 4096U; - -constexpr uint8_t jpeg_output_bytes_per_pixel() -{ -#if JD_FORMAT == 1 - return 2; -#elif JD_FORMAT == 2 - return 1; -#else - return 3; -#endif -} - -constexpr lv_color_format_t jpeg_output_color_format() -{ -#if JD_FORMAT == 1 - return LV_COLOR_FORMAT_RGB565; -#elif JD_FORMAT == 2 - return LV_COLOR_FORMAT_L8; -#else - return LV_COLOR_FORMAT_RGB888; -#endif -} - -struct JpegMemoryDecodeContext -{ - const uint8_t* data = nullptr; - std::size_t size = 0; - std::size_t pos = 0; - uint8_t* output = nullptr; - uint16_t width = 0; - uint16_t height = 0; - uint32_t stride = 0; -}; - -size_t jpeg_memory_input(JDEC* jd, uint8_t* buff, size_t ndata) -{ - if (jd == nullptr || jd->device == nullptr) - { - return 0; - } - - auto* ctx = static_cast(jd->device); - if (ctx->data == nullptr || ctx->pos >= ctx->size) - { - return 0; - } - - const std::size_t remaining = ctx->size - ctx->pos; - const std::size_t bytes = std::min(remaining, ndata); - if (buff != nullptr && bytes > 0) - { - std::memcpy(buff, ctx->data + ctx->pos, bytes); - } - ctx->pos += bytes; - return bytes; -} - -int jpeg_memory_output(JDEC* jd, void* bitmap, JRECT* rect) -{ - if (jd == nullptr || jd->device == nullptr || bitmap == nullptr || rect == nullptr) - { - return 0; - } - - auto* ctx = static_cast(jd->device); - if (ctx->output == nullptr || - rect->right < rect->left || - rect->bottom < rect->top || - rect->right >= ctx->width || - rect->bottom >= ctx->height) - { - return 0; - } - - const uint32_t bpp = jpeg_output_bytes_per_pixel(); - const uint32_t width = static_cast(rect->right - rect->left + 1); - const uint32_t height = static_cast(rect->bottom - rect->top + 1); - const uint32_t row_bytes = width * bpp; - const auto* src = static_cast(bitmap); - for (uint32_t row = 0; row < height; ++row) - { - uint8_t* dst = ctx->output + - (static_cast(rect->top) + row) * ctx->stride + - static_cast(rect->left) * bpp; - std::memcpy(dst, src + row * row_bytes, row_bytes); - } - return 1; -} - -lv_image_dsc_t* decode_jpeg_payload_to_image_desc(const ui::map_tiles::MapTileRef& ref, - const ui::map_tiles::MapTilePayload& payload) -{ - JpegMemoryDecodeContext ctx{}; - ctx.data = payload.data; - ctx.size = payload.size; - - void* work_buffer = lv_malloc(kMapTileJpegWorkBufferBytes); - if (work_buffer == nullptr) - { - log_map_tile_decode_failure("jpeg_alloc_work", - ref, - ui::map_tiles::MapTileFormat::Jpeg, - payload.size, - -12); - return nullptr; - } - - JDEC decoder{}; - const JRESULT prepare_result = jd_prepare(&decoder, - jpeg_memory_input, - work_buffer, - kMapTileJpegWorkBufferBytes, - &ctx); - if (prepare_result != JDR_OK || decoder.width == 0 || decoder.height == 0) - { - log_map_tile_decode_failure("jpeg_prepare", - ref, - ui::map_tiles::MapTileFormat::Jpeg, - payload.size, - static_cast(prepare_result)); - lv_free(work_buffer); - return nullptr; - } - - const uint8_t bpp = jpeg_output_bytes_per_pixel(); - ctx.width = decoder.width; - ctx.height = decoder.height; - ctx.stride = static_cast(ctx.width) * bpp; - const uint32_t data_size = ctx.stride * static_cast(ctx.height); - - lv_image_dsc_t* img_dsc = static_cast(lv_malloc(sizeof(lv_image_dsc_t))); - if (img_dsc == nullptr) - { - log_map_tile_decode_failure("jpeg_alloc_desc", - ref, - ui::map_tiles::MapTileFormat::Jpeg, - payload.size, - -12); - lv_free(work_buffer); - return nullptr; - } - std::memset(img_dsc, 0, sizeof(lv_image_dsc_t)); - - ctx.output = static_cast(lv_malloc(data_size)); - if (ctx.output == nullptr) - { - log_map_tile_decode_failure("jpeg_alloc_pixels", - ref, - ui::map_tiles::MapTileFormat::Jpeg, - data_size, - -12); - lv_free(img_dsc); - lv_free(work_buffer); - return nullptr; - } - - const JRESULT decode_result = jd_decomp(&decoder, jpeg_memory_output, 0); - if (decode_result != JDR_OK) - { - log_map_tile_decode_failure("jpeg_decomp", - ref, - ui::map_tiles::MapTileFormat::Jpeg, - payload.size, - static_cast(decode_result)); - lv_free(ctx.output); - lv_free(img_dsc); - lv_free(work_buffer); - return nullptr; - } - - img_dsc->header.magic = LV_IMAGE_HEADER_MAGIC; - img_dsc->header.w = ctx.width; - img_dsc->header.h = ctx.height; - img_dsc->header.cf = jpeg_output_color_format(); - img_dsc->header.flags = 0; - img_dsc->header.stride = ctx.stride; - img_dsc->data_size = data_size; - img_dsc->data = ctx.output; - - lv_free(work_buffer); - return img_dsc; -} -#endif - lv_image_dsc_t* decode_payload_to_image_desc(const ui::map_tiles::MapTileRef& ref, const ui::map_tiles::MapTilePayload& payload) { @@ -693,20 +502,6 @@ lv_image_dsc_t* decode_payload_to_image_desc(const ui::map_tiles::MapTileRef& re return nullptr; } - if (payload_format == ui::map_tiles::MapTileFormat::Jpeg) - { -#if LV_USE_TJPGD - return decode_jpeg_payload_to_image_desc(ref, payload); -#else - log_map_tile_decode_failure("jpeg_decoder_disabled", - ref, - payload_format, - payload.size, - -38); - return nullptr; -#endif - } - lv_image_dsc_t compressed{}; compressed.header.magic = LV_IMAGE_HEADER_MAGIC; compressed.header.cf = source_format; diff --git a/platform/linux/common/src/platform/linux/map_tile_cache.cpp b/platform/linux/common/src/platform/linux/map_tile_cache.cpp index ce79a47e..d028dd69 100644 --- a/platform/linux/common/src/platform/linux/map_tile_cache.cpp +++ b/platform/linux/common/src/platform/linux/map_tile_cache.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -207,7 +208,8 @@ std::string url_template_for_source(MapBaseSource source) const char* content_type_for_source(MapBaseSource source) { - return source == MapBaseSource::Satellite ? "image/jpeg" : "image/png"; + (void)source; + return "image/png"; } MapContourProfile major_contour(int interval_m) noexcept @@ -238,6 +240,24 @@ std::uintmax_t file_size_or_zero(const std::filesystem::path& path) return ec ? 0U : size; } +bool file_has_png_signature(const std::filesystem::path& path) +{ + constexpr unsigned char kPngSignature[] = {0x89, 'P', 'N', 'G', + 0x0D, 0x0A, 0x1A, 0x0A}; + unsigned char header[sizeof(kPngSignature)]{}; + + std::ifstream input(path, std::ios::binary); + if (!input.is_open()) + { + return false; + } + + input.read(reinterpret_cast(header), + static_cast(sizeof(header))); + return input.gcount() == static_cast(sizeof(header)) && + std::memcmp(header, kPngSignature, sizeof(header)) == 0; +} + struct DownloadContext { std::ofstream stream; @@ -385,7 +405,8 @@ const char* map_base_source_label(MapBaseSource source) noexcept const char* map_base_source_extension(MapBaseSource source) noexcept { - return source == MapBaseSource::Satellite ? "jpg" : "png"; + (void)source; + return "png"; } const char* map_contour_kind_key(MapContourKind kind) noexcept @@ -586,6 +607,22 @@ MapTileResult MapTileCache::ensure_tile(const MapTileId& requested) const http_status); } + if (!file_has_png_signature(temp_path)) + { + std::error_code remove_ec; + std::filesystem::remove(temp_path, remove_ec); + append_map_diagnostic( + "tile", + std::string(map_base_source_key(tile.source)) + " z" + + std::to_string(tile.z) + "/" + std::to_string(tile.x) + + "/" + std::to_string(tile.y) + " rejected non-PNG response from " + + url); + return fail_result(tile, + relative_path, + "Downloaded tile is not PNG.", + http_status); + } + std::error_code ec; std::filesystem::remove(path, ec); ec.clear();