async def eval_p_js_co(page: QtWebEngineWidgets.QWebEnginePage, js: str): fut = asyncio.get_event_loop().create_future() def ready(r): asyncio.get_event_loop().call_soon_threadsafe(fut.set_result, r) page.runJavaScript(js, ready) return await fut
def run_js_code(page: QWebEnginePage, code: str) -> object: loop = QEventLoop() result_value = {'value': None} def _on_callback(result: object): result_value['value'] = result loop.quit() page.runJavaScript(code, _on_callback) loop.exec() return result_value['value']
def runJavaScript(self, script, worldId=-1, callback=None): """ Public method to run a script in the context of the page. @param script JavaScript script source to be executed @type str @param worldId ID to run the script under @type int @param callback callback function to be executed when the script has ended @type function """ if worldId > -1: if callback is None: QWebEnginePage.runJavaScript(self, script, worldId) else: QWebEnginePage.runJavaScript(self, script, worldId, callback) else: if callback is None: QWebEnginePage.runJavaScript(self, script) else: QWebEnginePage.runJavaScript(self, script, callback)
def test_qt(self): from PyQt5.QtCore import QTimer from PyQt5.QtWidgets import QApplication from PyQt5.QtWebEngineWidgets import QWebEnginePage from PyQt5.QtGui import QImageReader, QFontDatabase from PyQt5.QtNetwork import QNetworkAccessManager from calibre.utils.img import image_from_data, image_to_data, test # Ensure that images can be read before QApplication is constructed. # Note that this requires QCoreApplication.libraryPaths() to return the # path to the Qt plugins which it always does in the frozen build, # because Qt is patched to know the layout of the calibre application # package. On non-frozen builds, it should just work because the # hard-coded paths of the Qt installation should work. If they do not, # then it is a distro problem. fmts = set(map(lambda x: x.data().decode('utf-8'), QImageReader.supportedImageFormats())) # no2to3 testf = {'jpg', 'png', 'svg', 'ico', 'gif'} self.assertEqual(testf.intersection(fmts), testf, "Qt doesn't seem to be able to load some of its image plugins. Available plugins: %s" % fmts) data = P('images/blank.png', allow_user_override=False, data=True) img = image_from_data(data) image_from_data(P('catalog/mastheadImage.gif', allow_user_override=False, data=True)) for fmt in 'png bmp jpeg'.split(): d = image_to_data(img, fmt=fmt) image_from_data(d) # Run the imaging tests test() from calibre.gui2 import ensure_app, destroy_app display_env_var = os.environ.pop('DISPLAY', None) try: ensure_app() self.assertGreaterEqual(len(QFontDatabase().families()), 5, 'The QPA headless plugin is not able to locate enough system fonts via fontconfig') from calibre.ebooks.covers import create_cover create_cover('xxx', ['yyy']) na = QNetworkAccessManager() self.assertTrue(hasattr(na, 'sslErrors'), 'Qt not compiled with openssl') if iswindows: from PyQt5.Qt import QtWin QtWin p = QWebEnginePage() def callback(result): callback.result = result if hasattr(print_callback, 'result'): QApplication.instance().quit() def print_callback(result): print_callback.result = result if hasattr(callback, 'result'): QApplication.instance().quit() p.runJavaScript('1 + 1', callback) p.printToPdf(print_callback) QTimer.singleShot(5000, lambda: QApplication.instance().quit()) QApplication.instance().exec_() test_flaky = ismacos and not is_ci if not test_flaky: self.assertEqual(callback.result, 2, 'Simple JS computation failed') self.assertIn(b'Skia/PDF', bytes(print_callback.result), 'Print to PDF failed') del p del na destroy_app() del QWebEnginePage finally: if display_env_var is not None: os.environ['DISPLAY'] = display_env_var
class TestForm(QMainWindow, Ui_MainWindow): #생성자 def __init__(self): super().__init__() self.init_my_location() self.setupUi(self) # 초기화 # self.url = "http://localhost:8080/umbrella" self.url = "http://localhost:8000/umbrella2.html" self.webEngineView.load(QUrl(self.url)) self.page = QWebEnginePage() self.page.setUrl(QUrl(self.url)) self.page.setView(self.webEngineView) chrome_option = Options() #headless 모드 chrome_option.add_argument("--headless") #사운드 뮤트 chrome_option.add_argument("--mute-audio") # webdriver 설정(chrome) --headless self.browser = webdriver.Chrome( chrome_options=chrome_option, executable_path="webdriver/Chrome/chromedriver.exe") self.browser.get(self.url) self.runner = Runner(self.page, self.browser) self.comboBox.addItem("키워드") self.comboBox.addItem("주소") # self.page.featurePermissionRequested.connect(self.setPagePermission) # self.pushButton.clicked.connect(self.map_removeMarkers) # self.pushButton.clicked.connect(lambda: self.map_setLevel(random.randrange(7))) #현재위치 #self.pushButton.clicked.connect(lambda: self.coord_to_address(37.62244036,127.072065, 0)) #거리 self.pushButton.clicked.connect( lambda: self.getDistance([33.450500, 126.569968], [[ 33.450500, 126.569968 ], [35.404195, 126.886323], [39.668777, 126.065913]])) self.pushButton.clicked.connect(lambda: self.runner.coord_to_address( self.my_location_lat, self.my_location_lng, random.randrange(0, 5)) ) # self.pushButton.clicked.connect(lambda: self.getDistance([33.450500,126.569968],[[33.450500,126.569968],[35.404195,126.886323],[39.668777,126.065913]])) # self.pushButton.clicked.connect(self.test_a) # self.pushButton.clicked.connect(self.search) self.lineEdit.returnPressed.connect(self.search) self.page.loadFinished.connect( lambda: self.setMap(self.my_location_lat, self.my_location_lng)) # self.setMap(self.my_location_lat, self.my_location_lng) #아이피로 현재 위치 받아오기(google api 사용) def init_my_location(self): url = 'https://www.googleapis.com/geolocation/v1/geolocate?key=AIzaSyDQKxbTt0MrFNH85kTJXzickMD5s88UVaI' data = { 'considerIp': True, } result = requests.post(url, data) my_location = json.loads(result.text) # print(my_location) # print("lat : ",my_location.get('location').get('lat')) # print("lon : ",my_location.get('location').get('lng')) self.my_location_lat = my_location.get('location').get('lat') self.my_location_lng = my_location.get('location').get('lng') def test_a(self): script = """ return centerX.val() """ centerX = self.run(script) print(centerX) #위치권한 요청이 왔을때 허용해줌 def setPagePermission(self, url, feature): self.page.setFeaturePermission(url, feature, QWebEnginePage.PermissionGrantedByUser) def search(self): search_text = self.lineEdit.text().strip() if self.comboBox.currentIndex() == 0: script = """ removeMarkers(); // 주소-좌표 변환 객체를 생성합니다 var geocoder = new kakao.maps.services.Geocoder(); // 장소 검색 객체를 생성합니다 var ps = new kakao.maps.services.Places(); // 키워드로 장소를 검색합니다 ps.keywordSearch('""" + search_text + """', placesSearchCB); function placesSearchCB (data, status, pagination) { if (status === kakao.maps.services.Status.OK) { // 검색된 장소 위치를 기준으로 지도 범위를 재설정하기위해 // LatLngBounds 객체에 좌표를 추가합니다 var bounds = new kakao.maps.LatLngBounds(); for (var i=0; i<data.length; i++) { displayMarker(data[i]); bounds.extend(new kakao.maps.LatLng(data[i].y, data[i].x)); } // 검색된 장소 위치를 기준으로 지도 범위를 재설정합니다 map.setBounds(bounds); } } """ elif self.comboBox.currentIndex(): script = """ removeMarkers(); // 주소-좌표 변환 객체를 생성합니다 var geocoder = new kakao.maps.services.Geocoder(); // 주소로 좌표를 검색합니다 geocoder.addressSearch('""" + search_text + """', function(result, status) { // 정상적으로 검색이 완료됐으면 if (status === kakao.maps.services.Status.OK) { var coords = new kakao.maps.LatLng(result[0].y, result[0].x); // 결과값으로 받은 위치를 마커로 표시합니다 var marker = new kakao.maps.Marker({ map: map, position: coords }); //*** 마커 담기 markerList.push(marker) // 인포윈도우로 장소에 대한 설명을 표시합니다 infowindow.open(map, marker); // 지도의 중심을 결과값으로 받은 위치로 이동시킵니다 map.setCenter(coords); } }); """ else: return self.run(script) #거리 계산하는 메소드 # center에는 기준좌표 [lat, lng] # pointList에는 측정할 좌표 리스트 [ [lat,lng] , [lat,lng] ....... ] #리턴값은 측정한 거리(int값) list ex) [ [0], [218667], [691542] ] #단위는 m def getDistance(self, center, pointList): # center가 None일 경우 # 기본값으로 '내위치'의 좌표 center = center or [self.my_location_lat, self.my_location_lng] script = """ var tmp_point_arr = """ + str(pointList) + """ var tmp_center = """ + str(center) + """ var tmp_div = $('#tmp_div'); var result_arr = new Array(); for(var i=0; i < tmp_point_arr.length; i++){ const polyline = new window.daum.maps.Polyline({ map : map, path : [ new window.daum.maps.LatLng(tmp_center[0], tmp_center[1]), new window.daum.maps.LatLng(tmp_point_arr[i][0], tmp_point_arr[i][1]) ], strokeWeight : 0 }); result_arr.push(polyline.getLength()); } return '['+result_arr.toString()+']'; """ result = list(map(int, eval(self.run(script)))) print(result) for i in result: print(f"거리 : {i}m, type : {type(i)}") return result #좌표로 주소 얻기 idx #0 : 지번주소 #1 : 지번주소 - 시도단위 #2 : 지번주소 - 구 단위 #3 : 지번주소 - 동 단위 #4 : 지번주소 - 우편번호(6자리) #없을경우 "" def coord_to_address(self, lat, lng, idx): if not idx in (0, 1, 2, 3, 4): idx = 0 result = "" print(lat) print(lng) script = """ go_py_result = '대기중' var coord = new kakao.maps.LatLng(""" + str(lat) + """, """ + str( lng) + """); var c2a = function(result, status) { tmp_div.append("result0 -"+result[0].address.address_name); //go_py_result = result[0].address.address_name; var idx = """ + str(idx) + """ if(idx === 0){ go_py_result = result[0].address.address_name; }else if(idx === 1){ go_py_result = result[0].address.region_1depth_name; }else if(idx === 2){ go_py_result = result[0].address.region_2depth_name; }else if(idx === 3){ go_py_result = result[0].address.region_3depth_name; }else if(idx === 4){ go_py_result = result[0].address.zip_code; }else{ go_py_result = result[0].address.address_name; } }; geocoder.coord2Address(coord.getLng(), coord.getLat(), c2a); """ script2 = "return go_py_result;" self.run(script) for i in range(50): result = self.run(script2) if result != "대기중": print("idx : ", idx, "c 2 a : ", result) return result break print("idx : ", idx, "c 2 a : ", result) return "" #지도 확대 레벨 설정 def map_setLevel(self, level): script = """ map.setLevel(""" + str(level) + """) """ self.run(script) #마커 다 지우는 메소드 def map_removeMarkers(self): script = """ removeMarkers(); """ self.run(script) #맵 이동 def setMap(self, lat, lng): script = """ var umbrella_location = new kakao.maps.LatLng(""" + str( lat) + """, """ + str(lng) + """); map.setCenter(umbrella_location); """ self.run(script) #스크립트 실행 def run(self, script): # print("run runJavaScript") self.page.runJavaScript(script) # print("run execute_Script") result = self.browser.execute_script(script) return result def marking(self, data): typeP = ["", "약국", "우체국", "농협"] for item in data: addr = item.get('addr') code = item.get('code') created_at = item.get('created_at') lat = item.get('lat') lng = item.get('lng') name = item.get('name') remain_stat = item.get('remain_stat') stock_at = item.get('stock_at') type = typeP[int(item.get('type'))] print( f"addr = {addr}\ncode = {code}\ncreated_at = {created_at}\nlat = {lat}\nlng = {lng}\nname = {name}\nremain_stat = {remain_stat}\nstock_at = {stock_at}\ntype = {type}" ) script = """ """ self.run(script)
class POSM(QMainWindow): def __init__(self): super().__init__() self.setLocale(QLocale(QLocale.English)) self.initUI() self.setAttribute(Qt.WA_AlwaysShowToolTips) sizegrip = QtWidgets.QSizeGrip(self) self.layout.addWidget(sizegrip, 0, QtCore.Qt.AlignBottom | QtCore.Qt.AlignRight) self.record = [None] recordAction = QAction(datetime.datetime.now().strftime('%H:%M:%S'), self) recordAction.triggered.connect(lambda: self.changeMap(0)) self.recordMenu.addAction(recordAction) def initUI(self): self.layout = QHBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.console = InformationalConsole(app) self.horSplitter = QSplitter(Qt.Horizontal) self.horSplitter.setChildrenCollapsible(False) self.editionSplitter = QSplitter(Qt.Vertical) self.editionSplitter.setChildrenCollapsible(False) self.queryUI = QueryUI() self.queryUI.setOnRequestChanged(self.changeCurrentMap) self.editionSplitter.addWidget(self.queryUI) self.queryWidget = QWidget() self.queryWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.queryWidget.setLayout(QVBoxLayout()) self.queryWidget.layout().setContentsMargins(0, 0, 0, 0) self.queryWidget.layout().setSpacing(0) self.queryHeader = QLabel("Query") self.queryHeader.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.queryHeader.setFixedHeight(self.queryHeader.sizeHint().height() + 10) self.queryHeader.setContentsMargins(5, 5, 0, 5) self.queryWidget.layout().addWidget(self.queryHeader) self.queryText = CodeEditor() self.qlHighlighter = OverpassQLHighlighter(self.queryText.document()) self.queryText.setReadOnly(True) self.queryWidget.layout().addWidget(self.queryText) self.editionSplitter.addWidget(self.queryWidget) self.horSplitter.addWidget(self.editionSplitter) self.emptyMapPage = QWebEnginePage() self.emptyMapPage.setHtml(EMPTY_HTML) self.manualModePage = QWebEnginePage() soup = bs4.BeautifulSoup(EMPTY_HTML, features="html.parser") js = soup.new_tag("script") js.string = (MANUAL_MODE_JS_SCRIPT % (str([]))) soup.append(js) self.manualModePage.setHtml(str(soup)) self.mapRenderer = QWebEngineView() self.mapRenderer.setMinimumWidth(500) self.mapRenderer.setPage(self.emptyMapPage) self.consoleSplitter = QSplitter(Qt.Vertical) self.consoleSplitter.setChildrenCollapsible(False) self.consoleSplitter.addWidget(self.mapRenderer) self.consoleWidget = QWidget() self.consoleWidget.setLayout(QVBoxLayout()) self.consoleWidget.layout().setContentsMargins(0, 0, 0, 0) self.consoleWidget.layout().setSpacing(0) self.consoleHeader = QLabel("Console") self.consoleHeader.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.consoleHeader.setContentsMargins(5, 5, 0, 5) self.consoleWidget.layout().addWidget(self.consoleHeader) self.consoleWidget.layout().addWidget(self.console) self.consoleSplitter.addWidget(self.consoleWidget) self.horSplitter.addWidget(self.consoleSplitter) self.layout.addWidget(self.horSplitter) self.initMenuBar() centralWidget = QWidget(self) centralWidget.setLayout(self.layout) self.setCentralWidget(centralWidget) self.setWindowTitle('Python Open Street Map') def initMenuBar(self): menubar = self.menuBar() fileMenu = menubar.addMenu('File') openAct = QAction('Open netedit', self) openAct.triggered.connect(self.openNet) fileMenu.addAction(openAct) saveMenu = fileMenu.addMenu("Save") saveOutputAct = QAction('output', self) saveOutputAct.triggered.connect(self.saveNet) saveOutputAct.setShortcut('Ctrl+S') saveMenu.addAction(saveOutputAct) saveQueryAct = QAction('query', self) saveQueryAct.triggered.connect(self.saveQuery) saveQueryAct.setShortcut('Ctrl+Shift+S') saveMenu.addAction(saveQueryAct) saveInteractiveModeAct = QAction('interactive mode', self) saveInteractiveModeAct.triggered.connect(self.saveInteractiveQuery) saveMenu.addAction(saveInteractiveModeAct) openMenu = fileMenu.addMenu("Open") openQuery = QAction('query', self) openQuery.triggered.connect(self.openQuery) openQuery.setShortcut('Ctrl+O') openMenu.addAction(openQuery) openInteractiveMode = QAction('interactive mode', self) openInteractiveMode.triggered.connect(self.openInteractiveQuery) openMenu.addAction(openInteractiveMode) self.recordMenu = openMenu.addMenu("record") runMenu = menubar.addMenu('Run') playAct = QAction('Play', self) playAct.triggered.connect(self.playQuery) playAct.setShortcut('Ctrl+P') runMenu.addAction(playAct) playTableRowAct = QAction('Play row selection', self) playTableRowAct.triggered.connect(self.playTableRow) playTableRowAct.setShortcut('Ctrl+T') runMenu.addAction(playTableRowAct) self.requestMenu = menubar.addMenu('Request') addRequestAct = QAction('Add request', self) addRequestAct.triggered.connect(lambda b: self.addRequest()) addRequestAct.setShortcut('Ctrl+A') self.requestMenu.addAction(addRequestAct) templatesMenu = self.requestMenu.addMenu("Add template") addRoadAct = QAction('Roads', self) addRoadAct.triggered.connect(lambda: self.addTemplate([ OverpassFilter("highway", TagComparison.EQUAL, "", False, True), OverpassFilter("name", TagComparison.EQUAL, "", False, True), OverpassFilter("ref", TagComparison.EQUAL, "", False, True), OverpassFilter("maxspeed", TagComparison.AT_MOST, "120", False, False), OverpassFilter("lanes", TagComparison.EQUAL, "", False, True), OverpassFilter("oneway", TagComparison.EQUAL, "", False, True) ])) templatesMenu.addAction(addRoadAct) addMainRoadAct = QAction('Main roads', self) mainHighways = "^(motorway|trunk|primary|secondary|residential)(_link)?$" everythingButYes = "^(y(e([^s]|$|s.)|[^e]|$)|[^y]|$).*" addMainRoadAct.triggered.connect(lambda: self.addTemplate([ OverpassFilter("highway", TagComparison.EQUAL, mainHighways, False, False), OverpassFilter("construction", TagComparison.HAS_NOT_KEY, "", False, False), OverpassFilter("noexit", TagComparison.EQUAL, "yes", True, True), OverpassFilter("access", TagComparison.EQUAL, everythingButYes, True, False) ])) templatesMenu.addAction(addMainRoadAct) addParkingAct = QAction('Parking', self) addParkingAct.triggered.connect(lambda: self.addTemplate([ OverpassFilter("service", TagComparison.EQUAL, "parking", False, False), OverpassFilter("highway", TagComparison.HAS_KEY, "", False, True) ])) templatesMenu.addAction(addParkingAct) addPedestriansAct = QAction('Pedestrians', self) pedestrianHighway = [ "pedestrian", "footway", "path", "cycleway", "bridleway", "steps", "crossing" ] addPedestriansAct.triggered.connect(lambda: self.addTemplate([ OverpassFilter("highway", TagComparison.IS_ONE_OF, pedestrianHighway, False, True) ])) templatesMenu.addAction(addPedestriansAct) removeRequestAct = QAction('Remove current request', self) removeRequestAct.triggered.connect(self.removeRequest) removeRequestAct.setShortcut('Ctrl+R') self.requestMenu.addAction(removeRequestAct) self.manualModeAct = QAction( 'Switch between interactive and manual mode', self) self.manualModeAct.triggered.connect(self.switchManualMode) self.requestMenu.addAction(self.manualModeAct) self.manualModeMenu = menubar.addMenu('Manual mode') self.manualModeMenu.setEnabled(False) manualModeCleanPolygonAct = QAction('Clean polygon', self) manualModeCleanPolygonAct.triggered.connect( self.cleanManualModePolygon) self.manualModeMenu.addAction(manualModeCleanPolygonAct) manualModeGetPolygonAct = QAction('Polygon coordinates', self) manualModeGetPolygonAct.triggered.connect( lambda: self.manualModePage.runJavaScript( "getPolygons();", self.logManualModePolygonCoords)) self.manualModeMenu.addAction(manualModeGetPolygonAct) windowsMenu = menubar.addMenu('Windows') cleanMapAct = QAction('Clean map', self) cleanMapAct.triggered.connect(self.cleanMap) windowsMenu.addAction(cleanMapAct) self.showHideInteractiveModeAct = QAction('Interactive mode', self) self.showHideInteractiveModeAct.triggered.connect( self.showHideInteractiveMode) windowsMenu.addAction(self.showHideInteractiveModeAct) showHideConsole = QAction('Console', self) showHideConsole.triggered.connect(self.showHideConsole) windowsMenu.addAction(showHideConsole) showHideQuery = QAction('Query', self) showHideQuery.triggered.connect(self.showHideQuery) windowsMenu.addAction(showHideQuery) # ACTIONS def cleanMap(self): if self.queryText.isReadOnly(): if self.queryUI.getCurrentMap() is not None: self.mapRenderer.setPage(self.queryUI.updateMaps(EMPTY_HTML)) else: soup = bs4.BeautifulSoup(EMPTY_HTML, features="html.parser") js = soup.new_tag("script") js.string = (MANUAL_MODE_JS_SCRIPT % (str([]))) soup.append(js) self.manualModePage.setHtml(str(soup)) logging.info("Cleaning map") def changeMap(self, i): if i == 0: if not self.queryText.isReadOnly(): self.switchManualMode() if self.queryText.isReadOnly(): self.queryUI.reset() self.queryText.clear() self.mapRenderer.setPage(self.emptyMapPage) self.queryUI.updateMaps(EMPTY_HTML) elif self.record[i]["interactiveMode"]: if not self.queryText.isReadOnly(): self.switchManualMode() if self.queryText.isReadOnly(): self.queryUI.setQuery(self.record[i]["query"]) self.queryText.setPlainText(self.record[i]["query"].getQL()) self.mapRenderer.setPage( self.queryUI.updateMaps(self.record[i]["html"])) else: if self.queryText.isReadOnly(): self.switchManualMode() if not self.queryText.isReadOnly(): self.queryUI.reset() self.queryText.setPlainText(self.record[i]["query"]) self.manualModePage.setHtml(self.record[i]["html"]) self.mapRenderer.setPage(self.manualModePage) def logManualModePolygonCoords(self, coords): coordsString = " ".join([str(c) for point in coords for c in point]) logging.info("Polygon coordinates:\"{}\"".format(coordsString)) pyperclip.copy(coordsString) logging.debug("LINE") def cleanManualModePolygon(self): logging.info("Cleaning polygon.") self.manualModePage.runJavaScript( "cleanPolygon();", lambda returnValue: logging.debug("LINE")) def showHideInteractiveMode(self): if self.queryUI.isHidden(): if self.editionSplitter.isHidden(): self.editionSplitter.show() self.queryText.hide() self.queryUI.show() logging.info("Showing 'Interactive mode' window.") else: if self.queryText.isHidden(): self.editionSplitter.hide() self.queryUI.hide() logging.info("Hiding 'Interactive mode' window.") logging.debug("LINE") def showHideConsole(self): if self.console.isHidden(): self.console.show() logging.info("Showing 'Console' window.") self.consoleWidget.setMaximumHeight(QWIDGETSIZE_MAX) else: self.console.hide() self.consoleWidget.setMaximumHeight( self.queryHeader.sizeHint().height()) logging.info("Hiding 'Console' window.") logging.debug("LINE") def showHideQuery(self): if self.queryText.isHidden(): if self.editionSplitter.isHidden(): self.editionSplitter.show() self.queryUI.hide() self.queryText.show() logging.info("Showing 'Query' window.") self.queryWidget.setMaximumHeight(QWIDGETSIZE_MAX) else: if self.queryUI.isHidden(): self.editionSplitter.hide() self.queryText.hide() self.queryWidget.setMaximumHeight( self.queryHeader.sizeHint().height()) logging.info("Hiding 'Query' window.") logging.debug("LINE") def switchManualMode(self): if self.queryText.isReadOnly(): reply = QMessageBox.question( self, "Manual mode", "Are you sure?\nThe interactive mode will remain as it is now." ) if reply == QMessageBox.Yes: self.queryText.setReadOnly(False) self.queryUI.hide() for action in self.requestMenu.actions(): action.setEnabled(False) self.manualModeAct.setEnabled(True) self.manualModeMenu.setEnabled(True) self.showHideInteractiveModeAct.setEnabled(False) self.mapRenderer.setPage(self.manualModePage) logging.info("Switching to manual mode.") else: logging.info( "'Switch between interactive and manual mode' cancelled.") else: reply = QMessageBox.question( self, "Interactive mode", "Are you sure?\nThe current query will be removed.") if reply == QMessageBox.Yes: try: self.queryText.clear() self.queryText.setPlainText( self.queryUI.getQuery().getQL()) except BadFilterAttributes as e: logging.error(str(e)) except RuntimeError: logging.warning("Failed to write query.") self.queryText.clear() self.queryText.setPlainText("") self.queryText.setReadOnly(True) self.queryUI.show() for action in self.requestMenu.actions(): action.setEnabled(True) self.manualModeMenu.setEnabled(False) self.showHideInteractiveModeAct.setEnabled(True) self.changeCurrentMap(0) logging.info("Switching to interactive mode.") else: logging.info( "'Switch between interactive and manual mode' cancelled.") logging.info("Showing 'manual mode' polygon.") def addRequest(self, filters=None): self.queryUI.addRequestByFilters(filters) logging.info("Request added.") logging.debug("LINE") def addTemplate(self, filters): logging.info("Template applied.") self.queryUI.addRequestByFilters(filters) def removeRequest(self): reply = QMessageBox.question( self, "Remove current request", "Are you sure? This option is not undoable.") if reply == QMessageBox.Yes: self.queryUI.removeRequest() logging.info("'Remove request' successfully executed.") else: logging.info("'Remove request' cancelled.") logging.debug("LINE") def saveQuery(self): filename, selectedFilter = QFileDialog.getSaveFileName( self, 'Save query', expanduser("~/filename.txt"), "Text files (*.txt)") if filename != "": if self.queryText.isReadOnly(): try: query = self.queryUI.getQuery().getQL() f = open(filename, "w+") f.seek(0) f.truncate() f.write(query) f.close() logging.info("Query saved successfully.") except (RuntimeError, BadFilterAttributes) as e: logging.error(str(e)) except OSError: logging.error( "There was a problem creating the file with the query." ) else: try: f = open(filename, "w+") f.seek(0) f.truncate() f.write(self.queryText.toPlainText()) f.close() logging.info("Query saved successfully.") except OSError: logging.error( "There was a problem creating the file with the query." ) else: logging.info("\"Save query\" canceled.") logging.debug("LINE") def openQuery(self): filename, selectedFilter = QFileDialog.getOpenFileName( self, 'Open query', expanduser("~/filename.txt")) if filename != "": try: if self.queryText.isReadOnly(): self.switchManualMode() f = open(filename, "r") self.queryText.clear() self.queryText.setPlainText(f.read()) f.close() logging.info("File read successfully.") except UnicodeDecodeError: logging.error("The given file is not readable as text.") except OSError: logging.error("There was a problem opening the query file.") else: logging.info("\"Open query\" canceled.") logging.debug("LINE") def saveInteractiveQuery(self): filename, selectedFilter = QFileDialog.getSaveFileName( self, 'Save query', expanduser("~/filename.json"), "JSON files (*.json)") if filename != "": try: query = self.queryUI.getQuery() query.saveToFile(filename) logging.info("Query saved successfully.") except (RuntimeError, BadFilterAttributes) as e: logging.error(str(e)) except OSError: logging.error( "There was a problem creating the file with the query.") else: logging.info("\"Save query\" canceled.") logging.debug("LINE") def openInteractiveQuery(self): filename, selectedFilter = QFileDialog.getOpenFileName( self, 'Open query', expanduser("~/filename.json")) if filename != "": try: self.queryUI.setQuery(OverpassQuery.getFromFile(filename)) if not self.queryText.isReadOnly(): self.switchManualMode() except json.decoder.JSONDecodeError: logging.error( "The given file has not the right format (json). The file could not be opened." ) except UnicodeDecodeError: logging.error( "The given file is not readable as text. The file could not be opened." ) except (TypeError, KeyError): logging.error( "Fields are missing from the file or there are fields with the wrong data type. " "The file could not be opened.") except OSError: logging.error( "There was a problem opening the query file. The file could not be opened." ) else: logging.info("\"Open query\" canceled.") logging.debug("LINE") def saveNet(self): filename, selectedFilter = QFileDialog.getSaveFileName( self, 'Save File', expanduser("~/filenameWithoutExtension")) if filename != "": buildNet(filename) else: logging.info("\"Save File\" canceled.") logging.debug("LINE") return filename def openNet(self): try: filename = self.saveNet() if filename == "": logging.error("Can't open NETEDIT without a file.") else: openNetedit(filename + ".net.xml") logging.info("Opening NETEDIT.") logging.warning( "If NETEDIT is not open in ten seconds, there was an unhandled problem." ) logging.debug("LINE") except OSError: logging.error("Can't find NETEDIT.") except Exception: logging.error(traceback.format_exc()) # POLYGONS def changeCurrentMap(self, i): if self.queryUI.getCurrentMap() is None: self.mapRenderer.setPage(self.emptyMapPage) else: self.mapRenderer.setPage(self.queryUI.getCurrentMap()) def playQuery(self): newRecord = { "interactiveMode": self.queryText.isReadOnly(), "query": self.queryText.toPlainText(), "html": "" } if self.queryText.isReadOnly(): try: query = self.queryUI.getQuery() newRecord["query"] = query self.queryText.setPlainText(query.getQL()) except (RuntimeError, BadFilterAttributes) as e: logging.error(str(e)) return try: html = buildHTMLWithQuery(self.queryText.toPlainText()) if self.queryText.isReadOnly(): self.mapRenderer.setPage(self.queryUI.updateMaps(html)) newRecord["html"] = html else: soup = bs4.BeautifulSoup(html, features="html.parser") js = soup.new_tag("script") js.string = (MANUAL_MODE_JS_SCRIPT % (str([]))) soup.append(js) self.manualModePage.setHtml(str(soup)) newRecord["html"] = str(soup) self.mapRenderer.setPage(self.manualModePage) logging.info("Query drawn.") logging.debug("LINE") self.addRecord(newRecord) except (OverpassRequestException, OsmnxException) as e: logging.error(str(e)) except ox.EmptyOverpassResponse: logging.error("There are no elements with the given query.") except OSError: logging.error( "There was a problem creating the file with the request response." ) except Exception: logging.error(traceback.format_exc()) def addRecord(self, newRecord): self.record.append(newRecord) index = len(self.record) - 1 recordAction = QAction(datetime.datetime.now().strftime('%H:%M:%S'), self) recordAction.triggered.connect(lambda: self.changeMap(index)) self.recordMenu.addAction(recordAction) def playTableRow(self): try: self.mapRenderer.setPage(self.queryUI.updateMapFromRow()) except (OverpassRequestException, OsmnxException) as e: logging.error(str(e)) logging.warning( "Before open NETEDIT you must run a query with the row filters applied." ) except ox.EmptyOverpassResponse: logging.error("There are no elements with the given row.") except OSError: logging.error( "There was a problem creating the file with the row selection." ) except RuntimeError as e: logging.error(str(e)) except Exception: logging.error(traceback.format_exc()) logging.debug("LINE") # EVENTS def closeEvent(self, event): for f in os.listdir(tempDir): os.remove(os.path.join(tempDir, f)) QMainWindow.closeEvent(self, event)
class RequestWidget(QWidget): def __init__(self, parent, keyList, request=None): super().__init__(parent) self.keyList = keyList # INITIALIZE POLYGON MANAGEMENT self.polygonSettings = [] self.polygonActivated = False self.html = "" self.webChannel = QWebChannel() self.webChannel.registerObject('request', self) self.polygonPage = QWebEnginePage() self.polygonPage.setWebChannel(self.webChannel) # INITIALIZE UI self.layout = self.__generateLayout__() self.setLayout(self.layout) elementsTypeGB, self.nodesCB, self.waysCB, self.relCB, self.areasCB = self.__generateTypeWidget__() self.layout.addRow("ELEMENTS TYPE", elementsTypeGB) self.layout.addRow(HorizontalLine(self)) self.locationNameWidget = self.__generateLocationWidget__() self.layout.addRow("LOCATION", self.locationNameWidget) self.layout.addRow(HorizontalLine(self)) filtersButtons, self.filtersWidget, self.filtersLayout = self.__generateFiltersWidget__() self.layout.addRow("FILTERS", filtersButtons) self.layout.addRow(self.filtersWidget) self.layout.addRow(HorizontalLine(self)) polygonButtons = self.__generatePolygonWidget__() self.layout.addRow("POLYGON", polygonButtons) self.layout.addRow(HorizontalLine(self)) self.surroundGB, self.aroundRadiusEdit = self.__generateSurroundingWidget__() self.layout.addRow("SURROUNDINGS", self.surroundGB) self.layout.addRow(HorizontalLine(self)) self.idsWidget = self.__generateIdsWidget__() self.layout.addRow("IDS", self.idsWidget) # SETTING DATA if request is None: self.requestName = SetNameManagement.getUniqueSetName() else: self.__setRequest__(request) # UI COMPONENTS def __generateLayout__(self): layout = QFormLayout() layout.setContentsMargins(10, 10, 10, 10) layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) layout.setLabelAlignment(Qt.AlignLeft) layout.setFormAlignment(Qt.AlignLeft | Qt.AlignTop) return layout def __generateLocationWidget__(self): locationNameWidget = QLineEdit() locationNameWidget.setPlaceholderText("Areas: 'New York', 'Italy'...") locationNameWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) return locationNameWidget def __generatePolygonWidget__(self): return WidgetFactory.buildIconButtonGroup([ {"image": "polygon.png", "tooltip": "Draw polygon", "checkable": True, "action": self.enableDisablePolygon}, {"image": "reset.png", "tooltip": "Remove polygon", "checkable": False, "action": self.clearPolygon} ]) def __generateTypeWidget__(self): elementsTypeGB = QWidget() elementsTypeLayout = QVBoxLayout() elementsTypeGB.setLayout(elementsTypeLayout) elementsTypeLayout.setContentsMargins(10, 0, 0, 0) elementsTypeLayout.setSpacing(0) nodesCB = QCheckBox(self.tr("&Nodes")) elementsTypeLayout.addWidget(nodesCB) waysCB = QCheckBox(self.tr("&Ways")) waysCB.setChecked(True) elementsTypeLayout.addWidget(waysCB) relCB = QCheckBox(self.tr("&Relations")) elementsTypeLayout.addWidget(relCB) areasCB = QCheckBox(self.tr("&Areas")) areasCB.stateChanged.connect(self.__onAreaSelected__) elementsTypeLayout.addWidget(areasCB) nodesCB.stateChanged.connect(lambda b: self.areasCB.setChecked(False) if b else None) waysCB.stateChanged.connect(lambda b: self.areasCB.setChecked(False) if b else None) relCB.stateChanged.connect(lambda b: self.areasCB.setChecked(False) if b else None) return elementsTypeGB, nodesCB, waysCB, relCB, areasCB def __generateSurroundingWidget__(self): surroundGB = QGroupBox() surroundGB.setFlat(True) surroundLayout = QVBoxLayout() surroundLayout.setContentsMargins(0, 0, 0, 0) noneRB = QRadioButton(self.tr("&None")) noneRB.setObjectName("None") noneRB.setChecked(True) surroundLayout.addWidget(noneRB) adjacentRB = QRadioButton(self.tr("&Adjacent streets")) adjacentRB.setObjectName("Adjacent") surroundLayout.addWidget(adjacentRB) aroundRB = QRadioButton(self.tr("&Streets around")) aroundRB.setObjectName("Around") surroundLayout.addWidget(aroundRB) aroundRadiusEdit = QLineEdit("") aroundRadiusEdit.hide() aroundRadiusEdit.setPlaceholderText("Radius in meters") aroundRadiusEdit.setValidator(QIntValidator(0, 10000000, surroundGB)) aroundRB.toggled.connect(lambda b: aroundRadiusEdit.show() if b else aroundRadiusEdit.hide()) surroundLayout.addWidget(aroundRadiusEdit) surroundGB.setLayout(surroundLayout) return surroundGB, aroundRadiusEdit def __generateFiltersWidget__(self): filtersButtons = QWidget() filtersButtonsLayout = QHBoxLayout() filtersButtons.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) filtersButtonsLayout.setAlignment(Qt.AlignRight) filtersButtonsLayout.setSpacing(0) filtersButtonsLayout.setContentsMargins(0, 0, 0, 0) filtersButtons.setLayout(filtersButtonsLayout) addFilterButton = IconButton(QIcon(os.path.join(picturesDir, "add.png")), filtersButtons.windowHandle(), filtersButtons.height()) addFilterButton.setToolTip("Add filter") addFilterButton.setFlat(True) addFilterButton.setStyleSheet("""QPushButton::menu-indicator{image: none;}""") filtersMenu = QMenu() equalAct = QAction('Equal', self) equalAct.triggered.connect(lambda: self.addFilterByComparison(comparison=TagComparison.EQUAL)) filtersMenu.addAction(equalAct) maxAct = QAction('Maximum', self) maxAct.triggered.connect(lambda: self.addFilterByComparison(comparison=TagComparison.AT_MOST)) filtersMenu.addAction(maxAct) minAct = QAction('Minimum', self) minAct.triggered.connect(lambda: self.addFilterByComparison(comparison=TagComparison.AT_LEAST)) filtersMenu.addAction(minAct) containAllAct = QAction('Contain all', self) containAllAct.triggered.connect(lambda: self.addFilterByComparison(comparison=TagComparison.CONTAIN_ALL)) filtersMenu.addAction(containAllAct) oneOfAct = QAction('Is one of', self) oneOfAct.triggered.connect(lambda: self.addFilterByComparison(comparison=TagComparison.IS_ONE_OF)) filtersMenu.addAction(oneOfAct) hasKeyAct = QAction('Has key', self) hasKeyAct.triggered.connect(lambda: self.addFilterByComparison(comparison=TagComparison.HAS_KEY)) filtersMenu.addAction(hasKeyAct) hasOneKeyAct = QAction('Has one key', self) hasOneKeyAct.triggered.connect(lambda: self.addFilterByComparison(comparison=TagComparison.HAS_ONE_KEY)) filtersMenu.addAction(hasOneKeyAct) hasNotKeyAct = QAction('Has not key', self) hasNotKeyAct.triggered.connect(lambda: self.addFilterByComparison(comparison=TagComparison.HAS_NOT_KEY)) filtersMenu.addAction(hasNotKeyAct) addFilterButton.setMenu(filtersMenu) filtersButtonsLayout.addWidget(addFilterButton) filtersWidget = QWidget(self) filtersLayout = QVBoxLayout() filtersLayout.setContentsMargins(10, 10, 10, 10) filtersWidget.setLayout(filtersLayout) return filtersButtons, filtersWidget, filtersLayout def __generateIdsWidget__(self): return VariableInputList(0, "Numeric id", QRegularExpressionValidator(QRegularExpression("^[0-9]+$"))) # REQUEST GETTERS def __getLocationName__(self): return self.locationNameWidget.text() def __getType__(self): return OsmType.getType(self.nodesCB.isChecked(), self.waysCB.isChecked(), self.relCB.isChecked(), self.areasCB.isChecked()) def __getSelectedSurrounding__(self): switcher = { "Adjacent": Surround.ADJACENT, "Around": Surround.AROUND, "None": Surround.NONE } selectedSurrounding = [b for b in self.surroundGB.findChildren(QRadioButton) if b.isChecked()][0] return switcher.get(selectedSurrounding.objectName()) @pyqtSlot(QJsonValue) def __setPolygons__(self, val): self.polygonSettings = [] for point in val.toArray(): self.polygonSettings.append([point["lat"].toDouble(), point["lng"].toDouble()]) def __getPolygon__(self): return self.polygonSettings def getAroundRadius(self): return int(self.aroundRadiusEdit.text()) if len(self.aroundRadiusEdit.text()) > 0 else 100 def getRequest(self): request = OverpassRequest(self.__getType__(), self.__getSelectedSurrounding__(), self.requestName, self.getAroundRadius()) request.setLocationName(self.__getLocationName__()) if request.locationId is None and request.locationName != "": logging.warning("Location name not found in the request '{}'. It will be ignored.".format(self.requestName)) request.addPolygon(self.__getPolygon__()) request.setIds(self.__getIds__()) for filterWidget in self.filtersWidget.findChildren(FilterWidget): try: request.addFilter(filterWidget.getFilter()) except BadFilterAttributes as e: raise BadFilterAttributes("Error in one of the filters of request '{}'. {}".format(self.requestName, str(e))) return request def getName(self): return self.requestName def getMap(self): return self.polygonPage def __getIds__(self): return self.idsWidget.getItems() # REQUEST SETTERS def addFilterByComparison(self, comparison): newFilterWidget = FilterWidget(self.filtersWidget, comparison, self.keyList) self.filtersLayout.addWidget(newFilterWidget) def addFilter(self, filter): currentKeys = {filter.getKey(): filter for filter in self.findChildren(FilterWidget)} if filter.key != "" and filter.key in currentKeys.keys(): logging.warning("The key {} is used more than once in the set {}.".format(filter.key, self.getName())) newFilterWidget = FilterWidget(self.filtersWidget, filter.comparison, self.keyList) self.filtersLayout.addWidget(newFilterWidget) newFilterWidget.setFilter(filter) def __setLocationName__(self, locationName): self.locationNameWidget.setText(locationName) def __setType__(self, requestType): typeConfig = OsmType.getConfig(requestType) self.waysCB.setChecked(typeConfig["way"]) self.nodesCB.setChecked(typeConfig["node"]) self.relCB.setChecked(typeConfig["rel"]) self.areasCB.setChecked(typeConfig["area"]) def __setSelectedSurrounding__(self, surroundValue): if surroundValue == Surround.ADJACENT: self.surroundGB.findChild(QRadioButton, "Adjacent").setChecked(True) elif surroundValue == Surround.AROUND: self.surroundGB.findChild(QRadioButton, "Around").setChecked(True) elif surroundValue == Surround.NONE: self.surroundGB.findChild(QRadioButton, "None").setChecked(True) def changePolygon(self, coors): self.polygonSettings = coors if self.html != "": self.changePage(self.html) def setAroundRadius(self, radius): return self.aroundRadiusEdit.setText(str(radius)) def __setRequest__(self, request): self.requestName = request.name self.__setType__(request.type) self.setAroundRadius(request.aroundRadius) self.__setSelectedSurrounding__(request.surrounding) for filterWidget in self.filtersWidget.findChildren(FilterWidget): filterWidget.deleteLater() for filter in request.filters: self.addFilter(filter) self.__setLocationName__(request.locationName) self.setIds(request.ids) self.changePolygon(request.polygon) def changePage(self, html): self.html = html soup = bs4.BeautifulSoup(html, features="html.parser") js = soup.new_tag("script") js.string = (JS_SCRIPT % (str(self.polygonSettings), str(self.polygonActivated).lower())) soup.append(js) soup.head.append(soup.new_tag("script", src="qrc:///qtwebchannel/qwebchannel.js")) htmlFileName = os.path.join(tempDir, "{}.html".format(self.requestName)) with open(htmlFileName, "w+") as f: f.write(str(soup)) self.polygonPage.load(QUrl.fromLocalFile(htmlFileName)) def setIds(self, ids=None): self.idsWidget.setItems(ids) def addId(self, newId=0): self.idsWidget.addItem(newId) def __del__(self): SetNameManagement.releaseName(self.requestName) # SIGNALS def __onAreaSelected__(self): self.nodesCB.setChecked(False) self.waysCB.setChecked(False) self.relCB.setChecked(False) def clearPolygon(self): self.polygonPage.runJavaScript("cleanPolygon();", lambda x: logging.debug("LINE")) def enableDisablePolygon(self, checked): self.polygonActivated = checked self.polygonPage.runJavaScript("enablePolygon();" if checked else "disablePolygon();")
class TestForm(QMainWindow, Ui_MainWindow): #생성자 def __init__(self): super().__init__() self.setupUi(self) # 초기화 self.url = QUrl("http://localhost:8080/umbrella") self.webEngineView.load(QUrl(self.url)) self.page = QWebEnginePage() self.page.setUrl(self.url) self.page.setView(self.webEngineView) self.comboBox.addItem("키워드") self.comboBox.addItem("주소") # self.page.featurePermissionRequested.connect(self.setPagePermission) self.pushButton.clicked.connect(self.search) self.lineEdit.returnPressed.connect(self.search) self.init_my_location() self.page.loadFinished.connect( lambda: self.setMap(self.my_location_lat, self.my_location_lng)) # self.setMap(self.my_location_lat, self.my_location_lng) #아이피로 현재 위치 받아오기(google api 사용) def init_my_location(self): url = 'https://www.googleapis.com/geolocation/v1/geolocate?key=AIzaSyDQKxbTt0MrFNH85kTJXzickMD5s88UVaI' data = { 'considerIp': True, } result = requests.post(url, data) my_location = json.loads(result.text) # print(my_location) print("lat : ", my_location.get('location').get('lat')) print("lon : ", my_location.get('location').get('lng')) self.my_location_lat = my_location.get('location').get('lat') self.my_location_lng = my_location.get('location').get('lng') def setMap(self, lat, lng): script = """ var umbrella_location = new kakao.maps.LatLng(""" + str( lat) + """, """ + str(lng) + """); map.setCenter(umbrella_location); """ self.run(script) #위치권한 요청이 왔을때 허용해줌 def setPagePermission(self, url, feature): self.page.setFeaturePermission(url, feature, QWebEnginePage.PermissionGrantedByUser) def search(self): search_text = self.lineEdit.text().strip() if self.comboBox.currentIndex() == 0: script = """ removeMarkers(); // 주소-좌표 변환 객체를 생성합니다 var geocoder = new kakao.maps.services.Geocoder(); // 장소 검색 객체를 생성합니다 var ps = new kakao.maps.services.Places(); // 키워드로 장소를 검색합니다 ps.keywordSearch('""" + search_text + """', placesSearchCB); function placesSearchCB (data, status, pagination) { if (status === kakao.maps.services.Status.OK) { // 검색된 장소 위치를 기준으로 지도 범위를 재설정하기위해 // LatLngBounds 객체에 좌표를 추가합니다 var bounds = new kakao.maps.LatLngBounds(); for (var i=0; i<data.length; i++) { displayMarker(data[i]); bounds.extend(new kakao.maps.LatLng(data[i].y, data[i].x)); } // 검색된 장소 위치를 기준으로 지도 범위를 재설정합니다 map.setBounds(bounds); } } """ elif self.comboBox.currentIndex(): script = """ removeMarkers(); var positions = []; positions=Marker3.main3(); for (var i=0; i<positions.length;i++){ map:map, position:new kakao.maps.LatLng(positions[i].get('lat')+positions[i].get('lng')) } // 주소-좌표 변환 객체를 생성합니다 var geocoder = new kakao.maps.services.Geocoder(); // 주소로 좌표를 검색합니다 geocoder.addressSearch('""" + search_text + """', function(result, status) { // 정상적으로 검색이 완료됐으면 if (status === kakao.maps.services.Status.OK) { var coords = new kakao.maps.LatLng(result[0].y, result[0].x); // 결과값으로 받은 위치를 마커로 표시합니다 var marker = new kakao.maps.Marker({ map: map, position: coords }); //*** 마커 담기 markerList.push(marker) // 인포윈도우로 장소에 대한 설명을 표시합니다 infowindow.open(map, marker); // 지도의 중심을 결과값으로 받은 위치로 이동시킵니다 map.setCenter(coords); } }); """ else: return self.run(script) def run(self, script): self.page.runJavaScript(script)
def eval_p_js_faf(page: QtWebEngineWidgets.QWebEnginePage, js: str): page.runJavaScript(js)