Mapnik Extents – How to Get TextSymbolizer Bounding Boxes in Python

extentsmapnikxml

Is there a way to obtain the bounding boxes of each TextSymbolizer present on the map? For now, I'm using mapnik 3 and I create a map using a custom XML containing TextSymbolizer. I want to have something like xmin, ymin and xmax, ymax of every labels.

Here is a sample of my XML file:

<Rule>
     <MinScaleDenominator>0</MinScaleDenominator>
     <MaxScaleDenominator>34000</MaxScaleDenominator>
     <Filter>([symbo] = &quot;100&quot;) and ([placement_ok] = &quot;true&quot;)</Filter>
     <TextSymbolizer
         face-name="DejaVu Sans Book" size="14.29"
         label-position-tolerance="0" fill="black"
         placement="point" allow-overlap="false"
         halo-fill="white" halo-radius="1.5">
         [ref_label]
     </TextSymbolizer>
</Rule>

Best Answer

I didn't find any built-in functions or any other straighforward way to do this.

I managed to find a solution using psycopg to fetch the data inside the database and Pillow to calculate the extents of labels.

Considering a map at a scale of 1/25 000, with a pixel size of 0.00028 m. This script works if you center your label placement in mapnik. You can modify the calculation of coordinates by applying the same translation as specified in mapnik.

def calculate_text_extent(fontname, fontsize, content):
    font = ImageFont.truetype(fontname, fontsize)
    size = font.getsize(content)
    return size

text = 'Hello World'
font = 'DejaVuSans.ttf'
size = 14
padding = 5
scale_denominator = 25000
pixel_size = calculate_text_extent(font, size, text)
width = (((pixel_size[0] + padding)/2)*0.00028*scale_denominator)
height = (((pixel_size[1] + padding)/2)*0.00028*scale_denominator)
xmin = x - width
xmax = x + width
ymin = y - height
ymax = y + height

The drawback is that it sometimes is a little displaced vertically, especially with crazy fonts and letters that expands downwards (such as p, j, g, q). I believe it is caused by mapnik, who considers the baseline of the text to place labels and not the lowest line of the text.

Related Question