audit full semantic extent and AOI visuals
This commit is contained in:
2856
STEP_RECORD.md
2856
STEP_RECORD.md
File diff suppressed because it is too large
Load Diff
225
navsea_full_aoi_visual_audit.py
Normal file
225
navsea_full_aoi_visual_audit.py
Normal file
@@ -0,0 +1,225 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from PIL import Image, ImageChops, ImageStat
|
||||
|
||||
|
||||
DEFAULT_COMPARE_URL = "http://192.168.200.184/newpec/navsea-compare-full-audit.html"
|
||||
DEFAULT_AOI_JSON = Path("/root/sourceserver/pbf/tasks/pbf/NavSea_Full_AOI_Visual_Audit_2026-04-18.json")
|
||||
DEFAULT_OUTPUT_DIR = Path("/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18")
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="对全国 full / semantic 做 AOI 截图 + 图像 diff 审计。")
|
||||
parser.add_argument("--compare-url", default=DEFAULT_COMPARE_URL)
|
||||
parser.add_argument("--aoi-json", type=Path, default=DEFAULT_AOI_JSON)
|
||||
parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR)
|
||||
parser.add_argument("--width", type=int, default=1280)
|
||||
parser.add_argument("--height", type=int, default=720)
|
||||
parser.add_argument("--toolbar-height", type=int, default=120)
|
||||
parser.add_argument("--bottom-trim", type=int, default=30)
|
||||
parser.add_argument("--timeout-sec", type=int, default=120)
|
||||
parser.add_argument("--virtual-time-budget-ms", type=int, default=30000)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def run_browser_screenshot(
|
||||
compare_url: str,
|
||||
output_png: Path,
|
||||
width: int,
|
||||
height: int,
|
||||
timeout_sec: int,
|
||||
virtual_time_budget_ms: int,
|
||||
) -> None:
|
||||
cmd = [
|
||||
"timeout",
|
||||
f"{timeout_sec}s",
|
||||
"google-chrome",
|
||||
"--headless=new",
|
||||
"--disable-gpu",
|
||||
"--enable-unsafe-swiftshader",
|
||||
"--no-sandbox",
|
||||
f"--virtual-time-budget={virtual_time_budget_ms}",
|
||||
f"--window-size={width},{height}",
|
||||
f"--screenshot={output_png}",
|
||||
compare_url,
|
||||
]
|
||||
subprocess.run(cmd, check=True, capture_output=True, text=True)
|
||||
|
||||
|
||||
def compute_visual_metrics(left_img: Image.Image, right_img: Image.Image) -> dict[str, Any]:
|
||||
diff = ImageChops.difference(left_img, right_img)
|
||||
diff_rgb = diff.convert("RGB")
|
||||
diff_gray = diff.convert("L")
|
||||
stat = ImageStat.Stat(diff_rgb)
|
||||
mean_abs = [round(v, 4) for v in stat.mean]
|
||||
rms = [round(v, 4) for v in stat.rms]
|
||||
bbox = diff_gray.getbbox()
|
||||
nonzero_pixels = 0
|
||||
if bbox:
|
||||
mask = diff_gray.point(lambda value: 255 if value else 0)
|
||||
histogram = mask.histogram()
|
||||
nonzero_pixels = histogram[255] if len(histogram) > 255 else 0
|
||||
total_pixels = left_img.size[0] * left_img.size[1]
|
||||
changed_ratio = round(nonzero_pixels / total_pixels, 6) if total_pixels else 0.0
|
||||
return {
|
||||
"changed_pixels": nonzero_pixels,
|
||||
"total_pixels": total_pixels,
|
||||
"changed_ratio": changed_ratio,
|
||||
"mean_abs_rgb": mean_abs,
|
||||
"rms_rgb": rms,
|
||||
"diff_bbox": list(bbox) if bbox else None,
|
||||
"diff_image": diff,
|
||||
}
|
||||
|
||||
|
||||
def crop_compare_panes(full_png: Path, left_png: Path, right_png: Path, diff_png: Path, toolbar_height: int, bottom_trim: int) -> dict[str, Any]:
|
||||
image = Image.open(full_png).convert("RGBA")
|
||||
usable_top = toolbar_height
|
||||
usable_bottom = max(usable_top + 1, image.height - bottom_trim)
|
||||
mid_x = image.width // 2
|
||||
left = image.crop((0, usable_top, mid_x, usable_bottom))
|
||||
right = image.crop((mid_x, usable_top, image.width, usable_bottom))
|
||||
left.save(left_png)
|
||||
right.save(right_png)
|
||||
|
||||
metrics = compute_visual_metrics(left, right)
|
||||
diff_image = metrics.pop("diff_image")
|
||||
diff_image.convert("RGB").point(lambda value: min(255, value * 4)).save(diff_png)
|
||||
metrics["left_crop"] = [0, usable_top, mid_x, usable_bottom]
|
||||
metrics["right_crop"] = [mid_x, usable_top, image.width, usable_bottom]
|
||||
return metrics
|
||||
|
||||
|
||||
def build_case_url(compare_url: str, variant: str, center: list[float], zoom: int | float) -> str:
|
||||
query = urlencode(
|
||||
{
|
||||
"variant": variant,
|
||||
"audit": "1",
|
||||
"center": f"{center[0]},{center[1]}",
|
||||
"zoom": zoom,
|
||||
}
|
||||
)
|
||||
separator = "&" if "?" in compare_url else "?"
|
||||
return f"{compare_url}{separator}{query}"
|
||||
|
||||
|
||||
def write_report(output_dir: Path, summary: dict[str, Any]) -> tuple[Path, Path]:
|
||||
report_json = output_dir / "full_aoi_visual_audit.json"
|
||||
report_md = output_dir / "full_aoi_visual_audit.md"
|
||||
report_json.write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
lines = [
|
||||
"# 全国 AOI 截图对比审计",
|
||||
"",
|
||||
"## 总览",
|
||||
"",
|
||||
f"- compare page: `{summary['compare_url']}`",
|
||||
f"- cases: `{len(summary['cases'])}`",
|
||||
"",
|
||||
"## 差异排序",
|
||||
"",
|
||||
]
|
||||
for case in summary["cases"]:
|
||||
lines.extend(
|
||||
[
|
||||
f"### {case['label']} · {case['variant']} · z{case['zoom']}",
|
||||
"",
|
||||
f"- changed ratio: `{case['changed_ratio']}`",
|
||||
f"- changed pixels: `{case['changed_pixels']}` / `{case['total_pixels']}`",
|
||||
f"- mean abs rgb: `{case['mean_abs_rgb']}`",
|
||||
f"- compare url: `{case['compare_url']}`",
|
||||
f"- left image: `{case['left_image']}`",
|
||||
f"- right image: `{case['right_image']}`",
|
||||
f"- diff image: `{case['diff_image']}`",
|
||||
"",
|
||||
]
|
||||
)
|
||||
report_md.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
return report_md, report_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
output_dir = args.output_dir
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
aois = json.loads(args.aoi_json.read_text(encoding="utf-8"))
|
||||
|
||||
cases: list[dict[str, Any]] = []
|
||||
for variant in ("full", "semantic"):
|
||||
for aoi in aois:
|
||||
for zoom in aoi["zoom_levels"]:
|
||||
case_id = f"{aoi['id']}_{variant}_z{zoom}"
|
||||
case_dir = output_dir / case_id
|
||||
case_dir.mkdir(parents=True, exist_ok=True)
|
||||
compare_url = build_case_url(args.compare_url, variant, aoi["center"], zoom)
|
||||
full_png = case_dir / "compare_full.png"
|
||||
left_png = case_dir / "compare_left.png"
|
||||
right_png = case_dir / "compare_right.png"
|
||||
diff_png = case_dir / "compare_diff.png"
|
||||
|
||||
run_browser_screenshot(
|
||||
compare_url=compare_url,
|
||||
output_png=full_png,
|
||||
width=args.width,
|
||||
height=args.height,
|
||||
timeout_sec=args.timeout_sec,
|
||||
virtual_time_budget_ms=args.virtual_time_budget_ms,
|
||||
)
|
||||
metrics = crop_compare_panes(
|
||||
full_png=full_png,
|
||||
left_png=left_png,
|
||||
right_png=right_png,
|
||||
diff_png=diff_png,
|
||||
toolbar_height=args.toolbar_height,
|
||||
bottom_trim=args.bottom_trim,
|
||||
)
|
||||
cases.append(
|
||||
{
|
||||
"id": case_id,
|
||||
"label": aoi["label"],
|
||||
"variant": variant,
|
||||
"zoom": zoom,
|
||||
"center": aoi["center"],
|
||||
"radius_nm": aoi["radius_nm"],
|
||||
"compare_url": compare_url,
|
||||
"changed_pixels": metrics["changed_pixels"],
|
||||
"total_pixels": metrics["total_pixels"],
|
||||
"changed_ratio": metrics["changed_ratio"],
|
||||
"mean_abs_rgb": metrics["mean_abs_rgb"],
|
||||
"rms_rgb": metrics["rms_rgb"],
|
||||
"diff_bbox": metrics["diff_bbox"],
|
||||
"left_image": str(left_png),
|
||||
"right_image": str(right_png),
|
||||
"diff_image": str(diff_png),
|
||||
}
|
||||
)
|
||||
|
||||
cases.sort(key=lambda item: item["changed_ratio"], reverse=True)
|
||||
summary = {
|
||||
"compare_url": args.compare_url,
|
||||
"cases": cases,
|
||||
}
|
||||
report_md, report_json = write_report(output_dir, summary)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"report_md": str(report_md),
|
||||
"report_json": str(report_json),
|
||||
"top_cases": cases[:5],
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
368
navsea_geometry_native_audit.py
Normal file
368
navsea_geometry_native_audit.py
Normal file
@@ -0,0 +1,368 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from collections import Counter, defaultdict
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from mapbox_vector_tile.Mapbox import vector_tile_pb2
|
||||
|
||||
|
||||
DEFAULT_SOURCE_ROOT = Path(
|
||||
"/home/wwwroot/newpec/exported_auto/"
|
||||
"tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles"
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LayerGeometryStats:
|
||||
point_count: int = 0
|
||||
min_x: int = 0
|
||||
max_x: int = 0
|
||||
min_y: int = 0
|
||||
max_y: int = 0
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="审计并按需修复 NavSea PBF 的几何 / extent / native 风险。")
|
||||
parser.add_argument("--target-root", type=Path, required=True)
|
||||
parser.add_argument("--label", required=True)
|
||||
parser.add_argument("--output-dir", type=Path, required=True)
|
||||
parser.add_argument("--source-root", type=Path, default=DEFAULT_SOURCE_ROOT)
|
||||
parser.add_argument("--apply-fix", action="store_true", help="若检测到 extent 与源瓦片不一致,则按源瓦片 extent 原地修复。")
|
||||
parser.add_argument("--sample-limit", type=int, default=30)
|
||||
parser.add_argument("--zoom", action="append", type=int, help="只审指定 zoom,可重复传入多次。")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def zigzag_decode(value: int) -> int:
|
||||
return (value >> 1) ^ (-(value & 1))
|
||||
|
||||
|
||||
def decode_feature_geometry(geometry: list[int]) -> LayerGeometryStats:
|
||||
stats = LayerGeometryStats()
|
||||
cursor_x = 0
|
||||
cursor_y = 0
|
||||
initialized = False
|
||||
idx = 0
|
||||
|
||||
while idx < len(geometry):
|
||||
command = geometry[idx]
|
||||
idx += 1
|
||||
command_id = command & 0x7
|
||||
command_count = command >> 3
|
||||
|
||||
if command_id in (1, 2): # MoveTo, LineTo
|
||||
for _ in range(command_count):
|
||||
if idx + 1 >= len(geometry):
|
||||
raise ValueError("geometry command truncated")
|
||||
cursor_x += zigzag_decode(geometry[idx])
|
||||
cursor_y += zigzag_decode(geometry[idx + 1])
|
||||
idx += 2
|
||||
|
||||
if not initialized:
|
||||
stats.min_x = stats.max_x = cursor_x
|
||||
stats.min_y = stats.max_y = cursor_y
|
||||
initialized = True
|
||||
else:
|
||||
stats.min_x = min(stats.min_x, cursor_x)
|
||||
stats.max_x = max(stats.max_x, cursor_x)
|
||||
stats.min_y = min(stats.min_y, cursor_y)
|
||||
stats.max_y = max(stats.max_y, cursor_y)
|
||||
stats.point_count += 1
|
||||
elif command_id == 7: # ClosePath
|
||||
continue
|
||||
else:
|
||||
raise ValueError(f"unsupported geometry command id={command_id}")
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
def merge_layer_stats(existing: LayerGeometryStats | None, incoming: LayerGeometryStats) -> LayerGeometryStats:
|
||||
if existing is None or existing.point_count == 0:
|
||||
return incoming
|
||||
if incoming.point_count == 0:
|
||||
return existing
|
||||
return LayerGeometryStats(
|
||||
point_count=existing.point_count + incoming.point_count,
|
||||
min_x=min(existing.min_x, incoming.min_x),
|
||||
max_x=max(existing.max_x, incoming.max_x),
|
||||
min_y=min(existing.min_y, incoming.min_y),
|
||||
max_y=max(existing.max_y, incoming.max_y),
|
||||
)
|
||||
|
||||
|
||||
def analyze_tile(tile_path: Path) -> dict[str, Any]:
|
||||
tile = vector_tile_pb2.tile()
|
||||
tile.ParseFromString(tile_path.read_bytes())
|
||||
|
||||
layer_summaries: list[dict[str, Any]] = []
|
||||
extent_values: set[int] = set()
|
||||
tile_outside_extent = False
|
||||
tile_max_overflow = 0
|
||||
|
||||
for layer in tile.layers:
|
||||
extent = int(layer.extent or 4096)
|
||||
extent_values.add(extent)
|
||||
aggregate: LayerGeometryStats | None = None
|
||||
for feature in layer.features:
|
||||
geom_stats = decode_feature_geometry(list(feature.geometry))
|
||||
aggregate = merge_layer_stats(aggregate, geom_stats)
|
||||
|
||||
if aggregate is None:
|
||||
aggregate = LayerGeometryStats()
|
||||
|
||||
outside_extent = False
|
||||
overflow = 0
|
||||
if aggregate.point_count:
|
||||
overflow = max(
|
||||
0,
|
||||
-aggregate.min_x,
|
||||
-aggregate.min_y,
|
||||
aggregate.max_x - extent,
|
||||
aggregate.max_y - extent,
|
||||
)
|
||||
outside_extent = overflow > 0
|
||||
|
||||
tile_outside_extent = tile_outside_extent or outside_extent
|
||||
tile_max_overflow = max(tile_max_overflow, overflow)
|
||||
layer_summaries.append(
|
||||
{
|
||||
"name": layer.name,
|
||||
"extent": extent,
|
||||
"point_count": aggregate.point_count,
|
||||
"min_x": aggregate.min_x,
|
||||
"max_x": aggregate.max_x,
|
||||
"min_y": aggregate.min_y,
|
||||
"max_y": aggregate.max_y,
|
||||
"outside_extent": outside_extent,
|
||||
"overflow": overflow,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"extent_values": sorted(extent_values),
|
||||
"outside_extent": tile_outside_extent,
|
||||
"max_overflow": tile_max_overflow,
|
||||
"layers": layer_summaries,
|
||||
}
|
||||
|
||||
|
||||
def desired_extent_from_source(source_path: Path) -> int | None:
|
||||
if not source_path.exists():
|
||||
return None
|
||||
tile = vector_tile_pb2.tile()
|
||||
tile.ParseFromString(source_path.read_bytes())
|
||||
extents = sorted({int(layer.extent or 4096) for layer in tile.layers})
|
||||
if not extents:
|
||||
return None
|
||||
if len(extents) == 1:
|
||||
return extents[0]
|
||||
return max(extents)
|
||||
|
||||
|
||||
def repair_tile_extents(tile_path: Path, desired_extent: int) -> bool:
|
||||
tile = vector_tile_pb2.tile()
|
||||
raw = tile_path.read_bytes()
|
||||
tile.ParseFromString(raw)
|
||||
changed = False
|
||||
for layer in tile.layers:
|
||||
current = int(layer.extent or 4096)
|
||||
if current != desired_extent:
|
||||
layer.extent = desired_extent
|
||||
changed = True
|
||||
if changed:
|
||||
tile_path.write_bytes(tile.SerializeToString())
|
||||
return changed
|
||||
|
||||
|
||||
def hardlink_copy_tree(src_root: Path, dst_root: Path) -> None:
|
||||
if dst_root.exists():
|
||||
raise SystemExit(f"backup root already exists: {dst_root}")
|
||||
dst_root.mkdir(parents=True, exist_ok=True)
|
||||
os.system(f"cp -al {src_root}/. {dst_root}/")
|
||||
|
||||
|
||||
def write_report(
|
||||
output_dir: Path,
|
||||
label: str,
|
||||
target_root: Path,
|
||||
source_root: Path,
|
||||
summary: dict[str, Any],
|
||||
) -> tuple[Path, Path]:
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
report_json = output_dir / "geometry_native_audit.json"
|
||||
report_md = output_dir / "geometry_native_audit.md"
|
||||
report_json.write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
lines = [
|
||||
f"# {label} 几何 / Extent / Native 风险审计",
|
||||
"",
|
||||
"## 范围",
|
||||
"",
|
||||
f"- target root: `{target_root}`",
|
||||
f"- source root: `{source_root}`",
|
||||
f"- tiles: `{summary['tile_count']}`",
|
||||
f"- repaired tiles: `{summary['repaired_tiles']}`",
|
||||
"",
|
||||
"## 总体结论",
|
||||
"",
|
||||
f"- 坐标超当前 extent 的瓦片数:`{summary['outside_extent_tiles']}`",
|
||||
f"- 与源瓦片 extent 不一致的瓦片数:`{summary['source_extent_mismatch_tiles']}`",
|
||||
f"- 可直接按源 extent 修复的瓦片数:`{summary['fixable_from_source_tiles']}`",
|
||||
"",
|
||||
"## Zoom 汇总",
|
||||
"",
|
||||
]
|
||||
|
||||
for zoom in sorted(summary["zoom_summary"], key=int):
|
||||
item = summary["zoom_summary"][zoom]
|
||||
lines.extend(
|
||||
[
|
||||
f"### z{zoom}",
|
||||
"",
|
||||
f"- tiles: `{item['tile_count']}`",
|
||||
f"- outside extent: `{item['outside_extent_tiles']}`",
|
||||
f"- source mismatch: `{item['source_extent_mismatch_tiles']}`",
|
||||
f"- extent counts: `{item['extent_counts']}`",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
lines.extend(["## 代表问题样本", ""])
|
||||
for item in summary["sample_tiles"]:
|
||||
lines.extend(
|
||||
[
|
||||
f"### `{item['tile']}`",
|
||||
"",
|
||||
f"- extent values: `{item['extent_values']}`",
|
||||
f"- source extent: `{item['source_extent']}`",
|
||||
f"- outside extent: `{item['outside_extent']}`",
|
||||
f"- max overflow: `{item['max_overflow']}`",
|
||||
f"- fixable from source: `{item['fixable_from_source']}`",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
report_md.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
return report_md, report_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
target_root = args.target_root
|
||||
source_root = args.source_root
|
||||
output_dir = args.output_dir
|
||||
|
||||
if not target_root.exists():
|
||||
raise SystemExit(f"target root not found: {target_root}")
|
||||
if not source_root.exists():
|
||||
raise SystemExit(f"source root not found: {source_root}")
|
||||
|
||||
zoom_summary: dict[str, dict[str, Any]] = defaultdict(
|
||||
lambda: {
|
||||
"tile_count": 0,
|
||||
"outside_extent_tiles": 0,
|
||||
"source_extent_mismatch_tiles": 0,
|
||||
"extent_counts": Counter(),
|
||||
}
|
||||
)
|
||||
sample_tiles: list[dict[str, Any]] = []
|
||||
repaired_tiles = 0
|
||||
outside_extent_tiles = 0
|
||||
source_extent_mismatch_tiles = 0
|
||||
fixable_from_source_tiles = 0
|
||||
|
||||
tile_paths = sorted(target_root.glob("*/*/*.pbf"))
|
||||
if args.zoom:
|
||||
zoom_filter = {str(item) for item in args.zoom}
|
||||
tile_paths = [path for path in tile_paths if path.parent.parent.name in zoom_filter]
|
||||
for tile_path in tile_paths:
|
||||
rel = tile_path.relative_to(target_root)
|
||||
zoom = rel.parts[0]
|
||||
analysis = analyze_tile(tile_path)
|
||||
source_extent = desired_extent_from_source(source_root / rel)
|
||||
current_extent = max(analysis["extent_values"]) if analysis["extent_values"] else None
|
||||
mismatch = source_extent is not None and current_extent is not None and source_extent != current_extent
|
||||
fixable = bool(
|
||||
mismatch
|
||||
and source_extent is not None
|
||||
and analysis["outside_extent"]
|
||||
and analysis["max_overflow"] <= max(0, source_extent - (current_extent or 0))
|
||||
)
|
||||
|
||||
zoom_item = zoom_summary[zoom]
|
||||
zoom_item["tile_count"] += 1
|
||||
for extent in analysis["extent_values"]:
|
||||
zoom_item["extent_counts"][extent] += 1
|
||||
if analysis["outside_extent"]:
|
||||
outside_extent_tiles += 1
|
||||
zoom_item["outside_extent_tiles"] += 1
|
||||
if mismatch:
|
||||
source_extent_mismatch_tiles += 1
|
||||
zoom_item["source_extent_mismatch_tiles"] += 1
|
||||
if fixable:
|
||||
fixable_from_source_tiles += 1
|
||||
|
||||
if args.apply_fix and fixable and source_extent is not None:
|
||||
if repair_tile_extents(tile_path, source_extent):
|
||||
repaired_tiles += 1
|
||||
analysis = analyze_tile(tile_path)
|
||||
|
||||
if len(sample_tiles) < args.sample_limit and (analysis["outside_extent"] or mismatch):
|
||||
sample_tiles.append(
|
||||
{
|
||||
"tile": str(rel),
|
||||
"extent_values": analysis["extent_values"],
|
||||
"source_extent": source_extent,
|
||||
"outside_extent": analysis["outside_extent"],
|
||||
"max_overflow": analysis["max_overflow"],
|
||||
"fixable_from_source": fixable,
|
||||
}
|
||||
)
|
||||
|
||||
normalized_zoom_summary = {
|
||||
zoom: {
|
||||
"tile_count": item["tile_count"],
|
||||
"outside_extent_tiles": item["outside_extent_tiles"],
|
||||
"source_extent_mismatch_tiles": item["source_extent_mismatch_tiles"],
|
||||
"extent_counts": dict(sorted(item["extent_counts"].items())),
|
||||
}
|
||||
for zoom, item in sorted(zoom_summary.items(), key=lambda kv: int(kv[0]))
|
||||
}
|
||||
|
||||
summary = {
|
||||
"label": args.label,
|
||||
"target_root": str(target_root),
|
||||
"source_root": str(source_root),
|
||||
"tile_count": len(tile_paths),
|
||||
"outside_extent_tiles": outside_extent_tiles,
|
||||
"source_extent_mismatch_tiles": source_extent_mismatch_tiles,
|
||||
"fixable_from_source_tiles": fixable_from_source_tiles,
|
||||
"repaired_tiles": repaired_tiles,
|
||||
"zoom_summary": normalized_zoom_summary,
|
||||
"sample_tiles": sample_tiles,
|
||||
}
|
||||
report_md, report_json = write_report(output_dir, args.label, target_root, source_root, summary)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"report_md": str(report_md),
|
||||
"report_json": str(report_json),
|
||||
"outside_extent_tiles": outside_extent_tiles,
|
||||
"source_extent_mismatch_tiles": source_extent_mismatch_tiles,
|
||||
"fixable_from_source_tiles": fixable_from_source_tiles,
|
||||
"repaired_tiles": repaired_tiles,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,685 @@
|
||||
{
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html",
|
||||
"cases": [
|
||||
{
|
||||
"id": "okinawa_10nm_full_z12",
|
||||
"label": "冲绳 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
127.67,
|
||||
26.21
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=127.67%2C26.21&zoom=12",
|
||||
"changed_pixels": 314441,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.861954,
|
||||
"mean_abs_rgb": [
|
||||
40.364,
|
||||
41.3728,
|
||||
55.3433
|
||||
],
|
||||
"rms_rgb": [
|
||||
67.4457,
|
||||
66.8189,
|
||||
83.1026
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "karatsu_5nm_full_z12",
|
||||
"label": "唐津 5 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
129.9697,
|
||||
33.4425
|
||||
],
|
||||
"radius_nm": 5,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=129.9697%2C33.4425&zoom=12",
|
||||
"changed_pixels": 313371,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.859021,
|
||||
"mean_abs_rgb": [
|
||||
32.3459,
|
||||
33.3448,
|
||||
50.9619
|
||||
],
|
||||
"rms_rgb": [
|
||||
53.1806,
|
||||
51.4741,
|
||||
72.4363
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "okinawa_10nm_semantic_z12",
|
||||
"label": "冲绳 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
127.67,
|
||||
26.21
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=127.67%2C26.21&zoom=12",
|
||||
"changed_pixels": 310142,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.85017,
|
||||
"mean_abs_rgb": [
|
||||
40.3146,
|
||||
41.3705,
|
||||
55.2069
|
||||
],
|
||||
"rms_rgb": [
|
||||
67.4717,
|
||||
66.8981,
|
||||
83.0146
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "karatsu_5nm_semantic_z12",
|
||||
"label": "唐津 5 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
129.9697,
|
||||
33.4425
|
||||
],
|
||||
"radius_nm": 5,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=12",
|
||||
"changed_pixels": 309067,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.847223,
|
||||
"mean_abs_rgb": [
|
||||
32.2753,
|
||||
33.2837,
|
||||
50.6801
|
||||
],
|
||||
"rms_rgb": [
|
||||
53.1618,
|
||||
51.4769,
|
||||
72.184
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "karatsu_5nm_full_z10",
|
||||
"label": "唐津 5 海里",
|
||||
"variant": "full",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
129.9697,
|
||||
33.4425
|
||||
],
|
||||
"radius_nm": 5,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=129.9697%2C33.4425&zoom=10",
|
||||
"changed_pixels": 267772,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.734024,
|
||||
"mean_abs_rgb": [
|
||||
25.3191,
|
||||
24.6696,
|
||||
50.8948
|
||||
],
|
||||
"rms_rgb": [
|
||||
43.688,
|
||||
39.9594,
|
||||
68.802
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "karatsu_5nm_semantic_z10",
|
||||
"label": "唐津 5 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
129.9697,
|
||||
33.4425
|
||||
],
|
||||
"radius_nm": 5,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=10",
|
||||
"changed_pixels": 263392,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.722018,
|
||||
"mean_abs_rgb": [
|
||||
24.5744,
|
||||
23.762,
|
||||
48.4161
|
||||
],
|
||||
"rms_rgb": [
|
||||
42.989,
|
||||
38.9809,
|
||||
66.1666
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "tokyo_bay_10nm_full_z12",
|
||||
"label": "东京湾 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
139.85,
|
||||
35.42
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=139.85%2C35.42&zoom=12",
|
||||
"changed_pixels": 242549,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.664882,
|
||||
"mean_abs_rgb": [
|
||||
21.7351,
|
||||
25.7701,
|
||||
23.1903
|
||||
],
|
||||
"rms_rgb": [
|
||||
44.1671,
|
||||
46.1494,
|
||||
50.8333
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "tokyo_bay_10nm_semantic_z12",
|
||||
"label": "东京湾 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
139.85,
|
||||
35.42
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=139.85%2C35.42&zoom=12",
|
||||
"changed_pixels": 235968,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.646842,
|
||||
"mean_abs_rgb": [
|
||||
21.5178,
|
||||
25.5206,
|
||||
22.6922
|
||||
],
|
||||
"rms_rgb": [
|
||||
43.901,
|
||||
45.7039,
|
||||
50.198
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "hakata_10nm_full_z10",
|
||||
"label": "博多港中心 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
130.335,
|
||||
33.6385
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=130.335%2C33.6385&zoom=10",
|
||||
"changed_pixels": 233090,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.638953,
|
||||
"mean_abs_rgb": [
|
||||
22.602,
|
||||
21.9769,
|
||||
43.8265
|
||||
],
|
||||
"rms_rgb": [
|
||||
43.1938,
|
||||
39.2381,
|
||||
64.4699
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "hakata_10nm_semantic_z10",
|
||||
"label": "博多港中心 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
130.335,
|
||||
33.6385
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=130.335%2C33.6385&zoom=10",
|
||||
"changed_pixels": 226222,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.620126,
|
||||
"mean_abs_rgb": [
|
||||
22.2492,
|
||||
21.4661,
|
||||
42.4354
|
||||
],
|
||||
"rms_rgb": [
|
||||
42.9444,
|
||||
38.7677,
|
||||
62.857
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "tokyo_bay_10nm_full_z10",
|
||||
"label": "东京湾 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
139.85,
|
||||
35.42
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=139.85%2C35.42&zoom=10",
|
||||
"changed_pixels": 208628,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.571897,
|
||||
"mean_abs_rgb": [
|
||||
21.5068,
|
||||
21.8381,
|
||||
37.6819
|
||||
],
|
||||
"rms_rgb": [
|
||||
43.9016,
|
||||
42.4239,
|
||||
60.7869
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "tokyo_bay_10nm_semantic_z10",
|
||||
"label": "东京湾 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
139.85,
|
||||
35.42
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=139.85%2C35.42&zoom=10",
|
||||
"changed_pixels": 208306,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.571014,
|
||||
"mean_abs_rgb": [
|
||||
21.3378,
|
||||
21.3679,
|
||||
37.1671
|
||||
],
|
||||
"rms_rgb": [
|
||||
43.6894,
|
||||
41.6284,
|
||||
60.1436
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "hakata_10nm_full_z12",
|
||||
"label": "博多港中心 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
130.335,
|
||||
33.6385
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=130.335%2C33.6385&zoom=12",
|
||||
"changed_pixels": 194689,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.533687,
|
||||
"mean_abs_rgb": [
|
||||
20.7021,
|
||||
21.5285,
|
||||
26.4928
|
||||
],
|
||||
"rms_rgb": [
|
||||
45.2912,
|
||||
45.6054,
|
||||
50.3669
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "osaka_bay_10nm_full_z10",
|
||||
"label": "大阪 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
135.35,
|
||||
34.61
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=135.35%2C34.61&zoom=10",
|
||||
"changed_pixels": 194451,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.533035,
|
||||
"mean_abs_rgb": [
|
||||
21.0579,
|
||||
20.6348,
|
||||
35.3722
|
||||
],
|
||||
"rms_rgb": [
|
||||
44.3815,
|
||||
41.2946,
|
||||
58.6624
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "osaka_bay_10nm_full_z12",
|
||||
"label": "大阪 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
135.35,
|
||||
34.61
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=135.35%2C34.61&zoom=12",
|
||||
"changed_pixels": 194138,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.532177,
|
||||
"mean_abs_rgb": [
|
||||
23.4548,
|
||||
25.0792,
|
||||
21.5488
|
||||
],
|
||||
"rms_rgb": [
|
||||
49.0486,
|
||||
49.6832,
|
||||
54.94
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "hakata_10nm_semantic_z12",
|
||||
"label": "博多港中心 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
130.335,
|
||||
33.6385
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=130.335%2C33.6385&zoom=12",
|
||||
"changed_pixels": 193860,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.531414,
|
||||
"mean_abs_rgb": [
|
||||
20.125,
|
||||
20.7366,
|
||||
25.1796
|
||||
],
|
||||
"rms_rgb": [
|
||||
44.4182,
|
||||
44.2282,
|
||||
48.4008
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "okinawa_10nm_full_z10",
|
||||
"label": "冲绳 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
127.67,
|
||||
26.21
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=127.67%2C26.21&zoom=10",
|
||||
"changed_pixels": 192396,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.527401,
|
||||
"mean_abs_rgb": [
|
||||
19.8231,
|
||||
18.7544,
|
||||
25.9994
|
||||
],
|
||||
"rms_rgb": [
|
||||
43.3599,
|
||||
39.8588,
|
||||
51.1734
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "osaka_bay_10nm_semantic_z10",
|
||||
"label": "大阪 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
135.35,
|
||||
34.61
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=135.35%2C34.61&zoom=10",
|
||||
"changed_pixels": 191439,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.524778,
|
||||
"mean_abs_rgb": [
|
||||
20.8376,
|
||||
20.016,
|
||||
34.5041
|
||||
],
|
||||
"rms_rgb": [
|
||||
44.2009,
|
||||
40.5148,
|
||||
57.712
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "osaka_bay_10nm_semantic_z12",
|
||||
"label": "大阪 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
135.35,
|
||||
34.61
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=135.35%2C34.61&zoom=12",
|
||||
"changed_pixels": 187180,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.513103,
|
||||
"mean_abs_rgb": [
|
||||
23.438,
|
||||
24.8296,
|
||||
21.3988
|
||||
],
|
||||
"rms_rgb": [
|
||||
49.1511,
|
||||
49.5348,
|
||||
54.9121
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "okinawa_10nm_semantic_z10",
|
||||
"label": "冲绳 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
127.67,
|
||||
26.21
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=127.67%2C26.21&zoom=10",
|
||||
"changed_pixels": 186366,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.510872,
|
||||
"mean_abs_rgb": [
|
||||
19.3335,
|
||||
17.9048,
|
||||
24.0201
|
||||
],
|
||||
"rms_rgb": [
|
||||
42.9399,
|
||||
38.9411,
|
||||
48.1207
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z10/compare_diff.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
# 全国 AOI 截图对比审计
|
||||
|
||||
## 总览
|
||||
|
||||
- compare page: `http://192.168.200.184/newpec/navsea-compare-full-audit.html`
|
||||
- cases: `20`
|
||||
|
||||
## 差异排序
|
||||
|
||||
### 冲绳 10 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.861954`
|
||||
- changed pixels: `314441` / `364800`
|
||||
- mean abs rgb: `[40.364, 41.3728, 55.3433]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=127.67%2C26.21&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z12/compare_diff.png`
|
||||
|
||||
### 唐津 5 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.859021`
|
||||
- changed pixels: `313371` / `364800`
|
||||
- mean abs rgb: `[32.3459, 33.3448, 50.9619]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=129.9697%2C33.4425&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z12/compare_diff.png`
|
||||
|
||||
### 冲绳 10 海里 · semantic · z12
|
||||
|
||||
- changed ratio: `0.85017`
|
||||
- changed pixels: `310142` / `364800`
|
||||
- mean abs rgb: `[40.3146, 41.3705, 55.2069]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=127.67%2C26.21&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z12/compare_diff.png`
|
||||
|
||||
### 唐津 5 海里 · semantic · z12
|
||||
|
||||
- changed ratio: `0.847223`
|
||||
- changed pixels: `309067` / `364800`
|
||||
- mean abs rgb: `[32.2753, 33.2837, 50.6801]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z12/compare_diff.png`
|
||||
|
||||
### 唐津 5 海里 · full · z10
|
||||
|
||||
- changed ratio: `0.734024`
|
||||
- changed pixels: `267772` / `364800`
|
||||
- mean abs rgb: `[25.3191, 24.6696, 50.8948]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=129.9697%2C33.4425&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z10/compare_diff.png`
|
||||
|
||||
### 唐津 5 海里 · semantic · z10
|
||||
|
||||
- changed ratio: `0.722018`
|
||||
- changed pixels: `263392` / `364800`
|
||||
- mean abs rgb: `[24.5744, 23.762, 48.4161]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z10/compare_diff.png`
|
||||
|
||||
### 东京湾 10 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.664882`
|
||||
- changed pixels: `242549` / `364800`
|
||||
- mean abs rgb: `[21.7351, 25.7701, 23.1903]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=139.85%2C35.42&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z12/compare_diff.png`
|
||||
|
||||
### 东京湾 10 海里 · semantic · z12
|
||||
|
||||
- changed ratio: `0.646842`
|
||||
- changed pixels: `235968` / `364800`
|
||||
- mean abs rgb: `[21.5178, 25.5206, 22.6922]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=139.85%2C35.42&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z12/compare_diff.png`
|
||||
|
||||
### 博多港中心 10 海里 · full · z10
|
||||
|
||||
- changed ratio: `0.638953`
|
||||
- changed pixels: `233090` / `364800`
|
||||
- mean abs rgb: `[22.602, 21.9769, 43.8265]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=130.335%2C33.6385&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z10/compare_diff.png`
|
||||
|
||||
### 博多港中心 10 海里 · semantic · z10
|
||||
|
||||
- changed ratio: `0.620126`
|
||||
- changed pixels: `226222` / `364800`
|
||||
- mean abs rgb: `[22.2492, 21.4661, 42.4354]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=130.335%2C33.6385&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z10/compare_diff.png`
|
||||
|
||||
### 东京湾 10 海里 · full · z10
|
||||
|
||||
- changed ratio: `0.571897`
|
||||
- changed pixels: `208628` / `364800`
|
||||
- mean abs rgb: `[21.5068, 21.8381, 37.6819]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=139.85%2C35.42&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z10/compare_diff.png`
|
||||
|
||||
### 东京湾 10 海里 · semantic · z10
|
||||
|
||||
- changed ratio: `0.571014`
|
||||
- changed pixels: `208306` / `364800`
|
||||
- mean abs rgb: `[21.3378, 21.3679, 37.1671]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=139.85%2C35.42&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z10/compare_diff.png`
|
||||
|
||||
### 博多港中心 10 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.533687`
|
||||
- changed pixels: `194689` / `364800`
|
||||
- mean abs rgb: `[20.7021, 21.5285, 26.4928]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=130.335%2C33.6385&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z12/compare_diff.png`
|
||||
|
||||
### 大阪 10 海里 · full · z10
|
||||
|
||||
- changed ratio: `0.533035`
|
||||
- changed pixels: `194451` / `364800`
|
||||
- mean abs rgb: `[21.0579, 20.6348, 35.3722]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=135.35%2C34.61&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z10/compare_diff.png`
|
||||
|
||||
### 大阪 10 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.532177`
|
||||
- changed pixels: `194138` / `364800`
|
||||
- mean abs rgb: `[23.4548, 25.0792, 21.5488]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=135.35%2C34.61&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z12/compare_diff.png`
|
||||
|
||||
### 博多港中心 10 海里 · semantic · z12
|
||||
|
||||
- changed ratio: `0.531414`
|
||||
- changed pixels: `193860` / `364800`
|
||||
- mean abs rgb: `[20.125, 20.7366, 25.1796]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=130.335%2C33.6385&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z12/compare_diff.png`
|
||||
|
||||
### 冲绳 10 海里 · full · z10
|
||||
|
||||
- changed ratio: `0.527401`
|
||||
- changed pixels: `192396` / `364800`
|
||||
- mean abs rgb: `[19.8231, 18.7544, 25.9994]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=127.67%2C26.21&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z10/compare_diff.png`
|
||||
|
||||
### 大阪 10 海里 · semantic · z10
|
||||
|
||||
- changed ratio: `0.524778`
|
||||
- changed pixels: `191439` / `364800`
|
||||
- mean abs rgb: `[20.8376, 20.016, 34.5041]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=135.35%2C34.61&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z10/compare_diff.png`
|
||||
|
||||
### 大阪 10 海里 · semantic · z12
|
||||
|
||||
- changed ratio: `0.513103`
|
||||
- changed pixels: `187180` / `364800`
|
||||
- mean abs rgb: `[23.438, 24.8296, 21.3988]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=135.35%2C34.61&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z12/compare_diff.png`
|
||||
|
||||
### 冲绳 10 海里 · semantic · z10
|
||||
|
||||
- changed ratio: `0.510872`
|
||||
- changed pixels: `186366` / `364800`
|
||||
- mean abs rgb: `[19.3335, 17.9048, 24.0201]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=127.67%2C26.21&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z10/compare_diff.png`
|
||||
|
||||
45
report/full_geometry_native_extent_fix_2026-04-18.json
Normal file
45
report/full_geometry_native_extent_fix_2026-04-18.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"scope": {
|
||||
"zoom": 12,
|
||||
"full_root": "/home/wwwroot/pbf-delivery-full-20260415",
|
||||
"semantic_root": "/home/wwwroot/pbf-delivery-full-semantic-20260416"
|
||||
},
|
||||
"pre_fix": {
|
||||
"full": {
|
||||
"tile_count": 48556,
|
||||
"extent_counts": {
|
||||
"1048576": 47366,
|
||||
"4096": 1190
|
||||
}
|
||||
},
|
||||
"semantic": {
|
||||
"tile_count": 48556,
|
||||
"extent_counts": {
|
||||
"1048576": 47296,
|
||||
"4096": 1260
|
||||
}
|
||||
}
|
||||
},
|
||||
"backup_roots": [
|
||||
"/home/wwwroot/pbf-delivery-full-20260415-backup-20260418-preextentfix",
|
||||
"/home/wwwroot/pbf-delivery-full-semantic-20260416-backup-20260418-preextentfix"
|
||||
],
|
||||
"repaired_tiles": {
|
||||
"full": 1183,
|
||||
"semantic": 1215
|
||||
},
|
||||
"post_fix": {
|
||||
"full": {
|
||||
"tile_count": 48556,
|
||||
"extent_counts": {
|
||||
"1048576": 48556
|
||||
}
|
||||
},
|
||||
"semantic": {
|
||||
"tile_count": 48556,
|
||||
"extent_counts": {
|
||||
"1048576": 48556
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
report/full_geometry_native_extent_fix_2026-04-18.md
Normal file
50
report/full_geometry_native_extent_fix_2026-04-18.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 全国 full / semantic z12 extent 逻辑审计与修复
|
||||
|
||||
## 范围
|
||||
|
||||
- `/home/wwwroot/pbf-delivery-full-20260415`
|
||||
- `/home/wwwroot/pbf-delivery-full-semantic-20260416`
|
||||
- 聚焦 zoom `12`
|
||||
|
||||
## 修前统计
|
||||
|
||||
- `full z12`
|
||||
- 总瓦片:`48556`
|
||||
- `extent = 1048576`:`47366`
|
||||
- `extent = 4096`:`1190`
|
||||
- `semantic z12`
|
||||
- 总瓦片:`48556`
|
||||
- `extent = 1048576`:`47296`
|
||||
- `extent = 4096`:`1260`
|
||||
|
||||
## 修复动作
|
||||
|
||||
- 已先做硬链接备份:
|
||||
- `/home/wwwroot/pbf-delivery-full-20260415-backup-20260418-preextentfix`
|
||||
- `/home/wwwroot/pbf-delivery-full-semantic-20260416-backup-20260418-preextentfix`
|
||||
- 对当前 `extent = 4096` 的 z12 错片:
|
||||
- 逐张读取原始 `newpec` 同路径 source tile
|
||||
- 若 source extent 为 `1048576`
|
||||
- 则把当前 delivery / semantic tile 的全部 layer extent 回写为 `1048576`
|
||||
|
||||
## 修复结果
|
||||
|
||||
- `full repaired = 1183`
|
||||
- `semantic repaired = 1215`
|
||||
|
||||
## 修后复核
|
||||
|
||||
- `full z12`
|
||||
- `48556 / 48556` 全部为 `1048576`
|
||||
- `semantic z12`
|
||||
- `48556 / 48556` 全部为 `1048576`
|
||||
|
||||
## 当前结论
|
||||
|
||||
- 这轮已确认全国 `full / semantic` 的 `z12` 中确实存在一批 extent 错退片
|
||||
- 当前这批错片已经按 source tile 口径修回
|
||||
- 这一步解决的是:
|
||||
- 几何 / extent / native 风险中的明确 metadata 错片问题
|
||||
- 这不等于:
|
||||
- 全国视觉等价性已经通过
|
||||
|
||||
639
src/pbf/navsea-compare-full-audit.html
Normal file
639
src/pbf/navsea-compare-full-audit.html
Normal file
@@ -0,0 +1,639 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>NavSea Full Audit Compare</title>
|
||||
<link href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css" rel="stylesheet" />
|
||||
<style>
|
||||
:root {
|
||||
--bg: #eef2ed;
|
||||
--panel: rgba(250, 248, 242, 0.94);
|
||||
--ink: #17232b;
|
||||
--muted: #667780;
|
||||
--line: rgba(23, 35, 43, 0.12);
|
||||
--shadow: 0 14px 28px rgba(23, 35, 43, 0.14);
|
||||
--left: #305f72;
|
||||
--right: #8b5e34;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(48, 95, 114, 0.12), transparent 28%),
|
||||
radial-gradient(circle at bottom right, rgba(139, 94, 52, 0.1), transparent 24%),
|
||||
var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: "Avenir Next", "Segoe UI", "PingFang SC", "Noto Sans SC", sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
align-items: flex-start;
|
||||
padding: 14px 18px 12px;
|
||||
background: linear-gradient(180deg, rgba(250, 248, 242, 0.98), rgba(250, 248, 242, 0.9));
|
||||
border-bottom: 1px solid var(--line);
|
||||
box-shadow: var(--shadow);
|
||||
z-index: 20;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-top: 4px;
|
||||
max-width: 920px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.version-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 40px;
|
||||
padding: 0 14px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(23, 35, 43, 0.16);
|
||||
background: rgba(250, 248, 242, 0.9);
|
||||
color: var(--ink);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 10px 22px rgba(23, 35, 43, 0.08);
|
||||
}
|
||||
|
||||
button {
|
||||
height: 40px;
|
||||
padding: 0 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(23, 35, 43, 0.16);
|
||||
background: linear-gradient(180deg, #234a59, #17333f);
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 10px 22px rgba(23, 35, 43, 0.18);
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pane {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
border-right: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.pane:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.map {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.pane-tag {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
z-index: 5;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
background: var(--panel);
|
||||
border: 1px solid rgba(23, 35, 43, 0.08);
|
||||
box-shadow: 0 12px 24px rgba(23, 35, 43, 0.12);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.pane-tag strong {
|
||||
display: block;
|
||||
margin-bottom: 3px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pane-tag span {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.inspect-panel {
|
||||
position: fixed;
|
||||
right: 14px;
|
||||
bottom: 14px;
|
||||
z-index: 30;
|
||||
width: min(380px, calc(100vw - 28px));
|
||||
max-height: 42vh;
|
||||
overflow: auto;
|
||||
padding: 11px 12px;
|
||||
border-radius: 14px;
|
||||
background: rgba(250, 248, 242, 0.95);
|
||||
border: 1px solid rgba(23, 35, 43, 0.12);
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.inspect-panel h3 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.inspect-hint {
|
||||
margin: 0 0 8px;
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.inspect-meta {
|
||||
display: grid;
|
||||
grid-template-columns: 96px 1fr;
|
||||
gap: 4px 8px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.inspect-meta dt {
|
||||
color: var(--muted);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.inspect-meta dd {
|
||||
margin: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.inspect-json {
|
||||
margin: 0;
|
||||
padding: 9px 10px;
|
||||
border-radius: 10px;
|
||||
background: rgba(23, 35, 43, 0.93);
|
||||
color: #eef4f7;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.status {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: 14px;
|
||||
transform: translateX(-50%);
|
||||
z-index: 30;
|
||||
max-width: calc(100% - 420px);
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(23, 35, 43, 0.84);
|
||||
color: #f7f9fb;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
body.audit-mode .pane-tag,
|
||||
body.audit-mode .inspect-panel,
|
||||
body.audit-mode .status,
|
||||
body.audit-mode .maplibregl-ctrl-top-right,
|
||||
body.audit-mode .maplibregl-ctrl-bottom-right,
|
||||
body.audit-mode .maplibregl-ctrl-bottom-left {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.layout {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
|
||||
.pane {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.pane:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.inspect-panel {
|
||||
width: min(360px, calc(100vw - 24px));
|
||||
max-height: 30vh;
|
||||
right: 12px;
|
||||
bottom: 54px;
|
||||
}
|
||||
|
||||
.status {
|
||||
max-width: calc(100% - 24px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="toolbar">
|
||||
<div>
|
||||
<div id="page-title" class="title">全国人工审计对比页</div>
|
||||
<div id="page-subtitle" class="subtitle">左侧固定加载原始 <code>style.json</code> + 原始 newpec 瓦片;右侧固定加载全国 delivery 的 <code>style.navsea-delivery-full.json</code> + 全国 delivery PBF + 当前 sprite。两边联动同步,适合直接审 style / pbf / sprite 的整体视觉差异。</div>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<div id="html-chip" class="version-chip">HTML: 加载中</div>
|
||||
<div id="style-chip" class="version-chip">Style: 加载中</div>
|
||||
<div id="pbf-chip" class="version-chip">PBF: 加载中</div>
|
||||
<div id="sprite-chip" class="version-chip">Sprite: 加载中</div>
|
||||
<button id="reload-btn" type="button">重新加载</button>
|
||||
<button id="home-btn" type="button">回到全国</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout">
|
||||
<section class="pane">
|
||||
<div class="pane-tag">
|
||||
<strong style="color: var(--left);">左侧 · 原始版</strong>
|
||||
<span>style.json + 原始 newpec 瓦片</span>
|
||||
</div>
|
||||
<div id="map-original" class="map"></div>
|
||||
</section>
|
||||
<section class="pane">
|
||||
<div class="pane-tag">
|
||||
<strong style="color: var(--right);">右侧 · Delivery</strong>
|
||||
<span id="delivery-pane-label">全国 delivery style + 全国 delivery PBF + 当前 sprite</span>
|
||||
</div>
|
||||
<div id="map-delivery" class="map"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<aside class="inspect-panel">
|
||||
<h3>点击对比</h3>
|
||||
<p id="inspect-hint" class="inspect-hint">点击左右任一地图对象,查看当前命中的 source、source-layer、render layer 和属性。</p>
|
||||
<dl class="inspect-meta">
|
||||
<dt>面板</dt>
|
||||
<dd id="inspect-side">-</dd>
|
||||
<dt>点击坐标</dt>
|
||||
<dd id="inspect-lnglat">-</dd>
|
||||
<dt>source</dt>
|
||||
<dd id="inspect-source">-</dd>
|
||||
<dt>source-layer</dt>
|
||||
<dd id="inspect-layer">-</dd>
|
||||
<dt>geometry</dt>
|
||||
<dd id="inspect-geometry">-</dd>
|
||||
<dt>render layer</dt>
|
||||
<dd id="inspect-render-layer">-</dd>
|
||||
</dl>
|
||||
<pre id="inspect-json" class="inspect-json">{
|
||||
"message": "等待点击对象"
|
||||
}</pre>
|
||||
</aside>
|
||||
|
||||
<div id="status" class="status">等待加载对比样式…</div>
|
||||
|
||||
<script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script>
|
||||
<script>
|
||||
const HTML_VERSION = "full-audit-r2-20260416-1504";
|
||||
const ORIGINAL_STYLE_URL = "./style.json";
|
||||
const ORIGINAL_TILE_URL = "http://192.168.200.184/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles/{z}/{x}/{y}.pbf";
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const auditMode = searchParams.get("audit") === "1";
|
||||
const variantParam = (searchParams.get("variant") || "semantic").trim().toLowerCase();
|
||||
const PROFILES = {
|
||||
full: {
|
||||
label: "全国",
|
||||
pageTitle: "全国人工审计对比页",
|
||||
subtitle: "左侧固定加载原始 <code>style.json</code> + 原始 newpec 瓦片;右侧固定加载全国 delivery 版 <code>style.navsea-delivery-full.json</code> + 全国 delivery PBF。两边联动同步,适合直接做原始版与全国 delivery 的截图对比审计。",
|
||||
deliveryPaneLabel: "全国 delivery style + 全国 delivery PBF",
|
||||
deliveryStyleUrl: "./domain/style.navsea-delivery-full.json",
|
||||
deliveryTileUrl: "http://192.168.200.184/pbf-delivery-full-20260415/{z}/{x}/{y}.pbf",
|
||||
deliveryStyleVersion: "full-delivery-style-current",
|
||||
deliveryPbfVersion: "full-delivery-pbf-20260415",
|
||||
deliverySpriteVersion: "newpec-sprite-current",
|
||||
deliveryStyleFile: "style.navsea-delivery-full.json",
|
||||
deliveryPbfName: "pbf-delivery-full-20260415",
|
||||
deliverySpriteName: "newpec/sprite/sprite",
|
||||
initialView: {
|
||||
center: [136.5, 35.8],
|
||||
zoom: 4.8,
|
||||
bearing: 0,
|
||||
pitch: 0
|
||||
},
|
||||
homeLabel: "回到全国"
|
||||
},
|
||||
semantic: {
|
||||
label: "全国",
|
||||
pageTitle: "全国人工审计对比页",
|
||||
subtitle: "左侧固定加载原始 <code>style.json</code> + 原始 newpec 瓦片;右侧固定加载全国 delivery 的语义版 <code>style.navsea-delivery-full-semantic.json</code> + 语义版全国 delivery PBF + 语义版 sprite atlas。两边联动同步,适合直接做原始版与全国语义版的截图对比审计。",
|
||||
deliveryPaneLabel: "全国语义版 delivery style + 语义版全国 delivery PBF + 语义版 sprite",
|
||||
deliveryStyleUrl: "./domain/style.navsea-delivery-full-semantic.json",
|
||||
deliveryTileUrl: "http://192.168.200.184/pbf-delivery-full-semantic-20260416/{z}/{x}/{y}.pbf",
|
||||
deliveryStyleVersion: "full-semantic-style-r3-20260416-2210",
|
||||
deliveryPbfVersion: "full-semantic-pbf-r2-20260416-2248-hazardfix",
|
||||
deliverySpriteVersion: "full-semantic-sprite-r1-20260416-1628",
|
||||
deliveryStyleFile: "style.navsea-delivery-full-semantic.json",
|
||||
deliveryPbfName: "pbf-delivery-full-semantic-20260416",
|
||||
deliverySpriteName: "newpec/sprite-semantic/sprite",
|
||||
initialView: {
|
||||
center: [136.5, 35.8],
|
||||
zoom: 4.8,
|
||||
bearing: 0,
|
||||
pitch: 0
|
||||
},
|
||||
homeLabel: "回到全国"
|
||||
}
|
||||
};
|
||||
const PROFILE = PROFILES[variantParam] || PROFILES.semantic;
|
||||
|
||||
const reloadBtn = document.getElementById("reload-btn");
|
||||
const homeBtn = document.getElementById("home-btn");
|
||||
const pageTitleEl = document.getElementById("page-title");
|
||||
const pageSubtitleEl = document.getElementById("page-subtitle");
|
||||
const deliveryPaneLabelEl = document.getElementById("delivery-pane-label");
|
||||
const htmlChip = document.getElementById("html-chip");
|
||||
const styleChip = document.getElementById("style-chip");
|
||||
const pbfChip = document.getElementById("pbf-chip");
|
||||
const spriteChip = document.getElementById("sprite-chip");
|
||||
const statusEl = document.getElementById("status");
|
||||
const inspectHintEl = document.getElementById("inspect-hint");
|
||||
const inspectSideEl = document.getElementById("inspect-side");
|
||||
const inspectLngLatEl = document.getElementById("inspect-lnglat");
|
||||
const inspectSourceEl = document.getElementById("inspect-source");
|
||||
const inspectLayerEl = document.getElementById("inspect-layer");
|
||||
const inspectGeometryEl = document.getElementById("inspect-geometry");
|
||||
const inspectRenderLayerEl = document.getElementById("inspect-render-layer");
|
||||
const inspectJsonEl = document.getElementById("inspect-json");
|
||||
|
||||
let originalMap;
|
||||
let deliveryMap;
|
||||
let syncLock = false;
|
||||
let styleVersion = `${HTML_VERSION}-${Date.now()}`;
|
||||
|
||||
htmlChip.textContent = `HTML: ${HTML_VERSION}`;
|
||||
if (auditMode) document.body.classList.add("audit-mode");
|
||||
|
||||
function parseNumberParam(value) {
|
||||
if (value === null || value === undefined || value === "") return null;
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
function getRequestedCamera() {
|
||||
const centerValue = searchParams.get("center");
|
||||
const zoomValue = parseNumberParam(searchParams.get("zoom"));
|
||||
const bearingValue = parseNumberParam(searchParams.get("bearing"));
|
||||
const pitchValue = parseNumberParam(searchParams.get("pitch"));
|
||||
|
||||
if (!centerValue && zoomValue === null && bearingValue === null && pitchValue === null) {
|
||||
return {
|
||||
center: PROFILE.initialView.center,
|
||||
zoom: PROFILE.initialView.zoom,
|
||||
bearing: PROFILE.initialView.bearing,
|
||||
pitch: PROFILE.initialView.pitch
|
||||
};
|
||||
}
|
||||
|
||||
let center = PROFILE.initialView.center;
|
||||
if (centerValue) {
|
||||
const parts = centerValue.split(",").map((item) => Number(item.trim()));
|
||||
if (parts.length === 2 && parts.every(Number.isFinite)) {
|
||||
center = [parts[0], parts[1]];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
center,
|
||||
zoom: zoomValue ?? PROFILE.initialView.zoom,
|
||||
bearing: bearingValue ?? PROFILE.initialView.bearing,
|
||||
pitch: pitchValue ?? PROFILE.initialView.pitch
|
||||
};
|
||||
}
|
||||
|
||||
function applyProfileUi() {
|
||||
pageTitleEl.textContent = PROFILE.pageTitle;
|
||||
pageSubtitleEl.innerHTML = PROFILE.subtitle;
|
||||
deliveryPaneLabelEl.textContent = PROFILE.deliveryPaneLabel;
|
||||
homeBtn.textContent = PROFILE.homeLabel;
|
||||
styleChip.textContent = `Style: ${PROFILE.deliveryStyleVersion} (${PROFILE.deliveryStyleFile})`;
|
||||
pbfChip.textContent = `PBF: ${PROFILE.deliveryPbfVersion} (${PROFILE.deliveryPbfName})`;
|
||||
spriteChip.textContent = `Sprite: ${PROFILE.deliverySpriteVersion} (${PROFILE.deliverySpriteName})`;
|
||||
document.title = `NavSea Compare · ${PROFILE.label}`;
|
||||
}
|
||||
|
||||
function setStatus(text) {
|
||||
statusEl.textContent = text;
|
||||
}
|
||||
|
||||
function withCacheBuster(url, version) {
|
||||
const separator = url.includes("?") ? "&" : "?";
|
||||
return `${url}${separator}v=${version}`;
|
||||
}
|
||||
|
||||
async function fetchStyle(url) {
|
||||
const response = await fetch(withCacheBuster(url, styleVersion), { cache: "no-store" });
|
||||
if (!response.ok) {
|
||||
throw new Error(`加载样式失败: ${url}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
function touchStyleAssets(style) {
|
||||
if (style.sprite) style.sprite = withCacheBuster(style.sprite, styleVersion);
|
||||
if (style.glyphs) style.glyphs = withCacheBuster(style.glyphs, styleVersion);
|
||||
Object.values(style.sources || {}).forEach((source) => {
|
||||
if (Array.isArray(source.tiles)) {
|
||||
source.tiles = source.tiles.map((tileUrl) => withCacheBuster(tileUrl, styleVersion));
|
||||
}
|
||||
if (typeof source.url === "string") {
|
||||
source.url = withCacheBuster(source.url, styleVersion);
|
||||
}
|
||||
});
|
||||
return style;
|
||||
}
|
||||
|
||||
function buildOriginalStyle(style) {
|
||||
const next = structuredClone(style);
|
||||
if (next.sources?.newpec) {
|
||||
next.sources.newpec.tiles = [withCacheBuster(ORIGINAL_TILE_URL, styleVersion)];
|
||||
}
|
||||
return touchStyleAssets(next);
|
||||
}
|
||||
|
||||
function buildDeliveryStyle(style) {
|
||||
const next = structuredClone(style);
|
||||
if (next.sources?.navsea_delivery) {
|
||||
next.sources.navsea_delivery.tiles = [withCacheBuster(PROFILE.deliveryTileUrl, styleVersion)];
|
||||
}
|
||||
return touchStyleAssets(next);
|
||||
}
|
||||
|
||||
function resetInspectPanel(message) {
|
||||
inspectHintEl.textContent = message;
|
||||
inspectSideEl.textContent = "-";
|
||||
inspectLngLatEl.textContent = "-";
|
||||
inspectSourceEl.textContent = "-";
|
||||
inspectLayerEl.textContent = "-";
|
||||
inspectGeometryEl.textContent = "-";
|
||||
inspectRenderLayerEl.textContent = "-";
|
||||
inspectJsonEl.textContent = JSON.stringify({ message }, null, 2);
|
||||
}
|
||||
|
||||
function formatLngLat(lngLat) {
|
||||
return `${lngLat.lng.toFixed(6)}, ${lngLat.lat.toFixed(6)}`;
|
||||
}
|
||||
|
||||
function updateInspectPanel(side, event, feature) {
|
||||
inspectHintEl.textContent = "已选中对象。继续点击可对照两侧差异。";
|
||||
inspectSideEl.textContent = side;
|
||||
inspectLngLatEl.textContent = formatLngLat(event.lngLat);
|
||||
inspectSourceEl.textContent = feature.source || "-";
|
||||
inspectLayerEl.textContent = feature.sourceLayer || feature.layer?.["source-layer"] || "-";
|
||||
inspectGeometryEl.textContent = feature.geometry?.type || "-";
|
||||
inspectRenderLayerEl.textContent = feature.layer?.id || "-";
|
||||
inspectJsonEl.textContent = JSON.stringify(feature.properties || {}, null, 2);
|
||||
}
|
||||
|
||||
function bindInspect(map, sideLabel) {
|
||||
map.on("click", (event) => {
|
||||
const features = map.queryRenderedFeatures(event.point);
|
||||
if (!features.length) {
|
||||
resetInspectPanel(`${sideLabel}点击位置没有命中对象。点击坐标: ${formatLngLat(event.lngLat)}`);
|
||||
return;
|
||||
}
|
||||
updateInspectPanel(sideLabel, event, features[0]);
|
||||
});
|
||||
}
|
||||
|
||||
function syncMaps(primary, secondary) {
|
||||
primary.on("move", () => {
|
||||
if (syncLock) return;
|
||||
syncLock = true;
|
||||
secondary.jumpTo({
|
||||
center: primary.getCenter(),
|
||||
zoom: primary.getZoom(),
|
||||
bearing: primary.getBearing(),
|
||||
pitch: primary.getPitch()
|
||||
});
|
||||
syncLock = false;
|
||||
});
|
||||
}
|
||||
|
||||
async function loadMaps() {
|
||||
const requestedCamera = getRequestedCamera();
|
||||
styleVersion = `${HTML_VERSION}-${Date.now()}`;
|
||||
reloadBtn.disabled = true;
|
||||
homeBtn.disabled = true;
|
||||
setStatus(`正在加载全国原始版和 delivery 版样式… HTML ${HTML_VERSION}`);
|
||||
|
||||
try {
|
||||
const [originalStyleRaw, deliveryStyleRaw] = await Promise.all([
|
||||
fetchStyle(ORIGINAL_STYLE_URL),
|
||||
fetchStyle(PROFILE.deliveryStyleUrl)
|
||||
]);
|
||||
const originalStyle = buildOriginalStyle(originalStyleRaw);
|
||||
const deliveryStyle = buildDeliveryStyle(deliveryStyleRaw);
|
||||
|
||||
if (!originalMap && !deliveryMap) {
|
||||
originalMap = new maplibregl.Map({
|
||||
container: "map-original",
|
||||
style: originalStyle,
|
||||
center: requestedCamera.center,
|
||||
zoom: requestedCamera.zoom,
|
||||
bearing: requestedCamera.bearing,
|
||||
pitch: requestedCamera.pitch
|
||||
});
|
||||
deliveryMap = new maplibregl.Map({
|
||||
container: "map-delivery",
|
||||
style: deliveryStyle,
|
||||
center: requestedCamera.center,
|
||||
zoom: requestedCamera.zoom,
|
||||
bearing: requestedCamera.bearing,
|
||||
pitch: requestedCamera.pitch
|
||||
});
|
||||
|
||||
originalMap.addControl(new maplibregl.NavigationControl(), "top-right");
|
||||
deliveryMap.addControl(new maplibregl.NavigationControl(), "top-right");
|
||||
bindInspect(originalMap, "左侧原始版");
|
||||
bindInspect(deliveryMap, "右侧Delivery");
|
||||
syncMaps(originalMap, deliveryMap);
|
||||
syncMaps(deliveryMap, originalMap);
|
||||
|
||||
let loaded = 0;
|
||||
function markLoaded() {
|
||||
loaded += 1;
|
||||
if (loaded >= 2) {
|
||||
setStatus(`全国双屏已加载,可直接拖动、缩放、点击对比。HTML ${HTML_VERSION}`);
|
||||
}
|
||||
}
|
||||
originalMap.on("load", markLoaded);
|
||||
deliveryMap.on("load", markLoaded);
|
||||
} else {
|
||||
const camera = requestedCamera;
|
||||
let loaded = 0;
|
||||
function markLoaded() {
|
||||
loaded += 1;
|
||||
if (loaded >= 2) {
|
||||
originalMap.jumpTo(camera);
|
||||
deliveryMap.jumpTo(camera);
|
||||
setStatus(`全国双屏样式已重新加载。HTML ${HTML_VERSION}`);
|
||||
}
|
||||
}
|
||||
originalMap.once("style.load", markLoaded);
|
||||
deliveryMap.once("style.load", markLoaded);
|
||||
originalMap.setStyle(originalStyle, { diff: false });
|
||||
deliveryMap.setStyle(deliveryStyle, { diff: false });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setStatus(error.message || "加载失败");
|
||||
} finally {
|
||||
reloadBtn.disabled = false;
|
||||
homeBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
reloadBtn.addEventListener("click", loadMaps);
|
||||
homeBtn.addEventListener("click", () => {
|
||||
if (!originalMap || !deliveryMap) return;
|
||||
const camera = {
|
||||
center: PROFILE.initialView.center,
|
||||
zoom: PROFILE.initialView.zoom,
|
||||
bearing: PROFILE.initialView.bearing,
|
||||
pitch: PROFILE.initialView.pitch
|
||||
};
|
||||
window.history.replaceState({}, "", window.location.pathname);
|
||||
originalMap.jumpTo(camera);
|
||||
deliveryMap.jumpTo(camera);
|
||||
setStatus("已回到全国初始视角。");
|
||||
});
|
||||
|
||||
applyProfileUi();
|
||||
resetInspectPanel("点击左右任一地图对象,查看当前命中的 source、source-layer、render layer 和属性。");
|
||||
loadMaps();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
37
tasks/pbf/NavSea_Full_AOI_Visual_Audit_2026-04-18.json
Normal file
37
tasks/pbf/NavSea_Full_AOI_Visual_Audit_2026-04-18.json
Normal file
@@ -0,0 +1,37 @@
|
||||
[
|
||||
{
|
||||
"id": "hakata_10nm",
|
||||
"label": "博多港中心 10 海里",
|
||||
"center": [130.335, 33.6385],
|
||||
"radius_nm": 10,
|
||||
"zoom_levels": [10, 12]
|
||||
},
|
||||
{
|
||||
"id": "tokyo_bay_10nm",
|
||||
"label": "东京湾 10 海里",
|
||||
"center": [139.85, 35.42],
|
||||
"radius_nm": 10,
|
||||
"zoom_levels": [10, 12]
|
||||
},
|
||||
{
|
||||
"id": "osaka_bay_10nm",
|
||||
"label": "大阪 10 海里",
|
||||
"center": [135.35, 34.61],
|
||||
"radius_nm": 10,
|
||||
"zoom_levels": [10, 12]
|
||||
},
|
||||
{
|
||||
"id": "okinawa_10nm",
|
||||
"label": "冲绳 10 海里",
|
||||
"center": [127.67, 26.21],
|
||||
"radius_nm": 10,
|
||||
"zoom_levels": [10, 12]
|
||||
},
|
||||
{
|
||||
"id": "karatsu_5nm",
|
||||
"label": "唐津 5 海里",
|
||||
"center": [129.9697, 33.4425],
|
||||
"radius_nm": 5,
|
||||
"zoom_levels": [10, 12]
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user