134 lines
5.3 KiB
Python
Raw Normal View History

2025-07-28 10:56:04 +08:00
import os
import json
import numpy as np
import requests
from urllib.parse import urlparse, urljoin
from pathlib import Path
from pyproj import Transformer
from py3dtiles.tileset.content import B3dm
from py3dtiles.tilers.b3dm.wkb_utils import TriangleSoup
CACHE_DIR = "./_cache_tiles"
os.makedirs(CACHE_DIR, exist_ok=True)
def download_file(url, cache_dir=CACHE_DIR):
os.makedirs(cache_dir, exist_ok=True)
filename = os.path.basename(urlparse(url).path)
local_path = os.path.join(cache_dir, filename)
if not os.path.exists(local_path):
print(f"📥 Downloading {url}")
r = requests.get(url)
r.raise_for_status()
with open(local_path, "wb") as f:
f.write(r.content)
return local_path
def apply_transform(points, transform):
n = points.shape[0]
homo = np.hstack([points, np.ones((n,1))])
transformed = (transform @ homo.T).T[:, :3]
return transformed
def extract_positions_from_b3dm(b3dm_bytes, transform=None):
soup = TriangleSoup.from_glb_bytes(b3dm_bytes)
positions = soup.get_positions() # Nx3 numpy
if transform is not None:
positions = apply_transform(positions, transform)
return positions
def load_tileset_json(path_or_url):
if path_or_url.startswith("http"):
local_path = download_file(path_or_url)
else:
local_path = path_or_url
with open(local_path, "r", encoding="utf-8") as f:
js = json.load(f)
if "geometricError" not in js:
print("⚠️ tileset.json缺少geometricError自动补 1000.0")
js["geometricError"] = 1000.0
return js, os.path.dirname(local_path), path_or_url if path_or_url.startswith("http") else None
def parse_transform(tile_json):
# 返回 4x4 np.array
if "transform" in tile_json:
return np.array(tile_json["transform"]).reshape(4,4)
else:
return np.eye(4)
def recursive_load(tile_json, base_dir, base_url, bbox, transformer, transform_accum=None):
if transform_accum is None:
transform_accum = np.eye(4)
points_list = []
tile_transform = parse_transform(tile_json)
transform_total = transform_accum @ tile_transform
print(f"\n处理 Tile当前累计 transform 矩阵:\n{transform_total}")
print(f"Tile 的 bbox (输入): {bbox}")
content = tile_json.get("content")
if content:
uri = content.get("uri")
if uri and uri.endswith(".b3dm"):
if base_url is not None:
b3dm_url = urljoin(base_url + "/", uri)
b3dm_path = download_file(b3dm_url)
else:
b3dm_path = os.path.join(base_dir, uri)
if os.path.exists(b3dm_path):
with open(b3dm_path, "rb") as f:
b3dm = B3dm.from_array(f.read())
positions = extract_positions_from_b3dm(b3dm.glb_bytes, transform_total)
print(f"加载 {b3dm_path},原始顶点数量: {len(positions)}")
lon, lat, elev = transformer.transform(
positions[:,0], positions[:,1], positions[:,2]
)
print(f"转换后经度范围: {lon.min():.6f} ~ {lon.max():.6f}")
print(f"转换后纬度范围: {lat.min():.6f} ~ {lat.max():.6f}")
geo_pts = np.vstack([lon, lat, elev]).T
mask = (
(geo_pts[:,0] >= bbox[0]) & (geo_pts[:,0] <= bbox[2]) &
(geo_pts[:,1] >= bbox[1]) & (geo_pts[:,1] <= bbox[3])
)
in_bbox_count = np.sum(mask)
print(f"bbox内点数量: {in_bbox_count}")
points_list.append(geo_pts[mask])
elif uri and uri.endswith(".json"):
if base_url is not None:
child_url = urljoin(base_url + "/", uri)
child_json, child_base_dir, child_base_url = load_tileset_json(child_url)
else:
child_path = os.path.join(base_dir, uri)
child_json, child_base_dir, child_base_url = load_tileset_json(child_path)
pts = recursive_load(child_json, child_base_dir, child_base_url, bbox, transformer, transform_total)
if pts.size > 0:
points_list.append(pts)
for child in tile_json.get("children", []):
pts = recursive_load(child, base_dir, base_url, bbox, transformer, transform_total)
if pts.size > 0:
points_list.append(pts)
if points_list:
return np.vstack(points_list)
else:
return np.empty((0,3))
def load_tileset_all_points(path_or_url, bbox):
transformer = Transformer.from_crs("EPSG:4979", "EPSG:4326", always_xy=True)
tileset_json, base_dir, base_url = load_tileset_json(path_or_url)
points = recursive_load(tileset_json, base_dir, base_url, bbox, transformer)
print(f"\n总计提取 {len(points)} 个点在区域内")
return points
if __name__ == "__main__":
bbox = [116.391, 39.905, 116.405, 39.915]
model1_url = "http://8.137.54.85:9000/300bdf2b-a150-406e-be63-d28bd29b409f/dszh/1748398014403562192_OUT/B3DM/tileset.json"
model2_url = "http://8.137.54.85:9000/300bdf2b-a150-406e-be63-d28bd29b409f/dszh/1748325943733189898_OUT/B3DM/tileset.json"
pts1 = load_tileset_all_points(model1_url, bbox)
pts2 = load_tileset_all_points(model2_url, bbox)