Spaces:
Running
Running
Add web app
Browse files- Dockerfile +21 -0
- README.md +12 -1
- pages/00_home.py +27 -0
- pages/01_imagery.py +128 -0
- requirements.txt +6 -0
Dockerfile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM jupyter/base-notebook:latest
|
| 2 |
+
|
| 3 |
+
RUN mamba install -c conda-forge leafmap geopandas localtileserver -y && \
|
| 4 |
+
fix-permissions "${CONDA_DIR}" && \
|
| 5 |
+
fix-permissions "/home/${NB_USER}"
|
| 6 |
+
|
| 7 |
+
COPY requirements.txt .
|
| 8 |
+
RUN pip install -r requirements.txt
|
| 9 |
+
|
| 10 |
+
RUN mkdir ./pages
|
| 11 |
+
COPY /pages ./pages
|
| 12 |
+
|
| 13 |
+
ENV PROJ_LIB='/opt/conda/share/proj'
|
| 14 |
+
|
| 15 |
+
USER root
|
| 16 |
+
RUN chown -R ${NB_UID} ${HOME}
|
| 17 |
+
USER ${NB_USER}
|
| 18 |
+
|
| 19 |
+
EXPOSE 8765
|
| 20 |
+
|
| 21 |
+
CMD ["solara", "run", "./pages", "--host=0.0.0.0"]
|
README.md
CHANGED
|
@@ -11,8 +11,19 @@ app_port: 8765
|
|
| 11 |
|
| 12 |
## TN-historical-imagery
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
- Web App: <https://giswqs-tn-historical-imagery.hf.space>
|
| 17 |
- GitHub: <https://github.com/giswqs/tn-historical-imagery>
|
| 18 |
- Hugging Face: <https://huggingface.co/spaces/giswqs/tn-historical-imagery>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
## TN-historical-imagery
|
| 13 |
|
| 14 |
+
A Interactive Web App for Visualizing Tennessee Historical Imagery
|
| 15 |
+
|
| 16 |
+
### Introduction
|
| 17 |
+
|
| 18 |
+
This web app allows users to visualize historical imagery for Tennessee. The historical imagery is provided by the Tennessee Department of Transportation (TDOT) and includes aerial 2-ft resolution photos taken between 1997 and 2006.
|
| 19 |
+
The original imagery in MrSID format was download from the [TNGIC Google Drive](https://drive.google.com/drive/folders/1qYlPFBLkcOpO4xjvBvHwMH9RDtbsSNkv). The imagery was then converted to Cloud Optimized GeoTIFF format
|
| 20 |
+
and hosted on Source Cooperative.
|
| 21 |
|
| 22 |
- Web App: <https://giswqs-tn-historical-imagery.hf.space>
|
| 23 |
- GitHub: <https://github.com/giswqs/tn-historical-imagery>
|
| 24 |
- Hugging Face: <https://huggingface.co/spaces/giswqs/tn-historical-imagery>
|
| 25 |
+
- Dataset: <https://source.coop/repositories/giswqs/tn-imagery>
|
| 26 |
+
|
| 27 |
+
### Acknowledgements
|
| 28 |
+
|
| 29 |
+
This work is supported by the U.S. Geological Survey through Grant/Cooperative Agreement No. G23AP00683 (GY23-GY27) in collaboration with [AmericaView](https://americaview.org).
|
pages/00_home.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import solara
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
@solara.component
|
| 5 |
+
def Page():
|
| 6 |
+
with solara.Column(align="center"):
|
| 7 |
+
markdown = """
|
| 8 |
+
## A Interactive Web App for Visualizing Tennessee Historical Imagery
|
| 9 |
+
|
| 10 |
+
### Introduction
|
| 11 |
+
|
| 12 |
+
This web app allows users to visualize historical imagery for Tennessee. The historical imagery is provided by the Tennessee Department of Transportation (TDOT) and includes aerial 2-ft resolution photos taken between 1997 and 2006.
|
| 13 |
+
The original imagery in MrSID format was download from the [TNGIC Google Drive](https://drive.google.com/drive/folders/1qYlPFBLkcOpO4xjvBvHwMH9RDtbsSNkv). The imagery was then converted to Cloud Optimized GeoTIFF format
|
| 14 |
+
and hosted on Source Cooperative.
|
| 15 |
+
|
| 16 |
+
- Web App: <https://giswqs-tn-historical-imagery.hf.space>
|
| 17 |
+
- GitHub: <https://github.com/giswqs/tn-historical-imagery>
|
| 18 |
+
- Hugging Face: <https://huggingface.co/spaces/giswqs/tn-historical-imagery>
|
| 19 |
+
- Dataset: <https://source.coop/repositories/giswqs/tn-imagery>
|
| 20 |
+
|
| 21 |
+
### Acknowledgements
|
| 22 |
+
|
| 23 |
+
This work is supported by the U.S. Geological Survey through Grant/Cooperative Agreement No. G23AP00683 (GY23-GY27) in collaboration with [AmericaView](https://americaview.org).
|
| 24 |
+
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
solara.Markdown(markdown)
|
pages/01_imagery.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import leafmap
|
| 3 |
+
import solara
|
| 4 |
+
import ipywidgets as widgets
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import geopandas as gpd
|
| 7 |
+
import tempfile
|
| 8 |
+
from shapely.geometry import Point
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def add_widgets(m):
|
| 12 |
+
style = {"description_width": "initial"}
|
| 13 |
+
padding = "0px 0px 0px 5px"
|
| 14 |
+
|
| 15 |
+
checkbox = widgets.Checkbox(
|
| 16 |
+
value=True,
|
| 17 |
+
description="Footprints",
|
| 18 |
+
style=style,
|
| 19 |
+
layout=widgets.Layout(width="90px", padding="0px"),
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
split = widgets.Checkbox(
|
| 23 |
+
value=False,
|
| 24 |
+
description="Split map",
|
| 25 |
+
style=style,
|
| 26 |
+
layout=widgets.Layout(width="92px", padding=padding),
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
reset = widgets.Checkbox(
|
| 30 |
+
value=False,
|
| 31 |
+
description="Reset",
|
| 32 |
+
style=style,
|
| 33 |
+
layout=widgets.Layout(width="75px", padding="0px"),
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
output = widgets.Output()
|
| 37 |
+
|
| 38 |
+
def reset_map(change):
|
| 39 |
+
if change.new:
|
| 40 |
+
pass
|
| 41 |
+
|
| 42 |
+
reset.observe(reset_map, names="value")
|
| 43 |
+
|
| 44 |
+
def handle_click(**kwargs):
|
| 45 |
+
if kwargs.get("type") == "click":
|
| 46 |
+
latlon = kwargs.get("coordinates")
|
| 47 |
+
geometry = Point(latlon[::-1])
|
| 48 |
+
selected = m.gdf[m.gdf.intersects(geometry)]
|
| 49 |
+
setattr(m, "zoom_to_layer", False)
|
| 50 |
+
if len(selected) > 0:
|
| 51 |
+
filename = selected.iloc[0]["Filename"]
|
| 52 |
+
county = selected.iloc[0]["County"]
|
| 53 |
+
year = filename.split("_")[-1][:4]
|
| 54 |
+
with output:
|
| 55 |
+
output.clear_output()
|
| 56 |
+
output.append_stdout(f"County: {county} | Year: {year}")
|
| 57 |
+
url = f"https://data.source.coop/giswqs/tn-imagery/imagery/{filename}"
|
| 58 |
+
layer = m.find_layer("Selected Image")
|
| 59 |
+
if layer is not None:
|
| 60 |
+
m.remove(layer)
|
| 61 |
+
m.default_style = {"cursor": "wait"}
|
| 62 |
+
m.add_cog_layer(url, name="Selected Image", zoom_to_layer=False)
|
| 63 |
+
m.default_style = {"cursor": "default"}
|
| 64 |
+
else:
|
| 65 |
+
with output:
|
| 66 |
+
output.clear_output()
|
| 67 |
+
output.append_stdout("No image found.")
|
| 68 |
+
|
| 69 |
+
m.on_interaction(handle_click)
|
| 70 |
+
|
| 71 |
+
box = widgets.VBox([widgets.HBox([checkbox, split, reset]), output])
|
| 72 |
+
m.add_widget(box, position="topright", add_header=False)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
zoom = solara.reactive(8)
|
| 76 |
+
center = solara.reactive((35.64836915737426, -86.21246337890626))
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class Map(leafmap.Map):
|
| 80 |
+
def __init__(self, **kwargs):
|
| 81 |
+
kwargs["toolbar_control"] = False
|
| 82 |
+
kwargs["draw_control"] = False
|
| 83 |
+
super().__init__(**kwargs)
|
| 84 |
+
basemap = {
|
| 85 |
+
"url": "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}",
|
| 86 |
+
"attribution": "Google",
|
| 87 |
+
"name": "Google Satellite",
|
| 88 |
+
}
|
| 89 |
+
self.add_tile_layer(**basemap, shown=False)
|
| 90 |
+
|
| 91 |
+
wms_url = "https://tnmap.tn.gov/arcgis/services/BASEMAPS/IMAGERY_WEB_MERCATOR/MapServer/WMSServer"
|
| 92 |
+
self.add_wms_layer(wms_url, layers="0", name="TDOT Imagery", shown=True)
|
| 93 |
+
|
| 94 |
+
self.add_layer_manager(opened=False)
|
| 95 |
+
# add_widgets(self)
|
| 96 |
+
geojson = "https://github.com/opengeos/datasets/releases/download/vector/TN_Counties.geojson"
|
| 97 |
+
style = {"color": "#3388ff", "opacity": 1, "weight": 2, "fillOpacity": 0}
|
| 98 |
+
self.add_geojson(
|
| 99 |
+
geojson,
|
| 100 |
+
layer_name="TN Counties",
|
| 101 |
+
style=style,
|
| 102 |
+
zoom_to_layer=False,
|
| 103 |
+
info_mode=None,
|
| 104 |
+
)
|
| 105 |
+
gdf = gpd.read_file(geojson)
|
| 106 |
+
setattr(self, "gdf", gdf)
|
| 107 |
+
add_widgets(self)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
@solara.component
|
| 111 |
+
def Page():
|
| 112 |
+
with solara.Column(style={"min-width": "500px"}):
|
| 113 |
+
# solara components support reactive variables
|
| 114 |
+
# solara.SliderInt(label="Zoom level", value=zoom, min=1, max=20)
|
| 115 |
+
# using 3rd party widget library require wiring up the events manually
|
| 116 |
+
# using zoom.value and zoom.set
|
| 117 |
+
Map.element( # type: ignore
|
| 118 |
+
zoom=zoom.value,
|
| 119 |
+
on_zoom=zoom.set,
|
| 120 |
+
center=center.value,
|
| 121 |
+
on_center=center.set,
|
| 122 |
+
scroll_wheel_zoom=True,
|
| 123 |
+
toolbar_ctrl=False,
|
| 124 |
+
data_ctrl=False,
|
| 125 |
+
height="780px",
|
| 126 |
+
)
|
| 127 |
+
# solara.Text(f"Center: {center.value}")
|
| 128 |
+
# solara.Text(f"Zoom: {zoom.value}")
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
geopandas
|
| 2 |
+
leafmap
|
| 3 |
+
pydantic< 2.0
|
| 4 |
+
setuptools<71
|
| 5 |
+
solara
|
| 6 |
+
|