pci工具程式碼
阿新 • • 發佈:2018-11-02
""" 2018-10-17至30 作者:王涵 微信:18843112066 郵箱:[email protected] """ import datetime import xlrd import pandas as pd import numpy as np from django.core.exceptions import MultipleObjectsReturned from django.db.models import Model, Count, F, Q, Case, When from geographiclib.geodesic import Geodesic from dbbackend.models import ZJcell, LngLat, ZJenb, ZJadjacent class Cellinforeader: """讀取excel的類""" def __init__(self, path=r'C:\Users\Administrator\Desktop\嘉興工參\圈定區域網內鄰區關係(1).xlsx', sheetname='圈定小區'): try: self.workbook = xlrd.open_workbook(path) self.sheet = self.workbook.sheet_by_name(sheetname) except Exception as e: print(repr(e)) # 列名與列號字典,例如{'小區名稱':2} self.col_dict = {} # 如果鄰區表中有個cgi在小區工參裡沒找到,就無法新增鄰區,把這樣的cgi過濾出來保留以便後續使用 self.mismatchedcells = dict() self.mismatchedcgis = [] # self.mismatchedcgi_t = dict() self.mismatchedrowid = [] # sheet.ncols: Nominal number of columns in sheet. for i in range(self.sheet.ncols): self.col_dict[self.alltypestrip(self.sheet.cell_value(0, i))] = i # ' abc '.strip()去除字串前後的空格 # 提取每行資料,匯入小區資料庫ZJcell中 def insertcells(self, eci='ECGI', cgi='CGI', cellname='小區名稱', freq='頻段', pci='PCID', lng='基站經度', lat='基站緯度', azimuth='扇區方位角', enbname='基站名稱', enbid='LTEENODEBID', cellid='小區標識'): insertcount = 0 for i in range(1, self.sheet.nrows): row = self.sheet.row_values(i) # 下面囉嗦了這麼多行,無非是要實現兩個功能,一是根據列名尋找資料,避免根據列的位置尋找資料時格式不能改 # 二是利用strip函式將字串前後的空格刪除,避免基站名後面帶空格的情況,這裡利用的小技巧有: # 1、把列名和列號做成dict,這樣就可直接通過列名取列號,比迴圈查詢函式省時間 # 2、alltypestrip()函式利用了python三目運算 # 3、因為變數名重複,所以在結果的變數名前加下劃線 _ ,加以區分 # 4、這麼多重複的寫法,看似可以使用迴圈來解決,但那樣反而複雜,不如使用相同的格式列出來,以後看著清晰, # 相當於配置的賦值都應該這樣寫 _eci = self.alltypestrip(row[self.col_dict[eci]]) _cellid = self.alltypestrip(row[self.col_dict[cellid]]) _enbname = self.alltypestrip(row[self.col_dict[enbname]]) _enbid = self.alltypestrip(row[self.col_dict[enbid]]) _lng = self.alltypestrip(row[self.col_dict[lng]]) _lat = self.alltypestrip(row[self.col_dict[lat]]) _cgi = self.alltypestrip(row[self.col_dict[cgi]]) _cellname = self.alltypestrip(row[self.col_dict[cellname]]) _freq = self.alltypestrip(row[self.col_dict[freq]]) _pci = self.alltypestrip(row[self.col_dict[pci]]) _azimuth = self.alltypestrip(row[self.col_dict[azimuth]]) # 這個變數用於標識讀取的一行excel資料用get_or_create建立時,是否已存在, # 如果資料庫中不存在,就在資料庫中建立一條資料,返回值中的元組的第二項設為True, createdornot2 = None try: enb, createdornot = ZJenb.objects.get_or_create(enbname=_enbname, enbid=_enbid) lnglat, createdornot1 = LngLat.objects.get_or_create(lng=_lng, lat=_lat) cell, createdornot2 = ZJcell.objects.get_or_create(eci=_eci, cgi=_cgi, cellname=_cellname, freq=_freq, pci=_pci, azimuth=_azimuth, lnglat=lnglat, enb=enb, cellid=_cellid) # ' abc '.strip()去除字串前後的空格 except Exception as e: print(repr(e)) print('插入小區工參時報錯,可能與資料庫有關,第' + str(i) + '行,小區名' + str(_cellname)) # get_or_create函式返回元組的第二個資料表示是否新建立了一條資料 if createdornot2: insertcount += 1 print("匯入" + str(insertcount) + "條資料") # 對enbid 'float' object使用strip函式時報錯,所以增加這個函式,使用strip前判斷一下是不是str型別。 @staticmethod def alltypestrip(args): return args.strip() if type(args) is str else args # 插入鄰區關係 def insertadj(self, scgi='本地小區CGI', tcgi='鄰小區CGI'): insertcount = 0 starttime = datetime.datetime.now() for i in range(1, self.sheet.nrows): if i % 2000 == 0: delta = datetime.datetime.now() - starttime print('正在錄入第'+str(i)+'行,用時'+str(delta)) row = self.sheet.row_values(i) _scgi = self.alltypestrip(row[self.col_dict[scgi]]) _tcgi = self.alltypestrip(row[self.col_dict[tcgi]]) # 如果源小區或目的小區查不到,將cgi寫入陣列中,後續將資料放入dict,用於輸出 mismatchedcgi = [] # 按照cgi查詢源小區和目的小區,用於後續建立多對多表 source = None try: source = ZJcell.objects.get(cgi__exact=_scgi) except (ZJcell.DoesNotExist, MultipleObjectsReturned) as e: print(repr(e)) print('小區工參表中沒有找到第' + str(i) + '行源小區cgi,無法新增多對多鄰區關係......:' + _scgi) mismatchedcgi.append(_scgi) # self.mismatchedcgi.update({str(i): _scgi}) target = None try: target = ZJcell.objects.get(cgi__exact=_tcgi) except (ZJcell.DoesNotExist, MultipleObjectsReturned) as e: print(repr(e)) print('小區工參表中沒有找到第' + str(i) + '行目標小區cgi,無法新增多對多鄰區關係....:' + _tcgi) mismatchedcgi.append(_tcgi) # self.mismatchedcgi_t.update({str(i): _tcgi}) # 如果source和target中有一個為None,則不能新增鄰區關係 if source and target: ''' # 以下程式碼因效率低而廢棄 # scgi_tcgi = source.cgi + '&' + target.cgi inverse_adj_set = None try: # 查是否存在反向鄰區,存在的話,將反向鄰區的存在反向鄰區指示改為True inverse_adj_set = ZJadjacent.objects.filter(cellfrom=target, cellto=source) except Exception as e: print(repr(e)) print('查反向鄰區inverse_adj_set = ZJadjacent.objects.filter(cellfrom=target, cellto=source)報錯') # 正向鄰區關係的has_inverse_adj(是否存在反向鄰區指示,True表示存在反向鄰區,False表示不存在) has_inverse_adj = False # 如果存在反向鄰區,將反向鄰區的has_inverse_adj(存在反向鄰區指示)改為True if inverse_adj_set: # 不寫save()的話,下面這種賦值方式不會修改資料庫中的欄位。 # inverse_adj_set.first().has_inverse_adj = True inverse_adj_set.update(has_inverse_adj=True) # not F('has_inverse_adj') # 如果存在反向鄰區關係,正向鄰區關係的has_inverse_adj(存在反向鄰區指示)也設為True has_inverse_adj = True # 因為geodict1['azi1']的範圍是從-180到+180,而通常的方位角是0到360,所以需要轉換 # geodict1['azi2']+180也是這個原因 azi1 = geodict1['azi1'] if azi1 < 0: azi1 += 360 ''' # 計算兩站之間距離和方位角 geodict1 = Geodesic.WGS84.Inverse(source.lnglat.lat, source.lnglat.lng, target.lnglat.lat, target.lnglat.lng) # 因為Geodesic.WGS84.Inverse輸出的結果是-180到+180,需要轉換成0到360 azi1 = geodict1['azi1'] if geodict1['azi1'] >= 0 else geodict1['azi1'] + 360 # Geodesic.WGS84.Inverse輸出的azi2是1到2連線的方位角在2點的數值,當距離近時,地球近似為平面,所以基本與azi1一樣 # 想讓azi2表示站在2點看1點的方位角,所以要加180,但不希望出現360度,所以使用下面的三目運算將360改為0 azi2 = geodict1['azi2'] + 180 if geodict1['azi2'] != 180 else 0 # source_target_cgi = 2 ** 32 * source.eci + target.eci主鍵是64bit,前32bit是源小區,後32bit是目標小區 source_target_cgi = 2 ** 32 * source.eci + target.eci try: adj, iscreated = ZJadjacent.objects.get_or_create(source_target_cgi=source_target_cgi, cellfrom=source, cellto=target, distance=round(geodict1['s12']), azi1=round(azi1), azi2=round(azi2)) # 如果是新建立的一條資料,就將計數器加1 if iscreated: insertcount += 1 # source.adj.add(target) except Exception as e: print(repr(e)) print('因ZJadjacent.objects.create報錯,建立鄰區關係失敗:' + str(source) + '-' + str(target)) else: self.mismatchedcgis.extend(mismatchedcgi) self.mismatchedrowid.append(i) self.mismatchedcells.update({str(i): {'source': _scgi, 'target': _tcgi, 'mismatched': mismatchedcgi}}) print('共新增' + str(insertcount) + '條鄰區') # 將所有不匹配的鄰區中的cgi打印出來 def mismatchedadj(self, path=r'J:\工參表裡沒有鄰區表中cgi.xlsx'): df1 = pd.DataFrame(self.mismatchedcells).T df2 = pd.DataFrame({'工參中沒有的cgi': self.mismatchedcgis}) # subset='工參中沒有的cgi' 表示只考慮列名為:工參中沒有的cgi 這一列的重複項,不設則需考慮全部列,也可以設成多列 # inplace=True是直接在df2表中刪除重複項,如果設成inplace=False則不修改原表df2,而是返回去重後的新表 df2.drop_duplicates(subset='工參中沒有的cgi', inplace=True) with pd.ExcelWriter(path) as writer: df1.to_excel(writer, sheet_name='Sheet1') df2.to_excel(writer, sheet_name='Sheet2') # 計算鄰區對之間的距離,原來在匯入過程中計算距離,由於是逐條計算,而且鄰區條數過多,80萬條,導致效率低,執行緩慢 # 這個函式先把同站點的鄰區對的距離設為0,在通過批量計算相同經緯度之間的鄰區對之間的距離,批量更新資料庫 # 實際跑發現效率不高,廢棄 ''' @staticmethod def adjdistance(): samesite = ZJadjacent.objects.filter(cellfrom__lnglat=F('cellto__lnglat')) affectedrows = samesite.update(distance=0, azi1=None, azi2=None) diffsite = ZJadjacent.objects.exclude(cellto__lnglat=F('cellfrom__lnglat')) affectedrows += Cellinforeader.recursive(diffsite) print('共更新' + str(affectedrows) + '行鄰區資料的距離和方位角欄位') # 通過遞迴呼叫實現批量計算鄰區對之間的距離 # 用Cellinforeader.recursive()來呼叫靜態方法 @staticmethod def recursive(queryset): if queryset.exists: adj1 = queryset.first() lnglat1 = adj1.cellfrom.lnglat lnglat2 = adj1.cellto.lnglat # 計算兩站之間距離和方位角 geodict1 = Geodesic.WGS84.Inverse(lnglat1.lat, lnglat1.lng, lnglat2.lat, lnglat2.lng) # 因為Geodesic.WGS84.Inverse輸出的結果是-180到+180,需要轉換成0到360 azi1 = geodict1['azi1'] if geodict1['azi1'] >= 0 else geodict1['azi1'] + 360 # Geodesic.WGS84.Inverse輸出的azi2是1到2連線的方位角在2點的數值,當距離近時,地球近似為平面,所以基本與azi1一樣 # 想讓azi2表示站在2點看1點的方位角,所以要加180,但不希望出現360度,所以使用下面的三目運算將360改為0 azi2 = geodict1['azi2'] + 180 if geodict1['azi2'] != 180 else 0 # 這句通過Q()函式實現 or 或邏輯,查詢結果是源小區的經緯度是lnglat1且目標小區的經緯度是lnglat2的, # 或者源小區的經緯度是lnglat2且目標小區的經緯度是lnglat1的鄰區對 lnglat_set1 = queryset.filter(Q(cellfrom__lnglat=lnglat1) & Q(cellto__lnglat=lnglat2)) lnglat_set2 = queryset.filter(Q(cellfrom__lnglat=lnglat2) & Q(cellto__lnglat=lnglat1)) affectedrows = lnglat_set1.update(distance=round(geodict1['s12']), azi1=azi1, azi2=azi2) affectedrows += lnglat_set2.update(distance=round(geodict1['s12']), azi1=azi2, azi2=azi1) ''' ''' azi1=Case( When(cellfrom__lnglat=lnglat1, then=azi1), When(cellfrom__lnglat=lnglat2, then=azi2) ), azi2=Case( When(cellto__lnglat=lnglat2, then=azi2), # django.core.exceptions.FieldError: Joined field references are not permitted in this query When(cellto__lnglat=lnglat1, then=azi1) ), )''' ''' # 從鄰區結果集中,去除這兩個經緯度為起始和結束或結束和起始點的鄰區對,剩餘的結果集繼續迭代到這個函式中。 qset = queryset.exclude((Q(cellfrom__lnglat=lnglat1) & Q(cellto__lnglat=lnglat2)) | (Q(cellfrom__lnglat=lnglat2) & Q(cellto__lnglat=lnglat1))) # affectedrows += self.recursive(qset) 修改為如下形式為了將這個函式變為靜態函式。 affectedrows += Cellinforeader.recursive(qset) if affectedrows % 2000 < 20: print('已執行'+affectedrows+'行,當前時間'+datetime.datetime.now().strftime('%H:%M:%S')) return affectedrows else: return 0 ''' class Checkpci: """檢查pci衝突與混淆,得出可用pci""" # cgilist格式:['460-00-325632-129','460-00-325632-131'] D1 331 D1 332 # freqpcilist格式:[{'cellname': 'Z737423嘉興線務局LY建委大樓WZD_129', 'freq': 'D1', 'pci': 341}, # {'cellname': 'Z730485嘉興紡織大廈D_1', 'freq': 'D1', 'pci': 115}] # def __init__(self, cgilist=None, freqpcilist=None): def __init__(self, freqlist=None, mod3list=None): if not freqlist: freqlist = ['F1', 'F2', 'D1', 'D2', 'D3'] if not mod3list: mod3list = [0, 1, 2] self.freqpcimatrix = dict() for f in freqlist: self.freqpcimatrix[f] = [i for i in range(504) if i % 3 in mod3list] # 用於存放鄰區的鄰區的pci頻點小區名cgi self.adjdict = dict() # 根據輸入的頻點和PCI,從初始化freqpcimatrix頻點PCI矩陣中剔除相應頻點的PCI,引數格式("freq", "pci") def delfreqpci(self, freqpcilist=None): if freqpcilist: for freq, pci in freqpcilist: try: self.freqpcimatrix.get(freq).remove(pci) except (AttributeError, ValueError) as e: print(repr(e)) print('預置的頻點PCI矩陣中沒有' + freq + '頻點,或PCI' + str(pci)) # 根據輸入的小區cgi列表,查詢對應的小區頻點和PCI,從初始化頻點PCI矩陣中剔除 def delbycgi(self, cgilist=None, cellnamelist=None): freqpcilist = None if cgilist: try: freqpcilist = ZJcell.objects.filter(cgi__in=cgilist).values_list("freq", "pci") except TypeError as e: print(repr(e)) print('cgilist引數為空,或者格式不對') elif cellnamelist: try: freqpcilist = ZJcell.objects.filter(cellname__in=cellnamelist).values_list("freq", "pci") except TypeError as e: print(repr(e)) print('cellnamelist引數為空,或者格式不對') if freqpcilist: self.delfreqpci(freqpcilist=freqpcilist) # 根據輸入的小區,查詢該小區鄰區表,將這個小區及其鄰區的頻點PCI從列表中剔除。 def delbycelladj(self, cgi=None, cellname=None): source = None freqpcilist = [] if cgi: try: source = ZJcell.objects.get(cgi__exact=cgi) except (ZJcell.DoesNotExist, MultipleObjectsReturned) as e: print(repr(e)) print('鄰區表中的源小區cgi在小區表中不存在......:' + cgi) elif cellname: try: source = ZJcell.objects.get(cellname__exact=cellname) except (ZJcell.DoesNotExist, MultipleObjectsReturned) as e: print(repr(e)) print('鄰區表中的源小區cgi在小區表中不存在......:' + cgi) if source: freqpcilist = [(source.freq, source.pci)] # 增加.distinct()去重 adjfreqpcilist = source.adj.values_list('freq', 'pci').distinct() # 增加鄰區的鄰區列表,已方便人工核查 self.adjdict[source.cellname] = list(source.adj.values(鄰區的鄰區=F('cellname'), 鄰鄰區的cgi=F('cgi'), 頻點=F('freq'), 鄰鄰區的pci=F('pci'))) # extend方法是list[]特有的,'QuerySet' object has no attribute 'extend' freqpcilist.extend(adjfreqpcilist) # freqpcilist在擴充套件(extend)後仍是list型別,不會變為QuerySet if freqpcilist: # 直接呼叫根據頻點PCI列表刪除預設表中的頻點PCI項的函式delfreqpci,與上個函式delbycgi是平行的。 self.delfreqpci(freqpcilist=freqpcilist) # 輸入指定的鄰小區列表,將這些鄰區的鄰區中存在的頻點和pci組合從初始頻點PCI矩陣中剔除。 # 注意,這個函式與delbycgi()區別是,這個函式還需要查詢列表中的小區的鄰區,而delbycgi()只查列表中的小區。 def delbycelllist(self, readfrom=r'C:\Users\Administrator\Desktop\嘉興工參\鄰區cgi.xlsx', sheet_name='Sheet1', cgilist=None, cellnamelist=None): if cgilist: for cgi in cgilist: self.delbycelladj(cgi=cgi) elif cellnamelist: for cellname in cellnamelist: self.delbycelladj(cellname=cellname) elif readfrom: # header=None 表示沒有列投,從第一行開始就是資料 dfcgi = pd.read_excel(readfrom, sheet_name=sheet_name, header=None) # .stack()函式作用:轉置,列傳行,其實stack作用比轉置大,.T才是轉置 nparr = np.array(dfcgi.stack()) # .tolist()將np陣列轉為list陣列[] cgiarray = nparr.tolist() for cgi in cgiarray: self.delbycelladj(cgi=cgi) # print(cgi) def outputpci(self, path=r'J:\pci列表.xlsx'): df1 = pd.DataFrame(self.freqpcimatrix) # df.to_excel(path) # 建立空df表 df2 = pd.DataFrame() with pd.ExcelWriter(path) as writer: df1.to_excel(writer, sheet_name='未使用PCI') # 迴圈遍歷鄰區的鄰區字典中的每個鄰區列表,輸出到excel for source in self.adjdict: # 利用字典建立df表 df_tmp = pd.DataFrame(self.adjdict[source]) # 在df表的0列插入一列源小區名 df_tmp.insert(0, '規劃小區的鄰區', source) # 將某一個源小區的鄰區df表,合併到整個鄰區表的後面 df2 = df2.append(df_tmp, ignore_index=True) # 輸出整個鄰區表到另一個sheet df2.to_excel(writer, sheet_name='規劃小區的鄰區的鄰區列表') # 根據經緯度及搜尋範圍,查詢pci的最小複用距離,即pci列表中每個pci在所給經緯度點處,計算距離最近的同頻同pci小區的距離 class Findsamepci: """以經緯度點為圓心,查詢搜尋半徑內,與所給pci列表中相同PCI的小區,並計算小區到圓心距離,這個距離成為最小複用距離""" # 初始化函式的作用是確定圓心及半徑,並找出半徑內所有小區。注意,因為只找小區,不找小區的鄰區,為了儘可能多的找到 # 全部複用的PCI,所以半徑必須足夠大,一般應超過10公里(km),這樣才能從更廣的範圍內查詢最小複用距離。 def __init__(self, lng, lat, radius=20000): self.lng = lng self.lat = lat self.radius = radius north = Geodesic.WGS84.Direct(lat, lng, 0, radius) northlat = north['lat2'] south = Geodesic.WGS84.Direct(lat, lng, 180, radius) southlat = south['lat2'] east = Geodesic.WGS84.Direct(lat, lng, 90, radius) # 由於複製上面的'lat2'沒有改成'lon2',導致查詢結果為空。 eastlng = east['lon2'] west = Geodesic.WGS84.Direct(lat, lng, 270, radius) westlng = west['lon2'] self.cellinrectangle_list = ZJcell.objects.filter(lnglat__lng__gt=westlng, lnglat__lng__lt=eastlng, lnglat__lat__gt=southlat, lnglat__lat__lt=northlat) # 經過測試,這樣用相當於建立了兩個不同的QuerySet物件: ''' source_list = ZJcell.objects.filter(cgi__in=['460-00-325632-129','460-00-325632-131']) another_list=source_list another_list <QuerySet [<ZJcell: Z736934嘉興質檢所D_129>, <ZJcell: Z736934嘉興質檢所D_131>]> source_list <QuerySet [<ZJcell: Z736934嘉興質檢所D_129>, <ZJcell: Z736934嘉興質檢所D_131>]> another_list = source_list.exclude(cgi__exact='460-00-325632-129') another_list <QuerySet [<ZJcell: Z736934嘉興質檢所D_131>]> source_list <QuerySet [<ZJcell: Z736934嘉興質檢所D_129>, <ZJcell: Z736934嘉興質檢所D_131>]> ''' # 不用提前將矩形區域轉成圓形區域。到測距時,在判斷是否超範圍。 # self.cellincycle_list = cellinrectangle_list # 存放頻點-》PCI-》小區列表結構的dict,方便查詢 # self.freqpcicell_dict = None # 存放區域內已存在的頻點和PCI self.existed_freqpci_matrix = dict() if self.cellinrectangle_list: # freqpci_list = cellinrectangle_list.values('freq', 'pci').distinct().annotate(num=Count('id')) # 通過聚合化區域內所有站點的頻點,得到這些基站的頻點分類,如D1,F1,利用頻點分類作為資料字典的key freq_list = self.cellinrectangle_list.values('freq').distinct() # freq_list格式<QuerySet [{'freq': 'D1'}]> for freq in freq_list: # freqstr作為資料字典的key和過濾函式的查詢條件 freqk = freq['freq'] # 通過每個頻點,過濾後,列出所有不重複的pci,用於生成該頻點的pci列表。 pci_list = self.cellinrectangle_list.filter(freq__exact=freqk).values('pci').order_by('pci').distinct() # 下面的矩陣的格式:{'D1': [0, 1, 2, 3, ..., 503], 'F1': [0, 1, 2, 3, ..., 503], ...} self.existed_freqpci_matrix[freqk] = [pci['pci'] for pci in pci_list] # 查單個頻點PCI在圓點處的最小複用距離 def samepcidistance(self, freq, pci): specified_pci_cell_list = self.cellinrectangle_list.filter(freq__exact=freq, pci__exact=pci) distance = self.radius * 2 cgi = None cellname = None if specified_pci_cell_list: for cell in specified_pci_cell_list: geodict1 = Geodesic.WGS84.Inverse(self.lat, self.lng, cell.lnglat.lat, cell.lnglat.lng) if distance > geodict1['s12']: distance = geodict1['s12'] cgi = cell.cgi cellname = cell.cellname if distance > self.radius: distance = self.radius return {'cellname': cellname, 'cgi': cgi, 'distance': round(distance)} # 查頻點和PCI字典中,每個頻點PCI組合在圓心處的最小複用距離。 # 格式{'F1': {'1': {'cellname': 'Z736934嘉興質檢所D_129', 'cgi': '460-00-325632-129', 'distance': 20000}, # '2': {}...} # 'D1':{}} # def freqpcilistdistances(self, freqpcimatrix=None, saveto=r'J:/pci最小複用距離.xlsx'): if freqpcimatrix is None: freqpcimatrix = self.existed_freqpci_matrix # minpcidistance = {} minpcidistance = dict() column_name = ['cellname', 'cgi', 'freq', 'pci', 'distance'] df = pd.DataFrame([], columns=column_name) for freq in freqpcimatrix: # minpcidistance = {'F1':{}} minpcidistance[freq] = {} pcilist = freqpcimatrix[freq] for pci in pcilist: mindistance = self.samepcidistance(freq, pci) # minpcidistance = {'F1':{'1':{samepcidistance函式的返回值}}} minpcidistance[freq][str(pci)] = mindistance data1 = [[mindistance['cellname'], mindistance['cgi'], freq, pci, mindistance['distance']]] df1 = pd.DataFrame(data1, columns=column_name) df = df.append(df1, ignore_index=True) df.to_excel(saveto) return minpcidistance