Python – How to Get More Information About WMS Legends Using OWSLib

copernicuslegendowslibpythonwms

I am working with OWSLib in Python and I am trying to read a WMS of the Corine land cover in Ireland for the year 2000. Below you can see the request of the WMS.

from owslib.wms import WebMapService
ireland_wms = WebMapService('https://gis-test.epa.ie/geoserver/ows?SERVICE=WMS&')

Then I print some general info about the layer of interest.

def layer_report(service, layer_name):
  print(f"WMS general info:\n")
  print(f"WMS version: {service.identification.version}")
  print(f"WMS title: {service.identification.title}")
  print(f"WMS abstract: {service.identification.abstract}")
  print(f"Provider name: {service.provider.name}")
  print(f"Provider address: {service.provider.contact.address}")
  print(f"\nLayer info:\n")
  print('- Title: ', service.contents[layer_name].title)
  print('- BoundingBox: ', service.contents[layer_name].boundingBox)
  print('- BoundingBoxWGS84: ', service.contents[layer_name].boundingBoxWGS84)
  print('- CrsOptions: ', service[layer_name].crsOptions)
  print('- Format Options: ', service.getOperationByName('GetMap').formatOptions)
  print('- Layer styles: ', service[layer_name].styles)
  print('- Operations ', [op.name for op in service.operations])

layer_report(ireland_wms, 'EPA:LAND_CLC00Rev')

and I get the following output

enter image description here

Now I have the legend that corresponds to type of Land cover, If I click on link shown in the above picture I get the following legend.

enter image description here

Now I would like to know if there is any way to gain more information about the legend. In particular, I would like to get, if it is possible, the color codes of the legend in order to use them for plotting. Is there any way to do this using OWSLib or any other library which is compatible with Python?

Best Answer

I found a rather nasty solution 😅

... with the official EEA DiscoMap WMS there's the possibility of getting the legend as a json-dict!
While the colors are still not directly available in the dictionary, there are base64 strings of the legend color-images... from which the actual rgba values can be extracted as follows (all imports are native python3!):

(I really don't get why they don't just provide a rgba tuple for the colors directly... but this is at least a working (python) method to extract the colors from the legend images)

import json
import requests
import base64
import io
import PIL

def most_common_color(base64_string):
    """
    extract the most common color from a base64 encoded png image
    as rgba tuple (with values in the range [0-1])
    
    Parameters
    ----------
    base64_string : str
        the base64 string.

    Returns
    -------
    most_common_color : tuple
        a rgba tuple of the most common color in the image.

    """
    img = base64.b64decode(base64_string)
    img = io.BytesIO(img)
    img = PIL.PngImagePlugin.PngImageFile(img)
    colors = img.getcolors()
    
    _, most_common_color = max(colors, key=lambda x: x[0])
    return tuple(i/255 for i in most_common_color)


url = "https://image.discomap.eea.europa.eu/arcgis/rest/services/Corine/CLC2018_LAEA/MapServer/legend?dynamicLayers=%5B0%5D&f=pjson"
leg = json.loads(requests.get(url).content.decode())

# select the first layer
layer = leg["layers"][0]

# check the name of the layer
layer_name = layer["layerName"]

# extract legend colors from legend-image data
legend = {i["label"] : most_common_color(i["imageData"]) for i in layer["legend"]}

... and here's the proof of concept that it actually works:

import matplotlib.pyplot as plt
f, ax = plt.subplots(figsize=(12,7))
ax.grid(axis="x")

ax.scatter(legend.keys(), [1]*len(legend), c=list(legend.values()),
           s=200, zorder=5)
ax.tick_params(rotation=90, left=False, labelleft=False)
f.subplots_adjust(top=.99, bottom=0.93, left=0, right=1)

enter image description here



update

Since it was mentioned in the comments that this does not work with the actual WMS service... here's how you could "decode" the given legend image to get the color-codes:

Since for your WMS only a png of the full legend is available, the only possibility I see is to chop the legend into the individual entries and then do the same procedure as above, e.g.:

from owslib.wms import WebMapService
import requests
import io
import PIL
import numpy as np


ireland_wms = WebMapService('https://gis-test.epa.ie/geoserver/ows?SERVICE=WMS&')
imgurl = ireland_wms["EPA:LAND_CLC00Rev"].styles["EPA:LAND_CLC00Rev"]["legend"]

# the pixel size of the entries (to split the legend into individual rows)
entry_width, entry_height = 20, 20

img = requests.get(imgurl)
img = img.content

img = io.BytesIO(img)
img = PIL.PngImagePlugin.PngImageFile(img)

# split the legend into the individual entries
imgarray = np.array(img)
entries = np.split(imgarray[:,:entry_width,:], imgarray.shape[0]/entry_height)

colors = []
for i in entries:
    entry_img = PIL.Image.fromarray(i)
    # extract most common color that is NOT white
    _, mcc = max((i for i in entry_img.getcolors() if i[1] != (255, 255, 255)), 
                 key=lambda x: x[0])
    # append the color as rgb tuple with values in the range [0, 1]
    colors.append(tuple(i/255 for i in mcc))

which then gives you something like:

enter image description here