Skip to content
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

[DO NOT MERGE] feat: validate vector tile styles BM-1123 #1014

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions validation/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build
node_modules
tsconfig.tsbuildinfo

errors.json
146 changes: 146 additions & 0 deletions validation/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions validation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "validation",
"version": "1.0.0",
"description": "Validation for basemaps-config vector tile stylesheets",
"repository": "github:linz/basemaps-config",
"type": "module",
"license": "MIT",
"scripts": {
"start": "node build/checker.js",
"build": "tsc -b",
"validate": "npx gl-style-validate ../config/style/aerialhybrid.json"
},
"devDependencies": {
"@mapbox/mapbox-gl-style-spec": "^14.7.1",
"@types/node": "^22.9.0"
},
"dependencies": {
"zod": "^3.23.8"
}
}
51 changes: 51 additions & 0 deletions validation/scripts/dir-checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import os
import tkinter as tk
from tkinter import filedialog


def get_dir() -> str | None:
# Create a root window and hide it (we don't need the window to show)
root = tk.Tk()
root.withdraw()

# Open a directory selection dialog
directory = filedialog.askdirectory(title="Select a directory")

# If the user cancels, the directory will be an empty string
if directory:
return directory

print("No directory selected.")
return None


def has_subdirs(dir: str) -> bool:
# Define the required subdirectories
subdirs = ["fonts", "sprites", "style"]

# Check if the required subdirectories exist in the given directory
missing_subdirs = [
subdir
for subdir in subdirs
if not os.path.isdir(os.path.join(dir, subdir))
]

if missing_subdirs:
print(
f"The following required subdirectories are missing: {', '.join(missing_subdirs)}"
)
return False

return True


if __name__ == "__main__":
dir = get_dir()

if dir is None:
exit(1)

if has_subdirs(dir) is False:
exit(1)

exit(0)
155 changes: 155 additions & 0 deletions validation/scripts/image-checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import json
from os import listdir
from tkinter import filedialog, Tk
from typing import Any


def select_style_json() -> str:
file_path = filedialog.askopenfilename(
title="Select style json (e.g. style/aerialhybrid.json)",
filetypes=[("JSON files", "*.json"), ("All files", "*.*")],
)

if not file_path:
print("No file selected. Exiting.")
exit(1)

return file_path


def select_sprite_dir():
dir_path = filedialog.askdirectory(
title="Select sprites directory (e.g. sprites/topographic)"
)

if not dir_path:
print("No directory selected. Exiting.")
exit(1)

return dir_path


def extract_icon_image_names(style_json_path: dict[str, str]) -> set[str]:
image_names: set[str] = set()

try:
with open(style_json_path, "r") as file:
data = json.load(file)

if type(data) is not dict:
print("Data is not dict. Exiting.")
exit(1)

layers = data.get("layers")

if type(layers) is not list:
print("Layers is not list. Exiting.")
exit(1)

for layer in layers:
if type(layer) is not dict:
print("Layer is not dict. Exiting.")
exit(1)

layout = layer.get("layout")

if layout is None:
# print("Layout is None. Continuing.")
continue

if type(layout) is not dict:
print("Layout is not dict. Exiting.")
exit(1)

icon_image = layout.get("icon-image")

if icon_image is None:
# print("Icon image is None. Continuing.")
continue

if type(icon_image) is str:
image_names.add(icon_image)
continue

if type(icon_image) is not dict:
print("Icon image is not dict or str. Exiting.")
exit(1)

stops = icon_image.get("stops")

if stops is None:
print("Stops is None. Exiting.")
exit(1)

if type(stops) is not list:
print("Stops is not list. Exiting.")
exit(1)

for stop in stops:
if type(stop) is not list:
print("Stop is not list. Exiting.")
exit(1)

if len(stop) != 2:
print("Stop is not tuple. Exiting.")
exit(1)

image_name = stop[1]

if type(image_name) is not str:
print("Image name is not str. Exiting.")
exit(1)

image_names.add(image_name)

# print(f"Loaded JSON data from {style_json_path}:")
# print(json.dumps(data, indent=4))
except Exception as e:
print(f"Failed to load style json: {e}")
exit(1)

return image_names


def check_icon_image_names_in_sprites_dr(icon_image_names: set[str], sprites_dir_path: str) -> bool:
files_in_dir: set[str] = set(listdir(sprites_dir_path))

missing_files: set[str] = set()

for name in icon_image_names:
if not any(file.startswith(name) for file in files_in_dir):
missing_files.add(name)

if len(missing_files) > 0:
print("missing_files")

for i, missing in enumerate(sorted(missing_files)):
print(i + 1, missing)

return False
else:
print('no missing files')

return True





def main():
root = Tk()
root.withdraw()

style_json_path: str = select_style_json()
icon_image_names: set[str] = extract_icon_image_names(style_json_path)

# print(sorted(icon_image_names))

sprites_dir_path: str = select_sprite_dir()
succeeded: bool = check_icon_image_names_in_sprites_dr(icon_image_names, sprites_dir_path)

exit(0)


if __name__ == "__main__":
main()
Loading
Loading