Plotly is not rendering Choropleth Mapbox Polygons - python

I have been trying to render geoJSON in Plotly by converting shapefiles from https://geoportal.statistics.gov.uk/datasets/local-authority-districts-december-2019-boundaries-uk-bfc.
The Python Plotly docs for plotly.graph_objects.Choroplethmapbox mention that in the geoJSON an id field is required for each feature. I have tried both creating an artificial id and using the plotly featurekeyid field but neither of them are working. When I do use the id key, I have checked that the id key is in the correct location and have tried both as int64 and string.
Sometimes the base mapbox layer will render but no polygons and others the code will run and then hang.
I have also tried reducing the size of the .shp file using mapshaper's various algorithms then saving that to geoJSON format and skipping the conversion step in Python from .shp to geoJSON but again to no avail. Also changing the tolerance in the shapely manipulation does not seem to change the output.
What I am expecting is a map projection with a mapbox base layer with the local authority district polygons on top and filled. The below link shows the polygons and was created on mapshaper.org:
Polygons of Local Authority District
My mapbox access token is valid.
This is an example of trying to render the Local Authority Boundaries polygons by adding in an id field and converting the .shp file to geoJSON and then creating the trace:
import geopandas as gpd
from shapely.geometry import LineString, MultiLineString
import plotly.graph_objs as go
# load in shp files
lad_shp = gpd.read_file('zip://../../data/external/Local_Authority_Districts_(December_2019)_Boundaries_UK_BFC-shp.zip', encoding='utf-8')
# using empet code to convert .shp to geoJSON
def shapefile_to_geojson(gdf, index_list, tolerance=0.025):
# gdf - geopandas dataframe containing the geometry column and values to be mapped to a colorscale
# index_list - a sublist of list(gdf.index) or gdf.index for all data
# tolerance - float parameter to set the Polygon/MultiPolygon degree of simplification
# returns a geojson type dict
geo_names = list(gdf[f'lad19nm']) # name of authorities
geojson = {'type': 'FeatureCollection', 'features': []}
for index in index_list:
geo = gdf['geometry'][index].simplify(tolerance)
if isinstance(geo.boundary, LineString):
gtype = 'Polygon'
bcoords = np.dstack(geo.boundary.coords.xy).tolist()
elif isinstance(geo.boundary, MultiLineString):
gtype = 'MultiPolygon'
bcoords = []
for b in geo.boundary:
x, y = b.coords.xy
coords = np.dstack((x,y)).tolist()
bcoords.append(coords)
else: pass
feature = {'type': 'Feature',
'id' : index,
'properties': {'name': geo_names[index]},
'geometry': {'type': gtype,
'coordinates': bcoords},
}
geojson['features'].append(feature)
return geojson
geojsdata = shapefile_to_geojson(lad_shp, list(lad_shp.index))
# length to generate synthetic data for z attribute
L = len(geojsdata['features'])
# check id key is there
geojsdata['features'][0].keys()
>> dict_keys(['type', 'id', 'properties', 'geometry'])
# example of authroity name
geojsdata['features'][0]['properties']['name']
>> 'Hartlepool'
# check id
k=5
geojsdata['features'][k]['id']
>> '5'
trace = go.Choroplethmapbox(z=np.random.randint(10, 75, size=L), # synthetic data
locations=[geojsdata['features'][k]['id'] for k in range(L)],
colorscale='Viridis',
colorbar=dict(thickness=20, ticklen=3),
geojson=geojsdata,
text=regions,
marker_line_width=0.1, marker_opacity=0.7)
layout = go.Layout(title_text='UK LAD Choropleth Demo',
title_x=0.5,
width=750,
height=700,
mapbox=dict(center=dict(lat=54, lon=-2),
accesstoken=mapbox_access_token,
zoom=3))
fig=go.Figure(data=[trace], layout =layout)
fig.show()
The geoJSON output from the above shapefile_to_geojson function can be found here: https://www.dropbox.com/s/vuf3jtrr2boq5eg/lad19-geo.json?dl=0
Does anyone have any idea what could be causing the issue? I'm assuming the .shp files are good as they are rendered fine on mapshaper.org and QGis. Any help would be greatly appreciated.
Thank you.

Simply changing the projection system corrected the error. Doing this before conversion to geoJSON:
lad_shp = lad_shp.to_crs(epsg=4326)

Related

Loop over geoJson features and add plotly graph in popup html string to each

I've got a geoJson file with a bunch of features which I'm trying to display on an interactive folium map and I'm trying to add a plotly graph that pops up when you click on a polygon. At this moment I already have a folder with all plotly graphs for each city, 'currentWorkingDirectory/graphs/CityName.HTML'. I also have the interactive folium map with the different polygons which I can hover over or click for a popup.
Now I'm having trouble with adding the plotly graphs as a html string to the geojson popups. Could someone help me with this? I'll add a code snippet of the folium map and what I've tried:
import folium
import geopandas as gpd
import codecs
map = folium.Map(location=['51.096246199999996', '4.178629103169916'], tiles="cartodbpositron", zoom_start=9)
geojson_file_df = gpd.read_file('Refgem_geojson.json')
loc = 'Project GEO ICT'
title_html = '''
<h3 align="center" style="font-size:20px"><b>{}</b></h3>
'''.format(loc)
map.get_root().html.add_child(folium.Element(title_html))
g_map = folium.GeoJson(
geojson_file,
name="GeoJson",
style_function=lambda x: {'fillColor': 'orange'}
).add_to(map)
folium.GeoJsonTooltip(
fields=['NISCODE','NAAM', 'OPPERVL'],
aliases=['NISCODE', 'Naam', 'Oppervlakte'],
sticky=False
).add_to(g_map)
folium.GeoJsonPopup(
fields=["NAAM", "Average Prices: " ,"Woonhuis", "Villa", "Studio"],
aliases=["Naam", "Average Prices: ","Woonhuis", "Villa", "Studio"]
).add_to(g_map)
html="""
<iframe src=\"""" + codecs.open("graphs/AARTSELAAR.html", 'r').read() + """\" width="850" height="400" frameborder="0">
"""
popup1 = folium.Popup(folium.Html(html, script=True))
folium.Marker(['51.096246199999996','4.178629103169916'],popup=popup1,icon=folium.Icon( icon='home', prefix='fa')).add_to(map)
map
Here ^ I tried to add the popup to a marker, but that didn't work for me (it's also not really what I want, I want to add the popup to a polygon).
I believe I should make some sort of loop that iterates over all features in the geoJson and adds a popup for every iteration.
You have not provided sample data / geometry so used standard geopandas sample data
this will create popups / tooltips for each geometry. The popup is a plotly figure convented to an embedded URI encoded image. A pie chart of population of country as %age of population of all geometries.
investigated customising GeoJsonPopup() but found no solution
hence create a layer for each feature with it's own popup
import geopandas as gpd
import folium
from statistics import mean
import plotly.express as px
import base64, io
# some geometry
gdf = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres")).loc[
lambda d: d["continent"].eq("Europe") & ~d.bounds.lt(-30).any(axis=1)
]
# create the map, nicely centered and zoomed
bounds = gdf.total_bounds
x = mean([bounds[0], bounds[2]])
y = mean([bounds[1], bounds[3]])
location = (y, x)
m = folium.Map(location=location)
m.fit_bounds([[bounds[1], bounds[0]], [bounds[3], bounds[2]]])
# given need to create a geojson layer for each figure, create
# feature group to contain them
fg = folium.FeatureGroup(name="Europe", show=False)
# create an encocded image of graph...
# change to generate graph you want
def b64image(vals=[1, 2]):
fig = (
px.pie(values=vals)
.update_layout(margin={"l": 0, "r": 0, "t": 0, "b": 0})
.update_traces(texttemplate="%{percent:.0%}")
)
b = io.BytesIO(fig.to_image(format="png", width=80, height=80))
b64 = base64.b64encode(b.getvalue())
return "data:image/png;base64," + b64.decode("utf-8")
tot_pop = gdf["pop_est"].sum()
# create a geojson layer for each feature
for i, r in gdf.iterrows():
# geodataframe of row
gdf_ = gpd.GeoDataFrame(r.to_frame().T, crs=gdf.crs)
# URI encoded image of plotly figure
img_ = f'<img src="{b64image([r["pop_est"], tot_pop-r["pop_est"]])}"/>'
choro_ = folium.GeoJson(
gdf_.__geo_interface__,
name=r["name"],
style_function=lambda x: {"fillColor": "orange"},
tooltip=folium.GeoJsonTooltip(gdf_.drop(columns="geometry").columns.tolist()),
)
# this is the real work around, add to layer which is a choro
folium.Popup(img_).add_to(choro_)
choro_.add_to(fg)
fg.add_to(m)
m

My code fails with, "ValueError: Marker location must be assigned when added directly to map.", and I don't quite understand why or how to fix it

I am working on Launch Sites Locations Analysis with Folium using a spaceX dataset, and I am trying to create Marker Clusters, because they are good way to simplify a map containing many markers having the same coordinate. I created a Marker Cluster Object and assigned a color to launch outcomes.
Also, the only columns in my dataframe right now are: "Launch Site", "Lat", "Long", "Class", and "marker_color".
Now, the question is: "for each row in spacex_df data frame, create a Marker object with its coordinate, and customize the Marker's icon property to indicate if this launch was a success or fail".
My Code:
import folium
import wget
import pandas as pd
from folium.plugins import MarkerCluster
from folium.plugins import MousePosition
from folium.features import DivIcon
def assign_marker_color(launch_outcome):
if launch_outcome == 1:
return 'green'
else:
return 'red'
spacex_csv_file = wget.download('https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBM-DS0321EN-SkillsNetwork/datasets/spacex_launch_geo.csv')
spacex_df = pd.read_csv(spacex_csv_file)
spacex_df = spacex_df[['Launch Site', 'Lat', 'Long', 'class']]
launch_sites_df = spacex_df.groupby(['Launch Site'], as_index=False).first()
launch_sites_df = launch_sites_df[['Launch Site', 'Lat', 'Long']]
marker_cluster = MarkerCluster()
spacex_df['marker_color'] = spacex_df['class'].apply(assign_marker_color)
spacex_df.tail(10)
site_map.add_child(marker_cluster)
for index, record in spacex_df.iterrows():
marker = folium.Marker(location = [record['Lat'], record['Long']],icon=folium.Icon(color='white',icon_color=record['marker_color'])).add_to(marker_cluster)
marker_cluster.add_child(marker)
site_map
I have tried creating a variable called coordinates, where I can store the latitude and longitude columns of the df, then save that coordinates value to the location parameter necessary in folium.Marker(), but not even that worked. I am a little confused and would love any type of help!
Once again, my error message is, "ValueError: Marker location must be assigned when added directly to map. <folium.folium.Map at 0x7f11f92cd310>".

How to see city map when ploting with Geopandas lib

I have just started learinig Geopandas lib in Python.
I have a dataset with Lat(E) and Lon(N) of car accidents in Belgrade.
I want to plot those dots on the map of Belgrade.
This is my code:
import pandas as pd
import geopandas as gpd
import matplotlib.pyplot as plt
pd.set_option('display.max_rows', 150)
pd.set_option('display.max_columns', 200)
pd.set_option('display.width', 5000)
# reading csv into geopandas
geo_df = gpd.read_file('SaobracajBeograd.csv')
geo_df.columns = ["ID", "Date,Time", "E", "N", "Outcome", "Type", "Description", "geometry"]
geo_df.geometry = gpd.points_from_xy(geo_df.E, geo_df.N)
#print(geo_df)
# reading built in dataset for each city
world_cities = gpd.read_file(gpd.datasets.get_path('naturalearth_cities'))
# I want to plot geometry column only for Belgrade
ax = world_cities[world_cities.name == 'Belgrade'].plot(figsize=(7, 7), alpha=0.5, edgecolor='black')
geo_df.plot(ax=ax, color='red')
plt.show()
This is the result that I get:
How can I prettify this plot, so that I can see the map of the city ( with streets if possible, in color) and with smaller red dots?
as per comments, folium provides base map of overall geometry
have added two layers
Belgrade, I have obtained this geometry from osmnx this is beyond the scope of this question so have just included the polygon as a WKT string
the points that you provided via link in comments
from pathlib import Path
import pandas as pd
import geopandas as gpd
import shapely
import folium
# downloaded data
df = pd.read_csv(
Path.home().joinpath("Downloads/SaobracajBeograd.csv"),
names=["ID", "Date,Time", "E", "N", "Outcome", "Type", "Description"],
)
# create geodataframe, NB CRS
geo_df = gpd.GeoDataFrame(
df, geometry=gpd.points_from_xy(df["E"], df["N"]), crs="epsg:4386"
)
# couldn't find belgrade geometry, used osmnx and simplified geometry as a WKT string
belgrade_poly = shapely.wkt.loads(
"POLYGON ((20.2213764 44.9154621, 20.2252450 44.9070062, 20.2399466 44.9067193, 20.2525385 44.8939145, 20.2419942 44.8842235, 20.2610016 44.8826597, 20.2794675 44.8754192, 20.2858284 44.8447802, 20.2856918 44.8332410, 20.3257447 44.8342507, 20.3328068 44.8098272, 20.3367239 44.8080890, 20.3339619 44.8058144, 20.3353253 44.8011005, 20.3336310 44.8003791, 20.3360230 44.7898245, 20.3384687 44.7907875, 20.3405086 44.7859144, 20.3417344 44.7872272, 20.3474466 44.7713203, 20.3509860 44.7687822, 20.3398029 44.7558716, 20.3220093 44.7448572, 20.3160895 44.7387338, 20.3235092 44.7345531, 20.3359605 44.7308053, 20.3437350 44.7301552, 20.3450306 44.7243651, 20.3497410 44.7209764, 20.3521450 44.7143627, 20.3633795 44.7046060, 20.3830709 44.7030441, 20.3845248 44.7011631, 20.3847991 44.7032182, 20.3924066 44.7036702, 20.4038881 44.6984458, 20.4097684 44.6992834, 20.4129839 44.7024603, 20.4192098 44.7021308, 20.4217436 44.7034920, 20.4251744 44.6976337, 20.4279418 44.6980838, 20.4313251 44.6940680, 20.4358368 44.6933579, 20.4402665 44.6905161, 20.4452138 44.6910160, 20.4495428 44.6880459, 20.4539572 44.6888231, 20.4529809 44.6911331, 20.4550753 44.6919188, 20.4534174 44.6929137, 20.4571253 44.6957696, 20.4570013 44.7008391, 20.4614601 44.7027894, 20.4646634 44.7018970, 20.4674388 44.7050131, 20.4753542 44.7039532, 20.4760757 44.7050260, 20.4802055 44.7033479, 20.4867635 44.7061539, 20.4983359 44.7022445, 20.5049892 44.7021663, 20.5071809 44.7071295, 20.5027682 44.7154832, 20.5028502 44.7217294, 20.5001912 44.7225288, 20.5007294 44.7251513, 20.5093727 44.7271542, 20.5316662 44.7248060, 20.5385861 44.7270519, 20.5390058 44.7329843, 20.5483761 44.7280993, 20.5513810 44.7308508, 20.5510751 44.7340860, 20.5483958 44.7345580, 20.5503614 44.7352316, 20.5509440 44.7434333, 20.5416617 44.7521169, 20.5358563 44.7553171, 20.5348919 44.7609694, 20.5393015 44.7624855, 20.5449353 44.7698750, 20.5490005 44.7708792, 20.5488362 44.7733456, 20.5647717 44.7649237, 20.5711431 44.7707818, 20.5772388 44.7711074, 20.5798915 44.7727751, 20.5852472 44.7808647, 20.5817268 44.7826053, 20.5823183 44.7845765, 20.5792147 44.7843299, 20.5777701 44.7872565, 20.5744279 44.7854098, 20.5740215 44.7886805, 20.5693220 44.7911579, 20.5655386 44.7906451, 20.5635444 44.7921747, 20.5598333 44.7901679, 20.5536143 44.7898282, 20.5502434 44.7909478, 20.5435002 44.8022967, 20.5424780 44.8073064, 20.5474459 44.8103678, 20.5530335 44.8102412, 20.5652728 44.8188428, 20.5738545 44.8279189, 20.5724006 44.8315147, 20.5776931 44.8371416, 20.5765153 44.8378971, 20.5863097 44.8427122, 20.5826128 44.8462544, 20.5762290 44.8486489, 20.5825139 44.8520894, 20.5953933 44.8552493, 20.6206689 44.8543410, 20.6212821 44.8560293, 20.6173687 44.8574761, 20.5961883 44.8615803, 20.5928447 44.8609861, 20.5911876 44.8626994, 20.6019440 44.8670619, 20.6196285 44.8673213, 20.6232109 44.8693710, 20.6164092 44.8815202, 20.6152606 44.8895682, 20.5777643 44.8860527, 20.5311826 44.8712209, 20.5230234 44.8646244, 20.5226088 44.8685278, 20.5187616 44.8654899, 20.5197414 44.8694015, 20.5132944 44.8687179, 20.5076686 44.8735038, 20.5065584 44.8670548, 20.4991594 44.8719635, 20.4938631 44.8734651, 20.4821047 44.8723679, 20.4737899 44.8677144, 20.4661802 44.8592493, 20.4594505 44.8560945, 20.4600397 44.8546034, 20.4650988 44.8535738, 20.4600110 44.8491680, 20.4623204 44.8477906, 20.4603705 44.8445375, 20.4711373 44.8342913, 20.4706338 44.8317839, 20.4498025 44.8343946, 20.4244846 44.8431449, 20.4138827 44.8526577, 20.3912248 44.8598333, 20.3749815 44.8683583, 20.3617778 44.8791076, 20.3436922 44.9103973, 20.3390650 44.9117584, 20.3011288 44.9426876, 20.2946156 44.9402419, 20.2960052 44.9381397, 20.2746476 44.9304194, 20.2703905 44.9345682, 20.2213764 44.9154621))"
)
# plot belgrade city limits
m = gpd.GeoDataFrame(geometry=[belgrade_poly], crs="epsg:4326").explore(name="Belgrade", height=300, width=500)
# plot the points, just for demo purposes plot outcomes as different colors
m = geo_df.explore(m=m, column="Outcome", cmap=["red","green","blue"], name="points")
# add layer control so layers can be switched on / off
folium.LayerControl().add_to(m)
m
supplementary update
Obtain Belgrade geometry
import osmnx as ox
gdf = ox.geocode_to_gdf({'city': 'Belgrade'})
It seems that you are not able to get a map of city using the dataset from world_cities.
For example, if you check
belgrade = world_cities[world_cities.name == 'Belgrade']
it returns following geopandas dataframe
name geometry
102 Belgrade POINT (20.46604 44.82059)
The geometry is in the form of Point which is basically longitude and latitude. The geometry must include polygon of some sort to get the shape of the city you want.
For example, if you extract the country from the world dataset as follows:
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
serbia = world[world.name == "Serbia"]
it returns the following geopandas dataframe for serbia
pop_est continent name iso_a3 gdp_md_est geometry
172 7111024 Europe Serbia SRB 101800.0 POLYGON ((18.82982 45.90887, 18.82984 45.90888...
As you see, the geometry is in the shape of polygon. Now you can plot the map using serbia.plot() to get the map of serbia, which looks as follows:
To get the map of the city, you need to first download the shape file of the city in the form of *.shp file along with other supporting files, and read the *.shp file as gpd.read_file("file.shp"). Only then you are able to plot the map of the required city.

Add Points to Geopandas Object

My objective is to create some kind of geojson object and add several Point's objects to it, with a For Loop.
What am I missing here?
from geojson import Feature
import pandas as pd
import geopandas as gpd
# Point((-115.81, 37.24))
# Create a Dataframe with **Schools Centroids**
myManipulationObj = pd.DataFrame
for schoolNumber in listOfResults:
myManipulationObj.append(centroids[schoolNumber])
# GDF should be a Beautiful collection (geoDataFrame) of Points
gdf = gpd.GeoDataFrame(myManipulationObj, geometry='Coordinates')
After that, I want to use geopandas write() to create a .geojson file.
Any Help?
(solved)
I solved that problem by:
creating a python list (listOfPoints),
Using the POINT object as geometry parameter to the FEATURE object,
Using the List of Features (with Points) to create a FeatureCollection
Leave here for future reference if someone needs :D
# Used to get the Index of Schools from the M Model Optimized
listOfResults = []
for e in range(numSchools):
tempObj = m.getVarByName(str(e))
# If This School is on the Results Optimized
if(tempObj.x != 0):
listOfResults.append(int(tempObj.varName))
# Select, from the List Of Results, A set of Centroid Points
listOfPoints = []
for schoolNumber in listOfResults:
# Attention to the Feature(geometry) from geopandas
listOfPoints.append(Feature(geometry=centroids[schoolNumber]))
# Creating a FeatureCollection with the Features (Points) manipulated above
resultCentroids = FeatureCollection(listOfPoints)

raise ValueError when producing a shape file with geopandas

I have just recently started to work with shapefiles. I have a shapefile in which each object is a polygon. I want to produce a new shapefile in which the geometry of each polygon is replaced by its centroid. There is my code.
import geopandas as gp
from shapely.wkt import loads as load_wkt
fname = '../data_raw/bg501c_starazagora.shp'
outfile = 'try.shp'
shp = gp.GeoDataFrame.from_file(fname)
centroids = list()
index = list()
df = gp.GeoDataFrame()
for i,r in shp.iterrows():
index.append(i)
centroid = load_wkt(str(r['geometry'])).centroid.wkt
centroids.append(centroid)
df['geometry'] = centroids
df['INDEX'] = index
gp.GeoDataFrame.to_file(df,outfile)
When I run the script I end up with raise ValueError("Geometry column cannot contain mutiple " ValueError: Geometry column cannot contain mutiple geometry types when writing to file.
I cannot understand what is wrong. Any help?
The issue is that you're populating the geometry field with a string representation of the geometry rather than a shapely geometry object.
No need to convert to wkt. Your loop could instead be:
for i,r in shp.iterrows():
index.append(i)
centroid = r['geometry'].centroid
centroids.append(centroid)
However, there's no need to loop through the geodataframe at all. You could create a new one of shapefile centroids as follows:
df=gp.GeoDataFrame(data=shp, geometry=shp['geometry'].centroid)
df.to_file(outfile)

Categories

Resources