创建用于卫星图像可视化的 Streamlit 应用程序:一步步指南

原文:towardsdatascience.com/creating-a-streamlit-app-for-satellite-imagery-visualization-a-step-by-step-guide-8209593be994

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/1d5473add54a0b5a400d6bd539ab8c60.png

作者提供的 Streamlit 应用程序视频记录,用于在任何时间可视化地球上的任何位置

目录

  1. 🌟 简介

  2. 📌 设置

  3. 💾 设计页面

  4. 🌍 地图可视化函数

  5. 📄 结论

  6. 📚 参考资料

🌟 简介

分享已成为我们生活的一部分。每一秒,都有大量的图片和视频上传到各种平台,如 X、Instagram 和 TikTok,主要是为了与他人分享时刻。编码也不例外,我们可以使用的一个平台是 Streamlit。我之前已经发布了几篇关于使用各种方法下载和可视化卫星图像的文章。在这篇文章中,我将展示如何开发一个无需设置凭据的 Streamlit 应用程序。这个应用程序将列出 Sentinel-2 捕获的任何地球上的任何时期的可用图像,并允许用户从该列表中选择一个图像,并在 RGB 中可视化该位置的图像,同时进行场景分类。这篇文章是开发此应用程序的逐步指南,以便与他人分享。如果您想了解更多,请继续阅读!

📌 设置

要使用 Streamlit 创建任何应用程序,第一步是创建一个任意命名的文件夹,并在其中设置两件事。第一件事是一个空的 Python 脚本,它将包含我们的主要代码。第二件事是可选的,是一个名为 .streamlit 的子文件夹。这个子文件夹用于存储登录我们的 Web 应用程序的用户名和密码(凭据)。如果您不想为您的应用程序设置登录页面,可以忽略这个子文件夹。否则,打开记事本,按照以下格式写下您的用户名和密码,并将其保存为 secrets.toml。

[passwords]
# Follow the rule: username = "password"
Mahyar = "abc123"

如果您遵循设置步骤,您应该会在您的文件夹中看到以下文件:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b5f05307fc3d1dcf3e05c61b744ea07a.png

作者提供的文件夹结构快照

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/9114acde535957666d3e5fc21820a507.png

.streamlit 文件夹结构的快照,作者提供

💾 设计页面

下一步是考虑你想要添加到应用程序中以增强用户体验的元素。这包括任何指导用户的文本信息,你需要从用户那里获取以运行应用程序的信息,以及在不同步骤执行应用程序的按钮。由于我们的目标是让用户登录并查看任何位置和期间的卫星图像,我们需要在我们的页面上包含以下元素:

登录页面欢迎信息 页面标题 卫星名称信息 位置和位置周围的缓冲区 搜索期间 云量

三个按钮: 一个用于获取可用列表,一个用于下载列表,一个用于可视化所选图像。所有这些都可以使用主脚本中的以下行实现:

第一部分:所需库和用户认证

import streamlit as st
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from st_files_connection import FilesConnection
from pystac_client import Client
from odc.stac import load
import hmac

def check_password():
    """Returns `True` if the user had a correct password."""

    def login_form():
        """Form with widgets to collect user information"""
        with st.form("Credentials"):
            st.text_input("Username", key="username")
            st.text_input("Password", type="password", key="password")
            st.form_submit_button("Log in", on_click=password_entered)

    def password_entered():
        """Checks whether a password entered by the user is correct."""
        if st.session_state["username"] in st.secrets[
            "passwords"
        ] and hmac.compare_digest(
            st.session_state["password"],
            st.secrets.passwords[st.session_state["username"]],
        ):
            st.session_state["password_correct"] = True
            del st.session_state["password"]  # Don't store the username or password.
            del st.session_state["username"]
        else:
            st.session_state["password_correct"] = False

    # Return True if the username + password is validated.
    if st.session_state.get("password_correct", False):
        return True

    # Show inputs for username + password.
    login_form()
    if "password_correct" in st.session_state:
        st.error("😕  User not known or password incorrect")
    return False

if not check_password():
    st.stop() 

此代码设置了一个用于可视化卫星图像的 Streamlit 应用程序。它首先导入必要的库,包括用于构建 Web 应用的 streamlit,用于数据处理和数值运算的 pandas 和 numpy,用于文件连接的 st_files_connection,以及用于安全密码比较的 hmac。

check_password() 函数处理用户认证。它创建一个 login_form() 来验证输入的凭据与存储的凭据使用 HMAC 进行安全比较。主要的 check_password() 函数管理登录过程的状态,并在登录失败时显示错误信息。如果用户未认证,应用程序将停止。

第二部分:用户输入和三个按钮:

# Main Streamlit app starts here
st.write("Welcome to Satellite Visualization App by Streamlit...")

# Display Title
st.title("Satellite Map Portal")
st.markdown("Enter the data below.")

# Initialize session state for date_labels and user_date
if 'date_labels' not in st.session_state:
    st.session_state.date_labels = []

if 'data' not in st.session_state:
    st.session_state.data = None

if 'user_date' not in st.session_state:
    st.session_state.user_date = None

if 'user_date_index' not in st.session_state:
    st.session_state.user_date_index = 0

collections=["sentinel-2-l2a"]
columns = ['collection', 'start_date', 'end_date', 'min_cloud_cover', 'max_cloud_cover', 'longitude', 'latitude','buffer']

with st.form(key="test"):

    collection=st.selectbox("collection*",options=collections,index=None)
    start_date = st.date_input(label="start_date*")
    end_date = st.date_input(label="end_date*")
    max_cloud_cover  = st.number_input(label="max_cloud_cover*",value=10)
    longitude=st.number_input(label="longitude*", format="%.4f",value=-119.7513)
    latitude=st.number_input(label="latitude*", format="%.4f",value=37.2502)
    buffer=st.number_input(label="buffer (0.01 = 1 km)*", format="%.2f",value=0.01)

    # Mark Mandatory fields
    st.markdown("**required*")

    submit_button_run = st.form_submit_button(label="Run")
    submit_button_list = st.form_submit_button(label="List Available Images")
    submit_button_viz = st.form_submit_button(label="Visualize")

一旦认证成功,应用程序将显示标题和说明。它初始化会话状态变量。表单包括下拉菜单用于选择图像集合,日期输入用于指定搜索期间,数值输入用于最大云量,位置坐标和缓冲区大小。

它还包括运行应用程序、列出可用图像和可视化所选图像的按钮。这种设置确保用户可以轻松输入所需的参数,并与应用程序交互,以可视化任何位置和期间的卫星图像。

一旦你在 .py 文件中保存了这些行,你可以在终端使用以下命令运行你的代码:

C:UsersStreamlit> streamlit run streamlit_app_authen_sentinel_2.py

在你的浏览器中,你将看到登录和主页:

第一部分:登录页面

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a32f4e5520563a06ebb676d73f390d99.png

登录页面的快照,图片由作者提供

第二部分:主页

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5e2c23e62a97f3ecd46b21a371221497.png

主页快照,图片由作者提供

🌍 地图可视化函数

到目前为止,我们已经创建了登录页面并设计了主页。在主页上,我们目前正在收集用户输入,并有三个尚未启用的按钮。在本节中,我们将完成代码,使这些按钮能够正常工作。第一个按钮是“运行”,它设计用来使用用户输入的参数搜索云数据库,并在 DataFrame 中显示可用的图像。为了给这个按钮添加功能,我们包括了两个额外的部分(第三部分 和第四部分)。

第三部分:定义搜索函数

def search_satellite_images(collection="sentinel-2-l2a",
                            bbox=[-120.15,38.93,-119.88,39.25],
                            date="2023-06-01/2023-06-30",
                            cloud_cover=(0, 10)):

    # Define the search client
    client=Client.open("https://earth-search.aws.element84.com/v1")
    search = client.search(collections=[collection],
                            bbox=bbox,
                            datetime=date,
                            query=[f"eo:cloud_cover<{cloud_cover[1]}", f"eo:cloud_cover>{cloud_cover[0]}"])

    # Print the number of matched items
    print(f"Number of images found: {search.matched()}")

    data = load(search.items(), bbox=bbox, groupby="solar_day", chunks={})

    print(f"Number of days in data: {len(data.time)}")

    return data

def get_bbox_with_buffer(latitude=37.2502, longitude=-119.7513, buffer=0.01):

    min_lat = latitude - buffer
    max_lat = latitude + buffer
    min_lon = longitude - buffer
    max_lon = longitude + buffer

    bbox = [min_lon, min_lat, max_lon, max_lat]
    return bbox 

第三部分首先定义了一个函数 search_satellite_images,该函数连接到 STAC API,根据用户指定的参数(如集合类型、边界框、日期范围和云层覆盖)搜索卫星图像。该函数返回匹配的数据。另一个函数,* get_bbox_with_buffer_,计算给定纬度和经度周围带有指定缓冲区的边界框。

第四部分:运行按钮

# Create an empty DataFrame with these columns
df = pd.DataFrame(columns=columns)

if "mdf" not in st.session_state:
    st.session_state.mdf = pd.DataFrame(columns=df.columns)

# New Data
with st.form(key="test"):

    collection=st.selectbox("collection*",options=collections,index=None)
    start_date = st.date_input(label="start_date*")
    end_date = st.date_input(label="end_date*")
    max_cloud_cover  = st.number_input(label="max_cloud_cover*",value=10)
    longitude=st.number_input(label="longitude*", format="%.4f",value=-119.7513)
    latitude=st.number_input(label="latitude*", format="%.4f",value=37.2502)
    buffer=st.number_input(label="buffer (0.01 = 1 km)*", format="%.2f",value=0.01)

    # Mark Mandatory fields
    st.markdown("**required*")

    submit_button_run = st.form_submit_button(label="Run")
    submit_button_list = st.form_submit_button(label="List Available Images")
    submit_button_viz = st.form_submit_button(label="Visualize")

    if submit_button_run:
        new_df=pd.DataFrame(
            [
                {   
                    "collection": collection,
                    "start_date":start_date.strftime("%Y-%m-%d"),
                    "end_date": end_date.strftime("%Y-%m-%d"),
                    "max_cloud_cover":max_cloud_cover,
                    "longitude": longitude,
                    "latitude": latitude,
                    "buffer": buffer,

                }

            ]
        )

        st.session_state.mdf = pd.concat([st.session_state.mdf, new_df], axis=0)
        st.dataframe(st.session_state.mdf)
        st.success("Your request successfully submitted!")

        data = search_satellite_images(collection=collection,
                                       date=f"{start_date}/{end_date}",
                                       cloud_cover=(0, max_cloud_cover),
                                       bbox=get_bbox_with_buffer(latitude=latitude, longitude=longitude, buffer=buffer))
        st.session_state.data = data

        date_labels = []
        # Determine the number of time steps
        numb_days = len(data.time)
        # Iterate through each time step
        for t in range(numb_days):
            scl_image = data[["scl"]].isel(time=t).to_array()
            dt = pd.to_datetime(scl_image.time.values)
            year = dt.year
            month = dt.month
            day = dt.day
            date_string = f"{year}-{month:02d}-{day:02d}"
            date_labels.append(date_string)

        st.session_state.date_labels= date_labels

在第二部分,主应用程序逻辑初始化一个空的 DataFrame 并检查会话状态中是否存在 DataFrame。

当点击“运行”按钮时,它会创建一个新的 DataFrame 条目,包含用户输入,更新会话状态,并显示更新后的 DataFrame。然后它调用 search_satellite_images 来获取数据,并处理这些数据以提取和格式化日期标签,将它们存储在会话状态中。这种设置允许用户输入参数,搜索卫星图像,并可视化结果。

如果我们现在运行 Streamlit 应用程序,我们将看到如下表格,显示我们指定的位置和日期:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/63b907f2eefde712e44d284ad2fcd70b.png

激活“运行”按钮后,现在是时候完成“列出可用图像”按钮的激活了。这个按钮将显示用户指定的位置、日期和云层覆盖下 Sentinel-2 捕获的可用图像。

第五部分:列出可用图像按钮

 if submit_button_list:
        user_date=st.selectbox("Available Images*",options=st.session_state.date_labels,index=None)
        if user_date:
            st.session_state.user_date = user_date
            st.session_state.user_date_index = user_date.index()

当点击“列出可用图像”按钮时,此代码创建一个下拉菜单,列出卫星图像的可用日期。当用户选择一个日期时,它会更新会话状态以存储所选日期及其索引,从而根据用户的选取执行进一步的操作。

现在,如果你重新运行应用程序并点击“列出可用图像”按钮,你应该会看到列出可用图像的下拉菜单。选择其中一个:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/baf65cbdd3b0ae668fc5b43f4199e138.png

用户输入可用图像的快照,图片由作者提供

在选择一个可用图像后,最后一步是完成并激活“可视化”按钮。 “可视化”按钮的目标是显示用户从指定位置和缓冲区中选择的可用卫星图像。我使用了本篇文章中描述的代码进行可视化,但添加了一个额外的功能,在卫星地图的右侧显示场景分类的饼图。为了实现这一点,我们需要定义一个函数,该函数返回每个场景类的计数,然后将其用于可视化按钮:

第六部分:计数函数

def count_classified_pixels(data,num):

    scl_image = data[["scl"]].isel(time=num).to_array()

    # Count the classified pixels 
    count_saturated = np.count_nonzero(scl_image == 1)        # Saturated or defective
    count_dark = np.count_nonzero(scl_image == 2)             # Dark Area Pixels
    count_cloud_shadow = np.count_nonzero(scl_image == 3)     # Cloud Shadows
    count_vegetation = np.count_nonzero(scl_image == 4)       # Vegetation
    count_soil = np.count_nonzero(scl_image == 5)             # Bare Soils
    count_water = np.count_nonzero(scl_image == 6)            # Water
    count_clouds_low= np.count_nonzero(scl_image == 7)        # Clouds Low Probability / Unclassified
    count_clouds_med = np.count_nonzero(scl_image == 8)       # Clouds Medium Probability
    count_clouds_high = np.count_nonzero(scl_image == 9)      # Clouds High Probability
    count_clouds_cirrus = np.count_nonzero(scl_image == 10)   # Cirrus
    count_clouds_snow = np.count_nonzero(scl_image == 11)     # Snow

    counts = {
    'Dark/Bright': count_cloud_shadow +count_dark+count_clouds_low+count_clouds_med+count_clouds_high+count_clouds_cirrus +count_clouds_snow +count_saturated,
    'Vegetation': count_vegetation,
    'Bare Soil': count_soil,
    'Water': count_water,
    }

    return counts

第七部分:可视化按钮

if submit_button_viz:

        date_string_title= f"Sentinel-2 Image over AOI"
        fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(16, 8))  

        rgb = st.session_state.data[["red", "green", "blue"]].isel(time=st.session_state.user_date_index).to_array()
        rgb.plot.imshow(robust=True, ax=axs[0])
        axs[0].axis('off')  # Hide the axes ticks and labels
        axs[0].set_title(date_string_title)

        # Preparing data 
        counts = count_classified_pixels(st.session_state.data, st.session_state.user_date_index )
        labels = list(counts.keys())
        values = list(counts.values())
        colors = ['DarkGrey', 'chartreuse', 'DarkOrange', 'cyan']
        explode = (0.3, 0.1, 0.1, 0.1)  # Exploding the first slice

        # Plotting the pie chart 
        axs[1].pie(values, labels=labels, colors=colors, autopct='%1.0f%%', startangle=140, explode=explode)
        axs[1].legend(labels, loc='best', bbox_to_anchor=(1, 0.5))
        axs[1].axis('equal')  # Ensure the pie chart is a circle
        axs[1].set_title('Distribution of Classes')

        # Display the figure in Streamlit
        st.pyplot(fig)

当点击“可视化”按钮时,代码设置了一个带有标题的图表,表明图像是覆盖感兴趣区域(AOI)的哨兵-2 图像。创建了一个包含两个并列子图的图形。从所选日期的会话状态数据中提取了 RGB 图像,并在第一个子图中显示,没有轴标签,增强了视觉清晰度。让我们再次运行应用程序,同时激活所有按钮:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/7218fab5d1b63d742c3125031f267ace.png

流式应用程序的最终版本,图片由作者提供

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/37d20a4895d922b7f79db3c8330057d0.png

流式应用程序的输出,图片由作者提供

随意更改云层覆盖、日期和坐标的默认值,看看它们如何影响可视化。

📄 结论

在本指南中,我们介绍了创建流式应用程序以可视化地球上任何时间任何地点的卫星图像的过程。我们首先设置了一个登录页面以确保应用程序的安全,然后设计主页面以收集用户输入。我们实现了“运行”按钮的功能来搜索可用的卫星图像,“列出可用图像”按钮以显示可用图像的下拉菜单,以及“可视化”按钮以显示选定的卫星图像。这种交互式方法使用户能够轻松地将他们的代码与社区分享,并有效地探索不同的场景并从卫星图像中获得见解。

📚 参考文献

github.com/stac-utils/pystac-client/blob/main/docs/quickstart.rst

www.element84.com/earth-search/examples/

科普尼克斯哨兵数据[2024]用于哨兵数据

科普尼克斯服务信息[2024]用于科普尼克斯服务信息。

📱 在其他平台上与我联系,获取更多有趣的内容!领英ResearchGateGitHub,以及***Twitter***。

通过以下链接可以找到相关帖子:

创建交互式地图以显示卫星图像的时间序列

使用卫星图像跟踪大盐湖萎缩情况(Python)

从太空观看风暴:一个创建惊人视图的 Python 脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值