From 37267985b4a3f094b94c0ebdb564e49594181771 Mon Sep 17 00:00:00 2001 From: Young Hoon Lee Date: Tue, 23 Sep 2025 20:59:17 +0900 Subject: [PATCH] First commit --- ExtEXIFData.py | 192 ++++++++++++++++++++ JusoMngr.py | 51 ++++++ Main.py | 26 +++ MgrUtilityPoleConverter.py | 303 ++++++++++++++++++++++++++++++++ MgrUtilityPoleOCR.py | 46 +++++ MgrUtilityPoleUI.py | 167 ++++++++++++++++++ PoleXLS.py | 173 +++++++++++++++++++ Pole_serial_sorter.py | 108 ++++++++++++ StoreXLS.py | 186 ++++++++++++++++++++ UI.py | 246 ++++++++++++++++++++++++++ UtilPack.py | 298 ++++++++++++++++++++++++++++++++ UtilPack2.py | 346 +++++++++++++++++++++++++++++++++++++ VWorldAPIs.py | 77 +++++++++ test.py | 89 ++++++++++ 14 files changed, 2308 insertions(+) create mode 100644 ExtEXIFData.py create mode 100644 JusoMngr.py create mode 100644 Main.py create mode 100644 MgrUtilityPoleConverter.py create mode 100644 MgrUtilityPoleOCR.py create mode 100644 MgrUtilityPoleUI.py create mode 100644 PoleXLS.py create mode 100644 Pole_serial_sorter.py create mode 100644 StoreXLS.py create mode 100644 UI.py create mode 100644 UtilPack.py create mode 100644 UtilPack2.py create mode 100644 VWorldAPIs.py create mode 100644 test.py diff --git a/ExtEXIFData.py b/ExtEXIFData.py new file mode 100644 index 0000000..62220b8 --- /dev/null +++ b/ExtEXIFData.py @@ -0,0 +1,192 @@ +import os + +from PIL import Image +from PIL.ExifTags import TAGS, GPSTAGS + +import UtilPack as util + +# EXIF 데이터에서 사진이 찍힌 날자와 시간을 가져온다 +def GetDateInfo(ImgPath): + img = _imageOpen(ImgPath) + if None == img: + return "", 0.0, 0.0 + + dataEXIF = _getEXIFData(img) + date = _getDateEXIF(dataEXIF) + dataGPS = _parseGPSInfo(dataEXIF) + lat, lon = _getlatlon(dataGPS) + + return date, lat, lon + +def ProcessImg(imgAbsPath, fRatio, nQlt = 85, bOrientApy = True): + # + dirSave = "Thumb" + dirpath, filename = os.path.split(imgAbsPath) + parenDir = os.path.dirname(dirpath) + dirThumb = os.path.join(parenDir, dirSave) + finalPath = os.path.join(dirThumb, filename) + + # 설정은 상관없이 이전에 리사이즈 해 둔 것이 있으면 경로 반환 + if True == os.path.exists(finalPath): + #print(f"[] -> {finalPath}") + return finalPath + + img = _imageOpen(imgAbsPath) + if None == img: + return imgAbsPath + + nRotate = 0 + if True == bOrientApy: + dataEXIF = _getEXIFData(img) + nRotate = _getOrientationEXIF(dataEXIF) + + img = _rotateImg(img, nRotate) + img = _resizeImg(img, fRatio) + + if False == os.path.exists(dirThumb): + os.makedirs(dirThumb, 0o777) + print(f"Thumb folder Created : {dirThumb}") + + img.save(finalPath, optimize=True, quality=nQlt) + + #print(f"{imgAbsPath} -> {finalPath}") + + return finalPath + +def _rotateImg(imgSrc, nRotate): + if None == imgSrc: + return imgSrc + + if not isinstance(imgSrc, Image.Image): + #raise TypeError("Input must be a PIL Image object") + print("Type Error") + return imgSrc + + if 0 >= nRotate or 360 <= nRotate: + return imgSrc + + imgRet = imgSrc.rotate(nRotate, expand=True) + + bbox = imgRet.getbbox() + imgRet = imgRet.crop(bbox) + + return imgRet + +def _resizeImg(imgSrc, fRatio ): + if None == imgSrc: + return imgSrc + + if not isinstance(imgSrc, Image.Image): + #raise TypeError("Input must be a PIL Image object") + return imgSrc + + if 0 >= fRatio or 1.0 < fRatio: + return imgSrc + + imgRet = imgSrc.resize((int(imgSrc.width * fRatio), int(imgSrc.height * fRatio))) + return imgRet + +def _imageOpen(ImgPath): + imgRet = None + try: + # 이미지 열기 시도 + imgRet = Image.open(ImgPath) + imgRet.load() # 이미지 파일을 실제로 로드하여 확인 + + util.DbgOut("Image open successful") + # 이미지 처리 코드 추가 + except FileNotFoundError: + util.DbgOut(f"can't find Image': {ImgPath}") + except OSError: + util.DbgOut(f"Image broken or unsupported MIME: {ImgPath}") + except Exception as e: + util.DbgOut(f"unexpected error: {e}") + + return imgRet + +# 이미지에서 EXIF 데이터를 추출하여 딕셔너리 형태로 반환 +def _getEXIFData(image): + exif_data = {} + try: + info = image._getexif() + if info: + for tag, value in info.items(): + decoded = TAGS.get(tag, tag) + exif_data[decoded] = value + except AttributeError: + pass + + return exif_data + +def _getDateEXIF(exif_data): + date_taken = exif_data.get('DateTimeOriginal') + return date_taken + +# 0 : NoData +# 1: "Landscape (normal) -> 0" +# 3: "Landscape (upside down) -> 180" +# 6: "Portrait (rotated 90° CW) -> 270" +# 8: "Portrait (rotated 90° CCW) -> 90" +def _getOrientationEXIF(exif_data): + if exif_data is None: + return 0 + + retValue = 0 + # EXIF 태그에서 Orientation 추출 + for tag, value in exif_data.items(): + tag_name = TAGS.get(tag, tag) + if tag_name == 'Orientation': + if value == 3: + retValue = 180 + elif value == 6: + retValue = 270 + elif value == 8: + retValue = 90 + else: # value == 1: + retValue = 0 + break + + return retValue + +# EXIF 데이터에서 GPS 정보를 파싱하여 딕셔너리 형태로 반환 +def _parseGPSInfo(exif_data): + gps_info = {} + if 'GPSInfo' in exif_data: + for key in exif_data['GPSInfo'].keys(): + decode = GPSTAGS.get(key, key) + gps_info[decode] = exif_data['GPSInfo'][key] + + return gps_info + +# 도, 분, 초 형식의 GPS 데이터를 도 형식으로 변환 +def _convertToDegrees(value): + + d = 0.0 + if 0 < value[0]: + d = float(value[0]) + + m = 0.0 + if 0 < value[1]: + m = float(value[1]) / 60.0 + + s = 0.0 + if 0 < value[2]: + s = float(value[2]) / 3600.0 + + return d + m + s + +# GPS 정보를 사용하여 위도와 경도를 도 형식으로 반환 +def _getlatlon(gps_info): + lat,lon = 0.0, 0.0 + if 'GPSLatitude' in gps_info and 'GPSLatitudeRef' in gps_info: + lat = _convertToDegrees(gps_info['GPSLatitude']) + if gps_info['GPSLatitudeRef'] != 'N': + lat = -lat + + if 'GPSLongitude' in gps_info and 'GPSLongitudeRef' in gps_info: + lon = _convertToDegrees(gps_info['GPSLongitude']) + if gps_info['GPSLongitudeRef'] != 'E': + lon = -lon + + return lat, lon + diff --git a/JusoMngr.py b/JusoMngr.py new file mode 100644 index 0000000..7677ad4 --- /dev/null +++ b/JusoMngr.py @@ -0,0 +1,51 @@ +import csv + +class JusoDB: + def __init__(self, path_csv): + self.path = path_csv + self.dict_juso = [] + + self.ReadCSV(path_csv) + + def ReadCSV(self, path): + # CSV 파일 읽기 + with open(path, mode='r', newline='', encoding='utf-8') as file: + reader = csv.DictReader(file) + # 헤더 읽기 + header = next(reader) + + # 각 행을 딕셔너리로 저장 + for row in reader: + self.dict_juso.append(row) + + print(f"Juso Count : {len(self.dict_juso)}") + + def CompareAndFindValue(self, src, trg, value): + retValue = "" + for row in self.dict_juso: + temp1 = value.replace(" ", "").strip() + temp2 = row[src].replace(" ","").strip() + if temp1 == temp2: + retValue = row[trg] + break; + + return retValue + + def ConvJusoToRoad(self, juso): + return self.CompareAndFindValue("jibun", "road", juso) + + def ConvRoadToJibun(self, juso): + return self.CompareAndFindValue("road", "jibun", juso) + + def GetRoadArea(self, road): + if road == "": + return "" + + #서울시 용산구 무시무시로20길 99-99 <- 현재까지 가진 데이터는 전부 이런 형식 + words = road.split() + if len(words) < 3: + return "" + + return words[2] + + \ No newline at end of file diff --git a/Main.py b/Main.py new file mode 100644 index 0000000..33231be --- /dev/null +++ b/Main.py @@ -0,0 +1,26 @@ +import os +import sys + +import MgrUtilityPoleUI + +from PyQt5.QtWidgets import QApplication + + +def main(argc, argv): + app = QApplication(argv) + + app.setQuitOnLastWindowClosed(True) + main_window = MgrUtilityPoleUI.MyApp() + main_window.show() + + sys.exit(app.exec_()) + + +# For Main Loop +if __name__ == '__main__': + argc = len(sys.argv) + argv = sys.argv + main(argc, argv) + + + diff --git a/MgrUtilityPoleConverter.py b/MgrUtilityPoleConverter.py new file mode 100644 index 0000000..b6a5e42 --- /dev/null +++ b/MgrUtilityPoleConverter.py @@ -0,0 +1,303 @@ +import os +import sys + +import UtilPack as util +import PoleXLS as myxl +import ExtEXIFData as exEXIF +import VWorldAPIs as vwapi +import JusoMngr + +mCurPath = "" # 스크립트가 실행되는 경로 +mArcPath = "Zips" # 압축파일이 있는 폴더, 하위를 훑는다. +mXlsPath = "" # 엑셀 파일이 있는 경로, 틀리면 안됨. +mImgPath = "/Volumes/ExSSD/Working/Images" # 이미지가 들어있는 폴더, 이 아래에 A,B,Pole 등이 있어야 한다. + +# 분류작업을 위해 임시로 생성 +mTempPath = "/Volumes/ExSSD/Working/TmpImgs" + +#def main(argc, argv): +# if argc != 2: +# printUsage() +# return + +# 기준 경로, 사용할 경로 +# 사용할 경로가 상대경로면 기준 경로를 이용해 계산, +# 사용할 경로가 절대경로만 경로가 유효한지 확인해서 그대로 반환, 아니라면 기준경로 반환 +# 기준경로는 따로 입력되는게 없으면 실행 경로 +def GetAbsPath(PathCri, PathTrg): + if True == util.IsEmptyStr(PathCri): + PathCri = os.getcwd() + + PathRet = PathCri + if True == os.path.isabs(PathTrg): + PathRet = os.path.join(PathCri, PathTrg) + else: + PathRet = PathTrg + + if False == os.path.exists(PathTrg): + PathRet = PathCri + + return PathTrg + +def ExtractArchives(pathArc, pathTrg): + if False == os.path.exists(pathArc): + return [] + + ZIPList = os.listdir(pathArc) + + retUNZipList = [] + for zipFile in ZIPList: + zipName, zipExt = os.path.splitext(zipFile) + trgPath = os.path.join(pathTrg, zipName) + + # 압축을 푼 폴더가 존재한다면 넘어간다. 이후 보강해야 함 + if True == os.path.exists(trgPath): + continue + + zipPath = os.path.join(pathArc, zipFile) + util.ExtractZIP(zipPath, trgPath) + + retUNZipList.append(trgPath) + + return retUNZipList + + +def GetPoleInfos(xls, nRow): + if None == xls or False == isinstance(xls, myxl.PoleXLS): + return [] + + retList = xls.GetAllRowValue("PoleInfo", nRow, 5) + return retList + + +def GetJPGFileList(listUNZip, pathImage): + trgDirList = [] + # 압축 푼 폴더가 있으면 그것만, 아니면 이미지 폴더 전부를 다 + if len(listUNZip) > 0: + trgDirList = listUNZip + else: + trgDirList = util.GetSubDirectories(pathImage) + + retImgList = [] + for dirName in trgDirList: + imgSubDirPath = os.path.join(pathImage, dirName) + contents = os.listdir(imgSubDirPath) + + for item in contents: + name, ext = os.path.splitext(item) + if ext.lower() == '.jpg': + imgPath = os.path.join(imgSubDirPath, item) + retImgList.append(imgPath) + + return retImgList + +# 필요한 이미지 정보를 모아 한 시트에 전부 넣는다. +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") + jusomgr = JusoMngr.JusoDB(pathcsv) + + nIdx = 1 + for item in listImg: + # 이미지가 삽입되었는지 여부 확인 + if 0 < xls.FindInsertedImgPath(pathImgDir, item): + nIdx = xls.FindLastRow(sheetTrg) + 1 + continue + + all_date, lat, lon = exEXIF.GetDateInfo(item) + pathRel = os.path.relpath( item, pathImgDir ) + level = util.GetParentDirName(item, 1) + if level == "Thumb": + continue + + # 0 : 지번, 1 : 구역, 2 : 도로명, 3 : 도로명 구역 + juso = vwapi.GetJusofromGPS(lat, lon) + + # 주소 조회가 안되는 현상이 있다. 재시도... + if lat != 0.0 and lon != 0.0 and juso[2] == "": + juso = vwapi.GetJusofromGPS(lat, lon) + + # 도로명 주소를 못 끌어오면, 저장해 둔 목록에서 한번 더 조회 + if juso[0] != "" and juso[2] == "": + juso[2] = jusomgr.ConvJusoToRoad(juso[0]) + juso[3] = jusomgr.GetRoadArea(juso[2]) + + xls.SetCellValueStr(sheetTrg, 1, nIdx, nIdx) + xls.SetCellValueStr(sheetTrg, 2, nIdx, "") # 이미지가 들어갈 시트 이름. 넣을 떄 넣자. + 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]) + + # 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}") + + nIdx += 1 + + return nIdx + +# DB 시트에 있는 파일을 골라내서 PoleInfo 시트에 넣는다. +# PoleInfo : 번호, GPS 좌표, 구역, 주소, 사진 +def CollectPoleInfoData(xls, TrgSheetName, DBSheetName, pathImgDir): + if None == xls: + return -1 + + nLastDBRow = xls.FindLastRow( DBSheetName ) + if 0 >= nLastDBRow: + print(f"{DBSheetName} Sheet is Empty.") + return -1 + + # 헤더가 있는 시트는 헤더가 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) + if None == listValues: + continue + + if listValues[1] == TrgSheetName: + continue + + if listValues[3] != "Pole" : + continue + + if listValues[7] == "" or listValues[7] == "0.0,0.0": + continue + + print( listValues ) + + 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]) + + imgPath = os.path.join(pathImgDir, listValues[4]) + imgPath = exEXIF.ProcessImg(imgPath, 0.1, 80, True) + xls.SetCellValueImg(TrgSheetName, 5, nTrgRow, imgPath) + + xls.SetCellValueStr(TrgSheetName, 6, nTrgRow, listValues[0]) + + # 목표 시트에 넣었으니 시트 이름을 DB 시트에 적어 넣는다. + xls.SetCellValueStr(DBSheetName, 2, nDBRow, TrgSheetName) + + nTrgRow += 1 + + return nTrgRow + +# DB 시트와 점봇대 정보 시트로 정보를 찾고 계산한다. +# Pole : 연번, 점검일자, 구역, 가까운 전신주 번호, 가까운 전신주 거리, 심각, 양호, 등급, 비고 +def CollectPoleData( xls, TrgSheetName, InfoSheetName, DBSheetName, pathImgDir ): + if None == xls: + return -1 + + nLastDBRow = xls.FindLastRow( DBSheetName ) + if 0 >= nLastDBRow: + print(f"{DBSheetName} Sheet is Empty.") + return -1 + + # 헤더가 있는 시트는 헤더가 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) + if None == listValues: + continue + + if listValues[1] == TrgSheetName: + continue + + if listValues[3] != "A" and listValues[3] != "B" : + continue + + if listValues[7] == "" or listValues[7] == "0.0,0.0": + continue + + # 날짜만 골라낸다. + strDate = "" + if listValues[2] != "": + strDate, strTime = listValues[2] .split(' ') + strDate = strDate.replace(':','/') + + xls.SetCellValueStr(TrgSheetName, 1, nTrgRow, str(nTrgRow -1)) + xls.SetCellValueStr(TrgSheetName, 2, nTrgRow, strDate) + + # 가장 가까운 점봇대를 찾는다. 인덱스를 얻어서... + strLat, strLon = listValues[7] .split(',') + nPoleIdx, nDistance = xls.FindCloestPole(float(strLat), float(strLon)) + + # 배열은 0부터, Pole 인덱스는 1부터 시작하고, 셀 번호는 헤더를 포함해서 2 부터 시작한다. + # 셀 번호를 넣어서 해당하는 정보를 가져온다. + # 0:번호, 1:GPS 좌표, 2:구역, 3:주소, 4:사진 + #print( f"{nDBRow} : {nTrgRow} : {nPoleIdx}") + retList = xls.GetAllRowValue(InfoSheetName, nPoleIdx, 5) + + xls.SetCellValueStr(TrgSheetName, 3, nTrgRow, retList[2]) + xls.SetCellValueStr(TrgSheetName, 4, nTrgRow, nPoleIdx) + xls.SetCellValueStr(TrgSheetName, 5, nTrgRow, nDistance) + + ColIdx = 6 + if listValues[3] == "B" : + ColIdx = 7 + + imgAbsPath = os.path.join(pathImgDir, listValues[4]) + imgpath = exEXIF.ProcessImg(imgAbsPath, 0.1, 80, True) + xls.SetCellValueImg(TrgSheetName, ColIdx, nTrgRow, imgpath) + + xls.SetCellValueStr(TrgSheetName, 8, nTrgRow, listValues[3]) + xls.SetCellValueStr(TrgSheetName, 9, nTrgRow, nDBRow) + + # 목표 시트에 넣었으니 시트 이름을 DB 시트에 적어 넣는다. + xls.SetCellValueStr(DBSheetName, 2, nDBRow, TrgSheetName) + + nTrgRow += 1 + + return nTrgRow + +def main(): + # 경로를 정리하여 절대 경로로 변환해서 저장한다. + CurPath = GetAbsPath(mCurPath, "") + ArcDirPath = GetAbsPath(CurPath, mArcPath) + XlsDirPath = GetAbsPath(CurPath, mXlsPath) + ImgDirPath = GetAbsPath(CurPath, mImgPath) + + # 압축해제 + listUNZip = ExtractArchives(ArcDirPath, ImgDirPath) + + # 이미지 파일 목록 작성 + trgImgList = GetJPGFileList(listUNZip, ImgDirPath) + + # 엑셀을 연다. + XLSPath = os.path.join(XlsDirPath, "EPoleDB.xlsx") + tempxls = myxl.PoleXLS(XLSPath) + tempxls.DBXLSOpen() + + # 이미지 정보를 전부 모아서 한 시트에 저장 + CollectImageData(tempxls, "ImgInfo", trgImgList, ImgDirPath) + # 모아 놓은 이미지 정보에서 점봇대 정보를 골라서 저장 + CollectPoleInfoData(tempxls, "PoleInfo", "ImgInfo", ImgDirPath) + # 점봇대 정보 시트를 바탕으로 제보받은 이미지를 판별, 계산하여 저장 + CollectPoleData(tempxls, "Poles", "PoleInfo", "ImgInfo", ImgDirPath) + + tempxls.DBXLSClose() + + +def printUsage(): + print("Usage : python main.py ") + +# For Main Loop +if __name__ == '__main__': +# argc = len(sys.argv) +# argv = sys.argv +# main(argc, argv) + main() \ No newline at end of file diff --git a/MgrUtilityPoleOCR.py b/MgrUtilityPoleOCR.py new file mode 100644 index 0000000..7da73f5 --- /dev/null +++ b/MgrUtilityPoleOCR.py @@ -0,0 +1,46 @@ +import cv2 +import pytesseract +from pytesseract import Output + +img_path = '/Volumes/ExSSD/Working/용공추 사진/2,3월 데이터/Pole/20250307_153821.jpg' + +# 이미지 경로 +img = cv2.imread(img_path) + +# 전처리 +gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) +blur = cv2.GaussianBlur(gray, (3, 3), 0) +thresh = cv2.adaptiveThreshold( + blur, 255, + cv2.ADAPTIVE_THRESH_MEAN_C, + cv2.THRESH_BINARY_INV, + 11, 2 +) + +# OCR +custom_config = r'--oem 3 --psm 6' +data = pytesseract.image_to_data( + thresh, lang='kor+eng', + config=custom_config, + output_type=Output.DICT +) + +# 보안등 아래 숫자 찾기 +target_text = '보안등' +number_below = None + +for i, word in enumerate(data['text']): + if word.strip() == target_text: + for j in range(i + 1, len(data['text'])): + if data['text'][j].strip().isdigit() and data['top'][j] > data['top'][i]: + number_below = data['text'][j].strip() + break + break + +print(f"보안등 아래 숫자: {number_below}") + +# 디버깅용: 전체 텍스트 출력 +print("\n전체 인식 텍스트:") +for word in data['text']: + if word.strip(): + print(word.strip()) \ No newline at end of file diff --git a/MgrUtilityPoleUI.py b/MgrUtilityPoleUI.py new file mode 100644 index 0000000..23935f5 --- /dev/null +++ b/MgrUtilityPoleUI.py @@ -0,0 +1,167 @@ +import sys +import os +import shutil +import UtilPack as util + + +from PyQt5.QtCore import Qt, QSettings, QRect +from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QPushButton, QVBoxLayout, QLineEdit, \ + QHBoxLayout, QTableWidget, QTableWidgetItem, QAbstractItemView, QHeaderView, QFileDialog, \ + QListWidget, QListWidgetItem, QMessageBox +from PyQt5.QtGui import QResizeEvent, QCloseEvent, QColor + +class MyApp(QMainWindow): + + def __init__(self): + super().__init__() + self.initUI() + self.loadINI() + self.initDB() + + # + def closeEvent(self, a0: 'QCloseEvent | None'): + filenameLog = f"{util.GetCurrentTime()}_DbgLog.txt" + pathDbgLog = os.path.join( self.pathLog, filenameLog) + util.SaveDbgMessages(pathDbgLog) + self.saveINI() + super().closeEvent(a0) + + # + def resizeEvent(self, a0: 'QResizeEvent | None'): + pass + + # + def loadINI(self): + pass + + # + def saveINI(self): + pass + + # + def initDB(self): + pass + + # + def MakeUI_Left(self): + layout = QVBoxLayout() + + layout_top = QHBoxLayout() + self.listWidget_Folders = QListWidget() + self.listWidget_Folders.setFixedHeight(100) + + layout_Btns = QVBoxLayout() + btn_FolderAdd = QPushButton("폴더 추가") + btn_FolderAdd.clicked.connect(self.on_btnFolderAdd_clicked) + btn_FolderDel = QPushButton("폴더 삭제") + btn_FolderDel.clicked.connect(self.on_btnFolderDel_clicked) + btn_FolderParss = QPushButton("폴더 파싱") + btn_FolderParss.clicked.connect(self.on_btnFolderParse_clicked) + + layout_Btns.addWidget(btn_FolderAdd) + layout_Btns.addWidget(btn_FolderDel) + layout_Btns.addWidget(btn_FolderParss) + + layout_top.addWidget(self.listWidget_Folders) + layout_top.addLayout(layout_Btns) + + self.tableWidget_Src = QTableWidget() + self.tableWidget_Src.verticalHeader().setVisible(False) + self.tableWidget_Src.setEditTriggers(QAbstractItemView.NoEditTriggers) + self.tableWidget_Src.setSortingEnabled(True) + self.tableWidget_Src.setColumnCount(5) + self.tableWidget_Src.setHorizontalHeaderLabels(["Path", "Title", "H.ID", "Img Cnt", "Dw Cnt"]) + self.tableWidget_Src.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self.tableWidget_Src.horizontalHeader().sectionClicked.connect(self.on_tableWidget_Src_headerClicked) + self.tableWidget_Src.setSelectionBehavior(QAbstractItemView.SelectRows) + self.tableWidget_Src.setSelectionMode(QAbstractItemView.SingleSelection) + self.tableWidget_Src.itemSelectionChanged.connect(self.on_tableWidget_Src_itemSelectionChanged) + + layout.addLayout(layout_top) + layout.addWidget(self.tableWidget_Src) + + return layout + + # + def MakeUI_Center(self): + layout = QVBoxLayout() + + btn_Emptyfolder = QPushButton("빈 폴더 이동") + btn_Emptyfolder.clicked.connect(self.on_btn_Emptyfolder_clicked) + btn_ChkDuplicate = QPushButton("중복 검사 및 제거") + btn_ChkDuplicate.clicked.connect(self.on_btn_ChkDuplicate_clicked) + btn_Archive = QPushButton("압축 및 데이터 저장") + btn_Archive.clicked.connect(self.on_btn_Archive_clicked) + btn_EnterCalibre = QPushButton("컬리버에 삽입") + btn_EnterCalibre.clicked.connect(self.on_btn_EnterCalibre_clicked) + + layout.addWidget(btn_Emptyfolder) + layout.addWidget(btn_ChkDuplicate) + layout.addWidget(btn_Archive) + layout.addWidget(btn_EnterCalibre) + + return layout + + # + def MakeUI_Right(self): + layout_top = QHBoxLayout() + self.edit_DB = QLineEdit(self) + self.edit_DB.setReadOnly(True) + self.edit_DB.setText("...") + btn_DB = QPushButton("...") + btn_DB.setFixedWidth(50) + btn_DB.clicked.connect(self.on_btnDB_clicked) + layout_top.addWidget(self.edit_DB) + layout_top.addWidget(btn_DB) + + self.tableWidget_DB = QTableWidget() + self.tableWidget_DB.verticalHeader().setVisible(False) + self.tableWidget_DB.setColumnCount(5) + self.tableWidget_DB.setHorizontalHeaderLabels(["Title", "Author", "Cover", "Exts", "ID"]) + self.tableWidget_DB.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self.tableWidget_DB.setSelectionBehavior(QAbstractItemView.SelectRows) + self.tableWidget_DB.setSelectionMode(QAbstractItemView.SingleSelection) + self.tableWidget_DB.itemSelectionChanged.connect(self.on_tableWidget_DB_itemSelectionChanged) + + layout = QVBoxLayout() + layout.addLayout(layout_top) + layout.addWidget(self.tableWidget_DB) + + return layout + + # + def MakeUI(self): + layout_L = self.MakeUI_Left() + layout_C = self.MakeUI_Center() + layout_R = self.MakeUI_Right() + + # 레이아웃 설정 + layout = QHBoxLayout() + layout.addLayout(layout_L, stretch = 10) + layout.addLayout(layout_C, stretch = 1) + layout.addLayout(layout_R, stretch = 10) + + return layout + + # + def initUI(self): + layout = self.MakeUI() + + # 레이아웃을 윈도우에 적용 + central_widget = QWidget() + central_widget.setLayout(layout) + self.setCentralWidget(central_widget) + self.setWindowTitle('Manga Database') + self.resize(800, 600) + self.show() + + # 테이블의 특정 행에 배경색을 설정한다 + # nRow: 배경색을 설정할 행 번호, color: 배경색 (Qt.GlobalColor) + def SrcTableRowBgColor(self, nRow:int, color:Qt.GlobalColor) -> None: + for col in range(self.tableWidget_Src.columnCount()): + item = self.tableWidget_Src.item(nRow, col) + if item: + item.setBackground(Qt.GlobalColor(color)) + + + diff --git a/PoleXLS.py b/PoleXLS.py new file mode 100644 index 0000000..ffab361 --- /dev/null +++ b/PoleXLS.py @@ -0,0 +1,173 @@ +from StoreXLS import DBXLStorage +import os +import UtilPack as util + +class PoleXLS(DBXLStorage): + m_dictSheetInfo = {} + + # 여기서부터 이 프로젝트에 특화된 함수 + def checkSheetsInfo(self): + sheet = self.getSheet(self.xls_infosheet) + if sheet is None: + return + + # 3번째 줄부터 읽는다. + for row in sheet.iter_rows(min_row=3, values_only=True): + sheetName = row[0] + idxList = range(0, len(row)) + listRecp = [] + for colIdx, item in zip(idxList, row): + # 시트 이름은 넘어가자. + if 0 == colIdx: + continue + + self.getSheet(sheetName, True) + + title, recp = util.GetSheetInfoValue(item) + self.SetCellValueStr(sheetName, colIdx, 1, title) + listRecp.append(recp) + + self.m_dictSheetInfo[sheetName] = listRecp + + #print(self.m_dictSheetInfo) + + + def FindCloestPole(self, srcLat, srcLon): + sheet = self.getSheet("PoleInfo") + if sheet is None: + return -1, 0 + + if None == srcLat or None == srcLon: + return -1, 0 + + # 헤더 다음 줄부터 읽는다. + listDistance = [] + for row in sheet.iter_rows(min_row=2, values_only=True): + temp = row[1] + + if "None" in temp: + continue + + dist = 0 + if temp != "": + parts = temp.split(',') + + trgLat = float(parts[0]) + trgLon = float(parts[1]) + dist = util.GetDistanceGPS(srcLat, srcLon, trgLat, trgLon) + + listDistance.append(dist) + + if 0 >= len(listDistance): + return -1, 0 + + min_value = min(listDistance) + retIdx = listDistance.index(min_value) + 1 + + return retIdx, round( min_value, 0 ) + + + def GetPoleInfoAll(self, nIdx): + sheet = self.getSheet("PoleInfo") + if sheet is None: + return None + + retList = [] + retList.append(sheet.cell(row=nIdx,column=1).value) + retList.append(sheet.cell(row=nIdx,column=2).value) + retList.append(sheet.cell(row=nIdx,column=3).value) + retList.append(sheet.cell(row=nIdx,column=4).value) + retList.append(sheet.cell(row=nIdx,column=5).value) + retList.append(sheet.cell(row=nIdx,column=6).value) + + return retList + + + def GetPoleInfo(self, nIdx, colNum): + sheet = self.getSheet("PoleInfo") + if sheet is None: + return None + + if 0 >= nIdx or 0 >= colNum: + return None + + return sheet.cell(row=nIdx,column=colNum).value + + + def AddImgInfo(self, TrgSheetName, EXIF_Date, EXIF_GPS, Path): + sheet = self.getSheet("ImgInfo") + if sheet is None: + return -1 + + # 마지막 행 찾기 + last_row = self.FindLastRow("ImgInfo") + if 0 > last_row: + return -1 + + TrgRow = last_row + 1 + + sheet.cell(row=TrgRow,column=1,value=TrgSheetName) + sheet.cell(row=TrgRow,column=2,value=EXIF_Date) + sheet.cell(row=TrgRow,column=3,value=EXIF_GPS) + sheet.cell(row=TrgRow,column=4,value=Path) + + return TrgRow + + + def FindRowCompValue(self, sheetNameTrg, nTrgCol, valueSrc, bCmpFile = False, pathBase = ""): + sheet = self.getSheet(sheetNameTrg) + if sheet is None: + return -1 + + value = valueSrc + if True == bCmpFile: + if False == os.path.isabs(valueSrc): + value = os.path.join(pathBase, valueSrc) + + retIdx = 0 + trgIdx = 0 + for row in sheet.iter_rows(min_row=2, values_only=True): + valueTrg = row[nTrgCol] + + if True == bCmpFile: + # 상대경로로 저장했지만 혹시 절대경로 일 수도 있으니... + if False == os.path.isabs(valueTrg): + valueTrg = os.path.join(pathBase, valueTrg) + + if True == os.path.samefile(value, valueTrg): + retIdx = trgIdx + 1 + break; + else: + if value == valueTrg: + retIdx = trgIdx + 1 + break; + + trgIdx += 1 + + return retIdx + + + def FindInsertedImgPath(self, pathBase, pathImg): + retIdx = self.FindRowCompValue("ImgInfo", 4, pathImg, True, pathBase ) + return retIdx + + # + def GetAllRowValue(self, SheetName, nRow, nColCount): + sheet = self.getSheet(SheetName) + if sheet is None: + return None + + if 0 >= nColCount: + return None + + retList = [] + # 엑셀의 컬럼은 1부터 시작하고, 입력받는 값은 컬럼의 개수다. 조심! + for nCol in range(1, nColCount + 1): + retList.append(sheet.cell(row=nRow,column=nCol).value) + + return retList + + + + + diff --git a/Pole_serial_sorter.py b/Pole_serial_sorter.py new file mode 100644 index 0000000..de88522 --- /dev/null +++ b/Pole_serial_sorter.py @@ -0,0 +1,108 @@ +import os +import shutil +import pytesseract +from PIL import Image +import re +from pytesseract import Output +import cv2 +import numpy as np + +# --- 사용자 설정 --- +# 원본 이미지 폴더 경로 +TOP_SOURCE_DIR = '/Volumes/ExSSD/Working/용공추 사진/' +SOURCE_DIR = '/Volumes/ExSSD/Working/용공추 사진/Raw_Data_4' + +# 결과 저장 폴더 +SERIAL_FOLDER = os.path.join(TOP_SOURCE_DIR, '일련번호_사진') +NON_SERIAL_FOLDER = os.path.join(TOP_SOURCE_DIR, '일반_사진') + +# 일련번호 정규표현식 (기본: 5~6자리 숫자) +#SERIAL_PATTERN = r'\b\d{5,6}\b' +SERIAL_PATTERN = r' ' + +def find_number_below_security_light_tesseract(data): + texts = data['text'] + tops = data['top'] + heights = data['height'] + + print(texts) + + # "보안등" 위치 찾기 + for i, text in enumerate(texts): + if '보안등' in text: + base_y = tops[i] + heights[i] # 아래 기준점 + + candidates = [] + for j, candidate_text in enumerate(texts): + if re.fullmatch(r'\d+', candidate_text): # 숫자인 경우 + if tops[j] > base_y + 5: # '보안등' 아래쪽에 있는지 확인 + candidates.append((tops[j], candidate_text)) + + if not candidates: + return None + + # y값이 가장 가까운(위에 있는) 숫자 반환 + candidates.sort(key=lambda x: x[0]) + return candidates[0][1] + + return None # "보안등"이 없으면 None + + +def extract_serial_number(text): + matches = re.findall(SERIAL_PATTERN, text) + return matches[0] if matches else None + + +def classify_and_extract(): + if not os.path.exists(SERIAL_FOLDER): + os.makedirs(SERIAL_FOLDER) + if not os.path.exists(NON_SERIAL_FOLDER): + os.makedirs(NON_SERIAL_FOLDER) + + for root, _, files in os.walk(SOURCE_DIR): + for file in files: + if file.lower().endswith(('.jpg', '.jpeg', '.png')): + img_path = os.path.join(root, file) + + try: + img = Image.open(img_path) + text = pytesseract.image_to_string(img, lang='eng+kor') # 한국어+영어 혼합 OCR + serial = extract_serial_number(text) + + if serial: + dest_path = os.path.join(SERIAL_FOLDER, file) + shutil.copy2(img_path, dest_path) + print(f"[✓] {file} → 일련번호: {serial}") + else: + dest_path = os.path.join(NON_SERIAL_FOLDER, file) + shutil.copy2(img_path, dest_path) + print(f"[ ] {file} → 일반 사진") + except Exception as e: + print(f"[!] 오류: {file} → {e}") + + +if __name__ == '__main__': +# classify_and_extract() + + #img_path = "/Volumes/ExSSD/Working/용공추 사진/2,3월 데이터/Pole/20250218_114838.jpg" + img_path = '/Volumes/ExSSD/Working/용공추 사진/2,3월 데이터/Pole/20250307_153821.jpg' + #img_path ="/Volumes/ExSSD/Working/용공추 사진/2,3월 데이터/Pole/20250303_121704.jpg" + + image = Image.open(img_path) + + + # image = cv2.imread(img_path, cv2.IMREAD_COLOR) + # gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + # _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) + # kernel = np.ones((1, 1), np.uint8) + # denoised = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) + + data = pytesseract.image_to_data(image, output_type=Output.DICT, lang='eng+kor') + + number = find_number_below_security_light_tesseract(data) + print("보안등 아래 숫자:", number) + + custom_config = r'--oem 3 --psm 6' + text = pytesseract.image_to_string(image, lang='kor+eng', config=custom_config) + + print(text) \ No newline at end of file diff --git a/StoreXLS.py b/StoreXLS.py new file mode 100644 index 0000000..5a28537 --- /dev/null +++ b/StoreXLS.py @@ -0,0 +1,186 @@ +import os +import openpyxl as Workbook + +from openpyxl.utils import get_column_letter +from openpyxl.drawing.image import Image + +import UtilPack as util + +from abc import ABC, abstractmethod + +class DBXLStorage(ABC): + xls_name = "EPoleDB.xlsx" + xls_infosheet = "DBInfo" + + m_wb = None + m_openedXLS = "" + m_defaultImgW = 140 + + def __init__(self, path): + self.path = path + + #def __enter__(self): + #self.DBXLSOpen(self.path) + + def __exit__(self, ex_type, ex_value, traceback): + self.DBXLSClose() + + def DBXLSOpen(self): + xls_path = self.GetXLSPath(self.path) + util.DbgOut(xls_path, True) + + try: + self.m_wb = Workbook.load_workbook(xls_path) + util.DbgOut("xls Open Successed") + except FileNotFoundError: + self.m_wb = Workbook.Workbook() + + ws = self.m_wb.active + ws.title = self.xls_infosheet #DBInfo + ws.cell(row=1,column=1,value="PoleDB_XLS") + + self.m_wb.save(xls_path) + util.DbgOut(f"{xls_path} Created", True) + + self.m_openedXLS = xls_path + + if self.m_wb is None: + util.DbgOut("XLS Open Something Wrong...", True) + self.m_openedXLS = "" + self.m_wb = None + return + + self.checkSheetsInfo() + + + def DBXLSClose(self): + if self.m_wb is None or self.m_openedXLS is None: + util.DbgOut("XLS Close something wrong...", True) + return + + self.m_wb.save(self.m_openedXLS) + self.m_wb.close() + util.DbgOut(f"Close : {self.m_openedXLS} Saved") + + self.m_wb = None + + + def GetCellValueStr(self, sheetName, nCol, nRow): + sheet = self.getSheet(sheetName); + if sheet is None: + return "" + + return sheet.cell(row=nRow, column=nCol).value.str + + + def SetCellValueStr(self, sheetName, nCol, nRow, ValueStr): + sheet = self.getSheet(sheetName); + if sheet is None: + return + + sheet.cell(row=nRow,column=nCol,value=ValueStr) + + + def SetCellValueImg(self, sheetName, nCol, nRow, ImagePath): + sheet = self.getSheet(sheetName); + if sheet is None: + return + + if False == os.path.exists(ImagePath): + return + + xlImg = Image(ImagePath) + nNewH = util.GetRSzHeight(xlImg.width, + xlImg.height, self.m_defaultImgW ) + xlImg.height = nNewH + xlImg.width = self.m_defaultImgW + + ColLetter = get_column_letter(nCol) + TrgCell = f"{ColLetter}{nRow}" + + cellW, cellH = util.GetCellSize(xlImg.width, xlImg.height) + + sheet.row_dimensions[nRow].height = cellH + sheet.column_dimensions[ColLetter].width = cellW + + sheet.add_image(xlImg, TrgCell) + + + def GetCellValue(self, sheetName, nCol, nRow): + sheet = self.getSheet(sheetName); + if sheet is None: + return "" + + return sheet.cell(nRow, nCol).value + + + # 시트를 가져온다. + # 엑셀 파일이 안 열려 있으면 None + def getSheet(self, sheetName, bNew = False): + retSheet = None + + if None == sheetName or "" == sheetName: + return None + + if self.m_wb is None: + util.DbgOut("XLS not opened", True) + return None + + try: + retSheet = self.m_wb[sheetName] + except KeyError: + if True == bNew: + retSheet = self.m_wb.create_sheet(title=sheetName) + util.DbgOut(f"GetSheet : {sheetName} is Created", True) + + return retSheet + + + # 데이터베이스용 엑셀 파일의 경로를 얻어온다. + # 폴더만 입력되었으면 파일이름을 삽입하고, 완전한 경로라면 패스 + def GetXLSPath(self, pathSrc): + retPath = self.path + + if os.path.isdir(pathSrc): + retPath = os.path.join(pathSrc, self.xls_name) + + return retPath + + def FindLastRow(self, sheetName): + sheet = self.getSheet(sheetName) + if sheet is None: + return -1 + + # 마지막 행 찾기 + last_row = sheet.max_row + + # 빈 셀이 있는지 확인하고, 실제로 값이 있는 마지막 행 찾기 + while last_row > 0: + row = sheet[last_row] # 현재 행을 가져옴 + + # 현재 행의 모든 셀을 검사하여 값이 있는지 확인 + is_empty = True + for cell in row: + if cell.value is not None: + is_empty = False + break + + # 값이 있는 행을 찾았으면 종료 + if not is_empty: + break + + # 행 번호를 하나씩 감소시켜 이전 행을 검사 + last_row -= 1 + + return last_row + + + @abstractmethod + def checkSheetsInfo(self): + pass + + + + + + diff --git a/UI.py b/UI.py new file mode 100644 index 0000000..caa7154 --- /dev/null +++ b/UI.py @@ -0,0 +1,246 @@ +import os +import sys +import UtilPack as util +import VWorldAPIs as vw +import ExtEXIFData as exif + +from PyQt5.QtCore import Qt, QUrl +from PyQt5.QtWebEngineWidgets import QWebEngineView +from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QPushButton, QVBoxLayout, QLineEdit, QHBoxLayout, QListWidget, QTableWidget, QRadioButton, QLabel, QFileDialog, QListWidgetItem +from PyQt5.QtGui import QPixmap, QTransform + +QApplication.setAttribute(Qt.AA_ShareOpenGLContexts) + +class MyApp(QMainWindow): + + def __init__(self): + super().__init__() + self.initUI() + self.loadINI() + + def closeEvent(self, event): + event.accept() + + def initUI(self): + layout = self.MakeUI() + + # 레이아웃을 윈도우에 적용 + central_widget = QWidget() + central_widget.setLayout(layout) + self.setCentralWidget(central_widget) + self.setWindowTitle('Poles Database') + self.move(100, 300) + self.show() + + def loadINI(self): + print("Load INI") + + def saveINI(self): + print("Save INI") + + def MakeUI_Left_Top(self): + label_SrcPath = QLabel("Source Path") + self.edit_SrcPath = QLineEdit("", self) + self.edit_SrcPath.setReadOnly(True) + btn_SrcPath_OK = QPushButton("...", self) + btn_SrcPath_OK.clicked.connect(self.on_click_SourcePath) + self.edit_SrcPath.setEnabled(False) + btn_SrcPath_OK.setEnabled(False) + + layout01 = QHBoxLayout() + layout01.addWidget(label_SrcPath) + layout01.addWidget(self.edit_SrcPath) + layout01.addWidget(btn_SrcPath_OK) + + label_ImgPath = QLabel("Image Path") + self.edit_ImgPath = QLineEdit("", self) + self.edit_ImgPath.setReadOnly(True) + btn_ImgPath_OK = QPushButton("...", self) + btn_ImgPath_OK.clicked.connect(self.on_click_ImagePath) + + layout02 = QHBoxLayout() + layout02.addWidget(label_ImgPath) + layout02.addWidget(self.edit_ImgPath) + layout02.addWidget(btn_ImgPath_OK) + + self.btn_ParseImages = QPushButton("Parse!!", self) + self.btn_ParseImages.clicked.connect(self.on_click_btn_ParseImages) + self.btn_ParseImages.setMaximumWidth(410) + + layout = QVBoxLayout() + layout.addLayout(layout01) + layout.addLayout(layout02) + layout.addWidget(self.btn_ParseImages) + + return layout + + def MakeUI_Left_Mid(self): + # 이미지 목록 + self.list_Images = QListWidget(self) + self.list_Images.itemSelectionChanged.connect(self.on_SelChanged_ImageList) + self.list_Images.setMinimumWidth(100) + self.list_Images.setMaximumWidth(200) + + # 이미지 정보 테이블 + self.list_Info = QListWidget(self) + self.list_Info.setMinimumWidth(100) + self.list_Info.setMaximumWidth(200) + + layout_r = QVBoxLayout() + layout_r.addWidget(self.list_Info) + + layout = QHBoxLayout() + layout.addWidget(self.list_Images) + layout.addLayout(layout_r) + + return layout + + def MakeUI_left_Down(self): + label_ExlPath = QLabel("DB(xlsx) Path") + self.edit_ExlPath = QLineEdit("", self) + self.edit_ExlPath.setReadOnly(True) + btn_ExlPath_OK = QPushButton("OK", self) + layout01 = QHBoxLayout() + layout01.addWidget(label_ExlPath) + layout01.addWidget(self.edit_ExlPath) + layout01.addWidget(btn_ExlPath_OK) + + btn_Exl_Export = QPushButton("Export to EXCEL", self) + btn_Exl_Export.clicked.connect(self.on_click_btn_ExportToExcel) + + layout = QVBoxLayout() + layout.addLayout(layout01) + layout.addWidget(btn_Exl_Export) + + return layout + + def MakeUI_Left(self): + lsyout_lefttop = self.MakeUI_Left_Top() + layout_leftmid = self.MakeUI_Left_Mid() + layout_leftDwn = self.MakeUI_left_Down() + + layout = QVBoxLayout() + layout.addLayout(lsyout_lefttop) + layout.addLayout(layout_leftmid) + layout.addLayout(layout_leftDwn) + + return layout + + def MakeUI(self): + layout_L = self.MakeUI_Left() + + # show Image in middle Layout + self.label_Image = QLabel(self) + + # Load Default Image + pixmap = QPixmap("layoutImg.jpeg") + scaled_Pix = pixmap.scaledToWidth(450) + self.label_Image.setPixmap(scaled_Pix) + self.label_Image.setMaximumWidth(450) + + # 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")) + + # 레이아웃 설정 + layout = QHBoxLayout() + layout.addLayout(layout_L) + layout.addWidget(self.label_Image) + layout.addWidget(self.webview_map) + + return layout + + def on_click_SourcePath(self): + folder_path = QFileDialog.getExistingDirectory(self, '폴더 선택', '') + self.edit_SrcPath.setText(folder_path) + + def on_click_ImagePath(self): + folder_path = QFileDialog.getExistingDirectory(self, '폴더 선택', '') + self.edit_ImgPath.setText(folder_path) + + def on_click_btn_ParseImages(self): + self.btn_ParseImages.setEnabled(False) + pathImage= self.edit_ImgPath.text() + + trgDirList = util.GetSubDirectories(pathImage) + + retImgList = [] + for dirName in trgDirList: + if dirName.lower() in "thumb": + continue + + imgSubDirPath = os.path.join(pathImage, dirName) + contents = os.listdir(imgSubDirPath) + + for item in contents: + name, ext = os.path.splitext(item) + if ext.lower() == '.jpg': + imgPath = os.path.join(imgSubDirPath, item) + retImgList.append(imgPath) + + for img in retImgList: + item = QListWidgetItem(util.GetParentDirName(img, 0)) + listData = [] + listData.append(img) + + all_date, lat, lon = exif.GetDateInfo(img) + listData.append(str(all_date)) + listData.append(str(lat)) + listData.append(str(lon)) + + juso, area = vw.GetJusofromGPS(lat, lon) + listData.append(area) + listData.append(juso) + + listData.append(util.GetParentDirName(img, 0)) + listData.append(util.GetParentDirName(img, 1)) + item.setData(Qt.UserRole, listData) + self.list_Images.addItem(item) + + self.btn_ParseImages.setEnabled(True) + print("Parse END!") + + def on_click_btn_ExportToExcel(self): + print("Excel!!") + + def on_SelChanged_ImageList(self): + items = self.list_Images.selectedItems() + for item in items: + listData = item.data(Qt.UserRole) + + self.list_Info.clear() + for content in listData: + row = QListWidgetItem(content) + self.list_Info.addItem(row) + + pixmap = QPixmap(listData[0]) + + szLabel = self.label_Image.size() + + + resized_pixmap = pixmap.scaled(100, 100) + rotated_pixmap = resized_pixmap.transformed(QTransform().rotate(90)) + + self.label_Image.setPixmap(pixmap) + + + # def open_image(self): + # # 이미지 파일 열기 다이얼로그 + # file_name, _ = QFileDialog.getOpenFileName(self, "Open Image File", "", "Images (*.png *.xpm *.jpg)") + + # if file_name: + # # QPixmap을 사용해 이미지를 QLabel에 설정 + # pixmap = QPixmap(file_name) + # self.label.setPixmap(pixmap) + # self.label.setScaledContents(True) + +if __name__ == '__main__': + app = QApplication(sys.argv) + + app.setQuitOnLastWindowClosed(True) + main_window = MyApp() + #main_window.show() + + sys.exit(app.exec_()) + diff --git a/UtilPack.py b/UtilPack.py new file mode 100644 index 0000000..db4e9f2 --- /dev/null +++ b/UtilPack.py @@ -0,0 +1,298 @@ +import os +import re +import math +import time +import rarfile +import zipfile +import shutil +import difflib +import subprocess +import UtilPack as util + +m_dbgLevel = 0 +listDbgStr = [] + +# +def IsEmptyStr(string): + return 0 == len(string.strip()) + +# +def GetCurrentTime(): + # 현재 시간을 구하고 구조체로 변환 + 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 + +#for debug +def DbgOut(strInput, bPrint = False): + strMsg = (f"{GetCurrentTime()} : {strInput}") + listDbgStr.append(strMsg) + + if True == bPrint: + print(strMsg) + +def printDbgMessages(): + for line in listDbgStr: + print(line) + +# 입력된 패스에서 부모 폴더를 찾는다. 0 은 자기자신 1은 부모, 숫자대로 위로. +def GetParentDirName(FullPath, nUp): + 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] + +# os 모듈을 사용하여 자식 폴더를 반환 +def GetSubDirectories(folder_path): + subdirectories = [ + d for d in os.listdir(folder_path) + if os.path.isdir(os.path.join(folder_path, d))] + + return subdirectories + +# 입력된 경로의 자식 폴더를 찾아 반환한다. +# 반환하는 리스트는 리커시브 - 손자, 증손자 폴더까지 전부 포함한다 +def GetAllSubDirectories(root_dir): + subdirectories = [] + + # 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 ListFileExtRcr(pathTrg, strExt): + listRet= [] + + # pathTrg의 하위 디렉토리 및 파일 목록을 얻음 + for dirpath, dirnames, filenames in os.walk(pathTrg): + for file in filenames: + extTmp = GetExtStr(file, False) + if extTmp.lower() == strExt and file.startswith('.'): + listRet.append(os.path.join(dirpath, file)) + + return listRet + +# 입력된 경로가 자식 폴더를 가지고 있는지 판단한다.- 최종 폴더인지 여부 +# 자식이 없으면 True, 자식이 있으면 False +def IsFinalFolder(path): + bRet = True + + contents = os.listdir(path) + for item in contents: + if True == os.path.isdir(item): + bRet = False + break + + return bRet; + +# 어떤 경로 안에서 특정 확장자의 파일을 뽑아내어 그 리스트를 반환한다. +def FindFileFromExt(path, ext): + bDot = False + if 0 <= ext.find('.'): + bDot = True + + listRet = [] + 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, bDot = True): + 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, subString): + # 문자열에서 부분 문자열의 인덱스를 찾습니다. + strIdx = mainString.find(subString) + if strIdx == -1: # 부분 문자열이 존재하지 않으면 그대로 반환합니다. + return mainString + + endIdx = strIdx + len(subString) + + # 부분 문자열을 제거하고 새로운 문자열을 반환합니다. + return mainString[:strIdx] + mainString[endIdx:] + +def ExtractZIP(zip_file, extract_to): + with zipfile.ZipFile(zip_file, 'r') as zf: + zf.extractall(extract_to) + +# +def CreateZIP(output_zip, *files): + 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, *files, bRmvRPath = True): + command = "zip " + + if True == bRmvRPath: + command += "-j " + + command += f"\"{zipName}\" " + + # 이중 리스트인 이유를 모르겠다. + for file in files: + strTemp = "" + if isinstance(file, list): + strTemp = ' '.join(file) + else: + strTemp = f"\"{file}\" " + + command += strTemp + + # for item in file: + # command += f"\"{item}\" " + result = subprocess.run(command, shell=True, + capture_output=True, text=True) + + bRet = False + if 0 == result.returncode: + bRet = True + + return bRet + +# 특정 확장자만 쉘을 이용해서 압축한다 +def CreateZIPShExt(zipName, TrgExt): + 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 + +# JSON 을 트리 구조로 출력한다. +def PrintJSONTree(data, indent=0): + 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)) + +# 세로 크기를 가로 비율대로 줄여서 반환 (가로를 기준으로 세로 길이) +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 + + + + + \ No newline at end of file diff --git a/UtilPack2.py b/UtilPack2.py new file mode 100644 index 0000000..1dfa78b --- /dev/null +++ b/UtilPack2.py @@ -0,0 +1,346 @@ +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] = [] + + +# +def IsEmptyStr(string: str) -> bool: + temp = f"{string}" + return 0 == len(temp.strip()) + + +# +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 + + +#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 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)) + +# +def IsPathWithin(base_path: str, target_path: str) -> bool: + base = Path(base_path).resolve() + target = Path(target_path).resolve() + return target.is_relative_to(base) + +# 랜덤 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: 파일이 정상인지 여부 +def VerifyIsValidFile(strPath: str, strCompHash: str, strMethod: str="sha256")->bool: + Hash_Calcd = CalculateFileHash(strPath, strMethod) + + return strCompHash.lower() == Hash_Calcd.lower() + +""" +# 사용 예시 +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("파일이 손상되었거나 다릅니다!") +""" \ No newline at end of file diff --git a/VWorldAPIs.py b/VWorldAPIs.py new file mode 100644 index 0000000..feef9d8 --- /dev/null +++ b/VWorldAPIs.py @@ -0,0 +1,77 @@ +import io +import requests +import math +import numpy as np +import json +import folium + +from bs4 import BeautifulSoup +from pyproj import Transformer + +#위도(latitude), 경도(longitude) +Earth_r = 20037508.34 # meter + +def GetJusofromGPS(lat,lon): + ret_juso = ["", "", "", ""] + + if lat <= 0.0 or lon <= 0.0: + return ret_juso + + apiurl = "https://api.vworld.kr/req/address?" + params = { + "service": "address", + "request": "getaddress", + "crs": "epsg:4326", + "point": f"{lon},{lat}", + "format": "json", + "type": "BOTH", + "key": "7E59C6EC-6BB0-3ED9-ACCC-4158869D7CFD" + } + + response = requests.get(apiurl, params=params) + + data = None + if response.status_code == 200: + data = response.json() + + print(data) + + if "OK" == data["response"]["status"]: + for item in data["response"]["result"]: + type = item["type"] + + if type.lower() == "parcel": + ret_juso[0] = item["text"] + ret_juso[1] = item["structure"]["level4L"] + elif type.lower() == "road": + ret_juso[2] = item["text"] + ret_juso[3] = item["structure"]["level4L"] + + else: + print( f"{data["response"]["status"] } : {data["error"]["code"] }" ) + + return ret_juso + +# VWorld 좌표계 : EPSG:3857 +# GPS 좌표계 : EPSG:4326 +# 경도는 람다 (\lambda) 위도는 파이 (\phi) +def Transform4326to3857(srcLat, srcLon): + transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True) + + # 좌표 변환 실행 + x, y = transformer.transform(srcLon, srcLat) + + return y, x + + + + +#37.536514,126.977138 +#37.541047,126.990966 +#37.539324791666665,126.98787990833334 +#37.539325,126.987880 +#GetJusofromGPS(37.52356569972222,126.9683578) +#"14105383.450839", "3950184.1545913" +#37.36340831688752, 35.48510801650072 +#"3950184.1545913", "14105383.450839" + diff --git a/test.py b/test.py new file mode 100644 index 0000000..be934f6 --- /dev/null +++ b/test.py @@ -0,0 +1,89 @@ +import os +import shutil +import UtilPack as util +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" + +indexFile = ".index.maps" + +# 엑셀을 연다. +XLSPath = os.path.join(XlsDirPath, "EPoleDB.xlsx") +tempxls = myxl.PoleXLS(XLSPath) +tempxls.DBXLSOpen() + +TrgSheet = "ImgInfo" +TrgSheetLastCol = 8 +# index, sheet, date, level, relpath, area, juso, gps +nLastRow = tempxls.FindLastRow(TrgSheet) + +for nRow in range(1, nLastRow): + listValues = tempxls.GetAllRowValue(TrgSheet, nRow, TrgSheetLastCol ) + + if TrgSheetLastCol != len(listValues): + continue + + #print(listValues) + + level = listValues[3] + relpath = listValues[4] + area = listValues[5] + gps = [7] + + if None == area or True == util.IsEmptyStr(area): + continue + + pathSrcFull = os.path.join(ImgDirPath, relpath) + if False == os.path.exists(pathSrcFull): + continue + + tempPath = f"{area}_{relpath}" + pathTrgFull = os.path.join(ImgSortedPath, tempPath) + pathTrgDir = os.path.dirname(pathTrgFull) + + if False == os.path.exists(pathTrgDir): + os.makedirs(pathTrgDir) + + indexfilePath = os.path.join(pathTrgDir, indexFile) + + with open(indexfilePath, "w") as file: + file.write(f"{area} : {level} ") + + + print(f"{pathSrcFull} -> {pathTrgFull}") + shutil.copy(pathSrcFull, pathTrgFull) + + + + + + + +#a = "/Volumes/ExSSD/Working/Images" +#b = "/Volumes/ExSSD/Working/Images/a" + +#c = os.path.relpath(b, a) +#d = os.path.realpath(c) + +# 37.53245379972222,126.96456829972223 + +#juso, area = vwapi.GetJusofromGPS(37.533949916666664,126.99432473611111) + +#print(f"{area} : {juso}") + +#Pole : 37.54353059972222,126.9650134 +#Trg : 37.5439572,126.95750569972222 + +#srcLat = 37.54353059972222 +#srcLon = 126.9650134 + +#trgLat = 37.5439572 +#trgLon = 126.95750569972222 + +#dist = util.GetDistanceGPS(srcLat, srcLon, trgLat, trgLon) +#print(dist) +