返回列表
数学

生态景观造路

上传时间:2026-04-09 10:20 · 作者:ytry1983

解题过程与思路预览 请仅渲染可信 HTML
最短距离:多廊道平移问题演示

最短距离:生态景观带路径 (第6题)

利用方向平移处理不同角度的固定路径段

湖岸 l1 湖岸 l2 酒店 R 酒店 S

📐 几何逻辑:分向平移

由于景观带 $l_1$ 和 $l_2$ 方向不同,我们需要进行分步平移:

  • 将 $R$ 沿垂直于 $l_1$ 的方向平移,距离等于该处景观带宽度。
  • 将 $S$ 沿垂直于 $l_2$ 的方向平移,距离等于该处景观带宽度。

连接两个平移点得到的线段 $R'S'$,其与两条内侧边界的交点就是廊道的最佳位置点。

💡 优化提示

如果画面仍有部分遮挡,您可以尝试上下滑动页面。当前的布局已将“酒店 S”及其作图痕迹提升到了安全高度(约 $y=410$),在大多数屏幕上均可完整显示。

查看解析页源码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>最短距离:多廊道平移问题演示</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        .step-transition {
            transition: all 0.5s ease-in-out;
        }
        @keyframes pulse-dot {
            0%, 100% { transform: scale(1); opacity: 1; }
            50% { transform: scale(1.3); opacity: 0.7; }
        }
        .animate-pulse-custom {
            animation: pulse-dot 2s infinite;
        }
        .math-font {
            font-family: 'Times New Roman', serif;
            font-style: italic;
        }
    </style>
</head>
<body class="bg-slate-50 min-h-screen font-sans p-4 md:p-8 flex flex-col items-center">

    <div class="max-w-4xl w-full bg-white rounded-2xl shadow-xl overflow-hidden border border-slate-200">
        <!-- Header -->
        <div class="bg-teal-600 p-6 text-white text-center">
            <h1 class="text-2xl font-bold text-white">最短距离:生态景观带路径 (第6题)</h1>
            <p class="mt-2 opacity-90 text-white">利用方向平移处理不同角度的固定路径段</p>
        </div>

        <!-- Animation Area -->
        <div class="relative h-[600px] w-full bg-slate-50 border-y border-slate-200 overflow-hidden">
            <!-- 增加 viewBox 高度到 650,并调整几何图形位置,避开底部的说明框 -->
            <svg id="animation-svg" class="w-full h-full" viewBox="0 0 600 650" preserveAspectRatio="xMidYMid meet">
                <!-- 景观带背景 - V型 (整体上移约 50px) -->
                <!-- 景观带1 (斜向) -->
                <path d="M 50 350 L 250 20 L 310 50 L 110 380 Z" fill="#ecfdf5" stroke="#10b981" stroke-width="1" />
                <line x1="250" y1="20" x2="50" y2="350" stroke="#059669" stroke-width="2" stroke-dasharray="4" />
                <line x1="310" y1="50" x2="110" y2="380" stroke="#059669" stroke-width="2" stroke-dasharray="4" />
                
                <!-- 景观带2 (水平) -->
                <path d="M 110 380 L 550 380 L 550 320 L 310 320 Z" fill="#ecfdf5" stroke="#10b981" stroke-width="1" />
                <line x1="110" y1="380" x2="550" y2="380" stroke="#059669" stroke-width="2" stroke-dasharray="4" />
                <line x1="310" y1="320" x2="550" y2="320" stroke="#059669" stroke-width="2" stroke-dasharray="4" />

                <!-- 文字标注 -->
                <text x="140" y="100" transform="rotate(-60, 140, 100)" class="text-[10px] fill-teal-700 font-bold italic">湖岸 l1</text>
                <text x="450" y="395" class="text-[10px] fill-teal-700 font-bold italic">湖岸 l2</text>

                <!-- 酒店 R 和 S -->
                <g id="points-static">
                    <!-- 酒店 R 位置上移 -->
                    <rect x="200" y="60" width="12" height="12" fill="#ef4444" rx="2" />
                    <text x="180" y="70" class="font-bold fill-slate-800">酒店 R</text>
                    
                    <!-- 酒店 S 位置上移至 410,确保在说明框上方 -->
                    <rect x="420" y="410" width="12" height="12" fill="#ef4444" rx="2" />
                    <text x="440" y="420" class="font-bold fill-slate-800">酒店 S</text>
                </g>

                <!-- 动态图层 -->
                <g id="dynamic-layers"></g>
            </svg>

            <!-- Overlay Info - 增加透明度和微调位置 -->
            <div id="info-box" class="absolute bottom-4 left-6 right-6 bg-white/80 backdrop-blur-sm p-4 rounded-xl shadow-lg border border-teal-100 transition-opacity duration-300 pointer-events-none">
                <h3 id="step-title" class="text-lg font-bold text-teal-900"></h3>
                <p id="step-desc" class="text-slate-700 text-sm mt-1"></p>
            </div>
        </div>

        <!-- Controls -->
        <div class="p-6 flex justify-between items-center bg-slate-50">
            <button id="prev-btn" class="px-6 py-2 rounded-full border border-slate-300 text-slate-600 hover:bg-white transition-all active:scale-95 disabled:opacity-50">
                上一步
            </button>
            
            <div id="dot-indicators" class="flex gap-2"></div>

            <button id="next-btn" class="px-6 py-2 rounded-full bg-teal-600 text-white hover:bg-teal-700 transition-all shadow-lg shadow-teal-200 active:scale-95">
                下一步
            </button>
        </div>
    </div>

    <!-- Explanation -->
    <div class="mt-8 grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl w-full pb-12">
        <div class="bg-white p-6 rounded-xl shadow-sm border border-slate-200">
            <h4 class="font-bold text-teal-800 flex items-center gap-2 text-teal-800">
                <span>📐</span> 几何逻辑:分向平移
            </h4>
            <div class="text-sm text-slate-600 mt-2 space-y-2 leading-relaxed">
                <p>由于景观带 $l_1$ 和 $l_2$ 方向不同,我们需要进行分步平移:</p>
                <ul class="list-disc list-inside bg-teal-50 p-2 rounded text-teal-900">
                    <li>将 $R$ 沿垂直于 $l_1$ 的方向平移,距离等于该处景观带宽度。</li>
                    <li>将 $S$ 沿垂直于 $l_2$ 的方向平移,距离等于该处景观带宽度。</li>
                </ul>
                <p>连接两个平移点得到的线段 $R'S'$,其与两条内侧边界的交点就是廊道的最佳位置点。</p>
            </div>
        </div>
        <div class="bg-white p-6 rounded-xl shadow-sm border border-slate-200">
            <h4 class="font-bold text-teal-800 flex items-center gap-2">
                <span>💡</span> 优化提示
            </h4>
            <p class="text-sm text-slate-600 mt-2">
                如果画面仍有部分遮挡,您可以尝试上下滑动页面。当前的布局已将“酒店 S”及其作图痕迹提升到了安全高度(约 $y=410$),在大多数屏幕上均可完整显示。
            </p>
        </div>
    </div>

    <script>
        const steps = [
            { title: "1. 场景分析", desc: "路径 R→C→D→P→Q→S 中,CD 和 PQ 是长度固定的廊道(宽度)。我们需要找到最优的 D 和 P 点。" },
            { title: "2. 垂直平移 R", desc: "将点 R 沿垂直于斜岸 l1 的方向向内平移,得到点 R'。这抵消了廊道 CD 对路径方向的影响。" },
            { title: "3. 垂直平移 S", desc: "将点 S 沿垂直于水平岸 l2 的方向向上平移,得到点 S'。这抵消了廊道 PQ 对路径方向的影响。" },
            { title: "4. 连接 R'S' 确定交点", desc: "连接 R'S',其与 l1 内侧交点为 D,与 l2 内侧交点为 P。直线 A-B 最短原理在此生效。" },
            { title: "5. 还原路径与证明", desc: "完成作图。橙色平行四边形展示了 AM=A'N 的等价性,红色折线即为所求的最短观光路径。" }
        ];

        let currentStep = 0;
        const dynamicLayer = document.getElementById('dynamic-layers');
        const stepTitle = document.getElementById('step-title');
        const stepDesc = document.getElementById('step-desc');
        const prevBtn = document.getElementById('prev-btn');
        const nextBtn = document.getElementById('next-btn');
        const dotIndicators = document.getElementById('dot-indicators');

        steps.forEach((_, i) => {
            const dot = document.createElement('div');
            dot.className = `h-2 w-8 rounded-full transition-all ${i === 0 ? 'bg-teal-600 w-12' : 'bg-slate-300'}`;
            dotIndicators.appendChild(dot);
        });

        function updateUI() {
            stepTitle.textContent = steps[currentStep].title;
            stepDesc.textContent = steps[currentStep].desc;
            prevBtn.disabled = currentStep === 0;
            nextBtn.textContent = currentStep === steps.length - 1 ? "重新开始" : "下一步";

            Array.from(dotIndicators.children).forEach((dot, i) => {
                dot.className = `h-2 transition-all rounded-full ${i === currentStep ? 'bg-teal-600 w-12' : 'bg-slate-300 w-8'}`;
            });

            dynamicLayer.innerHTML = '';

            // 几何定义 (整体 y 上移 50)
            const pR = { x: 206, y: 66 }; 
            const pS = { x: 426, y: 416 }; 
            
            const v1 = { dx: 52, dy: 35 }; 
            const pRPrime = { x: pR.x + v1.dx, y: pR.y + v1.dy };
            
            const v2 = { dx: 0, dy: -60 }; 
            const pSPrime = { x: pS.x + v2.dx, y: pS.y + v2.dy };

            const pD = { x: 285, y: 135 }; 
            const pP = { x: 395, y: 320 };
            
            const pC = { x: pD.x - v1.dx, y: pD.y - v1.dy };
            const pQ = { x: pP.x - v2.dx, y: pP.y - v2.dy };

            if (currentStep >= 1) {
                createSVGElement('line', { x1: pR.x, y1: pR.y, x2: pRPrime.x, y2: pRPrime.y, stroke: '#f59e0b', 'stroke-width': 1.5, 'stroke-dasharray': '3' });
                createSVGElement('circle', { cx: pRPrime.x, cy: pRPrime.y, r: 4, fill: '#f59e0b' });
                createSVGElement('text', { x: pRPrime.x + 10, y: pRPrime.y, class: 'fill-amber-600 text-[10px] font-bold', textContent: "R'" });
            }

            if (currentStep >= 2) {
                createSVGElement('line', { x1: pS.x, y1: pS.y, x2: pSPrime.x, y2: pSPrime.y, stroke: '#f59e0b', 'stroke-width': 1.5, 'stroke-dasharray': '3' });
                createSVGElement('circle', { cx: pSPrime.x, cy: pSPrime.y, r: 4, fill: '#f59e0b' });
                createSVGElement('text', { x: pSPrime.x - 25, y: pSPrime.y - 10, class: 'fill-amber-600 text-[10px] font-bold', textContent: "S'" });
            }

            if (currentStep >= 3) {
                createSVGElement('line', { x1: pRPrime.x, y1: pRPrime.y, x2: pSPrime.x, y2: pSPrime.y, stroke: '#94a3b8', 'stroke-width': 1.5, 'stroke-dasharray': '6' });
                createSVGElement('circle', { cx: pD.x, cy: pD.y, r: 4, fill: '#10b981' });
                createSVGElement('circle', { cx: pP.x, cy: pP.y, r: 4, fill: '#10b981' });
                createSVGElement('text', { x: pD.x + 10, y: pD.y, class: 'fill-emerald-700 text-[10px] font-bold', textContent: 'D' });
                createSVGElement('text', { x: pP.x - 5, y: pP.y - 10, class: 'fill-emerald-700 text-[10px] font-bold', textContent: 'P' });
            }

            if (currentStep >= 4) {
                const poly1 = `${pR.x},${pR.y} ${pRPrime.x},${pRPrime.y} ${pD.x},${pD.y} ${pC.x},${pC.y}`;
                const poly2 = `${pS.x},${pS.y} ${pSPrime.x},${pSPrime.y} ${pP.x},${pP.y} ${pQ.x},${pQ.y}`;
                createSVGElement('polygon', { points: poly1, fill: '#fbbf24', opacity: 0.15, stroke: '#d97706', 'stroke-width': 1, 'stroke-dasharray': '2' });
                createSVGElement('polygon', { points: poly2, fill: '#fbbf24', opacity: 0.15, stroke: '#d97706', 'stroke-width': 1, 'stroke-dasharray': '2' });

                createSVGElement('line', { x1: pC.x, y1: pC.y, x2: pD.x, y2: pD.y, stroke: '#10b981', 'stroke-width': 4 });
                createSVGElement('line', { x1: pP.x, y1: pP.y, x2: pQ.x, y2: pQ.y, stroke: '#10b981', 'stroke-width': 4 });
                createSVGElement('text', { x: pC.x - 10, y: pC.y, class: 'fill-emerald-700 text-[10px] font-bold', textContent: 'C' });
                createSVGElement('text', { x: pQ.x + 10, y: pQ.y + 15, class: 'fill-emerald-700 text-[10px] font-bold', textContent: 'Q' });

                const pathData = `M ${pR.x} ${pR.y} L ${pC.x} ${pC.y} M ${pD.x} ${pD.y} L ${pP.x} ${pP.y} M ${pQ.x} ${pQ.y} L ${pS.x} ${pS.y}`;
                createSVGElement('path', { d: pathData, stroke: '#ef4444', 'stroke-width': 3, fill: 'none' });
            }
        }

        function createSVGElement(type, props) {
            const el = document.createElementNS('http://www.w3.org/2000/svg', type);
            for (let key in props) {
                if (key === 'textContent') {
                    el.textContent = props[key];
                } else {
                    el.setAttribute(key, props[key]);
                }
            }
            dynamicLayer.appendChild(el);
            return el;
        }

        nextBtn.addEventListener('click', () => {
            currentStep = (currentStep + 1) % steps.length;
            updateUI();
        });

        prevBtn.addEventListener('click', () => {
            if (currentStep > 0) {
                currentStep--;
                updateUI();
            }
        });

        updateUI();
    </script>
</body>
</html>