diff --git a/.gitignore b/.gitignore index 1e5bea3..c56b192 100644 --- a/.gitignore +++ b/.gitignore @@ -188,5 +188,11 @@ poetry.toml # LSP config files pyrightconfig.json +# Sample Images +/Images + +*복사본.xlsx +.~lock* + # End of https://www.toptal.com/developers/gitignore/api/python,linux diff --git a/EPoleDB.xlsx b/EPoleDB.xlsx index 81df6ab..2723eea 100644 Binary files a/EPoleDB.xlsx and b/EPoleDB.xlsx differ diff --git a/MgrUtilityPoleConverter.py b/MgrUtilityPoleConverter.py index 459635b..f293b4b 100644 --- a/MgrUtilityPoleConverter.py +++ b/MgrUtilityPoleConverter.py @@ -1,5 +1,5 @@ import os -import sys +#import sys import UtilPack as util import PoleXLS as myxl @@ -7,38 +7,8 @@ import ExtEXIFData as exEXIF import VWorldAPIs as vwapi import JusoMngr -mCurPath = "" # 스크립트가 실행되는 경로 -mXlsPath = "" # 엑셀 파일이 있는 경로, 틀리면 안됨. -# "/Volumes/ExSSD/Working/Images" # 이미지가 들어있는 폴더, 이 아래에 A,B,Pole 등이 있어야 한다. -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): +def GetPoleInfos(xls, nRow:int): if None == xls or False == isinstance(xls, myxl.PoleXLS): 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): return -1 - pathcsv = os.path.join(mCurPath, "JusoDB.csv") + pathCurrent = os.getcwd() + pathcsv = os.path.join(pathCurrent, "JusoDB.csv") jusomgr = JusoMngr.JusoDB(pathcsv) nIdx = 1 @@ -86,18 +57,20 @@ def CollectImageData(xls, sheetTrg, listImg, pathImgDir): xls.SetCellValueStr(sheetTrg, 3, nIdx, all_date) xls.SetCellValueStr(sheetTrg, 4, nIdx, level) xls.SetCellValueStr(sheetTrg, 5, nIdx, pathRel) - xls.SetCellValueStr(sheetTrg, 6, nIdx, juso[3]) - xls.SetCellValueStr(sheetTrg, 7, nIdx, juso[2]) - xls.SetCellValueStr(sheetTrg, 8, nIdx, f"{lat},{lon}") - xls.SetCellValueStr(sheetTrg, 9, nIdx, juso[1]) - xls.SetCellValueStr(sheetTrg, 10, nIdx, juso[0]) + xls.SetCellValueStr(sheetTrg, 6, nIdx, juso[1]) + xls.SetCellValueStr(sheetTrg, 7, nIdx, juso[0]) + xls.SetCellValueStr(sheetTrg, 8, nIdx, juso[3]) + xls.SetCellValueStr(sheetTrg, 9, nIdx, juso[2]) + xls.SetCellValueStr(sheetTrg, 10, nIdx, f"{lon}") + xls.SetCellValueStr(sheetTrg, 11, nIdx, f"{lat}") # VWorld 좌표로 변환하여 저장 lat_Vworld, lon_Vworld = 0.0, 0.0 if lat != 0.0 and lon != 0.0 : 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 @@ -105,20 +78,23 @@ def CollectImageData(xls, sheetTrg, listImg, pathImgDir): # DB 시트에 있는 파일을 골라내서 PoleInfo 시트에 넣는다. # PoleInfo : 번호, GPS 좌표, 구역, 주소, 사진 -def CollectPoleInfoData(xls, TrgSheetName, DBSheetName, pathImgDir): +def CollectPoleInfoData(xls, TrgSheetName:str, DBSheetName:str, pathImgDir:str)-> int: if None == xls: return -1 - nLastDBRow = xls.FindLastRow( DBSheetName ) + nLastDBRow:int = xls.FindLastRow( DBSheetName ) if 0 >= nLastDBRow: print(f"{DBSheetName} Sheet is Empty.") return -1 - # 헤더가 있는 시트는 헤더가 1, 그래서 2부터 시작 + # 헤더가 있는 시트는 헤더의 인덱스가 1, 그래서 2부터 시작 nTrgRow = xls.FindLastRow(TrgSheetName) + 1 for nDBRow in range(1, nLastDBRow + 1): - listValues = xls.GetAllRowValue(DBSheetName, nDBRow, 8) - # 0: Index, 1: Sheetname, 2:Date, 3:Level, 4:Rel Path, 5:Area, 6:Juso, 7:GPS(Lat,Lon) + listValues = xls.GetAllRowValue(DBSheetName, nDBRow, 11) + + # 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: continue @@ -128,13 +104,14 @@ def CollectPoleInfoData(xls, TrgSheetName, DBSheetName, pathImgDir): if listValues[3] != "Pole" : continue - if listValues[7] == "" or listValues[7] == "0.0,0.0": + # GPS 좌표가 없으면 패스 + if listValues[9] == "" or listValues[10] == "": continue xls.SetCellValueStr(TrgSheetName, 1, nTrgRow, str(nTrgRow -1)) - xls.SetCellValueStr(TrgSheetName, 2, nTrgRow, listValues[7]) - xls.SetCellValueStr(TrgSheetName, 3, nTrgRow, listValues[5]) - xls.SetCellValueStr(TrgSheetName, 4, nTrgRow, listValues[6]) + xls.SetCellValueStr(TrgSheetName, 2, nTrgRow, listValues[5]) + xls.SetCellValueStr(TrgSheetName, 3, nTrgRow, listValues[6]) + xls.SetCellValueStr(TrgSheetName, 4, nTrgRow, listValues[7]) imgPath = os.path.join(pathImgDir, listValues[4]) 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, 7, nTrgRow, listValues[9]) + xls.SetCellValueStr(TrgSheetName, 8, nTrgRow, listValues[10]) + # 목표 시트에 넣었으니 시트 이름을 DB 시트에 적어 넣는다. xls.SetCellValueStr(DBSheetName, 2, nDBRow, TrgSheetName) @@ -163,9 +143,11 @@ def CollectPoleData( xls, TrgSheetName, InfoSheetName, DBSheetName, pathImgDir ) # 헤더가 있는 시트는 헤더가 1, 그래서 2부터 시작 nTrgRow = xls.FindLastRow(TrgSheetName) + 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: continue @@ -175,7 +157,8 @@ def CollectPoleData( xls, TrgSheetName, InfoSheetName, DBSheetName, pathImgDir ) if listValues[3] != "A" and listValues[3] != "B" : continue - if listValues[7] == "" or listValues[7] == "0.0,0.0": + # GPS 좌표가 없으면 패스 + if listValues[9] == "" or listValues[10] == "": continue # 날짜만 골라낸다. @@ -188,12 +171,13 @@ def CollectPoleData( xls, TrgSheetName, InfoSheetName, DBSheetName, pathImgDir ) 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)) # 배열은 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}") retList = xls.GetAllRowValue(InfoSheetName, nPoleIdx, 5) @@ -220,13 +204,13 @@ def CollectPoleData( xls, TrgSheetName, InfoSheetName, DBSheetName, pathImgDir ) return nTrgRow -def GetJPGFileLIst(pathImageDir): - trgDirList = util.GetSubDirectories(pathImageDir) +def GetJPGFileLIst(pathImageDir:str) -> list[str]: + trgDirList:list[str] = util.GetSubDirectories(pathImageDir) #print(f"DBG : GetJPGFileLIst : {pathImageDir}, {trgDirList}") #input("Press Enter to continue...") - retImgList = [] + retImgList:list[str] = [] for dirName in trgDirList: if dirName.lower() == "thumb": continue @@ -243,9 +227,9 @@ def GetJPGFileLIst(pathImageDir): 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") diff --git a/MgrUtilityPoleOCR.py b/MgrUtilityPoleOCR.py index 7da73f5..bd8de07 100644 --- a/MgrUtilityPoleOCR.py +++ b/MgrUtilityPoleOCR.py @@ -1,8 +1,9 @@ import cv2 import pytesseract 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) diff --git a/MgrUtilityPoleUI.py b/MgrUtilityPoleUI.py index dd10d69..422e156 100644 --- a/MgrUtilityPoleUI.py +++ b/MgrUtilityPoleUI.py @@ -143,7 +143,8 @@ class MyApp(QMainWindow): # Show GPS Mapped map in Right Layout self.webview_map = QWebEngineView() 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() @@ -165,7 +166,7 @@ class MyApp(QMainWindow): self.btn_ParseImages.setEnabled(False) 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) print("Parse END!") diff --git a/PoleXLS.py b/PoleXLS.py index ffab361..09f727f 100644 --- a/PoleXLS.py +++ b/PoleXLS.py @@ -23,7 +23,8 @@ class PoleXLS(DBXLStorage): 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) listRecp.append(recp) @@ -43,17 +44,16 @@ class PoleXLS(DBXLStorage): # 헤더 다음 줄부터 읽는다. listDistance = [] 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 dist = 0 - if temp != "": - parts = temp.split(',') - - trgLat = float(parts[0]) - trgLon = float(parts[1]) + if strLon != "" and strLat != "": + trgLat = float(strLat) + trgLon = float(strLon) dist = util.GetDistanceGPS(srcLat, srcLon, trgLat, trgLon) listDistance.append(dist) @@ -67,7 +67,7 @@ class PoleXLS(DBXLStorage): return retIdx, round( min_value, 0 ) - def GetPoleInfoAll(self, nIdx): + def GetPoleInfoAll(self, nIdx:int): sheet = self.getSheet("PoleInfo") if sheet is None: return None @@ -83,7 +83,7 @@ class PoleXLS(DBXLStorage): return retList - def GetPoleInfo(self, nIdx, colNum): + def GetPoleInfo(self, nIdx:int, colNum:int): sheet = self.getSheet("PoleInfo") if sheet is None: return None @@ -94,7 +94,7 @@ class PoleXLS(DBXLStorage): 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") if sheet is None: return -1 @@ -114,7 +114,7 @@ class PoleXLS(DBXLStorage): 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) if sheet is None: return -1 @@ -129,12 +129,16 @@ class PoleXLS(DBXLStorage): for row in sheet.iter_rows(min_row=2, values_only=True): valueTrg = row[nTrgCol] + if valueTrg is None or valueTrg == "": + continue; + if True == bCmpFile: # 상대경로로 저장했지만 혹시 절대경로 일 수도 있으니... if False == os.path.isabs(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 break; else: @@ -147,16 +151,19 @@ class PoleXLS(DBXLStorage): return retIdx - def FindInsertedImgPath(self, pathBase, pathImg): + def FindInsertedImgPath(self, pathBase: str, pathImg: str): retIdx = self.FindRowCompValue("ImgInfo", 4, pathImg, True, pathBase ) return retIdx # - def GetAllRowValue(self, SheetName, nRow, nColCount): + def GetAllRowValue(self, SheetName:str, nRow:int, nColCount:int): sheet = self.getSheet(SheetName) if sheet is None: return None + if 0 >= nRow: + return None + if 0 >= nColCount: return None diff --git a/StoreXLS.py b/StoreXLS.py index 5a28537..9cba0cc 100644 --- a/StoreXLS.py +++ b/StoreXLS.py @@ -116,16 +116,16 @@ class DBXLStorage(ABC): # 시트를 가져온다. # 엑셀 파일이 안 열려 있으면 None - def getSheet(self, sheetName, bNew = False): - retSheet = None - - if None == sheetName or "" == sheetName: - return None - + def getSheet(self, sheetName:str, bNew:bool = False): if self.m_wb is None: util.DbgOut("XLS not opened", True) return None + if util.IsEmptyStr(sheetName): + return None + + retSheet = None + try: retSheet = self.m_wb[sheetName] except KeyError: @@ -138,7 +138,7 @@ class DBXLStorage(ABC): # 데이터베이스용 엑셀 파일의 경로를 얻어온다. # 폴더만 입력되었으면 파일이름을 삽입하고, 완전한 경로라면 패스 - def GetXLSPath(self, pathSrc): + def GetXLSPath(self, pathSrc:str)-> str: retPath = self.path if os.path.isdir(pathSrc): @@ -146,7 +146,7 @@ class DBXLStorage(ABC): return retPath - def FindLastRow(self, sheetName): + def FindLastRow(self, sheetName:str)-> int: sheet = self.getSheet(sheetName) if sheet is None: return -1 diff --git a/UI.py b/UI.py index caa7154..88dd001 100644 --- a/UI.py +++ b/UI.py @@ -1,6 +1,7 @@ import os import sys import UtilPack as util +from pathlib import Path import VWorldAPIs as vw import ExtEXIFData as exif @@ -141,7 +142,8 @@ class MyApp(QMainWindow): # Show GPS Mapped map in Right Layout self.webview_map = QWebEngineView() 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() diff --git a/UtilPack.py b/UtilPack.py index db4e9f2..79568df 100644 --- a/UtilPack.py +++ b/UtilPack.py @@ -2,22 +2,25 @@ import os import re import math import time -import rarfile +#import rarfile import zipfile -import shutil -import difflib +#import shutil +#import difflib import subprocess -import UtilPack as util +from pathlib import Path 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()) # -def GetCurrentTime(): +def GetCurrentTime() -> str: # 현재 시간을 구하고 구조체로 변환 current_time_struct = time.localtime() @@ -34,7 +37,7 @@ def GetCurrentTime(): return strRet #for debug -def DbgOut(strInput, bPrint = False): +def DbgOut(strInput:str, bPrint:bool = False): strMsg = (f"{GetCurrentTime()} : {strInput}") listDbgStr.append(strMsg) @@ -46,7 +49,7 @@ def printDbgMessages(): print(line) # 입력된 패스에서 부모 폴더를 찾는다. 0 은 자기자신 1은 부모, 숫자대로 위로. -def GetParentDirName(FullPath, nUp): +def GetParentDirName(FullPath:str, nUp:int) -> str: parts = FullPath.split(os.sep) nTrgIdx = 0 @@ -60,7 +63,7 @@ def GetParentDirName(FullPath, nUp): return parts[nTrgIdx] # os 모듈을 사용하여 자식 폴더를 반환 -def GetSubDirectories(folder_path): +def GetSubDirectories(folder_path:str)-> list[str]: subdirectories = [ d for d in os.listdir(folder_path) if os.path.isdir(os.path.join(folder_path, d))] @@ -69,8 +72,8 @@ def GetSubDirectories(folder_path): # 입력된 경로의 자식 폴더를 찾아 반환한다. # 반환하는 리스트는 리커시브 - 손자, 증손자 폴더까지 전부 포함한다 -def GetAllSubDirectories(root_dir): - subdirectories = [] +def GetAllSubDirectories(root_dir:str)-> list[str]: + subdirectories:list[str] = [] # root_dir에서 하위 디렉토리 및 파일 목록을 얻음 for dirpath, dirnames, filenames in os.walk(root_dir): @@ -83,8 +86,8 @@ def GetAllSubDirectories(root_dir): return subdirectories -def ListFileExtRcr(pathTrg, strExt): - listRet= [] +def ListFileExtRcr(pathTrg:str, strExt:str) -> list[str]: + listRet:list[str] = [] # pathTrg의 하위 디렉토리 및 파일 목록을 얻음 for dirpath, dirnames, filenames in os.walk(pathTrg): @@ -97,7 +100,7 @@ def ListFileExtRcr(pathTrg, strExt): # 입력된 경로가 자식 폴더를 가지고 있는지 판단한다.- 최종 폴더인지 여부 # 자식이 없으면 True, 자식이 있으면 False -def IsFinalFolder(path): +def IsFinalFolder(path:str) -> bool: bRet = True contents = os.listdir(path) @@ -109,12 +112,12 @@ def IsFinalFolder(path): return bRet; # 어떤 경로 안에서 특정 확장자의 파일을 뽑아내어 그 리스트를 반환한다. -def FindFileFromExt(path, ext): +def FindFileFromExt(path:str, ext:str) -> list[str]: bDot = False if 0 <= ext.find('.'): bDot = True - listRet = [] + listRet:list[str] = [] if False == os.path.exists(path): return listRet @@ -131,7 +134,7 @@ def FindFileFromExt(path, ext): # 파일 이름에서 확장자를 뽑아낸다. True : '.' 을 포함한다. -def GetExtStr(file_path, bDot = True): +def GetExtStr(file_path:str, bDot:bool = True) -> str: retStr = "" # 파일 경로에서 마지막 점을 찾아 확장자를 추출 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) if strIdx == -1: # 부분 문자열이 존재하지 않으면 그대로 반환합니다. @@ -158,12 +161,12 @@ def RmvSubString(mainString, subString): # 부분 문자열을 제거하고 새로운 문자열을 반환합니다. 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: zf.extractall(extract_to) # -def CreateZIP(output_zip, *files): +def CreateZIP(output_zip:str, *files): with zipfile.ZipFile(output_zip, 'w') as zf: for file in files: pathTemp = os.path.join('root', os.path.basename(file)) @@ -176,7 +179,7 @@ def CreateZIP(output_zip, *files): return bRet # 파일 리스트에 들어있는 파일만 골라서 압축을 합니다. 상대경로를 제거하는게 기본값. -def CreateZIPShell(zipName, *files, bRmvRPath = True): +def CreateZIPShell(zipName:str, *files, bRmvRPath:bool = True)-> bool: command = "zip " if True == bRmvRPath: @@ -206,7 +209,7 @@ def CreateZIPShell(zipName, *files, bRmvRPath = True): return bRet # 특정 확장자만 쉘을 이용해서 압축한다 -def CreateZIPShExt(zipName, TrgExt): +def CreateZIPShExt(zipName:str, TrgExt:str): command = f"zip -j {zipName} *.{TrgExt}" result = subprocess.run(command, shell=True, capture_output=True, text=True) @@ -218,7 +221,7 @@ def CreateZIPShExt(zipName, TrgExt): return bRet # JSON 을 트리 구조로 출력한다. -def PrintJSONTree(data, indent=0): +def PrintJSONTree(data, indent:int=0): if isinstance(data, dict): for key, value in data.items(): print(' ' * indent + str(key)) @@ -230,7 +233,7 @@ def PrintJSONTree(data, indent=0): print(' ' * indent + str(data)) # 세로 크기를 가로 비율대로 줄여서 반환 (가로를 기준으로 세로 길이) -def GetRSzHeight(nWidth, nHeight, nTrgW): +def GetRSzHeight(nWidth:int, nHeight:int, nTrgW:int) -> int: if nWidth <= 0 or nTrgW <= 0: return 0; @@ -240,7 +243,7 @@ def GetRSzHeight(nWidth, nHeight, nTrgW): return nTrgH # 가로 크기를 세로 비율대로 줄여서 반환 (세로를 기준으로 가로 길이) -def GetRSzWidth(nWidth, nHeight, nTrgH): +def GetRSzWidth(nWidth:int, nHeight:int, nTrgH:int) -> int: if nHeight <= 0 or nTrgH <= 0: return 0; @@ -252,15 +255,15 @@ def GetRSzWidth(nWidth, nHeight, nTrgH): # 엑셀 열 너비는 약 7.5픽셀 단위 # 엑셀 행 높이는 약 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 row_height = (nImgH / DPI) * 25 * 3 return column_width, row_height -def GetSheetInfoValue(text): - if text is None: - return "", "" +def GetSheetInfoValue(text:str) -> tuple[str, list[str]]: + if IsEmptyStr(text): + return "", [] pattern = r'<(.*?)>' matches = re.findall(pattern, text) @@ -269,7 +272,7 @@ def GetSheetInfoValue(text): return cleaned, matches # 두 GPS 좌표 간 거리 구하기 (미터) -def GetDistanceGPS(lat1, lon1, lat2, lon2): +def GetDistanceGPS(lat1:float, lon1:float, lat2:float, lon2:float) -> float: # 지구의 반지름 (단위: 미터) R = 6371008.8 @@ -295,4 +298,5 @@ def GetDistanceGPS(lat1, lon1, lat2, lon2): + \ No newline at end of file diff --git a/UtilPack2.py b/UtilPack2.py deleted file mode 100644 index b0e60d2..0000000 --- a/UtilPack2.py +++ /dev/null @@ -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 - diff --git a/requirements.txt b/requirements.txt index b24d1b9..cfd584a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,10 +35,8 @@ pure_eval==0.2.3 Pygments==2.18.0 pyproj==3.7.2 PyQt5==5.15.11 -PyQt5-Qt5==5.15.17 PyQt5_sip==12.17.1 PyQtWebEngine==5.15.7 -PyQtWebEngine-Qt5==5.15.17 pytesseract==0.3.13 python-dateutil==2.9.0.post0 rarfile==4.2 diff --git a/test.py b/test.py index be934f6..077b6f5 100644 --- a/test.py +++ b/test.py @@ -5,9 +5,9 @@ import PoleXLS as myxl import VWorldAPIs as vwapi -ImgDirPath = "/Volumes/ExSSD/Working/Images" -ImgSortedPath = "/Volumes/ExSSD/Working/Sorted" -XlsDirPath = "/Users/minarinari/Workspace/Python/UtilityPole_Info" +ImgDirPath = util.NormalizePath("/Volumes/ExSSD/Working/Images") +ImgSortedPath = util.NormalizePath("/Volumes/ExSSD/Working/Sorted") +XlsDirPath = util.NormalizePath("/Users/minarinari/Workspace/Python/UtilityPole_Info") indexFile = ".index.maps"