├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── apps ├── callery_home.py ├── callery_naip.py ├── callery_photos.py ├── callery_planet.py ├── cog.py ├── flickering.py ├── get_bounds.py ├── heatmap.py ├── home.py ├── osm_names.py ├── scotland.py ├── split.py ├── upload.py └── viirs.py ├── callery_pear.py ├── data ├── PyCTN.csv ├── cog_files.txt └── scotland_xyz.tsv ├── interact.py ├── ntl.py ├── packages.txt ├── postBuild ├── raster.py ├── requirements.txt ├── search_names.py ├── setup.sh ├── split_map.py ├── streamlit_app.py ├── streamlit_call.py └── xyz.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | private/ 6 | .vscode/ 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Qiusheng Wu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: sh setup.sh && streamlit run streamlit_app.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # streamlit-template 2 | 3 | A streamlit app template for geospatial applications based on [streamlit-option-menu](https://github.com/victoryhb/streamlit-option-menu). 4 | 5 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/giswqs/streamlit-template/master?urlpath=proxy/8501/) 6 | 7 | App URL: 8 | 9 | ![](https://i.imgur.com/xd64mCi.png) 10 | -------------------------------------------------------------------------------- /apps/callery_home.py: -------------------------------------------------------------------------------- 1 | import ee 2 | import pandas as pd 3 | import streamlit as st 4 | import geemap.foliumap as geemap 5 | 6 | 7 | def app(): 8 | st.title("Home") 9 | 10 | st.markdown( 11 | """ 12 | ##### Map of Callery Pear locations in the State of Tennessee 13 | 14 | """ 15 | ) 16 | 17 | col1, col2, col3 = st.columns(3) 18 | 19 | with col1: 20 | df = pd.read_csv("data/PyCTN.csv") 21 | st.session_state["locations"] = df 22 | names = df["Name"].values.tolist() 23 | names.sort() 24 | name = st.selectbox("Select a location", names) 25 | 26 | with col2: 27 | zoom = st.slider("Zoom", 1, 22, 8) 28 | 29 | with col3: 30 | st.info( 31 | """ 32 | Click the layer control in the upper-righh corner of the map to toggle between basemaps. 33 | 34 | """ 35 | ) 36 | 37 | latitude = df[df["Name"] == name]["latitude"].values[0] 38 | longitude = df[df["Name"] == name]["longitude"].values[0] 39 | 40 | m = geemap.Map(center=(latitude, longitude), zoom=zoom, locate_control=True) 41 | m.add_basemap("ROADMAP") 42 | m.add_basemap("HYBRID") 43 | states = ee.FeatureCollection("TIGER/2018/States") 44 | style = {"color": "000000", "width": 2, "fillColor": "00000000"} 45 | m.addLayer(states.style(**style), {}, "US States") 46 | m.add_points_from_xy( 47 | "data/PyCTN.csv", 48 | popup=["Name", "latitude", "longitude"], 49 | layer_name="Callery Pear Locations", 50 | ) 51 | m.to_streamlit(height=700) 52 | -------------------------------------------------------------------------------- /apps/callery_naip.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ee 3 | import geemap.foliumap as geemap 4 | import streamlit as st 5 | 6 | 7 | def app(): 8 | 9 | st.title("NAIP Imagery") 10 | 11 | st.markdown( 12 | """ 13 | NAIP: National Agriculture Imagery Program. See this [link](https://developers.google.com/earth-engine/datasets/catalog/USDA_NAIP_DOQQ) for more information. 14 | 15 | """ 16 | ) 17 | 18 | df = st.session_state["locations"] 19 | names = df["Name"].values.tolist() 20 | names.sort() 21 | 22 | col1, col2, col3, col5, col6, col7, _ = st.columns([1.8, 2, 2, 1, 1, 1, 1]) 23 | 24 | Map = geemap.Map(plugin_Draw=True, Draw_export=True) 25 | # with col1: 26 | # basemap = st.selectbox( 27 | # "Select a basemap", geemap.folium_basemaps.keys(), index=1) 28 | Map.add_basemap("HYBRID") 29 | Map.add_basemap("ROADMAP") 30 | 31 | with col1: 32 | name = st.selectbox("Select a location", names) 33 | latitude = df[df["Name"] == name]["latitude"].values[0] 34 | longitude = df[df["Name"] == name]["longitude"].values[0] 35 | 36 | # roi = ee.FeatureCollection("users/giswqs/MRB/NWI_HU8_Boundary_Simplify") 37 | # roi = ee.FeatureCollection( 38 | # "TIGER/2018/States").filter(ee.Filter.eq("NAME", "Tennessee")) 39 | roi = ee.Geometry.Point([longitude, latitude]).buffer(1000) 40 | 41 | style = {"color": "000000", "width": 2, "fillColor": "00000000"} 42 | 43 | with col2: 44 | checkbox = st.checkbox("Add NAIP imagery", value=True) 45 | 46 | with col5: 47 | lat = st.text_input("Center latitude", latitude) 48 | 49 | with col6: 50 | lon = st.text_input("Center longitude", longitude) 51 | 52 | with col7: 53 | zoom = st.slider("Zoom", 1, 22, 17) 54 | 55 | if checkbox: 56 | with col3: 57 | year = st.slider("Select a year", 2003, 2021, 2018) 58 | naip = ee.ImageCollection("USDA/NAIP/DOQQ") 59 | naip = naip.filter(ee.Filter.calendarRange(year, year, "year")) 60 | naip = naip.filterBounds(roi) 61 | 62 | # 2005, 2006, 2007, 63 | vis_params = {"bands": ["N", "R", "G"]} 64 | if year in [2005, 2006, 2007]: 65 | vis_params = {"bands": ["R", "G", "B"]} 66 | 67 | Map.addLayer(naip, vis_params, f"NAIP {year}") 68 | 69 | # Map.addLayer(roi.style(**style), {}, "Tennessee") 70 | 71 | Map.add_points_from_xy( 72 | "data/PyCTN.csv", 73 | popup=["Name", "latitude", "longitude"], 74 | layer_name="Callery Pear Locations", 75 | ) 76 | 77 | Map.set_center(float(lon), float(lat), int(zoom)) 78 | Map.to_streamlit(width=1400, height=700) 79 | -------------------------------------------------------------------------------- /apps/callery_photos.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import geemap.foliumap as geemap 3 | 4 | 5 | def app(): 6 | st.title("Photos") 7 | 8 | st.markdown( 9 | """ 10 | ##### Photos of Callery Pear locations along James White Parkway, Knoxville, TN (see [Google Map](https://goo.gl/maps/ogfSyf4k11pLLJVP9)) 11 | 12 | """ 13 | ) 14 | 15 | expander = st.expander("Click to show the photos", True) 16 | 17 | links = [ 18 | "https://i.imgur.com/lAXIidc.jpg", 19 | "https://i.imgur.com/R8X17Cr.jpg", 20 | "https://i.imgur.com/mHsz3NY.jpg", 21 | ] 22 | 23 | with expander: 24 | st.image(links) 25 | 26 | center = (35.964760, -83.899257) 27 | m = geemap.Map(center=center, zoom=18, locate_control=True) 28 | m.add_basemap("ROADMAP") 29 | m.add_basemap("HYBRID") 30 | m.add_marker(location=center, tooltip="Callery Pear") 31 | m.to_streamlit(height=700) 32 | -------------------------------------------------------------------------------- /apps/callery_planet.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ee 3 | import geemap.foliumap as geemap 4 | import streamlit as st 5 | import pandas as pd 6 | 7 | 8 | def app(): 9 | 10 | st.title("Planet Imagery") 11 | st.markdown( 12 | """ 13 | See https://www.planet.com for more information. 14 | """ 15 | ) 16 | 17 | df = st.session_state["locations"] 18 | 19 | col1, col2, col3, _, col4, col5, col6, col7, _ = st.columns( 20 | [3, 0.7, 1, 0.2, 1, 1, 1, 1, 1] 21 | ) 22 | 23 | Map = geemap.Map(locate_control=True, plugin_LatLngPopup=False) 24 | 25 | Map.add_basemap("HYBRID") 26 | Map.add_basemap("ROADMAP") 27 | 28 | names = df["Name"].values.tolist() 29 | names.sort() 30 | 31 | with col1: 32 | name = st.selectbox("Select a location", names) 33 | latitude = df[df["Name"] == name]["latitude"].values[0] 34 | longitude = df[df["Name"] == name]["longitude"].values[0] 35 | 36 | with col2: 37 | ratio = st.radio("Planet imagery", ('Quarterly', "Monthly")) 38 | 39 | with col3: 40 | year = st.slider("Select a year", 2016, 2021, 2020) 41 | 42 | with col5: 43 | lat = st.text_input("Center latitude", latitude) 44 | 45 | with col6: 46 | lon = st.text_input("Center longitude", longitude) 47 | 48 | with col7: 49 | zoom = st.slider("Zoom", 1, 22, 17) 50 | 51 | if ratio == "Quarterly": 52 | with col4: 53 | quarter = st.slider("Select a quarter", 1, 4, 1) 54 | Map.add_planet_by_quarter(year, quarter) 55 | else: 56 | with col4: 57 | month = st.slider("Select a month", 1, 12, 1) 58 | Map.add_planet_by_month(year, month) 59 | 60 | states = ee.FeatureCollection("TIGER/2018/States") 61 | style = {"color": "000000", "width": 2, "fillColor": "00000000"} 62 | Map.addLayer(states.style(**style), {}, "US States") 63 | Map.add_points_from_xy( 64 | "data/PyCTN.csv", 65 | popup=["Name", "latitude", "longitude"], 66 | layer_name="Callery Pear Locations", 67 | ) 68 | Map.set_center(float(lon), float(lat), int(zoom)) 69 | Map.to_streamlit(height=700) 70 | -------------------------------------------------------------------------------- /apps/cog.py: -------------------------------------------------------------------------------- 1 | import os 2 | import leafmap.foliumap as leafmap 3 | import streamlit as st 4 | import palettable 5 | 6 | 7 | @st.cache 8 | def load_cog_list(): 9 | print(os.getcwd()) 10 | in_txt = os.path.join(os.getcwd(), "data/cog_files.txt") 11 | with open(in_txt) as f: 12 | return [line.strip() for line in f.readlines()[1:]] 13 | 14 | 15 | @st.cache 16 | def get_palettes(): 17 | palettes = dir(palettable.matplotlib)[:-16] 18 | return ["matplotlib." + p for p in palettes] 19 | 20 | 21 | def app(): 22 | 23 | st.title("Visualize COG") 24 | st.markdown( 25 | """ 26 | An interactive web app for visualizing local raster datasets and Cloud Optimized GeoTIFF ([COG](https://www.cogeo.org)). The app was built using [streamlit](https://streamlit.io) and [leafmap](https://leafmap.org). 27 | 28 | 29 | """ 30 | ) 31 | 32 | row1_col1, row1_col2 = st.columns([2, 1]) 33 | 34 | with row1_col1: 35 | cog_list = load_cog_list() 36 | cog = st.selectbox("Select a sample Cloud Opitmized GeoTIFF (COG)", cog_list) 37 | 38 | with row1_col2: 39 | empty = st.empty() 40 | 41 | url = empty.text_input( 42 | "Enter a HTTP URL to a Cloud Optimized GeoTIFF (COG)", 43 | cog, 44 | ) 45 | 46 | add_palette = st.checkbox("Add a color palette") 47 | if add_palette: 48 | options = [i.lower() for i in leafmap.list_palettes()] 49 | options.sort() 50 | palette = st.selectbox("Select a color palette", options) 51 | else: 52 | palette = None 53 | 54 | submit = st.button("Submit") 55 | 56 | m = leafmap.Map(latlon_control=False) 57 | 58 | if submit: 59 | if url: 60 | try: 61 | m.add_cog_layer(url, palette=palette) 62 | except Exception as e: 63 | with row1_col2: 64 | st.error(e) 65 | st.error("Work in progress. Try it again later.") 66 | 67 | with row1_col1: 68 | m.to_streamlit() 69 | -------------------------------------------------------------------------------- /apps/flickering.py: -------------------------------------------------------------------------------- 1 | import ee 2 | import geemap.foliumap as geemap 3 | import streamlit as st 4 | from streamlit_folium import st_folium 5 | 6 | m = geemap.Map() 7 | dem = ee.Image("USGS/SRTMGL1_003") 8 | m.addLayer(dem, {}, "DEM") 9 | st_data = st_folium(m, width=1000) -------------------------------------------------------------------------------- /apps/get_bounds.py: -------------------------------------------------------------------------------- 1 | from unicodedata import bidirectional 2 | from urllib import response 3 | import folium 4 | import pandas as pd 5 | import streamlit as st 6 | import leafmap.foliumap as leafmap 7 | from streamlit_folium import st_folium 8 | 9 | 10 | def app(): 11 | 12 | st.title('Streamlit folium bidirectional functionality') 13 | 14 | col1, col2 = st.columns([3, 1]) 15 | m = leafmap.Map() 16 | 17 | with col1: 18 | output = m.to_streamlit(bidirectional=True) 19 | 20 | with col2: 21 | try: 22 | center = m.st_map_center(output) 23 | st.text_input('Map Center Latitude', center[0]) 24 | st.text_input('Map center Longitude', center[1]) 25 | st.write(output) 26 | except: 27 | pass 28 | -------------------------------------------------------------------------------- /apps/heatmap.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import leafmap.foliumap as leafmap 3 | 4 | 5 | def app(): 6 | 7 | st.title("Heatmap") 8 | 9 | filepath = "https://raw.githubusercontent.com/giswqs/leafmap/master/examples/data/us_cities.csv" 10 | m = leafmap.Map(tiles="stamentoner") 11 | m.add_heatmap( 12 | filepath, 13 | latitude="latitude", 14 | longitude="longitude", 15 | value="pop_max", 16 | name="Heat map", 17 | radius=20, 18 | ) 19 | m.to_streamlit(height=700) 20 | -------------------------------------------------------------------------------- /apps/home.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import leafmap.foliumap as leafmap 3 | 4 | 5 | def app(): 6 | st.title("Home") 7 | 8 | st.markdown( 9 | """ 10 | A [streamlit](https://streamlit.io) app template for geospatial applications based on [streamlit-option-menu](https://github.com/victoryhb/streamlit-option-menu). 11 | To create a direct link to a pre-selected menu, add `?page=` to the URL, e.g., `?page=upload`. 12 | https://share.streamlit.io/giswqs/streamlit-template?page=upload 13 | 14 | """ 15 | ) 16 | 17 | m = leafmap.Map(locate_control=True) 18 | m.add_basemap("ROADMAP") 19 | m.to_streamlit(height=700) 20 | -------------------------------------------------------------------------------- /apps/osm_names.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import leafmap.foliumap as leafmap 3 | import streamlit as st 4 | 5 | 6 | def search(name, limit): 7 | url = 'https://photon.komoot.io/api/' 8 | r = requests.get(url, params={'q': name, 'limit': limit}).json() 9 | df = leafmap.geojson_to_df(r, drop_geometry=False) 10 | df['longitude'] = df['geometry.coordinates'].str[0] 11 | df['latitude'] = df['geometry.coordinates'].str[1] 12 | df['country'] = df['countrycode'] 13 | df.drop(['type', 'geometry.coordinates', 'countrycode'], axis=1, inplace=True) 14 | return df 15 | 16 | 17 | def app(): 18 | 19 | st.title('Search Geographic Names') 20 | col1, col2 = st.columns([3, 1]) 21 | 22 | m = leafmap.Map(latlon_control=False, center=(40, -100), zoom=4) 23 | m.add_basemap('HYBRID') 24 | m.add_basemap('ROADMAP') 25 | m.add_basemap('OpenStreetMap') 26 | 27 | with col2: 28 | limit = st.slider("The number of results to return", 1, 8000, 1000) 29 | name = st.text_input("Enter a name") 30 | if name: 31 | try: 32 | df = search(name, limit) 33 | if not df.empty: 34 | column = st.selectbox( 35 | "Filter by", df.columns, index=list(df.columns).index('country') 36 | ) 37 | if "US" in df[column].unique(): 38 | default = "US" 39 | else: 40 | default = df[column].unique() 41 | filters = st.multiselect( 42 | "Select values", 43 | df[column].unique(), 44 | default=default, 45 | ) 46 | 47 | if filters: 48 | df = df[df[column].isin(filters)] 49 | st.text(f"Found {len(df)} results") 50 | leafmap.st_download_button("Download data", df, csv_sep="\t") 51 | m.add_points_from_xy( 52 | df, 53 | x='longitude', 54 | y='latitude', 55 | popup=[ 56 | 'name', 57 | 'longitude', 58 | 'latitude', 59 | 'osm_key', 60 | 'city', 61 | 'county', 62 | 'state', 63 | 'country', 64 | ], 65 | ) 66 | else: 67 | st.error("No results found") 68 | except Exception as e: 69 | st.error("No results found") 70 | 71 | with col1: 72 | m.to_streamlit(height=700) 73 | -------------------------------------------------------------------------------- /apps/scotland.py: -------------------------------------------------------------------------------- 1 | import folium 2 | import pandas as pd 3 | import streamlit as st 4 | import leafmap.foliumap as leafmap 5 | import folium.plugins as plugins 6 | 7 | def app(): 8 | 9 | st.title('National Library of Scotland XYZ Layers') 10 | df = pd.read_csv('data/scotland_xyz.tsv', sep='\t') 11 | basemaps = leafmap.basemaps 12 | names = df['Name'].values.tolist() + list(basemaps.keys()) 13 | links = df['URL'].values.tolist() + list(basemaps.values()) 14 | 15 | col1, col2, col3, col4, col5, col6 = st.columns([3, 3, 1, 1, 1, 1.5]) 16 | with col1: 17 | left_name = st.selectbox( 18 | 'Select the left layer', names, index=names.index('Great Britain - Bartholomew Half Inch, 1897-1907') 19 | ) 20 | 21 | with col2: 22 | right_name = st.selectbox( 23 | 'Select the right layer', 24 | names, 25 | index=names.index('HYBRID'), 26 | ) 27 | 28 | with col3: 29 | # lat = st.slider('Latitude', -90.0, 90.0, 55.68, step=0.01) 30 | lat = st.text_input('Latitude', " 55.68") 31 | 32 | with col4: 33 | # lon = st.slider('Longitude', -180.0, 180.0, -2.98, step=0.01) 34 | lon = st.text_input('Longitude', "-2.98") 35 | 36 | with col5: 37 | # zoom = st.slider('Zoom', 1, 24, 6, step=1) 38 | zoom = st.text_input('Zoom', "6") 39 | 40 | with col6: 41 | checkbox = st.checkbox('Add OS 25 inch') 42 | 43 | # with col7: 44 | with st.expander("Acknowledgements"): 45 | markdown = """ 46 | The map tile access is by kind arrangement of the National Library of Scotland on the understanding that re-use is for personal purposes. They host most of the map layers except these: 47 | - The Roy Maps are owned by the British Library. 48 | - The Great Britain – OS maps 1:25,000, 1937-61 and One Inch 7th series, 1955-61 are hosted by MapTiler. 49 | 50 | If you wish you use these layers within a website, or for a commercial or public purpose, please view the [National Library of Scotland Historic Maps Subscription API](https://maps.nls.uk/projects/subscription-api/) or contact them at maps@nls.uk. 51 | """ 52 | st.markdown(markdown, unsafe_allow_html=True) 53 | 54 | m = leafmap.Map( 55 | center=[float(lat), float(lon)], 56 | zoom=int(zoom), 57 | locate_control=True, 58 | draw_control=False, 59 | measure_control=False, 60 | ) 61 | measure = plugins.MeasureControl(position="bottomleft", active_color = 'orange') 62 | measure.add_to(m) 63 | 64 | if left_name in basemaps: 65 | left_layer = basemaps[left_name] 66 | else: 67 | left_layer = folium.TileLayer( 68 | tiles=links[names.index(left_name)], 69 | name=left_name, 70 | attr='National Library of Scotland', 71 | overlay=True, 72 | ) 73 | 74 | if right_name in basemaps: 75 | right_layer = basemaps[right_name] 76 | else: 77 | right_layer = folium.TileLayer( 78 | tiles=links[names.index(right_name)], 79 | name=right_name, 80 | attr='National Library of Scotland', 81 | overlay=True, 82 | ) 83 | 84 | if checkbox: 85 | for index, name in enumerate(names): 86 | if 'OS 25 inch' in name: 87 | m.add_tile_layer( 88 | links[index], name, attribution='National Library of Scotland' 89 | ) 90 | 91 | m.split_map(left_layer, right_layer) 92 | m.to_streamlit(height=600) 93 | -------------------------------------------------------------------------------- /apps/split.py: -------------------------------------------------------------------------------- 1 | import ee 2 | import folium 3 | import streamlit as st 4 | import geemap.foliumap as geemap 5 | 6 | 7 | def app(): 8 | 9 | st.title('Split-panel Map') 10 | 11 | markdown = """The split-panel map requires two layers: `left_layer` and `right_layer`. The layer instance can be a string representing a basemap, or an HTTP URL to a Cloud Optimized GeoTIFF (COG), or a folium TileLayer instance.""" 12 | st.markdown(markdown) 13 | 14 | st.header('Using basemaps') 15 | with st.echo(): 16 | m = geemap.Map(height=500) 17 | m.split_map(left_layer='TERRAIN', right_layer='OpenTopoMap') 18 | m.to_streamlit(height=600) 19 | 20 | st.header('Using COG') 21 | with st.echo(): 22 | m = geemap.Map(height=600, center=[39.4948, -108.5492], zoom=12) 23 | url = 'https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-02-16/pine-gulch-fire20/1030010076004E00.tif' 24 | url2 = 'https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-14/pine-gulch-fire20/10300100AAC8DD00.tif' 25 | m.split_map(url, url2) 26 | m.to_streamlit(height=600) 27 | 28 | st.header('Using folium TileLayer') 29 | with st.echo(): 30 | m = geemap.Map(center=[40, -100], zoom=4) 31 | 32 | url1 = ( 33 | 'https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2001_Land_Cover_L48/wms?' 34 | ) 35 | url2 = ( 36 | 'https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2019_Land_Cover_L48/wms?' 37 | ) 38 | 39 | left_layer = folium.WmsTileLayer( 40 | url=url1, 41 | layers='NLCD_2001_Land_Cover_L48', 42 | name='NLCD 2001', 43 | attr='MRLC', 44 | fmt="image/png", 45 | transparent=True, 46 | ) 47 | right_layer = folium.WmsTileLayer( 48 | url=url2, 49 | layers='NLCD_2019_Land_Cover_L48', 50 | name='NLCD 2019', 51 | attr='MRLC', 52 | fmt="image/png", 53 | transparent=True, 54 | ) 55 | 56 | m.split_map(left_layer, right_layer) 57 | m.add_legend(builtin_legend='NLCD') 58 | m.to_streamlit(height=600) 59 | 60 | st.header('Using Earth Engine layers') 61 | with st.echo(): 62 | m = geemap.Map(center=[39.3322, -106.7349], zoom=10) 63 | srtm = ee.Image("USGS/SRTMGL1_003") 64 | hillshade = ee.Terrain.hillshade(srtm) 65 | vis = { 66 | 'min': 0, 67 | 'max': 5000, 68 | 'palette': ["006633", "E5FFCC", "662A00", "D8D8D8", "F5F5F5"], 69 | } 70 | left_layer = geemap.ee_tile_layer(hillshade, name='Hillshade') 71 | right_layer = geemap.ee_tile_layer(srtm, vis, name='DEM') 72 | m.split_map(left_layer, right_layer) 73 | m.to_streamlit(height=600) 74 | -------------------------------------------------------------------------------- /apps/upload.py: -------------------------------------------------------------------------------- 1 | import os 2 | import geopandas as gpd 3 | import streamlit as st 4 | 5 | 6 | def save_uploaded_file(file_content, file_name): 7 | """ 8 | Save the uploaded file to a temporary directory 9 | """ 10 | import tempfile 11 | import os 12 | import uuid 13 | 14 | _, file_extension = os.path.splitext(file_name) 15 | file_id = str(uuid.uuid4()) 16 | file_path = os.path.join(tempfile.gettempdir(), f"{file_id}{file_extension}") 17 | 18 | with open(file_path, "wb") as file: 19 | file.write(file_content.getbuffer()) 20 | 21 | return file_path 22 | 23 | 24 | def app(): 25 | 26 | st.title("Upload Vector Data") 27 | 28 | row1_col1, row1_col2 = st.columns([2, 1]) 29 | width = 950 30 | height = 600 31 | 32 | with row1_col2: 33 | 34 | backend = st.selectbox( 35 | "Select a plotting backend", ["folium", "kepler.gl", "pydeck"], index=2 36 | ) 37 | 38 | if backend == "folium": 39 | import leafmap.foliumap as leafmap 40 | elif backend == "kepler.gl": 41 | import leafmap.kepler as leafmap 42 | elif backend == "pydeck": 43 | import leafmap.deck as leafmap 44 | 45 | url = st.text_input( 46 | "Enter a URL to a vector dataset", 47 | "https://github.com/giswqs/streamlit-geospatial/raw/master/data/us_states.geojson", 48 | ) 49 | 50 | data = st.file_uploader( 51 | "Upload a vector dataset", type=["geojson", "kml", "zip", "tab"] 52 | ) 53 | 54 | container = st.container() 55 | 56 | if data or url: 57 | if data: 58 | file_path = save_uploaded_file(data, data.name) 59 | layer_name = os.path.splitext(data.name)[0] 60 | elif url: 61 | file_path = url 62 | layer_name = url.split("/")[-1].split(".")[0] 63 | 64 | with row1_col1: 65 | if file_path.lower().endswith(".kml"): 66 | gpd.io.file.fiona.drvsupport.supported_drivers["KML"] = "rw" 67 | gdf = gpd.read_file(file_path, driver="KML") 68 | else: 69 | gdf = gpd.read_file(file_path) 70 | lon, lat = leafmap.gdf_centroid(gdf) 71 | if backend == "pydeck": 72 | 73 | column_names = gdf.columns.values.tolist() 74 | random_column = None 75 | with container: 76 | random_color = st.checkbox("Apply random colors", True) 77 | if random_color: 78 | random_column = st.selectbox( 79 | "Select a column to apply random colors", column_names 80 | ) 81 | 82 | m = leafmap.Map(center=(40, -100)) 83 | # m = leafmap.Map(center=(lat, lon)) 84 | m.add_gdf(gdf, random_color_column=random_column) 85 | st.pydeck_chart(m) 86 | 87 | else: 88 | m = leafmap.Map(center=(lat, lon), draw_export=True) 89 | m.add_gdf(gdf, layer_name=layer_name) 90 | if backend == "folium": 91 | m.zoom_to_gdf(gdf) 92 | m.to_streamlit(width=width, height=height) 93 | 94 | else: 95 | with row1_col1: 96 | m = leafmap.Map() 97 | st.pydeck_chart(m) 98 | -------------------------------------------------------------------------------- /apps/viirs.py: -------------------------------------------------------------------------------- 1 | import ee 2 | import folium 3 | import streamlit as st 4 | import geemap.foliumap as geemap 5 | import geemap.colormaps as cm 6 | 7 | 8 | @st.cache 9 | def uploaded_file_to_gdf(data): 10 | import tempfile 11 | import os 12 | import uuid 13 | 14 | _, file_extension = os.path.splitext(data.name) 15 | file_id = str(uuid.uuid4()) 16 | file_path = os.path.join(tempfile.gettempdir(), f"{file_id}{file_extension}") 17 | 18 | with open(file_path, "wb") as file: 19 | file.write(data.getbuffer()) 20 | 21 | if file_path.lower().endswith(".kml"): 22 | gpd.io.file.fiona.drvsupport.supported_drivers["KML"] = "rw" 23 | gdf = gpd.read_file(file_path, driver="KML") 24 | else: 25 | gdf = gpd.read_file(file_path) 26 | 27 | return gdf 28 | 29 | 30 | def app(): 31 | 32 | st.title('Nighttime Light Data Analysis') 33 | 34 | # markdown = """The split-panel map requires two layers: `left_layer` and `right_layer`. The layer instance can be a string representing a basemap, or an HTTP URL to a Cloud Optimized GeoTIFF (COG), or a folium TileLayer instance.""" 35 | # st.markdown(markdown) 36 | 37 | with st.expander("Click to see the data sources", False): 38 | markdown = """ 39 | 40 | - [VIIRS Stray Light Corrected Nighttime Day/Night Band Composites Version 1](https://developers.google.com/earth-engine/datasets/catalog/NOAA_VIIRS_DNB_MONTHLY_V1_VCMSLCFG) (463.83 m) 41 | 42 | """ 43 | st.markdown(markdown) 44 | 45 | row1_col1, _, row1_col2 = st.columns([3, 0.03, 1]) 46 | 47 | Map = geemap.Map( 48 | add_google_map=False, 49 | plugin_LatLngPopup=False, 50 | locate_control=True, 51 | plugin_Draw=True, 52 | ) 53 | Map.add_basemap("HYBRID") 54 | Map.add_basemap("TERRAIN") 55 | 56 | fc = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017') 57 | names = fc.aggregate_array('country_na').getInfo() 58 | names = list(set(names)) 59 | names.sort() 60 | palettes = cm.list_colormaps() 61 | 62 | nightlight = ee.ImageCollection("NOAA/VIIRS/DNB/MONTHLY_V1/VCMSLCFG") 63 | 64 | with row1_col2: 65 | with st.form("get_NTL"): 66 | years = st.slider("Select years", 2014, 2021, [2014, 2021]) 67 | months = st.slider("Select months", 1, 12, [1, 12]) 68 | min_max = st.slider( 69 | "Select visualization min-max", 0.0, 50.0, [0.0, 10.0], 0.5 70 | ) 71 | palette = st.selectbox( 72 | "Select a palette", palettes, index=palettes.index("gist_earth") 73 | ) 74 | countries = st.multiselect("Select countries", names) 75 | diff_chk = st.checkbox("NTL differencing: end_year - start_year") 76 | trans_chk = st.checkbox("Make low values transparent") 77 | split = st.checkbox("Split-panel map") 78 | 79 | submitted = st.form_submit_button("Submit") 80 | 81 | if submitted: 82 | 83 | start_a = ee.Date.fromYMD(years[0], months[0], 1) 84 | end_a = start_a.advance(months[1] - months[0], "month") 85 | 86 | start_b = ee.Date.fromYMD(years[1], months[0], 1) 87 | end_b = start_b.advance(months[1] - months[0], "month") 88 | 89 | ntl_a = nightlight.filterDate(start_a, end_a).mean().select("avg_rad") 90 | ntl_b = nightlight.filterDate(start_b, end_b).mean().select("avg_rad") 91 | 92 | if countries: 93 | selected_countries = ee.FeatureCollection( 94 | 'USDOS/LSIB_SIMPLE/2017' 95 | ).filter(ee.Filter.inList("country_na", countries)) 96 | country_style = selected_countries.style( 97 | **{'color': '000000', 'width': 2, 'fillColor': '00000000'} 98 | ) 99 | Map.addLayer(country_style, {}, "Countries") 100 | Map.centerObject(selected_countries, 4) 101 | 102 | ntl_a = ntl_a.clip(selected_countries) 103 | ntl_b = ntl_b.clip(selected_countries) 104 | 105 | if trans_chk: 106 | ntl_a = ntl_a.updateMask(ntl_a.gt(min_max[0])) 107 | ntl_b = ntl_b.updateMask(ntl_b.gt(min_max[0])) 108 | 109 | if split: 110 | left_layer = geemap.ee_tile_layer( 111 | ntl_a, 112 | { 113 | 'min': min_max[0], 114 | 'max': min_max[1], 115 | 'palette': cm.get_palette(palette, 15), 116 | }, 117 | f'NTL {years[0]}', 118 | ) 119 | right_layer = geemap.ee_tile_layer( 120 | ntl_b, 121 | { 122 | 'min': min_max[0], 123 | 'max': min_max[1], 124 | 'palette': cm.get_palette(palette, 15), 125 | }, 126 | f'NTL {years[1]}', 127 | ) 128 | Map.split_map(left_layer, right_layer) 129 | else: 130 | Map.addLayer( 131 | ntl_a, 132 | { 133 | 'min': min_max[0], 134 | 'max': min_max[1], 135 | 'palette': cm.get_palette(palette, 15), 136 | }, 137 | f'NTL {years[0]}', 138 | ) 139 | Map.addLayer( 140 | ntl_b, 141 | { 142 | 'min': min_max[0], 143 | 'max': min_max[1], 144 | 'palette': cm.get_palette(palette, 15), 145 | }, 146 | f'NTL {years[1]}', 147 | ) 148 | 149 | if diff_chk: 150 | diff = ntl_b.subtract(ntl_a) 151 | Map.addLayer( 152 | diff, 153 | { 154 | 'min': min_max[0], 155 | 'max': min_max[1], 156 | 'palette': cm.get_palette(palette, 15), 157 | }, 158 | f'NTL {years[1]} - {years[0]}', 159 | ) 160 | 161 | with row1_col1: 162 | Map.to_streamlit(height=650) 163 | -------------------------------------------------------------------------------- /callery_pear.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from streamlit_option_menu import option_menu 3 | 4 | # import your app modules here 5 | from apps import callery_home, callery_naip, callery_photos, callery_planet 6 | 7 | st.set_page_config(page_title="Callery Pear", layout="wide") 8 | 9 | # A dictionary of apps in the format of {"App title": "App icon"} 10 | # More icons can be found here: https://icons.getbootstrap.com 11 | 12 | apps = [ 13 | {"func": callery_home.app, "title": "Home", "icon": "house"}, 14 | {"func": callery_photos.app, "title": "Photos", "icon": "images"}, 15 | {"func": callery_naip.app, "title": "NAIP Imagery (1-m)", "icon": "globe"}, 16 | {"func": callery_planet.app, "title": "Planet Imagery (5-m)", "icon": "camera"}, 17 | ] 18 | 19 | titles = [app["title"] for app in apps] 20 | icons = [app["icon"] for app in apps] 21 | 22 | params = st.experimental_get_query_params() 23 | 24 | if "page" in params: 25 | default_index = int(titles.index(params["page"][0].lower())) 26 | else: 27 | default_index = 0 28 | 29 | with st.sidebar: 30 | selected = option_menu( 31 | "Main Menu", 32 | options=titles, 33 | icons=icons, 34 | menu_icon="cast", 35 | default_index=default_index, 36 | ) 37 | 38 | st.sidebar.title("About") 39 | st.sidebar.info( 40 | """ 41 | Web App URL: 42 | 43 | Contact Qiusheng Wu (qwu18@utk.edu) if you have any questions or comments. 44 | """ 45 | ) 46 | 47 | for app in apps: 48 | if app["title"] == selected: 49 | app["func"]() 50 | break 51 | -------------------------------------------------------------------------------- /data/PyCTN.csv: -------------------------------------------------------------------------------- 1 | Name,latitude,longitude 2 | Near Schumpert Park,36.039581,-83.958827 3 | Haw Ridge and Greenway Area,36.001379,-84.184458 4 | North Boundary Greenway Area,35.954901,-84.364534 5 | Chuck Swan State Forest area,36.350828,-83.918664 6 | Hatfield Knob Viewing Tower,36.440746,-84.123799 7 | Big South Fork Natl River & Rec Area,36.46869,-84.646313 8 | Along N/S transect of Hwy 154 near Pickett SP,36.569066,-84.799185 9 | Along N/S transect of Hwy 154 near Pogue Creek Canyon State Natural Area,36.522494,-84.815225 10 | near Fall Creek Falls SP,35.734975,-85.440727 11 | Near Cove Lake St. Park,36.308739,-84.211149 12 | Near Panther Creek State Park,36.205953,-83.407045 13 | Near Big Ridge S.P.,36.240585,-83.930859 14 | Anderson County Park,36.27495,-84.031888 15 | Along Norris Freeway = 441 S,36.216416,-84.079714 16 | along 441 S,36.23254,-84.108609 17 | Cove Creek WMA,36.266805,-84.102333 18 | N/S transect of Hwy 12 Crossville ,35.94469,-85.042113 19 | N/S transect of Hwy 13 S Pikeville,35.604366,-85.194416 20 | N/S transect of Hwy 14 Dunlap,35.373862,-85.384318 21 | N/S transect of Hwy 15 Whitwell,35.197823,-85.523146 22 | 411 S Maryville,35.748868,-84.003007 23 | 412 S Vonore,35.593349,-84.214805 24 | 413 S Madisonville,35.500817,-84.387837 25 | 414 S Englewood,35.414914,-84.485861 26 | 415 S Etowah,35.337415,-84.515481 27 | 416 S Delano,35.269568,-84.55955 28 | Seven Islands Birding Park,35.953968,-83.686354 29 | Fairfield Glade area near Crossville,36.005656,-84.879966 30 | Plateau REC,36.013044,-85.13638 31 | Tullahoma REC,35.305493,-86.158715 32 | Gatlinburg - GSNP,35.733239,-83.44317 33 | Townsend TN,35.677082,-83.745208 34 | GSMNP Near Tellico Plains,35.376757,-84.273718 35 | GSMNP near Greenbriar,35.738639,-83.416437 36 | -------------------------------------------------------------------------------- /data/cog_files.txt: -------------------------------------------------------------------------------- 1 | https://www.maxar.com/open-data/california-colorado-fires 2 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-02-16/pine-gulch-fire20/1030010076004E00.tif 3 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-08-18/pine-gulch-fire20/1040010041D3B300.tif 4 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-11-13/grizzly-creek-fire20/1040010045785200.tif 5 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-11-13/grizzly-creek-fire20/10400100443AEC00.tif 6 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-02-06/czu-lightning-complex-fire/104001004941E100.tif 7 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-02-18/cameron-peak-fire20/103001008DA5B500.tif 8 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-02-22/czu-lightning-complex-fire/103001008DB2E200.tif 9 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-04-01/grizzly-creek-fire20/104001004881EF00.tif 10 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-04-17/czu-lightning-complex-fire/103001008F905300.tif 11 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-04-17/czu-lightning-complex-fire/1030010092B22200.tif 12 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-06-27/czu-lightning-complex-fire/1030010094A52300.tif 13 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-09-08/czu-lightning-complex-fire/103001009C9FBB00.tif 14 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-09-24/lnu-lightning-complex-fire/103001009A079B00.tif 15 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-10-05/czu-lightning-complex-fire/103001009C10F800.tif 16 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-10-05/czu-lightning-complex-fire/103001009A266800.tif 17 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-11-04/czu-lightning-complex-fire/1050010019917900.tif 18 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-11-04/czu-lightning-complex-fire/1050010019917800.tif 19 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-11-18/czu-lightning-complex-fire/1050010019C2F600.tif 20 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-11-28/cameron-peak-fire20/103001009D72E000.tif 21 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-12-10/czu-lightning-complex-fire/105001001A3A8700.tif 22 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-12-28/lnu-lightning-complex-fire/10300100A1972700.tif 23 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-12-28/lnu-lightning-complex-fire/103001009F5D6B00.tif 24 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-01-15/cameron-peak-fire20/1040010057992100.tif 25 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-04-15/lnu-lightning-complex-fire/10300100A4B23600.tif 26 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-04-23/czu-lightning-complex-fire/10300100A589D100.tif 27 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-05-09/lnu-lightning-complex-fire/10300100A332EE00.tif 28 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-05-23/river-carmel-fires/10300100A77E9400.tif 29 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-05-23/river-carmel-fires/10300100A500A500.tif 30 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-05-24/river-carmel-fires/105001001D64E200.tif 31 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-06-27/lnu-lightning-complex-fire/10300100A8663800.tif 32 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-06-30/river-carmel-fires/10300100A9D60C00.tif 33 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-06-30/czu-lightning-complex-fire/10300100A8C66400.tif 34 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-06-30/czu-lightning-complex-fire/10300100A8892900.tif 35 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-07-11/czu-lightning-complex-fire/10300100AB381200.tif 36 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-07-11/czu-lightning-complex-fire/10300100AA180600.tif 37 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-07-13/pine-gulch-fire20/10300100AA57D700.tif 38 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-07-20/lnu-lightning-complex-fire/104001005C529000.tif 39 | https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-07-28/pine-gulch-fire20/104001005DB06E00.tif 40 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-14/pine-gulch-fire20/10300100AAC8DD00.tif 41 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-16/pine-gulch-fire20/104001005D4A6100.tif 42 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-17/grizzly-creek-fire20/10300100ACCA3700.tif 43 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-17/cameron-peak-fire20/10300100AB4ED400.tif 44 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-20/swir-cog/104A0100606FFE00.tif 45 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-20/pine-gulch-fire20/10300100ACD06200.tif 46 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-20/pine-gulch-fire20/10300100AAD4A000.tif 47 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-20/pine-gulch-fire20/10300100AA293800.tif 48 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-20/lnu-lightning-complex-fire/10400100606FFE00.tif 49 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/river-carmel-fires/10300100ACBA2B00.tif 50 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/river-carmel-fires/10300100AA49F600.tif 51 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/lnu-lightning-complex-fire/104001005C1AC900.tif 52 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/river-carmel-fires/104001005F9F5300.tif 53 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/river-carmel-fires/104001005F453300.tif 54 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/river-carmel-fires/10300100ADC14400.tif 55 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/czu-lightning-complex-fire/104001005F43D400.tif 56 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-23/grizzly-creek-fire20/104001005FA09C00.tif 57 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-23/grizzly-creek-fire20/104001005DC71000.tif 58 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-26/river-carmel-fires/105001001F58F000.tif 59 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-26/lnu-lightning-complex-fire/10300100AC163A00.tif 60 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-29/river-carmel-fires/10300100AAD27500.tif 61 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-29/river-carmel-fires/10300100A9C75A00.tif 62 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-09-03/cameron-peak-fire20/1040010060188800.tif 63 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-09-03/cameron-peak-fire20/104001005F7E6500.tif 64 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-09-03/cameron-peak-fire20/10300100AE685A00.tif 65 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-09-04/cameron-peak-fire20/1040010060761C00.tif 66 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-05/cameron-peak-fire20/104001006113B700.tif 67 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-05/cameron-peak-fire20/10400100610CD400.tif 68 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-12/cameron-peak-fire20/1040010062B14C00.tif 69 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-12/cameron-peak-fire20/10400100626BFA00.tif 70 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-12/cameron-peak-fire20/10400100622A6600.tif 71 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-12/cameron-peak-fire20/10400100606B6300.tif 72 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-12/cameron-peak-fire20/104001005F908800.tif 73 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-15/cameron-peak-fire20/10500100205EDA00.tif 74 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-15/cameron-peak-fire20/10500100205ED900.tif 75 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-22/east-troublesome-fire20/10300100B0004A00.tif 76 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-22/east-troublesome-fire20/10300100AD0D1200.tif 77 | https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-22/east-troublesome-fire20/10300100AD0CA600.tif 78 | -------------------------------------------------------------------------------- /data/scotland_xyz.tsv: -------------------------------------------------------------------------------- 1 | Name URL 2 | Ordnance Survey - Air Photos, 1944-1950 - 1:10,560 https://geo.nls.uk/maps/air-photos/{z}/{x}/{y}.png 3 | Ordnance Survey - Six Inch Scotland, 1843-1882 - 1:10,560 https://mapseries-tilesets.s3.amazonaws.com/os/6inchfirst/{z}/{x}/{y}.png 4 | War Office, Great Britain 1:25,000. GSGS 3906, 1940-43 https://mapseries-tilesets.s3.amazonaws.com/gsgs3906/{z}/{x}/{y}.png 5 | Roy - Roy Highlands, 1747-1752 - 1:36000 https://mapseries-tilesets.s3.amazonaws.com/roy/highlands/{z}/{x}/{y}.png 6 | Roy - Roy Lowlands, 1752-1755 - 1:36000 https://mapseries-tilesets.s3.amazonaws.com/roy/lowlands/{z}/{x}/{y}.png 7 | Great Britain - OS 1:10,560, 1949-1970 https://mapseries-tilesets.s3.amazonaws.com/os/britain10knatgrid/{z}/{x}/{y}.png 8 | Great Britain - Bartholomew Half Inch, 1897-1907 https://mapseries-tilesets.s3.amazonaws.com/bartholomew_great_britain/{z}/{x}/{y}.png 9 | OS 25 inch, 1892-1914 - Scotland South https://mapseries-tilesets.s3.amazonaws.com/25_inch/scotland_1/{z}/{x}/{y}.png 10 | OS 25 inch, 1892-1914 - Scotland North https://mapseries-tilesets.s3.amazonaws.com/25_inch/scotland_2/{z}/{x}/{y}.png 11 | OS 25 inch, 1892-1914 - Bedfordshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/bedfordshire/{z}/{x}/{y}.png 12 | OS 25 inch, 1892-1914 - Berkshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/berkshire/{z}/{x}/{y}.png 13 | OS 25 inch, 1892-1914 - Buckinghamshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/buckingham/{z}/{x}/{y}.png 14 | OS 25 inch, 1892-1914 - Cambridgeshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/cambridge/{z}/{x}/{y}.png 15 | OS 25 inch, 1892-1914 - Cheshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/cheshire/{z}/{x}/{y}.png 16 | OS 25 inch, 1892-1914 - Cornwall https://mapseries-tilesets.s3.amazonaws.com/25_inch/cornwall/{z}/{x}/{y}.png 17 | OS 25 inch, 1892-1914 - Cumberland https://mapseries-tilesets.s3.amazonaws.com/25_inch/cumberland/{z}/{x}/{y}.png 18 | OS 25 inch, 1892-1914 - Devon https://mapseries-tilesets.s3.amazonaws.com/25_inch/devon/{z}/{x}/{y}.png 19 | OS 25 inch, 1892-1914 - Dorset https://mapseries-tilesets.s3.amazonaws.com/25_inch/dorset/{z}/{x}/{y}.png 20 | OS 25 inch, 1892-1914 - Durham https://mapseries-tilesets.s3.amazonaws.com/25_inch/durham/{z}/{x}/{y}.png 21 | OS 25 inch, 1892-1914 - Essex https://mapseries-tilesets.s3.amazonaws.com/25_inch/essex/{z}/{x}/{y}.png 22 | OS 25 inch, 1892-1914 - Gloucestershire https://mapseries-tilesets.s3.amazonaws.com/25_inch/gloucestershire/{z}/{x}/{y}.png 23 | OS 25 inch, 1892-1914 - Hampshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/hampshire/{z}/{x}/{y}.png 24 | OS 25 inch, 1892-1914 - Herefordshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/herefordshire/{z}/{x}/{y}.png 25 | OS 25 inch, 1892-1914 - Hertfordshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/hertfordshire/{z}/{x}/{y}.png 26 | OS 25 inch, 1892-1914 - Huntingdon https://mapseries-tilesets.s3.amazonaws.com/25_inch/huntingdon/{z}/{x}/{y}.png 27 | OS 25 inch, 1892-1914 - Kent https://mapseries-tilesets.s3.amazonaws.com/25_inch/kent/{z}/{x}/{y}.png 28 | OS 25 inch, 1892-1914 - Lancashire https://mapseries-tilesets.s3.amazonaws.com/25_inch/lancashire/{z}/{x}/{y}.png 29 | OS 25 inch, 1892-1914 - Leicestershire https://mapseries-tilesets.s3.amazonaws.com/25_inch/leicestershire/{z}/{x}/{y}.png 30 | OS 25 inch, 1892-1914 - Lincolnshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/lincolnshire/{z}/{x}/{y}.png 31 | OS 25 inch, 1892-1914 - London https://mapseries-tilesets.s3.amazonaws.com/25_inch/london/{z}/{x}/{y}.png 32 | OS 25 inch, 1892-1914 - Middlesex https://mapseries-tilesets.s3.amazonaws.com/25_inch/middlesex/{z}/{x}/{y}.png 33 | OS 25 inch, 1892-1914 - Norfolk https://mapseries-tilesets.s3.amazonaws.com/25_inch/norfolk/{z}/{x}/{y}.png 34 | OS 25 inch, 1892-1914 - Northamptonshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/northampton/{z}/{x}/{y}.png 35 | OS 25 inch, 1892-1914 - Northumberland https://mapseries-tilesets.s3.amazonaws.com/25_inch/northumberland/{z}/{x}/{y}.png 36 | OS 25 inch, 1892-1914 - Nottinghamshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/nottinghamshire/{z}/{x}/{y}.png 37 | OS 25 inch, 1892-1914 - Oxford https://mapseries-tilesets.s3.amazonaws.com/25_inch/oxford/{z}/{x}/{y}.png 38 | OS 25 inch, 1892-1914 - Rutland https://mapseries-tilesets.s3.amazonaws.com/25_inch/rutland/{z}/{x}/{y}.png 39 | OS 25 inch, 1892-1914 - Shropshire / Derbyshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/Shrop_Derby/{z}/{x}/{y}.png 40 | OS 25 inch, 1892-1914 - Somerset https://mapseries-tilesets.s3.amazonaws.com/25_inch/somerset/{z}/{x}/{y}.png 41 | OS 25 inch, 1892-1914 - Stafford https://mapseries-tilesets.s3.amazonaws.com/25_inch/stafford/{z}/{x}/{y}.png 42 | OS 25 inch, 1892-1914 - Suffolk https://mapseries-tilesets.s3.amazonaws.com/25_inch/suffolk/{z}/{x}/{y}.png 43 | OS 25 inch, 1892-1914 - Surrey https://mapseries-tilesets.s3.amazonaws.com/25_inch/surrey/{z}/{x}/{y}.png 44 | OS 25 inch, 1892-1914 - Sussex https://mapseries-tilesets.s3.amazonaws.com/25_inch/sussex/{z}/{x}/{y}.png 45 | OS 25 inch, 1892-1914 - Wales https://mapseries-tilesets.s3.amazonaws.com/25_inch/wales/{z}/{x}/{y}.png 46 | OS 25 inch, 1892-1914 - Warwick https://mapseries-tilesets.s3.amazonaws.com/25_inch/warwick/{z}/{x}/{y}.png 47 | OS 25 inch, 1892-1914 - Westmorland https://mapseries-tilesets.s3.amazonaws.com/25_inch/westmorland/{z}/{x}/{y}.png 48 | OS 25 inch, 1892-1914 - Wiltshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/wiltshire2nd/{z}/{x}/{y}.png 49 | OS 25 inch, 1892-1914 - Worcestershire https://mapseries-tilesets.s3.amazonaws.com/25_inch/Worcestershire/{z}/{x}/{y}.png 50 | OS 25 inch, 1892-1914 - Yorkshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/yorkshire/{z}/{x}/{y}.png 51 | OS 25 inch, 1892-1914 'Holes' (fills gaps in series) https://geo.nls.uk/mapdata3/os/25_inch_holes_england/{z}/{x}/{y}.png -------------------------------------------------------------------------------- /interact.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from streamlit_option_menu import option_menu 3 | 4 | # import your app modules here 5 | from apps import get_bounds 6 | 7 | st.set_page_config(page_title="Streamlit Folium", layout="wide") 8 | 9 | # A dictionary of apps in the format of {"App title": "App icon"} 10 | # More icons can be found here: https://icons.getbootstrap.com 11 | 12 | apps = [ 13 | {"func":get_bounds.app, "title": "Home", "icon": "house"}, 14 | ] 15 | 16 | titles = [app["title"] for app in apps] 17 | icons = [app["icon"] for app in apps] 18 | 19 | params = st.experimental_get_query_params() 20 | 21 | if "page" in params: 22 | default_index = int(titles.index(params["page"][0].lower())) 23 | else: 24 | default_index = 0 25 | 26 | with st.sidebar: 27 | selected = option_menu( 28 | "Main Menu", 29 | options=titles, 30 | icons=icons, 31 | menu_icon="cast", 32 | default_index=default_index, 33 | ) 34 | 35 | st.sidebar.title("About") 36 | st.sidebar.info( 37 | """ 38 | Web App URL: 39 | 40 | Contact Qiusheng Wu (qwu18@utk.edu) if you have any questions or comments. 41 | """ 42 | ) 43 | 44 | for app in apps: 45 | if app["title"] == selected: 46 | app["func"]() 47 | break 48 | -------------------------------------------------------------------------------- /ntl.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from streamlit_option_menu import option_menu 3 | 4 | # import your app modules here 5 | from apps import viirs 6 | 7 | st.set_page_config(page_title="Nighttime Light Data Analysis", layout="wide") 8 | 9 | # A dictionary of apps in the format of {"App title": "App icon"} 10 | # More icons can be found here: https://icons.getbootstrap.com 11 | 12 | apps = [ 13 | {"func":viirs.app, "title": "Home", "icon": "house"}, 14 | ] 15 | 16 | titles = [app["title"] for app in apps] 17 | icons = [app["icon"] for app in apps] 18 | 19 | params = st.experimental_get_query_params() 20 | 21 | if "page" in params: 22 | default_index = int(titles.index(params["page"][0].lower())) 23 | else: 24 | default_index = 0 25 | 26 | with st.sidebar: 27 | selected = option_menu( 28 | "Main Menu", 29 | options=titles, 30 | icons=icons, 31 | menu_icon="cast", 32 | default_index=default_index, 33 | ) 34 | 35 | st.sidebar.title("About") 36 | st.sidebar.info( 37 | """ 38 | Web App URL: 39 | 40 | Contact Qiusheng Wu (qwu18@utk.edu) if you have any questions or comments. 41 | """ 42 | ) 43 | 44 | for app in apps: 45 | if app["title"] == selected: 46 | app["func"]() 47 | break 48 | -------------------------------------------------------------------------------- /packages.txt: -------------------------------------------------------------------------------- 1 | ffmpeg 2 | gifsicle 3 | build-essential 4 | python3-dev 5 | gdal-bin 6 | libgdal-dev -------------------------------------------------------------------------------- /postBuild: -------------------------------------------------------------------------------- 1 | # enable nbserverproxy 2 | jupyter serverextension enable --sys-prefix nbserverproxy 3 | # streamlit launches at startup 4 | mv streamlit_call.py ${NB_PYTHON_PREFIX}/lib/python*/site-packages/ 5 | # enable streamlit extension 6 | jupyter serverextension enable --sys-prefix streamlit_call 7 | -------------------------------------------------------------------------------- /raster.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from streamlit_option_menu import option_menu 3 | 4 | # import your app modules here 5 | from apps import cog 6 | 7 | st.set_page_config(page_title="Search Geographic Names", layout="wide") 8 | 9 | # A dictionary of apps in the format of {"App title": "App icon"} 10 | # More icons can be found here: https://icons.getbootstrap.com 11 | 12 | apps = [ 13 | {"func":cog.app, "title": "Home", "icon": "house"}, 14 | ] 15 | 16 | titles = [app["title"] for app in apps] 17 | icons = [app["icon"] for app in apps] 18 | 19 | params = st.experimental_get_query_params() 20 | 21 | if "page" in params: 22 | default_index = int(titles.index(params["page"][0].lower())) 23 | else: 24 | default_index = 0 25 | 26 | with st.sidebar: 27 | selected = option_menu( 28 | "Main Menu", 29 | options=titles, 30 | icons=icons, 31 | menu_icon="cast", 32 | default_index=default_index, 33 | ) 34 | 35 | st.sidebar.title("About") 36 | st.sidebar.info( 37 | """ 38 | Contact Qiusheng Wu (qwu18@utk.edu) if you have any questions or comments. 39 | """ 40 | ) 41 | 42 | for app in apps: 43 | if app["title"] == selected: 44 | app["func"]() 45 | break -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | --find-links=https://girder.github.io/large_image_wheels GDAL 2 | # geemap 3 | geopandas 4 | jupyter-server-proxy 5 | keplergl 6 | # leafmap 7 | localtileserver 8 | nbserverproxy 9 | streamlit 10 | streamlit-folium 11 | streamlit-option-menu 12 | geemap 13 | leafmap 14 | # git+https://github.com/giswqs/leafmap 15 | # git+https://github.com/giswqs/geemap 16 | 17 | 18 | -------------------------------------------------------------------------------- /search_names.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from streamlit_option_menu import option_menu 3 | 4 | # import your app modules here 5 | from apps import osm_names 6 | 7 | st.set_page_config(page_title="Search Geographic Names", layout="wide") 8 | 9 | # A dictionary of apps in the format of {"App title": "App icon"} 10 | # More icons can be found here: https://icons.getbootstrap.com 11 | 12 | apps = [ 13 | {"func":osm_names.app, "title": "Home", "icon": "house"}, 14 | ] 15 | 16 | titles = [app["title"] for app in apps] 17 | icons = [app["icon"] for app in apps] 18 | 19 | params = st.experimental_get_query_params() 20 | 21 | if "page" in params: 22 | default_index = int(titles.index(params["page"][0].lower())) 23 | else: 24 | default_index = 0 25 | 26 | with st.sidebar: 27 | selected = option_menu( 28 | "Main Menu", 29 | options=titles, 30 | icons=icons, 31 | menu_icon="cast", 32 | default_index=default_index, 33 | ) 34 | 35 | st.sidebar.title("About") 36 | st.sidebar.info( 37 | """ 38 | Web App URL: 39 | 40 | Contact Qiusheng Wu (qwu18@utk.edu) if you have any questions or comments. 41 | """ 42 | ) 43 | 44 | for app in apps: 45 | if app["title"] == selected: 46 | app["func"]() 47 | break 48 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | # sudo add-apt-repository ppa:ubuntugis/ppa && sudo apt-get update 2 | # sudo apt-get update 3 | # sudo apt-get install python3-dev 4 | # sudo apt-get install gdal-bin 5 | # sudo apt-get install libgdal-dev 6 | # export CPLUS_INCLUDE_PATH=/usr/include/gdal 7 | # export C_INCLUDE_PATH=/usr/include/gdal 8 | # gdal-config --version 9 | # pip install GDAL==$(gdal-config --version | awk -F'[.]' '{print $1"."$2}') localtileserver 10 | 11 | mkdir -p ~/.streamlit/ 12 | echo "\ 13 | [server]\n\ 14 | headless = true\n\ 15 | port = $PORT\n\ 16 | enableCORS = false\n\ 17 | \n\ 18 | " > ~/.streamlit/config.toml -------------------------------------------------------------------------------- /split_map.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from streamlit_option_menu import option_menu 3 | 4 | # import your app modules here 5 | from apps import split, scotland 6 | 7 | st.set_page_config(page_title="Split-panel Map", layout="wide") 8 | 9 | # A dictionary of apps in the format of {"App title": "App icon"} 10 | # More icons can be found here: https://icons.getbootstrap.com 11 | 12 | apps = [ 13 | {"func":split.app, "title": "Home", "icon": "house"}, 14 | {"func":scotland.app, "title": "Scotland", "icon": "globe"}, 15 | ] 16 | 17 | titles = [app["title"] for app in apps] 18 | icons = [app["icon"] for app in apps] 19 | 20 | params = st.experimental_get_query_params() 21 | 22 | if "page" in params: 23 | default_index = int(titles.index(params["page"][0].lower())) 24 | else: 25 | default_index = 0 26 | 27 | with st.sidebar: 28 | selected = option_menu( 29 | "Main Menu", 30 | options=titles, 31 | icons=icons, 32 | menu_icon="cast", 33 | default_index=default_index, 34 | ) 35 | 36 | st.sidebar.title("About") 37 | st.sidebar.info( 38 | """ 39 | Web App URL: 40 | 41 | Contact Qiusheng Wu (qwu18@utk.edu) if you have any questions or comments. 42 | """ 43 | ) 44 | 45 | for app in apps: 46 | if app["title"] == selected: 47 | app["func"]() 48 | break -------------------------------------------------------------------------------- /streamlit_app.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from streamlit_option_menu import option_menu 3 | from apps import home, heatmap, upload # import your app modules here 4 | 5 | st.set_page_config(page_title="Streamlit Geospatial", layout="wide") 6 | 7 | # A dictionary of apps in the format of {"App title": "App icon"} 8 | # More icons can be found here: https://icons.getbootstrap.com 9 | 10 | apps = [ 11 | {"func":home.app, "title": "Home", "icon": "house"}, 12 | {"func":heatmap.app, "title": "Heatmap", "icon": "map"}, 13 | {"func":upload.app, "title": "Upload", "icon": "cloud-upload"}, 14 | ] 15 | 16 | titles = [app["title"] for app in apps] 17 | icons = [app["icon"] for app in apps] 18 | 19 | params = st.experimental_get_query_params() 20 | 21 | if "page" in params: 22 | default_index = int(titles.index(params["page"][0].lower())) 23 | else: 24 | default_index = 0 25 | 26 | with st.sidebar: 27 | selected = option_menu( 28 | "Main Menu", 29 | options=titles, 30 | icons=icons, 31 | menu_icon="cast", 32 | default_index=default_index, 33 | ) 34 | 35 | st.sidebar.title("About") 36 | st.sidebar.info( 37 | """ 38 | This web [app](https://share.streamlit.io/giswqs/streamlit-template) is maintained by [Qiusheng Wu](https://wetlands.io). You can follow me on social media: 39 | [GitHub](https://github.com/giswqs) | [Twitter](https://twitter.com/giswqs) | [YouTube](https://www.youtube.com/c/QiushengWu) | [LinkedIn](https://www.linkedin.com/in/qiushengwu). 40 | 41 | Source code: 42 | 43 | More menu icons: 44 | """ 45 | ) 46 | 47 | for app in apps: 48 | if app["title"] == selected: 49 | app["func"]() 50 | break -------------------------------------------------------------------------------- /streamlit_call.py: -------------------------------------------------------------------------------- 1 | from subprocess import Popen 2 | 3 | 4 | def load_jupyter_server_extension(nbapp): 5 | """serve the streamlit app""" 6 | Popen( 7 | [ 8 | "streamlit", 9 | "run", 10 | "streamlit_app.py", 11 | "--browser.serverAddress=0.0.0.0", 12 | "--server.enableCORS=False", 13 | ] 14 | ) 15 | -------------------------------------------------------------------------------- /xyz.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from streamlit_option_menu import option_menu 3 | 4 | # import your app modules here 5 | from apps import scotland 6 | 7 | st.set_page_config(page_title="Split-panel Map", layout="wide") 8 | 9 | # A dictionary of apps in the format of {"App title": "App icon"} 10 | # More icons can be found here: https://icons.getbootstrap.com 11 | 12 | apps = [ 13 | {"func":scotland.app, "title": "Home", "icon": "house"}, 14 | ] 15 | 16 | titles = [app["title"] for app in apps] 17 | icons = [app["icon"] for app in apps] 18 | 19 | params = st.experimental_get_query_params() 20 | 21 | if "page" in params: 22 | default_index = int(titles.index(params["page"][0].lower())) 23 | else: 24 | default_index = 0 25 | 26 | with st.sidebar: 27 | selected = option_menu( 28 | "Main Menu", 29 | options=titles, 30 | icons=icons, 31 | menu_icon="cast", 32 | default_index=default_index, 33 | ) 34 | 35 | st.sidebar.title("About") 36 | st.sidebar.info( 37 | """ 38 | Web App URL: 39 | 40 | Contact Qiusheng Wu (qwu18@utk.edu) if you have any questions or comments. 41 | """ 42 | ) 43 | 44 | st.sidebar.title("Data Sources") 45 | markdown = """National Library of Scotland: """ 46 | st.sidebar.info(markdown) 47 | 48 | for app in apps: 49 | if app["title"] == selected: 50 | app["func"]() 51 | break --------------------------------------------------------------------------------