When polygon spans 2 tiles, OpenLayers creates 2 labels, one for each tile. How to prevent it?
MRE polygons in PBF tiles rendered by OpenLayers
In empty directory create these two files:
geom.geojson contains two polygons spanning upper and lower half of EPSG:3857
[ { "type": "Feature", "geometry": { "coordinates": [ [ [ 20037508.0, -10018754.0 ], [ 10018754.000000002, -1342258.52173304 ], [ -10018753.999999996, -1342258.52173304 ], [ -20037508.0, -10018753.999999998 ], [ -10018754.00000001, -18695249.47826696 ], [ 10018754.000000002, -18695249.478266962 ] ] ], "type": "Polygon" }, "properties": { "id": "POLYGON-0" } }, { "type": "Feature", "geometry": { "coordinates": [ [ [ 20037508.0, 10018754.0 ], [ 10018754.000000002, 18695249.478266962 ], [ -10018753.999999996, 18695249.478266962 ], [ -20037508.0, 10018754.000000002 ], [ -10018754.00000001, 1342258.5217330419 ], [ 10018754.000000002, 1342258.52173304 ] ] ], "type": "Polygon" }, "properties": { "id": "POLYGON-1" } } ] index.html renders PBF layer from ./tilesMeta/ directory:
<!DOCTYPE html> <html> <head> <title>OpenLayers PBF Tiles</title> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ol.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/ol.css"> <style> #map { width: 100%; height: 100vh; } </style> </head> <body> <div id="map"></div> <script> const getStyle = function(feature) { //console.log(feature) return new ol.style.Style({ text: new ol.style.Text({ text: ''+feature.get('id'), font: 'bold 2rem Arial', placement: 'line', }), stroke: new ol.style.Stroke({ color: 'blue', width: 2 }), fill: new ol.style.Fill({ color: 'rgba(0, 0, 255, 0.1)' }) }) } const vectorSource = new ol.source.VectorTile({ format: new ol.format.MVT(), url: 'tilesMeta/{z}/{x}/{y}.pbf' }); const vectorLayer = new ol.layer.VectorTile({ source: vectorSource, style: getStyle, }); const map = new ol.Map({ target: 'map', layers: [vectorLayer], view: new ol.View({ center: ol.proj.fromLonLat([0, 0]), zoom: 1, multiWorld: false, }) }); if (true) { map.getLayers().insertAt(1,new ol.layer.Tile({ source: new ol.source.TileDebug({ tileGrid: vectorLayer.getSource().getTileGrid() }) }))} </script> </body> </html> Now, create PBF tiles with Tippecanoe, encode all polygons to zoom level 1.
tippecanoe --output-to-directory tilesMeta --no-tile-compression --minimum-zoom 1 --maximum-zoom 1 --base-zoom 1 --drop-rate=0 --force --projection=EPSG:3857 .\geom.geojson Finally, serve this directory at 8000 and visit localhost:8000 in web browser:
python -m http.server 8000 You will see each polygon having a label for each tile it intersects:
In this example duplicate labels look OK, but in real world if a small polygon will happen to be placed on interscetion of two tiles, it will get two labels and it will look bad.
Under the hood, each polygon is split into as many smaller polygons, as there are tiles it intersects, and for each of those parts style is applied independently. In the first place I wasn't expecting even Strokes to be correct, but OL draws outline only for real borders of polygon, not for tile intersections. But style.text logic ignores the fact each polygon can span multiple tiles. How to keep only 1 true label per polygon?
Official OL example of labeling polygons doesn't have this issue, because all polygons are fetched from single GeoJSON, not from PBF tiles: https://openlayers.org/en/latest/examples/vector-label-decluttering.html
Below is utility Powershell code to create geojson with desired polygons:
function Create-Polygon { [CmdletBinding()] param( [int]$Points, [double]$MinX = 0, [double]$MinY = 0, [double]$MaxX = 1024, [double]$MaxY = 1024 ) if ($Points -lt 3) { throw "Points must be at least 3 to form a polygon" } $polygon = @() $centerX = ($MinX + $MaxX) / 2 $centerY = ($MinY + $MaxY) / 2 $radiusX = ($MaxX - $MinX) / 2 $radiusY = ($MaxY - $MinY) / 2 for ($i = 0; $i -lt $Points; $i++) { $angle = (2 * [Math]::PI * $i) / $Points $x = $centerX + $radiusX * [Math]::Cos($angle) $y = $centerY + $radiusY * [Math]::Sin($angle) $polygon += [PSCustomObject]@{ X = $x Y = $y } } return $polygon } $EPSG = 20037508 $polygons = @() $polygons += ,@(Create-Polygon 6 -$EPSG -$EPSG $EPSG 0) $polygons += ,@(Create-Polygon 6 -$EPSG 0 $EPSG $EPSG) $polygons | % {$i=0}{ $polygon = $_ $coords = $polygon | % {,@($_.x,$_.y)} return [pscustomobject]@{ type = 'Feature' geometry = @{type='Polygon'; coordinates=@(,@($coords)); } properties = @{ id='POLYGON-'+$i++ } } } | ConvertTo-Json -Depth 99 > geom.geojson 
placement=linebecause it looks very appealing. Now I know duplicate labels is a known issue and it's unlikely it will be fixed any time soon.