Skip to content

Instantly share code, notes, and snippets.

@hansthen
Created June 29, 2025 12:00
Show Gist options
  • Save hansthen/8433c74378fc8a52a2e51b7b10d4e8a9 to your computer and use it in GitHub Desktop.
Save hansthen/8433c74378fc8a52a2e51b7b10d4e8a9 to your computer and use it in GitHub Desktop.
import folium
import streamlit as st
from folium.plugins import GeoMan, MousePosition
from folium import JsCode
from streamlit_folium import st_folium
from scenario import parser
from streamlit_argparse import Form
import requests
from scenario import parser
import json
import shlex
from form import *
st.set_page_config(
page_title="geoman",
layout="wide"
)
parser.exit = lambda *args, **kwargs: None
args = [
"--area",
"""{"type": "FeatureCollection", "features": [{"type": "Feature",
"properties": {}, "geometry": {"type": "Polygon", "coordinates":
[[[-74.090424, 40.723063], [-74.090424, 40.7561], [-74.027252, 40.7561],
[-74.027252, 40.723063], [-74.090424, 40.723063]]]}}], "properties":
{"type": "whitelist"}}""",
"--ignore",
"""
{"geometry": {"type": "Point", "coordinates": [-73.981681, 40.730975]},
"properties": {"line": "L", "name": "1st Ave", "notes": "L-all times",
"objectid": "146", "url": "http://web.mta.info/nyct/service/", "ignore": true},
"type": "Feature"}
"""
]
initial = parser.parse_args(args)
st.session_state.setdefault("ignore", initial.ignore)
st.session_state.setdefault("area", initial.area)
def handle_return():
geojson = st.session_state.map_widget
if geojson["properties"]["type"] == "ignore":
st.session_state.ignore = geojson["features"]
else:
st.session_state.area = [geojson]
@st.cache_data
def get_ignored_stations():
return [
f["properties"]["objectid"] for f in st.session_state.ignore
]
@st.cache_data
def get_initial_layers():
return st.session_state.area
left, right = st.columns([1,1])
with left:
items = folium.FeatureGroup()
m = folium.Map(
location=[40.73, -73.94],
zoom_start=12,
to_streamlit=JsCode("""
() => {
console.log("placeholder");
}
""")
)
ignored = get_ignored_stations()
rt = folium.plugins.Realtime(
"https://raw.githubusercontent.com/python-visualization/folium-example-data/main/subway_stations.geojson",
get_feature_id=JsCode("(f) => { return f.properties.objectid; }"),
point_to_layer=JsCode("""
(f, latlng) => {
return L.circleMarker(latlng, {
radius: 5,
color: "light gray",
fillOpacity: 0.7,
// No editing using geoman
pmIgnore: true,
})
}
"""),
on_each_feature=JsCode("""
(f, layer) => {
layer.on("click", (event) => {
var loc = event.sourceTarget.feature.geometry;
var layer = event.sourceTarget;
var parents = layer._eventParents;
var rt = parents[Object.keys(parents)[0]];
var index = rt.options.ignored.indexOf(f.properties.objectid);
if (index < 0) {
rt.options.ignored.push(f.properties.objectid);
layer.setStyle({"color": "green"});
} else {
rt.options.ignored.splice(index, 1);
layer.setStyle({"color": "light gray"});
}
// Return this to streamlit
var geojson = rt.toGeoJSON();
// Filter only ignored features
geojson.features = geojson.features.filter(
(f) => {
var i = rt.options.ignored.indexOf(
f.properties.objectid
)
return i >= 0;
}
);
geojson.properties = {type: "ignore"}
layer._map.options.toStreamlit();
Streamlit.setComponentValue(geojson);
});
}
"""),
refresh=10,
ignored=ignored,
)
# Do this after creating the realtime layer, because we need the reference
style=JsCode("""
# Do this after creating the realtime layer, because we need the reference
style=JsCode("""
(f) => {
var realtime = %(realtime)s;
if (realtime.options.ignored.indexOf(f.properties.objectid) >= 0) {
return {"color": "green"};
} else {
return {"color": "light gray"};
}
}
""" % dict(realtime=rt.get_name()))
rt.options["style"] = style
rt.add_to(m)
items.add_to(m)
MousePosition().add_to(m)
handler = JsCode("""
(e) => {
console.log("event", e);
var map = %(map)s;
var layers = L.PM.Utils.findLayers(map);
var jsons = []
layers.forEach((layer) => {
var json = layer.toGeoJSON();
if(layer.options.radius) {
json.properties.radius = layer.options.radius;
}
jsons.push(json);
});
var geojson = {
type: 'FeatureCollection',
features: jsons,
properties: {type: "area"}
};
Streamlit.setComponentValue(geojson);
}
""" % dict(map=m.get_name()))
click = JsCode("""
(e) => {
console.log(e.target);
console.log(e.target.toGeoJSON());
}
"""
)
for whitelist in get_initial_layers():
wl = folium.GeoJson(whitelist).add_to(items)
gm = GeoMan(
feature_group=items,
draw_marker=False,
draw_polyline=False,
draw_circle_marker=False,
draw_text=False,
on={
"click": click,
"pm:change": handler,
"pm:update": handler,
"pm:dragend": handler,
"pm:remove": handler,
"pm:rotate": handler,
}
).add_to(m)
event_handlers = {
"pm:create": handler,
"pm:cut": handler,
"pm:globaldrawmodetoggled": handler,
"pm:globaleditmodetoggled": handler,
"pm:globaldragmodetoggled": handler,
"pm:globalremovalmodetoggled": handler,
"pm:globalcutmodetoggled": handler,
"pm:globalrotatemodetoggled": handler,
}
m.on(**event_handlers)
result = st_folium(
m,
width=1200,
height=500,
returned_objects=[],
key="map_widget",
on_change=handle_return
)
with right:
form = Form(
parser,
area=area_widget,
ignore=ignore_widget,
dow=dow_widget,
)
output = form.render(args)
output = output.split("--")
output = "\n--".join(output).strip()
st.code(output, language="bash")
1,1 Top
from scenario import parser
import streamlit as st
from streamlit_argparse import Form
import json
import shlex
def show_as_form(args):
try:
parser.exit = lambda *args, **kwargs: None
initial = parser.parse_args(args)
form = Form(
parser,
area=area_widget,
spots=spots_widget,
dow=dow_widget,
)
output = form.render(args)
output = output.split("--")
output = "\n--".join(output).strip()
return output
except Exception as e:
st.write(e)
st.write("invalid results")
def area_widget(form, action, value):
area = st.session_state.get("area", [])
st.text_area("area", area, disabled=True)
return [json.dumps(area)]
def ignore_widget(form, action, value):
ignore = st.session_state.get("ignore", [])
st.text_area("ignore", ignore, disabled=True)
return [json.dumps(f) for f in ignore]
def dow_widget(form, action, value):
names = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"]
checkboxes = st.columns(len(names))
result = []
for col, name in zip(checkboxes, names):
with col:
if st.checkbox(name):
result.append(name)
return list(result)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment