mirror of
https://github.com/0015/map_tiles.git
synced 2026-01-17 09:06:59 +01:00
feat: Release version 1.2.0 with new features
This commit is contained in:
@@ -31,7 +31,7 @@ You can easily add this component to your project using the idf.py command or by
|
||||
From your project's root directory, simply run the following command in your terminal:
|
||||
|
||||
```bash
|
||||
idf.py add-dependency "0015/map_tiles^1.1.0"
|
||||
idf.py add-dependency "0015/map_tiles^1.2.0"
|
||||
```
|
||||
|
||||
This command will automatically add the component to your idf_component.yml file and download the required files the next time you build your project.
|
||||
@@ -43,7 +43,7 @@ Add to your project's `main/idf_component.yml`:
|
||||
dependencies:
|
||||
map_tiles:
|
||||
git: "https://github.com/0015/map_tiles.git"
|
||||
version: "^1.1.0"
|
||||
version: "^1.2.0"
|
||||
```
|
||||
|
||||
### Manual Installation
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace: "0015"
|
||||
name: "map_tiles"
|
||||
version: "1.1.0"
|
||||
version: "1.2.0"
|
||||
maintainers:
|
||||
- Eric Nam <thatprojectstudio@gmail.com>
|
||||
description: "Map tiles component for LVGL 9.x - Load and display map tiles with GPS coordinate conversion"
|
||||
|
||||
49
script/README.md
Normal file
49
script/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# **LVGL Map Tile Converter**
|
||||
|
||||
This Python script is designed to convert a directory of PNG map tiles into a format compatible with LVGL (Light and Versatile Graphics Library) version 9\. The output is a series of binary files (.bin) with a header and pixel data in the RGB565 format, optimized for direct use with LVGL's image APIs.
|
||||
|
||||
The script is multithreaded and can significantly speed up the conversion process for large tile sets.
|
||||
|
||||
## **Features**
|
||||
|
||||
* **PNG to RGB565 Conversion**: Converts standard 24-bit PNG images into 16-bit RGB565.
|
||||
* **LVGL v9 Compatibility**: Generates a .bin file with the correct LVGL v9 header format.
|
||||
* **Multithreaded Conversion**: Utilizes a ThreadPoolExecutor to process tiles in parallel, configurable with the \--jobs flag.
|
||||
* **Directory Traversal**: Automatically finds and converts all .png files within a specified input directory structure.
|
||||
* **Skip Existing Files**: Skips conversion for tiles that already exist in the output directory unless the \--force flag is used.
|
||||
|
||||
## **Requirements**
|
||||
|
||||
The script requires the Pillow library to handle image processing. You can install it using pip:
|
||||
```bash
|
||||
pip install Pillow
|
||||
```
|
||||
|
||||
## **Usage**
|
||||
|
||||
The script is a command-line tool. You can run it from your terminal with the following arguments:
|
||||
```bash
|
||||
python lvgl_map_tile_converter.py --input <input_folder> --output <output_folder> [options]
|
||||
```
|
||||
|
||||
### **Arguments**
|
||||
|
||||
* \-i, \--input: **Required**. The root folder containing your map tiles. The script expects a tile structure like zoom/x/y.png.
|
||||
* \-o, \--output: **Required**. The root folder where the converted .bin tiles will be saved. The output structure will mirror the input: zoom/x/y.bin.
|
||||
* \-j, \--jobs: **Optional**. The number of worker threads to use for the conversion. Defaults to the number of CPU cores on your system. Using more jobs can speed up the process.
|
||||
* \-f, \--force: **Optional**. If this flag is set, the script will re-convert all tiles, even if the output .bin files already exist.
|
||||
|
||||
### **Examples**
|
||||
|
||||
**1\. Basic conversion with default settings:**
|
||||
```bash
|
||||
python lvgl_map_tile_converter.py --input ./map_tiles --output ./tiles1
|
||||
```
|
||||
**2\. Conversion using a specific number of threads (e.g., 8):**
|
||||
```bash
|
||||
python lvgl_map_tile_converter.py --input ./map_tiles --output ./tiles1 --jobs 8
|
||||
```
|
||||
**3\. Forcing a full re-conversion:**
|
||||
```bash
|
||||
python lvgl_map_tile_converter.py --input ./map_tiles --output ./tiles1 --force
|
||||
```
|
||||
178
script/lvgl_map_tile_converter.py
Normal file
178
script/lvgl_map_tile_converter.py
Normal file
@@ -0,0 +1,178 @@
|
||||
import os
|
||||
import struct
|
||||
import argparse
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from PIL import Image # pip install pillow
|
||||
|
||||
# No implicit defaults; these are set from CLI in main()
|
||||
INPUT_ROOT = None
|
||||
OUTPUT_ROOT = None
|
||||
|
||||
|
||||
# Convert RGB to 16-bit RGB565
|
||||
def to_rgb565(r, g, b):
|
||||
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
|
||||
|
||||
|
||||
# Strip .png or .bin extensions
|
||||
def clean_tile_name(filename):
|
||||
name = filename
|
||||
while True:
|
||||
name, ext = os.path.splitext(name)
|
||||
if ext.lower() not in [".png", ".bin", ".jpg", ".jpeg"]:
|
||||
break
|
||||
filename = name
|
||||
return filename
|
||||
|
||||
|
||||
# Create LVGL v9-compatible .bin image
|
||||
def make_lvgl_bin(png_path, bin_path):
|
||||
im = Image.open(png_path).convert("RGB")
|
||||
w, h = im.size
|
||||
pixels = im.load()
|
||||
|
||||
stride = (w * 16 + 7) // 8 # bytes per row (RGB565 = 16 bpp)
|
||||
flags = 0x00 # no compression, no premult
|
||||
color_format = 0x12 # RGB565
|
||||
magic = 0x19
|
||||
|
||||
header = bytearray()
|
||||
header += struct.pack("<B", magic)
|
||||
header += struct.pack("<B", color_format)
|
||||
header += struct.pack("<H", flags)
|
||||
header += struct.pack("<H", w)
|
||||
header += struct.pack("<H", h)
|
||||
header += struct.pack("<H", stride)
|
||||
header += struct.pack("<H", 0) # reserved
|
||||
|
||||
body = bytearray()
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
r, g, b = pixels[x, y]
|
||||
rgb565 = to_rgb565(r, g, b)
|
||||
body += struct.pack("<H", rgb565)
|
||||
|
||||
os.makedirs(os.path.dirname(bin_path), exist_ok=True)
|
||||
|
||||
if os.path.isdir(bin_path):
|
||||
# In case a folder mistakenly exists where a file should
|
||||
print(f"[Fix] Removing incorrect folder: {bin_path}")
|
||||
os.rmdir(bin_path)
|
||||
|
||||
with open(bin_path, "wb") as f:
|
||||
f.write(header)
|
||||
f.write(body)
|
||||
|
||||
print(f"[OK] {png_path} → {bin_path}")
|
||||
|
||||
|
||||
# Yield (input_path, output_path) pairs for all PNG tiles under INPUT_ROOT
|
||||
def _iter_tile_paths():
|
||||
for zoom in sorted(os.listdir(INPUT_ROOT)):
|
||||
zoom_path = os.path.join(INPUT_ROOT, zoom)
|
||||
if not os.path.isdir(zoom_path) or not zoom.isdigit():
|
||||
continue
|
||||
|
||||
for x_tile in sorted(os.listdir(zoom_path)):
|
||||
x_path = os.path.join(zoom_path, x_tile)
|
||||
if not os.path.isdir(x_path) or not x_tile.isdigit():
|
||||
continue
|
||||
|
||||
for y_file in sorted(os.listdir(x_path)):
|
||||
if not y_file.lower().endswith(".png"):
|
||||
continue
|
||||
|
||||
tile_base = clean_tile_name(y_file)
|
||||
input_path = os.path.join(INPUT_ROOT, zoom, x_tile, y_file)
|
||||
output_path = os.path.join(OUTPUT_ROOT, zoom, x_tile, f"{tile_base}.bin")
|
||||
yield input_path, output_path
|
||||
|
||||
|
||||
def convert_all_tiles(jobs=1, force=False):
|
||||
"""
|
||||
Convert tiles with optional threading.
|
||||
- jobs: number of worker threads (>=1)
|
||||
- force: if True, re-generate even if output exists
|
||||
"""
|
||||
if not os.path.isdir(INPUT_ROOT):
|
||||
print(f"[ERROR] '{INPUT_ROOT}' not found.")
|
||||
return
|
||||
|
||||
# Build task list (skip existing unless --force)
|
||||
tasks = []
|
||||
for input_path, output_path in _iter_tile_paths():
|
||||
if not force and os.path.isfile(output_path):
|
||||
print(f"[Skip] {output_path}")
|
||||
continue
|
||||
tasks.append((input_path, output_path))
|
||||
|
||||
if not tasks:
|
||||
print("[INFO] Nothing to do.")
|
||||
return
|
||||
|
||||
print(f"[INFO] Converting {len(tasks)} tiles with {jobs} thread(s)...")
|
||||
|
||||
if jobs <= 1:
|
||||
# Serial path
|
||||
for inp, outp in tasks:
|
||||
try:
|
||||
make_lvgl_bin(inp, outp)
|
||||
except Exception as e:
|
||||
print(f"[Error] Failed to convert {inp} → {e}")
|
||||
return
|
||||
|
||||
# Threaded path
|
||||
print_lock = threading.Lock()
|
||||
with ThreadPoolExecutor(max_workers=jobs) as ex:
|
||||
future_map = {ex.submit(make_lvgl_bin, inp, outp): (inp, outp) for inp, outp in tasks}
|
||||
for fut in as_completed(future_map):
|
||||
inp, outp = future_map[fut]
|
||||
try:
|
||||
fut.result()
|
||||
except Exception as e:
|
||||
with print_lock:
|
||||
print(f"[Error] Failed to convert {inp} → {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Convert OSM PNG tiles into LVGL-friendly .bin files (RGB565).",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i", "--input",
|
||||
required=True,
|
||||
default=argparse.SUPPRESS, # hide '(default: None)'
|
||||
help="Input root folder containing tiles in zoom/x/y.png structure",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o", "--output",
|
||||
required=True,
|
||||
default=argparse.SUPPRESS, # hide '(default: None)'
|
||||
help="Output root folder where .bin tiles will be written",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-j", "--jobs",
|
||||
type=int,
|
||||
default=os.cpu_count(),
|
||||
help="Number of worker threads",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f", "--force",
|
||||
action="store_true",
|
||||
help="Rebuild even if output file already exists",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Basic checks
|
||||
if not os.path.isdir(args.input):
|
||||
parser.error(f"Input folder not found or not a directory: {args.input}")
|
||||
os.makedirs(args.output, exist_ok=True)
|
||||
|
||||
# Apply CLI values
|
||||
INPUT_ROOT = args.input
|
||||
OUTPUT_ROOT = args.output
|
||||
|
||||
convert_all_tiles(jobs=max(1, args.jobs), force=args.force)
|
||||
Reference in New Issue
Block a user