Nefertury commited on
Commit
fbea52a
·
verified ·
1 Parent(s): e5ce11c

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +171 -0
  2. requirements.txt +3 -0
app.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # =======================================================================
2
+ # ШАГ 1: Импорт библиотек
3
+ # =======================================================================
4
+ import os
5
+ import json
6
+ import ee
7
+ import geemap
8
+ import gradio as gr
9
+ import html
10
+
11
+ # =======================================================================
12
+ # ШАГ 2: Аутентификация и инициализация GEE (АДАПТИРОВАНО ДЛЯ HF SPACES)
13
+ # =======================================================================
14
+ print("\n--- Инициализация Google Earth Engine ---")
15
+ try:
16
+ # Проверяем, запущено ли приложение в Hugging Face Spaces и есть ли секрет
17
+ if 'GEE_CREDENTIALS' in os.environ:
18
+ print("Аутентификация через секрет Hugging Face...")
19
+ # Декодируем JSON-строку из секрета
20
+ creds_json_str = os.environ['GEE_CREDENTIALS']
21
+ creds_json = json.loads(creds_json_str)
22
+
23
+ # Создаем учетные данные и инициализируем GEE
24
+ credentials = ee.ServiceAccountCredentials(creds_json['client_email'], key_data=creds_json_str)
25
+ ee.Initialize(credentials=credentials, project=creds_json['project_id'])
26
+ print("✅ Аутентификация через сервисный аккаунт GEE прошла успешно.")
27
+ else:
28
+ # Резервный вариант для локального запуска (если секрета нет)
29
+ print("Секрет не найден, попытка локальной аутентификации...")
30
+ ee.Authenticate()
31
+ ee.Initialize(project='gen-lang-client-0605302377')
32
+ print("✅ Локальная аутентификация и инициализация GEE прошли успешно.")
33
+
34
+ except Exception as e:
35
+ print(f"🔴 Критическая ошибка на этапе инициализации GEE: {e}")
36
+ # В среде HF это приведет к ошибке приложения, что и требуется.
37
+
38
+ # =======================================================================
39
+ # ШАГ 3: Обучение модели-классификатора
40
+ # (Код остается без изменений)
41
+ # =======================================================================
42
+ gee_classifier = None
43
+ bands_for_training = ['B2', 'B3', 'B4', 'B8', 'NDVI']
44
+
45
+ def add_ndvi(image):
46
+ ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI')
47
+ return image.addBands(ndvi)
48
+
49
+ def train_classifier():
50
+ global gee_classifier
51
+ if gee_classifier:
52
+ print("✅ Классификатор уже обучен.")
53
+ return
54
+
55
+ print("⏳ Обучение классификатора GEE... Это может занять около минуты.")
56
+ try:
57
+ desert = ee.FeatureCollection('projects/gen-lang-client-0605302377/assets/kalmykia_desert_samples').map(lambda f: f.set('class', 0))
58
+ solonchak = ee.FeatureCollection('projects/gen-lang-client-0605302377/assets/kalmykia_solonchak_samples').map(lambda f: f.set('class', 1))
59
+ arid = ee.FeatureCollection('projects/gen-lang-client-0605302377/assets/kalmykia_arid_samples').map(lambda f: f.set('class', 2))
60
+ greenery = ee.FeatureCollection('projects/gen-lang-client-0605302377/assets/kalmykia_greenery_samples').map(lambda f: f.set('class', 3))
61
+ water = ee.FeatureCollection('projects/gen-lang-client-0605302377/assets/kalmykia_water_samples').map(lambda f: f.set('class', 4))
62
+
63
+ training_points = desert.merge(solonchak).merge(arid).merge(greenery).merge(water)
64
+ roi_for_training = training_points.geometry().bounds()
65
+
66
+ image_for_training = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED') \
67
+ .filterDate('2023-06-01', '2023-09-01') \
68
+ .filterBounds(roi_for_training) \
69
+ .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 10)) \
70
+ .map(add_ndvi) \
71
+ .median() \
72
+ .clip(roi_for_training)
73
+
74
+ training_data = image_for_training.select(bands_for_training).sampleRegions(
75
+ collection=training_points, properties=['class'], scale=10, tileScale=4)
76
+
77
+ gee_classifier = ee.Classifier.smileRandomForest(numberOfTrees=50).train(
78
+ features=training_data, classProperty='class', inputProperties=bands_for_training)
79
+ print("✅ Модель-классификатор успешно обучена на 5 классах!")
80
+ except Exception as e:
81
+ print(f"🔴 Критическая ошибка во время обучения модели: {e}")
82
+
83
+ train_classifier()
84
+
85
+ # =======================================================================
86
+ # ШАГ 4: Функции для работы Gradio-приложения
87
+ # (Код остается без ��зменений)
88
+ # =======================================================================
89
+ def get_region_info(region_name):
90
+ regions = {
91
+ "Элиста (тестовая область)": {"center": [46.307, 44.258], "bbox": [44.15, 46.25, 44.35, 46.35]},
92
+ "Озеро Сарпа (Калмыкия)": {"center": [47.85, 44.75], "bbox": [44.65, 47.80, 44.85, 47.90]},
93
+ "Арзгир (Ставроп. край)": {"center": [45.38, 44.22], "bbox": [44.12, 45.33, 44.32, 45.43]},
94
+ "Будённовск (Ставроп. край)": {"center": [44.78, 44.15], "bbox": [44.05, 44.73, 44.25, 44.83]},
95
+ "Москва (тестовая область)": {"center": [55.75, 37.61], "bbox": [37.5, 55.7, 37.7, 55.8]}
96
+ }
97
+ return regions.get(region_name)
98
+
99
+ def generate_classified_map(region_info, year, classifier):
100
+ if not classifier: return None, "Ошибка: Классификатор не обучен."
101
+ print(f"-- Запрос на генерацию карты для {year} года...")
102
+ roi_to_classify = ee.Geometry.Rectangle(region_info['bbox'])
103
+ collection = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED') \
104
+ .filterDate(f'{year}-06-01', f'{year}-09-01') \
105
+ .filterBounds(roi_to_classify) \
106
+ .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 25))
107
+ if collection.size().getInfo() == 0: return None, f"⚠️ Не найдено снимков за {year} год."
108
+ image_to_classify = collection.map(add_ndvi).median().clip(roi_to_classify)
109
+ classified_image = image_to_classify.classify(classifier)
110
+ class_palette = ['#e3a25a', '#ffffff', '#ffff00', '#00ff00', '#0000FF']
111
+ vis_params = {'min': 0, 'max': 4, 'palette': class_palette}
112
+ map_id = classified_image.getMapId(vis_params)
113
+ print(f"-- ✅ Карта для {year} года сгенерирована.")
114
+ return {"center": region_info['center'], "tile_url": map_id['tile_fetcher'].url_format, "year": year}, None
115
+
116
+ def create_map_iframe_html(map_data, region_name):
117
+ center, tile_url, year = map_data['center'], map_data['tile_url'], map_data['year']
118
+ iframe_content = f"""
119
+ <!DOCTYPE html><html><head><title>Карта {year}</title><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" /><script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script><style>html, body, #map {{ height: 100%; width: 100%; margin: 0; padding: 0; }}</style></head><body><div id="map"></div><script>
120
+ var map = L.map('map').setView([{center[0]}, {center[1]}], 12);
121
+ L.tileLayer('https://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png', {{ attribution: '&copy; OpenStreetMap' }}).addTo(map);
122
+ L.tileLayer(`{tile_url}`, {{ attribution: 'Google Earth Engine' }}).addTo(map);
123
+ </script></body></html>"""
124
+ escaped_html = html.escape(iframe_content)
125
+ return f'<iframe srcdoc="{escaped_html}" style="width: 100%; height: 500px; border: 1px solid #ccc;"></iframe>'
126
+
127
+ def process_and_display_maps(region_name, year1, year2, year3):
128
+ if not gee_classifier: return "Ошибка: модель не обучена.", "", ""
129
+ region_info = get_region_info(region_name)
130
+ years = sorted(list(set([year1, year2, year3])))
131
+ outputs, messages = [], []
132
+ print(f"\n🚀 Новый запрос! Регион: {region_name}, Годы: {years}")
133
+ for year in years:
134
+ map_data, msg = generate_classified_map(region_info, year, gee_classifier)
135
+ if map_data: outputs.append(create_map_iframe_html(map_data, region_name))
136
+ else: outputs.append(f"<p style='text-align:center; padding-top: 200px;'>{msg}</p>")
137
+ if msg: messages.append(msg)
138
+ while len(outputs) < 3: outputs.append(None)
139
+ final_message = "✅ Готово. " + " ".join(messages)
140
+ return outputs[0], outputs[1], outputs[2], final_message
141
+
142
+ # =======================================================================
143
+ # ШАГ 5: Создание и запуск интерфейса Gradio
144
+ # (Код остается почти без изменений)
145
+ # =======================================================================
146
+ with gr.Blocks(css=".gradio-container {max-width: 1200px !important;}") as demo:
147
+ gr.Markdown("# 🛰️ Анализ почвенного покрова")
148
+ gr.Markdown("Выберите регион и три года для анализа. Карты появятся ниже.")
149
+ with gr.Row():
150
+ with gr.Column(scale=1):
151
+ region_dropdown = gr.Dropdown(
152
+ label="Регион",
153
+ choices=["Элиста (тестовая область)", "Озеро Сарпа (Калмыкия)", "Арзгир (Ставроп. край)", "Будённовск (Ставроп. край)", "Москва (тестовая область)"],
154
+ value="Элиста (тестовая область)")
155
+ year1_slider = gr.Slider(label="Год 1", minimum=2019, maximum=2025, step=1, value=2019)
156
+ year2_slider = gr.Slider(label="Год 2", minimum=2019, maximum=2025, step=1, value=2021)
157
+ year3_slider = gr.Slider(label="Год 3", minimum=2019, maximum=2025, step=1, value=2023)
158
+ submit_button = gr.Button("Сгенерировать карты", variant="primary")
159
+ status_message = gr.Markdown()
160
+ with gr.Row():
161
+ map1_output = gr.HTML()
162
+ map2_output = gr.HTML()
163
+ map3_output = gr.HTML()
164
+ submit_button.click(
165
+ fn=process_and_display_maps,
166
+ inputs=[region_dropdown, year1_slider, year2_slider, year3_slider],
167
+ outputs=[map1_output, map2_output, map3_output, status_message])
168
+
169
+ # --- ЗАПУСК ПРИЛОЖЕНИЯ ---
170
+ print("\n--- Запуск Gradio интерфейса ---")
171
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ geemap
3
+ earthengine-api