Keep vanetza-idf in obu-firmware, so a plain clone builds the firmware
obu-firmware builds against the vanetza-idf C-ITS library, which until now came from the colleague's microbu-esp32c5 tree beside the repository and was not tracked here, so a clone of this repository could not build the firmware it ships. The library alone is now part of obu-firmware, as obu-firmware/external/vanetza-idf: their external/vanetza-idf at commit cf4b99f, unchanged (9775 files; see its PROVENANCE.md). CMake takes it from there by default; -DVANETZA_IDF_DIR still points the build elsewhere. The rest of the colleague's tree (their own VAM firmware, PKI tooling, station-link Python tools, the V2X2MAP bridge) stays out of this repository and gitignored; nothing is pushed to their repository. NOTES.md, docs/06, TODO.md and the pcap verifier's usage line point at the new location.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/cache
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# requires-python = ">=3.9"
|
||||
# dependencies = ["pyshp", "shapely"]
|
||||
# ///
|
||||
"""Convert Natural Earth shapefile to Vanetza country data binary format.
|
||||
|
||||
Downloads the Natural Earth admin-0 countries shapefile at the requested
|
||||
resolution, then converts it to a compact binary format.
|
||||
|
||||
Output format:
|
||||
version : uint16 (little-endian)
|
||||
followed by a sequence of entries, each:
|
||||
m49_code : uint16 (little-endian)
|
||||
wkb_size : uint32 (little-endian)
|
||||
wkb_data : bytes (OGC WKB MultiPolygon)
|
||||
"""
|
||||
import argparse
|
||||
import struct
|
||||
import sys
|
||||
import urllib.request
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import shapefile
|
||||
from shapely.geometry import MultiPolygon, Polygon, shape
|
||||
|
||||
RESOLUTIONS = ["110m", "50m", "10m"]
|
||||
FORMAT_VERSION = 1
|
||||
|
||||
|
||||
def download_shapefile(resolution: str, cache_dir: Path) -> Path:
|
||||
name = f"ne_{resolution}_admin_0_countries"
|
||||
shp_dir = cache_dir / name
|
||||
shp_path = shp_dir / f"{name}.shp"
|
||||
|
||||
if shp_path.exists():
|
||||
return shp_path
|
||||
|
||||
url = f"https://naciscdn.org/naturalearth/{resolution}/cultural/{name}.zip"
|
||||
print(f"Downloading {url} ...", file=sys.stderr)
|
||||
data, _ = urllib.request.urlretrieve(url)
|
||||
with zipfile.ZipFile(data) as zf:
|
||||
shp_dir.mkdir(parents=True, exist_ok=True)
|
||||
zf.extractall(shp_dir)
|
||||
|
||||
return shp_path
|
||||
|
||||
|
||||
def convert(shp_path: Path, output: Path):
|
||||
sf = shapefile.Reader(str(shp_path))
|
||||
fields = [f[0] for f in sf.fields[1:]] # skip DeletionFlag
|
||||
|
||||
if "ISO_N3_EH" not in fields:
|
||||
print("Error: shapefile missing ISO_N3_EH field", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
iso_n3_eh_idx = fields.index("ISO_N3_EH")
|
||||
count = 0
|
||||
skipped = 0
|
||||
|
||||
with open(output, "wb") as out:
|
||||
out.write(struct.pack("<H", FORMAT_VERSION))
|
||||
|
||||
for sr in sf.shapeRecords():
|
||||
iso_n3_eh = sr.record[iso_n3_eh_idx]
|
||||
|
||||
# Skip non-numeric codes (e.g., "-99" for disputed territories)
|
||||
try:
|
||||
m49 = int(iso_n3_eh)
|
||||
except (ValueError, TypeError):
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
if m49 < 0 or m49 > 65535:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
geom = shape(sr.shape.__geo_interface__)
|
||||
|
||||
# Normalize to MultiPolygon
|
||||
if isinstance(geom, Polygon):
|
||||
geom = MultiPolygon([geom])
|
||||
elif not isinstance(geom, MultiPolygon):
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
wkb = geom.wkb
|
||||
|
||||
out.write(struct.pack("<H", m49))
|
||||
out.write(struct.pack("<I", len(wkb)))
|
||||
out.write(wkb)
|
||||
count += 1
|
||||
|
||||
print(f"Wrote {count} countries, skipped {skipped} entries", file=sys.stderr)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Convert Natural Earth to country data binary")
|
||||
parser.add_argument("--output", required=True, type=Path, help="Path to output .bin file")
|
||||
parser.add_argument("--resolution", choices=RESOLUTIONS, default="50m",
|
||||
help="Natural Earth resolution (default: 50m)")
|
||||
parser.add_argument("--cache-dir", type=Path,
|
||||
default=Path(__file__).parent / "cache",
|
||||
help="Cache directory for downloaded shapefiles")
|
||||
args = parser.parse_args()
|
||||
|
||||
shp_path = download_shapefile(args.resolution, args.cache_dir)
|
||||
convert(shp_path, args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# requires-python = ">=3.9"
|
||||
# dependencies = []
|
||||
# ///
|
||||
"""Generate a C++ source file embedding a binary file as std::array."""
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Embed binary file as C++ std::array")
|
||||
parser.add_argument("--input", required=True, type=Path, help="Path to binary input file")
|
||||
parser.add_argument("--output", required=True, type=Path, help="Path to C++ output file")
|
||||
args = parser.parse_args()
|
||||
|
||||
data = args.input.read_bytes()
|
||||
|
||||
with open(args.output, "w") as out:
|
||||
out.write("#include <vanetza/common/byte_view.hpp>\n\n")
|
||||
out.write(f"static const std::array<uint8_t, {len(data)}> vanetza_country_data_storage = {{\n")
|
||||
for i in range(0, len(data), 16):
|
||||
chunk = data[i:i + 16]
|
||||
out.write(" " + ", ".join(f"0x{b:02x}" for b in chunk) + ",\n")
|
||||
out.write("};\n\n")
|
||||
out.write("namespace vanetza {\n")
|
||||
out.write("namespace geodesy {\n")
|
||||
out.write("namespace country {\n\n")
|
||||
out.write("byte_view_range embedded()\n")
|
||||
out.write("{\n")
|
||||
out.write(" byte_view_iterator begin(vanetza_country_data_storage.data());\n")
|
||||
out.write(" byte_view_iterator end(vanetza_country_data_storage.data() + vanetza_country_data_storage.size());\n")
|
||||
out.write(" return {begin, end};\n")
|
||||
out.write("}\n\n")
|
||||
out.write("} // namespace country\n")
|
||||
out.write("} // namespace geodesy\n")
|
||||
out.write("} // namespace vanetza\n")
|
||||
|
||||
print(f"Embedded {len(data)} bytes into {args.output.name}", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# requires-python = ">=3.9"
|
||||
# dependencies = ["matplotlib", "shapely"]
|
||||
# ///
|
||||
"""Plot country polygons from a Vanetza country data binary file."""
|
||||
import argparse
|
||||
import colorsys
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
from shapely import wkb
|
||||
|
||||
EXPECTED_FORMAT_VERSION = 1
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Plot country polygons from .bin file")
|
||||
parser.add_argument("input", help="Path to country_data.bin")
|
||||
args = parser.parse_args()
|
||||
|
||||
fig, ax = plt.subplots(figsize=(16, 8))
|
||||
|
||||
with open(args.input, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
if len(data) < 2:
|
||||
print("Error: file too short to contain version header", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
version, = struct.unpack_from("<H", data, 0)
|
||||
if version != EXPECTED_FORMAT_VERSION:
|
||||
print(f"Error: unsupported country data format version {version}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
offset = 2
|
||||
while offset < len(data):
|
||||
m49, = struct.unpack_from("<H", data, offset)
|
||||
offset += 2
|
||||
wkb_size, = struct.unpack_from("<I", data, offset)
|
||||
offset += 4
|
||||
geom = wkb.loads(data[offset:offset + wkb_size])
|
||||
offset += wkb_size
|
||||
|
||||
h = (m49 * 0.618) % 1.0
|
||||
s = 0.5 + (hash(m49) % 50) / 100.0
|
||||
l = 0.4 + (hash(m49 * 7) % 30) / 100.0
|
||||
color = colorsys.hls_to_rgb(h, l, s)
|
||||
|
||||
for polygon in geom.geoms:
|
||||
x, y = polygon.exterior.xy
|
||||
ax.fill(x, y, alpha=0.4, edgecolor="black", linewidth=0.3, facecolor=color)
|
||||
if polygon.area > 1.0:
|
||||
centroid = polygon.centroid
|
||||
ax.text(centroid.x, centroid.y, str(m49),
|
||||
fontsize=5, ha="center", va="center")
|
||||
|
||||
ax.set_aspect("equal")
|
||||
ax.set_xlabel("Longitude")
|
||||
ax.set_ylabel("Latitude")
|
||||
ax.set_title("Country Polygons")
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user