import os
import json
import datetime
import streamlit as st
import streamlit.components.v1 as components
import re
import warnings
import traceback
warnings.filterwarnings("ignore")
# os.environ['SDK_CLIENT_HOST'] = 'https://pre-engine-aiearth.aliyun.com'
import aie
default_map_html = '''
IPyWidget export
'''
label_text_tmpl='''
{LABEL_TEXT}
'''
secondary_label_text_tmpl='''
{LABEL_TEXT}
'''
page_title_text_tmpl='''
{TITLE_TEXT}
{DESC_TEXT}
'''
label_text_component_height = 41
st.set_page_config(layout="wide")
# AIE数据集stac code
aie_dataset_category = [
"LANDSAT_LT05_T02_T1_L2"
,"LANDSAT_LE07_E02_T1_L2"
,"LANDSAT_LC08_C02_T1_L2"
,"LANDSAT_LC09_C02_T1_L2"
,"SENTINEL_MSIL2A"
]
# L5:1984-2011
# L7:1999-至今
# L8:2014-至今
# L9:2021-至今
# S2:2018-至今
# 数据集时间范围
dataset_timestamp = {
"LANDSAT_LT05_T02_T1_L2": {"startDate": "1984-01-01", "endDate": "2011-12-31", "sampleStart": "2011-11-01", "sampleEnd": "2011-11-30"},
"LANDSAT_LE07_E02_T1_L2": {"startDate": "1999-01-01", "endDate": "2021-12-31", "sampleStart": "2021-12-01", "sampleEnd": "2021-12-31"},
"LANDSAT_LC08_C02_T1_L2": {"startDate": "2014-01-01", "endDate": "__NOW__", "sampleStart": "2023-02-01", "sampleEnd": "2023-03-28"},
"LANDSAT_LC09_C02_T1_L2": {"startDate": "2021-01-01", "endDate": "__NOW__", "sampleStart": "2023-01-01", "sampleEnd": "2023-03-01"},
"SENTINEL_MSIL2A": {"startDate": "2018-01-01", "endDate": "__NOW__", "sampleStart": "2023-03-01", "sampleEnd": "2023-03-28"}
}
# aie_area_meta = {}
image_collection_reduce_funcs = [
"median",
"min",
"max",
"mosaic",
"mean"
]
def aie_init():
token = os.environ.get("SDK_TOKEN")
#token = "81921ae3af932cf35f1e56de4a8b1ca4"
aie.Authenticate(token=token)
aie.Initialize()
@st.cache_resource
def load_area_select_options():
file_path = "./china-area.json"
with open(file_path, 'r', encoding="utf-8") as reader:
area_arr = json.loads(reader.read())
area_dic = {}
for item in area_arr:
province_name = item['levelOneAreaName']
city_name = item['levelTwoAreaName']
district_name = item['areaName']
if not province_name in area_dic:
area_dic[province_name] = {}
cities = area_dic[province_name]
if not city_name in cities:
cities[city_name] = {}
districts = cities[city_name]
if not district_name in districts:
districts[district_name] = district_name
print("tianxun area data init complete")
return area_dic
@st.cache_resource
def load_dataset_info():
file_path = "./aie_dataset_meta.json"
with open(file_path, 'r', encoding="utf-8") as reader:
_dataset = json.loads(reader.read())
return _dataset
def page_reset_callback():
st.session_state['dateset_picker'] = 'LANDSAT_LT05_T02_T1_L2'
st.session_state['region_province_select'] = '北京市'
st.session_state['region_city_select'] = '请选择城市'
st.session_state['region_district_select'] = '请选择'
st.session_state['time_picker'] = [datetime.date(2011, 1, 1), datetime.date(2011, 6, 30)]
st.session_state['cloud_picker'] = '20%'
st.session_state['reduce_picker'] = 'median'
st.session_state['band_picker'] = []
st.session_state['min_input'] = '8000'
st.session_state['max_input'] = '13000'
st.session_state['render_map_html'] = None
def page_components_render(ctr_panel, map_panel):
with ctr_panel:
hide_streamlit_menu = """
"""
st.markdown(hide_streamlit_menu, unsafe_allow_html=True)
area_selections = load_area_select_options()
dataset_meta = load_dataset_info()
# title
components.html(page_title_text_tmpl.format(TITLE_TEXT="影像快速检索", DESC_TEXT="卫星影像数据获取是业务应用分析的第一步工作,基于平台现有Landsat和Sentinel系列卫星数据资源,根据用户选择的感兴趣行政区、时间范围、云量等参数,可快速获取覆盖区域内的已镶嵌和裁剪后的卫星数据。"), height=160)
# 选择数据集类型
dataset_label, dataset_select = st.columns([1.1, 3.9])
# todo: 实在不行,label用html渲染
with dataset_label:
# st.text("数据类型")
components.html(label_text_tmpl.format(LABEL_TEXT="数据类型"), height=label_text_component_height)
with dataset_select:
dataset_empty = st.empty()
dataset = dataset_empty.selectbox(label="请选择数据类型", options=aie_dataset_category, key="dateset_picker", label_visibility="collapsed")
# 区域过滤
region_label, province_select, city_select = st.columns([1.65,2.8,2.75])
with region_label:
# st.text("区域选择")
components.html(label_text_tmpl.format(LABEL_TEXT="区域选择"), height=label_text_component_height)
with province_select:
region_province_options = [name for name in area_selections]
region_province = st.selectbox(label="请选择省", options=region_province_options, key="region_province_select", label_visibility="collapsed")
with city_select:
region_city_options = []
if region_province:
region_city_options = [name for name in area_selections[region_province]]
region_city_options_update = ['请选择'] + region_city_options
region_city = st.selectbox(label="请选择市", options=region_city_options_update, key="region_city_select", label_visibility="collapsed")
# 选择时间区间
time_label, time_select = st.columns([1.1, 3.9])
with time_label:
# st.text("检索日期")
components.html(label_text_tmpl.format(LABEL_TEXT="检索日期"), height=label_text_component_height)
with time_select:
try:
min_time = datetime.date(2011, 1, 1)
max_time = datetime.date(2011, 6, 30)
sample_start_time = datetime.date(2011, 1, 1)
sample_end_time = datetime.date(2011, 6, 30)
if dataset and dataset in dataset_timestamp:
min_time = datetime.datetime.strptime(dataset_timestamp[dataset]['startDate'], '%Y-%m-%d')
if dataset_timestamp[dataset]['endDate'] == '__NOW__':
# max_time = datetime.date.today()
max_time = datetime.datetime.strptime(str(datetime.date.today()), '%Y-%m-%d')
else:
max_time = datetime.datetime.strptime(dataset_timestamp[dataset]['endDate'], '%Y-%m-%d')
# print(f"[DEBUG] {dataset} max_time = {max_time}, min_time = {min_time}")
sample_start_time = datetime.datetime.strptime(dataset_timestamp[dataset]['sampleStart'], '%Y-%m-%d')
sample_end_time = datetime.datetime.strptime(dataset_timestamp[dataset]['sampleEnd'], '%Y-%m-%d')
start_date, end_date = st.date_input(label="Select image time range", min_value=min_time, max_value=max_time,value=[sample_start_time, sample_end_time], key="time_picker", label_visibility="collapsed")
except Exception as e:
print("date picker error. ignore")
# 选择云量
cloud_label, cloud_select = st.columns([1.1, 3.9])
with cloud_label:
# st.text("云量")
components.html(label_text_tmpl.format(LABEL_TEXT="云 量"), height=label_text_component_height)
with cloud_select:
cloud_options = [ str(x) + "%" for x in range(0, 105, 5)]
end_cloud = st.select_slider(
'选择云量',
options=cloud_options,
key='cloud_picker',
value='20%', label_visibility="collapsed")
# 选择镶嵌方式
crop_label, crop_select = st.columns([1.1, 3.9])
with crop_label:
#st.text("镶嵌方式")
components.html(label_text_tmpl.format(LABEL_TEXT="镶嵌方式"), height=label_text_component_height)
with crop_select:
crop_type = st.selectbox(label="请选择镶嵌方式", options=image_collection_reduce_funcs, key="reduce_picker", label_visibility="collapsed")
# 波段选择(默认填充前3个)
band_label, band_select = st.columns([1.1, 3.9])
with band_label:
# st.text("波段选择")
components.html(label_text_tmpl.format(LABEL_TEXT="波段选择"), height=label_text_component_height)
with band_select:
bands_option = []
if dataset and dataset in dataset_meta:
bands_option = [band_name for band_name in dataset_meta[dataset]['bands']]
bands = st.multiselect("Select bands to display", options=bands_option, key="band_picker", label_visibility="collapsed", max_selections=3)
minmax_label, min_label, min_select, max_label, max_select = st.columns([1.55, 1.22, 1.74, 1.23, 1.74])
with min_label:
# st.text("最小值")
components.html(secondary_label_text_tmpl.format(LABEL_TEXT="最小值"), height=label_text_component_height)
with min_select:
default_min = 100
if dataset and dataset in dataset_meta:
default_min = dataset_meta[dataset]['min']
min = st.text_input(label="Enter image min",value=str(default_min), key="min_input", label_visibility="collapsed")
with max_label:
#st.text("最大值")
components.html(secondary_label_text_tmpl.format(LABEL_TEXT="最大值"), height=label_text_component_height)
with max_select:
default_max = 10000
if dataset and dataset in dataset_meta:
default_max = dataset_meta[dataset]['max']
max = st.text_input(label="Enter image max",value=str(default_max), key="max_input", label_visibility="collapsed")
reset_btn, query_btn = st.columns([1, 2])
with reset_btn:
page_reset = st.button("重置", type='secondary', key='reset_btn', use_container_width=True, on_click=page_reset_callback)
with query_btn:
submit = st.button("检索", type='primary', key='submit_btn', use_container_width=True)
# date_debug = str(dataset) + ", max_time = " + str(max_time) + ", min_time = " + str(min_time)
# st.write(date_debug)
if page_reset:
with map_panel:
components.html(default_map_html, height=780, scrolling=False)
return
if submit:
# 参数校验
errMsg = None
if not start_date or not end_date:
errMsg = "请选择时间"
elif start_date >= max_time.date():
errMsg = "未检索到数据,请重新设置检索日期"
elif not bands or len(bands) == 0:
errMsg = "请选择波段"
elif not min or not max:
errMsg = "请输入最大最小值"
else:
try:
ti = float(min)
ta = float(max)
except Exception as e:
errMsg = "最大最小值只能为数字"
if errMsg:
aie_init()
with ctr_panel:
st.error(errMsg)
with map_panel:
components.html(default_map_html, height=780, scrolling=False)
return
if dataset:
try:
# 拼接AIE 数据查询SDK语句
aie_init()
image_collection = aie.ImageCollection(dataset)
if start_date and end_date:
image_collection = image_collection.filterDate(start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d'))
if region_province or region_city:
fc = aie.FeatureCollection('China_City')
if region_province:
fc = fc.filter(aie.Filter.eq('province', region_province))
if region_city and region_city != '请选择':
fc = fc.filter(aie.Filter.eq('city', region_city))
# 用选择的数据重新初始化map组件
aie_map = aie.Map(
center=fc.getCenter(),
height=800,
zoom=7
)
region_vis_params = {
'color': '#00FF00'
}
aie_map.addLayer(
fc,
region_vis_params,
'行政区划边界',
bounds=fc.getBounds()
)
image_collection = image_collection.filterBounds(fc.geometry())
if end_cloud:
end_cloud_val = end_cloud.replace('%','')
image_collection = image_collection.filter(aie.Filter.lte('eo:cloud_cover', int(end_cloud_val)))
if crop_type == 'median':
image_collection = image_collection.median()
elif crop_type == 'min':
image_collection = image_collection.min()
elif crop_type == 'max':
image_collection = image_collection.max()
elif crop_type == 'mosaic':
image_collection = image_collection.mosaic()
elif crop_type == 'mean':
image_collection = image_collection.mean()
if region_province or region_city:
image_collection = image_collection.clipToCollection(fc)
vis_params = {}
if bands:
vis_params['bands'] = list(bands)
if min and max:
vis_params['min'] = float(min)
vis_params['max'] = float(max)
aie_map.addLayer(
image_collection,
vis_params,
dataset,
bounds=image_collection.getBounds()
)
except Exception as e:
traceback.print_exc()
with ctr_panel:
# st.error(e)
st.error("未检索到数据集信息, 请重新设置查询条件")
with map_panel:
components.html(default_map_html, height=780, scrolling=False)
return
with map_panel:
# 对于未点击submit的情况, 直接读取静态html字符串来加快load速度
if submit:
map_html_source = aie_map.to_html()
st.session_state['render_map_html'] = map_html_source
components.html(map_html_source, height=780, scrolling=False)
else:
display_template = default_map_html
if 'render_map_html' in st.session_state and st.session_state['render_map_html']:
display_template = st.session_state['render_map_html']
components.html(display_template, height=780, scrolling=False)
# components.html(default_map_html, height=800, scrolling=False)
row1_col1, row1_col2 = st.columns([0.8, 2.2])
page_components_render(row1_col1, row1_col2)