Sampling_Proj / pin.py
Louppian's picture
Upload folder using huggingface_hub
6b09cbd verified
Raw
History Blame Contribute Delete
13.2 kB
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)