Compare commits

..

2 Commits

Author SHA1 Message Date
787b0aaf4a .gitignore 적용 2025-11-15 16:00:39 +09:00
d79c10b975 일단 커밋. 오랫동안 커밋을 안해서 꼬였다.
리팩토리 중.
2025-11-15 15:59:49 +09:00
7 changed files with 523 additions and 285 deletions

192
.gitignore vendored Normal file
View File

@@ -0,0 +1,192 @@
# Created by https://www.toptal.com/developers/gitignore/api/python,linux
# Edit at https://www.toptal.com/developers/gitignore?templates=python,linux
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/python,linux

BIN
EPoleDB.xlsx Normal file

Binary file not shown.

View File

@@ -12,7 +12,6 @@ def main(argc, argv):
app.setQuitOnLastWindowClosed(True) app.setQuitOnLastWindowClosed(True)
main_window = MgrUtilityPoleUI.MyApp() main_window = MgrUtilityPoleUI.MyApp()
main_window.show() main_window.show()
sys.exit(app.exec_()) sys.exit(app.exec_())

View File

@@ -8,61 +8,38 @@ import VWorldAPIs as vwapi
import JusoMngr import JusoMngr
mCurPath = "" # 스크립트가 실행되는 경로 mCurPath = "" # 스크립트가 실행되는 경로
mArcPath = "Zips" # 압축파일이 있는 폴더, 하위를 훑는다.
mXlsPath = "" # 엑셀 파일이 있는 경로, 틀리면 안됨. mXlsPath = "" # 엑셀 파일이 있는 경로, 틀리면 안됨.
mImgPath = "/Volumes/ExSSD/Working/Images" # 이미지가 들어있는 폴더, 이 아래에 A,B,Pole 등이 있어야 한다.
# 분류작업을 위해 임시로 생성 # "/Volumes/ExSSD/Working/Images" # 이미지가 들어있는 폴더, 이 아래에 A,B,Pole 등이 있어야 한다.
mTempPath = "/Volumes/ExSSD/Working/TmpImgs" mImgPath = ""
#def main(argc, argv):
# if argc != 2:
# printUsage()
# return
# 기준 경로, 사용할 경로 def WorkingFolderCheck(path, xlsPath):
# 사용할 경로가 상대경로면 기준 경로를 이용해 계산, if False == os.path.exists(path):
# 사용할 경로가 절대경로만 경로가 유효한지 확인해서 그대로 반환, 아니라면 기준경로 반환 print(f"Error: Working Folder is not exist. {path}")
# 기준경로는 따로 입력되는게 없으면 실행 경로 return False
def GetAbsPath(PathCri, PathTrg):
if True == util.IsEmptyStr(PathCri):
PathCri = os.getcwd()
PathRet = PathCri pathList = ["A", "B", "Pole"]
if True == os.path.isabs(PathTrg):
PathRet = os.path.join(PathCri, PathTrg) 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: else:
PathRet = PathTrg mXlsPath = xlsPath
if False == os.path.exists(PathTrg): return True
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): def GetPoleInfos(xls, nRow):
if None == xls or False == isinstance(xls, myxl.PoleXLS): if None == xls or False == isinstance(xls, myxl.PoleXLS):
return [] return []
@@ -70,27 +47,6 @@ def GetPoleInfos(xls, nRow):
return retList 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): 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):
@@ -112,6 +68,8 @@ def CollectImageData(xls, sheetTrg, listImg, pathImgDir):
if level == "Thumb": if level == "Thumb":
continue continue
print(f"DBG : {nIdx}, CollectImageData : {item}, {pathImgDir}, {pathRel}")
# 0 : 지번, 1 : 구역, 2 : 도로명, 3 : 도로명 구역 # 0 : 지번, 1 : 구역, 2 : 도로명, 3 : 도로명 구역
juso = vwapi.GetJusofromGPS(lat, lon) juso = vwapi.GetJusofromGPS(lat, lon)
@@ -174,8 +132,6 @@ def CollectPoleInfoData(xls, TrgSheetName, DBSheetName, pathImgDir):
if listValues[7] == "" or listValues[7] == "0.0,0.0": if listValues[7] == "" or listValues[7] == "0.0,0.0":
continue continue
print( listValues )
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[7])
xls.SetCellValueStr(TrgSheetName, 3, nTrgRow, listValues[5]) xls.SetCellValueStr(TrgSheetName, 3, nTrgRow, listValues[5])
@@ -264,40 +220,44 @@ def CollectPoleData( xls, TrgSheetName, InfoSheetName, DBSheetName, pathImgDir )
return nTrgRow return nTrgRow
def main():
# 경로를 정리하여 절대 경로로 변환해서 저장한다.
CurPath = GetAbsPath(mCurPath, "")
ArcDirPath = GetAbsPath(CurPath, mArcPath)
XlsDirPath = GetAbsPath(CurPath, mXlsPath)
ImgDirPath = GetAbsPath(CurPath, mImgPath)
# 압축해제 def GetJPGFileLIst(pathImageDir):
listUNZip = ExtractArchives(ArcDirPath, ImgDirPath) trgDirList = util.GetSubDirectories(pathImageDir)
#print(f"DBG : GetJPGFileLIst : {pathImageDir}, {trgDirList}")
#input("Press Enter to continue...")
retImgList = []
for dirName in trgDirList:
if dirName.lower() == "thumb":
continue
imgSubDirPath = os.path.join(pathImageDir, 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 Process(PathImg, PathXLS):
# 이미지 파일 목록 작성 # 이미지 파일 목록 작성
trgImgList = GetJPGFileList(listUNZip, ImgDirPath) trgImgList = GetJPGFileLIst(PathImg)
# 엑셀을 연다. # 엑셀을 연다.
XLSPath = os.path.join(XlsDirPath, "EPoleDB.xlsx") XLSPath = os.path.join(PathXLS, "EPoleDB.xlsx")
tempxls = myxl.PoleXLS(XLSPath) tempxls = myxl.PoleXLS(XLSPath)
tempxls.DBXLSOpen() tempxls.DBXLSOpen()
# 이미지 정보를 전부 모아서 한 시트에 저장 # 이미지 정보를 전부 모아서 한 시트에 저장
CollectImageData(tempxls, "ImgInfo", trgImgList, ImgDirPath) CollectImageData(tempxls, "ImgInfo", trgImgList, PathImg)
# 모아 놓은 이미지 정보에서 점봇대 정보를 골라서 저장 # 모아 놓은 이미지 정보에서 점봇대 정보를 골라서 저장
CollectPoleInfoData(tempxls, "PoleInfo", "ImgInfo", ImgDirPath) CollectPoleInfoData(tempxls, "PoleInfo", "ImgInfo", PathImg)
# 점봇대 정보 시트를 바탕으로 제보받은 이미지를 판별, 계산하여 저장 # 점봇대 정보 시트를 바탕으로 제보받은 이미지를 판별, 계산하여 저장
CollectPoleData(tempxls, "Poles", "PoleInfo", "ImgInfo", ImgDirPath) CollectPoleData(tempxls, "Poles", "PoleInfo", "ImgInfo", PathImg)
tempxls.DBXLSClose() tempxls.DBXLSClose()
def printUsage():
print("Usage : python main.py <Image Folder Path> <Excel File Name>")
# For Main Loop
if __name__ == '__main__':
# argc = len(sys.argv)
# argv = sys.argv
# main(argc, argv)
main()

View File

@@ -1,14 +1,17 @@
import sys
import os import os
import shutil import sys
import UtilPack as util import UtilPack as util
import VWorldAPIs as vw
import ExtEXIFData as exif
import MgrUtilityPoleConverter as mgrConv
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
from PyQt5.QtCore import Qt, QSettings, QRect QApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
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): class MyApp(QMainWindow):
@@ -16,134 +19,10 @@ class MyApp(QMainWindow):
super().__init__() super().__init__()
self.initUI() self.initUI()
self.loadINI() self.loadINI()
self.initDB()
# def closeEvent(self, event):
def closeEvent(self, a0: 'QCloseEvent | None'): event.accept()
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): def initUI(self):
layout = self.MakeUI() layout = self.MakeUI()
@@ -151,17 +30,178 @@ class MyApp(QMainWindow):
central_widget = QWidget() central_widget = QWidget()
central_widget.setLayout(layout) central_widget.setLayout(layout)
self.setCentralWidget(central_widget) self.setCentralWidget(central_widget)
self.setWindowTitle('Manga Database') self.setWindowTitle('Poles Database')
self.resize(800, 600) self.move(100, 300)
self.show() self.show()
# 테이블의 특정 행에 배경색을 설정한다 def loadINI(self):
# nRow: 배경색을 설정할 행 번호, color: 배경색 (Qt.GlobalColor) print("Load INI")
def SrcTableRowBgColor(self, nRow:int, color:Qt.GlobalColor) -> None:
for col in range(self.tableWidget_Src.columnCount()): def saveINI(self):
item = self.tableWidget_Src.item(nRow, col) print("Save INI")
if item:
item.setBackground(Qt.GlobalColor(color)) 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()
mgrConv.Process(pathImage,"/home/gerd/Workspace/Python/UtilityPole_Info" )
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)

View File

@@ -14,11 +14,27 @@ from pathlib import Path
m_dbgLevel = 0 m_dbgLevel = 0
listDbgStr: list[str] = [] 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 IsEmptyStr(string: str) -> bool: def printDbgMessages():
temp = f"{string}" for line in listDbgStr:
return 0 == len(temp.strip()) 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)
# #
@@ -39,30 +55,10 @@ def GetCurrentTime() -> str:
return strRet 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(): def IsEmptyStr(string: str) -> bool:
for line in listDbgStr: temp = f"{string}"
print(line) return 0 == len(temp.strip())
#
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)
# 입력된 경로의 자식 폴더를 찾아 반환한다. # 입력된 경로의 자식 폴더를 찾아 반환한다.
# 반환하는 리스트는 리커시브 - 손자, 증손자 폴더까지 전부 포함한다 # 반환하는 리스트는 리커시브 - 손자, 증손자 폴더까지 전부 포함한다
@@ -148,6 +144,7 @@ def IsFinalFolder(path : str) -> bool:
return bRet; return bRet;
# 어떤 경로 안에서 특정 확장자의 파일을 뽑아내어 그 리스트를 반환한다. # 어떤 경로 안에서 특정 확장자의 파일을 뽑아내어 그 리스트를 반환한다.
def FindFileFromExt(path: str, ext: str)-> list[str]: def FindFileFromExt(path: str, ext: str)-> list[str]:
bDot = False bDot = False
@@ -281,11 +278,6 @@ def PrintJSONTree(data, indent: int=0 ) -> None:
else: else:
print(' ' * indent + str(data)) 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 생성 # 랜덤 UUID 생성
def UUIDGenRandom(): def UUIDGenRandom():
@@ -327,11 +319,6 @@ def CalculateFileHash(strFilePath: str, method: str="sha256")-> str:
# method (str): 사용할 해시 알고리즘 # method (str): 사용할 해시 알고리즘
#Returns: #Returns:
# bool: 파일이 정상인지 여부 # 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__": if __name__ == "__main__":
@@ -344,3 +331,72 @@ if __name__ == "__main__":
else: else:
print("파일이 손상되었거나 다릅니다!") 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

@@ -5,15 +5,9 @@ cloudpickle==3.0.0
comm==0.2.2 comm==0.2.2
debugpy==1.8.2 debugpy==1.8.2
decorator==5.1.1 decorator==5.1.1
Django==5.0.7
et-xmlfile==1.1.0 et-xmlfile==1.1.0
executing==2.0.1 executing==2.0.1
image==1.5.33 image==1.5.33
ipykernel==6.29.5
ipython==8.26.0
jedi==0.19.1
jupyter_client==8.6.2
jupyter_core==5.7.2
matplotlib-inline==0.1.7 matplotlib-inline==0.1.7
nest-asyncio==1.6.0 nest-asyncio==1.6.0
openpyxl==3.1.5 openpyxl==3.1.5
@@ -28,13 +22,10 @@ ptyprocess==0.7.0
pure_eval==0.2.3 pure_eval==0.2.3
Pygments==2.18.0 Pygments==2.18.0
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
pyzmq==26.0.3
rarfile==4.2 rarfile==4.2
six==1.16.0 six==1.16.0
spyder-kernels==2.5.2
sqlparse==0.5.1 sqlparse==0.5.1
stack-data==0.6.3 stack-data==0.6.3
tornado==6.4.1
traitlets==5.14.3 traitlets==5.14.3
wcwidth==0.2.13 wcwidth==0.2.13
wurlitzer==3.1.1 wurlitzer==3.1.1