Component Apps: Mapping with GeoJSON
Important
These recipes only apply to Component App development and will not work for Standard Apps.
Last Updated: December 2025
GeoJSON from a URL
@App.page
def geojson_from_url(lib):
return lib.tethys.Display(
lib.tethys.Map(
lib.ol.layer.Vector(
lib.ol.source.Vector(
options=lib.Props(
url="https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_urban_areas.geojson",
format_="GeoJSON"
)
)
)
)
)
GeoJSON from explicit inline
@App.page
def geojson_from_inline(lib):
features = {
'type': 'FeatureCollection',
'crs': {
'type': 'name',
'properties': {
'name': 'EPSG:3857',
},
},
'features': [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [0, 0],
},
},
{
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': [
[4e6, -2e6],
[8e6, 2e6],
],
},
},
{
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': [
[4e6, 2e6],
[8e6, -2e6],
],
},
},
{
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': [
[
[-5e6, -1e6],
[-3e6, -1e6],
[-4e6, 1e6],
[-5e6, -1e6],
],
],
},
},
{
'type': 'Feature',
'geometry': {
'type': 'MultiLineString',
'coordinates': [
[
[-1e6, -7.5e5],
[-1e6, 7.5e5],
],
[
[1e6, -7.5e5],
[1e6, 7.5e5],
],
[
[-7.5e5, -1e6],
[7.5e5, -1e6],
],
[
[-7.5e5, 1e6],
[7.5e5, 1e6],
],
],
},
},
{
'type': 'Feature',
'geometry': {
'type': 'MultiPolygon',
'coordinates': [
[
[
[-5e6, 6e6],
[-3e6, 6e6],
[-3e6, 8e6],
[-5e6, 8e6],
[-5e6, 6e6],
],
],
[
[
[-2e6, 6e6],
[0, 6e6],
[0, 8e6],
[-2e6, 8e6],
[-2e6, 6e6],
],
],
[
[
[1e6, 6e6],
[3e6, 6e6],
[3e6, 8e6],
[1e6, 8e6],
[1e6, 6e6],
],
],
],
},
},
{
'type': 'Feature',
'geometry': {
'type': 'GeometryCollection',
'geometries': [
{
'type': 'LineString',
'coordinates': [
[-5e6, -5e6],
[0, -5e6],
],
},
{
'type': 'Point',
'coordinates': [4e6, -5e6],
},
{
'type': 'Polygon',
'coordinates': [
[
[1e6, -6e6],
[3e6, -6e6],
[2e6, -4e6],
[1e6, -6e6],
],
],
},
],
},
},
],
}
return lib.tethys.Display(
lib.tethys.Map(
lib.ol.layer.Vector(
lib.ol.source.Vector(
options=lib.Props(
features=features,
format_="GeoJSON"
)
)
)
)
)
GeoJSON from File / Pandas Dataframe
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point
from tethys_sdk.components.utils import transform_coordinate
def csv_to_geojson(csv_path):
df = pd.read_csv(csv_path)
geometry = [Point(transform_coordinate(xy, "EPSG:4326", "EPSG:3857")) for xy in zip(df['lat'], df['lon'])]
gdf = gpd.GeoDataFrame(df, geometry=geometry, crs="EPSG:3857")
return gdf.to_json()
@App.page
def geojson_from_csv(lib):
resources = lib.hooks.use_resources()
geojson = csv_to_geojson(resources.path / "points.csv")
return lib.tethys.Display(
lib.tethys.Map(
lib.ol.layer.Vector(
lib.ol.source.Vector(
options=lib.Props(
features=geojson,
format_="GeoJSON"
)
)
)
)
)
GeoJSON Features with Dynamic Style
@App.page
def geojson_with_dynamic_style(lib):
fill_color, set_fill_color = lib.hooks.use_state("#ff0000")
stroke_color, set_stroke_color = lib.hooks.use_state("#000000")
return lib.tethys.Display(
lib.html.div(
style=lib.Style(
position="absolute",
top=10,
right=20,
zIndex=1
)
)(
lib.bs.FormLabel(htmlFor="fill-color")("Fill Color"),
lib.bs.FormControl(
id_="fill-color",
type_="color",
defaultValue=fill_color,
onChange=lambda e: set_fill_color(e.target.value)
),
lib.bs.FormLabel(htmlFor="stroke-color")("Stroke Color"),
lib.bs.FormControl(
id_="stroke-color",
type_="color",
defaultValue=stroke_color,
onChange=lambda e: set_stroke_color(e.target.value),
),
),
lib.tethys.Map(
lib.ol.layer.Vector(
style=lib.ol.style.Style(
stroke=lib.ol.style.Stroke(
color=stroke_color,
width=1
),
fill=lib.ol.style.Fill(
color=fill_color
)
)
)(
lib.ol.source.Vector(
options=lib.Props(
url="https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_urban_areas.geojson",
format_="GeoJSON"
)
)
)
)
)
Style GeoJSON with Unique Values Styler
@App.page
def geojson_unique_styler(lib):
return lib.tethys.Display(
lib.tethys.Map(
lib.ol.layer.Vector(
style=lib.ol.style.Styler(
method="unique_values",
fields=["scalerank"],
values=[
(
1,
lib.ol.style.Style(
stroke=lib.ol.style.Stroke(
color="purple",
width=1
),
fill=lib.ol.style.Fill(
color="white"
),
text=lib.ol.style.Text(
text="%{$feature.scalerank}",
fill=lib.ol.style.Fill(color="orange"),
stroke=lib.ol.style.Stroke(color="#f1234567", width=2),
),
)
),
(
2,
lib.ol.style.Style(
stroke=lib.ol.style.Stroke(
color="#459302",
width=1
),
fill=lib.ol.style.Fill(
color="#18181818"
),
text=lib.ol.style.Text(
text="%{$feature.scalerank}",
fill=lib.ol.style.Fill(color="orange"),
stroke=lib.ol.style.Stroke(color="#f1234567", width=2),
),
)
),
(
3,
lib.ol.style.Style(
stroke=lib.ol.style.Stroke(
color="#93102849",
width=1
),
fill=lib.ol.style.Fill(
color="#f930efd3"
),
text=lib.ol.style.Text(
text="%{$feature.scalerank}",
fill=lib.ol.style.Fill(color="orange"),
stroke=lib.ol.style.Stroke(color="#f1234567", width=2),
),
)
)
],
default=lib.ol.style.Style(
stroke=lib.ol.style.Stroke(
color="#000000",
width=1
),
fill=lib.ol.style.Fill(
color="#ff0000"
),
text=lib.ol.style.Text(
text="%{$feature.scalerank}",
fill=lib.ol.style.Fill(color="blue"),
stroke=lib.ol.style.Stroke(color="white", width=2),
),
)
)
)(
lib.ol.source.Vector(
options=lib.Props(
url="https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_urban_areas.geojson",
format_="GeoJSON"
)
)
)
)
)
Display Feature Props On Click
@App.page
def display_feature_props_on_click(lib):
props, set_props = lib.hooks.use_state({})
return lib.tethys.Display(
lib.tethys.Panel(title="Properties", show=len(props) > 0, on_close=lambda e: set_props({}), extent="300px")(
*[
lib.html.div(
lib.html.span(
lib.html.b(f"{k}: ")
),
lib.html.span(v)
) for k, v in props.items()
]
),
lib.tethys.Map(
lib.ol.layer.Vector(
onClick=lambda e: set_props(e.features[0] if e.features else lib.Props(Message="No features found"))
)(
lib.ol.source.Vector(
options=lib.Props(
url="https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_urban_areas.geojson",
format_="GeoJSON"
)
)
)
)
)