Search code examples
javascriptmathsvgcoordinatesgeo

Geo Coordinates offset - airport runways diagram


I'm trying to generate an airport runways digram, something like this:

airport runways

Each airport has a list of runways represented as follows:

const runways = [
    {
      identifier1: "110L",
      identifier2: "28R",
      length: 3107,
      width: 75,

      latitude1: 37.66247544,
      longitude1: -122.12726156,

      latitude2: 37.65822686,
      longitude2: -122.11795339,
    },
    {
      identifier1: "10R",
      identifier2: "28L",
      length: 5694,
      width: 150,
      
      latitude1: 37.66204453,
      longitude1: -122.12979078,

      latitude2: 37.65426,
      longitude2: -122.11273375,
    },
  ];

As you can see each runway has starting and ending geo points. For visuals I'm using svg-airports, the above example of the runways is represented by this:

<airport-diagram width="200" height="200">
  <airport-runway
    length-ft="3107"
    true-heading="119"
    offset-angle="29"
    offset-x="300"
    offset-y="1000"
  >
    <runway-id name="10L" pattern="left"></runway-id>
    <runway-id name="28R" pattern="right"></runway-id>
  </airport-runway>
  <airport-runway
    length-ft="5694"
    true-heading="119"
    offset-angle="29"
    offset-x="300"
    offset-y="-1000"
  >
    <runway-id name="10R" pattern="right"></runway-id>
    <runway-id name="28L" pattern="left"></runway-id>
  </airport-runway>
</airport-diagram>

As you can see that each runway has the following values

  • length
  • true-heading
  • offset-angle
  • offset-x
  • offset-y

I'm able to provide the length as I already have that info from my airport and I'm calculating the true-heading (bearing) as follows:

function radians(n) {
  return n * (Math.PI / 180);
}
function degrees(n) {
  return n * (180 / Math.PI);
}

function getBearing(startLat, startLong, endLat, endLong) {
  startLat = radians(startLat);
  startLong = radians(startLong);
  endLat = radians(endLat);
  endLong = radians(endLong);

  var dLong = endLong - startLong;

  var dPhi = Math.log(
    Math.tan(endLat / 2.0 + Math.PI / 4.0) /
      Math.tan(startLat / 2.0 + Math.PI / 4.0)
  );
  if (Math.abs(dLong) > Math.PI) {
    if (dLong > 0.0) dLong = -(2.0 * Math.PI - dLong);
    else dLong = 2.0 * Math.PI + dLong;
  }

  return (degrees(Math.atan2(dLong, dPhi)) + 180.0) % 180.0;
}

The question is, can I calculate the offset-angle, offset-x, and offset-y based on the geo coordinates between the runways ?

P.S: the offset values are in feet.


Solution

  • Convert longitude/latitude to Cartesian coordinates. Then it should be easy. I assume the conversion is the problem.

    For an airport (relatively small), a simple equirectangular projection can be used.

    <!doctype html>
    <html>
    <body>
       <script src="t.js"></script>
       <script src="svg-airports-1.0.js" async></script>
    </body>
    </html>
    
    // t.js
    // important parts within ////// commments
    const runways = [
       {
          identifier1: "110L",
          identifier2: "28R",
          length: 3107,
          width: 75,
          latitude1: 37.66247544,
          longitude1: -122.12726156,
          latitude2: 37.65822686,
          longitude2: -122.11795339,
       }, {
          identifier1: "10R",
          identifier2: "28L",
          length: 5694,
          width: 150,
          latitude1: 37.66204453,
          longitude1: -122.12979078,
          latitude2: 37.65426,
          longitude2: -122.11273375,
       },
    ];
    
    function runways2html(runways,scale=1.0) {
       //
       airport = document.createElement("airport-diagram")
       document.body.appendChild(airport)
       //////////////////////////////////////////////////////////////////////
       const radians = n => n * (Math.PI / 180)
       const degrees = n => n * (180 / Math.PI)
       const avg = (v1,v2) => .5*(v1+v2)
       const lon2x = lon => R*(lon-lon0)*cosLat0
       const lat2y = lat => R*(lat-lat0)
       //
       var lons = runways.map(({longitude1,longitude2}) => [longitude1,longitude2]).flat().map(v => radians(v))
       var lats = runways.map(({latitude1,latitude2}) => [latitude1,latitude2]).flat().map(v => radians(v))
       var lonMax = Math.max(...lons)
       var lonMin = Math.min(...lons)
       var latMax = Math.max(...lats)
       var latMin = Math.min(...lats)
       var lon0 = avg(lonMin,lonMax)
       var lat0 = avg(latMin,latMax)
       var cosLat0 = Math.cos(lat0)
       var R = 20902000 // Earth radius in feet (??)
       //////////////////////////////////////////////////////////////////////
       var [xMin,xMax] = [lonMin,lonMax].map(lon2x)
       var [yMin,yMax] = [latMin,latMax].map(lat2y)
       var width = xMax-xMin
       var height = yMax-yMin
       var [width,height] = [width,height].map(v => v*scale)
       airport.setAttribute("width",width)
       airport.setAttribute("height",height)
       //
       runways.forEach(runway => {
          var {
             identifier1,
             identifier2,
             length,
             width,
             latitude1,
             longitude1,
             latitude2,
             longitude2,
          } = runway
          //////////////////////////////////////////////////////////////////////
          var [longitude1,longitude2,latitude1,latitude2] = [longitude1,longitude2,latitude1,latitude2].map(v => radians(v))
          var [x1,x2] = [longitude1,longitude2].map(lon2x)
          var [y1,y2] = [latitude1,latitude2].map(lat2y)
          var heading = Math.atan2(x2-x1,y2-y1)
          heading = degrees(heading)
          var x = avg(x1,x2)
          var y = avg(y1,y2)
          //////////////////////////////////////////////////////////////////////
          var runway = document.createElement("airport-runway")
          runway.setAttribute("length-ft",length)
          runway.setAttribute("true-heading",heading)
          runway.setAttribute("offset-angle",0)
          runway.setAttribute("offset-x",x)
          runway.setAttribute("offset-y",y)
          var [id1,id2] = [0,1].map(i => document.createElement("runway-id"))
          id1.setAttribute("name",identifier1)
          id1.setAttribute("pattern",identifier1.endsWith("L")?"left":"right") // ??
          id2.setAttribute("name",identifier2)
          id2.setAttribute("pattern",identifier1.endsWith("L")?"right":"left") // ??
          runway.appendChild(id1)
          runway.appendChild(id2)
          airport.appendChild(runway)
       })
    }
    
    runways2html(runways,0.2)