Search code examples
openlayersopenlayers-7

How can I have OpenLayers draw the shortest line between two coordinates


I am specifying the coordinates of

[-122, 47],   (in USA)
[147, -32]  (in Australia)

The image it produces is:

result image

I was under the false impression that OpenLayers would draw the shortest line between two coordinates.

What would I need to change so the line drawn is the shortest?

(In this case, the line would cross over the Pacific instead of Atlantic Ocean)

let coordinates = [
  [-122, 47],
  [147, -32]
];

const myFeature = new ol.Feature({
  geometry: new ol.geom.LineString(coordinates),
  name: "Line"
});

myFeature.setStyle(
  new ol.style.Style({
    stroke: new ol.style.Stroke({ color: "green", width: 8 })
  })
);

const vectorSource = new ol.source.Vector({
  features: [myFeature]
});

const vectorLayer = new ol.layer.Vector({
  source: vectorSource
});

const map = new ol.Map({
  target: "map",
  layers: [
    new ol.layer.Tile({
      source: new ol.source.OSM()
    }),
    vectorLayer
  ],
  view: new ol.View({
    projection: "EPSG:4326",
    center: [0, 0],
    zoom: 1
  })
});
#map {
  height: 450px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/7.1.0/ol.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/7.1.0/dist/ol.min.js"></script>

<div id="map"></div>


Solution

  • Split the line at the antimeridian as in https://github.com/openlayers/openlayers/issues/11681#issuecomment-715583978

    function splitAtDateLine(coords) {
      const lineStrings = [];
      let lastX = Infinity
      let lineString;
      for (let i = 0, ii = coords.length; i < ii; ++i) {
        const coord = coords[i];
        const x = coord[0];
        if (Math.abs(lastX - x) > 180) { // Crossing date line will be shorter
          if (lineString) {
            const prevCoord = coords[i - 1];
            const w1 = 180 - Math.abs(lastX);
            const w2 = 180 - Math.abs(x);
            const y = (w1 / (w1 + w2)) * (coord[1] - prevCoord[1]) + prevCoord[1];
            if (Math.abs(lastX) !== 180) {
              lineString.push([lastX > 0 ? 180 : -180, y]);
            }
            lineStrings.push(lineString = []);
            if (Math.abs(x) !== 180) {
              lineString.push([x > 0 ? 180 : -180, y]);
            }
          } else {
            lineStrings.push(lineString = []);
          }
        }
        lastX = x;
        lineString.push(coord);
      }
      return lineStrings;
    }
    
    
    let coordinates = [
      [-122, 47],
      [147, -32]
    ];
    
    const myFeature = new ol.Feature({
      geometry: new ol.geom.MultiLineString(splitAtDateLine(coordinates)),
      name: "Line"
    });
    
    myFeature.setStyle(
      new ol.style.Style({
        stroke: new ol.style.Stroke({ color: "green", width: 8 })
      })
    );
    
    const vectorSource = new ol.source.Vector({
      features: [myFeature]
    });
    
    const vectorLayer = new ol.layer.Vector({
      source: vectorSource
    });
    
    const map = new ol.Map({
      target: "map",
      layers: [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        }),
        vectorLayer
      ],
      view: new ol.View({
        projection: "EPSG:4326",
        center: [0, 0],
        zoom: 1
      })
    });
    #map {
      height: 450px;
    }
    <link href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/7.1.0/ol.min.css" rel="stylesheet"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/7.1.0/dist/ol.min.js"></script>
    
    <div id="map"></div>

    This will produce what looks like the shortest route on a flat map. In reality on a round planet the shortest route is a great circle route as in https://openlayers.org/en/latest/examples/flight-animation.html