In short:
A pointer from one point towards another, angle calculated with any Bearing tool / formula in hand, did not show correctly, but some degrees wrong. Calculate made in lat / lon values and shown in another CRS than calculations were made. EPSG:3067 is used as map CRS.

How to "convert" the bearing from CRS to another and with which extra parameters?

Long story:
I want to have a pointer on map, which points from its current position over map to my desired destination - an arrow, that I can point from one to another, drawn as overlay on openlayers map. For that the tool seems to be to calculated correct Rhumb-Bearing.

So codewise I place a pointer symbol (circle where inside an centered arrow) and I rotate it with calculated bearing value.

I have learned about Bearing, that you can calculate it from [lat,lon] pair of coordinates either by hand 1 or using a library, like turf.js [2], [3]. I have tried all these three approaches, but my pointer does not point to correct direction. The issue is what is happening in my source [4]: I use a map with EPSG:3067 coordinate system, whereas

Turf.js library deals with GeoJSON vector data, so it uses unprojected WGS84 coordinate system, which is EPSG:6326.

and there comes the issue: in [4] they say that Bearing may vary depending on crs. So, my pointer, whether it is calculated with Bearing or Rhumb-bearing method, always has some deviation in the direction, at least on large distances, say > 200km.

I would like to be able to find a way to calculating the bearing in EPSG:3067 or somewhat to convert the one I get with turf.js or my current formula with lan-lot values.

How is bearing converted (one my sources said it alone cannot be converted)?

At least the formula and needed params.

I use map of Finland, which is quite north, and that may have an impact.


import { transform as transform2 } from 'ol/proj'; // openlayers.

// From map coords come with EPSG:3067
// I convert to EPSG:4326, which is one part of same WGS84 [6] as EPSG:6326 used in turf.js.
const lonlat = transform2([parseFloat(X1), parseFloat(Y1)], 'EPSG:3067', 'EPSG:4326');

//.. same to another one, too.

// Then with turf.js:
var point1 = Turf.point([startlon, startlat]);
var point2 = Turf.point([destlon, destlat]);
return (Turf.rhumbBearing(point1, point2, true) + 360) % 360;
#Then in css of an arrow pointing up on an overlay in same point of map where is (X1,Y1):
transform: [{ rotateZ: direction + ' deg' }] # Program is react native, this is part of the css of the element inside React code.

bearing showing wrong

The result kind of shows to correct direction, but not directly (some degrees wrong, if you see with a ruler.

Coordinates on pic: (EPSG:3067)


289792.8753007395 6780621.145424524


290219.31183128356 6781279.348593385, 290339.9582224339 6781381.352886491

target calculated from extent of a polygon, I gave the extent. Code for calculation:

function getCenterOfExtent(Extent){
        var X = parseFloat(Extent[0]) + (parseFloat(Extent[2])-parseFloat(Extent[0]))/2;
        var Y = parseFloat(Extent[1]) + (parseFloat(Extent[3])-parseFloat(Extent[1]))/2;
        return [X, Y];


1 https://www.movable-type.co.uk/scripts/latlong.html

[2] https://turfjs.org/docs/#bearing

[3] https://turfjs.org/docs/#rhumbBearing

[4] Azimuth (bearing) changes depending on map/project projection

[5] Do Leaflet and turfjs have different projections?

[6] https://tomroth.com.au/epsg/

  • 1
    Coding questions on GIS SE site relevant existing code to be part of the question. Please edit your question and add relevant code that results in "my pointer does not point to correct direction." A picture or two illustrating the wrong (and right) direction would also help.
    – TomazicM
    Feb 2, 2021 at 15:13
  • One simple way to solve this would be to get bearing with turf.js, then also with turf.js create short GeoJSON line along the bearing, then in OL transform this line to desired CRS and calculate bearing with simple cartesian math.
    – TomazicM
    Feb 2, 2021 at 15:29
  • I put some clarifications in edit: code and a pic. I think these were the relevant parts, dealing with coords, transform and calculation.
    – mico
    Feb 3, 2021 at 8:44
  • Can you please edit your question and post also coordinates of those two points.
    – TomazicM
    Feb 3, 2021 at 9:14
  • Edited. There is some calculus on the dest point. I checked it, it should be not the problem, though.
    – mico
    Feb 3, 2021 at 9:35

1 Answer 1


When you calculate bearing with turf.js, it gives a angle/direction on a sphere. When sphere is projected to a flat surface, its spheric geometry is transformed/deformed and as a consequence, this angle generally does not point to the same direction on a flat surface. Difference between angles before and after projection depends on type of projection and the coordinates of the point.

In your case, since points are so close that Earth curvature is neglible, you can calculate bearing with simple cartesian math, using projected coordinates. If you have points p1 and p2 in projected coordinates, bearing would then be

var bearing = Math.atan(Math.abs(p1[0] - p2[0]) / Math.abs(p1[1] - p2[1]));

Difference in angles depends on type of projection and point coordinates. In your case, where projection is UTM zone 35N, central meridian of this projections is at 27° of lat. In the case of your points, they are slightly off this meridian (23°), so difference is not big (blue line us turf bearing):

enter image description here

If you move points further away from central meridian (9°), difference is much bigger:

enter image description here

If you put your points on the meridian, difference disappears:

enter image description here

  • My map uses Web Mercator / Pseudo Mercator projection and from Wikipedia it says that there "meridians are equally spaced vertical lines, angles are locally correct (assuming spherical coordinates)". I think it is the same you explain. My finding was I should use the 3395 (WGS 84 / World Mercator), which actually is Openlayers default crs. That shows at least at ~100km scale still quite accurate results. So the area where results still were ok, was augmented. Whilst this finding is not a 100% answer, I leave it as comment here.
    – mico
    Feb 4, 2021 at 8:58

Your Answer

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

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