Spaces:
Sleeping
Sleeping
| import os | |
| import io | |
| import matplotlib | |
| matplotlib.use("Agg") # λ°±μλ Aggλ‘ κ³ μ | |
| import geopandas as gpd | |
| import pandas as pd | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import contextily as ctx | |
| from shapely.geometry import Point | |
| from shapely import wkt as shp_wkt | |
| from PIL import Image | |
| import gradio as gr | |
| # -------------------- κ²½λ‘ μ€μ -------------------- | |
| SHP_PATH = r"C:\Users\USER\iCloudDrive\μΈνλνκ΅\5νλ 1νκΈ°\νλ³Έλ‘ λ° μ€μ΅\TeamProject\Data\inha_boundary.shp" | |
| CSV_PATH = r"C:\Users\USER\iCloudDrive\μΈνλνκ΅\5νλ 1νκΈ°\νλ³Έλ‘ λ° μ€μ΅\TeamProject\λ°μ΄ν° μμ§\μΈ‘μ κ²°κ³Ό.csv" | |
| # μ΄λ―Έμ§ ν΄μλ(ν½μ ) | |
| IMG_W, IMG_H = 1200, 900 | |
| # -------------------- μΊ νΌμ€ κ²½κ³ μ€λΉ -------------------- | |
| gdf_boundary_4326 = gpd.read_file(SHP_PATH).to_crs(epsg=4326) | |
| gdf_boundary_3857 = gdf_boundary_4326.to_crs(epsg=3857) | |
| # DeprecationWarning νΌνκΈ° μν΄ union_all μ°μ μ¬μ© | |
| try: | |
| poly_3857 = gdf_boundary_3857.geometry.union_all() | |
| except AttributeError: | |
| poly_3857 = gdf_boundary_3857.geometry.unary_union | |
| if poly_3857.geom_type == "MultiPolygon": | |
| poly_3857 = max(poly_3857.geoms, key=lambda g: g.area) | |
| BOUND_MINX, BOUND_MINY, BOUND_MAXX, BOUND_MAXY = poly_3857.bounds | |
| # -------------------- μ΄κΈ° CSV λ‘λ© -------------------- | |
| # -------------------- μ΄κΈ° CSV λ‘λ© -------------------- | |
| if os.path.exists(CSV_PATH): | |
| df_u = pd.read_csv(CSV_PATH, encoding="cp949") | |
| # μ»¬λΌ μ 리 | |
| df_u.columns = [c.strip() for c in df_u.columns] | |
| df_u = df_u.loc[:, ~df_u.columns.str.contains("^Unnamed")] | |
| if {"lat", "lon", "dB"}.issubset(df_u.columns): | |
| # μ΄λ―Έ lat/lon/dB νμμ΄λ©΄ κ·Έλλ‘ μ¬μ© | |
| df_init = df_u[["lat", "lon", "dB"]].copy() | |
| elif "μμΉ" in df_u.columns and "dB" in df_u.columns: | |
| # μλ νμ: μμΉ(WKT), dB β lat/lon/dBλ‘ λ³ν | |
| pos_series = df_u["μμΉ"].astype(str).str.strip() | |
| def safe_load_wkt(s): | |
| s_up = s.upper() | |
| if "POINT" not in s_up: | |
| return None | |
| try: | |
| return shp_wkt.loads(s) | |
| except Exception: | |
| return None | |
| geom = pos_series.apply(safe_load_wkt) | |
| mask = geom.notnull() | |
| if mask.any(): | |
| geom_valid = geom[mask] | |
| lons = geom_valid.apply(lambda g: round(g.x, 7)) | |
| lats = geom_valid.apply(lambda g: round(g.y, 7)) | |
| dB_vals = pd.to_numeric(df_u.loc[mask, "dB"], errors="coerce") | |
| df_init = pd.DataFrame( | |
| { | |
| "lat": lats.values, | |
| "lon": lons.values, | |
| "dB": dB_vals.values, | |
| } | |
| ).dropna(subset=["lat", "lon"]) | |
| else: | |
| df_init = pd.DataFrame(columns=["lat", "lon", "dB"]) | |
| else: | |
| # νμμ μμλ³Ό μ μμΌλ©΄ λΉ DF | |
| df_init = pd.DataFrame(columns=["lat", "lon", "dB"]) | |
| else: | |
| df_init = pd.DataFrame(columns=["lat", "lon", "dB"]) | |
| # νμ 보μ | |
| for col in ["lat", "lon", "dB"]: | |
| if col not in df_init.columns: | |
| df_init[col] = pd.Series(dtype=float) | |
| # -------------------- μ§λ μ΄λ―Έμ§ μμ± -------------------- | |
| def make_map_image(df_points: pd.DataFrame): | |
| """μΊ νΌμ€ κ²½κ³ + μ μ₯λ ν¬μΈνΈλ€μ κ·Έλ¦° PNG μ΄λ―Έμ§λ₯Ό numpy λ°°μ΄λ‘ λ°ν.""" | |
| fig, ax = plt.subplots( | |
| figsize=(IMG_W / 100, IMG_H / 100), dpi=100 | |
| ) # β μ νν IMG_W x IMG_H ν½μ | |
| # μΊ νΌμ€ ν΄λ¦¬κ³€ (ν λ리λ§) | |
| gdf_boundary_3857.plot( | |
| ax=ax, | |
| facecolor="none", | |
| edgecolor="darkblue", | |
| linewidth=2, | |
| alpha=1.0, | |
| ) | |
| # λ² μ΄μ€λ§΅ | |
| ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik, alpha=0.7) | |
| # μ μ₯λ ν¬μΈνΈ μ°κΈ° | |
| df_points = df_points.dropna(subset=["lat", "lon"]) | |
| if len(df_points) > 0: | |
| gdf_pts = gpd.GeoDataFrame( | |
| df_points.copy(), | |
| geometry=gpd.points_from_xy(df_points["lon"], df_points["lat"]), | |
| crs=4326, | |
| ).to_crs(epsg=3857) | |
| gdf_pts.plot( | |
| ax=ax, | |
| column="dB", | |
| cmap="viridis", | |
| markersize=40, | |
| edgecolor="black", | |
| linewidth=0.3, | |
| alpha=0.9, | |
| legend=False, | |
| zorder=5, | |
| ) | |
| # ν΄λ¦¬κ³€ boundsμ λ± λ§κ² | |
| ax.set_xlim(BOUND_MINX, BOUND_MAXX) | |
| ax.set_ylim(BOUND_MINY, BOUND_MAXY) | |
| ax.axis("off") | |
| # μ¬λ°± μ κ±°ν΄μ μ 체 μΊλ²μ€λ₯Ό μ§λμ λ§μΆ€ | |
| plt.subplots_adjust(0, 0, 1, 1) | |
| # Figure -> PNG bytes -> PIL -> numpy | |
| buf = io.BytesIO() | |
| fig.savefig(buf, format="png", dpi=100) | |
| buf.seek(0) | |
| img = np.array(Image.open(buf)) | |
| plt.close(fig) | |
| return img | |
| # -------------------- ν½μ β μκ²½λ λ³ν -------------------- | |
| def pixel_to_latlon(x_px: int, y_px: int): | |
| """ | |
| Gradio μ΄λ―Έμ§ ν΄λ¦ μ’ν(ν½μ , (x, y))λ₯Ό | |
| EPSG:3857 β EPSG:4326(lat, lon)μΌλ‘ λ³ν. | |
| """ | |
| # x: [0, IMG_W] -> [BOUND_MINX, BOUND_MAXX] | |
| x_3857 = BOUND_MINX + (BOUND_MAXX - BOUND_MINX) * (x_px / IMG_W) | |
| # y: μκ° 0, μλκ° IMG_H β [BOUND_MAXY, BOUND_MINY]λ‘ λ§€ν | |
| y_3857 = BOUND_MAXY - (BOUND_MAXY - BOUND_MINY) * (y_px / IMG_H) | |
| g = gpd.GeoSeries([Point(x_3857, y_3857)], crs=3857).to_crs(epsg=4326) | |
| pt = g.iloc[0] | |
| lat, lon = pt.y, pt.x | |
| # μ¬κΈ°μ μμμ 7μλ¦¬λ‘ λ°μ¬λ¦Ό | |
| return round(float(lat), 7), round(float(lon), 7) | |
| # -------------------- Gradio μ½λ°± -------------------- | |
| def on_map_click(img, df_state, evt: gr.SelectData): | |
| """μ΄λ―Έμ§ ν΄λ¦ μ μκ²½λ ν μ€νΈ λ°μ€ κ°±μ .""" | |
| if evt is None or evt.index is None: | |
| return None, None | |
| x, y = evt.index # (x_px, y_px) | |
| lat, lon = pixel_to_latlon(x, y) | |
| return lat, lon # μ΄λ―Έ 7μλ¦¬λ‘ λ°μ¬λ¦Όλ κ° | |
| def add_point(lat, lon, db, df_state): | |
| """lat/lon/dB μΆκ°νκ³ CSV μ μ₯ + μ§λ κ°±μ .""" | |
| if df_state is None or isinstance(df_state, dict): | |
| df_state = df_init.copy() | |
| if lat is None or lon is None or db is None: | |
| msg = "μλ/κ²½λ/dB μ λ ₯ μ€λ₯." | |
| return df_state, make_map_image(df_state), df_state, msg | |
| try: | |
| lat = float(lat) | |
| lon = float(lon) | |
| db = float(db) | |
| except ValueError: | |
| msg = "μ«μ μ€λ₯." | |
| return df_state, make_map_image(df_state), df_state, msg | |
| # μμ νκ² ν λ² λ 7μλ¦¬λ‘ λ§μΆ€ | |
| lat = round(lat, 7) | |
| lon = round(lon, 7) | |
| # ν΄λ¦¬κ³€ μμΈμ§ μ²΄ν¬ | |
| pt = gpd.GeoSeries([Point(lon, lat)], crs=4326).to_crs(epsg=3857).iloc[0] | |
| if not poly_3857.contains(pt): | |
| msg = "μΊ νΌμ€ κ²½κ³ λ°." | |
| return df_state, make_map_image(df_state), df_state, msg | |
| new_row = pd.DataFrame([{"lat": lat, "lon": lon, "dB": db}]) | |
| new_df = pd.concat([df_state, new_row], ignore_index=True) | |
| os.makedirs(os.path.dirname(CSV_PATH), exist_ok=True) | |
| new_df[["lat", "lon", "dB"]].to_csv(CSV_PATH, index=False, encoding="cp949") | |
| # λ©μμ§λ 7μλ¦¬λ‘ | |
| msg = f"{len(new_df)}κ° μ μ μ₯λ¨ (λ§μ§λ§: lat={lat:.7f}, lon={lon:.7f}, dB={db:.1f})" | |
| return new_df, make_map_image(new_df), new_df, msg | |
| def load_uploaded_csv(file, df_state): | |
| """ | |
| CSV μ λ‘λ μ: | |
| - (μΌμ΄μ€ A) 'μμΉ', 'dB' 컬λΌμ΄ μμΌλ©΄: μμΉ(WKT) -> lat/lon, dB κ·Έλλ‘ | |
| - (μΌμ΄μ€ B) 'lat', 'lon', 'dB' 컬λΌμ΄ μμΌλ©΄: κ·Έλλ‘ μ¬μ© | |
| - λ λ€ μλλ©΄ μλ¬ | |
| """ | |
| if df_state is None or isinstance(df_state, dict): | |
| df_state = df_init.copy() | |
| if file is None: | |
| msg = "μ λ‘λλ νμΌ μμ." | |
| return df_state, make_map_image(df_state), df_state, msg | |
| # CSV μ½κΈ° (μΈμ½λ© μλ) | |
| try: | |
| df_u = pd.read_csv(file.name, encoding="cp949") | |
| except UnicodeDecodeError: | |
| df_u = pd.read_csv(file.name, encoding="utf-8") | |
| # μ»¬λΌ μ΄λ¦ 곡백/Unnamed μ 리 | |
| df_u.columns = [c.strip() for c in df_u.columns] | |
| df_u = df_u.loc[:, ~df_u.columns.str.contains("^Unnamed")] | |
| # ---------- μΌμ΄μ€ B: lat/lon/dB νμ ---------- | |
| if {"lat", "lon", "dB"}.issubset(df_u.columns): | |
| new_df = df_u[["lat", "lon", "dB"]].copy() | |
| # μ«μ/λ°μ¬λ¦Ό 보μ | |
| new_df["lat"] = pd.to_numeric(new_df["lat"], errors="coerce").round(7) | |
| new_df["lon"] = pd.to_numeric(new_df["lon"], errors="coerce").round(7) | |
| new_df["dB"] = pd.to_numeric(new_df["dB"], errors="coerce") | |
| new_df = new_df.dropna(subset=["lat", "lon"]) | |
| df_merged = new_df.copy() | |
| os.makedirs(os.path.dirname(CSV_PATH), exist_ok=True) | |
| df_merged[["lat", "lon", "dB"]].to_csv(CSV_PATH, index=False, encoding="cp949") | |
| msg = f"lat/lon/dB νμ CSVμμ {len(df_merged)}κ° μ μ½μ΄μ΄." | |
| return df_merged, make_map_image(df_merged), df_merged, msg | |
| # ---------- μΌμ΄μ€ A: μμΉ(WKT), dB νμ ---------- | |
| if "μμΉ" in df_u.columns and "dB" in df_u.columns: | |
| pos_series = df_u["μμΉ"].astype(str).str.strip() | |
| def safe_load_wkt(s): | |
| s_up = s.upper() | |
| if "POINT" not in s_up: | |
| return None | |
| try: | |
| return shp_wkt.loads(s) | |
| except Exception: | |
| return None | |
| geom = pos_series.apply(safe_load_wkt) | |
| mask = geom.notnull() | |
| if not mask.any(): | |
| msg = "'μμΉ' 컬λΌμ μ ν¨ν POINT WKTκ° μλ€." | |
| return df_state, make_map_image(df_state), df_state, msg | |
| geom_valid = geom[mask] | |
| lons = geom_valid.apply(lambda g: round(g.x, 7)) | |
| lats = geom_valid.apply(lambda g: round(g.y, 7)) | |
| dB_vals = pd.to_numeric(df_u.loc[mask, "dB"], errors="coerce") | |
| new_df = pd.DataFrame( | |
| { | |
| "lat": lats.values, | |
| "lon": lons.values, | |
| "dB": dB_vals.values, | |
| } | |
| ).dropna(subset=["lat", "lon"]) | |
| df_merged = new_df.copy() | |
| os.makedirs(os.path.dirname(CSV_PATH), exist_ok=True) | |
| df_merged[["lat", "lon", "dB"]].to_csv(CSV_PATH, index=False, encoding="cp949") | |
| msg = f"[μμΉ, dB] νμ CSVμμ μ ν¨ν μ {len(df_merged)}κ° μ½μ΄μ΄." | |
| return df_merged, make_map_image(df_merged), df_merged, msg | |
| # ---------- λ λ€ μλλ©΄ ---------- | |
| msg = "CSV νμμ μμλ³Ό μ μλ€. (μμΉ,dB λλ lat,lon,dB νμ)" | |
| return df_state, make_map_image(df_state), df_state, msg | |
| # -------------------- Gradio μ± -------------------- | |
| def build_app(): | |
| init_img = make_map_image(df_init) | |
| with gr.Blocks(title="μμ μΈ‘μ κΈ°λ‘κΈ°") as demo: | |
| gr.Markdown( | |
| "## μΈνλ μΊ νΌμ€ μμ μΈ‘μ \n" | |
| "- μ§λ μ΄λ―Έμ§ ν΄λ¦ β μ/κ²½λ μλ μ λ ₯ β dB μ μ₯\n" | |
| "- λλ [μμΉ, dB] CSV μ λ‘λν΄μ ν λ²μ 보μ¬μ£ΌκΈ°" | |
| ) | |
| df_state = gr.State(df_init) | |
| with gr.Row(): | |
| map_img = gr.Image( | |
| value=init_img, | |
| type="numpy", | |
| label=None, | |
| interactive=True, | |
| ) | |
| with gr.Column(): | |
| lat_box = gr.Number(label="μλ (lat)", interactive=False) | |
| lon_box = gr.Number(label="κ²½λ (lon)", interactive=False) | |
| db_box = gr.Number(label="λ°μ벨 (dB)") | |
| add_btn = gr.Button("νμ¬ μ’ν μΆκ° + CSV μ μ₯", variant="primary") | |
| csv_upload = gr.File( | |
| label="CSV μ λ‘λ ([μμΉ, dB] νμ)", | |
| file_types=[".csv"], | |
| ) | |
| status = gr.Markdown() | |
| table = gr.Dataframe( | |
| value=df_init, | |
| headers=["lat", "lon", "dB"], | |
| label="μ μ₯λ μΈ‘μ κ°", | |
| interactive=False, | |
| wrap=True, | |
| ) | |
| # μ΄λ―Έμ§ ν΄λ¦ μ: lat/lon κ°±μ | |
| map_img.select( | |
| fn=on_map_click, | |
| inputs=[map_img, df_state], | |
| outputs=[lat_box, lon_box], | |
| ) | |
| # λ²νΌ ν΄λ¦ μ: DF/μ§λ/μν κ°±μ + CSV μ μ₯ | |
| add_btn.click( | |
| fn=add_point, | |
| inputs=[lat_box, lon_box, db_box, df_state], | |
| outputs=[table, map_img, df_state, status], | |
| ) | |
| # CSV μ λ‘λ μ: μ λ‘λ λ°μ΄ν°λ‘ μ§λ/ν μ΄λΈ/μν κ°±μ | |
| csv_upload.change( | |
| fn=load_uploaded_csv, | |
| inputs=[csv_upload, df_state], | |
| outputs=[table, map_img, df_state, status], | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| app = build_app() | |
| app.launch(share=True) | |