亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹

發布時間:2021-09-08 16:26:15 來源:億速云 閱讀:288 作者:chen 欄目:開發技術

這篇文章主要介紹“PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹”,在日常操作中,相信很多人在PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

目錄
  • 1. Qt 的用法

  • 2. Pycharm 設置

    • 2.1 安裝 Pyqt5 和 pyinstaller 包

    • 2.2 Pycharm pyqt工具配置

  • 3 UDP圖形界面設計

    • 3.1 GUI設計

    • 3.2 將GUI文件轉換為py文件

    • 3.3 widget窗體提升,整合matplotlib的功能

    • 3.4 GUI 設計結果

  • 4 多線程編程UDP通訊

    • 4.1 信號和槽函數

    • 4.2 多線程

  • 5 Pyinstaller 打包成exe

    1. Qt 的用法

    ? pyqt5是qt的python版本,其主要是以對象的形式存在的,在編程的過程中無法可視化,帶來諸多的不便。為了簡化pyqt5的界面設計環節,我們可以使用qt中的設計器Qt Designer (C:\Qt\5.12.11\mingw73_32\bin\designer.exe)來設計圖形界面,生成的圖形界面通常保存在*.ui后綴的文件中。

    ?pyqt5可以直接調用.ui文件,也可以通過pyqt5自帶的pyuic.exe將設計好的.ui文件轉換為.py格式的pyqt5類,供其他模塊調用。

    ?Qt 的安裝教程在CSDN論壇上有很多,在此不再贅述了,建議使用 Qt5, 高版本的Qt6目前還沒有被 matplotlib 納入后端支持。

    2. Pycharm 設置

    ?首先安利一波,Pycharm在代碼顏色主題、功能界面、python環境切換、打開終端、jupyter notebook支持、變量查看、Markdown支持、console多開等方面具有較高的便利性,因此本人主要使用pychrm 進行相關代碼的開發,推薦使用。

    2.1 安裝 Pyqt5 和 pyinstaller 包

    ?在pycharm底部打開終端,并輸入如下代碼安裝pyqt5包和pyinstaller包。Pyinstaller包是用來將pyqt5GUI設計打包成exe可執行文件的工具,有了這個工具,就可以將程序拷貝到其他windows電腦上使用。

    pip install pyqt5,pyinstaller

    PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹

    ?后續還需要安裝matplotlib包,按照類似的方式進行安裝,不再贅述。在安裝了pyqt5后,matplotlib會自動以pyqt5為后端,繪制出來的圖像效果更好,所帶的工具欄也更加實用,推薦日常使用。

    2.2 Pycharm pyqt工具配置

    ?在使用Qt進行界面設計時,可以在pycharm中將Qt軟件自帶的幾個工具都配置為外部工具(這就是pycharm眾多優點之一),方便隨時調用。pycharm中以及點擊文件-設置-工具-外部工具(英文版自行對照)即可進入外部工具添加界面。

    PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹

    Qt Designer 工具(設計Qt 界面)

    程序路徑:

    C:\Qt\5.12.11\mingw73_32\bin\designer.exe

    工作目錄:

    $ProjectFileDir$

    PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹

    Qt Creator 工具(設計Qt 界面)

    ?程序路徑在對應環境的Script目錄下:

    C:\Anaconda3\envs\tensor37\Scripts\pyuic5.exe

    ?參數設置如下:

    $FileName$ -o $FileNameWithoutExtension$.py

    ?工作目錄:

    $ProjectFileDir$

    PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹

    PyUI 工具(Qt UI界面轉為python代碼)

    ?程序路徑:

    C:\Qt\Tools\QtCreator\bin\qtcreator.exe

    ?工作目錄:

    $ProjectFileDir$

    PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹

    ?完成上述設置后在右鍵菜單中可以打開designer.exe和creator.exe這兩個GUI設計應用,選中.ui文件右鍵運行pyuic.exe則會生成同名的.py文件,文件中包含有能夠產生相同GUI的pyqt5類。

    3 UDP圖形界面設計

    3.1 GUI設計

    ?在pycharm的空白處右鍵選擇外部工具,打開designer,新建Main Window窗體。

    PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹

    ?根據需要,我設計了一個UDP網絡編程的界面,主要功能是接收UDP客戶端發來的正弦數據,保存數據到txt文件中并將其繪制在底部的widget (窗體部件)中。

    PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹

    ?目標運行界面如下:

    PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹

    3.2 將GUI文件轉換為py文件

    ?設計好界面后,保存得到widget_recev.ui圖形文件,在左側的項目資源管理器中可以選中ui文件右鍵實用外部pyuic工具將其轉換為widget_recev.py文件,供程序調用。這個操作在后續的調試中經常會用到,隨時改動GUI隨時生成新的py文件。新建的py文件會覆蓋原來的內容,所以建議另建其他python模塊調用該模塊,避免信息丟失。

    PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹

    3.3 widget窗體提升,整合matplotlib的功能

    ?這里需要注意的是,matplotlib中的FigureCanvas和GUI中的widget都是Qwidget的子類,matplotlib是無法直接在widget中繪圖的,需要在Designer中將widget提升為Qwidget類。選中GUI中的widget右鍵選擇提升窗口部件,選擇Qwidget,給提升的類取一個好記的名字,在這里我使用的是mplwidget。

    PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹

    ?生成的widget_recev.py最后面會生成一句:

    from mplwidget import mplwidget

    ?將其放在類文件的開頭,否則會報錯。

    ?mplwidget.py 模塊需要自己構建,在對應的路徑下自己建一個mplwidget.py文件,主要功能是創建一個同時繼承了FigureCanvas與QWidget的類,按照上面預定義的,將其命名為mplwidget類。該操作使原來的widget窗體具有了matplotlib畫布功能,可以在上面繪圖了。mplwidget.py文件的內容如下:

    # _*_coding: UTF-8_*_
    # 開發作者 :TXH
    # 開發時間 :2021-09-05 14:42
    # 文件名稱 :mplwidget.py
    # 開發工具 :Python 3.7 + Pycharm IDE
    
    from PyQt5 import QtGui,QtWidgets
    from matplotlib.backends.backend_qt5agg \
     import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.figure import Figure
    from PyQt5.QtCore import QThread
    
    class MplCanvas(FigureCanvas,QThread):
        def __init__(self):
            self.fig = Figure()
            FigureCanvas.__init__(self, self.fig)
            FigureCanvas.setSizePolicy(self,
            QtWidgets.QSizePolicy.Expanding,
            QtWidgets.QSizePolicy.Expanding)
            FigureCanvas.updateGeometry(self)
    
    class mplwidget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            QtWidgets.QWidget.__init__(self, parent)
            self.canvas = MplCanvas()
            self.vbl = QtWidgets.QVBoxLayout()
            self.vbl.addWidget(self.canvas)
            self.setLayout(self.vbl)

    3.4 GUI 設計結果

    ?生成的pyqt5 UI(widget_recev.py)內容如下。該文件是根據Qt ui文件自動生成的,因此一般只需要知道里面有哪些部件即可,對于一些大小、位置設置的細節可以不用關注,因為已經在GUI設計的時候做好了。

    from PyQt5 import QtCore, QtGui, QtWidgets
    from PyQt_Learning.UDP.GUI.mplwidget import mplwidget  # 根據mplwidget 的位置改動
    
    class Ui_Widget(object):
        def setupUi(self, Widget):
            Widget.setObjectName("Widget")
            Widget.resize(280, 165)
            self.label_2 = QtWidgets.QLabel(Widget)
            self.label_2.setGeometry(QtCore.QRect(110, 10, 55, 16))
            self.label_2.setObjectName("label_2")
            self.lineEdit_2 = QtWidgets.QLineEdit(Widget)
            self.lineEdit_2.setGeometry(QtCore.QRect(110, 30, 61, 21))
            self.lineEdit_2.setObjectName("lineEdit_2")
            self.pushButton = QtWidgets.QPushButton(Widget)
            self.pushButton.setGeometry(QtCore.QRect(10, 120, 71, 24))
            self.pushButton.setObjectName("pushButton")
            self.label = QtWidgets.QLabel(Widget)
            self.label.setGeometry(QtCore.QRect(12, 10, 55, 16))
            self.label.setObjectName("label")
            self.lineEdit = QtWidgets.QLineEdit(Widget)
            self.lineEdit.setGeometry(QtCore.QRect(12, 30, 81, 21))
            self.lineEdit.setObjectName("lineEdit")
            self.pushButton_2 = QtWidgets.QPushButton(Widget)
            self.pushButton_2.setGeometry(QtCore.QRect(180, 120, 75, 24))
            self.pushButton_2.setObjectName("pushButton_2")
            self.lineEdit_5 = QtWidgets.QLineEdit(Widget)
            self.lineEdit_5.setGeometry(QtCore.QRect(190, 80, 61, 21))
            self.lineEdit_5.setObjectName("lineEdit_5")
            self.label_3 = QtWidgets.QLabel(Widget)
            self.label_3.setGeometry(QtCore.QRect(190, 60, 71, 16))
            self.label_3.setObjectName("label_3")
            self.label_4 = QtWidgets.QLabel(Widget)
            self.label_4.setGeometry(QtCore.QRect(10, 60, 71, 16))
            self.label_4.setObjectName("label_4")
            self.lineEdit_3 = QtWidgets.QLineEdit(Widget)
            self.lineEdit_3.setGeometry(QtCore.QRect(10, 80, 51, 21))
            self.lineEdit_3.setObjectName("lineEdit_3")
            self.label_5 = QtWidgets.QLabel(Widget)
            self.label_5.setGeometry(QtCore.QRect(110, 60, 71, 16))
            self.label_5.setObjectName("label_5")
            self.lineEdit_4 = QtWidgets.QLineEdit(Widget)
            self.lineEdit_4.setGeometry(QtCore.QRect(110, 80, 51, 21))
            self.lineEdit_4.setObjectName("lineEdit_4")
            self.label_6 = QtWidgets.QLabel(Widget)
            self.label_6.setGeometry(QtCore.QRect(70, 80, 21, 16))
            self.label_6.setObjectName("label_6")
    
            self.retranslateUi(Widget)
            QtCore.QMetaObject.connectSlotsByName(Widget)
    
        def retranslateUi(self, Widget):
            _translate = QtCore.QCoreApplication.translate
            Widget.setWindowTitle(_translate("Widget", "數據發送端"))
            self.label_2.setText(_translate("Widget", "端口"))
            self.lineEdit_2.setText(_translate("Widget", "9999"))
            self.pushButton.setText(_translate("Widget", "發送正弦"))
            self.label.setText(_translate("Widget", "IP地址"))
            self.lineEdit.setText(_translate("Widget", "127.0.0.1"))
            self.pushButton_2.setText(_translate("Widget", "停止發送"))
            self.lineEdit_5.setText(_translate("Widget", "8"))
            self.label_3.setText(_translate("Widget", "正弦通道數"))
            self.label_4.setText(_translate("Widget", "正弦頻率"))
            self.lineEdit_3.setText(_translate("Widget", "50"))
            self.label_5.setText(_translate("Widget", "正弦幅度"))
            self.lineEdit_4.setText(_translate("Widget", "1"))
            self.label_6.setText(_translate("Widget", "Hz"))

    4 多線程編程UDP通訊

    ?使用Qt進行界面設計非常方便。pyqt編程的難點在于底層的信號-槽函數機制以及多線程編程。先拋開多線程UDP編程,簡單舉例講一下信號-槽函數的原理。

    4.1 信號和槽函數

    ?信號相當于是GUI主循環中的事件,一旦觸發某個事件,對應的槽函數(對象方法)將會運行。

    ?信號可以是內建信號,也可以是自定義信號。內建信號一般直接跟部件相關聯,可以根據一定的規則構建對應的槽函數,例如:

    def on_pushButtom_clicked(self): 
        ...
    def on_pushButtom_2_clicked(self): 
        ...
    def on_pushButtom_3_clicked(self): 
        ...

    分別對應著pushButtom,pushButtom_2,pushButtom_3 三個按鈕被觸發 clicked()事件時的自動關聯槽函數,事件觸發后立即運行對應的槽函數。類似的,checkBox_5被觸發則默認自動關聯如下槽函數,傳遞chencked 布爾信號:

    def on_checkBox_5_toggled(self,checked):
        ...

    ?自定義信號則更加靈活,其在事件觸發時通過emit()函數發送數據。在pyqt5中,信號發送的數據類型可以是python支持的任何類型, 目前的測試表明,numpy.array、list、str、int、float等數據類型可以通過信號傳遞給槽函數,作為槽函數的輸入。

    在主線程(或GUI主循環)內,自定義簡單的信號和槽函數對如下。

    from PyQt5.QtCore import QObject
    from PyQt5 import QtCore
    
    class Test(QObject):
        test_signal = QtCore.pyqtSignal(list)  # 定義test_signal 信號
        def __init__(self, parent=None):
            super().__init__(parent)
            self.test_signal.connect(self.print_data)  # 將信號與test槽函數關聯
    
        def toggle(self):
            a = list([1, 2, 3, 4, 5])
            self.test_signal.emit(a)  # 向槽函數發送信號
    
        @QtCore.pyqtSlot(list)
        def print_data(self, list_var):  # 定義槽函數
            # 槽函數一旦接收到test_signal 發送的數據,立即執行后續內容
            print(list_var) 
    
    test = Test()
    test.toggle()
    
    >>> [1, 2, 3, 4, 5]

    ?信號一般在初始化方法之前定義,作為Qt類的成員,定義信號時給出所發送信號的數據類型,以下例子中使用的是list類型。在初始化時將信號與對應的槽函數相關聯。然后根據需要在不同的方法中發送信號給槽函數,槽函數接收到數據后立即執行函數中的內容。以上例子較為簡單,主要在主線線程內進行信號和槽函數的觸發。后文中將給出多線程進行信號和槽函數傳遞數據的案例。

    4.2 多線程

    ?pyqt的主界面使用的是主線程,可以看做是一個死循環。一旦主線程中產生了較為耗時的操作,將導致主線程出現假死的現象,體現在GUI界面就是無響應和無法進行任何操作。

    ?在進行GUI程序設計時一般遵循GUI界面和代碼界面分開設計的原則,主線程只負責管理基本GUI的動作,而耗時的操作則通過子線程進行計算。

    ?回到“多線程UDP通訊”的主題,在創建好GUI的基礎上,UDP通訊接收端的主函數如下。代碼實現了主線程向子線程、子線程向子線程以及子線程向主線程傳遞數據的三種情況。

    當然,所有信號與槽函數的連接都必須在主線程中完成。
    具體方法是:在主線程中創建子線程實例,將子線程作為主線程的成員,如此可以實現子線程與子線程,子線程與主線程之間的信號傳遞。

    # _*_coding: UTF-8_*_
    # 開發作者 :TXH
    # 開發時間 :2021-08-26 18:24
    # 文件名稱 :Receiver.py
    # 開發工具 :Python 3.7 + Pycharm IDE
    
    import socket
    import sys
    from PyQt5.QtWidgets import QApplication, QMainWindow
    from PyQt_Learning.UDP.GUI.widget_recev import Ui_MainWindow
    from PyQt5 import QtCore,uic
    from PyQt5.QtCore import QThread,pyqtSlot
    
    class QmyDialog(QMainWindow): # 主窗體本身占用一個主線程
        UDP_para = QtCore.pyqtSignal(list)
        sender_para = QtCore.pyqtSignal(list)
        def __init__(self, parent=None):
            super().__init__(parent)
            self.pause=False
            self.statusBar().showMessage('Load UI...')
            if 0:
                self.ui = uic.loadUi('E:/Pywork/PyQt_Learning/UDP/GUI/widget_recev.ui',self) #
            else:
                self.ui = Ui_MainWindow()
                self.ui.setupUi(self)
            self.statusBar().showMessage('Init Canvas...')
            self.canvas = self.ui.widget.canvas # 繪圖設置
            self.canvas.ax1 = self.canvas.fig.add_subplot(111)
            self.canvas.ax1.get_yaxis().grid(True)
    
            self.statusBar().showMessage('Init UDP...') # 狀態欄更新
            self.UDP = UDPThread(self.para(1)) # 創建子線程1
            self.UDP_para.connect(self.UDP.UDP_para_update) # 主線程向UDP線程傳遞參數
    
            self.statusBar().showMessage('Init plot sender...')
            self.Plot_fig = Plot_Thread(self.para(2)) # 創建子線程2
            self.UDP.send_data.connect(self.Plot_fig.send) # UDP子線程向繪圖子線程發送數據
            self.sender_para.connect(self.Plot_fig.Sender_para_update) # 主線程向繪圖子線程傳遞參數
            self.Plot_fig.plot_data.connect(self.plot_fig) # 繪圖子線程將數據發給主線程plot_fig函數,讓其繪圖
            self.statusBar().showMessage('Ready!')
    
        def on_pushButton_clicked(self):  # 設置參數
            self.update_udp_para()
            self.update_sender_para()
            self.statusBar().showMessage('Para changed...')
    
        def on_pushButton_2_clicked(self):  # 接收數據
            self.update_udp_para()
            self.update_sender_para()
            self.UDP.pause = False
            self.Plot_fig.pause=False
            self.UDP.start()
            self.ui.lineEdit.setReadOnly(True)
            self.ui.lineEdit_2.setReadOnly(True)
            self.Plot_fig.start()
            self.statusBar().showMessage('Receiving data...')
    
        def on_pushButton_3_clicked(self):  # 停止接收和繪圖
            self.pause=True
            self.update_udp_para()
            self.update_sender_para()
            self.statusBar().showMessage('Receiving paused!')
    
        def plot_fig(self,temp): # 繪圖函數,不計算,避免主線程阻塞,接收數據后立即繪圖
            self.canvas.ax1.clear()
            self.canvas.ax1.plot(temp)
            self.canvas.fig.tight_layout()
            self.canvas.draw()
    
        def update_udp_para(self): # UPD子線程參數設置
            self.UDP_para.emit(self.para(1))
    
        def update_sender_para(self):# 繪圖計算子線程參數設置
            self.sender_para.emit(self.para(2))
    
        def para(self,flag):
            if flag==1:
                return list([self.ui.lineEdit.text(),int(self.ui.lineEdit_2.text()),self.pause])
            else:
                return list([int(self.ui.lineEdit_3.text()),self.pause])
                
    # 定義UDP 接收線程類
    class UDPThread(QThread):
        send_data = QtCore.pyqtSignal(str)
        def __init__(self,udp_para_list):
            super().__init__()
            self.IP,self.Port,self.pause = udp_para_list
            self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 設置socket協議為UDP
            self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
        def run(self) -> None: # 死循環接收UDP數據
            try:
                self.s.bind((self.IP, self.Port))
            except:pass
            i = 1
            with open('out.txt', 'w') as f: # 將獲取的UDP數據保存到本地的txt中
                while True:
                    temp = self.s.recv(1024).decode('utf-8')  # 接收 socket 數據
                    if i%11==1:
                        self.send_data.emit(temp)
                    f.writelines(temp + '\n')
                    i=(i+1)%2000
                    if self.pause: # 判斷是否跳出循環
                        break
    
        def UDP_para_update(self,udp_para_list):
            self.Ip,self.Port,self.pause=udp_para_list
    
    # 定義繪圖計算子線程類
    class Plot_Thread(QThread):
        plot_data = QtCore.pyqtSignal(list)
        def __init__(self,para_list):
            super().__init__()
            self.pause = False
            self.data = []
            self.max_len = para_list[0]
            self.i=1
    
        def change_Len(self,len): # 繪圖長度設置
            if len<1000:
                self.max_len=1000
            else:
                self.max_len = len
    
        # 接收UDP接收子線程發來的數據,并轉發給GUI中的繪圖方法
        @pyqtSlot(str)
        def send(self,data): 
            self.data.append(float(data))
            if len(self.data)>self.max_len:
                self.data=self.data[(len(self.data)-self.max_len):]
            self.i=(self.i+1)%(1000)
            if self.i==0:
                self.plot_data.emit(self.data)  # 每收到 1000個點將data數據發給GUI進行繪圖
    
        def Sender_para_update(self,para_list): # 根據主線程的信號更新繪圖長度
            self.max_len,self.pause=para_list
    
    # if __name__ == "__main__":
    app = QApplication(sys.argv)  # 調用父類構造函數,創建窗體
    form = QmyDialog()  # 創建UI對象
    form.show()  #
    sys.exit(app.exec())  #

    ?效果顯示如下:
    點擊記錄并繪圖,程序在保存UDP接收的數據的同時,將部分數據發送到GUI界面進行繪圖。

    PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹

    5 Pyinstaller 打包成exe

    ?pyinstaller將代碼打包成exe時會面臨生成的exe過大的情況,一個很小功能的exe體積高達200M。歸根到底是pyinstaller將一些相互關聯的安裝包都打包到exe中了,而大多數安裝包在當前項目中并沒有真正使用到。

    ?經測試,可以使用pipenv 創建一個干凈的虛擬環境,降低exe的大小。 環境中只安裝需要的pyinstaller, pyqt5, numpy等即可。在虛擬環境下生成的pyqt5 exe可執行文件只有幾十兆。

    ?想進一步壓縮可以下載 upx.exe,將放入pipenv虛擬環境下的Script文件夾中,pyinstaller打包的時候會自動調用。壓縮量小,但算是有點效果的,畢竟沒有其他補救措施了。

    ?在pipenv虛擬環境下運行如下代碼:

    # Gen_EXE.py
    import os
    error = os.system('pyinstaller --clean -Fw E:\\Pywork\PyQt_Learning\\UDP\\GUI\\Receiver.py  E:\\Pywork\\PyQt_Learning\\UDP\\GUI\\widget_recev.py E:\\Pywork\\PyQt_Learning\\UDP\\GUI\\mplwidget.py') # 添加所有相關的py文件
    if not error: print('成功生成exe文件!')

    ?最終的大小約為44M,個人感覺還行。

    PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹

    到此,關于“PyQt5 GUI 接收UDP數據并動態繪圖的過程介紹”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    广水市| 修水县| 南开区| 江油市| 平顶山市| 启东市| 曲麻莱县| 敦煌市| 绍兴县| 阆中市| 南宁市| 河北省| 辉南县| 永顺县| 南郑县| 饶阳县| 五常市| 嘉祥县| 泰和县| 临清市| 南京市| 阜新市| 樟树市| 呼玛县| 黑龙江省| 尚义县| 克山县| 大连市| 左云县| 井陉县| 勐海县| 唐河县| 城步| 武隆县| 怀仁县| 拜城县| 上思县| 西安市| 沅江市| 曲阜市| 如皋市|