You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SVG绘制机场空域时Y轴坐标转换异常问题求助

SVG绘制机场空域的Y轴坐标转换问题解决方法

核心问题原因

SVG的坐标系以左上角为原点,Y轴向下递增;而地理坐标系中,纬度值向北(地图上方)递增。你之前仅反转了screenY的计算,但未同步调整基准点(机场坐标)的偏移量,导致整体坐标映射逻辑错误,进而出现跑道和航路点位置颠倒的问题。

修正后的坐标转换逻辑

需要同时调整offsetY和screenY的计算,确保地理纬度的递增方向对应SVG的向上方向:

function convertCoordinates(latitude, longitude) {
    const airportLatitude = 51.4775;
    const airportLongitude = -0.46139;

    const centerX = window.innerWidth / 2;
    const centerY = window.innerHeight / 2;

    const scaleFactor = 2000;
    // 保持X轴逻辑不变
    const offsetX = centerX - (airportLongitude * scaleFactor);
    // 调整Y轴基准偏移:用+号适配反转后的纬度计算
    const offsetY = centerY + (airportLatitude * scaleFactor);
    // 反转纬度对Y轴的影响,使纬度越大(越靠北),SVG的Y值越小(越靠上)
    const screenX = (longitude * scaleFactor) + offsetX;
    const screenY = -(latitude * scaleFactor) + offsetY;
    return { x: screenX, y: screenY };
}

逻辑验证

  • 航路点LON的纬度51.4861大于机场纬度51.4775,计算后screenY会比机场中心的Y值小,对应SVG上方位置,符合预期。
  • 跑道09L/27R的纬度51.4776大于09R/27L的51.4647,screenY更小,会显示在上方,与实际机场跑道布局一致。

修正后的完整代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Radar Screen</title>
  <style>
    body {
      margin: 0;
      padding: 0;
      overflow: hidden; /* Prevent scrolling */
    }

    #radar-container {
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
    }

    svg {
      width: 100%; /* Take full width of container */
      height: 100%; /* Take full height of container */
      border: 1px solid black;
    }
  </style>
</head>
<body>
  <div id="radar-container">
    <svg id="radar-svg">
      <!-- Runways, waypoints, and airport will be dynamically drawn here -->
    </svg>
  </div>
  <script>
    // Function to convert latitude and longitude to screen coordinates
    function convertCoordinates(latitude, longitude) {
        // Airport coordinates
        const airportLatitude = 51.4775; // Decimal degrees
        const airportLongitude = -0.46139; // Decimal degrees

        // Calculate center of SVG based on airport coordinates
        const centerX = window.innerWidth / 2;
        const centerY = window.innerHeight / 2;

        // Assume some mapping between latitude and y-axis, and longitude and x-axis
        // You may need to adjust these calculations based on your specific mapping
        const scaleFactor = 2000; // Adjust based on the scale of your radar screen
        const offsetX = centerX - (airportLongitude * scaleFactor);
        // 修正:调整offsetY计算方式
        const offsetY = centerY + (airportLatitude * scaleFactor);
        const screenX = (longitude * scaleFactor) + offsetX;
        // 修正:反转latitude对screenY的影响
        const screenY = -(latitude * scaleFactor) + offsetY;
        return { x: screenX, y: screenY };
    }

    // Waypoint coordinates
    const waypointCoordinates = [
      { name: "LON", latitude: 51.4861, longitude: -0.4666 },
    ];

    // Runway coordinates
    const runwayCoordinates = [
      { id: "27R", start: { latitude: 51.4776, longitude: -0.4332 }, end: { latitude: 51.4775, longitude: -0.4849 }},
      { id: "09L", start: { latitude: 51.4775, longitude: -0.4849 }, end: { latitude: 51.4776, longitude: -0.4332 }},
      { id: "09R", start: { latitude: 51.4647, longitude: -0.4823 }, end: { latitude: 51.4655, longitude: -0.4341 }},
      { id: "27L", start: { latitude: 51.4655, longitude: -0.4341 }, end: { latitude: 51.4647, longitude: -0.4823 }}
    ];

    // Get the SVG container
    const svgContainer = document.getElementById('radar-svg');

    // Function to draw waypoints
    function drawWaypoints() {
      // Draw new waypoints
      waypointCoordinates.forEach(waypoint => {
        const position = convertCoordinates(waypoint.latitude, waypoint.longitude);

        console.log(`Waypoint ${waypoint.name} position: (${position.x}, ${position.y})`);

        // Draw square representing waypoint
        const square = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
        square.setAttribute('x', position.x - 5); // Adjust position to center square
        square.setAttribute('y', position.y - 5); // Adjust position to center square
        square.setAttribute('width', 10);
        square.setAttribute('height', 10);
        square.setAttribute('fill', 'blue');
        svgContainer.appendChild(square);

        // Add label for the waypoint
        const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
        label.setAttribute('x', position.x + 10); // Offset label position
        label.setAttribute('y', position.y - 10); // Offset label position
        label.setAttribute('text-anchor', 'start'); // Align text to start of label
        label.setAttribute('dominant-baseline', 'middle'); // Center the text vertically
        label.setAttribute('font-family', 'Arial');
        label.setAttribute('font-size', '12');
        label.setAttribute('fill', 'black');
        label.textContent = waypoint.name;
        svgContainer.appendChild(label);
      });
    }

    // Function to draw runways
    function drawRunways() {
      // Draw new runways
      runwayCoordinates.forEach(runway => {
        const start = convertCoordinates(runway.start.latitude, runway.start.longitude);
        const end = convertCoordinates(runway.end.latitude, runway.end.longitude);

        console.log(`Runway ${runway.id} start: (${start.x}, ${start.y})`);
        console.log(`Runway ${runway.id} end: (${end.x}, ${end.y})`);

        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        line.setAttribute('x1', start.x);
        line.setAttribute('y1', start.y);
        line.setAttribute('x2', end.x);
        line.setAttribute('y2', end.y);
        line.setAttribute('stroke', 'black');
        line.setAttribute('stroke-width', '2');
        svgContainer.appendChild(line);
        // Add label
        const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
        label.setAttribute('x', start.x + 10); // Offset label position
        label.setAttribute('y', start.y - 10); // Offset label position
        label.setAttribute('text-anchor', 'start'); // Align text to start of label
        label.setAttribute('dominant-baseline', 'middle'); // Center the text vertically
        label.setAttribute('font-family', 'Arial');
        label.setAttribute('font-size', '12');
        label.setAttribute('fill', 'black');
        label.textContent = runway.id;
        svgContainer.appendChild(label);
      });
    }

    // Draw waypoints and runways initially
    drawWaypoints();
    drawRunways();
  </script>
</body>
</html>

额外调试技巧

  • 可以在控制台打印转换前后的坐标值,对比地理纬度与SVG Y值的对应关系,确认映射逻辑是否正确。
  • 若后续需要更复杂的地图投影,可考虑使用SVG的viewBox属性定义自定义坐标系,直接将地理坐标映射到viewBox范围内,避免手动计算偏移。

内容的提问来源于stack exchange,提问作者Maciej Olborski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 20:57:01