发布:完整项目代码(三视图 + 词条数据 + 加载页等)
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
|
||||
import math
|
||||
|
||||
from region_codes import make_code
|
||||
|
||||
DATA = [
|
||||
# ===================== 史前 =====================
|
||||
("~ BC 3,200,000", -3200000, "东非 · 埃塞俄比亚", "露西 · 南方古猿", "人", "fossil", 11.2, 40.5, "1974年在埃塞俄比亚哈达尔发现的一具约40%完整的南方古猿阿法种骨架,是直立行走的最早证据之一,被亲切地称为“人类祖母”。"),
|
||||
@@ -360,6 +362,44 @@ def dedupe(entries, km=DEDUP_KM):
|
||||
return out
|
||||
|
||||
DEDUPED = dedupe(DATA)
|
||||
|
||||
# —— 素描多样分配:同类主题轮换到不同画风,避免同一张素描重复出现 ——
|
||||
SKETCH_POOLS = {
|
||||
'sword': ['sword', 'helmet', 'cannon', 'fortress', 'bow', 'shield'],
|
||||
'crown': ['crown', 'throne', 'scepter', 'palace', 'laurel'],
|
||||
'machine': ['machine', 'gear', 'anvil', 'locomotive', 'factory'],
|
||||
'atom': ['atom', 'microscope', 'telescope', 'flask', 'lightbulb'],
|
||||
'scale': ['scale', 'gavel', 'columns', 'document'],
|
||||
'ship': ['ship', 'anchor', 'compass', 'globe'],
|
||||
'fossil': ['fossil', 'dinosaur', 'mountain'],
|
||||
'tablet': ['tablet', 'coin', 'document', 'hourglass'],
|
||||
'scroll': ['scroll', 'book', 'document', 'lyre'],
|
||||
'temple': ['temple', 'columns', 'palace', 'pyramid'],
|
||||
'colosseum': ['colosseum', 'fortress', 'helmet'],
|
||||
'figure': ['figure', 'mask', 'lyre'],
|
||||
'flag': ['flag', 'laurel', 'mountain', 'dove'],
|
||||
'bell': ['bell', 'gavel', 'document'],
|
||||
'rocket': ['rocket', 'telescope', 'lightbulb', 'compass'],
|
||||
'pyramid': ['pyramid', 'temple', 'palace', 'mountain'],
|
||||
'apple': ['apple', 'flask', 'tree', 'flower'],
|
||||
'book': ['book', 'scroll', 'document', 'key'],
|
||||
'tree': ['tree', 'flower', 'dinosaur'],
|
||||
'dove': ['dove', 'laurel', 'flag'],
|
||||
'globe': ['globe', 'compass', 'anchor', 'ship'],
|
||||
'horseman': ['horseman', 'bow', 'fortress', 'helmet'],
|
||||
}
|
||||
_pool_idx = {}
|
||||
def _reassign(e):
|
||||
pool = SKETCH_POOLS.get(e[5])
|
||||
if not pool:
|
||||
return e
|
||||
k = e[5]
|
||||
idx = _pool_idx.get(k, 0)
|
||||
s = pool[idx % len(pool)]
|
||||
_pool_idx[k] = idx + 1
|
||||
return (e[0], e[1], e[2], e[3], e[4], s, e[6], e[7], e[8])
|
||||
|
||||
DEDUPED = [_reassign(e) for e in DEDUPED]
|
||||
print(f"坐标去重:{len(DATA)} -> {len(DEDUPED)} 条(阈值 {DEDUP_KM} km)")
|
||||
|
||||
def esc(s):
|
||||
@@ -397,6 +437,7 @@ lines = ["// 世界历史里程碑条目数据",
|
||||
for i, (time, tn, region, name, cat, sketch, lat, lng, desc) in enumerate(DEDUPED, 1):
|
||||
lines.append(" {")
|
||||
lines.append(f" id: 'No.{i:03d}',")
|
||||
lines.append(f" code: '{esc(make_code(tn, region, lat, lng, i, name, cat))}',")
|
||||
lines.append(f" time: '{esc(normalize_time(time, tn))}',")
|
||||
lines.append(f" timeNumeric: {tn},")
|
||||
lines.append(f" region: '{esc(region)}',")
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
import random
|
||||
import math
|
||||
|
||||
from region_codes import make_code
|
||||
|
||||
N = 1000
|
||||
random.seed(20240101)
|
||||
|
||||
@@ -90,6 +92,7 @@ lines = ["// 压力测试用合成词条数据(1000 条)",
|
||||
for i, (time, tn, region, name, cat, sketch, lat, lng, desc) in enumerate(entries, 1):
|
||||
lines.append(" {")
|
||||
lines.append(f" id: 'No.{i:03d}',")
|
||||
lines.append(f" code: '{esc(make_code(tn, region, lat, lng, i, name, cat))}',")
|
||||
lines.append(f" time: '{esc(time)}',")
|
||||
lines.append(f" timeNumeric: {tn},")
|
||||
lines.append(f" region: '{esc(region)}',")
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""地区编码:把 region 字符串映射成「大洲码 + 国家/地区码 + 方位码」,
|
||||
用于生成编目号(如 AFETNE = 非洲 / 埃塞俄比亚 / 东北部)。
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
# region -> (大洲码, 国家/地区码, 现代国名[用于查标注点], 固定方位或 None)
|
||||
# 大洲码:AF 非洲 AS 亚洲 EU 欧洲 NA 北美 SA 南美 OC 大洋洲 AN 南极 GL 全球/海域
|
||||
REGION_CC = {
|
||||
# —— 非洲 ——
|
||||
"东非 · 肯尼亚": ("AF", "KE", "肯尼亚", None),
|
||||
"东非 · 埃塞俄比亚": ("AF", "ET", "埃塞俄比亚", None),
|
||||
"东非 · 坦桑尼亚": ("AF", "TZ", "坦桑尼亚", None),
|
||||
"北非 · 摩洛哥": ("AF", "MA", "摩洛哥", None),
|
||||
"古埃及 · 孟菲斯": ("AF", "EG", "埃及", None),
|
||||
"古埃及 · 阿拜多斯": ("AF", "EG", "埃及", None),
|
||||
"古埃及 · 吉萨": ("AF", "EG", "埃及", None),
|
||||
"古埃及 · 阿玛纳": ("AF", "EG", "埃及", None),
|
||||
"古埃及 · 亚历山大港": ("AF", "EG", "埃及", None),
|
||||
"北非 · 迦太基": ("AF", "TN", "突尼斯", None),
|
||||
"葡萄牙 · 休达": ("AF", "MA", "摩洛哥", None),
|
||||
"埃及 · 苏伊士": ("AF", "EG", "埃及", None),
|
||||
"南非 · 开普敦": ("AF", "ZA", "南非", None),
|
||||
"南非 · 比勒陀利亚": ("AF", "ZA", "南非", None),
|
||||
# —— 亚洲 ——
|
||||
"东亚 · 中国周口店": ("AS", "CN", "中国", None),
|
||||
"西亚 · 以色列": ("AS", "IL", "以色列", None),
|
||||
"西亚 · 伊拉克": ("AS", "IQ", "伊拉克", None),
|
||||
"日本 · 本州": ("AS", "JP", "日本", None),
|
||||
"西亚 · 两河流域": ("AS", "IQ", "伊拉克", None),
|
||||
"南亚 · 印度河谷": ("AS", "PK", "巴基斯坦", None),
|
||||
"美索不达米亚 · 两河流域": ("AS", "IQ", "伊拉克", None),
|
||||
"美索不达米亚 · 乌尔": ("AS", "IQ", "伊拉克", None),
|
||||
"美索不达米亚 · 巴比伦": ("AS", "IQ", "伊拉克", None),
|
||||
"中国 · 河南安阳": ("AS", "CN", "中国", None),
|
||||
"古印度 · 恒河流域": ("AS", "IN", "印度", None),
|
||||
"西亚 · 安纳托利亚": ("AS", "TR", "土耳其", None),
|
||||
"地中海 · 东岸": ("AS", "LB", "黎巴嫩", None),
|
||||
"中国 · 咸阳": ("AS", "CN", "中国", None),
|
||||
"古印度 · 蓝毗尼": ("AS", "NP", "尼泊尔", None),
|
||||
"中国 · 鲁国曲阜": ("AS", "CN", "中国", None),
|
||||
"古印度 · 华氏城": ("AS", "IN", "印度", None),
|
||||
"中国 · 北方边塞": ("AS", "CN", "中国", None),
|
||||
"中国 · 陕西西安": ("AS", "CN", "中国", None),
|
||||
"中国 · 陕西汉中": ("AS", "CN", "中国", None),
|
||||
"中国 · 四川": ("AS", "CN", "中国", None),
|
||||
"犹太 · 伯利恒": ("AS", "PS", "巴勒斯坦", None),
|
||||
"中国 · 东汉洛阳": ("AS", "CN", "中国", None),
|
||||
"拜占庭 · 君士坦丁堡": ("AS", "TR", "土耳其", None),
|
||||
"阿拉伯半岛 · 麦加": ("AS", "SA", "沙特阿拉伯", None),
|
||||
"中国 · 建康": ("AS", "CN", "中国", None),
|
||||
"阿拉伯半岛 · 麦地那": ("AS", "SA", "沙特阿拉伯", None),
|
||||
"波斯 · 萨珊": ("AS", "IR", "伊朗", None),
|
||||
"朝鲜半岛 · 百济": ("AS", "KR", "韩国", None),
|
||||
"中亚 · 怛罗斯": ("AS", "KZ", "哈萨克斯坦", None),
|
||||
"日本 · 京都": ("AS", "JP", "日本", None),
|
||||
"中国 · 敦煌": ("AS", "CN", "中国", None),
|
||||
"中国 · 开封": ("AS", "CN", "中国", None),
|
||||
"中国 · 北宋": ("AS", "CN", "中国", None),
|
||||
"柬埔寨 · 吴哥": ("AS", "KH", "柬埔寨", None),
|
||||
"蒙古高原": ("AS", "MN", "蒙古", None),
|
||||
"中国 · 大都": ("AS", "CN", "中国", None),
|
||||
"印度 · 卡利卡特": ("AS", "IN", "印度", None),
|
||||
"印度 · 帕尼帕特": ("AS", "IN", "印度", None),
|
||||
"日本 · 江户": ("AS", "JP", "日本", None),
|
||||
"中国 · 广东虎门": ("AS", "CN", "中国", None),
|
||||
"中国 · 武昌": ("AS", "CN", "中国", None),
|
||||
"中国 · 沈阳": ("AS", "CN", "中国", None),
|
||||
"中国 · 江西": ("AS", "CN", "中国", None),
|
||||
"日本 · 广岛": ("AS", "JP", "日本", None),
|
||||
"印度 · 新德里": ("AS", "IN", "印度", None),
|
||||
"以色列 · 特拉维夫": ("AS", "IL", "以色列", None),
|
||||
"朝鲜半岛": ("AS", "KP", "朝鲜", None),
|
||||
"尼泊尔 · 珠峰": ("AS", "NP", "尼泊尔", None),
|
||||
"印度尼西亚 · 万隆": ("AS", "ID", "印度尼西亚", None),
|
||||
"苏联 · 拜科努尔": ("AS", "KZ", "哈萨克斯坦", None),
|
||||
"中国 · 罗布泊": ("AS", "CN", "中国", None),
|
||||
"美国 · 越南": ("AS", "VN", "越南", None),
|
||||
"中东 · 埃及叙利亚": ("AS", "EG", "埃及", None),
|
||||
"伊朗 · 德黑兰": ("AS", "IR", "伊朗", None),
|
||||
"中国 · 香港": ("AS", "CN", "中国", None),
|
||||
"中国 · 澳门": ("AS", "CN", "中国", None),
|
||||
"伊拉克 · 巴格达": ("AS", "IQ", "伊拉克", None),
|
||||
"中国 · 酒泉": ("AS", "CN", "中国", None),
|
||||
"中国 · 文昌": ("AS", "CN", "中国", None),
|
||||
# —— 欧洲 ——
|
||||
"欧洲 · 德国": ("EU", "DE", "德国", None),
|
||||
"欧洲 · 奥地利": ("EU", "AT", "奥地利", None),
|
||||
"欧洲 · 法国": ("EU", "FR", "法国", None),
|
||||
"不列颠 · 威尔特郡": ("EU", "GB", "英国", None),
|
||||
"爱琴海 · 克里特岛": ("EU", "GR", "希腊", None),
|
||||
"古希腊 · 奥林匹亚": ("EU", "GR", "希腊", None),
|
||||
"罗马帝国 · 罗马": ("EU", "IT", "意大利", None),
|
||||
"古希腊 · 爱奥尼亚": ("EU", "GR", "希腊", None),
|
||||
"古希腊 · 雅典": ("EU", "GR", "希腊", None),
|
||||
"古希腊 · 马拉松": ("EU", "GR", "希腊", None),
|
||||
"古希腊 · 温泉关": ("EU", "GR", "希腊", None),
|
||||
"古希腊 · 斯塔基拉": ("EU", "GR", "希腊", None),
|
||||
"马其顿 · 希腊半岛": ("EU", "GR", "希腊", None),
|
||||
"古希腊 · 叙拉古": ("EU", "IT", "意大利", None),
|
||||
"古罗马 · 卡普阿": ("EU", "IT", "意大利", None),
|
||||
"古罗马 · 庞贝": ("EU", "IT", "意大利", None),
|
||||
"古罗马 · 米兰": ("EU", "IT", "意大利", None),
|
||||
"罗马帝国 · 塞萨洛尼基": ("EU", "GR", "希腊", None),
|
||||
"西罗马 · 拉文纳": ("EU", "IT", "意大利", None),
|
||||
"伊比利亚 · 直布罗陀": ("EU", "GB", "英国", None),
|
||||
"法兰克 · 图尔": ("EU", "FR", "法国", None),
|
||||
"法兰克 · 亚琛": ("EU", "DE", "德国", None),
|
||||
"法兰克 · 凡尔登": ("EU", "FR", "法国", None),
|
||||
"英格兰 · 黑斯廷斯": ("EU", "GB", "英国", None),
|
||||
"意大利 · 博洛尼亚": ("EU", "IT", "意大利", None),
|
||||
"西欧 · 克莱蒙": ("EU", "FR", "法国", None),
|
||||
"法国 · 巴黎": ("EU", "FR", "法国", None),
|
||||
"英格兰 · 兰尼米德": ("EU", "GB", "英国", None),
|
||||
"意大利 · 佛罗伦萨": ("EU", "IT", "意大利", None),
|
||||
"欧洲 · 英法": ("EU", "FR", "法国", None),
|
||||
"欧洲 · 意大利": ("EU", "IT", "意大利", None),
|
||||
"法国 · 鲁昂": ("EU", "FR", "法国", None),
|
||||
"德意志 · 美因茨": ("EU", "DE", "德国", None),
|
||||
"西班牙 · 格拉纳达": ("EU", "ES", "西班牙", None),
|
||||
"德意志 · 维滕贝格": ("EU", "DE", "德国", None),
|
||||
"波兰 · 弗龙堡": ("EU", "PL", "波兰", None),
|
||||
"德意志 · 奥格斯堡": ("EU", "DE", "德国", None),
|
||||
"英吉利海峡": ("EU", "GB", None, "CH"),
|
||||
"英国 · 伦敦": ("EU", "GB", "英国", None),
|
||||
"西班牙 · 马德里": ("EU", "ES", "西班牙", None),
|
||||
"意大利 · 帕多瓦": ("EU", "IT", "意大利", None),
|
||||
"欧洲 · 威斯特伐利亚": ("EU", "DE", "德国", None),
|
||||
"英国 · 剑桥": ("EU", "GB", "英国", None),
|
||||
"法国 · 凡尔赛": ("EU", "FR", "法国", None),
|
||||
"瑞典 · 乌普萨拉": ("EU", "SE", "瑞典", None),
|
||||
"欧洲 · 英格兰": ("EU", "GB", "英国", None),
|
||||
"英国 · 格拉斯哥": ("EU", "GB", "英国", None),
|
||||
"英格兰 · 伯克利": ("EU", "GB", "英国", None),
|
||||
"俄国 · 莫斯科": ("EU", "RU", "俄罗斯", None),
|
||||
"比利时 · 滑铁卢": ("EU", "BE", "比利时", None),
|
||||
"英格兰 · 斯托克顿": ("EU", "GB", "英国", None),
|
||||
"克里米亚 · 塞瓦斯托波尔": ("EU", "UA", "乌克兰", None),
|
||||
"意大利 · 都灵": ("EU", "IT", "意大利", None),
|
||||
"瑞典 · 斯德哥尔摩": ("EU", "SE", "瑞典", None),
|
||||
"俄国 · 圣彼得堡": ("EU", "RU", "俄罗斯", None),
|
||||
"德国 · 柏林": ("EU", "DE", "德国", None),
|
||||
"德国 · 曼海姆": ("EU", "DE", "德国", None),
|
||||
"德国 · 维尔茨堡": ("EU", "DE", "德国", None),
|
||||
"瑞士 · 伯尔尼": ("EU", "CH", "瑞士", None),
|
||||
"欧洲 · 萨拉热窝": ("EU", "BA", "波斯尼亚", None),
|
||||
"欧洲 · 康边": ("EU", "FR", "法国", None),
|
||||
"瑞士 · 日内瓦": ("EU", "CH", "瑞士", None),
|
||||
"欧洲 · 波兰": ("EU", "PL", "波兰", None),
|
||||
"苏联 · 斯大林格勒": ("EU", "RU", "俄罗斯", None),
|
||||
"法国 · 诺曼底": ("EU", "FR", "法国", None),
|
||||
"欧洲 · 布鲁塞尔": ("EU", "BE", "比利时", None),
|
||||
"苏联 · 切尔诺贝利": ("EU", "UA", "乌克兰", None),
|
||||
"欧洲 · 马斯特里赫特": ("EU", "NL", "荷兰", None),
|
||||
"苏格兰 · 爱丁堡": ("EU", "GB", "英国", None),
|
||||
"欧洲 · 法兰克福": ("EU", "DE", "德国", None),
|
||||
# —— 北美 ——
|
||||
"中南美 · 墨西哥湾": ("NA", "MX", "墨西哥", None),
|
||||
"北美 · 普利茅斯": ("NA", "US", "美国", None),
|
||||
"北美 · 费城": ("NA", "US", "美国", None),
|
||||
"世界 · 纽约": ("NA", "US", "美国", None),
|
||||
"海地 · 太子港": ("NA", "HT", "海地", None),
|
||||
"美国 · 萨姆特堡": ("NA", "US", "美国", None),
|
||||
"美国 · 葛底斯堡": ("NA", "US", "美国", None),
|
||||
"美国 · 华盛顿": ("NA", "US", "美国", None),
|
||||
"美国 · 波士顿": ("NA", "US", "美国", None),
|
||||
"美国 · 门洛帕克": ("NA", "US", "美国", None),
|
||||
"美国 · 基蒂霍克": ("NA", "US", "美国", None),
|
||||
"美国 · 纽约": ("NA", "US", "美国", None),
|
||||
"美国 · 珍珠港": ("NA", "US", "美国", None),
|
||||
"美国 · 芝加哥": ("NA", "US", "美国", None),
|
||||
"美国 · 新墨西哥": ("NA", "US", "美国", None),
|
||||
"古巴 · 哈瓦那": ("NA", "CU", "古巴", None),
|
||||
"古巴 · 加勒比": ("NA", "CU", "古巴", None),
|
||||
"美国 · 洛杉矶": ("NA", "US", "美国", None),
|
||||
"美国 · 西雅图": ("NA", "US", "美国", None),
|
||||
"美国 · 库比蒂诺": ("NA", "US", "美国", None),
|
||||
"美国 · 加利福尼亚": ("NA", "US", "美国", None),
|
||||
"美国 · 旧金山": ("NA", "US", "美国", None),
|
||||
"美国 · 卡纳维拉尔角": ("NA", "US", "美国", None),
|
||||
# —— 南美 ——
|
||||
"南美 · 秘鲁": ("SA", "PE", "秘鲁", None),
|
||||
"拉丁美洲 · 委内瑞拉": ("SA", "VE", "委内瑞拉", None),
|
||||
# —— 大洋洲 ——
|
||||
"澳大利亚 · 植物学湾": ("OC", "AU", "澳大利亚", None),
|
||||
# —— 跨区域 / 海域 / 极地 ——
|
||||
"北欧 · 格陵兰": ("NA", "GL", "格陵兰", None),
|
||||
"西班牙 → 美洲": ("NA", "ES", None, "AM"),
|
||||
"大西洋 · 托德西利亚斯": ("GL", "PT", None, "TR"),
|
||||
"欧洲 → 环球": ("GL", "GL", None, "GB"),
|
||||
"大西洋": ("GL", "AT", None, "OC"),
|
||||
"挪威 · 南极": ("AN", "AQ", None, "SP"),
|
||||
}
|
||||
|
||||
# 从 Natural Earth 国家数据读取标注点(按 ISO_A2 编码,用于计算条目在国家内的方位)
|
||||
COUNTRY_LABEL = {}
|
||||
try:
|
||||
with open("public/world-countries.geojson", encoding="utf-8") as _f:
|
||||
_gj = json.load(_f)
|
||||
for _ft in _gj.get("features", []):
|
||||
_p = _ft.get("properties", {})
|
||||
_iso = _p.get("ISO_A2")
|
||||
if _iso and _iso != "-99" and _p.get("LABEL_X") is not None and _p.get("LABEL_Y") is not None:
|
||||
COUNTRY_LABEL[_iso.upper()] = (_p["LABEL_X"], _p["LABEL_Y"])
|
||||
except Exception as _e:
|
||||
print(f"警告:读取国家标注点失败,方位将回退为 XX:{_e}")
|
||||
|
||||
|
||||
def _dir(lat, lng, cx, cy):
|
||||
dlat = lat - cy
|
||||
dlng = lng - cx
|
||||
ns = "N" if dlat > 0.5 else ("S" if dlat < -0.5 else "")
|
||||
ew = "E" if dlng > 0.5 else ("W" if dlng < -0.5 else "")
|
||||
if not ns and not ew:
|
||||
return "CE"
|
||||
if not ns:
|
||||
return "EA" if ew == "E" else "WE"
|
||||
if not ew:
|
||||
return "NO" if ns == "N" else "SO"
|
||||
return ns + ew
|
||||
|
||||
|
||||
# 条目类型枚举:E 事件/战役 P 人物/政党 T 学说/技术 I 文物/艺术品 B 建筑/奇观
|
||||
TYPE_OVERRIDE = {
|
||||
# 化石标本(人 → 文物)
|
||||
"露西 · 南方古猿": "I",
|
||||
"杰贝尔依罗智人化石": "I",
|
||||
# 建筑/奇观(物 → B)
|
||||
"哥贝克力石阵": "B",
|
||||
"加泰土丘聚落": "B",
|
||||
"胡夫金字塔": "B",
|
||||
"巨石阵": "B",
|
||||
"乌尔金字形神塔": "B",
|
||||
"米诺斯宫殿": "B",
|
||||
"罗马斗兽场": "B",
|
||||
"雅典卫城 · 帕特农神庙": "B",
|
||||
"万里长城": "B",
|
||||
"吴哥窟": "B",
|
||||
"巴黎圣母院": "B",
|
||||
"亚历山大图书馆": "B",
|
||||
"博洛尼亚大学": "B",
|
||||
"苏伊士运河通航": "B",
|
||||
# 学说/技术(物 → T)
|
||||
"赫梯铁器": "T",
|
||||
"造纸术萌芽": "T",
|
||||
"毕昇活字印刷": "T",
|
||||
"古腾堡印刷术": "T",
|
||||
"伽利略天文望远镜": "T",
|
||||
"哥白尼日心说": "T",
|
||||
"林奈分类法": "T",
|
||||
"门捷列夫元素周期表": "T",
|
||||
"瓦特改良蒸汽机": "T",
|
||||
"第一条铁路": "T",
|
||||
"诺贝尔炸药": "T",
|
||||
"电话发明": "T",
|
||||
"爱迪生白炽灯": "T",
|
||||
"本茨汽车": "T",
|
||||
"莱特兄弟首飞": "T",
|
||||
"福特T型车": "T",
|
||||
"晶体管发明": "T",
|
||||
"斯普特尼克1号": "T",
|
||||
"iPhone发布": "T",
|
||||
# 学说/技术(事 → T)
|
||||
"蔡伦造纸": "T",
|
||||
"达尔文《物种起源》": "T",
|
||||
"牛顿《自然哲学的数学原理》": "T",
|
||||
"爱因斯坦狭义相对论": "T",
|
||||
"互联网诞生": "T",
|
||||
"科赫发现结核杆菌": "T",
|
||||
"伦琴发现X射线": "T",
|
||||
"琴纳牛痘接种": "T",
|
||||
"首例心脏移植": "T",
|
||||
"克隆羊多利": "T",
|
||||
}
|
||||
|
||||
|
||||
def classify(name, category):
|
||||
if name in TYPE_OVERRIDE:
|
||||
return TYPE_OVERRIDE[name]
|
||||
if category == "人":
|
||||
return "P"
|
||||
if category == "事":
|
||||
return "E"
|
||||
return "I" # 物(默认文物/艺术品)
|
||||
|
||||
|
||||
def _time_seg(tn):
|
||||
age = abs(tn)
|
||||
if age < 10000:
|
||||
if tn < 0:
|
||||
return "BC" + f"{math.ceil(age / 100):02d}"
|
||||
return "AD" + f"{(tn - 1) // 100 + 1:02d}"
|
||||
if age < 100000:
|
||||
return "BCE1"
|
||||
if age < 1000000:
|
||||
return "BCE2"
|
||||
if age < 10000000:
|
||||
return "BCE3"
|
||||
return "BCE4"
|
||||
|
||||
|
||||
def make_code(tn, region, lat, lng, idx, name, category):
|
||||
"""生成编目号,如 E3-AFETNE-I0001。"""
|
||||
t = _time_seg(tn)
|
||||
# 地区段:大洲 + 国家/地区 + 方位
|
||||
cont, iso, zh, fixed = REGION_CC.get(region, ("GL", "XX", None, "XX"))
|
||||
if fixed:
|
||||
sub = fixed
|
||||
elif iso.upper() in COUNTRY_LABEL:
|
||||
cx, cy = COUNTRY_LABEL[iso.upper()]
|
||||
sub = _dir(lat, lng, cx, cy)
|
||||
else:
|
||||
sub = "XX"
|
||||
prefix = classify(name, category)
|
||||
return f"{t}-{cont}{iso}{sub}-{prefix}{idx:04d}"
|
||||
+29
-51
@@ -3,69 +3,47 @@ import puppeteer from 'puppeteer-core';
|
||||
const CHROME = 'C:/Program Files/Google/Chrome/Application/chrome.exe';
|
||||
const URL = process.argv[2] || 'http://localhost:5199/';
|
||||
|
||||
// 轻量冒烟测试:不做完整渲染流程 / 不触发 Cesium,
|
||||
// 只检查「加载无 JS 报错 + loading 环出现 + 6 秒后淡出 + App 就绪」。
|
||||
// 用真实 GPU(不强制 swiftshader 软件渲染)。
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: CHROME,
|
||||
headless: 'new',
|
||||
args: ['--no-sandbox', '--disable-gpu', '--use-gl=swiftshader', '--enable-unsafe-swiftshader', '--window-size=1440,900'],
|
||||
args: ['--no-sandbox', '--window-size=1440,900'],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 1440, height: 900 });
|
||||
|
||||
const errors = [];
|
||||
page.on('pageerror', (e) => errors.push('pageerror: ' + e.message));
|
||||
page.on('console', (m) => { if (m.type() === 'error') errors.push('console: ' + m.text()); });
|
||||
page.on('console', (m) => {
|
||||
// preview 模式无 HMR,WebSocket 连接失败是正常现象,过滤掉
|
||||
if (m.type() === 'error' && !/WebSocket|ERR_CONNECTION/i.test(m.text())) {
|
||||
errors.push('console: ' + m.text());
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
||||
await new Promise((r) => setTimeout(r, 6000));
|
||||
await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||||
|
||||
async function snapshot(label) {
|
||||
const info = await page.evaluate(() => {
|
||||
const app = window.__app;
|
||||
const gl = app.renderer.getContext();
|
||||
const w = gl.drawingBufferWidth, h = gl.drawingBufferHeight;
|
||||
const buf = new Uint8Array(w * h * 4);
|
||||
gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, buf);
|
||||
let sum = 0, n = 0, bright = 0;
|
||||
for (let i = 0; i < buf.length; i += 4) {
|
||||
const lum = 0.299 * buf[i] + 0.587 * buf[i + 1] + 0.114 * buf[i + 2];
|
||||
sum += lum; n++;
|
||||
if (lum > 40) bright++;
|
||||
}
|
||||
return {
|
||||
view: app.view,
|
||||
selected: app.selected,
|
||||
binderCurrent: app.binder.current,
|
||||
pages: app.binder.pages.length,
|
||||
timelineCards: app.timeline.cards.length,
|
||||
globeMarkers: app.globe.markers.length,
|
||||
detailName: document.getElementById('detail-name')?.textContent,
|
||||
detailNo: document.getElementById('detail-no')?.textContent,
|
||||
avgLum: +(sum / n).toFixed(2),
|
||||
brightPct: +((bright / n) * 100).toFixed(2),
|
||||
};
|
||||
});
|
||||
console.log(label, JSON.stringify(info, null, 0));
|
||||
}
|
||||
// 阶段一:加载环应已出现且正在展示
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
const loading = await page.evaluate(() => ({
|
||||
hasCanvas: !!document.querySelector('#loading-canvas canvas'),
|
||||
loadingShown: !document.getElementById('loading').classList.contains('hidden'),
|
||||
appReady: !!window.__app,
|
||||
}));
|
||||
|
||||
await snapshot('binder ');
|
||||
|
||||
await page.click('.view-btn[data-view="timeline"]');
|
||||
await new Promise((r) => setTimeout(r, 2400));
|
||||
await snapshot('timeline');
|
||||
|
||||
await page.click('.view-btn[data-view="globe"]');
|
||||
await new Promise((r) => setTimeout(r, 2400));
|
||||
await snapshot('globe ');
|
||||
|
||||
await page.click('.view-btn[data-view="binder"]');
|
||||
await new Promise((r) => setTimeout(r, 2400));
|
||||
await page.click('#btn-next');
|
||||
await new Promise((r) => setTimeout(r, 1500));
|
||||
await snapshot('flipped ');
|
||||
|
||||
await page.click('#btn-prev');
|
||||
await new Promise((r) => setTimeout(r, 1500));
|
||||
await snapshot('back ');
|
||||
// 阶段二:约 9 秒后,loading 应已淡出、App 应已就绪
|
||||
await new Promise((r) => setTimeout(r, 7000));
|
||||
const done = await page.evaluate(() => ({
|
||||
loadingHidden: document.getElementById('loading').classList.contains('hidden'),
|
||||
appReady: !!window.__app,
|
||||
view: window.__app?.view,
|
||||
pages: window.__app?.binder?.pages?.length,
|
||||
timelineCards: window.__app?.timeline?.cards?.length,
|
||||
}));
|
||||
|
||||
console.log('阶段一(加载中):', JSON.stringify(loading));
|
||||
console.log('阶段二(加载后):', JSON.stringify(done));
|
||||
console.log('ERRORS:', errors.length ? errors.join('\n') : '(none)');
|
||||
await browser.close();
|
||||
|
||||
Reference in New Issue
Block a user