Geographic Information Systems Asked by Abdurrahman Naufal on May 24, 2021
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?
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);
}
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):
offset
, and distance between the two points is d
, then radius r
of desired circle can be calculated.[cx1, cy1]
.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:
EDIT: There is a much simpler solution, where all the calculation is done by Turf.js. It goes like this:
pMid
is found one the line connecting two points with the help of turf.lineString
, turf.distance
and turf.along
methods.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.r
is calculated, which is distance of circle center point to any of the two points.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:
Correct answer by TomazicM on May 24, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP