Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Folium: color mapping when adding GeoJson to the map

Tags:

python

folium

I have a loop in my python code which adds features to my folium map. Each feature is held within a dictionary (called products) and each feature has a dictionary of parameters (GeoJson string and attributes). I want to color the feature based on one of the parameters.

I first create a list of unique values for the parameter. Then I map a color to each unique value:

orb_list = [value['relativeorbitnumber'] for key, value in products.items()]
orb_list = list(set(orb_list))

color_cycle = cycler(color=['#8e0038', '#8e0073', '#59008e'], fillColor=['#8e0038', '#8e0073', '#59008e'])

orb_colors=dict([[i, sty] for i, sty in zip(orb_list, cycle(color_cycle))])

Next, I create a loop in which I retrieve the color and fillColor corresponding to the parameter value of the feature, and use this in the style_function:

for key,value in products.items():
    footprint = json.dumps(wkt.loads(products[key]['footprint']))
    fillColor = orb_colors[products[key]['relativeorbitnumber']]['fillColor']
    color = orb_colors[products[key]['relativeorbitnumber']]['color']
    feat = folium.GeoJson(footprint,
               style_function=lambda x: {'fillColor':fillColor,'color':color},
               highlight_function=lambda feature: {'fillcolor':'green','color':'green'},
               name='Footprint')

    feat.add_to(mapa)

I thought this would work, however all the features seem to be colored by the last "fillColor" and "color" in my loop. Can anyone explain why this is? I assume the features aren't rendered until after all loops are complete, and so the last values of "color" and "fillColor" are used for all features. Can you suggest how my code should be modified to get around this problem? Or perhaps I am going about it all the wrong way and you can suggest a much cleaner method?

like image 492
ste_j Avatar asked Dec 23 '22 02:12

ste_j


1 Answers

You encountered a common pitfall with Python closures

The function you pass to e.g. style_function is not executed immediately in the loop, but later. At that time, fillColor will be retrieved from the outer scope (because it's not defined in the inner scope created by the lambda expression), where it will have the last value at this point.

To demonstrate what's happening:

In [1]: out = []
   ...: for k in ('a', 'b', 'c'):
   ...:     l = lambda: k
   ...:     out.append(l)
   ...: [l() for l in out]
Out[1]: ['c', 'c', 'c']

To prevent this, you could pass the (current) value of the variable as a default argument to the function created by the lambda expression, so that value is used when the function is being executed later:

In [2]: out = []
   ...: for k in ('a', 'b', 'c'):
   ...:     l = lambda k=k: k
   ...:     out.append(l)
   ...: [l() for l in out]
Out[2]: ['a', 'b', 'c']

Or in your code:

for key, value in products.items():
    footprint = json.dumps(wkt.loads(products[key]["footprint"]))
    fillColor = orb_colors[products[key]["relativeorbitnumber"]]["fillColor"]
    color = orb_colors[products[key]["relativeorbitnumber"]]["color"]
    feat = folium.GeoJson(
        footprint,
        style_function=lambda x, fillColor=fillColor, color=color: {
            "fillColor": fillColor,
            "color": color,
        },
        highlight_function=lambda feature: {"fillcolor": "green", "color": "green"},
        name="Footprint",
    )

    feat.add_to(mapa)

This should solve the problem.

like image 76
jeverling Avatar answered Dec 25 '22 14:12

jeverling