Compare commits
7 Commits
5a47b792d6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f662e4fd1b | |||
| c62cc8dcd8 | |||
| 88323dc82d | |||
| a6c1d1f111 | |||
| 00f9bf4ee7 | |||
| 787b0aaf4a | |||
| d79c10b975 |
192
.gitignore
vendored
Normal file
192
.gitignore
vendored
Normal 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
BIN
EPoleDB.xlsx
Normal file
Binary file not shown.
1
Main.py
1
Main.py
@@ -12,7 +12,6 @@ def main(argc, argv):
|
||||
app.setQuitOnLastWindowClosed(True)
|
||||
main_window = MgrUtilityPoleUI.MyApp()
|
||||
main_window.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
|
||||
@@ -8,58 +8,34 @@ import VWorldAPIs as vwapi
|
||||
import JusoMngr
|
||||
|
||||
mCurPath = "" # 스크립트가 실행되는 경로
|
||||
mArcPath = "Zips" # 압축파일이 있는 폴더, 하위를 훑는다.
|
||||
mXlsPath = "" # 엑셀 파일이 있는 경로, 틀리면 안됨.
|
||||
mImgPath = "/Volumes/ExSSD/Working/Images" # 이미지가 들어있는 폴더, 이 아래에 A,B,Pole 등이 있어야 한다.
|
||||
|
||||
# 분류작업을 위해 임시로 생성
|
||||
mTempPath = "/Volumes/ExSSD/Working/TmpImgs"
|
||||
# "/Volumes/ExSSD/Working/Images" # 이미지가 들어있는 폴더, 이 아래에 A,B,Pole 등이 있어야 한다.
|
||||
mImgPath = ""
|
||||
|
||||
#def main(argc, argv):
|
||||
# if argc != 2:
|
||||
# printUsage()
|
||||
# return
|
||||
|
||||
# 기준 경로, 사용할 경로
|
||||
# 사용할 경로가 상대경로면 기준 경로를 이용해 계산,
|
||||
# 사용할 경로가 절대경로만 경로가 유효한지 확인해서 그대로 반환, 아니라면 기준경로 반환
|
||||
# 기준경로는 따로 입력되는게 없으면 실행 경로
|
||||
def GetAbsPath(PathCri, PathTrg):
|
||||
if True == util.IsEmptyStr(PathCri):
|
||||
PathCri = os.getcwd()
|
||||
def WorkingFolderCheck(path, xlsPath):
|
||||
if False == os.path.exists(path):
|
||||
print(f"Error: Working Folder is not exist. {path}")
|
||||
return False
|
||||
|
||||
PathRet = PathCri
|
||||
if True == os.path.isabs(PathTrg):
|
||||
PathRet = os.path.join(PathCri, PathTrg)
|
||||
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:
|
||||
PathRet = PathTrg
|
||||
mXlsPath = xlsPath
|
||||
|
||||
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
|
||||
return True
|
||||
|
||||
|
||||
def GetPoleInfos(xls, nRow):
|
||||
@@ -70,27 +46,6 @@ def GetPoleInfos(xls, nRow):
|
||||
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):
|
||||
@@ -107,11 +62,13 @@ def CollectImageData(xls, sheetTrg, listImg, pathImgDir):
|
||||
continue
|
||||
|
||||
all_date, lat, lon = exEXIF.GetDateInfo(item)
|
||||
pathRel = os.path.relpath( item, pathImgDir )
|
||||
pathRel = os.path.relpath( item, pathImgDir)
|
||||
level = util.GetParentDirName(item, 1)
|
||||
if level == "Thumb":
|
||||
continue
|
||||
|
||||
print(f"DBG : {nIdx}, CollectImageData : {item}, {pathImgDir}, {pathRel}")
|
||||
|
||||
# 0 : 지번, 1 : 구역, 2 : 도로명, 3 : 도로명 구역
|
||||
juso = vwapi.GetJusofromGPS(lat, lon)
|
||||
|
||||
@@ -174,8 +131,6 @@ def CollectPoleInfoData(xls, TrgSheetName, DBSheetName, pathImgDir):
|
||||
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])
|
||||
@@ -264,40 +219,44 @@ def CollectPoleData( xls, TrgSheetName, InfoSheetName, DBSheetName, pathImgDir )
|
||||
|
||||
return nTrgRow
|
||||
|
||||
def main():
|
||||
# 경로를 정리하여 절대 경로로 변환해서 저장한다.
|
||||
CurPath = GetAbsPath(mCurPath, "")
|
||||
ArcDirPath = GetAbsPath(CurPath, mArcPath)
|
||||
XlsDirPath = GetAbsPath(CurPath, mXlsPath)
|
||||
ImgDirPath = GetAbsPath(CurPath, mImgPath)
|
||||
|
||||
# 압축해제
|
||||
listUNZip = ExtractArchives(ArcDirPath, ImgDirPath)
|
||||
def GetJPGFileLIst(pathImageDir):
|
||||
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.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()
|
||||
|
||||
|
||||
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()
|
||||
@@ -1,14 +1,17 @@
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
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
|
||||
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
|
||||
QApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
|
||||
|
||||
class MyApp(QMainWindow):
|
||||
|
||||
@@ -16,134 +19,10 @@ class MyApp(QMainWindow):
|
||||
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 closeEvent(self, event):
|
||||
event.accept()
|
||||
|
||||
#
|
||||
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()
|
||||
|
||||
@@ -151,17 +30,178 @@ class MyApp(QMainWindow):
|
||||
central_widget = QWidget()
|
||||
central_widget.setLayout(layout)
|
||||
self.setCentralWidget(central_widget)
|
||||
self.setWindowTitle('Manga Database')
|
||||
self.resize(800, 600)
|
||||
self.setWindowTitle('Poles Database')
|
||||
self.move(100, 300)
|
||||
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))
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
22
README.md
22
README.md
@@ -2,19 +2,19 @@
|
||||
|
||||
과정
|
||||
1. 여러 명의 인원이 전신주 및 공중선 사진을 찍는다.
|
||||
2. 찍은 사진을 압축하여 네이버 메일을 통해 지정된 계정으로 이메일을 보낸다.
|
||||
2. 찍은 사진을 압축하여 메일을 통해 지정된 계정으로 이메일을 보낸다.
|
||||
3. 이메일을 통해 받은 파일을 어떤 한 폴더에 모아 저장한다.
|
||||
4 스크립트 동작
|
||||
4.1 저장 폴더를 읽어 임시 폴더에 압축을 푼다. 이미 풀려 있으면 넘어간다.
|
||||
4.2 임시 폴더의 이미지를 읽어 엑셀 파일에 리스트업 한다.
|
||||
4.3 연번, 점검일자, 구역, 전신주번호, 현장사진, 등급부여. 비고
|
||||
4. 스크립트 동작
|
||||
<br>4.1. 저장 폴더를 읽어 임시 폴더에 압축을 푼다. 이미 풀려 있으면 넘어간다.
|
||||
<br>4.2. 임시 폴더의 이미지를 읽어 엑셀 파일에 리스트업 한다.
|
||||
<br>4.3. 연번, 점검일자, 구역, 전신주번호, 현장사진, 등급부여. 비고
|
||||
|
||||
5. 생성된 엑셀 파일을 권한이 있는 사람이 열어 전신주의 등급을 분류한다.
|
||||
6. 스크립트 동작
|
||||
6.1 등급 분류를 완료한 엑셀 파일을 읽는다.
|
||||
6.2 전신주 번호를 통해 각 전신주 사진을 지도에 매핑
|
||||
6.3 분류된 등급에 따라 다른 색깔을 칠한다.
|
||||
<br>6.1. 등급 분류를 완료한 엑셀 파일을 읽는다.
|
||||
<br>6.2. 전신주 번호를 통해 각 전신주 사진을 지도에 매핑
|
||||
<br>6.3. 분류된 등급에 따라 다른 색깔을 칠한다.
|
||||
|
||||
|
||||
고민해야 할 걸
|
||||
- 전신주 번호를 GPS 좌표와 매핑시켜 제보 사진만으로도 전신부 번호를 검색할 수 있어햐 한다.
|
||||
고민해야 할 점
|
||||
- 전신주 번호를 GPS 좌표와 매핑시켜 제보 사진만으로도 전신주 번호를 검색할 수 있어햐 한다.
|
||||
- 한번의 실행으로 위 과정을 통합할 수 있는 UI
|
||||
128
UtilPack2.py
128
UtilPack2.py
@@ -14,11 +14,27 @@ 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 IsEmptyStr(string: str) -> bool:
|
||||
temp = f"{string}"
|
||||
return 0 == len(temp.strip())
|
||||
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)
|
||||
|
||||
|
||||
#
|
||||
@@ -39,30 +55,10 @@ def GetCurrentTime() -> str:
|
||||
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 IsEmptyStr(string: str) -> bool:
|
||||
temp = f"{string}"
|
||||
return 0 == len(temp.strip())
|
||||
|
||||
# 입력된 경로의 자식 폴더를 찾아 반환한다.
|
||||
# 반환하는 리스트는 리커시브 - 손자, 증손자 폴더까지 전부 포함한다
|
||||
@@ -148,6 +144,7 @@ def IsFinalFolder(path : str) -> bool:
|
||||
|
||||
return bRet;
|
||||
|
||||
|
||||
# 어떤 경로 안에서 특정 확장자의 파일을 뽑아내어 그 리스트를 반환한다.
|
||||
def FindFileFromExt(path: str, ext: str)-> list[str]:
|
||||
bDot = False
|
||||
@@ -281,11 +278,6 @@ def PrintJSONTree(data, indent: int=0 ) -> None:
|
||||
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():
|
||||
@@ -327,11 +319,6 @@ def CalculateFileHash(strFilePath: str, method: str="sha256")-> 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__":
|
||||
@@ -344,3 +331,72 @@ if __name__ == "__main__":
|
||||
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
|
||||
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
appnope==0.1.4
|
||||
asgiref==3.8.1
|
||||
asttokens==2.4.1
|
||||
beautifulsoup4==4.14.2
|
||||
branca==0.8.2
|
||||
bs4==0.0.2
|
||||
certifi==2025.10.5
|
||||
charset-normalizer==3.4.4
|
||||
cloudpickle==3.0.0
|
||||
comm==0.2.2
|
||||
debugpy==1.8.2
|
||||
decorator==5.1.1
|
||||
Django==5.0.7
|
||||
Django==5.2.8
|
||||
et-xmlfile==1.1.0
|
||||
executing==2.0.1
|
||||
folium==0.20.0
|
||||
idna==3.11
|
||||
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
|
||||
Jinja2==3.1.6
|
||||
MarkupSafe==3.0.3
|
||||
matplotlib-inline==0.1.7
|
||||
nest-asyncio==1.6.0
|
||||
numpy==2.2.6
|
||||
opencv-python==4.12.0.88
|
||||
openpyxl==3.1.5
|
||||
packaging==24.1
|
||||
parso==0.8.4
|
||||
@@ -27,14 +33,23 @@ psutil==6.0.0
|
||||
ptyprocess==0.7.0
|
||||
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
|
||||
pyzmq==26.0.3
|
||||
rarfile==4.2
|
||||
requests==2.32.5
|
||||
six==1.16.0
|
||||
spyder-kernels==2.5.2
|
||||
soupsieve==2.8
|
||||
sqlparse==0.5.1
|
||||
stack-data==0.6.3
|
||||
tornado==6.4.1
|
||||
traitlets==5.14.3
|
||||
typing_extensions==4.15.0
|
||||
urllib3==2.5.0
|
||||
wcwidth==0.2.13
|
||||
wurlitzer==3.1.1
|
||||
xyzservices==2025.10.0
|
||||
|
||||
Reference in New Issue
Block a user