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)
|
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_())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,59 +8,35 @@ 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):
|
pathList = ["A", "B", "Pole"]
|
||||||
PathCri = os.getcwd()
|
|
||||||
|
for folder in pathList:
|
||||||
PathRet = PathCri
|
fullPath = os.path.join(path, folder)
|
||||||
if True == os.path.isabs(PathTrg):
|
if False == os.path.isdir(fullPath):
|
||||||
PathRet = os.path.join(PathCri, PathTrg)
|
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):
|
||||||
@@ -70,27 +46,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):
|
||||||
@@ -107,11 +62,13 @@ def CollectImageData(xls, sheetTrg, listImg, pathImgDir):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
all_date, lat, lon = exEXIF.GetDateInfo(item)
|
all_date, lat, lon = exEXIF.GetDateInfo(item)
|
||||||
pathRel = os.path.relpath( item, pathImgDir )
|
pathRel = os.path.relpath( item, pathImgDir)
|
||||||
level = util.GetParentDirName(item, 1)
|
level = util.GetParentDirName(item, 1)
|
||||||
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)
|
||||||
|
|
||||||
@@ -173,8 +130,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])
|
||||||
@@ -264,40 +219,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()
|
|
||||||
@@ -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()):
|
|
||||||
item = self.tableWidget_Src.item(nRow, col)
|
|
||||||
if item:
|
|
||||||
item.setBackground(Qt.GlobalColor(color))
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
24
README.md
24
README.md
@@ -2,19 +2,19 @@
|
|||||||
|
|
||||||
과정
|
과정
|
||||||
1. 여러 명의 인원이 전신주 및 공중선 사진을 찍는다.
|
1. 여러 명의 인원이 전신주 및 공중선 사진을 찍는다.
|
||||||
2. 찍은 사진을 압축하여 네이버 메일을 통해 지정된 계정으로 이메일을 보낸다.
|
2. 찍은 사진을 압축하여 메일을 통해 지정된 계정으로 이메일을 보낸다.
|
||||||
3. 이메일을 통해 받은 파일을 어떤 한 폴더에 모아 저장한다.
|
3. 이메일을 통해 받은 파일을 어떤 한 폴더에 모아 저장한다.
|
||||||
4 스크립트 동작
|
4. 스크립트 동작
|
||||||
4.1 저장 폴더를 읽어 임시 폴더에 압축을 푼다. 이미 풀려 있으면 넘어간다.
|
<br>4.1. 저장 폴더를 읽어 임시 폴더에 압축을 푼다. 이미 풀려 있으면 넘어간다.
|
||||||
4.2 임시 폴더의 이미지를 읽어 엑셀 파일에 리스트업 한다.
|
<br>4.2. 임시 폴더의 이미지를 읽어 엑셀 파일에 리스트업 한다.
|
||||||
4.3 연번, 점검일자, 구역, 전신주번호, 현장사진, 등급부여. 비고
|
<br>4.3. 연번, 점검일자, 구역, 전신주번호, 현장사진, 등급부여. 비고
|
||||||
|
|
||||||
5. 생성된 엑셀 파일을 권한이 있는 사람이 열어 전신주의 등급을 분류한다.
|
5. 생성된 엑셀 파일을 권한이 있는 사람이 열어 전신주의 등급을 분류한다.
|
||||||
6. 스크립트 동작
|
6. 스크립트 동작
|
||||||
6.1 등급 분류를 완료한 엑셀 파일을 읽는다.
|
<br>6.1. 등급 분류를 완료한 엑셀 파일을 읽는다.
|
||||||
6.2 전신주 번호를 통해 각 전신주 사진을 지도에 매핑
|
<br>6.2. 전신주 번호를 통해 각 전신주 사진을 지도에 매핑
|
||||||
6.3 분류된 등급에 따라 다른 색깔을 칠한다.
|
<br>6.3. 분류된 등급에 따라 다른 색깔을 칠한다.
|
||||||
|
|
||||||
|
고민해야 할 점
|
||||||
고민해야 할 걸
|
- 전신주 번호를 GPS 좌표와 매핑시켜 제보 사진만으로도 전신주 번호를 검색할 수 있어햐 한다.
|
||||||
- 전신주 번호를 GPS 좌표와 매핑시켜 제보 사진만으로도 전신부 번호를 검색할 수 있어햐 한다.
|
|
||||||
- 한번의 실행으로 위 과정을 통합할 수 있는 UI
|
- 한번의 실행으로 위 과정을 통합할 수 있는 UI
|
||||||
130
UtilPack2.py
130
UtilPack2.py
@@ -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__":
|
||||||
@@ -343,4 +330,73 @@ if __name__ == "__main__":
|
|||||||
print("파일이 정상입니다!")
|
print("파일이 정상입니다!")
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
appnope==0.1.4
|
appnope==0.1.4
|
||||||
asgiref==3.8.1
|
asgiref==3.8.1
|
||||||
asttokens==2.4.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
|
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
|
Django==5.2.8
|
||||||
et-xmlfile==1.1.0
|
et-xmlfile==1.1.0
|
||||||
executing==2.0.1
|
executing==2.0.1
|
||||||
|
folium==0.20.0
|
||||||
|
idna==3.11
|
||||||
image==1.5.33
|
image==1.5.33
|
||||||
ipykernel==6.29.5
|
Jinja2==3.1.6
|
||||||
ipython==8.26.0
|
MarkupSafe==3.0.3
|
||||||
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
|
||||||
|
numpy==2.2.6
|
||||||
|
opencv-python==4.12.0.88
|
||||||
openpyxl==3.1.5
|
openpyxl==3.1.5
|
||||||
packaging==24.1
|
packaging==24.1
|
||||||
parso==0.8.4
|
parso==0.8.4
|
||||||
@@ -27,14 +33,23 @@ psutil==6.0.0
|
|||||||
ptyprocess==0.7.0
|
ptyprocess==0.7.0
|
||||||
pure_eval==0.2.3
|
pure_eval==0.2.3
|
||||||
Pygments==2.18.0
|
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
|
python-dateutil==2.9.0.post0
|
||||||
pyzmq==26.0.3
|
|
||||||
rarfile==4.2
|
rarfile==4.2
|
||||||
|
requests==2.32.5
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
spyder-kernels==2.5.2
|
soupsieve==2.8
|
||||||
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
|
||||||
|
typing_extensions==4.15.0
|
||||||
|
urllib3==2.5.0
|
||||||
wcwidth==0.2.13
|
wcwidth==0.2.13
|
||||||
wurlitzer==3.1.1
|
wurlitzer==3.1.1
|
||||||
|
xyzservices==2025.10.0
|
||||||
|
|||||||
Reference in New Issue
Block a user