fix: 新增处 战役
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -162,6 +162,9 @@ function seed() {
|
|||||||
['israel', '以色列', 34.78, 32.08],
|
['israel', '以色列', 34.78, 32.08],
|
||||||
['lincoln', '林肯号航母', 58.4215, 24.1568],
|
['lincoln', '林肯号航母', 58.4215, 24.1568],
|
||||||
['ford', '福特号航母', 24.1002, 35.7397],
|
['ford', '福特号航母', 24.1002, 35.7397],
|
||||||
|
['virginia_srilanka', '洛杉矶级攻击核潜艇(斯里兰卡外海)', 78.5, 5.2],
|
||||||
|
['tomahawk_arabian', '阿拉伯海潜艇(战斧)', 59.2, 23.0],
|
||||||
|
['arabian_sea_sub_torpedo', '阿拉伯海潜艇(鱼雷)', 59.2, 23.0],
|
||||||
]
|
]
|
||||||
strikeSources.forEach((r) => insertStrikeSource.run(...r))
|
strikeSources.forEach((r) => insertStrikeSource.run(...r))
|
||||||
|
|
||||||
@@ -202,6 +205,12 @@ function seed() {
|
|||||||
israelLebanonTargets.forEach(([lng, lat, name, struckAt]) => insertStrikeLine.run('israel', lng, lat, name, struckAt))
|
israelLebanonTargets.forEach(([lng, lat, name, struckAt]) => insertStrikeLine.run('israel', lng, lat, name, struckAt))
|
||||||
lincolnTargets.forEach(([lng, lat, name, struckAt]) => insertStrikeLine.run('lincoln', lng, lat, name, struckAt))
|
lincolnTargets.forEach(([lng, lat, name, struckAt]) => insertStrikeLine.run('lincoln', lng, lat, name, struckAt))
|
||||||
fordTargets.forEach(([lng, lat, name, struckAt]) => insertStrikeLine.run('ford', lng, lat, name, struckAt))
|
fordTargets.forEach(([lng, lat, name, struckAt]) => insertStrikeLine.run('ford', lng, lat, name, struckAt))
|
||||||
|
const virginiaSrilankaTargets = [[79.8, 6.5, '德纳号轻型护卫舰', '2026-02-28T08:00:00.000Z']]
|
||||||
|
virginiaSrilankaTargets.forEach(([lng, lat, name, struckAt]) => insertStrikeLine.run('virginia_srilanka', lng, lat, name, struckAt))
|
||||||
|
const tomahawkArabianTargets = [[56.26, 27.18, '沙希德·鲁德基号/无人机航母', '2026-02-28T03:00:00.000Z']]
|
||||||
|
tomahawkArabianTargets.forEach(([lng, lat, name, struckAt]) => insertStrikeLine.run('tomahawk_arabian', lng, lat, name, struckAt))
|
||||||
|
const arabianSubTorpedoTargets = [[56.26, 27.18, '沙希德·鲁德基号/无人机航母', '2026-02-28T03:00:00.000Z']]
|
||||||
|
arabianSubTorpedoTargets.forEach(([lng, lat, name, struckAt]) => insertStrikeLine.run('arabian_sea_sub_torpedo', lng, lat, name, struckAt))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.exec(`
|
db.exec(`
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export function BaseStatusPanel({ keyLocations, className = '' }: BaseStatusPane
|
|||||||
<div className="mb-2 flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-military-text-secondary">
|
<div className="mb-2 flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-military-text-secondary">
|
||||||
<MapPin className="h-3 w-3 shrink-0 text-blue-400" />
|
<MapPin className="h-3 w-3 shrink-0 text-blue-400" />
|
||||||
美军基地态势
|
美军基地态势
|
||||||
<span className="normal-case opacity-75">(随爬虫 AI 实时更新)</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1.5 text-xs tabular-nums">
|
<div className="flex flex-col gap-1.5 text-xs tabular-nums">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export function IranBaseStatusPanel({ keyLocations = [], className = '' }: IranB
|
|||||||
<div className="mb-2 flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-military-text-secondary">
|
<div className="mb-2 flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-military-text-secondary">
|
||||||
<MapPin className="h-3 w-3 shrink-0 text-amber-500" />
|
<MapPin className="h-3 w-3 shrink-0 text-amber-500" />
|
||||||
伊朗基地态势
|
伊朗基地态势
|
||||||
<span className="normal-case opacity-75">(随爬虫 AI 实时更新)</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1.5 text-xs tabular-nums">
|
<div className="flex flex-col gap-1.5 text-xs tabular-nums">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ const TEHRAN_SOURCE: [number, number] = [51.389, 35.6892]
|
|||||||
|
|
||||||
/** 攻击动画时间衰减:N 天内按天衰减(脉冲缩小、频次降低),超出仅保留减弱呼吸效果;天数可在编辑面板配置 */
|
/** 攻击动画时间衰减:N 天内按天衰减(脉冲缩小、频次降低),超出仅保留减弱呼吸效果;天数可在编辑面板配置 */
|
||||||
const MS_PER_DAY = 24 * 60 * 60 * 1000
|
const MS_PER_DAY = 24 * 60 * 60 * 1000
|
||||||
|
/** 手动添加的打击源:始终参与实时渲染列表,用 strikeCutoffDays 做衰减;仅在图例选中该项时不衰减 */
|
||||||
|
const MANUAL_STRIKE_SOURCE_IDS = new Set(['virginia_srilanka', 'tomahawk_arabian', 'arabian_sea_sub_torpedo'])
|
||||||
function isWithinAnimationWindow(
|
function isWithinAnimationWindow(
|
||||||
iso: string | null | undefined,
|
iso: string | null | undefined,
|
||||||
referenceTime: string,
|
referenceTime: string,
|
||||||
@@ -105,8 +107,11 @@ const FALLBACK_STRIKE_SOURCES: { id: string; name: string; lng: number; lat: num
|
|||||||
{ id: 'israel', name: '以色列', lng: 34.78, lat: 32.08 },
|
{ id: 'israel', name: '以色列', lng: 34.78, lat: 32.08 },
|
||||||
{ id: 'lincoln', name: '林肯号航母', lng: 58.4215, lat: 24.1568 },
|
{ id: 'lincoln', name: '林肯号航母', lng: 58.4215, lat: 24.1568 },
|
||||||
{ id: 'ford', name: '福特号航母', lng: 24.1002, lat: 35.7397 },
|
{ id: 'ford', name: '福特号航母', lng: 24.1002, lat: 35.7397 },
|
||||||
|
{ id: 'virginia_srilanka', name: '洛杉矶级攻击核潜艇(斯里兰卡外海)', lng: 78.5, lat: 5.2 },
|
||||||
|
{ id: 'tomahawk_arabian', name: '阿拉伯海潜艇(战斧)', lng: 59.2, lat: 23.0 },
|
||||||
|
{ id: 'arabian_sea_sub_torpedo', name: '阿拉伯海潜艇(鱼雷)', lng: 59.2, lat: 23.0 },
|
||||||
]
|
]
|
||||||
const FALLBACK_STRIKE_LINES: { sourceId: string; targets: { lng: number; lat: number; name?: string }[] }[] = [
|
const FALLBACK_STRIKE_LINES: { sourceId: string; targets: { lng: number; lat: number; name?: string; struck_at?: string | null }[] }[] = [
|
||||||
{
|
{
|
||||||
sourceId: 'israel',
|
sourceId: 'israel',
|
||||||
targets: [
|
targets: [
|
||||||
@@ -143,6 +148,24 @@ const FALLBACK_STRIKE_LINES: { sourceId: string; targets: { lng: number; lat: nu
|
|||||||
{ lng: 48.35, lat: 33.48, name: '霍拉马巴德储备库' },
|
{ lng: 48.35, lat: 33.48, name: '霍拉马巴德储备库' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
sourceId: 'virginia_srilanka',
|
||||||
|
targets: [
|
||||||
|
{ lng: 79.8, lat: 6.5, name: '德纳号轻型护卫舰', struck_at: '2026-02-28T08:00:00.000Z' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceId: 'tomahawk_arabian',
|
||||||
|
targets: [
|
||||||
|
{ lng: 56.26, lat: 27.18, name: '沙希德·鲁德基号/无人机航母', struck_at: '2026-02-28T03:00:00.000Z' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceId: 'arabian_sea_sub_torpedo',
|
||||||
|
targets: [
|
||||||
|
{ lng: 56.26, lat: 27.18, name: '沙希德·鲁德基号/无人机航母', struck_at: '2026-02-28T03:00:00.000Z' },
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
/** 二次贝塞尔曲线路径,更平滑的弧线 height 控制弧高 */
|
/** 二次贝塞尔曲线路径,更平滑的弧线 height 控制弧高 */
|
||||||
@@ -232,6 +255,9 @@ export function WarMap() {
|
|||||||
const lincolnPathsRef = useRef<[number, number][][]>([])
|
const lincolnPathsRef = useRef<[number, number][][]>([])
|
||||||
const fordPathsRef = useRef<[number, number][][]>([])
|
const fordPathsRef = useRef<[number, number][][]>([])
|
||||||
const israelPathsRef = useRef<[number, number][][]>([])
|
const israelPathsRef = useRef<[number, number][][]>([])
|
||||||
|
const virginiaPathsRef = useRef<[number, number][][]>([])
|
||||||
|
const tomahawkPathsRef = useRef<[number, number][][]>([])
|
||||||
|
const arabianSubTorpedoPathsRef = useRef<[number, number][][]>([])
|
||||||
const hezbollahPathsRef = useRef<[number, number][][]>([])
|
const hezbollahPathsRef = useRef<[number, number][][]>([])
|
||||||
const hormuzPathsRef = useRef<[number, number][][]>([])
|
const hormuzPathsRef = useRef<[number, number][][]>([])
|
||||||
const [legendOpen, setLegendOpen] = useState(true)
|
const [legendOpen, setLegendOpen] = useState(true)
|
||||||
@@ -240,6 +266,8 @@ export function WarMap() {
|
|||||||
| 'lincoln'
|
| 'lincoln'
|
||||||
| 'ford'
|
| 'ford'
|
||||||
| 'israel'
|
| 'israel'
|
||||||
|
| 'virginia_srilanka'
|
||||||
|
| 'tomahawk_arabian'
|
||||||
| 'hezbollah'
|
| 'hezbollah'
|
||||||
| 'iranToUs'
|
| 'iranToUs'
|
||||||
| 'iran'
|
| 'iran'
|
||||||
@@ -356,21 +384,40 @@ export function WarMap() {
|
|||||||
}, [usForces.keyLocations, iranForces.keyLocations])
|
}, [usForces.keyLocations, iranForces.keyLocations])
|
||||||
|
|
||||||
const mapData = situation.mapData
|
const mapData = situation.mapData
|
||||||
const strikeSources =
|
/** 打击源:API 有则用,并合并手动场景源(斯里兰卡潜艇、阿巴斯港战斧+潜艇鱼雷),缺则从 fallback 补全 */
|
||||||
mapData?.strikeSources?.length > 0 ? mapData.strikeSources : FALLBACK_STRIKE_SOURCES
|
const strikeSources = useMemo(() => {
|
||||||
const strikeLines =
|
const fromApi = mapData?.strikeSources?.length > 0 ? mapData.strikeSources : FALLBACK_STRIKE_SOURCES
|
||||||
mapData?.strikeLines?.length > 0 ? mapData.strikeLines : FALLBACK_STRIKE_LINES
|
const ids = new Set((fromApi as { id: string }[]).map((s) => s.id))
|
||||||
|
const toAdd = FALLBACK_STRIKE_SOURCES.filter((s) => !ids.has(s.id))
|
||||||
|
return toAdd.length > 0 ? [...fromApi, ...toAdd] : fromApi
|
||||||
|
}, [mapData?.strikeSources])
|
||||||
|
/** 打击线:API 有则用 API,并始终合并斯里兰卡/阿巴斯港(战斧+潜艇) fallback,保证场景必有数据可渲染 */
|
||||||
|
const strikeLines = useMemo(() => {
|
||||||
|
const fromApi = mapData?.strikeLines?.length > 0 ? mapData.strikeLines : FALLBACK_STRIKE_LINES
|
||||||
|
const hasVirginia = fromApi.some((l) => l.sourceId === 'virginia_srilanka')
|
||||||
|
const hasTomahawk = fromApi.some((l) => l.sourceId === 'tomahawk_arabian')
|
||||||
|
const hasArabianSubTorpedo = fromApi.some((l) => l.sourceId === 'arabian_sea_sub_torpedo')
|
||||||
|
const extra = [
|
||||||
|
...(hasVirginia ? [] : FALLBACK_STRIKE_LINES.filter((l) => l.sourceId === 'virginia_srilanka')),
|
||||||
|
...(hasTomahawk ? [] : FALLBACK_STRIKE_LINES.filter((l) => l.sourceId === 'tomahawk_arabian')),
|
||||||
|
...(hasArabianSubTorpedo ? [] : FALLBACK_STRIKE_LINES.filter((l) => l.sourceId === 'arabian_sea_sub_torpedo')),
|
||||||
|
]
|
||||||
|
return extra.length > 0 ? [...fromApi, ...extra] : fromApi
|
||||||
|
}, [mapData?.strikeLines])
|
||||||
|
|
||||||
/** 伊朗→美军基地:仅用 DB 数据,N 天内显示飞行动画 */
|
/** 伊朗→美军基地:仅用 DB 数据,N 天内显示飞行动画 */
|
||||||
const strikeCutoffDays = situation.animationConfig?.strikeCutoffDays ?? 5
|
const strikeCutoffDays = situation.animationConfig?.strikeCutoffDays ?? 5
|
||||||
/** 实时模式:仅渲染最近 1 天内的进展(主视角);默认开启 */
|
/** 实时模式:渲染最近 5 天内的进展(主视角);全部=不限时长 */
|
||||||
const REALTIME_CUTOFF_DAYS = 1
|
const REALTIME_CUTOFF_DAYS = 5
|
||||||
|
const UNLIMITED_CUTOFF_DAYS = 365
|
||||||
const [isRealtimeView, setIsRealtimeView] = useState(true)
|
const [isRealtimeView, setIsRealtimeView] = useState(true)
|
||||||
const isRealtimeViewRef = useRef(true)
|
const isRealtimeViewRef = useRef(true)
|
||||||
isRealtimeViewRef.current = isRealtimeView
|
isRealtimeViewRef.current = isRealtimeView
|
||||||
/** 未选中单项时:全部=用配置天数,实时=1 天;选中某项时用配置天数(该单项完整动画) */
|
/** 未选中单项时:全部=不限时长(365天),实时=5 天;选中某项时用 strikeCutoffDays(该单项完整动画) */
|
||||||
const effectiveCutoffDays =
|
const effectiveCutoffDays =
|
||||||
strikeLegendFilter === null && isRealtimeView ? REALTIME_CUTOFF_DAYS : strikeCutoffDays
|
strikeLegendFilter === null
|
||||||
|
? (isRealtimeView ? REALTIME_CUTOFF_DAYS : UNLIMITED_CUTOFF_DAYS)
|
||||||
|
: strikeCutoffDays
|
||||||
|
|
||||||
const attackPaths = useMemo(() => {
|
const attackPaths = useMemo(() => {
|
||||||
const attacked = (usForces.keyLocations || []).filter(
|
const attacked = (usForces.keyLocations || []).filter(
|
||||||
@@ -396,7 +443,7 @@ export function WarMap() {
|
|||||||
return m
|
return m
|
||||||
}, [strikeSources])
|
}, [strikeSources])
|
||||||
|
|
||||||
/** 盟军打击线:仅用 effectiveCutoffDays 内目标显示飞行动画(全部=配置天数,实时=1 天) */
|
/** 盟军打击线:仅用 effectiveCutoffDays 内目标显示飞行动画(全部=不限时长,实时=5 天) */
|
||||||
const filterTargetsByAnimationWindow = useMemo(
|
const filterTargetsByAnimationWindow = useMemo(
|
||||||
() => (targets: { lng: number; lat: number; struck_at?: string | null }[]) =>
|
() => (targets: { lng: number; lat: number; struck_at?: string | null }[]) =>
|
||||||
targets.filter((t) => isWithinAnimationWindow(t.struck_at ?? null, referenceTime, effectiveCutoffDays)),
|
targets.filter((t) => isWithinAnimationWindow(t.struck_at ?? null, referenceTime, effectiveCutoffDays)),
|
||||||
@@ -448,6 +495,41 @@ export function WarMap() {
|
|||||||
return line.targets.map((t) => parabolaPath(coords, [t.lng, t.lat]))
|
return line.targets.map((t) => parabolaPath(coords, [t.lng, t.lat]))
|
||||||
}, [strikeLines, sourceCoords])
|
}, [strikeLines, sourceCoords])
|
||||||
|
|
||||||
|
/** 场景 A:斯里兰卡深海伏击 — 鱼雷轨迹为直线(潜艇 [78.5,5.2] → 驱逐舰 [79.8,6.5]),不采用抛物线 */
|
||||||
|
const virginiaSrilankaPaths = useMemo(() => {
|
||||||
|
const line = strikeLines.find((l) => l.sourceId === 'virginia_srilanka')
|
||||||
|
const coords = sourceCoords.virginia_srilanka
|
||||||
|
if (!coords || !line) return []
|
||||||
|
return line.targets.map((t) => [coords, [t.lng, t.lat]] as [number, number][])
|
||||||
|
}, [strikeLines, sourceCoords])
|
||||||
|
/** 场景 B:阿拉伯海战斧 → 阿巴斯港,抛物线弹道 */
|
||||||
|
const tomahawkArabianPaths = useMemo(() => {
|
||||||
|
const line = strikeLines.find((l) => l.sourceId === 'tomahawk_arabian')
|
||||||
|
const coords = sourceCoords.tomahawk_arabian
|
||||||
|
if (!coords || !line) return []
|
||||||
|
return line.targets.map((t) => parabolaPath(coords, [t.lng, t.lat]))
|
||||||
|
}, [strikeLines, sourceCoords])
|
||||||
|
/** 上述两场景在时间窗口内的路径(与林肯/福特/以色列一致,用于实时模式) */
|
||||||
|
const effectiveVirginiaSrilankaPaths = useMemo(() => {
|
||||||
|
const line = strikeLines.find((l) => l.sourceId === 'virginia_srilanka')
|
||||||
|
const coords = sourceCoords.virginia_srilanka
|
||||||
|
if (!coords || !line) return []
|
||||||
|
return filterTargetsByAnimationWindow(line.targets).map((t) => [coords, [t.lng, t.lat]] as [number, number][])
|
||||||
|
}, [strikeLines, sourceCoords, filterTargetsByAnimationWindow])
|
||||||
|
const effectiveTomahawkArabianPaths = useMemo(() => {
|
||||||
|
const line = strikeLines.find((l) => l.sourceId === 'tomahawk_arabian')
|
||||||
|
const coords = sourceCoords.tomahawk_arabian
|
||||||
|
if (!coords || !line) return []
|
||||||
|
return filterTargetsByAnimationWindow(line.targets).map((t) => parabolaPath(coords, [t.lng, t.lat]))
|
||||||
|
}, [strikeLines, sourceCoords, filterTargetsByAnimationWindow])
|
||||||
|
/** 场景 B:阿巴斯港 — 同艇潜射/鱼雷轨迹(直线,与战斧同时) */
|
||||||
|
const arabianSubTorpedoPaths = useMemo(() => {
|
||||||
|
const line = strikeLines.find((l) => l.sourceId === 'arabian_sea_sub_torpedo')
|
||||||
|
const coords = sourceCoords.arabian_sea_sub_torpedo
|
||||||
|
if (!coords || !line) return []
|
||||||
|
return line.targets.map((t) => [coords, [t.lng, t.lat]] as [number, number][])
|
||||||
|
}, [strikeLines, sourceCoords])
|
||||||
|
|
||||||
/** 黎巴嫩→以色列:攻击源为黎巴嫩境内多处(提尔、西顿、巴勒贝克等),目标为以色列北部 */
|
/** 黎巴嫩→以色列:攻击源为黎巴嫩境内多处(提尔、西顿、巴勒贝克等),目标为以色列北部 */
|
||||||
const hezbollahPaths = useMemo(() => {
|
const hezbollahPaths = useMemo(() => {
|
||||||
const sources = EXTENDED_WAR_ZONES.lebanonStrikeSources
|
const sources = EXTENDED_WAR_ZONES.lebanonStrikeSources
|
||||||
@@ -481,21 +563,24 @@ export function WarMap() {
|
|||||||
|
|
||||||
const warMapData = useWarMapData()
|
const warMapData = useWarMapData()
|
||||||
|
|
||||||
/** 当前参与动画的目标的最小衰减系数,用于脉冲范围与攻击频次(缩小脉冲、拉长飞行周期) */
|
/** 当前参与动画的目标的最小衰减系数;手动打击(virginia/tomahawk)用 strikeCutoffDays 参与衰减,其余用 effectiveCutoffDays */
|
||||||
const animationDecayFactor = useMemo(() => {
|
const animationDecayFactor = useMemo(() => {
|
||||||
const decays: number[] = []
|
const decays: number[] = []
|
||||||
;(usForces.keyLocations || [])
|
;(usForces.keyLocations || [])
|
||||||
.filter((l) => l.status === 'attacked' && l.attacked_at && isWithinAnimationWindow(l.attacked_at, referenceTime, effectiveCutoffDays))
|
.filter((l) => l.status === 'attacked' && l.attacked_at && isWithinAnimationWindow(l.attacked_at, referenceTime, effectiveCutoffDays))
|
||||||
.forEach((l) => decays.push(getDecayFactor(l.attacked_at ?? null, referenceTime, effectiveCutoffDays)))
|
.forEach((l) => decays.push(getDecayFactor(l.attacked_at ?? null, referenceTime, effectiveCutoffDays)))
|
||||||
for (const line of strikeLines) {
|
for (const line of strikeLines) {
|
||||||
for (const t of filterTargetsByAnimationWindow(line.targets)) {
|
const cutoffDays = MANUAL_STRIKE_SOURCE_IDS.has(line.sourceId) ? strikeCutoffDays : effectiveCutoffDays
|
||||||
decays.push(getDecayFactor(t.struck_at ?? null, referenceTime, effectiveCutoffDays))
|
const inWindow = (t: { struck_at?: string | null }) => isWithinAnimationWindow(t.struck_at ?? null, referenceTime, cutoffDays)
|
||||||
|
for (const t of line.targets) {
|
||||||
|
if (!inWindow(t)) continue
|
||||||
|
decays.push(getDecayFactor((t as { struck_at?: string | null }).struck_at ?? null, referenceTime, cutoffDays))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hormuzPaths.length > 0) decays.push(1)
|
if (hormuzPaths.length > 0) decays.push(1)
|
||||||
if (hezbollahPaths.length > 0) decays.push(1)
|
if (hezbollahPaths.length > 0) decays.push(1)
|
||||||
return decays.length > 0 ? Math.min(...decays) : 1
|
return decays.length > 0 ? Math.min(...decays) : 1
|
||||||
}, [usForces.keyLocations, strikeLines, referenceTime, effectiveCutoffDays, filterTargetsByAnimationWindow, hormuzPaths.length, hezbollahPaths.length])
|
}, [usForces.keyLocations, strikeLines, referenceTime, effectiveCutoffDays, strikeCutoffDays, hormuzPaths.length, hezbollahPaths.length])
|
||||||
|
|
||||||
const animationDecayRef = useRef(1)
|
const animationDecayRef = useRef(1)
|
||||||
animationDecayRef.current = animationDecayFactor
|
animationDecayRef.current = animationDecayFactor
|
||||||
@@ -534,9 +619,31 @@ export function WarMap() {
|
|||||||
const effectiveHormuzPaths =
|
const effectiveHormuzPaths =
|
||||||
strikeLegendFilter === 'hormuz' || strikeLegendFilter === 'iran' ? hormuzPaths : strikeLegendFilter === null ? hormuzPaths : []
|
strikeLegendFilter === 'hormuz' || strikeLegendFilter === 'iran' ? hormuzPaths : strikeLegendFilter === null ? hormuzPaths : []
|
||||||
|
|
||||||
|
/** 斯里兰卡伏击 / 阿巴斯港突袭(手动打击):实时模式下始终进入渲染列表并做衰减;图例选中时完整动画不衰减 */
|
||||||
|
const effectiveVirginiaSrilankaPathsDisplay =
|
||||||
|
strikeLegendFilter === 'virginia_srilanka'
|
||||||
|
? virginiaSrilankaPaths
|
||||||
|
: strikeLegendFilter === null
|
||||||
|
? virginiaSrilankaPaths
|
||||||
|
: []
|
||||||
|
const effectiveTomahawkArabianPathsDisplay =
|
||||||
|
strikeLegendFilter === 'tomahawk_arabian'
|
||||||
|
? tomahawkArabianPaths
|
||||||
|
: strikeLegendFilter === null
|
||||||
|
? tomahawkArabianPaths
|
||||||
|
: []
|
||||||
|
/** 阿巴斯港潜艇直线(与战斧同时):与 tomahawk_arabian 同显 */
|
||||||
|
const effectiveArabianSubTorpedoPathsDisplay =
|
||||||
|
strikeLegendFilter === 'tomahawk_arabian' || strikeLegendFilter === null
|
||||||
|
? arabianSubTorpedoPaths
|
||||||
|
: []
|
||||||
|
|
||||||
lincolnPathsRef.current = effectiveLincolnPaths
|
lincolnPathsRef.current = effectiveLincolnPaths
|
||||||
fordPathsRef.current = effectiveFordPaths
|
fordPathsRef.current = effectiveFordPaths
|
||||||
israelPathsRef.current = effectiveIsraelPaths
|
israelPathsRef.current = effectiveIsraelPaths
|
||||||
|
virginiaPathsRef.current = effectiveVirginiaSrilankaPathsDisplay
|
||||||
|
tomahawkPathsRef.current = effectiveTomahawkArabianPathsDisplay
|
||||||
|
arabianSubTorpedoPathsRef.current = effectiveArabianSubTorpedoPathsDisplay
|
||||||
hezbollahPathsRef.current = effectiveHezbollahPaths
|
hezbollahPathsRef.current = effectiveHezbollahPaths
|
||||||
hormuzPathsRef.current = effectiveHormuzPaths
|
hormuzPathsRef.current = effectiveHormuzPaths
|
||||||
attackPathsRef.current = effectiveAttackPaths
|
attackPathsRef.current = effectiveAttackPaths
|
||||||
@@ -566,6 +673,38 @@ export function WarMap() {
|
|||||||
if (sourceCoords.israel) push(sourceCoords.israel)
|
if (sourceCoords.israel) push(sourceCoords.israel)
|
||||||
flattenPaths(israelPathsAll)
|
flattenPaths(israelPathsAll)
|
||||||
break
|
break
|
||||||
|
case 'virginia_srilanka': {
|
||||||
|
const src = sourceCoords.virginia_srilanka
|
||||||
|
const paths = effectiveVirginiaSrilankaPathsDisplay
|
||||||
|
if (src && paths.length > 0) {
|
||||||
|
const tgt = paths[0][paths[0].length - 1]
|
||||||
|
const padLine = 1.2
|
||||||
|
return [
|
||||||
|
Math.min(src[0], tgt[0]) - padLine,
|
||||||
|
Math.min(src[1], tgt[1]) - padLine,
|
||||||
|
Math.max(src[0], tgt[0]) + padLine,
|
||||||
|
Math.max(src[1], tgt[1]) + padLine,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'tomahawk_arabian': {
|
||||||
|
const src = sourceCoords.tomahawk_arabian ?? sourceCoords.arabian_sea_sub_torpedo
|
||||||
|
const paths = [...effectiveTomahawkArabianPathsDisplay, ...effectiveArabianSubTorpedoPathsDisplay]
|
||||||
|
if (src && paths.length > 0) {
|
||||||
|
const allCoords = paths.flatMap((p) => p)
|
||||||
|
const lngs = allCoords.map((c) => c[0])
|
||||||
|
const lats = allCoords.map((c) => c[1])
|
||||||
|
const padLine = 1.2
|
||||||
|
return [
|
||||||
|
Math.min(...lngs) - padLine,
|
||||||
|
Math.min(...lats) - padLine,
|
||||||
|
Math.max(...lngs) + padLine,
|
||||||
|
Math.max(...lats) + padLine,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
case 'hezbollah':
|
case 'hezbollah':
|
||||||
flattenPaths(hezbollahPaths)
|
flattenPaths(hezbollahPaths)
|
||||||
break
|
break
|
||||||
@@ -616,6 +755,9 @@ export function WarMap() {
|
|||||||
lincolnPathsAll,
|
lincolnPathsAll,
|
||||||
fordPathsAll,
|
fordPathsAll,
|
||||||
israelPathsAll,
|
israelPathsAll,
|
||||||
|
effectiveVirginiaSrilankaPathsDisplay,
|
||||||
|
effectiveTomahawkArabianPathsDisplay,
|
||||||
|
effectiveArabianSubTorpedoPathsDisplay,
|
||||||
hezbollahPaths,
|
hezbollahPaths,
|
||||||
attackPathsAll,
|
attackPathsAll,
|
||||||
]
|
]
|
||||||
@@ -660,16 +802,113 @@ export function WarMap() {
|
|||||||
}),
|
}),
|
||||||
[effectiveIsraelPaths]
|
[effectiveIsraelPaths]
|
||||||
)
|
)
|
||||||
/** 盟军打击目标点位(林肯/福特/以色列→伊朗+以色列→黎巴嫩)。全部/单项:显示全部目标;实时:仅 effectiveCutoffDays 内 */
|
/** 场景 A:斯里兰卡鱼雷轨迹 — 直线、蓝色虚线(Mk 48 ADCAP 声纳制导路径) */
|
||||||
|
const submarineTorpedoLinesGeoJson = useMemo(
|
||||||
|
() => ({
|
||||||
|
type: 'FeatureCollection' as const,
|
||||||
|
features:
|
||||||
|
strikeLegendFilter === null || strikeLegendFilter === 'virginia_srilanka'
|
||||||
|
? effectiveVirginiaSrilankaPathsDisplay.map((coords) => ({
|
||||||
|
type: 'Feature' as const,
|
||||||
|
properties: { weapon: 'Mk 48 Torpedo', status: 'Impact Confirmed' },
|
||||||
|
geometry: { type: 'LineString' as const, coordinates: coords },
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
}),
|
||||||
|
[effectiveVirginiaSrilankaPathsDisplay, strikeLegendFilter]
|
||||||
|
)
|
||||||
|
/** 场景 B:阿拉伯海战斧弹道 — 抛物线(阿巴斯港/沙希德·鲁德基号) */
|
||||||
|
const tomahawkStrikeLinesGeoJson = useMemo(
|
||||||
|
() => ({
|
||||||
|
type: 'FeatureCollection' as const,
|
||||||
|
features:
|
||||||
|
strikeLegendFilter === null || strikeLegendFilter === 'tomahawk_arabian'
|
||||||
|
? effectiveTomahawkArabianPathsDisplay.map((coords) => ({
|
||||||
|
type: 'Feature' as const,
|
||||||
|
properties: { target: 'Drone Carrier Shahid Roudaki', damage: 'Critical/Sunk' },
|
||||||
|
geometry: { type: 'LineString' as const, coordinates: coords },
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
}),
|
||||||
|
[effectiveTomahawkArabianPathsDisplay, strikeLegendFilter]
|
||||||
|
)
|
||||||
|
/** 场景 B:阿巴斯港同艇潜射/鱼雷 — 直线(与战斧同时) */
|
||||||
|
const arabianSubTorpedoLinesGeoJson = useMemo(
|
||||||
|
() => ({
|
||||||
|
type: 'FeatureCollection' as const,
|
||||||
|
features:
|
||||||
|
strikeLegendFilter === null || strikeLegendFilter === 'tomahawk_arabian'
|
||||||
|
? effectiveArabianSubTorpedoPathsDisplay.map((coords) => ({
|
||||||
|
type: 'Feature' as const,
|
||||||
|
properties: { weapon: 'Sub torpedo', status: 'Impact Confirmed' },
|
||||||
|
geometry: { type: 'LineString' as const, coordinates: coords },
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
}),
|
||||||
|
[effectiveArabianSubTorpedoPathsDisplay, strikeLegendFilter]
|
||||||
|
)
|
||||||
|
/** 战场态势标注:红圈×斯里兰卡外海驱逐舰沉没;紫色◆阿巴斯港高价值目标摧毁 */
|
||||||
|
const tacticalSymbolsGeoJson = useMemo(() => {
|
||||||
|
const showAll = strikeLegendFilter === null
|
||||||
|
const showVirginia = strikeLegendFilter === 'virginia_srilanka'
|
||||||
|
const showTomahawk = strikeLegendFilter === 'tomahawk_arabian'
|
||||||
|
const features: GeoJSON.Feature<GeoJSON.Point, { symbol: string; name: string; target?: string; damage?: string }>[] = []
|
||||||
|
if (showAll || showVirginia) {
|
||||||
|
features.push({
|
||||||
|
type: 'Feature' as const,
|
||||||
|
properties: { symbol: 'destruction', name: 'Destroyed Surface Combatant' },
|
||||||
|
geometry: { type: 'Point' as const, coordinates: [79.8, 6.5] as [number, number] },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (showAll || showTomahawk) {
|
||||||
|
features.push({
|
||||||
|
type: 'Feature' as const,
|
||||||
|
properties: {
|
||||||
|
symbol: 'explosion',
|
||||||
|
name: 'Port Facility/High Value Asset Neutralized',
|
||||||
|
target: 'Drone Carrier Shahid Roudaki',
|
||||||
|
damage: 'Critical/Sunk',
|
||||||
|
},
|
||||||
|
geometry: { type: 'Point' as const, coordinates: [56.26, 27.18] as [number, number] },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return { type: 'FeatureCollection' as const, features }
|
||||||
|
}, [strikeLegendFilter])
|
||||||
|
/** 盟军打击发起点地标(林肯/福特/以色列/斯里兰卡潜艇/阿拉伯海潜艇):样式统一,仅阵营颜色不同 */
|
||||||
|
const alliedStrikeSourcesGeoJson = useMemo(
|
||||||
|
() => ({
|
||||||
|
type: 'FeatureCollection' as const,
|
||||||
|
features: strikeSources.map((s) => ({
|
||||||
|
type: 'Feature' as const,
|
||||||
|
properties: { id: s.id, name: s.name },
|
||||||
|
geometry: { type: 'Point' as const, coordinates: [s.lng, s.lat] as [number, number] },
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
[strikeSources]
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 盟军打击目标点位(林肯/福特/以色列→伊朗+以色列→黎巴嫩+斯里兰卡/阿巴斯港)。全部/单项:显示全部目标;实时:仅 effectiveCutoffDays 内 */
|
||||||
const alliedStrikeTargetsFeatures = useMemo(() => {
|
const alliedStrikeTargetsFeatures = useMemo(() => {
|
||||||
const out: GeoJSON.Feature<GeoJSON.Point, { name: string; decay: number }>[] = []
|
const out: GeoJSON.Feature<GeoJSON.Point, { name: string; decay: number }>[] = []
|
||||||
const onlyRealtimeFilter =
|
const onlyRealtimeFilter =
|
||||||
strikeLegendFilter === null && isRealtimeView
|
strikeLegendFilter === null && isRealtimeView
|
||||||
for (const line of strikeLines) {
|
for (const line of strikeLines) {
|
||||||
if (strikeLegendFilter != null && strikeLegendFilter !== 'lincoln' && strikeLegendFilter !== 'ford' && strikeLegendFilter !== 'israel') continue
|
if (
|
||||||
|
strikeLegendFilter != null &&
|
||||||
|
strikeLegendFilter !== 'lincoln' &&
|
||||||
|
strikeLegendFilter !== 'ford' &&
|
||||||
|
strikeLegendFilter !== 'israel' &&
|
||||||
|
strikeLegendFilter !== 'virginia_srilanka' &&
|
||||||
|
strikeLegendFilter !== 'tomahawk_arabian' &&
|
||||||
|
strikeLegendFilter !== 'arabian_sea_sub_torpedo'
|
||||||
|
)
|
||||||
|
continue
|
||||||
if (strikeLegendFilter === 'lincoln' && line.sourceId !== 'lincoln') continue
|
if (strikeLegendFilter === 'lincoln' && line.sourceId !== 'lincoln') continue
|
||||||
if (strikeLegendFilter === 'ford' && line.sourceId !== 'ford') continue
|
if (strikeLegendFilter === 'ford' && line.sourceId !== 'ford') continue
|
||||||
if (strikeLegendFilter === 'israel' && line.sourceId !== 'israel') continue
|
if (strikeLegendFilter === 'israel' && line.sourceId !== 'israel') continue
|
||||||
|
if (strikeLegendFilter === 'virginia_srilanka' && line.sourceId !== 'virginia_srilanka') continue
|
||||||
|
if (strikeLegendFilter === 'tomahawk_arabian' && line.sourceId !== 'tomahawk_arabian' && line.sourceId !== 'arabian_sea_sub_torpedo') continue
|
||||||
|
if (strikeLegendFilter === 'arabian_sea_sub_torpedo' && line.sourceId !== 'arabian_sea_sub_torpedo') continue
|
||||||
for (const t of line.targets) {
|
for (const t of line.targets) {
|
||||||
if (onlyRealtimeFilter && !isWithinAnimationWindow((t as { struck_at?: string | null }).struck_at ?? null, referenceTime, effectiveCutoffDays))
|
if (onlyRealtimeFilter && !isWithinAnimationWindow((t as { struck_at?: string | null }).struck_at ?? null, referenceTime, effectiveCutoffDays))
|
||||||
continue
|
continue
|
||||||
@@ -929,6 +1168,60 @@ export function WarMap() {
|
|||||||
})
|
})
|
||||||
israelSrc.setData({ type: 'FeatureCollection', features })
|
israelSrc.setData({ type: 'FeatureCollection', features })
|
||||||
}
|
}
|
||||||
|
// 斯里兰卡伏击:鱼雷轨迹蓝色光点(直线路径)
|
||||||
|
const virginiaSrc = map.getSource('allied-strike-dots-virginia') as
|
||||||
|
| { setData: (d: GeoJSON.FeatureCollection) => void }
|
||||||
|
| undefined
|
||||||
|
const virginiaPaths = virginiaPathsRef.current
|
||||||
|
if (virginiaSrc && virginiaPaths.length > 0) {
|
||||||
|
const features: GeoJSON.Feature<GeoJSON.Point>[] = []
|
||||||
|
virginiaPaths.forEach((path, i) => {
|
||||||
|
if (i % step !== 0) return
|
||||||
|
const progress = (elapsed / FLIGHT_DURATION_MS + 0.6 + i / Math.max(virginiaPaths.length, 1)) % 1
|
||||||
|
features.push({
|
||||||
|
type: 'Feature' as const,
|
||||||
|
properties: {},
|
||||||
|
geometry: { type: 'Point' as const, coordinates: interpolateOnPath(path, progress) },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
virginiaSrc.setData({ type: 'FeatureCollection', features })
|
||||||
|
}
|
||||||
|
// 阿巴斯港突袭:战斧弹道紫色光点(抛物线路径)
|
||||||
|
const tomahawkSrc = map.getSource('allied-strike-dots-tomahawk') as
|
||||||
|
| { setData: (d: GeoJSON.FeatureCollection) => void }
|
||||||
|
| undefined
|
||||||
|
const tomahawkPaths = tomahawkPathsRef.current
|
||||||
|
if (tomahawkSrc && tomahawkPaths.length > 0) {
|
||||||
|
const features: GeoJSON.Feature<GeoJSON.Point>[] = []
|
||||||
|
tomahawkPaths.forEach((path, i) => {
|
||||||
|
if (i % step !== 0) return
|
||||||
|
const progress = (elapsed / FLIGHT_DURATION_MS + 0.4 + i / Math.max(tomahawkPaths.length, 1)) % 1
|
||||||
|
features.push({
|
||||||
|
type: 'Feature' as const,
|
||||||
|
properties: {},
|
||||||
|
geometry: { type: 'Point' as const, coordinates: interpolateOnPath(path, progress) },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
tomahawkSrc.setData({ type: 'FeatureCollection', features })
|
||||||
|
}
|
||||||
|
// 阿巴斯港同艇潜射/鱼雷光点(直线路径,与战斧同时)
|
||||||
|
const arabianSubSrc = map.getSource('allied-strike-dots-arabian-sub') as
|
||||||
|
| { setData: (d: GeoJSON.FeatureCollection) => void }
|
||||||
|
| undefined
|
||||||
|
const arabianSubPaths = arabianSubTorpedoPathsRef.current
|
||||||
|
if (arabianSubSrc && arabianSubPaths.length > 0) {
|
||||||
|
const features: GeoJSON.Feature<GeoJSON.Point>[] = []
|
||||||
|
arabianSubPaths.forEach((path, i) => {
|
||||||
|
if (i % step !== 0) return
|
||||||
|
const progress = (elapsed / FLIGHT_DURATION_MS + 0.5 + i / Math.max(arabianSubPaths.length, 1)) % 1
|
||||||
|
features.push({
|
||||||
|
type: 'Feature' as const,
|
||||||
|
properties: {},
|
||||||
|
geometry: { type: 'Point' as const, coordinates: interpolateOnPath(path, progress) },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
arabianSubSrc.setData({ type: 'FeatureCollection', features })
|
||||||
|
}
|
||||||
// 真主党打击以色列北部:橙红光点,与林肯/福特/以色列同一动画方式
|
// 真主党打击以色列北部:橙红光点,与林肯/福特/以色列同一动画方式
|
||||||
const hezSrc = map.getSource('hezbollah-strike-dots') as
|
const hezSrc = map.getSource('hezbollah-strike-dots') as
|
||||||
| { setData: (d: GeoJSON.FeatureCollection) => void }
|
| { setData: (d: GeoJSON.FeatureCollection) => void }
|
||||||
@@ -1085,6 +1378,9 @@ export function WarMap() {
|
|||||||
(map.getSource('allied-strike-dots-lincoln') && lincolnPathsRef.current.length > 0) ||
|
(map.getSource('allied-strike-dots-lincoln') && lincolnPathsRef.current.length > 0) ||
|
||||||
(map.getSource('allied-strike-dots-ford') && fordPathsRef.current.length > 0) ||
|
(map.getSource('allied-strike-dots-ford') && fordPathsRef.current.length > 0) ||
|
||||||
(map.getSource('allied-strike-dots-israel') && israelPathsRef.current.length > 0) ||
|
(map.getSource('allied-strike-dots-israel') && israelPathsRef.current.length > 0) ||
|
||||||
|
(map.getSource('allied-strike-dots-virginia') && virginiaPathsRef.current.length > 0) ||
|
||||||
|
(map.getSource('allied-strike-dots-tomahawk') && tomahawkPathsRef.current.length > 0) ||
|
||||||
|
(map.getSource('allied-strike-dots-arabian-sub') && arabianSubTorpedoPathsRef.current.length > 0) ||
|
||||||
(map.getSource('hezbollah-strike-dots') && hezbollahPathsRef.current.length > 0) ||
|
(map.getSource('hezbollah-strike-dots') && hezbollahPathsRef.current.length > 0) ||
|
||||||
(map.getSource('iran-hormuz-dots') && hormuzPathsRef.current.length > 0) ||
|
(map.getSource('iran-hormuz-dots') && hormuzPathsRef.current.length > 0) ||
|
||||||
map.getSource('kurdish-pincer-growth') ||
|
map.getSource('kurdish-pincer-growth') ||
|
||||||
@@ -1262,7 +1558,7 @@ export function WarMap() {
|
|||||||
fitToTheater({ duration: 500 })
|
fitToTheater({ duration: 500 })
|
||||||
}}
|
}}
|
||||||
className={`flex items-center gap-1 rounded px-1 py-0.5 transition-colors ${strikeLegendFilter === null && isRealtimeView ? 'ring-1 ring-amber-400/80 bg-amber-400/10' : 'hover:bg-white/10'}`}
|
className={`flex items-center gap-1 rounded px-1 py-0.5 transition-colors ${strikeLegendFilter === null && isRealtimeView ? 'ring-1 ring-amber-400/80 bg-amber-400/10' : 'hover:bg-white/10'}`}
|
||||||
title="主视角,仅渲染最新进展(近 1 天)"
|
title="主视角,仅渲染最新进展(近 5 天)"
|
||||||
>
|
>
|
||||||
<span className="h-1.5 w-1.5 rounded-full bg-amber-400" /> 实时
|
<span className="h-1.5 w-1.5 rounded-full bg-amber-400" /> 实时
|
||||||
</button>
|
</button>
|
||||||
@@ -1290,6 +1586,22 @@ export function WarMap() {
|
|||||||
>
|
>
|
||||||
<span className="h-1.5 w-1.5 rounded-full bg-[#22D3EE]" /> 以色列打击
|
<span className="h-1.5 w-1.5 rounded-full bg-[#22D3EE]" /> 以色列打击
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => toggleStrikeFilter('virginia_srilanka')}
|
||||||
|
className={`flex items-center gap-1 rounded px-1 py-0.5 transition-colors ${strikeLegendFilter === 'virginia_srilanka' ? 'ring-1 ring-blue-400 bg-blue-400/20' : 'hover:bg-white/10'}`}
|
||||||
|
title="斯里兰卡海域深海伏击 — 鱼雷轨迹"
|
||||||
|
>
|
||||||
|
<span className="h-1.5 w-1.5 rounded-full bg-blue-400" /> 斯里兰卡伏击
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => toggleStrikeFilter('tomahawk_arabian')}
|
||||||
|
className={`flex items-center gap-1 rounded px-1 py-0.5 transition-colors ${strikeLegendFilter === 'tomahawk_arabian' ? 'ring-1 ring-purple-400 bg-purple-400/20' : 'hover:bg-white/10'}`}
|
||||||
|
title="阿巴斯港/沙希德·鲁德基号远程打击"
|
||||||
|
>
|
||||||
|
<span className="h-1.5 w-1.5 rounded-full bg-purple-400" /> 阿巴斯港突袭
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => toggleStrikeFilter('hormuz')}
|
onClick={() => toggleStrikeFilter('hormuz')}
|
||||||
@@ -1833,6 +2145,65 @@ export function WarMap() {
|
|||||||
/>
|
/>
|
||||||
</Source>
|
</Source>
|
||||||
|
|
||||||
|
{/* 盟军打击发起点地标:与目标点样式统一(大小/描边一致),仅颜色区分阵营 */}
|
||||||
|
<Source id="allied-strike-sources" type="geojson" data={alliedStrikeSourcesGeoJson}>
|
||||||
|
<Layer
|
||||||
|
id="allied-strike-sources-circle"
|
||||||
|
type="circle"
|
||||||
|
filter={
|
||||||
|
strikeLegendFilter == null
|
||||||
|
? ['in', ['get', 'id'], ['literal', ['lincoln', 'ford', 'israel', 'virginia_srilanka', 'tomahawk_arabian', 'arabian_sea_sub_torpedo']]]
|
||||||
|
: strikeLegendFilter === 'tomahawk_arabian'
|
||||||
|
? ['in', ['get', 'id'], ['literal', ['tomahawk_arabian', 'arabian_sea_sub_torpedo']]]
|
||||||
|
: ['==', ['get', 'id'], strikeLegendFilter]
|
||||||
|
}
|
||||||
|
paint={{
|
||||||
|
'circle-radius': ['interpolate', ['linear'], ['zoom'], 3, 1.5, 5, 2.5, 8, 4],
|
||||||
|
'circle-color': [
|
||||||
|
'match',
|
||||||
|
['get', 'id'],
|
||||||
|
'lincoln',
|
||||||
|
'#3B82F6',
|
||||||
|
'ford',
|
||||||
|
'#06B6D4',
|
||||||
|
'israel',
|
||||||
|
'#22D3EE',
|
||||||
|
'virginia_srilanka',
|
||||||
|
'#3B82F6',
|
||||||
|
'tomahawk_arabian',
|
||||||
|
'#A855F7',
|
||||||
|
'arabian_sea_sub_torpedo',
|
||||||
|
'#06B6D4',
|
||||||
|
'#94A3B8',
|
||||||
|
],
|
||||||
|
'circle-stroke-width': ['interpolate', ['linear'], ['zoom'], 3, 0.5, 7, 1],
|
||||||
|
'circle-stroke-color': '#fff',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Layer
|
||||||
|
id="allied-strike-sources-label"
|
||||||
|
type="symbol"
|
||||||
|
filter={
|
||||||
|
strikeLegendFilter == null
|
||||||
|
? ['in', ['get', 'id'], ['literal', ['lincoln', 'ford', 'israel', 'virginia_srilanka', 'tomahawk_arabian', 'arabian_sea_sub_torpedo']]]
|
||||||
|
: strikeLegendFilter === 'tomahawk_arabian'
|
||||||
|
? ['in', ['get', 'id'], ['literal', ['tomahawk_arabian', 'arabian_sea_sub_torpedo']]]
|
||||||
|
: ['==', ['get', 'id'], strikeLegendFilter]
|
||||||
|
}
|
||||||
|
layout={{
|
||||||
|
'text-field': ['get', 'name'],
|
||||||
|
'text-size': ['interpolate', ['linear'], ['zoom'], 3, 6, 5, 8, 7, 10, 10, 13],
|
||||||
|
'text-anchor': 'top',
|
||||||
|
'text-offset': [0, 0.8],
|
||||||
|
}}
|
||||||
|
paint={{
|
||||||
|
'text-color': '#FFFFFF',
|
||||||
|
'text-halo-color': '#1a1a1a',
|
||||||
|
'text-halo-width': 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Source>
|
||||||
|
|
||||||
{/* 美以联军打击伊朗:路径线 */}
|
{/* 美以联军打击伊朗:路径线 */}
|
||||||
<Source id="allied-strike-lines-lincoln" type="geojson" data={lincolnLinesGeoJson}>
|
<Source id="allied-strike-lines-lincoln" type="geojson" data={lincolnLinesGeoJson}>
|
||||||
<Layer
|
<Layer
|
||||||
@@ -1864,6 +2235,85 @@ export function WarMap() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Source>
|
</Source>
|
||||||
|
{/* 场景 A:斯里兰卡海域鱼雷轨迹 — 蓝色虚线(Mk 48 ADCAP 声纳制导路径) */}
|
||||||
|
<Source id="submarine-torpedo-lines" type="geojson" data={submarineTorpedoLinesGeoJson}>
|
||||||
|
<Layer
|
||||||
|
id="submarine-torpedo-lines"
|
||||||
|
type="line"
|
||||||
|
paint={{
|
||||||
|
'line-color': 'rgba(59, 130, 246, 0.7)',
|
||||||
|
'line-width': ['interpolate', ['linear'], ['zoom'], 4, 0.8, 8, 1.5, 12, 2.5],
|
||||||
|
'line-dasharray': [2, 1.5],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Source>
|
||||||
|
{/* 场景 B:阿拉伯海战斧 → 阿巴斯港无人机航母 */}
|
||||||
|
<Source id="tomahawk-strike-lines" type="geojson" data={tomahawkStrikeLinesGeoJson}>
|
||||||
|
<Layer
|
||||||
|
id="tomahawk-strike-lines"
|
||||||
|
type="line"
|
||||||
|
paint={{
|
||||||
|
'line-color': 'rgba(168, 85, 247, 0.5)',
|
||||||
|
'line-width': ['interpolate', ['linear'], ['zoom'], 4, 0.5, 8, 1, 12, 2],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Source>
|
||||||
|
{/* 场景 B:阿巴斯港同艇潜射/鱼雷 — 直线、青虚线(与战斧同时) */}
|
||||||
|
<Source id="arabian-sub-torpedo-lines" type="geojson" data={arabianSubTorpedoLinesGeoJson}>
|
||||||
|
<Layer
|
||||||
|
id="arabian-sub-torpedo-lines"
|
||||||
|
type="line"
|
||||||
|
paint={{
|
||||||
|
'line-color': 'rgba(6, 182, 212, 0.65)',
|
||||||
|
'line-width': ['interpolate', ['linear'], ['zoom'], 4, 0.6, 8, 1.2, 12, 2],
|
||||||
|
'line-dasharray': [2, 1.5],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Source>
|
||||||
|
{/* 战术态势标注:红圈 X 驱逐舰沉没 / 紫色爆炸云 港口高价值目标摧毁 */}
|
||||||
|
<Source id="tactical-symbols" type="geojson" data={tacticalSymbolsGeoJson}>
|
||||||
|
<Layer
|
||||||
|
id="tactical-symbols-circle"
|
||||||
|
type="circle"
|
||||||
|
paint={{
|
||||||
|
'circle-radius': ['interpolate', ['linear'], ['zoom'], 4, 4, 8, 8, 12, 12],
|
||||||
|
'circle-color': ['match', ['get', 'symbol'], 'destruction', '#EF4444', 'explosion', '#A855F7', '#94A3B8'],
|
||||||
|
'circle-stroke-width': 1.5,
|
||||||
|
'circle-stroke-color': '#fff',
|
||||||
|
'circle-opacity': 0.9,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Layer
|
||||||
|
id="tactical-symbols-icon"
|
||||||
|
type="symbol"
|
||||||
|
layout={{
|
||||||
|
'text-field': ['match', ['get', 'symbol'], 'destruction', '×', 'explosion', '◆', ''],
|
||||||
|
'text-size': ['interpolate', ['linear'], ['zoom'], 4, 10, 8, 14, 12, 18],
|
||||||
|
'text-anchor': 'center',
|
||||||
|
'text-allow-overlap': true,
|
||||||
|
}}
|
||||||
|
paint={{
|
||||||
|
'text-color': '#fff',
|
||||||
|
'text-halo-color': 'rgba(0,0,0,0.6)',
|
||||||
|
'text-halo-width': 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Layer
|
||||||
|
id="tactical-symbols-label"
|
||||||
|
type="symbol"
|
||||||
|
layout={{
|
||||||
|
'text-field': ['get', 'name'],
|
||||||
|
'text-size': ['interpolate', ['linear'], ['zoom'], 4, 7, 8, 9, 10, 11],
|
||||||
|
'text-anchor': 'top',
|
||||||
|
'text-offset': [0, 0.6],
|
||||||
|
}}
|
||||||
|
paint={{
|
||||||
|
'text-color': '#E5E7EB',
|
||||||
|
'text-halo-color': '#1a1a1a',
|
||||||
|
'text-halo-width': 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Source>
|
||||||
{/* 林肯号打击光点 */}
|
{/* 林肯号打击光点 */}
|
||||||
<Source
|
<Source
|
||||||
id="allied-strike-dots-lincoln"
|
id="allied-strike-dots-lincoln"
|
||||||
@@ -1963,7 +2413,106 @@ export function WarMap() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Source>
|
</Source>
|
||||||
{/* 盟军打击目标点位 (蓝色):含林肯/福特/以色列→伊朗 + 以色列→黎巴嫩,统一名称与脉冲动效 */}
|
{/* 斯里兰卡伏击:鱼雷轨迹光点(蓝色) */}
|
||||||
|
<Source
|
||||||
|
id="allied-strike-dots-virginia"
|
||||||
|
type="geojson"
|
||||||
|
data={{
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: effectiveVirginiaSrilankaPathsDisplay.map((path) => ({
|
||||||
|
type: 'Feature' as const,
|
||||||
|
properties: {},
|
||||||
|
geometry: { type: 'Point' as const, coordinates: path[0] },
|
||||||
|
})),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layer
|
||||||
|
id="allied-strike-dots-virginia-glow"
|
||||||
|
type="circle"
|
||||||
|
paint={{
|
||||||
|
'circle-radius': ['interpolate', ['linear'], ['zoom'], 4, 3, 8, 6, 12, 10],
|
||||||
|
'circle-color': 'rgba(59, 130, 246, 0.6)',
|
||||||
|
'circle-blur': 0.3,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Layer
|
||||||
|
id="allied-strike-dots-virginia-core"
|
||||||
|
type="circle"
|
||||||
|
paint={{
|
||||||
|
'circle-radius': ['interpolate', ['linear'], ['zoom'], 4, 1, 8, 2, 12, 4],
|
||||||
|
'circle-color': '#3B82F6',
|
||||||
|
'circle-stroke-width': 0.5,
|
||||||
|
'circle-stroke-color': '#fff',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Source>
|
||||||
|
{/* 阿巴斯港突袭:战斧弹道光点(紫色) */}
|
||||||
|
<Source
|
||||||
|
id="allied-strike-dots-tomahawk"
|
||||||
|
type="geojson"
|
||||||
|
data={{
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: effectiveTomahawkArabianPathsDisplay.map((path) => ({
|
||||||
|
type: 'Feature' as const,
|
||||||
|
properties: {},
|
||||||
|
geometry: { type: 'Point' as const, coordinates: path[0] },
|
||||||
|
})),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layer
|
||||||
|
id="allied-strike-dots-tomahawk-glow"
|
||||||
|
type="circle"
|
||||||
|
paint={{
|
||||||
|
'circle-radius': ['interpolate', ['linear'], ['zoom'], 4, 3, 8, 6, 12, 10],
|
||||||
|
'circle-color': 'rgba(168, 85, 247, 0.6)',
|
||||||
|
'circle-blur': 0.3,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Layer
|
||||||
|
id="allied-strike-dots-tomahawk-core"
|
||||||
|
type="circle"
|
||||||
|
paint={{
|
||||||
|
'circle-radius': ['interpolate', ['linear'], ['zoom'], 4, 1, 8, 2, 12, 4],
|
||||||
|
'circle-color': '#A855F7',
|
||||||
|
'circle-stroke-width': 0.5,
|
||||||
|
'circle-stroke-color': '#fff',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Source>
|
||||||
|
{/* 阿巴斯港同艇潜射/鱼雷光点(青色,与战斧同时) */}
|
||||||
|
<Source
|
||||||
|
id="allied-strike-dots-arabian-sub"
|
||||||
|
type="geojson"
|
||||||
|
data={{
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: effectiveArabianSubTorpedoPathsDisplay.map((path) => ({
|
||||||
|
type: 'Feature' as const,
|
||||||
|
properties: {},
|
||||||
|
geometry: { type: 'Point' as const, coordinates: path[0] },
|
||||||
|
})),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layer
|
||||||
|
id="allied-strike-dots-arabian-sub-glow"
|
||||||
|
type="circle"
|
||||||
|
paint={{
|
||||||
|
'circle-radius': ['interpolate', ['linear'], ['zoom'], 4, 3, 8, 6, 12, 10],
|
||||||
|
'circle-color': 'rgba(6, 182, 212, 0.6)',
|
||||||
|
'circle-blur': 0.3,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Layer
|
||||||
|
id="allied-strike-dots-arabian-sub-core"
|
||||||
|
type="circle"
|
||||||
|
paint={{
|
||||||
|
'circle-radius': ['interpolate', ['linear'], ['zoom'], 4, 1, 8, 2, 12, 4],
|
||||||
|
'circle-color': '#06B6D4',
|
||||||
|
'circle-stroke-width': 0.5,
|
||||||
|
'circle-stroke-color': '#fff',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Source>
|
||||||
|
{/* 盟军打击目标点位 (蓝色):含林肯/福特/以色列→伊朗 + 以色列→黎巴嫩 + 斯里兰卡/阿巴斯港,统一名称与脉冲动效 */}
|
||||||
<Source
|
<Source
|
||||||
id="allied-strike-targets"
|
id="allied-strike-targets"
|
||||||
type="geojson"
|
type="geojson"
|
||||||
|
|||||||
Reference in New Issue
Block a user