Mapping Europe with Bokeh (using GeoPandas, and handling MultiPolygons)

Dear Bokeh-ers,

Sorry if this is a silly question, but I’m trying to create some graphics which will include coloring a map of Europe, and I’m stuck on a seemingly basic problem.

I’m trying to use the GeoPandas naturalearth_lowres database of countries to extract the ones I’m interested in. The countries geometries are stored mostly as Polygon objects, but occasionally as MultiPolygons. Which is where my problem starts.

I’m trying to tease out the x and y coordinates in the way bokeh requires, but get stuck trying to extract them from MultiPolygons. I found the following helper function (from https://automating-gis-processes.github.io/Lesson5-interactive-map-Bokeh-advanced-plotting.html):

def multiGeomHandler(multi_geometry, coord_type, geom_type):
    """
    Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
    Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
    Individual geometries are separated with np.nan which is how Bokeh wants them.
    # Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
    # https://github.com/bokeh/bokeh/issues/2321
    """

    for i, part in enumerate(multi_geometry):
        # On the first part of the Multi-geometry initialize the coord_array (np.array)
        if i == 0:
            if geom_type == "MultiPoint":
                coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)
            elif geom_type == "MultiLineString":
                coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)
            elif geom_type == "MultiPolygon":
                coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)
        else:
            if geom_type == "MultiPoint":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiLineString":
                coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiPolygon":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])

    # Return the coordinates
    return coord_arrays

However, these np.nans, unlike what’s stated at the comment, are throwing errors: They are not recognized by JSON. I tried replacing them with None, other meaningless values (inf), empty lists or tuples, or just 0. This is the closest I got (when replacing np.nan with the empty list ):

Any advice on what I might be missing, or how to get rid of those superfluous lines? I can imagine why they appear, but can’t find a way to simply make it work.

Thanks in advance for any thought! I know it shouldn’t be this difficult.

Hi,

Do you have a very old version of Bokeh? Bokeh has serialized NumPy arrays either using a Base64 encoding for standalone documents, or a pure binary transport or Bokeh server apps, for quite some time now. Alternatively, are you converting the arrays to plain python lists at some point before passing to Bokeh? More information is needed in order to help: system, library, browser versions, etc, and really, a *complete* example with instructions to run that could be investigated.

Thanks,

Bryan

···

On Oct 31, 2017, at 11:31, [email protected] wrote:

Dear Bokeh-ers,

Sorry if this is a silly question, but I'm trying to create some graphics which will include coloring a map of Europe, and I'm stuck on a seemingly basic problem.

I'm trying to use the GeoPandas naturalearth_lowres database of countries to extract the ones I'm interested in. The countries geometries are stored mostly as Polygon objects, but occasionally as MultiPolygons. Which is where my problem starts.

I'm trying to tease out the x and y coordinates in the way bokeh requires, but get stuck trying to extract them from MultiPolygons. I found the following helper function (from https://automating-gis-processes.github.io/Lesson5-interactive-map-Bokeh-advanced-plotting.html):

def multiGeomHandler(multi_geometry, coord_type, geom_type):

"""
    Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
    Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
    Individual geometries are separated with np.nan which is how Bokeh wants them.
    # Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
    # https://github.com/bokeh/bokeh/issues/2321
    """

for i, part in enumerate(multi_geometry):

# On the first part of the Multi-geometry initialize the coord_array (np.array)

if i == 0:

if geom_type == "MultiPoint":

coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)

elif geom_type == "MultiLineString":

coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)

elif geom_type == "MultiPolygon":

coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)

else:

if geom_type == "MultiPoint":

coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])

elif geom_type == "MultiLineString":

coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])

elif geom_type == "MultiPolygon":

coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])

# Return the coordinates

return coord_arrays

However, these np.nans, unlike what's stated at the comment, are throwing errors: They are not recognized by JSON. I tried replacing them with None, other meaningless values (inf), empty lists or tuples, or just 0. This is the closest I got (when replacing np.nan with the empty list ):

Any advice on what I might be missing, or how to get rid of those superfluous lines? I can imagine why they appear, but can't find a way to simply make it work.

Thanks in advance for any thought! I know it shouldn't be this difficult.

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/cac321ef-8364-498c-8646-bc8a3a27fca5%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Thanks for the prompt reply Bryan!

Here’s the full spec plus my entire code.

system - OSX El Capitan, version 10.11.6, python 2.7.13, Bokeh
0.12.10 (just ran another pip install upgrade now). The browser is Chrome Version 61.0.3163.100 (Official Build) (64-bit).

import pandas as pd
import numpy as np
from bokeh.io import show
from bokeh.models import (
ColumnDataSource,
HoverTool,
LogColorMapper
)
import geopandas as gpd
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure
from bokeh.sampledata.us_counties import data as counties
from bokeh.sampledata.unemployment import data as unemployment

#obtain countries shapes
world = gpd.read_file(gpd.datasets.get_path(‘naturalearth_lowres’))

europe = (world.loc[world[‘continent’] == ‘Europe’])

def convert_GeoPandas_to_Bokeh_format(gdf):
“”"
Function to convert a GeoPandas GeoDataFrame to a Bokeh
ColumnDataSource object.

:param: (GeoDataFrame) gdf: GeoPandas GeoDataFrame with polygon(s) under
                            the column name 'geometry.'
                           
:return: ColumnDataSource for Bokeh.
"""
gdf_new = gdf.drop('geometry', axis=1).copy()
gdf_new['x'] = gdf.apply(getCoords,
                         geom_col='geometry',
                         coord_type='x',
                         axis=1)

gdf_new['y'] = gdf.apply(getCoords,
                         geom_col='geometry',
                         coord_type='y',
                         axis=1)

return ColumnDataSource(gdf_new)

def getXYCoords(geometry, coord_type):
“”" Returns either x or y coordinates from geometry coordinate sequence. Used with LineString and Polygon geometries."""
if coord_type == ‘x’:
return geometry.coords.xy[0]
elif coord_type == ‘y’:
return geometry.coords.xy[1]

def getPolyCoords(geometry, coord_type):
“”" Returns Coordinates of Polygon using the Exterior of the Polygon."""
ext = geometry.exterior
return getXYCoords(ext, coord_type)

def getLineCoords(geometry, coord_type):
“”" Returns Coordinates of Linestring object."""
return getXYCoords(geometry, coord_type)

def getPointCoords(geometry, coord_type):
“”" Returns Coordinates of Point object."""
if coord_type == ‘x’:
return geometry.x
elif coord_type == ‘y’:
return geometry.y

def multiGeomHandler(multi_geometry, coord_type, geom_type):
“”"
Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
Individual geometries are separated with np.nan which is how Bokeh wants them.
# Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
# https://github.com/bokeh/bokeh/issues/2321
“”"

for i, part in enumerate(multi_geometry):
    # On the first part of the Multi-geometry initialize the coord_array (np.array)
    if i == 0:
        if geom_type == "MultiPoint":
            coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)
        elif geom_type == "MultiLineString":
            coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)
        elif geom_type == "MultiPolygon":
            coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)
    else:
        if geom_type == "MultiPoint":
            coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])
        elif geom_type == "MultiLineString":
            coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])
        elif geom_type == "MultiPolygon":
            coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])


# Return the coordinates
return coord_arrays

def getCoords(row, geom_col, coord_type):
“”"
Returns coordinates (‘x’ or ‘y’) of a geometry (Point, LineString or Polygon) as a list (if geometry is LineString or Polygon).
Can handle also MultiGeometries.
“”"
# Get geometry
geom = row[geom_col]

# Check the geometry type
gtype = geom.geom_type

# "Normal" geometries
# -------------------

if gtype == "Point":
    return getPointCoords(geom, coord_type)
elif gtype == "LineString":
    return list( getLineCoords(geom, coord_type) )
elif gtype == "Polygon":
    return list( getPolyCoords(geom, coord_type) )
   
# Multi geometries
# ----------------

else:
    return list( multiGeomHandler(geom, coord_type, gtype) )

def getGeometryCoords(row, geom, coord_type, shape_type):

“”"

Returns the coordinates (‘x’ or ‘y’) of edges of a Polygon exterior.

:param: (GeoPandas Series) row : The row of each of the GeoPandas DataFrame.

:param: (str) geom : The column name.

:param: (str) coord_type : Whether it’s ‘x’ or ‘y’ coordinate.

:param: (str) shape_type

“”"

# Parse the exterior of the coordinate

if row[geom]

# exterior =

if coord_type == ‘x’:

# Get the x coordinates of the exterior

return list( exterior.coords.xy[0] )

elif coord_type == ‘y’:

# Get the y coordinates of the exterior

return list( exterior.coords.xy[1] )

# elif shape_type == 'point':
#     exterior = row[geom]

#     if coord_type == 'x':
#         # Get the x coordinates of the exterior
#         return  exterior.coords.xy[0][0]

#     elif coord_type == 'y':
#         # Get the y coordinates of the exterior
#         return  exterior.coords.xy[1][0]

Europe_Source = convert_GeoPandas_to_Bokeh_format(europe)
p = figure(title=“Europe”)
print(Europe_Source)
p.multi_line(‘x’, ‘y’, source=Europe_Source, color=“gray”, line_width=1)
show§

``

(This is evolving code, sorry it’s not pretty yet).

(np.nan was the original value, which would result in the error: ValueError: Out of range float values are not JSON compliant: nan.

When changing np.nan with the empty list I get the map with the awkward lines, as seen above).

Am I missing any detail?

···

On Tuesday, October 31, 2017 at 4:37:56 PM UTC, Bryan Van de ven wrote:

Hi,

Do you have a very old version of Bokeh? Bokeh has serialized NumPy arrays either using a Base64 encoding for standalone documents, or a pure binary transport or Bokeh server apps, for quite some time now. Alternatively, are you converting the arrays to plain python lists at some point before passing to Bokeh? More information is needed in order to help: system, library, browser versions, etc, and really, a complete example with instructions to run that could be investigated.

Thanks,

Bryan

On Oct 31, 2017, at 11:31, [email protected] wrote:

Dear Bokeh-ers,

Sorry if this is a silly question, but I’m trying to create some graphics which will include coloring a map of Europe, and I’m stuck on a seemingly basic problem.

I’m trying to use the GeoPandas naturalearth_lowres database of countries to extract the ones I’m interested in. The countries geometries are stored mostly as Polygon objects, but occasionally as MultiPolygons. Which is where my problem starts.

I’m trying to tease out the x and y coordinates in the way bokeh requires, but get stuck trying to extract them from MultiPolygons. I found the following helper function (from https://automating-gis-processes.github.io/Lesson5-interactive-map-Bokeh-advanced-plotting.html):

def multiGeomHandler(multi_geometry, coord_type, geom_type):

“”"
Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
Individual geometries are separated with np.nan which is how Bokeh wants them.
# Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)

# [https://github.com/bokeh/bokeh/issues/2321](https://github.com/bokeh/bokeh/issues/2321)
"""

for i, part in enumerate(multi_geometry):

On the first part of the Multi-geometry initialize the coord_array (np.array)

if i == 0:

if geom_type == “MultiPoint”:

coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)

elif geom_type == “MultiLineString”:

coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)

elif geom_type == “MultiPolygon”:

coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)

else:

if geom_type == “MultiPoint”:

coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])

elif geom_type == “MultiLineString”:

coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])

elif geom_type == “MultiPolygon”:

coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])

Return the coordinates

return coord_arrays

However, these np.nans, unlike what’s stated at the comment, are throwing errors: They are not recognized by JSON. I tried replacing them with None, other meaningless values (inf), empty lists or tuples, or just 0. This is the closest I got (when replacing np.nan with the empty list ):

Any advice on what I might be missing, or how to get rid of those superfluous lines? I can imagine why they appear, but can’t find a way to simply make it work.

Thanks in advance for any thought! I know it shouldn’t be this difficult.


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/cac321ef-8364-498c-8646-bc8a3a27fca5%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

The problem seems to be the islands that certain European countries hold – seems like the way I’m currently converting the multiploygons (which happen because of these islands, which define the overall shape of the country as they appear in the geopandas dataset) results in all these separate territories being connected into one messy clump.

And original ideas on how to deal with this rather silly predicament?

···

On Tuesday, October 31, 2017 at 5:13:00 PM UTC, [email protected] wrote:

Thanks for the prompt reply Bryan!

Here’s the full spec plus my entire code.

system - OSX El Capitan, version 10.11.6, python 2.7.13, Bokeh
0.12.10 (just ran another pip install upgrade now). The browser is Chrome Version 61.0.3163.100 (Official Build) (64-bit).

import pandas as pd
import numpy as np
from bokeh.io import show
from bokeh.models import (
ColumnDataSource,
HoverTool,
LogColorMapper
)
import geopandas as gpd
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure
from bokeh.sampledata.us_counties import data as counties
from bokeh.sampledata.unemployment import data as unemployment

#obtain countries shapes
world = gpd.read_file(gpd.datasets.get_path(‘naturalearth_lowres’))

europe = (world.loc[world[‘continent’] == ‘Europe’])

def convert_GeoPandas_to_Bokeh_format(gdf):
“”"
Function to convert a GeoPandas GeoDataFrame to a Bokeh
ColumnDataSource object.

:param: (GeoDataFrame) gdf: GeoPandas GeoDataFrame with polygon(s) under
                            the column name 'geometry.'
                           
:return: ColumnDataSource for Bokeh.
"""
gdf_new = gdf.drop('geometry', axis=1).copy()
gdf_new['x'] = gdf.apply(getCoords,
                         geom_col='geometry',
                         coord_type='x',
                         axis=1)

gdf_new['y'] = gdf.apply(getCoords,
                         geom_col='geometry',
                         coord_type='y',
                         axis=1)

return ColumnDataSource(gdf_new)

def getXYCoords(geometry, coord_type):
“”" Returns either x or y coordinates from geometry coordinate sequence. Used with LineString and Polygon geometries."""
if coord_type == ‘x’:
return geometry.coords.xy[0]
elif coord_type == ‘y’:
return geometry.coords.xy[1]

def getPolyCoords(geometry, coord_type):
“”" Returns Coordinates of Polygon using the Exterior of the Polygon."""
ext = geometry.exterior
return getXYCoords(ext, coord_type)

def getLineCoords(geometry, coord_type):
“”" Returns Coordinates of Linestring object."""
return getXYCoords(geometry, coord_type)

def getPointCoords(geometry, coord_type):
“”" Returns Coordinates of Point object."""
if coord_type == ‘x’:
return geometry.x
elif coord_type == ‘y’:
return geometry.y

def multiGeomHandler(multi_geometry, coord_type, geom_type):
“”"
Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
Individual geometries are separated with np.nan which is how Bokeh wants them.
# Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
# https://github.com/bokeh/bokeh/issues/2321
“”"

for i, part in enumerate(multi_geometry):
    # On the first part of the Multi-geometry initialize the coord_array (np.array)
    if i == 0:
        if geom_type == "MultiPoint":
            coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)
        elif geom_type == "MultiLineString":
            coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)
        elif geom_type == "MultiPolygon":
            coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)
    else:
        if geom_type == "MultiPoint":
            coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])
        elif geom_type == "MultiLineString":
            coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])
        elif geom_type == "MultiPolygon":
            coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])


# Return the coordinates
return coord_arrays

def getCoords(row, geom_col, coord_type):
“”"
Returns coordinates (‘x’ or ‘y’) of a geometry (Point, LineString or Polygon) as a list (if geometry is LineString or Polygon).
Can handle also MultiGeometries.
“”"
# Get geometry
geom = row[geom_col]

# Check the geometry type
gtype = geom.geom_type

# "Normal" geometries
# -------------------

if gtype == "Point":
    return getPointCoords(geom, coord_type)
elif gtype == "LineString":
    return list( getLineCoords(geom, coord_type) )
elif gtype == "Polygon":
    return list( getPolyCoords(geom, coord_type) )
   
# Multi geometries
# ----------------

else:
    return list( multiGeomHandler(geom, coord_type, gtype) )

def getGeometryCoords(row, geom, coord_type, shape_type):

“”"

Returns the coordinates (‘x’ or ‘y’) of edges of a Polygon exterior.

:param: (GeoPandas Series) row : The row of each of the GeoPandas DataFrame.

:param: (str) geom : The column name.

:param: (str) coord_type : Whether it’s ‘x’ or ‘y’ coordinate.

:param: (str) shape_type

“”"

# Parse the exterior of the coordinate

if row[geom]

# exterior =

if coord_type == ‘x’:

# Get the x coordinates of the exterior

return list( exterior.coords.xy[0] )

elif coord_type == ‘y’:

# Get the y coordinates of the exterior

return list( exterior.coords.xy[1] )

# elif shape_type == 'point':
#     exterior = row[geom]

#     if coord_type == 'x':
#         # Get the x coordinates of the exterior
#         return  exterior.coords.xy[0][0]


#     elif coord_type == 'y':
#         # Get the y coordinates of the exterior
#         return  exterior.coords.xy[1][0]

Europe_Source = convert_GeoPandas_to_Bokeh_format(europe)
p = figure(title=“Europe”)
print(Europe_Source)
p.multi_line(‘x’, ‘y’, source=Europe_Source, color=“gray”, line_width=1)
show§

``

(This is evolving code, sorry it’s not pretty yet).

(np.nan was the original value, which would result in the error: ValueError: Out of range float values are not JSON compliant: nan.

When changing np.nan with the empty list I get the map with the awkward lines, as seen above).

Am I missing any detail?

On Tuesday, October 31, 2017 at 4:37:56 PM UTC, Bryan Van de ven wrote:

Hi,

Do you have a very old version of Bokeh? Bokeh has serialized NumPy arrays either using a Base64 encoding for standalone documents, or a pure binary transport or Bokeh server apps, for quite some time now. Alternatively, are you converting the arrays to plain python lists at some point before passing to Bokeh? More information is needed in order to help: system, library, browser versions, etc, and really, a complete example with instructions to run that could be investigated.

Thanks,

Bryan

On Oct 31, 2017, at 11:31, [email protected] wrote:

Dear Bokeh-ers,

Sorry if this is a silly question, but I’m trying to create some graphics which will include coloring a map of Europe, and I’m stuck on a seemingly basic problem.

I’m trying to use the GeoPandas naturalearth_lowres database of countries to extract the ones I’m interested in. The countries geometries are stored mostly as Polygon objects, but occasionally as MultiPolygons. Which is where my problem starts.

I’m trying to tease out the x and y coordinates in the way bokeh requires, but get stuck trying to extract them from MultiPolygons. I found the following helper function (from https://automating-gis-processes.github.io/Lesson5-interactive-map-Bokeh-advanced-plotting.html):

def multiGeomHandler(multi_geometry, coord_type, geom_type):

“”"
Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
Individual geometries are separated with np.nan which is how Bokeh wants them.
# Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)

# [https://github.com/bokeh/bokeh/issues/2321](https://github.com/bokeh/bokeh/issues/2321)
"""

for i, part in enumerate(multi_geometry):

On the first part of the Multi-geometry initialize the coord_array (np.array)

if i == 0:

if geom_type == “MultiPoint”:

coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)

elif geom_type == “MultiLineString”:

coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)

elif geom_type == “MultiPolygon”:

coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)

else:

if geom_type == “MultiPoint”:

coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])

elif geom_type == “MultiLineString”:

coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])

elif geom_type == “MultiPolygon”:

coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])

Return the coordinates

return coord_arrays

However, these np.nans, unlike what’s stated at the comment, are throwing errors: They are not recognized by JSON. I tried replacing them with None, other meaningless values (inf), empty lists or tuples, or just 0. This is the closest I got (when replacing np.nan with the empty list ):

Any advice on what I might be missing, or how to get rid of those superfluous lines? I can imagine why they appear, but can’t find a way to simply make it work.

Thanks in advance for any thought! I know it shouldn’t be this difficult.


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/cac321ef-8364-498c-8646-bc8a3a27fca5%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Hi,

The correct way to specify multi-polygons (e.g. a collection is islands that are all part of the same country), is with nan’s separating the sub-polygons in numpy arrays. To send multiple multi-polygons, put them in a list. Your code is too involved, I regret that I do not have the bandwidth to dig into it more. Hopefully a simple working example for comparison will help you diagnose where you are having a problem. This code (which uses “patches” but could also use “multi-line” if you added points to “close” the loops):

import numpy as np
from bokeh.io import output_file, show
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure

output_file('fig.html')

source = ColumnDataSource(data=dict(

[ first multi-plygon array , second multi-polygon array ]

    xs = [ np.array([1, 2, 2, 1, np.nan, 1, 2, 2, 1]), np.array([3, 4, 4, 3, np.nan, 3, 4, 4, 3]) ],
    ys = [ np.array([1, 1, 2, 2, np.nan, 3, 3, 4, 4]), np.array([1, 1, 2, 2, np.nan, 3, 3, 4, 4]) ],
    c =  ['firebrick', 'navy']
))

p = figure(x_range=(0,5), y_range=(0,5))
p.patches('xs', 'ys', color='c', source=source)

show(p)

produces the following expected output with two multi-polygons (shaded red and blue) each with two distinct sub polygons:

Thanks,

Bryan

···

On Nov 1, 2017, at 07:45, [email protected] wrote:

The problem seems to be the islands that certain European countries hold – seems like the way I’m currently converting the multiploygons (which happen because of these islands, which define the overall shape of the country as they appear in the geopandas dataset) results in all these separate territories being connected into one messy clump.

And original ideas on how to deal with this rather silly predicament?

On Tuesday, October 31, 2017 at 5:13:00 PM UTC, limor.g…@gmail.com wrote:
Thanks for the prompt reply Bryan!

Here’s the full spec plus my entire code.

system - OSX El Capitan, version 10.11.6, python 2.7.13, Bokeh 0.12.10 (just ran another pip install upgrade now). The browser is Chrome Version 61.0.3163.100 (Official Build) (64-bit).

import pandas as pd
import numpy as np
from bokeh.io import show
from bokeh.models import (
ColumnDataSource,
HoverTool,
LogColorMapper
)
import geopandas as gpd
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure
from bokeh.sampledata.us_counties import data as counties
from bokeh.sampledata.unemployment import data as unemployment

#obtain countries shapes
world = gpd.read_file(gpd.datasets.get_path(‘naturalearth_lowres’))

europe = (world.loc[world[‘continent’] == ‘Europe’])

def convert_GeoPandas_to_Bokeh_format(gdf):
“”"
Function to convert a GeoPandas GeoDataFrame to a Bokeh
ColumnDataSource object.

:param: (GeoDataFrame) gdf: GeoPandas GeoDataFrame with polygon(s) under
                            the column name 'geometry.'
                            
:return: ColumnDataSource for Bokeh.
"""
gdf_new = gdf.drop('geometry', axis=1).copy()
gdf_new['x'] = gdf.apply(getCoords, 
                         geom_col='geometry', 
                         coord_type='x', 
                         axis=1)

gdf_new['y'] = gdf.apply(getCoords, 
                         geom_col='geometry', 
                         coord_type='y', 
                         axis=1)

return ColumnDataSource(gdf_new)

def getXYCoords(geometry, coord_type):
“”" Returns either x or y coordinates from geometry coordinate sequence. Used with LineString and Polygon geometries."""
if coord_type == ‘x’:
return geometry.coords.xy[0]
elif coord_type == ‘y’:
return geometry.coords.xy[1]

def getPolyCoords(geometry, coord_type):
“”" Returns Coordinates of Polygon using the Exterior of the Polygon."""
ext = geometry.exterior
return getXYCoords(ext, coord_type)

def getLineCoords(geometry, coord_type):
“”" Returns Coordinates of Linestring object."""
return getXYCoords(geometry, coord_type)

def getPointCoords(geometry, coord_type):
“”" Returns Coordinates of Point object."""
if coord_type == ‘x’:
return geometry.x
elif coord_type == ‘y’:
return geometry.y

def multiGeomHandler(multi_geometry, coord_type, geom_type):
“”"
Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
Individual geometries are separated with np.nan which is how Bokeh wants them.
# Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
# https://github.com/bokeh/bokeh/issues/2321
“”"

for i, part in enumerate(multi_geometry):
    # On the first part of the Multi-geometry initialize the coord_array (np.array)
    if i == 0:
        if geom_type == "MultiPoint":
            coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)
        elif geom_type == "MultiLineString":
            coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)
        elif geom_type == "MultiPolygon":
            coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)
    else:
        if geom_type == "MultiPoint":
            coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type),np.nan)])
        elif geom_type == "MultiLineString":
            coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type),np.nan)])
        elif geom_type == "MultiPolygon":
            coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type),np.nan)])


# Return the coordinates 
return coord_arrays

def getCoords(row, geom_col, coord_type):
“”"
Returns coordinates (‘x’ or ‘y’) of a geometry (Point, LineString or Polygon) as a list (if geometry is LineString or Polygon).
Can handle also MultiGeometries.
“”"
# Get geometry
geom = row[geom_col]

# Check the geometry type
gtype = geom.geom_type

# "Normal" geometries
# -------------------

if gtype == "Point":
    return getPointCoords(geom, coord_type)
elif gtype == "LineString":
    return list( getLineCoords(geom, coord_type) )
elif gtype == "Polygon":
    return list( getPolyCoords(geom, coord_type) )
    
# Multi geometries
# ----------------

else:
    return list( multiGeomHandler(geom, coord_type, gtype) ) 

def getGeometryCoords(row, geom, coord_type, shape_type):

“”"

Returns the coordinates (‘x’ or ‘y’) of edges of a Polygon exterior.

:param: (GeoPandas Series) row : The row of each of the GeoPandas DataFrame.

:param: (str) geom : The column name.

:param: (str) coord_type : Whether it’s ‘x’ or ‘y’ coordinate.

:param: (str) shape_type

“”"

# Parse the exterior of the coordinate

if row[geom]

# exterior =

if coord_type == ‘x’:

# Get the x coordinates of the exterior

return list( exterior.coords.xy[0] )

elif coord_type == ‘y’:

# Get the y coordinates of the exterior

return list( exterior.coords.xy[1] )

# elif shape_type == 'point':
#     exterior = row[geom]

#     if coord_type == 'x':
#         # Get the x coordinates of the exterior
#         return  exterior.coords.xy[0][0] 


#     elif coord_type == 'y':
#         # Get the y coordinates of the exterior
#         return  exterior.coords.xy[1][0]

Europe_Source = convert_GeoPandas_to_Bokeh_format(europe)
p = figure(title=“Europe”)
print(Europe_Source)
p.multi_line(‘x’, ‘y’, source=Europe_Source, color=“gray”, line_width=1)
show§

(This is evolving code, sorry it’s not pretty yet).

(np.nan was the original value, which would result in the error: ValueError: Out of range float values are not JSON compliant: nan.
When changing np.nan with the empty list I get the map with the awkward lines, as seen above).

Am I missing any detail?

On Tuesday, October 31, 2017 at 4:37:56 PM UTC, Bryan Van de ven wrote:
Hi,

Do you have a very old version of Bokeh? Bokeh has serialized NumPy arrays either using a Base64 encoding for standalone documents, or a pure binary transport or Bokeh server apps, for quite some time now. Alternatively, are you converting the arrays to plain python lists at some point before passing to Bokeh? More information is needed in order to help: system, library, browser versions, etc, and really, a complete example with instructions to run that could be investigated.

Thanks,

Bryan

On Oct 31, 2017, at 11:31, [email protected] wrote:

Dear Bokeh-ers,

Sorry if this is a silly question, but I’m trying to create some graphics which will include coloring a map of Europe, and I’m stuck on a seemingly basic problem.

I’m trying to use the GeoPandas naturalearth_lowres database of countries to extract the ones I’m interested in. The countries geometries are stored mostly as Polygon objects, but occasionally as MultiPolygons. Which is where my problem starts.

I’m trying to tease out the x and y coordinates in the way bokeh requires, but get stuck trying to extract them from MultiPolygons. I found the following helper function (from https://automating-gis-processes.github.io/Lesson5-interactive-map-Bokeh-advanced-plotting.html):

def multiGeomHandler(multi_geometry, coord_type, geom_type):

“”"
Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
Individual geometries are separated with np.nan which is how Bokeh wants them.
# Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
# https://github.com/bokeh/bokeh/issues/2321
“”"

for i, part in enumerate(multi_geometry):

On the first part of the Multi-geometry initialize the coord_array (np.array)

if i == 0:

if geom_type == “MultiPoint”:

coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)

elif geom_type == “MultiLineString”:

coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)

elif geom_type == “MultiPolygon”:

coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)

else:

if geom_type == “MultiPoint”:

coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])

elif geom_type == “MultiLineString”:

coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])

elif geom_type == “MultiPolygon”:

coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])

Return the coordinates

return coord_arrays

However, these np.nans, unlike what’s stated at the comment, are throwing errors: They are not recognized by JSON. I tried replacing them with None, other meaningless values (inf), empty lists or tuples, or just 0. This is the closest I got (when replacing np.nan with the empty list ):

Any advice on what I might be missing, or how to get rid of those superfluous lines? I can imagine why they appear, but can’t find a way to simply make it work.

Thanks in advance for any thought! I know it shouldn’t be this difficult.


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/cac321ef-8364-498c-8646-bc8a3a27fca5%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/5897c708-8a3e-493d-8950-54a7645dfcf6%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Thanks Bryan! Indeed helpful

···

On Wednesday, November 1, 2017 at 1:56:36 PM UTC, Bryan Van de ven wrote:

Hi,

The correct way to specify multi-polygons (e.g. a collection is islands that are all part of the same country), is with nan’s separating the sub-polygons in numpy arrays. To send multiple multi-polygons, put them in a list. Your code is too involved, I regret that I do not have the bandwidth to dig into it more. Hopefully a simple working example for comparison will help you diagnose where you are having a problem. This code (which uses “patches” but could also use “multi-line” if you added points to “close” the loops):

import numpy as np
from bokeh.io import output_file, show
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure

output_file('fig.html')

source = ColumnDataSource(data=dict(

[ first multi-plygon array , second multi-polygon array ]

    xs = [ np.array([1, 2, 2, 1, np.nan, 1, 2, 2, 1]), np.array([3, 4, 4, 3, np.nan, 3, 4, 4, 3]) ],
    ys = [ np.array([1, 1, 2, 2, np.nan, 3, 3, 4, 4]), np.array([1, 1, 2, 2, np.nan, 3, 3, 4, 4]) ],
    c =  ['firebrick', 'navy']
))

p = figure(x_range=(0,5), y_range=(0,5))
p.patches('xs', 'ys', color='c', source=source)

show(p)

produces the following expected output with two multi-polygons (shaded red and blue) each with two distinct sub polygons:

Thanks,

Bryan

On Nov 1, 2017, at 07:45, [email protected] wrote:

The problem seems to be the islands that certain European countries hold – seems like the way I’m currently converting the multiploygons (which happen because of these islands, which define the overall shape of the country as they appear in the geopandas dataset) results in all these separate territories being connected into one messy clump.

And original ideas on how to deal with this rather silly predicament?

On Tuesday, October 31, 2017 at 5:13:00 PM UTC, limor.g…@gmail.com wrote:
Thanks for the prompt reply Bryan!

Here’s the full spec plus my entire code.

system - OSX El Capitan, version 10.11.6, python 2.7.13, Bokeh 0.12.10 (just ran another pip install upgrade now). The browser is Chrome Version 61.0.3163.100 (Official Build) (64-bit).

import pandas as pd
import numpy as np
from bokeh.io import show
from bokeh.models import (
ColumnDataSource,
HoverTool,
LogColorMapper
)
import geopandas as gpd
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure
from bokeh.sampledata.us_counties import data as counties
from bokeh.sampledata.unemployment import data as unemployment

#obtain countries shapes
world = gpd.read_file(gpd.datasets.get_path(‘naturalearth_lowres’))

europe = (world.loc[world[‘continent’] == ‘Europe’])

def convert_GeoPandas_to_Bokeh_format(gdf):
“”"
Function to convert a GeoPandas GeoDataFrame to a Bokeh
ColumnDataSource object.

:param: (GeoDataFrame) gdf: GeoPandas GeoDataFrame with polygon(s) under
                            the column name 'geometry.'
                            
:return: ColumnDataSource for Bokeh.
"""
gdf_new = gdf.drop('geometry', axis=1).copy()
gdf_new['x'] = gdf.apply(getCoords, 
                         geom_col='geometry', 
                         coord_type='x', 
                         axis=1)

gdf_new['y'] = gdf.apply(getCoords, 
                         geom_col='geometry', 
                         coord_type='y', 
                         axis=1)

return ColumnDataSource(gdf_new)

def getXYCoords(geometry, coord_type):
“”" Returns either x or y coordinates from geometry coordinate sequence. Used with LineString and Polygon geometries."""
if coord_type == ‘x’:
return geometry.coords.xy[0]
elif coord_type == ‘y’:
return geometry.coords.xy[1]

def getPolyCoords(geometry, coord_type):
“”" Returns Coordinates of Polygon using the Exterior of the Polygon."""
ext = geometry.exterior
return getXYCoords(ext, coord_type)

def getLineCoords(geometry, coord_type):
“”" Returns Coordinates of Linestring object."""
return getXYCoords(geometry, coord_type)

def getPointCoords(geometry, coord_type):
“”" Returns Coordinates of Point object."""
if coord_type == ‘x’:
return geometry.x
elif coord_type == ‘y’:
return geometry.y

def multiGeomHandler(multi_geometry, coord_type, geom_type):
“”"
Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
Individual geometries are separated with np.nan which is how Bokeh wants them.
# Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
# https://github.com/bokeh/bokeh/issues/2321
“”"

for i, part in enumerate(multi_geometry):
    # On the first part of the Multi-geometry initialize the coord_array (np.array)
    if i == 0:
        if geom_type == "MultiPoint":
            coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)
        elif geom_type == "MultiLineString":
            coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)
        elif geom_type == "MultiPolygon":
            coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)
    else:
        if geom_type == "MultiPoint":
            coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type),np.nan)])
        elif geom_type == "MultiLineString":
            coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type),np.nan)])
        elif geom_type == "MultiPolygon":
            coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type),np.nan)])


# Return the coordinates 
return coord_arrays

def getCoords(row, geom_col, coord_type):
“”"
Returns coordinates (‘x’ or ‘y’) of a geometry (Point, LineString or Polygon) as a list (if geometry is LineString or Polygon).
Can handle also MultiGeometries.
“”"
# Get geometry
geom = row[geom_col]

# Check the geometry type
gtype = geom.geom_type

# "Normal" geometries
# -------------------

if gtype == "Point":
    return getPointCoords(geom, coord_type)
elif gtype == "LineString":
    return list( getLineCoords(geom, coord_type) )
elif gtype == "Polygon":
    return list( getPolyCoords(geom, coord_type) )
    
# Multi geometries
# ----------------

else:
    return list( multiGeomHandler(geom, coord_type, gtype) ) 

def getGeometryCoords(row, geom, coord_type, shape_type):

“”"

Returns the coordinates (‘x’ or ‘y’) of edges of a Polygon exterior.

:param: (GeoPandas Series) row : The row of each of the GeoPandas DataFrame.

:param: (str) geom : The column name.

:param: (str) coord_type : Whether it’s ‘x’ or ‘y’ coordinate.

:param: (str) shape_type

“”"

# Parse the exterior of the coordinate

if row[geom]

# exterior =

if coord_type == ‘x’:

# Get the x coordinates of the exterior

return list( exterior.coords.xy[0] )

elif coord_type == ‘y’:

# Get the y coordinates of the exterior

return list( exterior.coords.xy[1] )

# elif shape_type == 'point':
#     exterior = row[geom]

#     if coord_type == 'x':
#         # Get the x coordinates of the exterior
#         return  exterior.coords.xy[0][0] 


#     elif coord_type == 'y':
#         # Get the y coordinates of the exterior
#         return  exterior.coords.xy[1][0]

Europe_Source = convert_GeoPandas_to_Bokeh_format(europe)
p = figure(title=“Europe”)
print(Europe_Source)
p.multi_line(‘x’, ‘y’, source=Europe_Source, color=“gray”, line_width=1)
show§

(This is evolving code, sorry it’s not pretty yet).

(np.nan was the original value, which would result in the error: ValueError: Out of range float values are not JSON compliant: nan.
When changing np.nan with the empty list I get the map with the awkward lines, as seen above).

Am I missing any detail?

On Tuesday, October 31, 2017 at 4:37:56 PM UTC, Bryan Van de ven wrote:
Hi,

Do you have a very old version of Bokeh? Bokeh has serialized NumPy arrays either using a Base64 encoding for standalone documents, or a pure binary transport or Bokeh server apps, for quite some time now. Alternatively, are you converting the arrays to plain python lists at some point before passing to Bokeh? More information is needed in order to help: system, library, browser versions, etc, and really, a complete example with instructions to run that could be investigated.

Thanks,

Bryan

On Oct 31, 2017, at 11:31, [email protected] wrote:

Dear Bokeh-ers,

Sorry if this is a silly question, but I’m trying to create some graphics which will include coloring a map of Europe, and I’m stuck on a seemingly basic problem.

I’m trying to use the GeoPandas naturalearth_lowres database of countries to extract the ones I’m interested in. The countries geometries are stored mostly as Polygon objects, but occasionally as MultiPolygons. Which is where my problem starts.

I’m trying to tease out the x and y coordinates in the way bokeh requires, but get stuck trying to extract them from MultiPolygons. I found the following helper function (from https://automating-gis-processes.github.io/Lesson5-interactive-map-Bokeh-advanced-plotting.html):

def multiGeomHandler(multi_geometry, coord_type, geom_type):

“”"
Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
Individual geometries are separated with np.nan which is how Bokeh wants them.
# Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
# https://github.com/bokeh/bokeh/issues/2321
“”"

for i, part in enumerate(multi_geometry):

On the first part of the Multi-geometry initialize the coord_array (np.array)

if i == 0:

if geom_type == “MultiPoint”:

coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)

elif geom_type == “MultiLineString”:

coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)

elif geom_type == “MultiPolygon”:

coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)

else:

if geom_type == “MultiPoint”:

coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])

elif geom_type == “MultiLineString”:

coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])

elif geom_type == “MultiPolygon”:

coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])

Return the coordinates

return coord_arrays

However, these np.nans, unlike what’s stated at the comment, are throwing errors: They are not recognized by JSON. I tried replacing them with None, other meaningless values (inf), empty lists or tuples, or just 0. This is the closest I got (when replacing np.nan with the empty list ):

Any advice on what I might be missing, or how to get rid of those superfluous lines? I can imagine why they appear, but can’t find a way to simply make it work.

Thanks in advance for any thought! I know it shouldn’t be this difficult.


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/cac321ef-8364-498c-8646-bc8a3a27fca5%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/5897c708-8a3e-493d-8950-54a7645dfcf6%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Hey,

You can significantly simply your code by using Bokeh’s “GeoJSONDataSource”. Not only will it save you from manually creating the coordinate lists, which is quite error prone as you’ve noticed. It will also be a nice separation between GIS tools and Bokeh (plotting) with geojson being a clean interface. This benefit will be obvious when for example you change GeoPandas for some other tool like OGR, Shapely, PostGIS etc. All serious GIS tools allow exporting to geojson, which then will plugin instantly into you Bokeh plotting code.

import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook
from bokeh.models import GeoJSONDataSource, LinearColorMapper
import geopandas as gpd
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure

output_notebook()

#obtain countries shapes
world = gpd.read_file(gpd.datasets.get_path(‘naturalearth_lowres’))
europe = (world.loc[world[‘continent’] == ‘Europe’])

geo_source = GeoJSONDataSource(geojson=europe.to_json())

p = figure(title=“Europe”, x_range=(-30,60), y_range=(30,85))

p.patches(‘xs’, ‘ys’, fill_alpha=0.7,
fill_color={‘field’: ‘gdp_md_est’, ‘transform’: LinearColorMapper(palette=palette)},
line_color=‘black’, line_width=0.5, source=geo_source)

show§

``

Regards,
Rutger

···

On Tuesday, October 31, 2017 at 5:31:30 PM UTC+1, [email protected] wrote:

Dear Bokeh-ers,

Sorry if this is a silly question, but I’m trying to create some graphics which will include coloring a map of Europe, and I’m stuck on a seemingly basic problem.

I’m trying to use the GeoPandas naturalearth_lowres database of countries to extract the ones I’m interested in. The countries geometries are stored mostly as Polygon objects, but occasionally as MultiPolygons. Which is where my problem starts.

I’m trying to tease out the x and y coordinates in the way bokeh requires, but get stuck trying to extract them from MultiPolygons. I found the following helper function (from https://automating-gis-processes.github.io/Lesson5-interactive-map-Bokeh-advanced-plotting.html):

def multiGeomHandler(multi_geometry, coord_type, geom_type):
    """
    Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
    Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
    Individual geometries are separated with np.nan which is how Bokeh wants them.
    # Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
    # [https://github.com/bokeh/bokeh/issues/2321](https://github.com/bokeh/bokeh/issues/2321)
    """

    for i, part in enumerate(multi_geometry):
        # On the first part of the Multi-geometry initialize the coord_array (np.array)
        if i == 0:
            if geom_type == "MultiPoint":
                coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)
            elif geom_type == "MultiLineString":
                coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)
            elif geom_type == "MultiPolygon":
                coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)
        else:
            if geom_type == "MultiPoint":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiLineString":
                coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiPolygon":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])

    # Return the coordinates
    return coord_arrays


However, these np.nans, unlike what’s stated at the comment, are throwing errors: They are not recognized by JSON. I tried replacing them with None, other meaningless values (inf), empty lists or tuples, or just 0. This is the closest I got (when replacing np.nan with the empty list ):

Any advice on what I might be missing, or how to get rid of those superfluous lines? I can imagine why they appear, but can’t find a way to simply make it work.

Thanks in advance for any thought! I know it shouldn’t be this difficult.

This is wonderful! Really helpful tip.

I do have a follow up question though – I want to merge the europe dataset (which I need only for the geometry) with my own dataset, which contains a column with other features (I want to color by a different value than GDP). However, when I merge them and try to apply to_json to the new df, I get an overflow: Maximum recursion level reached (though the merged df is only 25-by-3).

I know I might be pushing my luck here, but any idea on how to effectively merge data frames to create a single usable DataSource?

Sorry for being lost here. This should be simple, but I’m constantly hitting walls.

···

On Thursday, November 2, 2017 at 8:40:01 AM UTC, Rutger Kassies wrote:

Hey,

You can significantly simply your code by using Bokeh’s “GeoJSONDataSource”. Not only will it save you from manually creating the coordinate lists, which is quite error prone as you’ve noticed. It will also be a nice separation between GIS tools and Bokeh (plotting) with geojson being a clean interface. This benefit will be obvious when for example you change GeoPandas for some other tool like OGR, Shapely, PostGIS etc. All serious GIS tools allow exporting to geojson, which then will plugin instantly into you Bokeh plotting code.

import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook
from bokeh.models import GeoJSONDataSource, LinearColorMapper
import geopandas as gpd
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure

output_notebook()

#obtain countries shapes
world = gpd.read_file(gpd.datasets.get_path(‘naturalearth_lowres’))
europe = (world.loc[world[‘continent’] == ‘Europe’])

geo_source = GeoJSONDataSource(geojson=europe.to_json())

p = figure(title=“Europe”, x_range=(-30,60), y_range=(30,85))

p.patches(‘xs’, ‘ys’, fill_alpha=0.7,
fill_color={‘field’: ‘gdp_md_est’, ‘transform’: LinearColorMapper(palette=palette)},
line_color=‘black’, line_width=0.5, source=geo_source)

show§

``

Regards,
Rutger

On Tuesday, October 31, 2017 at 5:31:30 PM UTC+1, [email protected] wrote:

Dear Bokeh-ers,

Sorry if this is a silly question, but I’m trying to create some graphics which will include coloring a map of Europe, and I’m stuck on a seemingly basic problem.

I’m trying to use the GeoPandas naturalearth_lowres database of countries to extract the ones I’m interested in. The countries geometries are stored mostly as Polygon objects, but occasionally as MultiPolygons. Which is where my problem starts.

I’m trying to tease out the x and y coordinates in the way bokeh requires, but get stuck trying to extract them from MultiPolygons. I found the following helper function (from https://automating-gis-processes.github.io/Lesson5-interactive-map-Bokeh-advanced-plotting.html):

def multiGeomHandler(multi_geometry, coord_type, geom_type):
    """
    Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
    Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
    Individual geometries are separated with np.nan which is how Bokeh wants them.
    # Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
    # [https://github.com/bokeh/bokeh/issues/2321](https://github.com/bokeh/bokeh/issues/2321)
    """

    for i, part in enumerate(multi_geometry):
        # On the first part of the Multi-geometry initialize the coord_array (np.array)
        if i == 0:
            if geom_type == "MultiPoint":
                coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)
            elif geom_type == "MultiLineString":
                coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)
            elif geom_type == "MultiPolygon":
                coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)
        else:
            if geom_type == "MultiPoint":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiLineString":
                coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiPolygon":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])

    # Return the coordinates
    return coord_arrays


However, these np.nans, unlike what’s stated at the comment, are throwing errors: They are not recognized by JSON. I tried replacing them with None, other meaningless values (inf), empty lists or tuples, or just 0. This is the closest I got (when replacing np.nan with the empty list ):

Any advice on what I might be missing, or how to get rid of those superfluous lines? I can imagine why they appear, but can’t find a way to simply make it work.

Thanks in advance for any thought! I know it shouldn’t be this difficult.

Otherwise, maybe there’s a way to use multiple sources in the .patches function? Could that be a feature request?

···

On Thursday, November 2, 2017 at 10:42:08 AM UTC, [email protected] wrote:

This is wonderful! Really helpful tip.

I do have a follow up question though – I want to merge the europe dataset (which I need only for the geometry) with my own dataset, which contains a column with other features (I want to color by a different value than GDP). However, when I merge them and try to apply to_json to the new df, I get an overflow: Maximum recursion level reached (though the merged df is only 25-by-3).

I know I might be pushing my luck here, but any idea on how to effectively merge data frames to create a single usable DataSource?

Sorry for being lost here. This should be simple, but I’m constantly hitting walls.

On Thursday, November 2, 2017 at 8:40:01 AM UTC, Rutger Kassies wrote:

Hey,

You can significantly simply your code by using Bokeh’s “GeoJSONDataSource”. Not only will it save you from manually creating the coordinate lists, which is quite error prone as you’ve noticed. It will also be a nice separation between GIS tools and Bokeh (plotting) with geojson being a clean interface. This benefit will be obvious when for example you change GeoPandas for some other tool like OGR, Shapely, PostGIS etc. All serious GIS tools allow exporting to geojson, which then will plugin instantly into you Bokeh plotting code.

import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook
from bokeh.models import GeoJSONDataSource, LinearColorMapper
import geopandas as gpd
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure

output_notebook()

#obtain countries shapes
world = gpd.read_file(gpd.datasets.get_path(‘naturalearth_lowres’))
europe = (world.loc[world[‘continent’] == ‘Europe’])

geo_source = GeoJSONDataSource(geojson=europe.to_json())

p = figure(title=“Europe”, x_range=(-30,60), y_range=(30,85))

p.patches(‘xs’, ‘ys’, fill_alpha=0.7,
fill_color={‘field’: ‘gdp_md_est’, ‘transform’: LinearColorMapper(palette=palette)},
line_color=‘black’, line_width=0.5, source=geo_source)

show§

``

Regards,
Rutger

On Tuesday, October 31, 2017 at 5:31:30 PM UTC+1, [email protected] wrote:

Dear Bokeh-ers,

Sorry if this is a silly question, but I’m trying to create some graphics which will include coloring a map of Europe, and I’m stuck on a seemingly basic problem.

I’m trying to use the GeoPandas naturalearth_lowres database of countries to extract the ones I’m interested in. The countries geometries are stored mostly as Polygon objects, but occasionally as MultiPolygons. Which is where my problem starts.

I’m trying to tease out the x and y coordinates in the way bokeh requires, but get stuck trying to extract them from MultiPolygons. I found the following helper function (from https://automating-gis-processes.github.io/Lesson5-interactive-map-Bokeh-advanced-plotting.html):

def multiGeomHandler(multi_geometry, coord_type, geom_type):
    """
    Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
    Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
    Individual geometries are separated with np.nan which is how Bokeh wants them.
    # Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
    # [https://github.com/bokeh/bokeh/issues/2321](https://github.com/bokeh/bokeh/issues/2321)
    """

    for i, part in enumerate(multi_geometry):
        # On the first part of the Multi-geometry initialize the coord_array (np.array)
        if i == 0:
            if geom_type == "MultiPoint":
                coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)
            elif geom_type == "MultiLineString":
                coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)
            elif geom_type == "MultiPolygon":
                coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)
        else:
            if geom_type == "MultiPoint":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiLineString":
                coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiPolygon":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])

    # Return the coordinates
    return coord_arrays


However, these np.nans, unlike what’s stated at the comment, are throwing errors: They are not recognized by JSON. I tried replacing them with None, other meaningless values (inf), empty lists or tuples, or just 0. This is the closest I got (when replacing np.nan with the empty list ):

Any advice on what I might be missing, or how to get rid of those superfluous lines? I can imagine why they appear, but can’t find a way to simply make it work.

Thanks in advance for any thought! I know it shouldn’t be this difficult.

Never mind – found a work around!

Thanks so much for all the help and support, couldn’t have done it without you :slight_smile:

···

On Thursday, November 2, 2017 at 10:43:23 AM UTC, [email protected] wrote:

Otherwise, maybe there’s a way to use multiple sources in the .patches function? Could that be a feature request?

On Thursday, November 2, 2017 at 10:42:08 AM UTC, [email protected] wrote:

This is wonderful! Really helpful tip.

I do have a follow up question though – I want to merge the europe dataset (which I need only for the geometry) with my own dataset, which contains a column with other features (I want to color by a different value than GDP). However, when I merge them and try to apply to_json to the new df, I get an overflow: Maximum recursion level reached (though the merged df is only 25-by-3).

I know I might be pushing my luck here, but any idea on how to effectively merge data frames to create a single usable DataSource?

Sorry for being lost here. This should be simple, but I’m constantly hitting walls.

On Thursday, November 2, 2017 at 8:40:01 AM UTC, Rutger Kassies wrote:

Hey,

You can significantly simply your code by using Bokeh’s “GeoJSONDataSource”. Not only will it save you from manually creating the coordinate lists, which is quite error prone as you’ve noticed. It will also be a nice separation between GIS tools and Bokeh (plotting) with geojson being a clean interface. This benefit will be obvious when for example you change GeoPandas for some other tool like OGR, Shapely, PostGIS etc. All serious GIS tools allow exporting to geojson, which then will plugin instantly into you Bokeh plotting code.

import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook
from bokeh.models import GeoJSONDataSource, LinearColorMapper
import geopandas as gpd
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure

output_notebook()

#obtain countries shapes
world = gpd.read_file(gpd.datasets.get_path(‘naturalearth_lowres’))
europe = (world.loc[world[‘continent’] == ‘Europe’])

geo_source = GeoJSONDataSource(geojson=europe.to_json())

p = figure(title=“Europe”, x_range=(-30,60), y_range=(30,85))

p.patches(‘xs’, ‘ys’, fill_alpha=0.7,
fill_color={‘field’: ‘gdp_md_est’, ‘transform’: LinearColorMapper(palette=palette)},
line_color=‘black’, line_width=0.5, source=geo_source)

show§

``

Regards,
Rutger

On Tuesday, October 31, 2017 at 5:31:30 PM UTC+1, [email protected] wrote:

Dear Bokeh-ers,

Sorry if this is a silly question, but I’m trying to create some graphics which will include coloring a map of Europe, and I’m stuck on a seemingly basic problem.

I’m trying to use the GeoPandas naturalearth_lowres database of countries to extract the ones I’m interested in. The countries geometries are stored mostly as Polygon objects, but occasionally as MultiPolygons. Which is where my problem starts.

I’m trying to tease out the x and y coordinates in the way bokeh requires, but get stuck trying to extract them from MultiPolygons. I found the following helper function (from https://automating-gis-processes.github.io/Lesson5-interactive-map-Bokeh-advanced-plotting.html):

def multiGeomHandler(multi_geometry, coord_type, geom_type):
    """
    Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
    Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
    Individual geometries are separated with np.nan which is how Bokeh wants them.
    # Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
    # [https://github.com/bokeh/bokeh/issues/2321](https://github.com/bokeh/bokeh/issues/2321)
    """

    for i, part in enumerate(multi_geometry):
        # On the first part of the Multi-geometry initialize the coord_array (np.array)
        if i == 0:
            if geom_type == "MultiPoint":
                coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)
            elif geom_type == "MultiLineString":
                coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)
            elif geom_type == "MultiPolygon":
                coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)
        else:
            if geom_type == "MultiPoint":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiLineString":
                coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiPolygon":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])

    # Return the coordinates
    return coord_arrays


However, these np.nans, unlike what’s stated at the comment, are throwing errors: They are not recognized by JSON. I tried replacing them with None, other meaningless values (inf), empty lists or tuples, or just 0. This is the closest I got (when replacing np.nan with the empty list ):

Any advice on what I might be missing, or how to get rid of those superfluous lines? I can imagine why they appear, but can’t find a way to simply make it work.

Thanks in advance for any thought! I know it shouldn’t be this difficult.

Hey,

I can easily merge the GeoPandas DataFrame with for example a normal DataFrame (non-geo). If in the example i posted i insert these lines (and change the column name in the colormapper), the countries get colored by the length of their name:

Normal Dataframe (non-geo)

df = pd.DataFrame(data=europe.name.str.len().values, index=europe.index, columns=[‘name_length’])

europe = europe.merge(df, left_index=True, right_index=True)

``

I’m merging on the index, but Pandas also allow to merge on columns etc.

Regards,
Rutger

···

On Thursday, November 2, 2017 at 11:42:08 AM UTC+1, [email protected] wrote:

This is wonderful! Really helpful tip.

I do have a follow up question though – I want to merge the europe dataset (which I need only for the geometry) with my own dataset, which contains a column with other features (I want to color by a different value than GDP). However, when I merge them and try to apply to_json to the new df, I get an overflow: Maximum recursion level reached (though the merged df is only 25-by-3).

I know I might be pushing my luck here, but any idea on how to effectively merge data frames to create a single usable DataSource?

Sorry for being lost here. This should be simple, but I’m constantly hitting walls.

On Thursday, November 2, 2017 at 8:40:01 AM UTC, Rutger Kassies wrote:

Hey,

You can significantly simply your code by using Bokeh’s “GeoJSONDataSource”. Not only will it save you from manually creating the coordinate lists, which is quite error prone as you’ve noticed. It will also be a nice separation between GIS tools and Bokeh (plotting) with geojson being a clean interface. This benefit will be obvious when for example you change GeoPandas for some other tool like OGR, Shapely, PostGIS etc. All serious GIS tools allow exporting to geojson, which then will plugin instantly into you Bokeh plotting code.

import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook
from bokeh.models import GeoJSONDataSource, LinearColorMapper
import geopandas as gpd
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure

output_notebook()

#obtain countries shapes
world = gpd.read_file(gpd.datasets.get_path(‘naturalearth_lowres’))
europe = (world.loc[world[‘continent’] == ‘Europe’])

geo_source = GeoJSONDataSource(geojson=europe.to_json())

p = figure(title=“Europe”, x_range=(-30,60), y_range=(30,85))

p.patches(‘xs’, ‘ys’, fill_alpha=0.7,
fill_color={‘field’: ‘gdp_md_est’, ‘transform’: LinearColorMapper(palette=palette)},
line_color=‘black’, line_width=0.5, source=geo_source)

show§

``

Regards,
Rutger

On Tuesday, October 31, 2017 at 5:31:30 PM UTC+1, [email protected] wrote:

Dear Bokeh-ers,

Sorry if this is a silly question, but I’m trying to create some graphics which will include coloring a map of Europe, and I’m stuck on a seemingly basic problem.

I’m trying to use the GeoPandas naturalearth_lowres database of countries to extract the ones I’m interested in. The countries geometries are stored mostly as Polygon objects, but occasionally as MultiPolygons. Which is where my problem starts.

I’m trying to tease out the x and y coordinates in the way bokeh requires, but get stuck trying to extract them from MultiPolygons. I found the following helper function (from https://automating-gis-processes.github.io/Lesson5-interactive-map-Bokeh-advanced-plotting.html):

def multiGeomHandler(multi_geometry, coord_type, geom_type):
    """
    Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
    Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
    Individual geometries are separated with np.nan which is how Bokeh wants them.
    # Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
    # [https://github.com/bokeh/bokeh/issues/2321](https://github.com/bokeh/bokeh/issues/2321)
    """

    for i, part in enumerate(multi_geometry):
        # On the first part of the Multi-geometry initialize the coord_array (np.array)
        if i == 0:
            if geom_type == "MultiPoint":
                coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)
            elif geom_type == "MultiLineString":
                coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)
            elif geom_type == "MultiPolygon":
                coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)
        else:
            if geom_type == "MultiPoint":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiLineString":
                coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiPolygon":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])

    # Return the coordinates
    return coord_arrays


However, these np.nans, unlike what’s stated at the comment, are throwing errors: They are not recognized by JSON. I tried replacing them with None, other meaningless values (inf), empty lists or tuples, or just 0. This is the closest I got (when replacing np.nan with the empty list ):

Any advice on what I might be missing, or how to get rid of those superfluous lines? I can imagine why they appear, but can’t find a way to simply make it work.

Thanks in advance for any thought! I know it shouldn’t be this difficult.

Thanks for this example. I was also looking for a way to convert a geopandas dataframe (also with multi-polygons) into a bokeh data source. It works indeed with GeoJSONDataSource. Is there a way to use widgets via CustomJS (no Bokeh server) on the data then? With columndatasource there is the source.data, but according to the documentation GeoJSONDataSource doesn’t have an attribute ‘data’. I would like to filter the data with sliders, select etc…

···

On Thursday, November 2, 2017 at 9:40:01 AM UTC+1, Rutger Kassies wrote:

Hey,

You can significantly simply your code by using Bokeh’s “GeoJSONDataSource”. Not only will it save you from manually creating the coordinate lists, which is quite error prone as you’ve noticed. It will also be a nice separation between GIS tools and Bokeh (plotting) with geojson being a clean interface. This benefit will be obvious when for example you change GeoPandas for some other tool like OGR, Shapely, PostGIS etc. All serious GIS tools allow exporting to geojson, which then will plugin instantly into you Bokeh plotting code.

import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook
from bokeh.models import GeoJSONDataSource, LinearColorMapper
import geopandas as gpd
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure

output_notebook()

#obtain countries shapes
world = gpd.read_file(gpd.datasets.get_path(‘naturalearth_lowres’))
europe = (world.loc[world[‘continent’] == ‘Europe’])

geo_source = GeoJSONDataSource(geojson=europe.to_json())

p = figure(title=“Europe”, x_range=(-30,60), y_range=(30,85))

p.patches(‘xs’, ‘ys’, fill_alpha=0.7,
fill_color={‘field’: ‘gdp_md_est’, ‘transform’: LinearColorMapper(palette=palette)},
line_color=‘black’, line_width=0.5, source=geo_source)

show§

``

Regards,
Rutger

On Tuesday, October 31, 2017 at 5:31:30 PM UTC+1, [email protected] wrote:

Dear Bokeh-ers,

Sorry if this is a silly question, but I’m trying to create some graphics which will include coloring a map of Europe, and I’m stuck on a seemingly basic problem.

I’m trying to use the GeoPandas naturalearth_lowres database of countries to extract the ones I’m interested in. The countries geometries are stored mostly as Polygon objects, but occasionally as MultiPolygons. Which is where my problem starts.

I’m trying to tease out the x and y coordinates in the way bokeh requires, but get stuck trying to extract them from MultiPolygons. I found the following helper function (from https://automating-gis-processes.github.io/Lesson5-interactive-map-Bokeh-advanced-plotting.html):

def multiGeomHandler(multi_geometry, coord_type, geom_type):
    """
    Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
    Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
    Individual geometries are separated with np.nan which is how Bokeh wants them.
    # Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
    # [https://github.com/bokeh/bokeh/issues/2321](https://github.com/bokeh/bokeh/issues/2321)
    """

    for i, part in enumerate(multi_geometry):
        # On the first part of the Multi-geometry initialize the coord_array (np.array)
        if i == 0:
            if geom_type == "MultiPoint":
                coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)
            elif geom_type == "MultiLineString":
                coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)
            elif geom_type == "MultiPolygon":
                coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)
        else:
            if geom_type == "MultiPoint":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiLineString":
                coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiPolygon":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])

    # Return the coordinates
    return coord_arrays


However, these np.nans, unlike what’s stated at the comment, are throwing errors: They are not recognized by JSON. I tried replacing them with None, other meaningless values (inf), empty lists or tuples, or just 0. This is the closest I got (when replacing np.nan with the empty list ):

Any advice on what I might be missing, or how to get rid of those superfluous lines? I can imagine why they appear, but can’t find a way to simply make it work.

Thanks in advance for any thought! I know it shouldn’t be this difficult.

You can write a custom extension to get the data attribute of the GeoJSONDataSource. It already exists, it’s just a “hidden” attribute. I showed here how to do that:

But it is outdated as I think they rewrote all the coffeescript codes since 0.12.10

The new link for the source code of geojson data sources is here https://github.com/bokeh/bokeh/blob/master/bokehjs/src/coffee/models/sources/geojson_data_source.ts

And you should only need to move the data attribute from this.internal to this.define

···

Le mercredi 21 février 2018 14:27:44 UTC-5, [email protected] a écrit :

Thanks for this example. I was also looking for a way to convert a geopandas dataframe (also with multi-polygons) into a bokeh data source. It works indeed with GeoJSONDataSource. Is there a way to use widgets via CustomJS (no Bokeh server) on the data then? With columndatasource there is the source.data, but according to the documentation GeoJSONDataSource doesn’t have an attribute ‘data’. I would like to filter the data with sliders, select etc…

On Thursday, November 2, 2017 at 9:40:01 AM UTC+1, Rutger Kassies wrote:

Hey,

You can significantly simply your code by using Bokeh’s “GeoJSONDataSource”. Not only will it save you from manually creating the coordinate lists, which is quite error prone as you’ve noticed. It will also be a nice separation between GIS tools and Bokeh (plotting) with geojson being a clean interface. This benefit will be obvious when for example you change GeoPandas for some other tool like OGR, Shapely, PostGIS etc. All serious GIS tools allow exporting to geojson, which then will plugin instantly into you Bokeh plotting code.

import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook
from bokeh.models import GeoJSONDataSource, LinearColorMapper
import geopandas as gpd
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure

output_notebook()

#obtain countries shapes
world = gpd.read_file(gpd.datasets.get_path(‘naturalearth_lowres’))
europe = (world.loc[world[‘continent’] == ‘Europe’])

geo_source = GeoJSONDataSource(geojson=europe.to_json())

p = figure(title=“Europe”, x_range=(-30,60), y_range=(30,85))

p.patches(‘xs’, ‘ys’, fill_alpha=0.7,
fill_color={‘field’: ‘gdp_md_est’, ‘transform’: LinearColorMapper(palette=palette)},
line_color=‘black’, line_width=0.5, source=geo_source)

show§

``

Regards,
Rutger

On Tuesday, October 31, 2017 at 5:31:30 PM UTC+1, [email protected] wrote:

Dear Bokeh-ers,

Sorry if this is a silly question, but I’m trying to create some graphics which will include coloring a map of Europe, and I’m stuck on a seemingly basic problem.

I’m trying to use the GeoPandas naturalearth_lowres database of countries to extract the ones I’m interested in. The countries geometries are stored mostly as Polygon objects, but occasionally as MultiPolygons. Which is where my problem starts.

I’m trying to tease out the x and y coordinates in the way bokeh requires, but get stuck trying to extract them from MultiPolygons. I found the following helper function (from https://automating-gis-processes.github.io/Lesson5-interactive-map-Bokeh-advanced-plotting.html):

def multiGeomHandler(multi_geometry, coord_type, geom_type):
    """
    Function for handling multi-geometries. Can be MultiPoint, MultiLineString or MultiPolygon.
    Returns a list of coordinates where all parts of Multi-geometries are merged into a single list.
    Individual geometries are separated with np.nan which is how Bokeh wants them.
    # Bokeh documentation regarding the Multi-geometry issues can be found here (it is an open issue)
    # [https://github.com/bokeh/bokeh/issues/2321](https://github.com/bokeh/bokeh/issues/2321)
    """

    for i, part in enumerate(multi_geometry):
        # On the first part of the Multi-geometry initialize the coord_array (np.array)
        if i == 0:
            if geom_type == "MultiPoint":
                coord_arrays = np.append(getPointCoords(part, coord_type), np.nan)
            elif geom_type == "MultiLineString":
                coord_arrays = np.append(getLineCoords(part, coord_type), np.nan)
            elif geom_type == "MultiPolygon":
                coord_arrays = np.append(getPolyCoords(part, coord_type), np.nan)
        else:
            if geom_type == "MultiPoint":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPointCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiLineString":
                coord_arrays = np.concatenate([coord_arrays, np.append(getLineCoords(part, coord_type), np.nan)])
            elif geom_type == "MultiPolygon":
                coord_arrays = np.concatenate([coord_arrays, np.append(getPolyCoords(part, coord_type), np.nan)])

    # Return the coordinates
    return coord_arrays


However, these np.nans, unlike what’s stated at the comment, are throwing errors: They are not recognized by JSON. I tried replacing them with None, other meaningless values (inf), empty lists or tuples, or just 0. This is the closest I got (when replacing np.nan with the empty list ):

Any advice on what I might be missing, or how to get rid of those superfluous lines? I can imagine why they appear, but can’t find a way to simply make it work.

Thanks in advance for any thought! I know it shouldn’t be this difficult.