1

I'm using OpenLayers with the EPSG 3857 Coordinate System and I want my polygon labels to be orientated along the longest side of the polygon.

The labels in OpenLayers are aligned horizontally by default but I can rotate them by passing a radian value. It goes clockwise on positive and counterclockwise on negative radian. So I need to calculate the correct radians.

First I iterate the points of a polygon and calculate the longest side. Then I determine my vector from one point to another by subtracting the x and y values and the unit vector in the direction of the x-axis. Now I calculate the scalar product and if it's zero then I know I have to rotate my labels by 90 degree or PI/2 for radian. This works fine I just add PI to flip the text so I can read it easier.

With if/else at the end I'm messing around to get the right angle but I'm not sure at this point because it works for the most polygons but not all of them.

What I'm missing here?

My math looks like this for each polygon:

var arrPoints = feature.getGeometry().getFlatCoordinates();

if (arrPoints.length >= 6) {

    var x = 0;
    var y = 1;
    
    var longestSide = 0;
    var longestSidePointFrom;
    var longestSidePointTo;

    for (var i = 4; i <= arrPoints.length; i += 2) {
        var pointFrom = [arrPoints[i - 4], arrPoints[i - 3]];
        var pointTo = [arrPoints[i - 2], arrPoints[i - 1]];

        const xDiff = arrPoints[i - 4] - arrPoints[i - 2];
        const yDiff = arrPoints[i - 3] - arrPoints[i - 1];

        var distance = Math.sqrt(xDiff * xDiff + yDiff * yDiff);

        if (distance > longestSide) {
            longestSide = distance;

            longestSidePointFrom = pointFrom;
            longestSidePointTo = pointTo;
        }
    }

    var vector = [longestSidePointFrom[x] - longestSidePointTo[x], longestSidePointFrom[y] - longestSidePointTo[y]];

    var scalarProduct = vector[x] * 1 + vector[y] * 0;

    if (scalarProduct == 0) {
        return Math.PI + (Math.PI / 2);
    } else {
        var sumA = Math.pow(vector[x], 2) + Math.pow(vector[y], 2);
        var sumB = Math.pow(1, 2) + Math.pow(0, 2);

        var cosine = scalarProduct / (Math.sqrt(sumA) * Math.sqrt(sumB));

        var radian = Math.acos(cosine);

        if (radian > (Math.PI / 2)) {
            radian = radian * -1 + (Math.PI)
        } else {
            radian = radian + 2 * Math.PI;
        }

        return radian;
    }
}

EDIT:

Here are 4 Polygons. The green line indicates the longest side. As you can see the angle for number 1 is not correct:

enter image description here

Here are the coordinates for the shown polygons:

1)

0: 44.7879 1: 313.8661 2: 42.1537 3: 317.1012 4: 45.4705 5: 319.8012 6: 48.1048 7: 316.5661 8: 44.7879 9: 313.8661

2)

0: 42.458 1: 311.9045 2: 39.7923 3: 315.1779 4: 42.1537 5: 317.1012 6: 44.7879 7: 313.8661 8: 42.458 9: 311.9045

3)

0: 37.2561 1: 307.6731 2: 34.5923 3: 310.9445 4: 36.9185 5: 312.8382 6: 39.5832 7: 309.5648 8: 37.2561 9: 307.6731

4)

0: 29.4927 1: 301.3244 2: 26.8156 3: 304.6124 4: 30.0662 5: 307.259 6: 32.7309 7: 303.9856 8: 29.4927 9: 301.3244

If I remove my if statements then it looks like this: enter image description here

2
  • 1
    In what way it doesn't work for some polygons? It would be helpful if you post a picture or two of working and non working polygons and corresponding longest line coordinates.
    – TomazicM
    Aug 12, 2020 at 20:30
  • sure, I just edit my post
    – BR75
    Aug 12, 2020 at 21:18

1 Answer 1

2

Using Math.atan is the easiest way to get the angle. It is also much simpler to work which coordinates from getCoordinates() instead of flat coordinates. https://codesandbox.io/s/great-babbage-800do?file=/main.js

The relevant code:

var vectorLayer = new VectorLayer({
  source: new VectorSource({
    features: features
  }),
  style: function (feature) {
    var arrPoints = feature.getGeometry().getCoordinates()[0];

    var sqLongestSide = 0;
    var longestSideXDiff;
    var longestSideYDiff;

    for (var i = 1; i < arrPoints.length; i++) {
      var pointFrom = arrPoints[i - 1];
      var pointTo = arrPoints[i];

      const xDiff = pointFrom[0] - pointTo[0];
      const yDiff = pointFrom[1] - pointTo[1];

      var sqDistance = xDiff * xDiff + yDiff * yDiff;

      if (sqDistance > sqLongestSide) {
        sqLongestSide = sqDistance;
        longestSideXDiff = xDiff;
        longestSideYDiff = yDiff;
      }
    }

    var radian = Math.atan(-longestSideYDiff / longestSideXDiff);
    style.getText().setText("OpenLayers");
    style.getText().setRotation(radian);
    return style;
  }
});
2
  • 1
    I added the relevant code from example. It's always nice to have as a part of the answer.
    – TomazicM
    Aug 15, 2020 at 20:04
  • this works great, thank you very much!
    – BR75
    Aug 16, 2020 at 17:55

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.