7

I user Openlayers 6. I want to create an arc line in OpenLayers that connects one coordinate to another. I have a case where when the distance between the points is too short, the line doesn't curve. Even though in this case I want to create an arc line like the kepler.gl site. How can I make an arc line like that?

OpenLayers mine

I want to like kepler.gl in Openlayers

My Openlayers Code:

var tileLayer = new ol.layer.Tile({
  source: new ol.source.Stamen({
    layer: 'toner',
  }),
});

var map = new ol.Map({
  layers: [tileLayer],
  target: 'map',
  view: new ol.View({
    center: [0, 0],
    zoom: 2,
  }),
});

var style = new ol.style.Style({
  stroke: new ol.style.Stroke({
    color: '#EAE911',
    width: 2,
  }),
});

var flightsSource = new ol.source.Vector({
  wrapX: false,
  attributions:
    'TES',
  loader: function () {
    var url = 'flights.json';
    fetch(url)
      .then(function (response) {
        return response.json();
      })
      .then(function (json) {
        var flightsData = json.flights;
        for (var i = 0; i < flightsData.length; i++) {
          var flight = flightsData[i];
          var from = flight[0];
          var to = flight[1];

          // create an arc circle between the two locations
          var arcGenerator = new arc.GreatCircle(
            { x: from[1], y: from[0] },
            { x: to[1], y: to[0] }
          );

          var arcLine = arcGenerator.Arc(100, { offset: 10 });
          if (arcLine.geometries.length === 1) {
            var line = new ol.geom.LineString(arcLine.geometries[0].coords);
            line.transform('EPSG:4326', 'EPSG:3857');

            var feature = new ol.Feature({
              geometry: line,
              finished: false,
            });
            // add the feature with a delay so that the animation
            // for all features does not start at the same time
            addLater(feature, i * 50);
          }
        }
        tileLayer.on('postrender', animateFlights);
      });
  },
});

var flightsLayer = new ol.layer.Vector({
  source: flightsSource,
  style: function (feature) {
    // if the animation is still active for a feature, do not
    // render the feature with the layer style
    if (feature.get('finished')) {
      return style;
    } else {
      return null;
    }
  },
});

map.addLayer(flightsLayer);

var pointsPerMs = 0.1;
function animateFlights(event) {
  var vectorContext = ol.render.getVectorContext(event);
  var frameState = event.frameState;
  vectorContext.setStyle(style);

  var features = flightsSource.getFeatures();
  for (var i = 0; i < features.length; i++) {
    var feature = features[i];
    if (!feature.get('finished')) {
      // only draw the lines for which the animation has not finished yet
      var coords = feature.getGeometry().getCoordinates();
      var elapsedTime = frameState.time - feature.get('start');
      var elapsedPoints = elapsedTime * pointsPerMs;

      if (elapsedPoints >= coords.length) {
        feature.set('finished', true);
      }

      var maxIndex = Math.min(elapsedPoints, coords.length);
      var currentLine = new ol.geom.LineString(coords.slice(0, maxIndex));

      // directly draw the line with the vector context
      vectorContext.drawGeometry(currentLine);
    }
  }
  // tell OpenLayers to continue the animation
  map.render();
}

function addLater(feature, timeout) {
  window.setTimeout(function () {
    feature.set('start', new Date().getTime());
    flightsSource.addFeature(feature);
  }, timeout);
}
2
  • And how would you define desired curvature of such a 'fake' geodetic line?
    – TomazicM
    Commented Jan 19, 2021 at 16:44
  • I don't know how to define the curvature. That is also what I am looking for a model like kepler.gl, namely the distance between the coordinates is short but has curvature and is not straight. Commented Jan 20, 2021 at 2:22

1 Answer 1

8

One possible solution for this is to draw circle arc through desired two points. This can be achieved with the help of some basic cartesian math on projected coordinates and Turf.js library (http://turfjs.org/).

Procedure goes like this (see EDIT below for simpler solution):

  1. First curvature of circle arc has to be decided. Simple method is to set the desired distance of arc in the middle of the line. If we call this distance offset, and distance between the two points is d, then radius r of desired circle can be calculated.
  2. Now that we have two points and radius, coordinates of circle center can be calculated. Formulas were obtained from StackOverflow (https://stackoverflow.com/questions/36211171/finding-center-of-a-circle-given-two-points-and-radius). As a result we get two possible centers, we decide for [cx1, cy1].
  3. With circle center, radius and two points, circle arc can be constructed using Turf.js turf.lineArc method. Since this method uses geodetic calculations, first true geodetic radius r1 has to be calculated, which is geodetic distance between radius center and any one of the two points.

In the example below, offset is set to be one tenth of distance between the points:

var p1LonLat = [104.784, -3.03];
var p2LonLat = [103.591, -1.625];

var p1 = ol.proj.fromLonLat(p1LonLat);
var p2 = ol.proj.fromLonLat(p2LonLat);
var geom = new ol.geom.LineString([p1, p2]);

var line = new ol.Feature({
  geometry: geom
});
vectorSource.addFeature(line);

var d = geom.getLength();
var offset = d / 10;
var r = (Math.pow(d / 2, 2) + Math.pow(offset, 2)) / (2 * offset);

var x1 = p1[0];
var x2 = p2[0];
var y1 = p1[1];
var y2 = p2[1];

var q = Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2));

var y3 = (y1 + y2) / 2;      
var x3 = (x1 + x2) / 2;

var basex = Math.sqrt(Math.pow(r, 2) - Math.pow((q / 2), 2)) * (y1 - y2) / q;
var basey = Math.sqrt(Math.pow(r, 2) - Math.pow((q / 2), 2)) * (x2 - x1) / q;

var cx1 = x3 + basex;
var cy1 = y3 + basey;
var cx2 = x3 - basex;
var cy2 = y3 - basey;

var pcLonLat = ol.proj.transform([cx1, cy1], 'EPSG:3857', 'EPSG:4326');
var r1 = turf.distance(turf.point(pcLonLat), turf.point(p1LonLat));

var bear1 = turf.bearing(turf.point(pcLonLat), turf.point(p1LonLat));
var bear2 = turf.bearing(turf.point(pcLonLat), turf.point(p2LonLat));

var arc = turf.lineArc(turf.point(pcLonLat), r1, bear2, bear1, {steps: 256});

var arcFeature = new ol.format.GeoJSON().readFeatures(arc, {
  featureProjection: 'EPSG:3857',
  dataProjection: 'EPSG:4326'
});
arcFeature[0].setStyle(new ol.style.Style({
    stroke: new ol.style.Stroke({
      color: '#0000FF',
      width: 1,
      lineDash: [5, 2],
      lineCap: 'butt'
    })  
}));
vectorSource.addFeature(arcFeature[0]);

Here's how the results looks like:

enter image description here

EDIT: There is a much simpler solution, where all the calculation is done by Turf.js. It goes like this:

  1. First middle point pMid is found one the line connecting two points with the help of turf.lineString, turf.distance and turf.along methods.
  2. Then circle center point centerPoint is calculated turf.destination, which is placed perpenidicular to line two line lengths (this can be adjusted to change arc curvature) from line center point.
  3. Then circle radius r is calculated, which is distance of circle center point to any of the two points.
  4. Then, now having circle center, radius and two points, circle arc arc2 is constructed using Turf.js turf.lineArc method.
var line = turf.lineString([p1LonLat, p2LonLat]);
var d = turf.distance(p1LonLat, p2LonLat);
var pMid = turf.along(line, (d / 2));

var lineBearing = turf.bearing(p1LonLat, p2LonLat);
var centerPoint = turf.destination(pMid, (2 * d), (lineBearing - 90));

var r = turf.distance(centerPoint, turf.point(p1LonLat));      
var bear1 = turf.bearing(centerPoint, turf.point(p1LonLat));
var bear2 = turf.bearing(centerPoint, turf.point(p2LonLat));
var arc2 = turf.lineArc(centerPoint, r, bear2, bear1, {steps: 256});

var arcFeature2 = new ol.format.GeoJSON().readFeatures(arc2, {
  featureProjection: 'EPSG:3857',
  dataProjection: 'EPSG:4326'
});
arcFeature2[0].setStyle(new ol.style.Style({
    stroke: new ol.style.Stroke({
      color: '#FF0000',
      width: 1,
      lineDash: [5, 2],
      lineCap: 'butt'
    })  
}));
vectorSource.addFeature(arcFeature2[0]);

This is final result of using both methods:

enter image description here

1
  • Thanks. This comment has solved my problem! Commented Jan 21, 2021 at 8:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.