Refactor: typing cleanup, 경로 하드코딩 제거, GPS 데이터 Lon/Lat 분리, 불필요 모듈 삭제

This commit is contained in:
minar
2026-06-24 03:09:16 +09:00
parent f662e4fd1b
commit aa5228a526
12 changed files with 124 additions and 523 deletions

6
.gitignore vendored
View File

@@ -188,5 +188,11 @@ poetry.toml
# LSP config files # LSP config files
pyrightconfig.json pyrightconfig.json
# Sample Images
/Images
*복사본.xlsx
.~lock*
# End of https://www.toptal.com/developers/gitignore/api/python,linux # End of https://www.toptal.com/developers/gitignore/api/python,linux

Binary file not shown.

View File

@@ -1,5 +1,5 @@
import os import os
import sys #import sys
import UtilPack as util import UtilPack as util
import PoleXLS as myxl import PoleXLS as myxl
@@ -7,38 +7,8 @@ import ExtEXIFData as exEXIF
import VWorldAPIs as vwapi import VWorldAPIs as vwapi
import JusoMngr import JusoMngr
mCurPath = "" # 스크립트가 실행되는 경로
mXlsPath = "" # 엑셀 파일이 있는 경로, 틀리면 안됨.
# "/Volumes/ExSSD/Working/Images" # 이미지가 들어있는 폴더, 이 아래에 A,B,Pole 등이 있어야 한다. def GetPoleInfos(xls, nRow:int):
mImgPath = ""
def WorkingFolderCheck(path, xlsPath):
if False == os.path.exists(path):
print(f"Error: Working Folder is not exist. {path}")
return False
pathList = ["A", "B", "Pole"]
for folder in pathList:
fullPath = os.path.join(path, folder)
if False == os.path.isdir(fullPath):
print(f"Error: {folder} Folder is not exist. {fullPath}")
return False
mImgPath = path
mCurPath = os.getcwd()
if False == os.path.exists(xlsPath):
mXlsPath = os.getcwd()
else:
mXlsPath = xlsPath
return True
def GetPoleInfos(xls, nRow):
if None == xls or False == isinstance(xls, myxl.PoleXLS): if None == xls or False == isinstance(xls, myxl.PoleXLS):
return [] return []
@@ -51,7 +21,8 @@ def CollectImageData(xls, sheetTrg, listImg, pathImgDir):
if None == xls or False == isinstance(xls, myxl.PoleXLS) or True == util.IsEmptyStr(sheetTrg): if None == xls or False == isinstance(xls, myxl.PoleXLS) or True == util.IsEmptyStr(sheetTrg):
return -1 return -1
pathcsv = os.path.join(mCurPath, "JusoDB.csv") pathCurrent = os.getcwd()
pathcsv = os.path.join(pathCurrent, "JusoDB.csv")
jusomgr = JusoMngr.JusoDB(pathcsv) jusomgr = JusoMngr.JusoDB(pathcsv)
nIdx = 1 nIdx = 1
@@ -86,18 +57,20 @@ def CollectImageData(xls, sheetTrg, listImg, pathImgDir):
xls.SetCellValueStr(sheetTrg, 3, nIdx, all_date) xls.SetCellValueStr(sheetTrg, 3, nIdx, all_date)
xls.SetCellValueStr(sheetTrg, 4, nIdx, level) xls.SetCellValueStr(sheetTrg, 4, nIdx, level)
xls.SetCellValueStr(sheetTrg, 5, nIdx, pathRel) xls.SetCellValueStr(sheetTrg, 5, nIdx, pathRel)
xls.SetCellValueStr(sheetTrg, 6, nIdx, juso[3]) xls.SetCellValueStr(sheetTrg, 6, nIdx, juso[1])
xls.SetCellValueStr(sheetTrg, 7, nIdx, juso[2]) xls.SetCellValueStr(sheetTrg, 7, nIdx, juso[0])
xls.SetCellValueStr(sheetTrg, 8, nIdx, f"{lat},{lon}") xls.SetCellValueStr(sheetTrg, 8, nIdx, juso[3])
xls.SetCellValueStr(sheetTrg, 9, nIdx, juso[1]) xls.SetCellValueStr(sheetTrg, 9, nIdx, juso[2])
xls.SetCellValueStr(sheetTrg, 10, nIdx, juso[0]) xls.SetCellValueStr(sheetTrg, 10, nIdx, f"{lon}")
xls.SetCellValueStr(sheetTrg, 11, nIdx, f"{lat}")
# VWorld 좌표로 변환하여 저장 # VWorld 좌표로 변환하여 저장
lat_Vworld, lon_Vworld = 0.0, 0.0 lat_Vworld, lon_Vworld = 0.0, 0.0
if lat != 0.0 and lon != 0.0 : if lat != 0.0 and lon != 0.0 :
lat_Vworld, lon_Vworld = vwapi.Transform4326to3857(lat, lon) lat_Vworld, lon_Vworld = vwapi.Transform4326to3857(lat, lon)
xls.SetCellValueStr(sheetTrg, 11, nIdx, f"{lat_Vworld},{lon_Vworld}") xls.SetCellValueStr(sheetTrg, 12, nIdx, f"{lon_Vworld}")
xls.SetCellValueStr(sheetTrg, 13, nIdx, f"{lat_Vworld}")
nIdx += 1 nIdx += 1
@@ -105,20 +78,23 @@ def CollectImageData(xls, sheetTrg, listImg, pathImgDir):
# DB 시트에 있는 파일을 골라내서 PoleInfo 시트에 넣는다. # DB 시트에 있는 파일을 골라내서 PoleInfo 시트에 넣는다.
# PoleInfo : 번호, GPS 좌표, 구역, 주소, 사진 # PoleInfo : 번호, GPS 좌표, 구역, 주소, 사진
def CollectPoleInfoData(xls, TrgSheetName, DBSheetName, pathImgDir): def CollectPoleInfoData(xls, TrgSheetName:str, DBSheetName:str, pathImgDir:str)-> int:
if None == xls: if None == xls:
return -1 return -1
nLastDBRow = xls.FindLastRow( DBSheetName ) nLastDBRow:int = xls.FindLastRow( DBSheetName )
if 0 >= nLastDBRow: if 0 >= nLastDBRow:
print(f"{DBSheetName} Sheet is Empty.") print(f"{DBSheetName} Sheet is Empty.")
return -1 return -1
# 헤더가 있는 시트는 헤더가 1, 그래서 2부터 시작 # 헤더가 있는 시트는 헤더의 인덱스가 1, 그래서 2부터 시작
nTrgRow = xls.FindLastRow(TrgSheetName) + 1 nTrgRow = xls.FindLastRow(TrgSheetName) + 1
for nDBRow in range(1, nLastDBRow + 1): for nDBRow in range(1, nLastDBRow + 1):
listValues = xls.GetAllRowValue(DBSheetName, nDBRow, 8) listValues = xls.GetAllRowValue(DBSheetName, nDBRow, 11)
# 0: Index, 1: Sheetname, 2:Date, 3:Level, 4:Rel Path, 5:Area, 6:Juso, 7:GPS(Lat,Lon)
# 0: Index, 1: Sheetname, 2: Date, 3: Level, 4: Rel Path,
# 5: Area, 6: Juso, 7: Area_Road, 8: Juso_Road,
# 9: GPS(Lon), 10: GPS(Lat), 11: VWorld(Lon), 12: VWorld(Lat)
if None == listValues: if None == listValues:
continue continue
@@ -128,13 +104,14 @@ def CollectPoleInfoData(xls, TrgSheetName, DBSheetName, pathImgDir):
if listValues[3] != "Pole" : if listValues[3] != "Pole" :
continue continue
if listValues[7] == "" or listValues[7] == "0.0,0.0": # GPS 좌표가 없으면 패스
if listValues[9] == "" or listValues[10] == "":
continue continue
xls.SetCellValueStr(TrgSheetName, 1, nTrgRow, str(nTrgRow -1)) xls.SetCellValueStr(TrgSheetName, 1, nTrgRow, str(nTrgRow -1))
xls.SetCellValueStr(TrgSheetName, 2, nTrgRow, listValues[7]) xls.SetCellValueStr(TrgSheetName, 2, nTrgRow, listValues[5])
xls.SetCellValueStr(TrgSheetName, 3, nTrgRow, listValues[5]) xls.SetCellValueStr(TrgSheetName, 3, nTrgRow, listValues[6])
xls.SetCellValueStr(TrgSheetName, 4, nTrgRow, listValues[6]) xls.SetCellValueStr(TrgSheetName, 4, nTrgRow, listValues[7])
imgPath = os.path.join(pathImgDir, listValues[4]) imgPath = os.path.join(pathImgDir, listValues[4])
imgPath = exEXIF.ProcessImg(imgPath, 0.1, 80, True) imgPath = exEXIF.ProcessImg(imgPath, 0.1, 80, True)
@@ -142,6 +119,9 @@ def CollectPoleInfoData(xls, TrgSheetName, DBSheetName, pathImgDir):
xls.SetCellValueStr(TrgSheetName, 6, nTrgRow, listValues[0]) xls.SetCellValueStr(TrgSheetName, 6, nTrgRow, listValues[0])
xls.SetCellValueStr(TrgSheetName, 7, nTrgRow, listValues[9])
xls.SetCellValueStr(TrgSheetName, 8, nTrgRow, listValues[10])
# 목표 시트에 넣었으니 시트 이름을 DB 시트에 적어 넣는다. # 목표 시트에 넣었으니 시트 이름을 DB 시트에 적어 넣는다.
xls.SetCellValueStr(DBSheetName, 2, nDBRow, TrgSheetName) xls.SetCellValueStr(DBSheetName, 2, nDBRow, TrgSheetName)
@@ -163,9 +143,11 @@ def CollectPoleData( xls, TrgSheetName, InfoSheetName, DBSheetName, pathImgDir )
# 헤더가 있는 시트는 헤더가 1, 그래서 2부터 시작 # 헤더가 있는 시트는 헤더가 1, 그래서 2부터 시작
nTrgRow = xls.FindLastRow(TrgSheetName) + 1 nTrgRow = xls.FindLastRow(TrgSheetName) + 1
for nDBRow in range(1, nLastDBRow + 1): for nDBRow in range(1, nLastDBRow + 1):
listValues = xls.GetAllRowValue(DBSheetName, nDBRow, 8) listValues = xls.GetAllRowValue(DBSheetName, nDBRow, 13)
# 0: Index, 1: Sheetname, 2:Date, 3:Level, 4:Rel Path, 5:Area, 6:Juso, 7:GPS(Lat,Lon) # 0: Index, 1: Sheetname, 2: Date, 3: Level, 4: Rel Path,
# 5: Area, 6: Juso, 7: Area_Road, 8: Juso_Road,
# 9: GPS(Lon), 10: GPS(Lat), 11: VWorld(Lon), 12: VWorld(Lat)
if None == listValues: if None == listValues:
continue continue
@@ -175,7 +157,8 @@ def CollectPoleData( xls, TrgSheetName, InfoSheetName, DBSheetName, pathImgDir )
if listValues[3] != "A" and listValues[3] != "B" : if listValues[3] != "A" and listValues[3] != "B" :
continue continue
if listValues[7] == "" or listValues[7] == "0.0,0.0": # GPS 좌표가 없으면 패스
if listValues[9] == "" or listValues[10] == "":
continue continue
# 날짜만 골라낸다. # 날짜만 골라낸다.
@@ -188,12 +171,13 @@ def CollectPoleData( xls, TrgSheetName, InfoSheetName, DBSheetName, pathImgDir )
xls.SetCellValueStr(TrgSheetName, 2, nTrgRow, strDate) xls.SetCellValueStr(TrgSheetName, 2, nTrgRow, strDate)
# 가장 가까운 점봇대를 찾는다. 인덱스를 얻어서... # 가장 가까운 점봇대를 찾는다. 인덱스를 얻어서...
strLat, strLon = listValues[7] .split(',') strLon = listValues[9]
strLat = listValues[10]
nPoleIdx, nDistance = xls.FindCloestPole(float(strLat), float(strLon)) nPoleIdx, nDistance = xls.FindCloestPole(float(strLat), float(strLon))
# 배열은 0부터, Pole 인덱스는 1부터 시작하고, 셀 번호는 헤더를 포함해서 2 부터 시작한다. # 배열은 0부터, Pole 인덱스는 1부터 시작하고, 셀 번호는 헤더를 포함해서 2 부터 시작한다.
# 셀 번호를 넣어서 해당하는 정보를 가져온다. # 셀 번호를 넣어서 해당하는 정보를 가져온다.
# 0:번호, 1:GPS 좌표, 2:구역, 3:주소, 4:사진 # 0:번호, 1:구역, 2:주소, 3: 도로명, 4:사진, 5: ImgIndex, 6:GPS(Lon), 7:GPS(Lat)
#print( f"{nDBRow} : {nTrgRow} : {nPoleIdx}") #print( f"{nDBRow} : {nTrgRow} : {nPoleIdx}")
retList = xls.GetAllRowValue(InfoSheetName, nPoleIdx, 5) retList = xls.GetAllRowValue(InfoSheetName, nPoleIdx, 5)
@@ -220,13 +204,13 @@ def CollectPoleData( xls, TrgSheetName, InfoSheetName, DBSheetName, pathImgDir )
return nTrgRow return nTrgRow
def GetJPGFileLIst(pathImageDir): def GetJPGFileLIst(pathImageDir:str) -> list[str]:
trgDirList = util.GetSubDirectories(pathImageDir) trgDirList:list[str] = util.GetSubDirectories(pathImageDir)
#print(f"DBG : GetJPGFileLIst : {pathImageDir}, {trgDirList}") #print(f"DBG : GetJPGFileLIst : {pathImageDir}, {trgDirList}")
#input("Press Enter to continue...") #input("Press Enter to continue...")
retImgList = [] retImgList:list[str] = []
for dirName in trgDirList: for dirName in trgDirList:
if dirName.lower() == "thumb": if dirName.lower() == "thumb":
continue continue
@@ -243,9 +227,9 @@ def GetJPGFileLIst(pathImageDir):
return retImgList return retImgList
def Process(PathImg, PathXLS): def Process(PathImg:str, PathXLS:str):
# 이미지 파일 목록 작성 # 이미지 파일 목록 작성
trgImgList = GetJPGFileLIst(PathImg) trgImgList:list[str] = GetJPGFileLIst(PathImg)
# 엑셀을 연다. # 엑셀을 연다.
XLSPath = os.path.join(PathXLS, "EPoleDB.xlsx") XLSPath = os.path.join(PathXLS, "EPoleDB.xlsx")

View File

@@ -1,8 +1,9 @@
import cv2 import cv2
import pytesseract import pytesseract
from pytesseract import Output from pytesseract import Output
import UtilPack as util
img_path = '/Volumes/ExSSD/Working/용공추 사진/2,3월 데이터/Pole/20250307_153821.jpg' img_path = util.NormalizePath('/Volumes/ExSSD/Working/용공추 사진/2,3월 데이터/Pole/20250307_153821.jpg')
# 이미지 경로 # 이미지 경로
img = cv2.imread(img_path) img = cv2.imread(img_path)

View File

@@ -143,7 +143,8 @@ class MyApp(QMainWindow):
# Show GPS Mapped map in Right Layout # Show GPS Mapped map in Right Layout
self.webview_map = QWebEngineView() self.webview_map = QWebEngineView()
self.webview_map.setFixedWidth(450) self.webview_map.setFixedWidth(450)
self.webview_map.setUrl(QUrl("file:///Users/minarinari/Workspace/Python/UtilityPole_Info/sample_Event.html")) pathHtml = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sample_Event.html")
self.webview_map.setUrl(QUrl.fromLocalFile(pathHtml))
# 레이아웃 설정 # 레이아웃 설정
layout = QHBoxLayout() layout = QHBoxLayout()
@@ -165,7 +166,7 @@ class MyApp(QMainWindow):
self.btn_ParseImages.setEnabled(False) self.btn_ParseImages.setEnabled(False)
pathImage= self.edit_ImgPath.text() pathImage= self.edit_ImgPath.text()
mgrConv.Process(pathImage,"/home/gerd/Workspace/Python/UtilityPole_Info" ) mgrConv.Process(pathImage,"D:\\Workspace\\Python\\UtilityPole_Info" )
self.btn_ParseImages.setEnabled(True) self.btn_ParseImages.setEnabled(True)
print("Parse END!") print("Parse END!")

View File

@@ -23,7 +23,8 @@ class PoleXLS(DBXLStorage):
self.getSheet(sheetName, True) self.getSheet(sheetName, True)
title, recp = util.GetSheetInfoValue(item) text = str(item) if item is not None else ""
title, recp = util.GetSheetInfoValue(text)
self.SetCellValueStr(sheetName, colIdx, 1, title) self.SetCellValueStr(sheetName, colIdx, 1, title)
listRecp.append(recp) listRecp.append(recp)
@@ -43,17 +44,16 @@ class PoleXLS(DBXLStorage):
# 헤더 다음 줄부터 읽는다. # 헤더 다음 줄부터 읽는다.
listDistance = [] listDistance = []
for row in sheet.iter_rows(min_row=2, values_only=True): for row in sheet.iter_rows(min_row=2, values_only=True):
temp = row[1] strLon = row[6]
strLat = row[7]
if "None" in temp: if "None" in strLon or "None" in strLat:
continue continue
dist = 0 dist = 0
if temp != "": if strLon != "" and strLat != "":
parts = temp.split(',') trgLat = float(strLat)
trgLon = float(strLon)
trgLat = float(parts[0])
trgLon = float(parts[1])
dist = util.GetDistanceGPS(srcLat, srcLon, trgLat, trgLon) dist = util.GetDistanceGPS(srcLat, srcLon, trgLat, trgLon)
listDistance.append(dist) listDistance.append(dist)
@@ -67,7 +67,7 @@ class PoleXLS(DBXLStorage):
return retIdx, round( min_value, 0 ) return retIdx, round( min_value, 0 )
def GetPoleInfoAll(self, nIdx): def GetPoleInfoAll(self, nIdx:int):
sheet = self.getSheet("PoleInfo") sheet = self.getSheet("PoleInfo")
if sheet is None: if sheet is None:
return None return None
@@ -83,7 +83,7 @@ class PoleXLS(DBXLStorage):
return retList return retList
def GetPoleInfo(self, nIdx, colNum): def GetPoleInfo(self, nIdx:int, colNum:int):
sheet = self.getSheet("PoleInfo") sheet = self.getSheet("PoleInfo")
if sheet is None: if sheet is None:
return None return None
@@ -94,7 +94,7 @@ class PoleXLS(DBXLStorage):
return sheet.cell(row=nIdx,column=colNum).value return sheet.cell(row=nIdx,column=colNum).value
def AddImgInfo(self, TrgSheetName, EXIF_Date, EXIF_GPS, Path): def AddImgInfo(self, TrgSheetName:str, EXIF_Date:str, EXIF_GPS:str, Path:str):
sheet = self.getSheet("ImgInfo") sheet = self.getSheet("ImgInfo")
if sheet is None: if sheet is None:
return -1 return -1
@@ -114,7 +114,7 @@ class PoleXLS(DBXLStorage):
return TrgRow return TrgRow
def FindRowCompValue(self, sheetNameTrg, nTrgCol, valueSrc, bCmpFile = False, pathBase = ""): def FindRowCompValue(self, sheetNameTrg:str, nTrgCol:int, valueSrc:str, bCmpFile:bool = False, pathBase:str = ""):
sheet = self.getSheet(sheetNameTrg) sheet = self.getSheet(sheetNameTrg)
if sheet is None: if sheet is None:
return -1 return -1
@@ -129,12 +129,16 @@ class PoleXLS(DBXLStorage):
for row in sheet.iter_rows(min_row=2, values_only=True): for row in sheet.iter_rows(min_row=2, values_only=True):
valueTrg = row[nTrgCol] valueTrg = row[nTrgCol]
if valueTrg is None or valueTrg == "":
continue;
if True == bCmpFile: if True == bCmpFile:
# 상대경로로 저장했지만 혹시 절대경로 일 수도 있으니... # 상대경로로 저장했지만 혹시 절대경로 일 수도 있으니...
if False == os.path.isabs(valueTrg): if False == os.path.isabs(valueTrg):
valueTrg = os.path.join(pathBase, valueTrg) valueTrg = os.path.join(pathBase, valueTrg)
if True == os.path.samefile(value, valueTrg):
if True == os.path.samefile(os.path.normpath(value), os.path.normpath(valueTrg)):
retIdx = trgIdx + 1 retIdx = trgIdx + 1
break; break;
else: else:
@@ -147,16 +151,19 @@ class PoleXLS(DBXLStorage):
return retIdx return retIdx
def FindInsertedImgPath(self, pathBase, pathImg): def FindInsertedImgPath(self, pathBase: str, pathImg: str):
retIdx = self.FindRowCompValue("ImgInfo", 4, pathImg, True, pathBase ) retIdx = self.FindRowCompValue("ImgInfo", 4, pathImg, True, pathBase )
return retIdx return retIdx
# #
def GetAllRowValue(self, SheetName, nRow, nColCount): def GetAllRowValue(self, SheetName:str, nRow:int, nColCount:int):
sheet = self.getSheet(SheetName) sheet = self.getSheet(SheetName)
if sheet is None: if sheet is None:
return None return None
if 0 >= nRow:
return None
if 0 >= nColCount: if 0 >= nColCount:
return None return None

View File

@@ -116,16 +116,16 @@ class DBXLStorage(ABC):
# 시트를 가져온다. # 시트를 가져온다.
# 엑셀 파일이 안 열려 있으면 None # 엑셀 파일이 안 열려 있으면 None
def getSheet(self, sheetName, bNew = False): def getSheet(self, sheetName:str, bNew:bool = False):
retSheet = None
if None == sheetName or "" == sheetName:
return None
if self.m_wb is None: if self.m_wb is None:
util.DbgOut("XLS not opened", True) util.DbgOut("XLS not opened", True)
return None return None
if util.IsEmptyStr(sheetName):
return None
retSheet = None
try: try:
retSheet = self.m_wb[sheetName] retSheet = self.m_wb[sheetName]
except KeyError: except KeyError:
@@ -138,7 +138,7 @@ class DBXLStorage(ABC):
# 데이터베이스용 엑셀 파일의 경로를 얻어온다. # 데이터베이스용 엑셀 파일의 경로를 얻어온다.
# 폴더만 입력되었으면 파일이름을 삽입하고, 완전한 경로라면 패스 # 폴더만 입력되었으면 파일이름을 삽입하고, 완전한 경로라면 패스
def GetXLSPath(self, pathSrc): def GetXLSPath(self, pathSrc:str)-> str:
retPath = self.path retPath = self.path
if os.path.isdir(pathSrc): if os.path.isdir(pathSrc):
@@ -146,7 +146,7 @@ class DBXLStorage(ABC):
return retPath return retPath
def FindLastRow(self, sheetName): def FindLastRow(self, sheetName:str)-> int:
sheet = self.getSheet(sheetName) sheet = self.getSheet(sheetName)
if sheet is None: if sheet is None:
return -1 return -1

4
UI.py
View File

@@ -1,6 +1,7 @@
import os import os
import sys import sys
import UtilPack as util import UtilPack as util
from pathlib import Path
import VWorldAPIs as vw import VWorldAPIs as vw
import ExtEXIFData as exif import ExtEXIFData as exif
@@ -141,7 +142,8 @@ class MyApp(QMainWindow):
# Show GPS Mapped map in Right Layout # Show GPS Mapped map in Right Layout
self.webview_map = QWebEngineView() self.webview_map = QWebEngineView()
self.webview_map.setFixedWidth(450) self.webview_map.setFixedWidth(450)
self.webview_map.setUrl(QUrl("file:///Users/minarinari/Workspace/Python/UtilityPole_Info/sample_Event.html")) html_path = Path(__file__).resolve().parent / "sample_Event.html"
self.webview_map.setUrl(QUrl.fromLocalFile(str(html_path)))
# 레이아웃 설정 # 레이아웃 설정
layout = QHBoxLayout() layout = QHBoxLayout()

View File

@@ -2,22 +2,25 @@ import os
import re import re
import math import math
import time import time
import rarfile #import rarfile
import zipfile import zipfile
import shutil #import shutil
import difflib #import difflib
import subprocess import subprocess
import UtilPack as util from pathlib import Path
m_dbgLevel = 0 m_dbgLevel = 0
listDbgStr = [] listDbgStr:list[str] = []
# #
def IsEmptyStr(string): def IsEmptyStr(string:str) -> bool:
if type(string) is not str:
return True
return 0 == len(string.strip()) return 0 == len(string.strip())
# #
def GetCurrentTime(): def GetCurrentTime() -> str:
# 현재 시간을 구하고 구조체로 변환 # 현재 시간을 구하고 구조체로 변환
current_time_struct = time.localtime() current_time_struct = time.localtime()
@@ -34,7 +37,7 @@ def GetCurrentTime():
return strRet return strRet
#for debug #for debug
def DbgOut(strInput, bPrint = False): def DbgOut(strInput:str, bPrint:bool = False):
strMsg = (f"{GetCurrentTime()} : {strInput}") strMsg = (f"{GetCurrentTime()} : {strInput}")
listDbgStr.append(strMsg) listDbgStr.append(strMsg)
@@ -46,7 +49,7 @@ def printDbgMessages():
print(line) print(line)
# 입력된 패스에서 부모 폴더를 찾는다. 0 은 자기자신 1은 부모, 숫자대로 위로. # 입력된 패스에서 부모 폴더를 찾는다. 0 은 자기자신 1은 부모, 숫자대로 위로.
def GetParentDirName(FullPath, nUp): def GetParentDirName(FullPath:str, nUp:int) -> str:
parts = FullPath.split(os.sep) parts = FullPath.split(os.sep)
nTrgIdx = 0 nTrgIdx = 0
@@ -60,7 +63,7 @@ def GetParentDirName(FullPath, nUp):
return parts[nTrgIdx] return parts[nTrgIdx]
# os 모듈을 사용하여 자식 폴더를 반환 # os 모듈을 사용하여 자식 폴더를 반환
def GetSubDirectories(folder_path): def GetSubDirectories(folder_path:str)-> list[str]:
subdirectories = [ subdirectories = [
d for d in os.listdir(folder_path) d for d in os.listdir(folder_path)
if os.path.isdir(os.path.join(folder_path, d))] if os.path.isdir(os.path.join(folder_path, d))]
@@ -69,8 +72,8 @@ def GetSubDirectories(folder_path):
# 입력된 경로의 자식 폴더를 찾아 반환한다. # 입력된 경로의 자식 폴더를 찾아 반환한다.
# 반환하는 리스트는 리커시브 - 손자, 증손자 폴더까지 전부 포함한다 # 반환하는 리스트는 리커시브 - 손자, 증손자 폴더까지 전부 포함한다
def GetAllSubDirectories(root_dir): def GetAllSubDirectories(root_dir:str)-> list[str]:
subdirectories = [] subdirectories:list[str] = []
# root_dir에서 하위 디렉토리 및 파일 목록을 얻음 # root_dir에서 하위 디렉토리 및 파일 목록을 얻음
for dirpath, dirnames, filenames in os.walk(root_dir): for dirpath, dirnames, filenames in os.walk(root_dir):
@@ -83,8 +86,8 @@ def GetAllSubDirectories(root_dir):
return subdirectories return subdirectories
def ListFileExtRcr(pathTrg, strExt): def ListFileExtRcr(pathTrg:str, strExt:str) -> list[str]:
listRet= [] listRet:list[str] = []
# pathTrg의 하위 디렉토리 및 파일 목록을 얻음 # pathTrg의 하위 디렉토리 및 파일 목록을 얻음
for dirpath, dirnames, filenames in os.walk(pathTrg): for dirpath, dirnames, filenames in os.walk(pathTrg):
@@ -97,7 +100,7 @@ def ListFileExtRcr(pathTrg, strExt):
# 입력된 경로가 자식 폴더를 가지고 있는지 판단한다.- 최종 폴더인지 여부 # 입력된 경로가 자식 폴더를 가지고 있는지 판단한다.- 최종 폴더인지 여부
# 자식이 없으면 True, 자식이 있으면 False # 자식이 없으면 True, 자식이 있으면 False
def IsFinalFolder(path): def IsFinalFolder(path:str) -> bool:
bRet = True bRet = True
contents = os.listdir(path) contents = os.listdir(path)
@@ -109,12 +112,12 @@ def IsFinalFolder(path):
return bRet; return bRet;
# 어떤 경로 안에서 특정 확장자의 파일을 뽑아내어 그 리스트를 반환한다. # 어떤 경로 안에서 특정 확장자의 파일을 뽑아내어 그 리스트를 반환한다.
def FindFileFromExt(path, ext): def FindFileFromExt(path:str, ext:str) -> list[str]:
bDot = False bDot = False
if 0 <= ext.find('.'): if 0 <= ext.find('.'):
bDot = True bDot = True
listRet = [] listRet:list[str] = []
if False == os.path.exists(path): if False == os.path.exists(path):
return listRet return listRet
@@ -131,7 +134,7 @@ def FindFileFromExt(path, ext):
# 파일 이름에서 확장자를 뽑아낸다. True : '.' 을 포함한다. # 파일 이름에서 확장자를 뽑아낸다. True : '.' 을 포함한다.
def GetExtStr(file_path, bDot = True): def GetExtStr(file_path:str, bDot:bool = True) -> str:
retStr = "" retStr = ""
# 파일 경로에서 마지막 점을 찾아 확장자를 추출 # 파일 경로에서 마지막 점을 찾아 확장자를 추출
last_dot_index = file_path.rfind('.') last_dot_index = file_path.rfind('.')
@@ -147,7 +150,7 @@ def GetExtStr(file_path, bDot = True):
# 문자열에 포함된 단어를 지운다. # 문자열에 포함된 단어를 지운다.
def RmvSubString(mainString, subString): def RmvSubString(mainString:str, subString:str) -> str:
# 문자열에서 부분 문자열의 인덱스를 찾습니다. # 문자열에서 부분 문자열의 인덱스를 찾습니다.
strIdx = mainString.find(subString) strIdx = mainString.find(subString)
if strIdx == -1: # 부분 문자열이 존재하지 않으면 그대로 반환합니다. if strIdx == -1: # 부분 문자열이 존재하지 않으면 그대로 반환합니다.
@@ -158,12 +161,12 @@ def RmvSubString(mainString, subString):
# 부분 문자열을 제거하고 새로운 문자열을 반환합니다. # 부분 문자열을 제거하고 새로운 문자열을 반환합니다.
return mainString[:strIdx] + mainString[endIdx:] return mainString[:strIdx] + mainString[endIdx:]
def ExtractZIP(zip_file, extract_to): def ExtractZIP(zip_file:str, extract_to:str):
with zipfile.ZipFile(zip_file, 'r') as zf: with zipfile.ZipFile(zip_file, 'r') as zf:
zf.extractall(extract_to) zf.extractall(extract_to)
# #
def CreateZIP(output_zip, *files): def CreateZIP(output_zip:str, *files):
with zipfile.ZipFile(output_zip, 'w') as zf: with zipfile.ZipFile(output_zip, 'w') as zf:
for file in files: for file in files:
pathTemp = os.path.join('root', os.path.basename(file)) pathTemp = os.path.join('root', os.path.basename(file))
@@ -176,7 +179,7 @@ def CreateZIP(output_zip, *files):
return bRet return bRet
# 파일 리스트에 들어있는 파일만 골라서 압축을 합니다. 상대경로를 제거하는게 기본값. # 파일 리스트에 들어있는 파일만 골라서 압축을 합니다. 상대경로를 제거하는게 기본값.
def CreateZIPShell(zipName, *files, bRmvRPath = True): def CreateZIPShell(zipName:str, *files, bRmvRPath:bool = True)-> bool:
command = "zip " command = "zip "
if True == bRmvRPath: if True == bRmvRPath:
@@ -206,7 +209,7 @@ def CreateZIPShell(zipName, *files, bRmvRPath = True):
return bRet return bRet
# 특정 확장자만 쉘을 이용해서 압축한다 # 특정 확장자만 쉘을 이용해서 압축한다
def CreateZIPShExt(zipName, TrgExt): def CreateZIPShExt(zipName:str, TrgExt:str):
command = f"zip -j {zipName} *.{TrgExt}" command = f"zip -j {zipName} *.{TrgExt}"
result = subprocess.run(command, shell=True, capture_output=True, text=True) result = subprocess.run(command, shell=True, capture_output=True, text=True)
@@ -218,7 +221,7 @@ def CreateZIPShExt(zipName, TrgExt):
return bRet return bRet
# JSON 을 트리 구조로 출력한다. # JSON 을 트리 구조로 출력한다.
def PrintJSONTree(data, indent=0): def PrintJSONTree(data, indent:int=0):
if isinstance(data, dict): if isinstance(data, dict):
for key, value in data.items(): for key, value in data.items():
print(' ' * indent + str(key)) print(' ' * indent + str(key))
@@ -230,7 +233,7 @@ def PrintJSONTree(data, indent=0):
print(' ' * indent + str(data)) print(' ' * indent + str(data))
# 세로 크기를 가로 비율대로 줄여서 반환 (가로를 기준으로 세로 길이) # 세로 크기를 가로 비율대로 줄여서 반환 (가로를 기준으로 세로 길이)
def GetRSzHeight(nWidth, nHeight, nTrgW): def GetRSzHeight(nWidth:int, nHeight:int, nTrgW:int) -> int:
if nWidth <= 0 or nTrgW <= 0: if nWidth <= 0 or nTrgW <= 0:
return 0; return 0;
@@ -240,7 +243,7 @@ def GetRSzHeight(nWidth, nHeight, nTrgW):
return nTrgH return nTrgH
# 가로 크기를 세로 비율대로 줄여서 반환 (세로를 기준으로 가로 길이) # 가로 크기를 세로 비율대로 줄여서 반환 (세로를 기준으로 가로 길이)
def GetRSzWidth(nWidth, nHeight, nTrgH): def GetRSzWidth(nWidth:int, nHeight:int, nTrgH:int) -> int:
if nHeight <= 0 or nTrgH <= 0: if nHeight <= 0 or nTrgH <= 0:
return 0; return 0;
@@ -252,15 +255,15 @@ def GetRSzWidth(nWidth, nHeight, nTrgH):
# 엑셀 열 너비는 약 7.5픽셀 단위 # 엑셀 열 너비는 약 7.5픽셀 단위
# 엑셀 행 높이는 약 25픽셀 단위 # 엑셀 행 높이는 약 25픽셀 단위
# 이대로는 사이즈가 안 맞아서 적당히 곱해줌 # 이대로는 사이즈가 안 맞아서 적당히 곱해줌
def GetCellSize(nImgW, nImgH, DPI = 96): def GetCellSize(nImgW:int, nImgH:int, DPI:int = 96) -> tuple[float, float]:
column_width = (nImgW / DPI) * 7.5 * 2 column_width = (nImgW / DPI) * 7.5 * 2
row_height = (nImgH / DPI) * 25 * 3 row_height = (nImgH / DPI) * 25 * 3
return column_width, row_height return column_width, row_height
def GetSheetInfoValue(text): def GetSheetInfoValue(text:str) -> tuple[str, list[str]]:
if text is None: if IsEmptyStr(text):
return "", "" return "", []
pattern = r'<(.*?)>' pattern = r'<(.*?)>'
matches = re.findall(pattern, text) matches = re.findall(pattern, text)
@@ -269,7 +272,7 @@ def GetSheetInfoValue(text):
return cleaned, matches return cleaned, matches
# 두 GPS 좌표 간 거리 구하기 (미터) # 두 GPS 좌표 간 거리 구하기 (미터)
def GetDistanceGPS(lat1, lon1, lat2, lon2): def GetDistanceGPS(lat1:float, lon1:float, lat2:float, lon2:float) -> float:
# 지구의 반지름 (단위: 미터) # 지구의 반지름 (단위: 미터)
R = 6371008.8 R = 6371008.8
@@ -296,3 +299,4 @@ def GetDistanceGPS(lat1, lon1, lat2, lon2):

View File

@@ -1,402 +0,0 @@
import os
import re
import time
import uuid
import rarfile
import zipfile
import shutil
import difflib
import subprocess
import hashlib
from pathlib import Path
m_dbgLevel = 0
listDbgStr: list[str] = []
#for debug
def DbgOut(strInput:str, bPrint:bool = False):
strMsg = (f"{GetCurrentTime()} : {strInput}")
listDbgStr.append(strMsg)
if True == bPrint:
print(strMsg)
#
def printDbgMessages():
for line in listDbgStr:
print(line)
#
def SaveDbgMessages(Path: str):
try:
with open(Path, 'w') as file:
for line in listDbgStr:
file.write(line + "\n")
except IOError:
DbgOut(f"Error: Could not write to the file at {Path}.", True)
#
def GetCurrentTime() -> str:
# 현재 시간을 구하고 구조체로 변환
current_time_struct = time.localtime()
# 구조체에서 연, 월, 일, 시간, 분, 초를 추출
year = current_time_struct.tm_year
month = current_time_struct.tm_mon
day = current_time_struct.tm_mday
hour = current_time_struct.tm_hour
minute = current_time_struct.tm_min
second = current_time_struct.tm_sec
strRet = (f"{year}/{month}/{day}_{hour}:{minute}:{second}")
return strRet
#
def IsEmptyStr(string: str) -> bool:
temp = f"{string}"
return 0 == len(temp.strip())
# 입력된 경로의 자식 폴더를 찾아 반환한다.
# 반환하는 리스트는 리커시브 - 손자, 증손자 폴더까지 전부 포함한다
def ListSubDirectories(root_dir:str)-> list[str]:
subdirectories: list[str] = []
# root_dir에서 하위 디렉토리 및 파일 목록을 얻음
for dirpath, dirnames, filenames in os.walk(root_dir):
# 하위 디렉토리 목록을 반복하며 하위 디렉토리만 추출
for dirname in dirnames:
path = os.path.join(dirpath, dirname)
if True == IsFinalFolder(path):
subdirectories.append(path)
return subdirectories
# 자식 폴더를 구해온다. 직계 자식만
def ListChildDirectories(pathDir:str, bVisibleOnly: bool = True) -> list[str]:
listRet:list[str] = []
listTemp = os.listdir(pathDir)
for name in listTemp:
pathChild = os.path.join(pathDir, name)
if os.path.isdir(pathChild):
if True == bVisibleOnly and name.startswith('.'):
continue
listRet.append(name)
return listRet
# PathDir 에 지정된 폴더의 파일목록만 구해온다. 자식 폴더에 있는건 무시.
def ListContainFiles(pathDir:str, bVisibleOnly:bool = True)-> list[str]:
listRet:list[str] = []
listTemp = os.listdir(pathDir)
for name in listTemp:
pathChild = os.path.join(pathDir, name)
if os.path.isfile(pathChild):
if True == bVisibleOnly and name.startswith('.'):
continue
listRet.append(name)
return listRet
# 리스트에 담긴 확장자와 같은 확장자를 가진 파일을 찾아서 리스트로 반환
def ListFileExtRcr(pathTrg: str, listExt: list[str]) -> list[str]:
listRet:list[str] = []
# pathTrg의 하위 디렉토리 및 파일 목록을 얻음
for dirpath, dirnames, filenames in os.walk(pathTrg):
for file in filenames:
extTmp = GetExtStr(file)
if extTmp.lower() in listExt and not file.startswith('.'):
listRet.append(os.path.join(dirpath, file))
return listRet
# 입력된 경로에서 부모 폴더를 찾는다. 0 은 자기자신 1은 부모, 숫자대로 위로.
def GetParentDirName(FullPath : str, nUp : int)->str:
parts = FullPath.split(os.sep)
nTrgIdx = 0
if nUp < len(parts):
nTrgIdx = len(parts) -nUp -1
elif nUp < 0:
nTrgIdx = len(parts) - 1
else:
nTrgIdx = 0
return parts[nTrgIdx]
# 입력된 경로가 자식 폴더를 가지고 있는지 판단한다.- 최종 폴더인지 여부
# 자식이 없으면 True, 자식이 있으면 False
def IsFinalFolder(path : str) -> bool:
bRet = True
contents = os.listdir(path)
for item in contents:
if True == os.path.isdir(item):
bRet = False
break
return bRet;
# 어떤 경로 안에서 특정 확장자의 파일을 뽑아내어 그 리스트를 반환한다.
def FindFileFromExt(path: str, ext: str)-> list[str]:
bDot = False
if 0 <= ext.find('.'):
bDot = True
listRet:list[str] = []
if False == os.path.exists(path):
return listRet
contents = os.listdir(path)
for item in contents:
if True == os.path.isdir(item):
continue
extItem = GetExtStr(item, bDot)
if extItem.lower() == ext.lower():
listRet.append(item)
return listRet
# 파일 이름에서 확장자를 뽑아낸다. True : '.' 을 포함한다.
def GetExtStr(file_path: str, bDot: bool = True)-> str:
retStr = ""
# 파일 경로에서 마지막 점을 찾아 확장자를 추출
last_dot_index = file_path.rfind('.')
if last_dot_index == -1:
retStr = "" # 점이 없는 경우 확장자가 없음
else:
if True == bDot:
retStr = file_path[last_dot_index:]
else:
retStr = file_path[last_dot_index+1:]
return retStr
# 문자열에 포함된 단어를 지운다.
def RmvSubString(mainString: str, subString: str)-> str:
# 문자열에서 부분 문자열의 인덱스를 찾습니다.
strIdx = mainString.find(subString)
if strIdx == -1: # 부분 문자열이 존재하지 않으면 그대로 반환합니다.
return mainString
endIdx = strIdx + len(subString)
# 부분 문자열을 제거하고 새로운 문자열을 반환합니다.
return mainString[:strIdx] + mainString[endIdx:]
#
def ExtractZIP(zip_file: str, extract_to: str):
with zipfile.ZipFile(zip_file, 'r') as zf:
zf.extractall(extract_to)
#
def CreateZIP(output_zip: str, files: list[str]) -> bool:
with zipfile.ZipFile(output_zip, 'w') as zf:
for file in files:
pathTemp = os.path.join('root', os.path.basename(file))
zf.write(file, pathTemp)
bRet = False
if os.path.exists(output_zip):
bRet = True
return bRet
# 파일 리스트에 들어있는 파일만 골라서 압축을 합니다. 상대경로를 제거하는게 기본값.
def CreateZIPShell(zipName: str, files: list[str], bRmvRPath: bool = True) -> bool:
command = "zip "
if True == bRmvRPath:
command += "-j "
command += f"\"{zipName}\" "
# 이중 리스트인 이유를 모르겠다.
for file in files:
command += f"\"{file}\" "
result = subprocess.run(command, shell=True, capture_output=True, text=True)
bRet = False
if 0 == result.returncode:
bRet = True
return bRet
# 특정 확장자만 쉘을 이용해서 압축한다
def CreateZIPShExt(zipName: str, TrgExt: str)-> bool:
command = f"zip -j {zipName} *.{TrgExt}"
result = subprocess.run(command, shell=True, capture_output=True, text=True)
bRet = False
if 0 == result.returncode:
bRet = True
return bRet
# 압축 파일 내의 모든 파일 및 디렉토리 목록 가져오기
def GetZipContentList(path: str) -> list[str]:
if True == IsEmptyStr(path) or not os.path.isfile(path):
return []
listRet = []
with zipfile.ZipFile( path , 'r') as zip_file:
listRet = zip_file.namelist()
return listRet
#
def GetZippedFileByte(pathZip: str, FileName: str) -> bytes:
retBytes:bytes = bytes()
if True == os.path.isfile(pathZip):
with zipfile.ZipFile( pathZip , 'r') as zip_file:
# 압축 파일 내의 특정 파일을 읽기
with zip_file.open(FileName) as file:
retBytes = file.read()
return retBytes
# JSON 을 트리 구조로 출력한다.
def PrintJSONTree(data, indent: int=0 ) -> None:
if isinstance(data, dict):
for key, value in data.items():
print(' ' * indent + str(key))
PrintJSONTree(value, indent + 1)
elif isinstance(data, list):
for item in data:
PrintJSONTree(item, indent)
else:
print(' ' * indent + str(data))
# 랜덤 UUID 생성
def UUIDGenRandom():
random_uuid = uuid.uuid4()
return random_uuid
# 이름을 기반으로 UUID 생성
def UUIDGenName(SeedName:str):
namespace_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, SeedName)
return namespace_uuid
#
def GetTextInBrakets(text:str)-> list[str]:
return re.findall(r'\[(.*?)\]', text)
#파일의 해시를 계산하는 함수.
#Args:
# strFilePath (str): 파일 경로
# method (str): 사용할 해시 알고리즘 ('md5', 'sha1', 'sha256' 등)
#Returns:
# str: 계산된 해시값 (16진수 문자열)
def CalculateFileHash(strFilePath: str, method: str="sha256")-> str:
funcHash = getattr(hashlib, method)()
with open(strFilePath, "rb") as f:
while True:
chunk = f.read(4096)
if not chunk:
break
funcHash.update(chunk)
return funcHash.hexdigest()
#파일의 해시를 비교하여 무결성 검증.
#Args:
# file_path (str): 파일 경로
# expected_hash (str): 기대하는 해시값
# method (str): 사용할 해시 알고리즘
#Returns:
# bool: 파일이 정상인지 여부
"""
# 사용 예시
if __name__ == "__main__":
file_to_check = "example.txt"
known_good_hash = "5d41402abc4b2a76b9719d911017c592" # 예시 (MD5)
is_valid = verify_file(file_to_check, known_good_hash, method='md5')
if is_valid:
print("파일이 정상입니다!")
else:
print("파일이 손상되었거나 다릅니다!")
"""
def VerifyIsValidFile(strPath: str, strCompHash: str, strMethod: str="sha256")->bool:
Hash_Calcd = CalculateFileHash(strPath, strMethod)
return strCompHash.lower() == Hash_Calcd.lower()
# 세로 크기를 가로 비율대로 줄여서 반환 (가로를 기준으로 세로 길이)
def GetRSzHeight(nWidth, nHeight, nTrgW):
if nWidth <= 0 or nTrgW <= 0:
return 0;
fRatio = nTrgW / nWidth
nTrgH = round(nHeight * fRatio)
return nTrgH
# 가로 크기를 세로 비율대로 줄여서 반환 (세로를 기준으로 가로 길이)
def GetRSzWidth(nWidth, nHeight, nTrgH):
if nHeight <= 0 or nTrgH <= 0:
return 0;
fRatio = nTrgH / nHeight
nTrgW = round(nWidth * fRatio)
return nTrgW
# 엑셀 열 너비는 약 7.5픽셀 단위
# 엑셀 행 높이는 약 25픽셀 단위
# 이대로는 사이즈가 안 맞아서 적당히 곱해줌
def GetCellSize(nImgW, nImgH, DPI = 96):
column_width = (nImgW / DPI) * 7.5 * 2
row_height = (nImgH / DPI) * 25 * 3
return column_width, row_height
def GetSheetInfoValue(text):
if text is None:
return "", ""
pattern = r'<(.*?)>'
matches = re.findall(pattern, text)
cleaned = re.sub(pattern, '', text)
return cleaned, matches
# 두 GPS 좌표 간 거리 구하기 (미터)
def GetDistanceGPS(lat1, lon1, lat2, lon2):
# 지구의 반지름 (단위: 미터)
R = 6371008.8
# 위도와 경도를 라디안으로 변환
lat1 = math.radians(lat1)
lon1 = math.radians(lon1)
lat2 = math.radians(lat2)
lon2 = math.radians(lon2)
# 차이 계산
dlat = lat2 - lat1
dlon = lon2 - lon1
# Haversine 공식 적용
a = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
# 거리 계산
distance = R * c
return distance

View File

@@ -35,10 +35,8 @@ pure_eval==0.2.3
Pygments==2.18.0 Pygments==2.18.0
pyproj==3.7.2 pyproj==3.7.2
PyQt5==5.15.11 PyQt5==5.15.11
PyQt5-Qt5==5.15.17
PyQt5_sip==12.17.1 PyQt5_sip==12.17.1
PyQtWebEngine==5.15.7 PyQtWebEngine==5.15.7
PyQtWebEngine-Qt5==5.15.17
pytesseract==0.3.13 pytesseract==0.3.13
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
rarfile==4.2 rarfile==4.2

View File

@@ -5,9 +5,9 @@ import PoleXLS as myxl
import VWorldAPIs as vwapi import VWorldAPIs as vwapi
ImgDirPath = "/Volumes/ExSSD/Working/Images" ImgDirPath = util.NormalizePath("/Volumes/ExSSD/Working/Images")
ImgSortedPath = "/Volumes/ExSSD/Working/Sorted" ImgSortedPath = util.NormalizePath("/Volumes/ExSSD/Working/Sorted")
XlsDirPath = "/Users/minarinari/Workspace/Python/UtilityPole_Info" XlsDirPath = util.NormalizePath("/Users/minarinari/Workspace/Python/UtilityPole_Info")
indexFile = ".index.maps" indexFile = ".index.maps"