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