OpenLayers: Align label with Line Feature

In this short post we are going to discuss how to display label on the LineString. This can be useful if you want to add some information along the line, for example start and destination city:

ol_rotating_text_slope_logo
Label along the LineString geometry

In our case the label would be OpenLayer’s Text style for vector feature. It contains property named: rotation, which contains rotation in radians (positive rotation clockwise).

In order to calculate rotation value we need to do bearing to angle conversion (for more information on math behind this: Calculate distance, bearing and more between Latitude/Longitude points):

function radians(n) {
    return n * (Math.PI / 180);
}

function degrees(n) {
    return n * (180 / Math.PI);
}

function bearing(start_lat, start_long, end_lat, end_long) {
    start_lat = radians(start_lat);
    start_long = radians(start_long);
    end_lat = radians(end_lat);
    end_long = radians(end_long);

    var dlong = end_long - start_long;

    var dphi = Math.log(Math.tan(end_lat / 2.0 + Math.PI / 4.0) /
            Math.tan(start_lat / 2.0 + Math.PI / 4.0));
    if (Math.abs(dlong) > Math.PI) {
        if (dlong > 0.0)
            dlong = -(2.0 * Math.PI - dlong);
        else
            dlong = (2.0 * Math.PI + dlong);
    }

    return (degrees(Math.atan2(dlong, dphi)) + 360.0) % 360.0;
}

function bearingToRadians(br) {
    return radians((450 - br) % 360);
}

function rotation(pt0, pt1, br) {
    var rotate = pt0[0] > pt1[0] ? Math.PI : 0;
    return bearingToRadians(br) + rotate
}

Here is the source code which add label to Line feature and display text on it, you can view it on github:

function radians(n) {
    return n * (Math.PI / 180);
}

function degrees(n) {
    return n * (180 / Math.PI);
}

function bearing(start_lat, start_long, end_lat, end_long) {
    start_lat = radians(start_lat);
    start_long = radians(start_long);
    end_lat = radians(end_lat);
    end_long = radians(end_long);

    var dlong = end_long - start_long;

    var dphi = Math.log(Math.tan(end_lat / 2.0 + Math.PI / 4.0) /
            Math.tan(start_lat / 2.0 + Math.PI / 4.0));
    if (Math.abs(dlong) > Math.PI) {
        if (dlong > 0.0)
            dlong = -(2.0 * Math.PI - dlong);
        else
            dlong = (2.0 * Math.PI + dlong);
    }

    return (degrees(Math.atan2(dlong, dphi)) + 360.0) % 360.0;
}

function bearingToRadians(br) {
    return radians((450 - br) % 360);
}

function rotation(pt0, pt1, br) {
    var rotate = pt0[0] > pt1[0] ? Math.PI : 0;
    return bearingToRadians(br) + rotate
}

var wsg84_pt1 = [-74.0059, 40.7127];
var wsg84_pt2 = [-75.6972, 45.4215];
// convert to default projection EPSG:3857 Web Mercarator
var pt1 = ol.proj.fromLonLat(wsg84_pt1);
var pt2 = ol.proj.fromLonLat(wsg84_pt2);

var map = new ol.Map({
    layers: [
        new ol.layer.Tile({
            source: new ol.source.OSM()
        })
    ],
    target: 'map',
    view: new ol.View({
        center: pt1,
        zoom: 6
    })
});
var FEATURE_LINE = 0;
var FEATURE_PT1 = 1;
var FEATURE_PT2 = 2;
var line = new ol.Feature({
    geometry: new ol.geom.LineString([pt1, pt2])
});
var pt1_feature = new ol.Feature({
    geometry: new ol.geom.Point(pt1)
});
var pt2_feature = new ol.Feature({
    geometry: new ol.geom.Point(pt2)
});
line.feature_type = FEATURE_LINE;
pt1_feature.feature_type = FEATURE_PT1;
pt2_feature.feature_type = FEATURE_PT2;

var feature_style = function (feature) {
    if (FEATURE_LINE == feature.feature_type) {
        return new ol.style.Style({
            stroke: new ol.style.Stroke({
                color: 'rgba(0, 204, 255, 0.6)',
                width: 10
            }),
            text: new ol.style.Text({
                rotation: -rotation(pt1, pt2, bearing(wsg84_pt1[1], wsg84_pt1[0], wsg84_pt2[1], wsg84_pt2[0])),
                text: 'New York to Ottawa',
                font: '17px sans-serif',
                fill: new ol.style.Fill({color: 'white'}),
                stroke: new ol.style.Stroke({color: 'black', width: 2})
            })
        });
    } else {
        var fill_color;
        switch (feature.feature_type) {
            case FEATURE_PT1:
                fill_color = new ol.style.Fill({color: 'rgba(191, 63, 191, 0.6)'});
                break;
            case FEATURE_PT2:
                fill_color = new ol.style.Fill({color: 'rgba(63, 191, 63, 0.6)'});
                break;
        }
        return new ol.style.Style({
            image: new ol.style.Circle({
                fill: fill_color,
                radius: 6,
                stroke: new ol.style.Stroke({color: 'black', width: 1.25})
            })
        });
    }
};
var vector_source = new ol.source.Vector({});
vector_source.addFeatures([pt1_feature, pt2_feature, line]);
var vector_layer = new ol.layer.Vector({
    source: vector_source,
    map: map,
    style: feature_style
});

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s