I want to plot some polygons contained in a GeoJson file. Is it possible to visualize a GeoJson file in Plotly that is not linked directly to a real world location?
As example I can use GeoPandas to plot a generic GeoJson file:
import json
geodata = json.loads(
"""{ "type": "FeatureCollection",
"features": [
{ "type": "Feature",
"geometry": {"type": "Polygon", "coordinates": [[[0,0],[0,1],[1,1]]]},
"properties": {"id": "upper_left"}
},
{ "type": "Feature",
"geometry": {"type": "Polygon", "coordinates": [[[0,0],[1,1],[1,0]]]},
"properties": {"id": "lower_right"}
}
]
}""")
import geopandas as gpd
df_shapes = gpd.GeoDataFrame.from_features(geodata["features"])
df_shapes.plot(color="none")
The result displays the two polygons (triangles) contained in the GeoJson:
How would I plot the same map using Plotly? This answer suggests to use scope to limit the base map that is shown. What can be done if there is no base map?
(I am not asking how to plot a square with a line. The GeoJson is just a simplified example.)
plotly shapes can be drawn.
using traces
It's then a case of list / dict comprehensions to restructure geojson polygons to plotly structure
import json
geodata = json.loads(
"""{ "type": "FeatureCollection",
"features": [
{ "type": "Feature",
"geometry": {"type": "Polygon", "coordinates": [[[0,0],[0,1],[1,1]]]},
"properties": {"id": "upper_left"}
},
{ "type": "Feature",
"geometry": {"type": "Polygon", "coordinates": [[[0,0],[1,1],[1,0]]]},
"properties": {"id": "lower_right"}
}
]
}"""
)
go.Figure(
[
go.Scatter(
**{
"x": [p[0] for p in f["geometry"]["coordinates"][0]],
"y": [p[1] for p in f["geometry"]["coordinates"][0]],
"fill": "toself",
"name": f["properties"]["id"],
}
)
for f in geodata["features"]
]
).update_layout(height=200, width=200, showlegend=False, margin={"l":0,"r":0,"t":0,"b":0})
using shapes
use geopandas geometry to get SVG then extract path
add theses polygons as shapes onto layout
from bs4 import BeautifulSoup
# input to plotly is path. use shapely geometry svg path for this
df_shapes = df_shapes.assign(
svgpath=df_shapes["geometry"].apply(
lambda p: BeautifulSoup(p.svg()).find("path")["d"]
)
)
go.Figure(
layout=dict(
height=200,
width=200,
showlegend=False,
margin={"l": 0, "r": 0, "t": 0, "b": 0},
xaxis={"range": [0, 1]},
yaxis={"range": [0, 1]},
shapes=[{"type": "path", "path": p} for p in df_shapes["svgpath"]],
)
)
Related
I am making a program that retrieves GeoJSON data from past convective outlooks from the Storm Prediction Center (SPC) and plot it using geopandas. With my current code, it is able to plot outlooks correctly onto a map. However, the coloring isn't right. I noticed that the GeoJSON returned by the SPC included outline and fill coloring data for the categories - (in properties field)
{"type": "FeatureCollection", "features": [{"type": "Feature", "geometry": {"type": "MultiPolygon", "coordinates": ...}, "properties": {"DN": 2, "VALID": "202109010100", "EXPIRE": "202109011200", "ISSUE": "202109010042", "LABEL": "TSTM", "LABEL2": "General Thunderstorms Risk", "stroke": "#55BB55", "fill": "#C1E9C1"}}, {"type": "Feature", "geometry": {"type": "MultiPolygon", "coordinates": ...}, "properties": {"DN": 3, "VALID": "202109010100", "EXPIRE": "202109011200", "ISSUE": "202109010042", "LABEL": "MRGL", "LABEL2": "Marginal Risk", "stroke": "#005500", "fill": "#66A366"}}, {"type": "Feature", "geometry": {"type": "MultiPolygon", "coordinates": ...}, "properties": {"DN": 4, "VALID": "202109010100", "EXPIRE": "202109011200", "ISSUE": "202109010042", "LABEL": "SLGT", "LABEL2": "Slight Risk", "stroke": "#DDAA00", "fill": "#FFE066"}}]}
Is it possible to use the stroke and fill data in properties to automatically color every MultiPolygon?
My current code is below (assume that all packages are imported)
outlook = "https://www.spc.noaa.gov/products/outlook/archive/2021/day1otlk_20210901_0100_cat.lyr.geojson"
world = geopandas.read_file(
geopandas.datasets.get_path('naturalearth_lowres')
)
df = geopandas.read_file(outlook)
ax = world.plot(color='white', edgecolor='#333333',linewidth=0.3)
print(type(df))
s = geopandas.GeoDataFrame(df)
s.plot(ax=ax,markersize=0.7,figsize=(1000,1000))
ax.set_xlim(-140, -70) # focus on continental US
ax.set_ylim(25, 50) # focus on continental US
plt.savefig('outlook.jpg', dpi=360) # save as outlook.jpg
I tried looking in the geopandas documentation but it didn't state how to use fields in geojson to color polygons.
You're one kwarg away from the final result. plot accepts Series as an argument for color :
colorstr, np.array, pd.Series (def: None) If specified, all objects
will be colored uniformly.
s.plot(ax=ax, color=s["fill"], markersize=0.7, figsize=(1000,1000)) # <- color=s["fill"] (added here)
Output :
I am trying to solve a particular case of comparison of polygons to others. I have five polygons distributed as in the figure below. The black polygon is the one with the largest area.
There may be other similar cases, the main rule is to remove the smallest polygons among all those that have one or more side portions in common.
The data for this case are in a GeoJson file as follows:
{"type":"FeatureCollection","features":[
{"type":"Feature","properties":{"id":1},"geometry":{"type":"Polygon","coordinates":[[[3.4545135498046875,45.533288879467456],[3.4960556030273433,45.533288879467456],[3.4960556030273433,45.57055337226086],[3.4545135498046875,45.57055337226086],[3.4545135498046875,45.533288879467456]]]}},
{"type":"Feature","properties":{"id":2},"geometry":{"type":"Polygon","coordinates":[[[3.4545135498046875,45.52917023833511],[3.4960556030273433,45.52917023833511],[3.4960556030273433,45.53891018749409],[3.4545135498046875,45.53891018749409],[3.4545135498046875,45.52917023833511]]]}},
{"type":"Feature","properties":{"id":3},"geometry":{"type":"Polygon","coordinates":[[[3.4845542907714844,45.5298015824607],[3.5159683227539062,45.5298015824607],[3.5159683227539062,45.543388795387294],[3.4845542907714844,45.543388795387294],[3.4845542907714844,45.5298015824607]]]}},
{"type":"Feature","properties":{"id":4},"geometry":{"type":"Polygon","coordinates":[[[3.465328216552734,45.542667432984864],[3.4735679626464844,45.542667432984864],[3.4735679626464844,45.5478369923404],[3.465328216552734,45.5478369923404],[3.465328216552734,45.542667432984864]]]}},
{"type":"Feature","properties":{"id":5},"geometry":{"type":"Polygon","coordinates":[[[3.4545138850808144,45.56799974017372],[3.4588050842285156,45.56799974017372],[3.4588050842285156,45.57055290285386],[3.4545138850808144,45.57055290285386],[3.4545138850808144,45.56799974017372]]]}}]}
Is there a solution to delete only the two blue polygons(id 2 and 5)? In python.
By transforming the Polygons into LineString one could look if a Linestring is a portion of another Linestring ? But I don't see how to do it. Or maybe looking to see if the LineString of the black and blue polygons have more than two points in common ? But we can't convert a LineString into more than two points.
The following approach may work for you using shared_paths which correctly calls out the path overlap between polygons 1, 2 and 5:
import json
import shapely as sh
import shapely.ops as ops
import shapely.geometry as geo
with open('./test.json') as f:
features = json.load(f)['features']
for f1 in features:
for f2 in features:
id1 = f1['properties']['id']
id2 = f2['properties']['id']
if int(id1) > int(id2):
s1 = geo.shape(f1['geometry'])
s2 = geo.shape(f2['geometry'])
coll = ops.shared_paths(s1.boundary, s2.boundary)
if not coll.is_empty:
print(f"{id1} and {id2} have shared path")
# update your feature collection etc
I had to reduce the precision to 5 decimal places in your feature geometry for this to work as initially it only detects the overlap between polygon 1 and 2. The shared corner between polygon 1 and 5 is slightly out in your input FeatureCollection:
{
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"id": 1
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[3.45451, 45.53328],
[3.49605, 45.53328],
[3.49605, 45.57055],
[3.45451, 45.57055],
[3.45451, 45.53328]
]
]
}
},
{
"type": "Feature",
"properties": {
"id": 2
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[3.45451, 45.52917],
[3.49605, 45.52917],
[3.49605, 45.53891],
[3.45451, 45.53891],
[3.45451, 45.52917]
]
]
}
},
{
"type": "Feature",
"properties": {
"id": 3
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[3.48455, 45.52980],
[3.51596, 45.52980],
[3.51596, 45.54338],
[3.48455, 45.54338],
[3.48455, 45.52980]
]
]
}
},
{
"type": "Feature",
"properties": {
"id": 4
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[3.465328, 45.54266],
[3.473567, 45.54266],
[3.473567, 45.54783],
[3.465328, 45.54783],
[3.465328, 45.54266]
]
]
}
},
{
"type": "Feature",
"properties": {
"id": 5
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[3.454513, 45.56799],
[3.458805, 45.56799],
[3.458805, 45.57055],
[3.454513, 45.57055],
[3.454513, 45.56799]
]
]
}
}
]
}
I want to visualize geo-data with timestamp information on it. I use plugins.TimestampedGeoJson in my python code to do that. the code is working actually but i need to display more than 1 feature in the map and each of these features can be shown/hidden from LayerControl.
is there any idea how to do that ?
#example with one feature
features_3 = []
for row in DF_3.itertuples():
long = row.longitude
lat = row.latitude
data= row.data
features_3 .append (
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [long,lat],
},
"properties": {
"time": str(row.xdate),
"popup": "Record : " + str(data) ,
"icon": "circle",
"iconstyle": {
"fillColor": color_scale(data),
"fillOpacity": 0.6,
"stroke": "false",
"radius": 8,
},
"style": {"weight": 0},
},
}
)
plugins.TimestampedGeoJson(
{"type": "FeatureCollection", "features": features_3 },
period="P1D",
add_last_point=True,
auto_play=True,
loop=False,
max_speed=1,
loop_button=True,
time_slider_drag_update=True,
duration="P1D",
).add_to(map)
LayerControl().add_to(map)
TimestampedGeoJson duration parameter causes polygons to disappear
The code in this link might help you. You can see how it's adding multiple polygon layers in the same map.
I have this geojason file
{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "visit_date": "2013-03-27Z", "name": "Mayi-Tatu", "n_workers": 150.0, "mineral": "Gold" }, "geometry": { "type": "Point", "coordinates": [ 29.66033, 1.01089 ] } },
{ "type": "Feature", "properties": { "visit_date": "2013-03-27Z", "name": "Mabanga", "n_workers": 115.0, "mineral": "Gold" }, "geometry": { "type": "Point", "coordinates": [ 29.65862, 1.00308 ] } },
{ "type": "Feature", "properties": { "visit_date": "2013-03-27Z", "name": "Molende", "n_workers": 130.0, "mineral": "Gold" }, "geometry": { "type": "Point", "coordinates": [ 29.65629, 0.98563 ] } },
...
{ "type": "Feature", "properties": { "visit_date": "2017-08-31Z", "name": "Kambasha", "n_workers": 37.0, "mineral": "Cassiterite" }, "geometry": { "type": "Point", "coordinates": [ 29.05973167, -2.25938167 ] } }
]
}
I read this file, with the next code:
filename = "ipis_cod_mines.geojson"
df_congomines_crs84_geo = gpd.read_file(filename)
But when I check the crs property of df_congomines_crs84_geo,
df_congomines_crs84_geo.crs
I got "{'init': 'epsg:4326'}", I don't understand why i don't get the right crs. (first question)
After, I read another dataset for the same area (both data belongs to congo)
df_countries_4326_geo = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
This dataset has crs equal to {'init': 'epsg:4326'}.
When i plot both datasets (without change the crs),
ax = congo_df.plot(alpha=0.5, color='brown', figsize=(11,4))
df_congomines_crs84_geo.plot(ax=ax, column='mineral')
plt.show()
I got the next image:
Image result
Why both image are not overlaped if they belong to the same area??? How can I fix it??? Is this problem related to the UTM zone???(second question)
CRS84 is equivalent to WGS84 for which the standard EPSG code is EPSG:4326. CRS84 was defined in an old geojson spec (2008). Reading a geojson file gives EPSG:4326 as the CRS.
I want to map some locations which are stored in a geoJson format. The problem is that map axes (both x and y) goes from -2e7 to 2e7 instead of -180 to 180 (longtidue -x axis-) and -90 to 90 (for latitude -y axis-). Then, when I want to map my position {"longitude": 48.90314,"latitude":20.70890} I have to write {"longitude": 4890314,"latitude":2070890} otherwise it is located wrong in the map.
Does anyone knows why this happens or what I am doing wrong?
This is my python script:
from bokeh.io import output_file, show
from bokeh.models import GeoJSONDataSource, HoverTool
from bokeh.plotting import figure
from bokeh.tile_providers import CARTODBPOSITRON
import json
geo_source = GeoJSONDataSource(geojson=json.dumps(geoJson_file))
p = figure()
p.add_tile(CARTODBPOSITRON)
p.circle(x='x', y='y', alpha=0.9, size=10, source=geo_source)
p.add_tools(HoverTool(tooltips=[
("name", "#name"),
("(Long, Lat)", "(#x, #y)"),
]))
output_file("geojson.html")
show(p)
This is the geoJson file (geoJson_file in py script) I want to map:
{
"type": "FeatureCollection",
"features": [
{
"geometry": {
"type": "Point",
"coordinates": [
48.90314,
20.70890
]
},
"type": "Feature",
"properties": {
"name": "marker_1"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [
-94.91849,
58.60959
]
},
"type": "Feature",
"properties": {
"name": "marker_2"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [
81.03195,
38.94440
]
},
"type": "Feature",
"properties": {
"name": "marker_3"
}
}
]
}
And this is the map I get (but as you can see lon/lat is not the one it should be because I have to modify the lon/lat numbers from of the geoJson file (48.90314 to 4890314 and 20.70890 to 2070890) otherwise it is not properly located in the map)