[GIS] OpenLayers: Keep text style label in visible polygon area

labelingopenlayersstyle

I have a feature with a polygon and a text attribute. The text attribute is displayed via a text-style above the polygons center. Nothing special and so far so good.

The current behavior:
I can move the map so that a part of the polygon is still visible while the label is outside of the viewport (because the label stays at the center of the polygon).

The wanted behavior:
I can move the map so that a part of the polygon is still visible and the label stays inside the viewport (so it changes its position) until the polygon is fully outside of the viewport and not visible any more.

Is it possible to create such behavior?

Best Answer

Solved it with the help of @Mike using the TurfJS framework for the intersection calculation. I'm using TypeScript in an Angular application so my code looks something like this:

private layer: ol.layer.Vector;
private style: ol.style.Style[];
public  map : ol.Map; // is set from the outside

constructor() {
  // creating layer and other stuff
  this.layer = ...

  var labelStyle = new ol.style.Style({
    text : new ol.style.Text({
      text: 'foo',
      font: '9pt sans-serif',
      stroke: new ol.style.Stroke({
        color: 'black',
        width: 0.75
      }),
      backgroundFill: new ol.style.Fill({
        color: 'white'
      }),
      overflow: true
    })
  });

  this.style = [labelStyle];

  this.layer.setStyle((feature) => this.myStyleFunction(feature));
}

private myStyleFunction(feature) {
  let geometry = feature.getGeometry();

  // Only render label for the widest polygon of a multipolygon
  if (geometry.getType() === 'MultiPolygon') {
    const polygons = geometry.getPolygons();
    let widest = 0;
    for (let i = 0, ii = polygons.length; i < ii; ++i) {
      const polygon = polygons[i];
      const width = ol.extent.getWidth(polygon.getExtent());
      if (width > widest) {
        widest = width;
        geometry = polygon;
      }
    }
  }

  const mapSize = this.map.getSize();
  const mapExtent = this.map.getView().calculateExtent([mapSize[0], mapSize[1]]);

  const coordinates = geometry.getCoordinates();
  const polygon = turf.polygon(coordinates);

  const extentPolygon = turf.polygon(ol.geom.Polygon.fromExtent(mapExtent).getCoordinates());

  const clippedGeometry = turf.intersect(polygon, extentPolygon);

  if (clippedGeometry) {

    style[0].setGeometry(new GeoJSON().readGeometry(clippedGeometry.geometry));
    style[0].getText().setText('' + feature.get('KN_MAX') + 'm');
  }
  return style;
}

It even works with multi polygons and shows one label per part of the multi polygon.

Related Question