Exploring Latitude, Longitude, and SVGs

In my attempt to visually represent an airport and its airspace using an SVG, I have encountered a persistent issue with the y-axis and its conversion. Despite verifying the coordinates multiple times, the positioning of the lines representing the runways (09L/27R above 09R/27L) and the placement of LON above both lines seem to be incorrect. Is there a specific technique or logic that can be applied in SVG to address this discrepancy?

<!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);
        const offsetY = centerY - (airportLatitude * scaleFactor);
        const screenX = (longitude * scaleFactor) + offsetX;
        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>

In my attempt to address the inverted Y-axis issue, I experimented with:

const screenY = -(latitude * scaleFactor) + offsetY;

Despite thorough verification of the coordinates, the problem persists.

Answer №1

I appended four new coordinates to your waypointCoordinates object, which are: NORTH, EAST, SOUTH, and WEST.

By making this adjustment, you can easily visualize that the NORTH point is located below LON, while SOUTH is positioned above LON.

One viable solution involves modifying the object returned from convertCoordinates() to

return { x: screenX, y: window.innerHeight - screenY };
as suggested by Jaromanda in the comments on your question.

The challenge here arises from the difference in reference points between longitude and latitude, where latitude starts at the equator (i.e. 0) and increases northward. Conversely, in SVG, the origin is the top left corner and increases as we move downward. Therefore, we need to introduce a negative value to screenY in the returned object above and adjust the vertical screen position by adding window.innerHeight.

To enhance visibility, I included a flip checkbox in the top left corner placed above the container by setting z-index: -1 for the radar-container div.

const flipCheckbox = document.getElementById("flipCheckbox");

      flipCheckbox.addEventListener("click", () => {
        while (svgContainer.lastChild) {
          svgContainer.removeChild(svgContainer.lastChild);
        }
        drawWaypoints();
        drawRunways();
      });

      // 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);
        const offsetY = centerY - (airportLatitude * scaleFactor);
        const screenX = (longitude * scaleFactor) + offsetX;
        const screenY = (latitude * scaleFactor) + offsetY;
        return flipCheckbox.checked ? {
          x: screenX,
          y: window.innerHeight - screenY
        } : {
          x: screenX,
          y: screenY
        };
      }

      // Waypoint coordinates
      const waypointCoordinates = [{
          name: "LON",
          latitude: 51.4861,
          longitude: -0.4666
        },
        {
          name: "EAST",
          latitude: 51.4861,
          longitude: -0.3375
        },
        {
          name: "NORTH",
          latitude: 51.5133,
          longitude: -0.4666
        },
        {
          name: "SOUTH",
          latitude: 51.4123,
          longitude: -0.4666
        },
        {
          name: "WEST",
          latitude: 51.4861,
          longitude: -0.5479
        }
      ];

      // 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('wid...

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

In the `componentDidUpdate()` method, the function `this.props` is not

I've encountered a peculiar issue that I just can't seem to figure out. It's possible that I missed something simple, but I'm stuck trying to solve this bug. Here's what's happening: In the child component, a counter is being ...

What is the best way to split a Bootstrap row into five equal-sized columns?

Trying to divide a Bootstrap row into five columns can be tricky when you only have 12 units available on the device. How can this division be achieved successfully? ...

Is there a way to extract all the data values starting with "data-" from the selected input fields in HTML5 and compile them into an object?

Looking for a way to automate input values: <input class="form" type="checkbox" name="item1" data-value="{ 'item1':'selected', 'price': 100 }" checked="checked"> <input class="form" type="checkbox" name="item2" data- ...

HTML and CSS: Focus on border for <td> cells only

I'm looking to apply a border to only one cell (B2) without nesting tables. Here's the code: <table border="0"> <tr> <td>A1</td> <td>B1</td> <td>C1</td> </tr> ...

What could be the reason for the empty array returned by the combinationSum function in Javascript?

The combinationSum function is returning an empty resultArr. When checking the ds array with console.log, it shows the correct answer, but for some reason, the final output array ends up being [[],[]]. var combinationSum = function(candidates, target) { ...

Is it deemed as an anti-pattern to establish directives for styling?

Currently, I am in the midst of developing a specialized component UI library for a specific project I'm involved in. This library will consist of unique stylized components with elements that are reused throughout. As I work on this project, I find ...

Locate and dismiss a toast message (Toastr)

I have a webpage where I can add multiple popup notifications called toasts dynamically using the toastr plugin found at https://github.com/CodeSeven/toastr. Each toast has an Ok link that, when clicked, should only close that specific toast and not all v ...

Loading items into multiple containers using PHP AJAX pagination

I'm looking to implement a "load more" button on my website, but I'm facing a challenge due to my three-column layout. I want to load more items into their respective parent elements without disrupting the design. While I've used infinite aj ...

How can you determine the index of a table column in JavaScript when you only know its class name?

I am looking for a way to dynamically hide/show table columns based on their classes, without having to add classes to each individual <td> element. This should be accomplished using classes on the columns themselves. Here is a sample of the table ...

NgOnChanges replaces form control value when user inputs text

In my autocomplete form control component: @Component({ selector: 'app-autocomplete', templateUrl: './app-autocomplete.view.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AutoCompleteFilterComponent ...

"Unlock the power of Meteor: dynamically serve up the specific range of items

I am facing a challenge with my vast collection of over 5000+ records. I aim to display the records in groups of 10 for easier viewing. How can I update the data dynamically to achieve this? Here is what I have attempted so far: In my server.js file : M ...

Expanding the functionality of Jquery with extend or bind techniques

Can someone clarify whether I should be using .bind() or .extend() and provide guidance on how to implement it? I am working with two checkboxes where a process is triggered once they are checked. In addition, there is a button that, when clicked, will ch ...

transmit a binary image to a modal using Angular

I am facing an issue with my code where I am unable to pass a binary image to a modal. The image displays correctly as a jpeg in the view, and I need it to display the same way in the modal as well. View <h4 style="color: #3953a5; font-size:22px;"&g ...

Issue with the pluses and minuses in the Bootstrap accordion

Can someone explain why the panel-header displays all "-" instead of "+"? When I click on the panel, it changes all "-" to "+". This happens consistently, not just the first time the page loads. My CSS code: .panel-heading a:after { font-family: &ap ...

Laravel 4 Data Cleaning for Improved Security

I am currently utilizing Laravel 4 to establish a RESTful interface for my AngularJS application. Right now, I am looking to update an object within my model named Discount Link. The method I am using involves: $data = Input::all(); $affectedRows ...

I'm struggling with developing react applications due to problems with canvas elements

I am currently using an M1 MacBook Pro, with Node version 15.4.1 and npm version 7.0.15. When I ran the command npx create-react-app my-app, it gave me this output: https://i.sstatic.net/OKKnA.jpg I have attempted various solutions but keep encountering ...

Tips for converting plain text output into HTML tags

I recently set up a contact form 7 on my website, and I'm trying to customize the message that appears after submission. However, I'm running into an issue when attempting to split the message into two different colors. I created a span class to ...

Preventing multiple event handlers from firing on an HTML element after a server response

I currently have a div block on my page: <div class='btn'>click here</div> <div class='dialogWindow'></div> along with some JavaScript containing a click handler: $('.btn').live('click', fu ...

Leveraging Vue.js to showcase API information through attribute binding

My application is designed to allow a user to select a Person, and then Vue makes an API call for that user's posts. Each post has its own set of comments sourced from here. You can view the codepen here Here is my HTML structure: <script src="h ...

Despite utilizing the .img-fluid class, the image remains incompatible with the parent div and does not fit properly

So, I'm currently working on a test code where my aim is to fit an image into the parent div using Bootstrap 5. However, I'm facing a challenge where the image leaves a noticeable gap width-wise, despite using the .img-fluid class. You can see th ...