Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env
__pycache__/
./venv
.idea
3 changes: 0 additions & 3 deletions .idea/.gitignore

This file was deleted.

24 changes: 0 additions & 24 deletions .idea/inspectionProfiles/Project_Default.xml

This file was deleted.

6 changes: 3 additions & 3 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 0 additions & 10 deletions .idea/nonlinear_process_manager.iml

This file was deleted.

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# industrial-emission-impact-forecaster
AI-powered system for predicting and mapping harmful substance concentrations. Leverages historical emission data from industrial sources, terrain analysis, and weather patterns to create real-time pollution dispersion forecasts and visualizations.

Visual interface prototype (click to make an access request): https://www.figma.com/design/o5duNLISrMPGriaWRyAwBy/ConcViewer-Online?node-id=0-1&t=RTBJ7TECdi2ffIIN-1
16 changes: 0 additions & 16 deletions app.py

This file was deleted.

Empty file added backend/__init__.py
Empty file.
Empty file added backend/app/__init__.py
Empty file.
32 changes: 32 additions & 0 deletions backend/app/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from .routers import pages, sources_api, simulation_params_api, layout, substances_api
from .core.config import STATIC_DIR
from .database import init_models
from contextlib import asynccontextmanager
from . import models

"""
как только будет Alembic - удалить init_model
заменить app = FastAPI(lifespan=lifespan) на app = FastAPI()
"""


@asynccontextmanager
async def lifespan(app: FastAPI):
print("Пересоздание таблиц в БД...")
await init_models()
print("Таблицы готовы.")
yield
print("Завершение работы...")


app = FastAPI(lifespan=lifespan)

app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")

app.include_router(pages.router)
app.include_router(sources_api.router)
app.include_router(simulation_params_api.router)
app.include_router(layout.router)
app.include_router(substances_api.router)
Empty file added backend/app/core/__init__.py
Empty file.
47 changes: 47 additions & 0 deletions backend/app/core/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from pathlib import Path
from pydantic_settings import BaseSettings, SettingsConfigDict

BASE_DIR = Path(__file__).resolve().parents[3]

TEMPLATES_DIR = BASE_DIR / "templates"
STATIC_DIR = BASE_DIR / "static"




class Settings(BaseSettings):
# --- База данных ---
database_hostname: str
database_port: str
database_password: str
database_name: str
database_username: str

# --- Авторизация (На будущее) ---
# secret_key: str
# algorithm: str
# access_token_expiration_minutes: int

# --- Yandex карты ---
ymaps_api_key: str
ymaps_lang: str = "ru_RU"
yweather_api_key: str

# extra="ignore" позволяет иметь в .env лишние переменные без ошибок
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")

@property
def template_config(self) -> dict:
return {
"YMAPS_API_KEY": self.ymaps_api_key,
"YMAPS_LANG": self.ymaps_lang,
"YWEATHER_API_KEY": self.yweather_api_key,
}

try:
settings = Settings()
except Exception as e:
print(f"ОШИБКА ЗАГРУЗКИ КОНФИГУРАЦИИ: {e}")
raise e

TEMPLATE_CONFIG = settings.template_config
Empty file.
34 changes: 34 additions & 0 deletions backend/app/core/emissions_calculation_math/coloring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import numpy as np


def colorize_tile_numpy(conc_grid, pdk=0.008, alpha_bg=110):
c = conc_grid.flatten()

# Цвета (R, G, B, Alpha)
color_green = np.array([46, 204, 113, alpha_bg])
color_yellow = np.array([241, 196, 15, 180])
color_orange = np.array([230, 126, 34, 210])
color_red = np.array([231, 76, 60, 240])

rgba = np.zeros((len(c), 4), dtype=np.float32)

t0, t1, t2, t3 = pdk * 0.001, pdk * 0.2, pdk * 0.6, pdk * 1.0

m_bg = c <= t0
m_gy = (c > t0) & (c <= t1)
m_yo = (c > t1) & (c <= t2)
m_or = (c > t2)

rgba[m_bg] = color_green

if np.any(m_gy):
f = (c[m_gy] - t0) / (t1 - t0)
rgba[m_gy] = color_green * (1 - f[:, None]) + color_yellow * f[:, None]
if np.any(m_yo):
f = (c[m_yo] - t1) / (t2 - t1)
rgba[m_yo] = color_yellow * (1 - f[:, None]) + color_orange * f[:, None]
if np.any(m_or):
f = np.clip((c[m_or] - t2) / (t3 - t2), 0, 1)
rgba[m_or] = color_orange * (1 - f[:, None]) + color_red * f[:, None]

return rgba.astype(np.uint8).reshape((256, 256, 4))
103 changes: 103 additions & 0 deletions backend/app/core/emissions_calculation_math/discretization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import numpy as np
from matplotlib.path import Path
from backend.app.core.emissions_calculation_math.math_numba import lat_lon_to_meters_yandex_single


def discretize_sources(sources_db):
v_xs, v_ys, v_rates, v_heights = [], [], [], []
v_sy0, v_sz0, v_settling = [], [], [] # Массивы для начального расширения

MAX_POINTS_PER_SOURCE = 200
MIN_STEP_METERS = 100.0

for s in sources_db:
s_type_str = s.type.value if hasattr(s.type, 'value') else str(s.type)

settling_vel = s.pollutant.settling_velocity if hasattr(s, 'pollutant') and s.pollutant else 0.0

if s_type_str == "point" or not s.coordinates:
mx, my = lat_lon_to_meters_yandex_single(s.latitude, s.longitude)
v_xs.append(mx)
v_ys.append(my)
v_rates.append(s.emission_rate)
v_heights.append(s.height)
v_sy0.append(0.0)
v_sz0.append(0.0)
v_settling.append(settling_vel)

elif s_type_str == "line":
coords = s.coordinates
line_points = []
for i in range(len(coords) - 1):
p1_x, p1_y = lat_lon_to_meters_yandex_single(coords[i][0], coords[i][1])
p2_x, p2_y = lat_lon_to_meters_yandex_single(coords[i + 1][0], coords[i + 1][1])

dist = np.hypot(p2_x - p1_x, p2_y - p1_y)

step = max(MIN_STEP_METERS, dist / MAX_POINTS_PER_SOURCE)
num_points = max(2, int(dist / step))

xs = np.linspace(p1_x, p2_x, num_points, endpoint=True)
ys = np.linspace(p1_y, p2_y, num_points, endpoint=True)
line_points.extend(list(zip(xs, ys)))

if not line_points:
continue

unique_points = list(dict.fromkeys(line_points))
rate_per_point = s.emission_rate / len(unique_points)

for x, y in unique_points:
v_xs.append(x)
v_ys.append(y)
v_rates.append(rate_per_point)
v_heights.append(s.height)
v_sy0.append(step)
v_sz0.append(5.0)
v_settling.append(settling_vel)

elif s_type_str == "polygon":
coords = s.coordinates
if coords[0] != coords[-1]:
coords.append(coords[0])

poly_points = [lat_lon_to_meters_yandex_single(c[0], c[1]) for c in coords]
poly_path = Path(poly_points)

xs, ys = zip(*poly_points)
min_x, max_x = min(xs), max(xs)
min_y, max_y = min(ys), max(ys)

area = (max_x - min_x) * (max_y - min_y)
dynamic_step = max(MIN_STEP_METERS, np.sqrt(area / MAX_POINTS_PER_SOURCE))

grid_x, grid_y = np.meshgrid(
np.arange(min_x, max_x, dynamic_step),
np.arange(min_y, max_y, dynamic_step)
)
flat_grid = np.vstack((grid_x.flatten(), grid_y.flatten())).T
mask = poly_path.contains_points(flat_grid)
inside_points = flat_grid[mask]

if len(inside_points) == 0:
inside_points = [[np.mean(xs), np.mean(ys)]]

rate_per_point = s.emission_rate / len(inside_points)

for x, y in inside_points:
v_xs.append(x)
v_ys.append(y)
v_rates.append(rate_per_point)
v_heights.append(s.height)
v_sy0.append(dynamic_step)
v_sz0.append(5.0)
v_settling.append(settling_vel)
return (
np.array(v_xs, dtype=np.float32),
np.array(v_ys, dtype=np.float32),
np.array(v_rates, dtype=np.float32),
np.array(v_heights, dtype=np.float32),
np.array(v_sy0, dtype=np.float32),
np.array(v_sz0, dtype=np.float32),
np.array(v_settling, dtype=np.float32)
)
Loading