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 |
+
|