def truncateGeojsonFeature(self, oGeoJSON: dict = None) -> None: sTitle = "GeoJSON airspaces truncate" if not oGeoJSON: oGeoJSON = self.oOutGeo #if self.oLog: # self.oLog.info(sTitle, outConsole=False) if poaffCst.cstGeoFeatures in oGeoJSON: oFeatures: dict = oGeoJSON[poaffCst.cstGeoFeatures] barre = bpaTools.ProgressBar(len(oFeatures), 20, title=sTitle) idx = 0 for oAs in oFeatures: idx += 1 oAsGeo = oAs[poaffCst.cstGeoGeometry] #get geometry self.truncateCoordinates( oAsGeo) #define the new coordinates of this geometry barre.update(idx) barre.reset() lSizeSrc: int = len(str(self.oInpGeo)) lSizeDst: int = len(str(self.oOutGeo)) lCompress: int = int((1 - (lSizeDst / lSizeSrc)) * 100) sCompress: str = "GeoJSON compressed at {0}%".format(lCompress) if self.oLog: self.oLog.info(sCompress, outConsole=True) else: print("(i)Info: " + sCompress) return
def saveAirspacesFilter(self, aContext) -> None: context = aContext[0] sMsg = "Prepare GeoJSON file - {0}".format(aContext[1]) self.oCtrl.oLog.info(sMsg) barre = bpaTools.ProgressBar(len(self.oAirspacesCatalog.oAirspaces), 20, title=sMsg, isSilent=self.oCtrl.oLog.isSilent) idx = 0 geojson = [] #Initialisation avant filtrage spécifique for o in self.geoAirspaces: oZone = o["properties"] idx+=1 include = not oZone["groupZone"] #Ne pas traiter les zones de type 'Regroupement' #if include and ("excludeAirspaceNotCoord" in oZone): #Ne pas traiter les zones qui n'ont pas de coordonnées geometriques # include = not oZone["excludeAirspaceNotCoord"] if include: include = False if context=="all": include = True elif context=="ifr": include = (not oZone["vfrZone"]) and (not oZone["groupZone"]) elif context=="vfr": include = oZone["vfrZone"] include = include or oZone.get("vfrZoneExt", False) #Exporter l'extension de vol possible en VFR de 0m jusqu'au FL175/5334m elif context=="ff": include = oZone["freeFlightZone"] include = include or oZone.get("freeFlightZoneExt", False) #Exporter l'extension de vol possible en VFR de 0m jusqu'au FL175/5334m include = include and (o["geometry"]["coordinates"]!=errLocalisationPoint) if include: geojson.append(o) barre.update(idx) barre.reset() if geojson: self.oCtrl.oAixmTools.writeGeojsonFile("airspaces", geojson, context) return
def parseGeographicBorders(self) -> None: sTitle = "Geographic borders" sXmlTag = "Gbr" sMsg = "Parsing {0} to GeoJSON - {1}".format(sXmlTag, sTitle) self.oCtrl.oLog.info(sMsg) if self.geoBorders == None: self.geoBorders = dict() oList = self.oCtrl.oAixm.root.find_all(sXmlTag, recursive=False) barre = bpaTools.ProgressBar(len(oList), 20, title=sMsg, isSilent=self.oCtrl.oLog.isSilent) idx = 0 geojson = [] for gbr in oList: idx += 1 j, l = self.gbr2json(gbr) geojson.append(j) self.geoBorders[gbr.GbrUid["mid"]] = LineString(l) barre.update(idx) barre.reset() self.oCtrl.oAixmTools.writeGeojsonFile("borders", geojson) return
def mergeOpenairAirspacesFile(self, sKeyFile: str, oFile: dict) -> None: if len(self.oAsCat.oGlobalCatalog) == 0: #Ctrl ../.. return if not oFile[ poaffCst. cstSpExecute]: #Flag pour prise en compte du traitement de fichier return self.oOpenair: dict = {} #Clean temporary dict fileOpenair = oFile[ poaffCst. cstSpOutPath] + sKeyFile + poaffCst.cstSeparatorFileName + poaffCst.cstAsAllOpenairFileName #Fichier comportant toutes les bordures de zones sTitle = "Openair airspaces consolidation - {0}".format(sKeyFile) self.oLog.info(sTitle + ": {1} --> {2}".format( sKeyFile, fileOpenair, oFile[poaffCst.cstSpProcessType]), outConsole=False) self.parseFile(fileOpenair, sKeyFile) oGlobalCats = self.oAsCat.oGlobalCatalog[ airspacesCatalog. cstKeyCatalogCatalog] #Récupération de la liste des zones consolidés barre = bpaTools.ProgressBar(len(oGlobalCats), 20, title=sTitle) idx = 0 for sGlobalKey, oGlobalCat in oGlobalCats.items( ): #Traitement du catalogue global complet idx += 1 if oGlobalCat[ airspacesCatalog.cstKeyCatalogKeySrcFile] == sKeyFile: #self.oLog.info(" --> Openair airspace consolidation {0}".format(sGlobalKey), outConsole=False) sUId = oGlobalCat["UId"] #Depuis le 06/10 ; Préserver les zones particulières de type Point if oGlobalCat.get("excludeAirspaceNotFfArea", False) == True and oGlobalCat.get( "geometryType", "") != "Point": None #Ne pas inclure cette zone sans bordure elif sUId in self.oOpenair: if sGlobalKey[:len( poaffCst.cstPoaffCloneObject )] == poaffCst.cstPoaffCloneObject: #Ex:'PoaffClone-1566263' #Cas spécifique d'un objet volontairement clôné dans le catalogue oAs: OpenairZone = deepcopy( self.oOpenair[sUId] ) #Instentiation d'un clône d'objet source else: oAs: OpenairZone = self.oOpenair[ sUId] #Instentiation de l'objet source oAs.sGUId = sGlobalKey #Stockage de la clé principale oAs.oCat = oGlobalCat #Référencement du Catalog de propriétés de cette zone self.oGlobalOpenair.update({sGlobalKey: oAs}) else: self.oLog.error( "Openair airspace not found in file - {0}".format( sUId), outConsole=False) barre.update(idx) barre.reset() return
def saveAirspacesFilter2(self, aContext: list, gpsType: str = "", exceptDay: str = "") -> None: context = aContext[0] sMsg = "Prepare Openair file - {0} / {1} / {2}".format( aContext[1], gpsType, exceptDay) self.oCtrl.oLog.info(sMsg) barre = bpaTools.ProgressBar(len(self.oAirspacesCatalog.oAirspaces), 20, title=sMsg, isSilent=self.oCtrl.oLog.isSilent) idx = 0 openair = [] #Initialisation avant filtrage spécifique for o in self.geoAirspaces: oZone = o["properties"] idx += 1 include = not oZone[ "groupZone"] #Ne pas traiter les zones de type 'Regroupement' if include and ( "excludeAirspaceNotCoord" in oZone ): #Ne pas traiter les zones qui n'ont pas de coordonnées geometriques include = not oZone["excludeAirspaceNotCoord"] if include: if len(o["geometry"]) == 1: #Exclure toutes zones en erreur de localisation (oAs.oBorder[0]!=errLocalisationPoint) include = False elif len( o["geometry"]) == 2 and o["geometry"][0][:4] != "V X=": #Exclure les doubles points fixes (DP.. + DP..) mais autoriser les cercles (V X=.. + DP..) include = False else: include = False if context == "all": include = True elif context == "ifr": include = (not oZone["vfrZone"]) and ( not oZone["groupZone"]) elif context == "vfr": include = oZone["vfrZone"] include = include or oZone.get( "vfrZoneExt", False ) #Exporter l'extension de vol possible en VFR de 0m jusqu'au FL175/5334m elif context == "ff": include = oZone["freeFlightZone"] include = include or oZone.get( "freeFlightZoneExt", False ) #Exporter l'extension de vol possible en VFR de 0m jusqu'au FL175/5334m if include == True and exceptDay != "": if exceptDay in oZone: include = False if include == True: openair.append(makeOpenair(o, gpsType)) barre.update(idx) barre.reset() if openair: self.oCtrl.oAixmTools.writeOpenairFile("airspaces", openair, context, gpsType, exceptDay) return
def cleanAirspacesCalalog(self, airspacesCatalog) -> None: if not self.geoAirspaces: #Contrôle si le fichier est vide return self.oAirspacesCatalog = airspacesCatalog #if self.oAirspacesCatalog.cleanAirspacesCalalog: #Contrôle si l'optimisation est déjà réalisée # return sMsg = "Clean catalog" self.oCtrl.oLog.info(sMsg) barre = bpaTools.ProgressBar(len(self.geoAirspaces), 20, title=sMsg, isSilent=self.oCtrl.oLog.isSilent) idx = 0 lNbChange = 0 for o in self.geoAirspaces: oZone = o["properties"] idx += 1 #Flag all not valid area oGeom: dict = o[ cstGeometry] #Sample - "geometry": {"type": "Polygon", "coordinates": [[['DP 44:21:02 N 001:28:43 E'], ../.. if len(oGeom) == 0: oZone.update({"excludeAirspaceNotCoord": True}) #Flag this change in catalog lNbChange += 1 if len(oGeom) == 1: #Point exclude = True oZone.update({"geometryType": "Point"}) #info for catalog "Point" #Transformer systématiquement les 'Point simples' en cercle de rayon 1 Km = 0.54 NM (Nautic Milles) #exemple d'un Point d'ogigine: ['DP 44:21:02 N 001:28:43 E'] #se transforme en - ['V X=44:21:02 N 001:28:43 E', 'DC 0.54'] oGeom[0] = "V X=" + oGeom[0][3:] oGeom.append("DC 0.54") elif len(oGeom) == 2 and oGeom[0][:4] != "V X=": #Line exclude = True else: exclude = False if exclude: oZone.update({"freeFlightZone": False}) #Change value in catalog if oZone.get("freeFlightZoneExt", False) == True: oZone.update({"freeFlightZoneExt": False}) #Change value in catalog oZone.update({"excludeAirspaceNotFfArea": True}) #Flag this change in catalog lNbChange += 1 barre.update(idx) barre.reset() if lNbChange > 0: self.oAirspacesCatalog.saveAirspacesCalalog( ) #Save the new catalogs self.oAirspacesCatalog.cleanAirspacesCalalog = True #Marqueur de traitement réalisé return
def cleanAirspacesCalalog(self, airspacesCatalog) -> None: if not self.geoAirspaces: #Contrôle si le fichier est vide return self.oAirspacesCatalog = airspacesCatalog if self.oAirspacesCatalog.cleanAirspacesCalalog: #Contrôle si l'optimisation est déjà réalisée return sMsg = "Clean catalog" self.oCtrl.oLog.info(sMsg) barre = bpaTools.ProgressBar(len(self.geoAirspaces), 20, title=sMsg, isSilent=self.oCtrl.oLog.isSilent) idx = 0 lNbChange = 0 for o in self.geoAirspaces: oZone = o[cstGeoProperties] idx += 1 #Flag all not valid area oGeom: dict = o[ cstGeojsonGeometry] #Sample - "geometry": {"type": "Polygon", "coordinates": [[[3.069444, 45.943611], [3.539167, 45.990556], ../.. if oGeom[cstGeoType] in [ cstGeoPoint ] and oGeom[cstGeoCoordinates] == errLocalisationPoint: oZone.update({"excludeAirspaceNotCoord": True}) #Flag this change in catalog lNbChange += 1 #if oZone["freeFlightZone"]: if oGeom[cstGeoType] in [cstGeoPoint, cstGeoLine]: exclude = True elif len(oGeom[cstGeoCoordinates][0]) < 3: exclude = True else: exclude = False if exclude: oZone.update({"freeFlightZone": False}) #Change value in catalog if oZone.get("freeFlightZoneExt", False) == True: oZone.update({"freeFlightZoneExt": False}) #Change value in catalog oZone.update({"excludeAirspaceNotFfArea": True}) #Flag this change in catalog oZone.update({"geometryType": oGeom[cstGeoType] }) #info for catalog 'Point' or 'LineString' lNbChange += 1 barre.update(idx) barre.reset() if lNbChange > 0: self.oAirspacesCatalog.saveAirspacesCalalog( ) #Save the new catalogs self.oAirspacesCatalog.cleanAirspacesCalalog = True #Marqueur de traitement réalisé return
def parseAirspacesBorders(self, airspacesCatalog) -> None: self.oAirspacesCatalog = airspacesCatalog #Controle de prerequis if self.geoBorders == None: self.parseGeographicBorders() sTitle = "Airspaces Borders" sXmlTag = "Abd" if not self.oCtrl.oAixm.root.find(sXmlTag, recursive=False): sMsg = "Missing tags {0} - {1}".format(sXmlTag, sTitle) self.oCtrl.oLog.warning(sMsg, outConsole=True) return sMsg = "Parsing {0} to GeoJSON - {1}".format(sXmlTag, sTitle) self.oCtrl.oLog.info(sMsg) barre = bpaTools.ProgressBar(len(self.oAirspacesCatalog.oAirspaces), 20, title=sMsg, isSilent=self.oCtrl.oLog.isSilent) idx = 0 self.geoAirspaces = [] #Réinitialisation avant traitement global for k,oZone in self.oAirspacesCatalog.oAirspaces.items(): idx+=1 if not oZone["groupZone"]: #Ne pas traiter les zones de type 'Regroupement' sAseUid = oZone["UId"] #Map 20200801 #if oZone["id"] in ["EBS27"]: #[W] LOW FLYING AREA GOLF ONE # self.oCtrl.oLog.critical("just for bug {0}".format(oZone["id"]), outConsole=False) oBorder = self.oAirspacesCatalog.findAixmObjectAirspacesBorders(sAseUid) if oBorder: self.parseAirspaceBorder(oZone, oBorder) else: sAseUidBase = self.oAirspacesCatalog.findZoneUIdBase(sAseUid) #Identifier la zone de base (de référence) if sAseUidBase==None: self.oCtrl.oLog.warning("Missing Airspaces Borders AseUid={0} of {1}".format(sAseUid, oZone["nameV"]), outConsole=False) self.geoAirspaces.append({"type":"Feature", "properties":oZone, "geometry":{"type": "Point", "coordinates": errLocalisationPoint}}) else: geom = self.findJsonObjectAirspacesBorders(sAseUidBase) #Recherche si la zone de base a déjà été parsé if geom: if geom["coordinates"]==errLocalisationPoint: self.oCtrl.oLog.warning("Missing Airspaces Borders sAseUidBase={0} used by AseUid={1} of {2}".format(sAseUidBase, sAseUid, oZone["nameV"]), outConsole=False) self.geoAirspaces.append({"type":"Feature", "properties":oZone, "geometry":geom}) else: oBorder = self.oAirspacesCatalog.findAixmObjectAirspacesBorders(sAseUidBase) if oBorder==None: self.oCtrl.oLog.warning("Missing Airspaces Borders AseUid={0} AseUidBase={1} of {2}".format(sAseUid, sAseUidBase, oZone["nameV"]), outConsole=False) self.geoAirspaces.append({"type":"Feature", "properties":oZone, "geometry":{"type": "Point", "coordinates": errLocalisationPoint}}) else: self.parseAirspaceBorder(oZone, oBorder) barre.update(idx) barre.reset() return
def parseAirspacesBorders(self, airspacesCatalog) -> None: self.oAirspacesCatalog = airspacesCatalog #Controle de prerequis if self.geoBorders == None: self.parseGeographicBorders() sTitle = "Airspaces Borders" sXmlTag = "Abd" if not self.oCtrl.oAixm.root.find(sXmlTag, recursive=False): sMsg = "Missing tags {0} - {1}".format(sXmlTag, sTitle) self.oCtrl.oLog.warning(sMsg, outConsole=True) return sMsg = "Parsing {0} to OpenAir - {1}".format(sXmlTag, sTitle) self.oCtrl.oLog.info(sMsg) barre = bpaTools.ProgressBar(len(self.oAirspacesCatalog.oAirspaces), 20, title=sMsg, isSilent=self.oCtrl.oLog.isSilent) idx = 0 self.geoAirspaces = [] #Réinitialisation avant traitement global for k,oZone in self.oAirspacesCatalog.oAirspaces.items(): idx+=1 if not oZone["groupZone"]: #Ne pas traiter les zones de type 'Regroupement' sAseUid = oZone["UId"] oBorder = self.oAirspacesCatalog.findAixmObjectAirspacesBorders(sAseUid) if oBorder: self.parseAirspaceBorder(oZone, oBorder) else: sAseUidBase = self.oAirspacesCatalog.findZoneUIdBase(sAseUid) #Identifier la zone de base (de référence) if sAseUidBase==None: self.oCtrl.oLog.warning("Missing Airspaces Borders AseUid={0} of {1}".format(sAseUid, oZone["nameV"]), outConsole=False) self.geoAirspaces.append({"type":"Openair", "properties":oZone, "geometry":errLocalisationPoint}) else: openair = self.findOpenairObjectAirspacesBorders(sAseUidBase) #Recherche si la zone de base a déjà été parsé if openair: self.geoAirspaces.append({"type":"Openair", "properties":oZone, "geometry":openair}) else: oBorder = self.oAirspacesCatalog.findAixmObjectAirspacesBorders(sAseUidBase) if oBorder==None: self.oCtrl.oLog.warning("Missing Airspaces Borders AseUid={0} AseUidBase={1} of {2}".format(sAseUid, sAseUidBase, oZone["nameV"]), outConsole=False) self.geoAirspaces.append({"type":"Openair", "properties":oZone, "geometry":errLocalisationPoint}) else: self.parseAirspaceBorder(oZone, oBorder) barre.update(idx) barre.reset() return
def syncFrequecies(self, oCat:dict) -> None: sTitle = "Airspaces frequencies" sMsg = "Synchronyse {0}".format(sTitle) self.oLog.info(sMsg) barre = bpaTools.ProgressBar(len(self.oFrequecies), 20, title=sMsg) idx = 0 for sAsKey, oFreqs in self.oFrequecies.items(): idx+=1 #OLD - Organiser les frequences radio par un tri alphabétique des typologies #oNewFrequencies:dict = {} #aFreqKeySort = sorted(oFreqs.keys()) #for sFreqKey in aFreqKeySort: # oNewFrequencies.update({sFreqKey:oFreqs[sFreqKey]}) #NEW - Organiser les frequences radio par la priorisation des typologies oNewFrequencies:dict = {} aFreqKeySort = sorted(oFreqs.keys()) for sFreqType in cstFreqTypePriority: for sFreqKey in aFreqKeySort: if sFreqKey[0:len(sFreqType)] == sFreqType: oNewFrequencies.update({sFreqKey:oFreqs[sFreqKey]}) for sFreqKey in aFreqKeySort: if not (sFreqKey in oNewFrequencies): oNewFrequencies.update({sFreqKey:oFreqs[sFreqKey]}) #Localisation principale sGUID:str = sAsKey self.affecFrequecies(oCat, sGUID, oNewFrequencies) self.syncGUIDdoublons(oCat, sGUID, oNewFrequencies) #Complement de recherche pour extension de zone #Exp: LFBD + LFBD1-1 + LFBD1-2 + LFBD2-1 + LFBD2-2 ../.. + LFBD7 + LFBD7.10 + LFBD7.20 + ../.. + LFBD10 #Exp: LFLC + LFLC1 + LFLC1.20 + LFLC2.1 + LFLC2.1.20 #Warning! des '-', des '.' ou des '0' finaux commes LFBD7.10 et LFBD7.20 for lIdx1 in range(1, 20): #Ajout indice sur clé locale pour localiser les extentions des zone sGUID1:str = sGUID + str(lIdx1) self.affecFrequecies(oCat, sGUID1, oNewFrequencies) self.syncGUIDdoublons(oCat, sGUID1, oNewFrequencies) self.syncGUID0(oCat, sGUID1, oNewFrequencies, "-") self.syncGUID0(oCat, sGUID1, oNewFrequencies, ".") #else: # self.oLog.error("syncFrequecies() - Airspace not found {0} - {1}".format(sAsKey, oFreqs), outConsole=False) barre.update(idx) barre.reset() return
def parseFile(self, sSrcFile: str, sKeyFile: str) -> None: sTitle = "Openair airspaces parsing file - {0}".format(sKeyFile) self.oLog.info(sTitle, outConsole=False) fopen = open(sSrcFile, "rt", encoding="cp1252", errors="ignore") #or encoding="utf-8" lines = fopen.readlines() barre = bpaTools.ProgressBar(len(lines), 20, title=sTitle) idx = 0 for line in lines: idx += 1 lig = cleanLine(line) self.parseLine(lig) barre.update(idx) barre.reset() #print() #self.oLog.info("--> {} Openair parsing zones".format(len(self.oOpenair)), outConsole=False) return
def parseObstacles(self) -> None: sTitle = "Obstacles" sXmlTag = "Obs" sMsg = "Parsing {0} to GeoJSON - {1}".format(sXmlTag, sTitle) self.oCtrl.oLog.info(sMsg) oList = self.oCtrl.oAixm.root.find_all(sXmlTag, recursive=False) barre = bpaTools.ProgressBar(len(oList), 20, title=sMsg, isSilent=self.oCtrl.oLog.isSilent) idx = 0 geojson = [] for obs in oList: idx+=1 geojson.append(self.obs2json(obs)) barre.update(idx) barre.reset() self.oCtrl.oAixmTools.writeGeojsonFile("obstacles", geojson) return
def parseRunwayCenterLinePosition(self) -> None: sTitle = "Runway Center Line Position" sXmlTag = "Rcp" sMsg = "Parsing {0} to GeoJSON - {1}".format(sXmlTag, sTitle) self.oCtrl.oLog.info(sMsg) oList = self.oCtrl.oAixm.root.find_all(sXmlTag, recursive=False) barre = bpaTools.ProgressBar(len(oList), 20, title=sMsg, isSilent=self.oCtrl.oLog.isSilent) idx = 0 geojson = [] for rcp in oList: idx+=1 geojson.append(self.rcp2json(rcp)) barre.update(idx) barre.reset() self.oCtrl.oAixmTools.writeGeojsonFile("runwaysCenter", geojson) return
def mergeGeoJsonAirspacesFile(self, sKeyFile: str, oFile: dict) -> None: if len(self.oAsCat.oGlobalCatalog) == 0: #Ctrl ../.. return if not oFile[ poaffCst. cstSpExecute]: #Flag pour prise en compte du traitement de fichier return fileGeoJSON = oFile[ poaffCst. cstSpOutPath] + sKeyFile + poaffCst.cstSeparatorFileName + poaffCst.cstAsAllGeojsonFileName #Fichier comportant toutes les bordures de zones sTitle = "GeoJSON airspaces consolidation - {0}".format(sKeyFile) self.oLog.info(sTitle + ": {0} --> {1}".format( fileGeoJSON, oFile[poaffCst.cstSpProcessType]), outConsole=False) self.makeGeojsonIndex(fileGeoJSON, sKeyFile) oGlobalCats = self.oAsCat.oGlobalCatalog[ airspacesCatalog. cstKeyCatalogCatalog] #Récupération de la liste des zones consolidés barre = bpaTools.ProgressBar(len(oGlobalCats), 20, title=sTitle) idx = 0 for sGlobalKey, oGlobalCat in oGlobalCats.items( ): #Traitement du catalogue global complet if oGlobalCat[ airspacesCatalog.cstKeyCatalogKeySrcFile] == sKeyFile: idx += 1 #self.oLog.info(" --> GeoJSON airspace consolidation {0}".format(sGlobalKey), outConsole=False) sUId = oGlobalCat["UId"] if sUId in self.oIdxGeoJSON: oAs = self.oIdxGeoJSON[sUId] oRet = self.oGeoRefArea.evalAreasRefInclusion( sGlobalKey, oAs) oGlobalCat.update(oRet) self.oGlobalGeoJSON.update({sGlobalKey: oAs}) else: self.oLog.error( "GeoJSON airspace not found in file - {0}".format( sUId), outConsole=False) barre.update(idx) barre.reset() self.oAsCat.saveCatalogFiles() return
def parseControlTowers(self) -> None: sTitle = "Aerodrome control towers" sXmlTag = "Uni" sMsg = "Parsing {0} to GeoJSON - {1}".format(sXmlTag, sTitle) self.oCtrl.oLog.info(sMsg) oList = self.oCtrl.oAixm.root.find_all(sXmlTag, recursive=False) barre = bpaTools.ProgressBar(len(oList), 20, title=sMsg, isSilent=self.oCtrl.oLog.isSilent) idx = 0 geojson = [] for uni in oList: idx+=1 twr = self.tower2json(uni) if twr: geojson.append(twr) barre.update(idx) barre.reset self.oCtrl.oAixmTools.writeGeojsonFile("towers", geojson) return
def makeGeojsonIndex(self, fileGeoJSON: str, sKeyFile: str) -> None: sTitle = "GeoJSON airspaces make index - {0}".format(sKeyFile) self.oIdxGeoJSON = {} #Clean previous data ofileGeoJSON: dict = bpaTools.readJsonFile( fileGeoJSON) #Chargement des zones if poaffCst.cstGeoFeatures in ofileGeoJSON: oFeatures = ofileGeoJSON[poaffCst.cstGeoFeatures] barre = bpaTools.ProgressBar(len(oFeatures), 20, title=sTitle) idx = 0 for oAs in oFeatures: idx += 1 oAsProp = oAs[poaffCst.cstGeoProperties] sUId = oAsProp["UId"] #Depuis le 06/10 ; Préserver les zones particulières de type Point if oAs.get( "excludeAirspaceNotFfArea", False) == True and oAs.get( "geometryType", "") != poaffCst.cstGeoPoint: None #Ne pas inclure cette zone sans bordure else: oAsGeo = oAs[poaffCst.cstGeoGeometry] self.oIdxGeoJSON.update({sUId: oAsGeo}) barre.update(idx) barre.reset() return
def parseUnknownGroundHeightRef(self) -> None: #Chargement des éléments inconnus du référetentiel sSrcFileName = self.refPath + self.sUnknownGroundHeightFileName if not os.path.exists(sSrcFileName): self.oLog.error("File not found - {0} ".format(sSrcFileName), outConsole=True) return oUnknownGroundHeight = bpaTools.readJsonFile(sSrcFileName) oGroundEstimatedHeight = dict() oFeatures = dict() if len(oUnknownGroundHeight) == 0: self.oLog.warning( "Empty reference file : {0}".format(sSrcFileName), outConsole=False) else: self.oLog.info("Load reference file : {0}".format(sSrcFileName), outConsole=False) #Select dataObject in src file oUnknownHeader = oUnknownGroundHeight[ self.sHead] #Get the header file oUnknownContent = oUnknownGroundHeight[ self.sRefe] #Get the content of referential #oNewUnknownContent = deepcopy(oUnknownContent) #Clone the initial repository for clean #Let specific header file & save the source file sHeadFileName = "_{0}_".format(oUnknownHeader["srcAixmOrigin"]) #Sauvegarde systématique des données manquantes dans le référentiel sCpyFileName = "{0}{1}{2}_{3}".format( self.refPath, sHeadFileName, bpaTools.getDateNow(), self.sUnknownGroundHeightFileName) shutil.copyfile(sSrcFileName, sCpyFileName) #Référentiel de destination (.json) sRefFileNameJson = "{0}{1}{2}".format( self.refPath, sHeadFileName, self.sGroundHeightFileNameJson) #Sauvegarde initiale de la première instance du jour (si pas déjà existant pour ne pas perdre les données en cas réexécution pour mises au point) if os.path.exists(sRefFileNameJson): sCpyFileName = "{0}{1}{2}_{3}".format( self.refPath, sHeadFileName, bpaTools.getDateNow(), self.sGroundHeightFileNameJson) if not os.path.exists(sCpyFileName): shutil.copyfile(sRefFileNameJson, sCpyFileName) self.oLog.info( "Save initial reference file : {0} --> {1}".format( sRefFileNameJson, sCpyFileName), outConsole=False) #Visualiseur du référentiel (.geojson) sRefFileNameGeoj = "{0}{1}{2}".format( self.refPath, sHeadFileName, self.sGroundHeightFileNameGeoj) #Sauvegarde initiale de la première instance du jour (si pas déjà existant pour ne pas perdre les données en cas réexécution pour mises au point) if os.path.exists(sRefFileNameGeoj): sCpyFileName = "{0}{1}{2}_{3}".format( self.refPath, sHeadFileName, bpaTools.getDateNow(), self.sGroundHeightFileNameGeoj) if not os.path.exists(sCpyFileName): shutil.copyfile(sRefFileNameGeoj, sCpyFileName) self.oLog.info( "Save initial reference file : {0} --> {1}".format( sRefFileNameGeoj, sCpyFileName), outConsole=False) #Chargement du reférentiel initial (si déjà existant ; pour ajout de la complétude des données manquantes) oJson = bpaTools.readJsonFile(sRefFileNameJson) if self.sRefe in oJson: oGroundEstimatedHeight = oJson[ self.sRefe] #Ne récupère que les datas du fichier #Chargement des zones avec description des bordures sGeoJsonFileName = self.srcPath + self.headFileName + "airspaces-all.geojson" if not os.path.exists(sGeoJsonFileName): sGeoJsonFileName = self.srcPath + self.headFileName + "airspaces-vfr.geojson" if not os.path.exists(sGeoJsonFileName): sGeoJsonFileName = self.srcPath + self.headFileName + "airspaces-freeflight.geojson" if not os.path.exists(sGeoJsonFileName): self.oLog.error( "File not found - {0} ".format(sGeoJsonFileName), outConsole=True) return oGeoJsondata = bpaTools.readJsonFile(sGeoJsonFileName) self.oLog.info( "Load source data file : {0}".format(sGeoJsonFileName), outConsole=False) if self.sFeat in oGeoJsondata: oFeatures = oGeoJsondata[self.sFeat] #Analyse de toutes les zones manquante du référentiel barre = bpaTools.ProgressBar( len(oUnknownContent), 20, title="Unknown Ground Estimated Height") geoJSON = [] idx = 0 for sZoneUId, sDestKey in oUnknownContent.items(): idx += 1 #if sDestKey == "[TMA-P] GENEVE TMA1 (LSGG1)@HEI.1000.FT": # print("stop debug") oZone = self.findZone(sZoneUId, oFeatures) if oZone: #Valid area control (exclude the single point or line) if oZone[self.sGeom]["type"] in ["Point", "LineString"]: #del oNewUnknownContent[sZoneUId] #Remove area in new repositoy oGroundEstimatedHeight.update({sDestKey: errNoArea}) else: aGroundEstimatedHeight, objJSON = self.getGroundEstimatedHeight( oZone ) #Détermine la hauteur sol moyenne (dessous la zone) if objJSON != {}: oGroundEstimatedHeight.update({ sDestKey: aGroundEstimatedHeight }) #Ajoute un point d'entrée attendu #sMsg = "Update Reference Data: sZoneUId={0} - key={1} - {2}m".format(sZoneUId, sDestKey, aGroundEstimatedHeight) #self.oLog.info(sMsg, outConsole=False) for g in objJSON: geoJSON.append(g) barre.update(idx) barre.reset() #if len(oNewUnknownContent) != len(oUnknownContent): # #Generate new repository if clean data # oNewRepository:dict = {} # oNewRepository.update({self.sHead:oUnknownHeader}) # oNewRepository.update({self.sRefe:oNewUnknownContent}) # bpaTools.writeJsonFile(sSrcFileName, oNewRepository) if len(oGroundEstimatedHeight) > 0: #Contruction du nouveau référentiel header = dict() header.update({"srcAixmOrigin": oUnknownHeader["srcAixmOrigin"]}) out = {self.sHead: header, self.sRefe: oGroundEstimatedHeight} bpaTools.writeJsonFile(sRefFileNameJson, out) self.oLog.info( "Written Referential Ground Estimated Height: {0} heights in file {1}" .format(len(oGroundEstimatedHeight), sRefFileNameJson), outConsole=True) #Construction du fichier geojson représentatif du référentiel; y compris la précision du cadrage des zones référencées... with open(sRefFileNameGeoj, "w", encoding="utf-8") as output: output.write( json.dumps( { "type": "FeatureCollection", self.sFeat: geoJSON }, ensure_ascii=False)) self.oLog.info( "Written GeoJSON View of Referential Ground Estimated Height: {0} features in file {1}" .format(len(oGroundEstimatedHeight), sRefFileNameGeoj), outConsole=True) return
def loadFrequecies(self) -> None: sTitle = "Airspaces frequencies" sXmlTag = "FrequenceS" oFreqS = self.situation.find(sXmlTag, recursive=False) if not oFreqS: sMsg = "Missing tags {0} - {1}".format(sXmlTag, sTitle) self.oLog.warning(sMsg, outConsole=True) return sXmlTag = "Frequence" if not oFreqS.find(sXmlTag, recursive=False): sMsg = "Missing tags {0} - {1}".format(sXmlTag, sTitle) self.oLog.warning(sMsg, outConsole=True) return sMsg = "Indexing {0} - {1}".format(sXmlTag, sTitle) self.oLog.info(sMsg) oList = oFreqS.find_all(sXmlTag, recursive=False) barre = bpaTools.ProgressBar(len(oList), 20, title=sMsg) idx = 0 #oFreqKey:dict = {} #oHorCode:dict = {} #oRemarque:dict = {} oAixmTools = aixmReader.AixmTools(None) for o in oList: idx+=1 if len(o)>1: #Exclure les tags interne de type '<Frequence>360.125</Frequence>' sFreq = o.Frequence.string if (float(sFreq)>=118) and (float(sFreq)<=136): sLocKey:str = o["lk"] #lk="[LF][RK][TWR CAEN Tour][134.525] sLocKey = sLocKey.replace("[", "") aLocKey:list = sLocKey.split("]") if len(aLocKey)>=3: #Exclure les Frequences sans référence exp - <Frequence lk="null[122.100]" pk="300054"> sAsKey:str = aLocKey[0] + aLocKey[1] #AirspaceKey = 'LFRK' aFreqKey:list = aLocKey[2].split(" ") sFreqKey:str = aFreqKey[0] #FrequenceKey = 'TWR','APP' etc... bExclude:bool = bool(sFreqKey in ["VDF","UAC","UTA","SRE","CEV","PAR"]) #Exclure certains typage de fréquences if not bExclude and o.Remarque: #Exclure certaines fréquences non utiles sRem:str = o.Remarque.string bExclude = bExclude or ("ur instruction" in sRem.lower()) #'Sur instruction ../..' ou 'FREQ sur instruction' etc... bExclude = bExclude or ("radar" in sRem.lower()) #'Sur instruction ../..' ou 'FREQ sur instruction' etc... bExclude = bExclude or (sRem[0:24] == "Diffusion des paramètres") bExclude = bExclude or (sRem[0:10] == "Exploitant") bExclude = bExclude or (sRem[0:15] == "CTL ACFT au sol") bExclude = bExclude or (sRem[0:20] == "Contrôle ACFT au sol") bExclude = bExclude or (sRem[0:18] == "Circulation au sol") bExclude = bExclude or (sRem[0:13] == "Fréquence SOL") bExclude = bExclude or (sRem[0: 7] == "Roulage") bExclude = bExclude or (sRem[0:11] == "FREQ Départ") bExclude = bExclude or (sRem[0:15] == "FREQ de secours") bExclude = bExclude or (sRem[0:37] == "Au profit des postes de stationnement") bExclude = bExclude or (sRem[0: 8] == "Détresse") bExclude = bExclude or (sRem[0:27] == "Coordinations des activités") if not bExclude: bExclude = bExclude or (aLocKey[2][0:3]=="TWR" and aLocKey[2][-3:]=="Sol") #[TWR SAINT YAN Sol] a filtrer / [TWR SAINT YAN Tour] a garder bExclude = bExclude or (aLocKey[2][0:3]=="TWR" and aLocKey[2][-6:]=="Prévol") bExclude = bExclude or (aLocKey[2][0:3]=="TWR" and aLocKey[2][-1:]==".") #[LF][MD][TWR LERINS .] a filtrer / [TWR CANNES Tour] a garder bExclude = bExclude or (aLocKey[2][0:3]=="APP" and aLocKey[2][-8:]=="Contrôle") #[APP BALE Contrôle] a filtrer / [APP ... Approche] a garder if not bExclude: #Memorisation de fréquence if not sAsKey in self.oFrequecies: self.oFrequecies.update({sAsKey:{}}) oFreqList:dict = self.oFrequecies[sAsKey] if o.HorCode.string != "H24": sFreq += "*" #(*) = Horaire non permanent aDetFreq:list = [sFreq] if o.Remarque: sRem:str = oAixmTools.getField(o, "Remarque", "ret")["ret"] aDetFreq.append(sRem) sPhone:str = self.getPhoneNumber(sRem) if sPhone: aDetFreq.append(sPhone) if o.Suppletive: bSupp:bool = True if str(o.Suppletive.string).lower=="oui" else False sFreqKey = self.makeNewKey(sFreqKey, oFreqList, bSupp) else: sFreqKey = self.makeNewKey(sFreqKey, oFreqList) oFreqList.update({sFreqKey:aDetFreq}) #Analyses #oFreqKey.update({sFreqKey:sFreqKey}) #sObj:str = o.HorCode.string #oHorCode.update({sObj:sObj}) #if o.Remarque: # sObj:str = o.Remarque.string # oRemarque.update({sObj:sObj}) barre.update(idx) barre.reset() #self.oLog.info("oFreqKey={}".format(oFreqKey)) --> 2020-08-21 14:49:33,261 poaff v2.0.3 INFO oFreqKey={'VDF': 'VDF', 'UAC': 'UAC', 'APP': 'APP', 'AFIS': 'AFIS', 'TWR': 'TWR', 'ATIS': 'ATIS', 'FIS': 'FIS', 'null': 'null', 'A/A': 'A/A', 'SRE': 'SRE', 'CEV': 'CEV', 'PAR': 'PAR', '': '', 'CCM': 'CCM'} #self.oLog.info("oHorCode={}".format(oHorCode)) --> 2020-08-21 14:49:33,261 poaff v2.0.3 INFO oHorCode={'HO': 'HO', 'HX': 'HX', 'H24': 'H24'} #self.oLog.info("oRemarque={}".format(oRemarque)) --> 2020-08-24 15:59:55,798 poaff v2.0.3 INFO oRemarque={'Canal 8.33': 'Canal 8.33', 'Particulière transit/Particular to transit': 'Particulière transit/Particular to transit', 'Exploitant/Operator : DAC Polynésie Française': 'Exploitant/Operator : DAC Polynésie Française', 'Plus toutes fréquences de LORIENT / Over all frequencies from LORIENT.': 'Plus toutes fréquences de LORIENT / Over all frequencies from LORIENT.', 'Exploitant/Operator : Aéroport de Tahiti': 'Exploitant/Operator : Aéroport de Tahiti', 'TEL : 01 39 56 54 70': 'TEL : 01 39 56 54 70', 'Exploitant/Operator : DAC POLYNESIE FRANCAISE': 'Exploitant/Operator : DAC POLYNESIE FRANCAISE', 'Air/sol - Air /ground': 'Air/sol - Air /ground', 'Auto-information en Français uniquement.': 'Auto-information en Français uniquement.', 'Exploitant/Operator : Territoire': 'Exploitant/Operator : Territoire', 'Traversée maritime VFR et transit CTR.': 'Traversée maritime VFR et transit CTR.', 'Diffusion des paramètres de DEP et ARR.#\nDEP and ARR parameters broadcasting.': 'Diffusion des paramètres de DEP et ARR.#\nDEP and ARR parameters broadcasting.', "Transit CAG - RAI - Jusqu'au/until FL 200": "Transit CAG - RAI - Jusqu'au/until FL 200", 'Exploitant/Operator : AVA': 'Exploitant/Operator : AVA', 'Exploitant/Operator : AVA#Assure APP/APP operations': 'Exploitant/Operator : AVA#Assure APP/APP operations', 'Sur instruction TWR pour circulation au sol.': 'Sur instruction TWR pour circulation au sol.', 'TEL : 05 62 47 53 27': 'TEL : 05 62 47 53 27', 'FR seulement / only': 'FR seulement / only', 'Canal 8.33#\nTEL : 02 98 32 02 02': 'Canal 8.33#\nTEL : 02 98 32 02 02', 'SKED : See NOTAM': 'SKED : See NOTAM', 'FR#\nEnglish language possible for commercial flights only with PPR 96 HR via E-mail : [email protected]': 'FR#\nEnglish language possible for commercial flights only with PPR 96 HR via E-mail : [email protected]', 'COS 25 NM/FL 030.': 'COS 25 NM/FL 030.', 'Information/Radar SIV 3 et 5': 'Information/Radar SIV 3 et 5', 'Diffusion des parametres DEP et ARR.TEL 0320161954.': 'Diffusion des parametres DEP et ARR.TEL 0320161954.', 'Exploitant/Operator : Collectivité de Saint-Barthélemy#HOR ATS : 1100 - SS+15': 'Exploitant/Operator : Collectivité de Saint-Barthélemy#HOR ATS : 1100 - SS+15', 'Secteur Ouest-Contrôle en TMA': 'Secteur Ouest-Contrôle en TMA', 'FR seulement/only': 'FR seulement/only', 'Exploitant/Operator : Conseil général#Renseignements sur ouverture / Information during opening : TWR ST PIERRE (0508 41 18 22).': 'Exploitant/Operator : Conseil général#Renseignements sur ouverture / Information during opening : TWR ST PIERRE (0508 41 18 22).', 'FR uniquement.': 'FR uniquement.', 'Absence ATS : A/A FR seulement/only': 'Absence ATS : A/A FR seulement/only', 'NIL': 'NIL', 'Réservée au trafic civil avec ACFT CIV HNO : RAI / reserved for CIV traffic with CIV ACFT during HNO: automatic info transmitter': 'Réservée au trafic civil avec ACFT CIV HNO : RAI / reserved for CIV traffic with CIV ACFT during HNO: automatic info transmitter', 'RAI / Automatic info transmitter': 'RAI / Automatic info transmitter', 'Fréquence TOUR / Tower frequency': 'Fréquence TOUR / Tower frequency', 'Secteur Est-Contrôle en TMA': 'Secteur Est-Contrôle en TMA', 'HOR ATS : 1100 - SS + 15': 'HOR ATS : 1100 - SS + 15', 'Secteur Ouest': 'Secteur Ouest', 'Canal 8.33 : diffusion paramètres ARR/DEP et activité zones LF-R 125 et LF-R 127.': 'Canal 8.33 : diffusion paramètres ARR/DEP et activité zones LF-R 125 et LF-R 127.', 'Canal 8.33 : contrôle ACFT au sol sauf RWY': 'Canal 8.33 : contrôle ACFT au sol sauf RWY', 'Exploitant/Operator : DAC Polynésie Française': 'Exploitant/Operator : DAC Polynésie Française', "Canal 8.33 : sur instruction ATC jusqu'au FL 100": "Canal 8.33 : sur instruction ATC jusqu'au FL 100", 'Exploitant/Operator : Territoire - FR seulement / only': 'Exploitant/Operator : Territoire - FR seulement / only', 'Canal 25': 'Canal 25', 'Canal 25 : pour décollage et approche finale.': 'Canal 25 : pour décollage et approche finale.', 'Transit VFR dans CTR et TMA de LORIENT. / Transit VFR within LORIENT CTR and TMA.': 'Transit VFR dans CTR et TMA de LORIENT. / Transit VFR within LORIENT CTR and TMA.', "jusqu'au FL200/up to FL200": "jusqu'au FL200/up to FL200", 'Canal 8.33#\nFREQ sur instruction ATC pour JH': 'Canal 8.33#\nFREQ sur instruction ATC pour JH', 'RAIZ en cas de fermeture du FIS/when FIS closed': 'RAIZ en cas de fermeture du FIS/when FIS closed', 'SIV 7': 'SIV 7', 'Air-Sol VHF - Veillée / VHF Air-Ground - Monitored': 'Air-Sol VHF - Veillée / VHF Air-Ground - Monitored', "Jusqu'au FL200/Up to FL200": "Jusqu'au FL200/Up to FL200", 'ATIS disponible au 03 88 59 94 16': 'ATIS disponible au 03 88 59 94 16', 'Particulère Transit VFR / Particular VFR transit.': 'Particulère Transit VFR / Particular VFR transit.', 'FREQ sur instruction': 'FREQ sur instruction', 'Canal 8.33.# TEL : 03 44 84 69 41': 'Canal 8.33.# TEL : 03 44 84 69 41', 'APP : secteur Ouest portée réduite, inutilisable en-dessous de 10 000 ft.#\nFréquence RAI quand AD fermé./\nAPP : West sector range reduced, unavailable below 10 000 ft.#\nRAI frequency when AD closed.': 'APP : secteur Ouest portée réduite, inutilisable en-dessous de 10 000 ft.#\nFréquence RAI quand AD fermé./\nAPP : West sector range reduced, unavailable below 10 000 ft.#\nRAI frequency when AD closed.', 'FR seulement/only. TEL 01 60 04 88 75': 'FR seulement/only. TEL 01 60 04 88 75', 'TEL +33(0)4 67 90 88 88': 'TEL +33(0)4 67 90 88 88', 'Fréquence supplétive / Auxilary frequency': 'Fréquence supplétive / Auxilary frequency', 'Secteur OUEST': 'Secteur OUEST', 'ALT antenne/antenna : 1440 m': 'ALT antenne/antenna : 1440 m', 'Tél. 05 59 22 43 72': 'Tél. 05 59 22 43 72', 'TEL : (689) 40 86 13 00': 'TEL : (689) 40 86 13 00', 'TEL : +33 5 62 32 62 68': 'TEL : +33 5 62 32 62 68', 'APP TOULOUSE (SFC/FL075 Secteur Albi-Castres-Carcassonne).': 'APP TOULOUSE (SFC/FL075 Secteur Albi-Castres-Carcassonne).', 'Information/Radar SIV 1': 'Information/Radar SIV 1', 'Secteur OUEST - Contrôle en TMA.#\nAppareils non-équipés 8,33 KHz : voir AD 2 LFBO.23.#\nWest sector - Control within TMA.#\nAircraft non 8.33 KHz-equipped : see AD 2 LFBO.23.': 'Secteur OUEST - Contrôle en TMA.#\nAppareils non-équipés 8,33 KHz : voir AD 2 LFBO.23.#\nWest sector - Control within TMA.#\nAircraft non 8.33 KHz-equipped : see AD 2 LFBO.23.', 'Secteur EST - Contrôle en TMA.#\nAppareils non-équipés 8,33 KHz : voir AD 2 LFBO.23.#\nEast Sector - Control within TMA.#\nAircraft non 8.33 KHz-equipped : see AD 2 LFBO.23.': 'Secteur EST - Contrôle en TMA.#\nAppareils non-équipés 8,33 KHz : voir AD 2 LFBO.23.#\nEast Sector - Control within TMA.#\nAircraft non 8.33 KHz-equipped : see AD 2 LFBO.23.', 'Canal 8.33 : RAI : activation/désactivation des secteurs T1/T2/T3 (espaces délégués Zürich).': 'Canal 8.33 : RAI : activation/désactivation des secteurs T1/T2/T3 (espaces délégués Zürich).', 'Exploitant / operator : Syndicat Mixte de Pierrefonds': 'Exploitant / operator : Syndicat Mixte de Pierrefonds', 'canal 8.33': 'canal 8.33', 'Assistance au VFR dans le SIV TOULOUSE.': 'Assistance au VFR dans le SIV TOULOUSE.', 'FR seulement HO': 'FR seulement HO', 'VFR en espace D sur instruction CTL-POS 40/FL150.#\nAppareils non-équipés 8,33 KHz : voir AD 2 LFBO.23.#\nVFR in class D airspace, with instruction CTL-POS 40/FL150.#\nAircraft non 8.33 KHz-equipped : see AD 2 LFBO.23.': 'VFR en espace D sur instruction CTL-POS 40/FL150.#\nAppareils non-équipés 8,33 KHz : voir AD 2 LFBO.23.#\nVFR in class D airspace, with instruction CTL-POS 40/FL150.#\nAircraft non 8.33 KHz-equipped : see AD 2 LFBO.23.', 'FREQ Zone Industrielle Nord. FR seulement/only.': 'FREQ Zone Industrielle Nord. FR seulement/only.', 'Information/Radar SIV 2 et 4': 'Information/Radar SIV 2 et 4', '#\nAu profit des postes de stationnement A, B, C, D, E, F, J, K et L.#\nIn respect of parking stands A, B, C, D, E, F, J, K and L.#\nCanal 8.33': '#\nAu profit des postes de stationnement A, B, C, D, E, F, J, K et L.#\nIn respect of parking stands A, B, C, D, E, F, J, K and L.#\nCanal 8.33', 'Au profit des postes de stationnement B et D. / In respect of parking stands B and D.#Canal 8.33': 'Au profit des postes de stationnement B et D. / In respect of parking stands B and D.#Canal 8.33', 'Exploitant/Operator : AVA. HOR TWR/TWR SKED': 'Exploitant/Operator : AVA. HOR TWR/TWR SKED', 'Détresse/Distress VHF': 'Détresse/Distress VHF', 'TEL : 05 34 46 08 36': 'TEL : 05 34 46 08 36', 'FR seulement/only.': 'FR seulement/only.', 'Utilisée sur instruction / Used upon instruction': 'Utilisée sur instruction / Used upon instruction', 'Sur instruction CTL': 'Sur instruction CTL', 'Commune APP / Common to APP': 'Commune APP / Common to APP', '- TMA Montpellier parties 7, 8, 9 et de 14 à 23 / TMA Montpellier parts 7, 8, 9 and from 14 to 23.#\n- Volumes des TMA 3, 4 et 5 inclus dans le SIV Montpellier partie 5 / Volumes of TMA 3, 4 and 5 included in FIS Montpellier part 5.': '- TMA Montpellier parties 7, 8, 9 et de 14 à 23 / TMA Montpellier parts 7, 8, 9 and from 14 to 23.#\n- Volumes des TMA 3, 4 et 5 inclus dans le SIV Montpellier partie 5 / Volumes of TMA 3, 4 and 5 included in FIS Montpellier part 5.', 'FREQ veillée': 'FREQ veillée', 'TEL ATIS : 04 67 13 11 70': 'TEL ATIS : 04 67 13 11 70', 'Particulière transit - veillée - RAI / Particular to transit - monitored': 'Particulière transit - veillée - RAI / Particular to transit - monitored', 'ATIS/V-25NM/FL40': 'ATIS/V-25NM/FL40', 'Urgence/Emergency': 'Urgence/Emergency', 'ATIS/V-FR-Tel : 01 60 80 96 86': 'ATIS/V-FR-Tel : 01 60 80 96 86', 'RADAR': 'RADAR', 'AUTO-INFO Jour-Nuit/AUTO-INFO Day-Night': 'AUTO-INFO Jour-Nuit/AUTO-INFO Day-Night', 'ATIS/V-Fr. uniquement/only#TEL :01 30 85 09 86': 'ATIS/V-Fr. uniquement/only#TEL :01 30 85 09 86', 'ATIS/V-TEL 01 60 17 97 94': 'ATIS/V-TEL 01 60 17 97 94', 'Au profit du poste de stationnement F. / In respect of parking stand F.#Canal 8.33': 'Au profit du poste de stationnement F. / In respect of parking stand F.#Canal 8.33', "ACFT à l'ARR ou au DEP de METZ NANCY LORRAINE#\nACFT inbound or outbound METZ NANCY LORRAINE": "ACFT à l'ARR ou au DEP de METZ NANCY LORRAINE#\nACFT inbound or outbound METZ NANCY LORRAINE", 'GCA - APP Control': 'GCA - APP Control', "En l'absence AFIS, A/A en FR seulement.": "En l'absence AFIS, A/A en FR seulement.", 'Roulage / Taxiing': 'Roulage / Taxiing', 'SIV secteur NORD/NORTH sector.': 'SIV secteur NORD/NORTH sector.', 'SIV secteur SUD/SOUTH sector.#': 'SIV secteur SUD/SOUTH sector.#', "Dans l'espace du SIV situé sous CTR NOUMEA LA TONTOUTA partie 1.#In SIV airspace located under CTR NOUMEA LA TONTOUTA part 1.": "Dans l'espace du SIV situé sous CTR NOUMEA LA TONTOUTA partie 1.#In SIV airspace located under CTR NOUMEA LA TONTOUTA part 1.", "Dans l'espace du SIV situé sous TMA NOUMEA partie 1.4 ILES LOYAUTE.#In SIV airspace located under TMA NOUMEA part 1.4 ILES LOYAUTE.": "Dans l'espace du SIV situé sous TMA NOUMEA partie 1.4 ILES LOYAUTE.#In SIV airspace located under TMA NOUMEA part 1.4 ILES LOYAUTE.", 'FREQ veillée/monitored': 'FREQ veillée/monitored', 'FREQ utilisable sur instruction ATC.': 'FREQ utilisable sur instruction ATC.', "Radar mobile susceptible d'être déplacé sans préavis/Mobile radar likely to be displaced without prior notice": "Radar mobile susceptible d'être déplacé sans préavis/Mobile radar likely to be displaced without prior notice", 'Freq veillée/Monitored frequency': 'Freq veillée/Monitored frequency', 'RAI': 'RAI', 'Coordinations des activités ops. de la marine.': 'Coordinations des activités ops. de la marine.', 'Canal 8.33.#FREQ utilisable sur instruction ATC.': 'Canal 8.33.#FREQ utilisable sur instruction ATC.', 'SRE CENTAURE': 'SRE CENTAURE', 'O/R APP': 'O/R APP', 'Roulage VHF - Veillée / VHF taxiing - Monitored': 'Roulage VHF - Veillée / VHF taxiing - Monitored', 'Détresse - Veillée / Distress - Monitored': 'Détresse - Veillée / Distress - Monitored', 'O/T RAIZ - FREQ veillée/monitored': 'O/T RAIZ - FREQ veillée/monitored', 'Fréquence SOL / Ground frequency': 'Fréquence SOL / Ground frequency', 'Détresse/Distress': 'Détresse/Distress', 'Particular Air-sol/Particular Air-Ground': 'Particular Air-sol/Particular Air-Ground', "FREQ information de vol planeurs selon protocole. Sur instruction ATC#\nGlider's flight information FREQ. With ATC instruction.": "FREQ information de vol planeurs selon protocole. Sur instruction ATC#\nGlider's flight information FREQ. With ATC instruction.", 'SIV 1, SIV 5 et/and SIV 6': 'SIV 1, SIV 5 et/and SIV 6', 'Diffusion des paramètres de DEP et ARR.': 'Diffusion des paramètres de DEP et ARR.', "ETE : 0700 - maximum de SS+30 et 1900, limité à 2100.#HIV : 0800 - maximum de SS+30 et 1900, limité à 2100.#En-dessous de 4500ft AMSL, contact radio non garanti à l'Ouest de PARIS INFO OUEST (région L'Aigle, Falaise et Mortagne au Perche).": "ETE : 0700 - maximum de SS+30 et 1900, limité à 2100.#HIV : 0800 - maximum de SS+30 et 1900, limité à 2100.#En-dessous de 4500ft AMSL, contact radio non garanti à l'Ouest de PARIS INFO OUEST (région L'Aigle, Falaise et Mortagne au Perche).", 'Canal 25 : supplétive sur instruction ATC': 'Canal 25 : supplétive sur instruction ATC', "Protection 50 NM jusqu'au FL250.": "Protection 50 NM jusqu'au FL250.", "Fréquence auxilliaire d'opération SAR. Exploitant AVA": "Fréquence auxilliaire d'opération SAR. Exploitant AVA", 'Exploitant AVA. Au sud du RDL 264° SDG': 'Exploitant AVA. Au sud du RDL 264° SDG', 'Exploitant AVA': 'Exploitant AVA', 'Fr. uniquement': 'Fr. uniquement', "Exploitant : Ville d'Ouessant. - O/R pour transport et EVASAN.": "Exploitant : Ville d'Ouessant. - O/R pour transport et EVASAN.", 'Particulière radar / Particular to radar': 'Particulière radar / Particular to radar', "Particulière Radar - Peut utiliser toutes les fréquences de l'APP / Particular to radar - Can use all the APP frequencies": "Particulière Radar - Peut utiliser toutes les fréquences de l'APP / Particular to radar - Can use all the APP frequencies", 'Détresse civile - veillée/Civilian distress - monitored': 'Détresse civile - veillée/Civilian distress - monitored', 'Détresse civile - veillée / Civilian distress - monitored': 'Détresse civile - veillée / Civilian distress - monitored', 'Utilisation suspendue.': 'Utilisation suspendue.', 'ETE : 0700 - maximum de SS+30 et 1900, limité à 2100.#HIV : 0800 - maximum de SS+30 et 1900, limité à 2100.#Contact radio non garanti sous 2000ft AMSL dans PARIS INFO NORD.#Contact radio et/ou radar non garanti sous 3500ft AMSL au Nord-Est de PARIS INFO NORD (région Sedan Charleville Mézières).': 'ETE : 0700 - maximum de SS+30 et 1900, limité à 2100.#HIV : 0800 - maximum de SS+30 et 1900, limité à 2100.#Contact radio non garanti sous 2000ft AMSL dans PARIS INFO NORD.#Contact radio et/ou radar non garanti sous 3500ft AMSL au Nord-Est de PARIS INFO NORD (région Sedan Charleville Mézières).', 'ETE : 0700 - maximum de SS+30 et 1900, limité à 2100.#HIV : 0800 - maximum de SS+30 et 1900, limité à 2100.#Contact radio non garanti sous 1500ft AMSL dans PARIS SUD INFO.': 'ETE : 0700 - maximum de SS+30 et 1900, limité à 2100.#HIV : 0800 - maximum de SS+30 et 1900, limité à 2100.#Contact radio non garanti sous 1500ft AMSL dans PARIS SUD INFO.', 'Commune GCA - non veillée / Common to GCA - not monitored': 'Commune GCA - non veillée / Common to GCA - not monitored', 'Particulière GCA - non veillée / Particular to GCA - not monitored': 'Particulière GCA - non veillée / Particular to GCA - not monitored', 'HOR : voir/see AD 2 NWWM.3#CTR NOUMEA MAGENTA parties 1 et 2 / parts 1 and 2#Absence ATS : A/A FR seulement/only#Exploitant/Operator : AVA': 'HOR : voir/see AD 2 NWWM.3#CTR NOUMEA MAGENTA parties 1 et 2 / parts 1 and 2#Absence ATS : A/A FR seulement/only#Exploitant/Operator : AVA', 'TWR / APP': 'TWR / APP', "Jusqu'au FL200 / Up to FL200": "Jusqu'au FL200 / Up to FL200", "Jusqu'au FL145 / Up to FL145": "Jusqu'au FL145 / Up to FL145", 'Exploitant/Operator : AVA.#HOR NOTAM / NOTAM SKED (voir/see AD 2 NWWE.3)': 'Exploitant/Operator : AVA.#HOR NOTAM / NOTAM SKED (voir/see AD 2 NWWE.3)', 'Fréquence pour régulation radar / Frequency for radar sequencing': 'Fréquence pour régulation radar / Frequency for radar sequencing', 'Secteur BE.': 'Secteur BE.', 'Secteur BW.': 'Secteur BW.', 'Freq veillée.RAI/Monitored frequency.Automatical information transmitter': 'Freq veillée.RAI/Monitored frequency.Automatical information transmitter', 'SIV IROISE 3 hors/out LANDIVISIAU SKED': 'SIV IROISE 3 hors/out LANDIVISIAU SKED', "Fréquence d'information.": "Fréquence d'information.", "Jusqu'au FL200": "Jusqu'au FL200", 'Au profit des postes de stationnement I.#\nIn favour of parking stands I.\n#Canal 8.33': 'Au profit des postes de stationnement I.#\nIn favour of parking stands I.\n#Canal 8.33', 'Principale': 'Principale', 'Diffusion des paramètres de DEP et ARR (EN)/ARR-DEP parameters broadcasting(EN).#Canal 8.33': 'Diffusion des paramètres de DEP et ARR (EN)/ARR-DEP parameters broadcasting(EN).#Canal 8.33', 'Au profit des postes de stationnement A, C et E. / In respect of parking stands A, C and E.#Canal 8.33': 'Au profit des postes de stationnement A, C et E. / In respect of parking stands A, C and E.#Canal 8.33', 'FREQ sur instruction UAC UN / UB /KN /YB /HN /UR /XR /KR /HR /KF /KD': 'FREQ sur instruction UAC UN / UB /KN /YB /HN /UR /XR /KR /HR /KF /KD', 'Fréquence supplétive CTR NOUMEA LA TONTOUTA parties 1 et 2.#\nAuxiliary frequency CTR NOUMEA LA TONTOUTA parts 1 and 2.': 'Fréquence supplétive CTR NOUMEA LA TONTOUTA parties 1 et 2.#\nAuxiliary frequency CTR NOUMEA LA TONTOUTA parts 1 and 2.', 'TEL : 02 31 08 42 80': 'TEL : 02 31 08 42 80', 'Secteur COTENTIN/COTENTIN sector': 'Secteur COTENTIN/COTENTIN sector', 'TMA NOUMEA partie 1.1 TONTOUTA.# \nTMA NOUMEA part 1.1 TONTOUTA.': 'TMA NOUMEA partie 1.1 TONTOUTA.# \nTMA NOUMEA part 1.1 TONTOUTA.', 'Secteur Sud/South sector.': 'Secteur Sud/South sector.', 'Appareils non-équipés 8,33 KHz : voir AD 2 LFBO.23.#\nAircraft non 8.33 KHz-equipped : see AD 2 LFBO.23.': 'Appareils non-équipés 8,33 KHz : voir AD 2 LFBO.23.#\nAircraft non 8.33 KHz-equipped : see AD 2 LFBO.23.', 'CTR NOUMEA LA TONTOUTA parties 1 et 2/parts 1 and 2.': 'CTR NOUMEA LA TONTOUTA parties 1 et 2/parts 1 and 2.', 'Exploitant / operator : SNA/OI#\nService VDF (Gonio)': 'Exploitant / operator : SNA/OI#\nService VDF (Gonio)', "Jusqu'au FL100.#\nAppareils non-équipés 8,33 KHz : voir AD 2 LFBO.23.#\nUp to FL100.#\nAircraft non 8.33 KHz-equipped : see AD 2 LFBO.23.": "Jusqu'au FL100.#\nAppareils non-équipés 8,33 KHz : voir AD 2 LFBO.23.#\nUp to FL100.#\nAircraft non 8.33 KHz-equipped : see AD 2 LFBO.23.", 'Diffusion des paramètres de DEP et ARR. (FR)': 'Diffusion des paramètres de DEP et ARR. (FR)', 'AFIS SKED#Exploitant/operator : AVA#243°/486 m THR 26': 'AFIS SKED#Exploitant/operator : AVA#243°/486 m THR 26', 'HOR : Voir/see AD 2 NLWW.3#Exploitant/Operator : AVA': 'HOR : Voir/see AD 2 NLWW.3#Exploitant/Operator : AVA', 'Secteur SUD/South sector': 'Secteur SUD/South sector', 'Exploitant/Operator : AVA. HOR/SKED : voir/see AD 2 NWWL.3': 'Exploitant/Operator : AVA. HOR/SKED : voir/see AD 2 NWWL.3', 'Exploitant/Operator : AVA. HOR NOTAM': 'Exploitant/Operator : AVA. HOR NOTAM', "Protection jusqu'au FL 150. Approche Ouest.#Protection up to FL 150. West Approach.": "Protection jusqu'au FL 150. Approche Ouest.#Protection up to FL 150. West Approach.", '- TMA Montpellier parties 1, 2, 3.1, 4, 4.1, 6, 6.1 et de 10 à 13 / TMA Montpellier parts 1, 2, 3.1, 4, 4.1, 6, 6.1 and from 10 to 13#- Volumes des TMA Montpellier parties 3, 4, 5 inclus dans le SIV Montpellier partie 1 / Volumes of TMA Montpellier parts 3, 4, 5 included in FIS Montpellier part 1': '- TMA Montpellier parties 1, 2, 3.1, 4, 4.1, 6, 6.1 et de 10 à 13 / TMA Montpellier parts 1, 2, 3.1, 4, 4.1, 6, 6.1 and from 10 to 13#- Volumes des TMA Montpellier parties 3, 4, 5 inclus dans le SIV Montpellier partie 1 / Volumes of TMA Montpellier parts 3, 4, 5 included in FIS Montpellier part 1', 'TEL : 02 40 05 12 74': 'TEL : 02 40 05 12 74', 'TEL: 04 95 65 82 49': 'TEL: 04 95 65 82 49', 'HOR : voir/see AD 2 NWWM.3#TMA NOUMEA partie/part 1.2 MAGENTA#Exploitant/Operator : AVA': 'HOR : voir/see AD 2 NWWM.3#TMA NOUMEA partie/part 1.2 MAGENTA#Exploitant/Operator : AVA', 'Diffusion des paramètres de DEP et ARR/DEP and ARR parameters broadcasting TEL: (0)4.68.10.23.56': 'Diffusion des paramètres de DEP et ARR/DEP and ARR parameters broadcasting TEL: (0)4.68.10.23.56', 'HOR : HOR TWR#Exploitant/Operator : AVA': 'HOR : HOR TWR#Exploitant/Operator : AVA', 'TMA NOUMEA partie 1.3 ILE DES PINS et TMA NOUMEA parties 2 et 3 secteur SUD.#\nFréquence supplétive TMA NOUMEA partie 1.1 TONTOUTA et TMA NOUMEA partie 1.4 ILES LOYAUTE.#\nTMA NOUMEA part 1.3 ILE DES PINS and TMA NOUMEA parts 2 and 3 SOUTH sector.#\nAuxiliary frequency TMA NOUMEA part 1.1 TONTOUTA and TMA NOUMEA part 1.4 ILES LOYAUTE.': 'TMA NOUMEA partie 1.3 ILE DES PINS et TMA NOUMEA parties 2 et 3 secteur SUD.#\nFréquence supplétive TMA NOUMEA partie 1.1 TONTOUTA et TMA NOUMEA partie 1.4 ILES LOYAUTE.#\nTMA NOUMEA part 1.3 ILE DES PINS and TMA NOUMEA parts 2 and 3 SOUTH sector.#\nAuxiliary frequency TMA NOUMEA part 1.1 TONTOUTA and TMA NOUMEA part 1.4 ILES LOYAUTE.', 'FREQ arrivée Est.': 'FREQ arrivée Est.', 'TMA NOUMEA parties 2 et 3 Secteur NORD#\nTMA NOUMEA parts 2 and 3 NORTH sector.': 'TMA NOUMEA parties 2 et 3 Secteur NORD#\nTMA NOUMEA parts 2 and 3 NORTH sector.', 'FREQ arrivée Ouest.': 'FREQ arrivée Ouest.', 'TMA NOUMEA partie 1.4/part 1.4 ILES LOYAUTE.': 'TMA NOUMEA partie 1.4/part 1.4 ILES LOYAUTE.', 'CTL ACFT au sol.': 'CTL ACFT au sol.', 'FREQ sur instruction UAC UE /UH /UF /KF /XH /XE /KE /KH /HH /HE /KD /UR /XR /KR /HR': 'FREQ sur instruction UAC UE /UH /UF /KF /XH /XE /KE /KH /HH /HE /KD /UR /XR /KR /HR', "FREQ IFR et VFR en circuit d'aérodrome.": "FREQ IFR et VFR en circuit d'aérodrome.", 'FREQ réservée aux VFR en transit SA-EA et hélicoptères.': 'FREQ réservée aux VFR en transit SA-EA et hélicoptères.', 'TEL : 05 53 63 53 55': 'TEL : 05 53 63 53 55', 'Diffusion des paramètres de DEP et ARR (EN).': 'Diffusion des paramètres de DEP et ARR (EN).', 'SIV 2.1/2.2/2.3': 'SIV 2.1/2.2/2.3', 'FREQ pour TKOF et APCH finale toute condition MTO.': 'FREQ pour TKOF et APCH finale toute condition MTO.', 'Diffusion des paramètres de DEP et ARR (FR).': 'Diffusion des paramètres de DEP et ARR (FR).', '(1) En dehors de ces HOR, PPR PN 2HR TEL permanence gestionnaire.#(1) Outside these SKED, PPR 2HR TEL AD operator permanence': '(1) En dehors de ces HOR, PPR PN 2HR TEL permanence gestionnaire.#(1) Outside these SKED, PPR 2HR TEL AD operator permanence', 'FREQ " FIRE ".': 'FREQ " FIRE ".', 'FR seulement': 'FR seulement', 'FREQ réservée aux DEP.': 'FREQ réservée aux DEP.', 'Eléments de PLN et paramètres de DEP.': 'Eléments de PLN et paramètres de DEP.', "Protection jusqu'au FL 150. Approche Est.#Protection up to FL 150. East Approach.": "Protection jusqu'au FL 150. Approche Est.#Protection up to FL 150. East Approach.", 'SIV 1.1/1.2': 'SIV 1.1/1.2', 'ATIS/V-Tél : 01 30 56 38 02': 'ATIS/V-Tél : 01 30 56 38 02', 'TEL : 04 85 44 09 66': 'TEL : 04 85 44 09 66', '#Portée réduite / Reduced range.': '#Portée réduite / Reduced range.', "HOR : voir/see AD 2 NWWM.3#Dans l'espace du SIV situé sous TMA NOUMEA partie 1.2 MAGENTA#In FIS airspace located under TMA NOUMEA part 1.2 MAGENTA#Exploitant/Operator : AVA": "HOR : voir/see AD 2 NWWM.3#Dans l'espace du SIV situé sous TMA NOUMEA partie 1.2 MAGENTA#In FIS airspace located under TMA NOUMEA part 1.2 MAGENTA#Exploitant/Operator : AVA", 'HOR TWR/TWR SKED#Exploitant/Operator : AVA': 'HOR TWR/TWR SKED#Exploitant/Operator : AVA', 'Fréquence supplétive/Auxiliary frequency': 'Fréquence supplétive/Auxiliary frequency', 'Diffusion des paramètres de DEP et ARR (FR)/ARR-DEP parameters broadcasting.\n#Canal 8.33': 'Diffusion des paramètres de DEP et ARR (FR)/ARR-DEP parameters broadcasting.\n#Canal 8.33', "En l'absence AFIS A/A en FR seulement.": "En l'absence AFIS A/A en FR seulement.", 'FREQ arrivée.': 'FREQ arrivée.', 'Transit VFR/IFR - RAIZ': 'Transit VFR/IFR - RAIZ', 'Air/Sol VHF / Air/Ground VHF': 'Air/Sol VHF / Air/Ground VHF', 'Roulage VHF / Taxiing VHF': 'Roulage VHF / Taxiing VHF', 'Secteur Ouest/West sector.': 'Secteur Ouest/West sector.', 'Secteur EST': 'Secteur EST', 'Exploitant: Agglomération du Choletais.': 'Exploitant: Agglomération du Choletais.', 'Circulation au sol sauf RWY / Taxiing except on RWY': 'Circulation au sol sauf RWY / Taxiing except on RWY', 'SIV 1b': 'SIV 1b', 'O/R TWR 15 min avant utilisation.': 'O/R TWR 15 min avant utilisation.', 'AD non contrôlé SAM matin.': 'AD non contrôlé SAM matin.', 'APP CHERBOURG, APP LE HAVRE, APP CAEN': 'APP CHERBOURG, APP LE HAVRE, APP CAEN', 'FR seulement/only#\nExploitant/Operator : Délégation Guadeloupe': 'FR seulement/only#\nExploitant/Operator : Délégation Guadeloupe', 'FREQ Départ.': 'FREQ Départ.', 'SIV 1 et/and 2.': 'SIV 1 et/and 2.', 'Secteur NORD/NORTH sector': 'Secteur NORD/NORTH sector', 'Absence HOR ATS A/A FR seulement/only': 'Absence HOR ATS A/A FR seulement/only', 'Secteur Est': 'Secteur Est', "ACFT à l'ARR ou au DEP d'EPINAL MIRECOURT et NANCY ESSEY#\nACFT inbound or outbound EPINAL MIRECOURT and NANCY ESSEY": "ACFT à l'ARR ou au DEP d'EPINAL MIRECOURT et NANCY ESSEY#\nACFT inbound or outbound EPINAL MIRECOURT and NANCY ESSEY", 'Sur instruction UAC / on instruction UAC': 'Sur instruction UAC / on instruction UAC', 'Absence ATS : A/A FR uniquement/only.': 'Absence ATS : A/A FR uniquement/only.', 'cf AD2 LFRD.17': 'cf AD2 LFRD.17', 'Utilise toutes les fréq SRE - Utilisable QFU 29 ou 11/#\nUse all the SRE frequencies - Available for QFU 29 or 11': 'Utilise toutes les fréq SRE - Utilisable QFU 29 ou 11/#\nUse all the SRE frequencies - Available for QFU 29 or 11', 'SPAR MAINTENANCE LUN/MON 0800-1200 loc.': 'SPAR MAINTENANCE LUN/MON 0800-1200 loc.', 'FREQ supplétive (RWY 05)': 'FREQ supplétive (RWY 05)', 'RAI (H24)': 'RAI (H24)', 'FREQ Principale (RWY 05)': 'FREQ Principale (RWY 05)', 'Auto INFO - Fréq club': 'Auto INFO - Fréq club', 'FREQ de secours Prévol.': 'FREQ de secours Prévol.', 'RDH 06 : 49ft - RDH 24 : 52ft': 'RDH 06 : 49ft - RDH 24 : 52ft', 'Hors HOR MIL Nancy Ochey./Outside Nancy Ochey MIL SKED.': 'Hors HOR MIL Nancy Ochey./Outside Nancy Ochey MIL SKED.', 'Contrôle ACFT au sol.': 'Contrôle ACFT au sol.', '#MO/ML/MN/ST/AJ/BT#Sur instruction ATC/On ATC instruction.': '#MO/ML/MN/ST/AJ/BT#Sur instruction ATC/On ATC instruction.', 'Secteur Nord/North sector.': 'Secteur Nord/North sector.', 'FREQ sur instruction UAC UN /UB /KN /YB /HN /UR /XR /KR /HR /UF /KF /KD': 'FREQ sur instruction UAC UN /UB /KN /YB /HN /UR /XR /KR /HR /UF /KF /KD', 'TEL: 05 46 00 13 92': 'TEL: 05 46 00 13 92', 'TEL : 04 95 71 10 99': 'TEL : 04 95 71 10 99', 'FREQ sur instruction ACC E,SE.': 'FREQ sur instruction ACC E,SE.', 'ETE : 0700-SS+30 limité à 1800.#HIV : 0800-SS+30 limité à 1730.': 'ETE : 0700-SS+30 limité à 1800.#HIV : 0800-SS+30 limité à 1730.', 'Exploitant: Syndicat intercommunal.': 'Exploitant: Syndicat intercommunal.', 'FIS, APP, TWR. Sur instruction CTL/On ATC instruction.': 'FIS, APP, TWR. Sur instruction CTL/On ATC instruction.', 'Radiocommunications Air/Air': 'Radiocommunications Air/Air', 'Au profit des postes de stationnement K, L. / In respect of parking stands K, L.#Canal 8.33': 'Au profit des postes de stationnement K, L. / In respect of parking stands K, L.#Canal 8.33', '#A1/A2/A3/A4/B1/B2/B3/B4/D1/D3/E1/E2/E3/F1/F2/F3/F4/K1/K2/M1/M2/M3/M4/MO/ML/MN/ST/AJ/BT#Sur instruction ATC/On ATC instruction': '#A1/A2/A3/A4/B1/B2/B3/B4/D1/D3/E1/E2/E3/F1/F2/F3/F4/K1/K2/M1/M2/M3/M4/MO/ML/MN/ST/AJ/BT#Sur instruction ATC/On ATC instruction', 'ATIS/S': 'ATIS/S', 'Au profit des postes de stationnement J. / In respect of parking stands J.#Canal 8.33': 'Au profit des postes de stationnement J. / In respect of parking stands J.#Canal 8.33', 'Service saisonnier .Fr.uniquement.': 'Service saisonnier .Fr.uniquement.', "Canal 25 : sur instruction ATC jusqu'au FL100": "Canal 25 : sur instruction ATC jusqu'au FL100", 'Couverture: 25 NM/FL 100.': 'Couverture: 25 NM/FL 100.', 'Couverture: 25NM/FL40.': 'Couverture: 25NM/FL40.', 'O/R FREQ. de HYERES / O/R HYERES FREQ.': 'O/R FREQ. de HYERES / O/R HYERES FREQ.', 'Emplacement/Location : 036°/2000 m DTHR 04': 'Emplacement/Location : 036°/2000 m DTHR 04', '25NM/FL40.': '25NM/FL40.', 'Canal 25 : service : Information/Radar.': 'Canal 25 : service : Information/Radar.', '25NM/FL100.': '25NM/FL100.', 'APP TOULOUSE (SFC/FL075 Secteur Agen-Pamiers).#\nAppareils non-équipés 8,33 KHz : voir AD 2 LFBO.23.#\nTOULOUSE APP (SFC/FL075 Agen-Pamiers sector).#\nAircraft non 8.33 KHz-equipped : see AD 2 LFBO.23.': 'APP TOULOUSE (SFC/FL075 Secteur Agen-Pamiers).#\nAppareils non-équipés 8,33 KHz : voir AD 2 LFBO.23.#\nTOULOUSE APP (SFC/FL075 Agen-Pamiers sector).#\nAircraft non 8.33 KHz-equipped : see AD 2 LFBO.23.', 'Particulière Transit VFR ou RAI/Particular VFR transit or automatic information transmitter': 'Particulière Transit VFR ou RAI/Particular VFR transit or automatic information transmitter', 'Particulière Air-Sol VHF/Particular Air-Groung VHF': 'Particulière Air-Sol VHF/Particular Air-Groung VHF', "Angle de descente / Slope gradient 3°#Indicatif d'appel / Call sign: LORIENT Précision.": "Angle de descente / Slope gradient 3°#Indicatif d'appel / Call sign: LORIENT Précision.", "Dans l'espace du SIV situé sous / in SIV airspace located under TMA NOUMEA partie 1.1 TONTOUTA sauf sous / except under CTR NOUMEA LA TONTOUTA partie 1.": "Dans l'espace du SIV situé sous / in SIV airspace located under TMA NOUMEA partie 1.1 TONTOUTA sauf sous / except under CTR NOUMEA LA TONTOUTA partie 1.", 'Pente/Gradient 2.7°': 'Pente/Gradient 2.7°', 'Exploitant/Operator : SNA/OI#\nService VDF (Gonio).': 'Exploitant/Operator : SNA/OI#\nService VDF (Gonio).', 'Exploitant SNA/OI. Au sud du RDL 264° SDG.#\nOperator SNA/OI. South of RDL 264° SDG.#\nService VDF (Gonio).': 'Exploitant SNA/OI. Au sud du RDL 264° SDG.#\nOperator SNA/OI. South of RDL 264° SDG.#\nService VDF (Gonio).', "Paramètres de DEP et ARR jusqu'au FL 100. TEL: 03 85 26 60 78": "Paramètres de DEP et ARR jusqu'au FL 100. TEL: 03 85 26 60 78", 'Radar CENTAURE': 'Radar CENTAURE', 'Roulage/Taxiing': 'Roulage/Taxiing', 'TEL : 04 82 89 48 18': 'TEL : 04 82 89 48 18', 'SIV 1a': 'SIV 1a', 'SIV 3.1/3.2': 'SIV 3.1/3.2', 'TEL 03 21 06 62 84': 'TEL 03 21 06 62 84', 'Fréquence de transit/Transit frequency': 'Fréquence de transit/Transit frequency', 'TEL : 35 24 24': 'TEL : 35 24 24', 'exploitant : ville de ROYAN': 'exploitant : ville de ROYAN', 'Hors HOR ATC.': 'Hors HOR ATC.', 'CTL et/and transit': 'CTL et/and transit', '#G1/G2/G3/G4/Y1/Y2/Y3/Y4/W1/W2/W3/LO/LE/LS#Sur instruction ATC/On ATC instruction': '#G1/G2/G3/G4/Y1/Y2/Y3/Y4/W1/W2/W3/LO/LE/LS#Sur instruction ATC/On ATC instruction', 'SIV 3, 4 et/and 4.1.': 'SIV 3, 4 et/and 4.1.', 'TEL : 02 35 80 80 34': 'TEL : 02 35 80 80 34', 'Fr. uniquement.': 'Fr. uniquement.'} return
def saveAirspacesFilter(self, aContext, epsilonReduce: float = None) -> None: if epsilonReduce and epsilonReduce > 0: self.epsilonReduce = epsilonReduce else: self.epsilonReduce = self.oCtrl.epsilonReduce if not self.geoAirspaces: #Contrôle si le fichier est vide return context = aContext[0] sMsg = "Prepare GeoJSON file - {0}".format(aContext[1]) self.oCtrl.oLog.info(sMsg) barre = bpaTools.ProgressBar(len(self.oAirspacesCatalog.oAirspaces), 20, title=sMsg, isSilent=self.oCtrl.oLog.isSilent) idx = 0 geojson = [] #Initialisation avant filtrage spécifique for o in self.geoAirspaces: oZone = o[cstGeoProperties] idx += 1 include = not oZone[ "groupZone"] #Ne pas traiter les zones de type 'Regroupement' #if include and ("excludeAirspaceNotCoord" in oZone): #Ne pas traiter les zones qui n'ont pas de coordonnées geometriques # include = not oZone["excludeAirspaceNotCoord"] if include: include = False if context == "all": include = True elif context == "ifr": include = (not oZone["vfrZone"]) and ( not oZone["groupZone"]) elif context == "vfr": include = oZone["vfrZone"] include = include or oZone.get( "vfrZoneExt", False ) #Exporter l'extension de vol possible en VFR de 0m jusqu'au FL195/5944m elif context == "ff": include = oZone["freeFlightZone"] include = include or oZone.get( "freeFlightZoneExt", False ) #Exporter l'extension de vol possible en VFR de 0m jusqu'au FL195/5944m include = include and ( o[cstGeojsonGeometry][cstGeoCoordinates] != errLocalisationPoint) if include: #Extract single parts of properties oSingleCat: dict = {} aSinglePorperties: list = [ "name", "nameV", "class", "type", "codeActivity", "lower", "lowerMin", "ordinalLowerM", "lowerM", "upper", "upperMax", "ordinalUpperM", "upperM", "groundEstimatedHeight", "desc", "activationCode", "activationDesc", "declassifiable", "timeScheduling", "Mhz", "GUId", "UId", "id" ] #Exclude: zoneType, groupZone, srcClass, srcType, vfrZone, vfrZoneExt, freeFlightZone, freeFlightZoneExt, srcName; etc... for sProp in aSinglePorperties: value = o[cstGeoProperties].get(sProp, None) if value != None: oSingleCat.update({sProp: value}) addColorProperties( o[cstGeoProperties], oSingleCat, self.oCtrl.oLog ) #Ajout des propriétés pour colorisation de la zone #Extraction des coordonnées oAsGeo = o[cstGeojsonGeometry] if oAsGeo[cstGeoType].lower() == (cstGeoPoint).lower(): oCoords: list = oAsGeo[ cstGeoCoordinates] #get coordinates of geometry elif oAsGeo[cstGeoType].lower() == (cstGeoLine).lower(): oCoords: list = oAsGeo[ cstGeoCoordinates] #get coordinates of geometry elif oAsGeo[cstGeoType].lower() == (cstGeoPolygon).lower(): oCoords: list = oAsGeo[cstGeoCoordinates][ 0] #get coordinates of geometry #Optimisation du tracé GeoJSON oCoordsDst: list = [] #if self.epsilonReduce<0: --> do not change ! if self.epsilonReduce == 0 or ( self.epsilonReduce > 0 and len(oCoords) > 40 ): #Ne pas optimiser le tracé des zones ayant moins de 40 segments (préservation des tracés de cercle minimaliste) oCoordsDst = rdp(oCoords, epsilon=self.epsilonReduce ) #Optimisation du tracé des coordonnées #Remplacement du tracé s'il a été optimisé iOrgSize: int = len(oCoords) iNewSize: int = len(oCoordsDst) if iNewSize > 0 and iNewSize != iOrgSize: percent: float = (1 - (iNewSize / iOrgSize)) * 100 percent = int(percent) if percent > 1 else round( percent, 1) sOpti: str = "Segments optimisés à {0}% ({1}->{2}) [rdp={3}] ***".format( percent, iOrgSize, iNewSize, self.epsilonReduce) self.oCtrl.oLog.debug( "GeoJSON RDP Optimisation: {0} - {1}".format( oZone["nameV"], sOpti), level=2, outConsole=False) #self.oCtrl.oLog.debug("RDP Src: {0}".format(oCoords), level=8) #self.oCtrl.oLog.debug("RDP Dst: {0}".format(oCoordsDst), level=8) if oAsGeo[cstGeoType].lower() == (cstGeoPoint).lower(): oNewGeo: dict = { cstGeoType: oAsGeo[cstGeoType], cstGeoCoordinates: oCoordsDst } elif oAsGeo[cstGeoType].lower() == (cstGeoLine).lower(): oNewGeo: dict = { cstGeoType: oAsGeo[cstGeoType], cstGeoCoordinates: oCoordsDst } elif oAsGeo[cstGeoType].lower() == (cstGeoPolygon).lower(): oNewGeo: dict = { cstGeoType: oAsGeo[cstGeoType], cstGeoCoordinates: [oCoordsDst] } oArea: dict = { cstGeoType: cstGeoFeature, cstGeoProperties: oSingleCat, cstGeojsonGeometry: oNewGeo } else: oArea: dict = { cstGeoType: cstGeoFeature, cstGeoProperties: oSingleCat, cstGeojsonGeometry: oAsGeo } geojson.append(oArea) barre.update(idx) barre.reset() if geojson: self.oCtrl.oAixmTools.writeGeojsonFile("airspaces", geojson, context) return
def saveGeoJsonAirspacesFile(self, sFile: str, sContext: str = "all", sAreaKey: str = None, epsilonReduce: float = None) -> None: oGeoFeatures: list = [] sContent: str = "" oNewHeader: dict = deepcopy(self.oAsCat.oGlobalCatalogHeader) #bpaTools.writeJsonFile(sFile + "-tmp.json", self.oGlobalGeoJSON) #Sérialisation pour mise au point oGlobalCats = self.oAsCat.oGlobalCatalog[ airspacesCatalog. cstKeyCatalogCatalog] #Récupération de la liste des zones consolidés sTitle = "GeoJSON save airspaces file - {0} / {1}".format( sContext, sAreaKey) barre = bpaTools.ProgressBar(len(oGlobalCats), 20, title=sTitle) idx = 0 for sGlobalKey, oGlobalCat in oGlobalCats.items( ): #Traitement du catalogue global complet idx += 1 #if oGlobalCat["id"] in ["TMA16169","TMA16170"]: # print(oGlobalCat["id"]) #Filtrage des zones par typologie de sorties bIsInclude: bool = False if sContext == "ifr": bIsInclude = (not oGlobalCat["vfrZone"]) and ( not oGlobalCat["groupZone"]) sContent = "ifrZone" sFile = sFile.replace("-all", "-ifr") elif sContext == "vfr": bIsInclude = oGlobalCat["vfrZone"] bIsInclude = bIsInclude or oGlobalCat.get( "vfrZoneExt", False ) #Exporter l'extension de vol possible en VFR de 0m jusqu'au FL195/5944m sContent = "vfrZone" sFile = sFile.replace("-all", "-vfr") elif sContext in ["ff", "wrn"]: bIsIncludeLoc: bool = True if sAreaKey != None: sKey4Find: str = sAreaKey.replace("geo", "ExtOf") if sAreaKey[: 9] == "geoFrench": #Spec for all french territories sKey4Find = "ExtOfFrench" if sKey4Find in oGlobalCat: bIsIncludeLoc = not oGlobalCat[ sKey4Find] #Exclusion de zone bIsInclude = bIsIncludeLoc and oGlobalCat["freeFlightZone"] #Relevage du plafond de carte pour certaines zones situées en France if bIsIncludeLoc and ("freeFlightZoneExt" in oGlobalCat): aFrLocation = [ "geoFrenchAlps", "geoFrenchVosgesJura", "geoFrenchPyrenees" ] bIsExtAlt4Loc: bool = False for sLoc in aFrLocation: if oGlobalCat.get(sLoc, False): bIsExtAlt4Loc = True break if bIsExtAlt4Loc: bIsInclude = bIsInclude or ( bIsIncludeLoc and oGlobalCat["freeFlightZoneExt"]) if sContext in ["wrn"]: bIsInclude = bIsInclude and oGlobalCat[ "class"] == "Q" #Ne préserver que les zones DANGEREUSEs else: bIsInclude = bIsInclude and oGlobalCat[ "class"] != "Q" #Exclusion systématique des zones DANGEREUSEs sContent = "freeflightZone" sFile = sFile.replace("-all", "-freeflight") elif sContext == "cfd": bIsIncludeLoc: bool = True if "ExtOfFrench" in oGlobalCat: bIsIncludeLoc = not oGlobalCat[ "ExtOfFrench"] #Pour Exclure toutes zones hors de France bIsInclude = bIsIncludeLoc and oGlobalCat["freeFlightZone"] if "use4cfd" in oGlobalCat: bIsInclude = bIsInclude or oGlobalCat["use4cfd"] #Relevage systématique du plafond de la carte elif "freeFlightZoneExt" in oGlobalCat: bIsInclude = bIsInclude or ( bIsIncludeLoc and oGlobalCat["freeFlightZoneExt"]) bIsInclude = bIsInclude and oGlobalCat[ "class"] != "Q" #Exclusion systématique des zones DANGEREUSEs sContent = "freeflightZone for FFVL-CFD" sFile = sFile.replace("-all", "-ffvl-cfd") sAreaKey = "" else: sContext = "all" sContent = "allZone" bIsInclude = True #Exclude area if unkwown coordonnees if bIsInclude and sContext != "all" and "excludeAirspaceNotCoord" in oGlobalCat: if oGlobalCat["excludeAirspaceNotCoord"]: bIsInclude = False #Filtrage des zones par régionalisation bIsArea: bool = True if sContext == "cfd": bIsArea = oGlobalCat.get( "geoFrenchAll", False) #Filtrage sur la totalité des territoires Français #Maintenir ou Supprimer la LTA-France1 (originale ou spécifique) des cartes non-concernées par le territoire Français --> [D] LTA FRANCE 1 (Id=LTA13071) [FL115-FL195] elif oGlobalCat["id"] in ["LTA13071", "BpFrenchSS"]: if not sAreaKey in [None, "geoFrench", "geoFrenchAll"]: bIsArea = False elif bIsInclude and sAreaKey: if sAreaKey == cstWithoutLocation: #Identification des zones non-retenues dans aucun des filtrages géographique paramétrés bIsArea = False for sAreaKey2 in self.oGeoRefArea.AreasRef.keys(): if sAreaKey2 in oGlobalCat: bIsArea = bIsArea or oGlobalCat[sAreaKey2] if bIsArea: break bIsArea = not bIsArea elif sAreaKey == cstDeltaExtended: bIsArea = oGlobalCat.get("deltaExt", False) elif sAreaKey == cstFreeFlightZoneExt: bIsArea = oGlobalCat.get("freeFlightZoneExt", False) elif sAreaKey in oGlobalCat: bIsArea = oGlobalCat[sAreaKey] else: bIsArea = False if not bIsArea: sKey4Find: str = sAreaKey.replace( "geo", "IncOf") #test d'inclusion volontaire de la zone ? bIsArea = oGlobalCat.get(sKey4Find, False) if bIsArea and bIsInclude and (sGlobalKey in self.oGlobalGeoJSON): oSingleCat: dict = {} if sContext == "cfd": #Single properties for CFD or FlyXC - {"name":"Agen1 119.15","category":"E","bottom":"2000F MSL","bottom_m":0,"top":"FL 65","color":"#bfbf40"} oSingleCat.update({"name": oGlobalCat["nameV"]}) oSingleCat.update({"category": oGlobalCat["class"]}) oSingleCat.update({"type": oGlobalCat["type"]}) oSingleCat.update({ "alt": "{0} {1}".format( aixmReader.getSerializeAlt(oGlobalCat), aixmReader.getSerializeAltM(oGlobalCat)) }) oSingleCat.update({ "bottom": aixmReader.getSerializeAlt(oGlobalCat, "Low") }) oSingleCat.update( {"top": aixmReader.getSerializeAlt(oGlobalCat, "Upp")}) oSingleCat.update({"bottom_m": oGlobalCat["lowerM"]}) oSingleCat.update({"top_m": oGlobalCat["upperM"]}) oSingleCat.update({ "Ids": "GUId={0} UId={1} Id={2}".format( oGlobalCat.get("GUId", "!"), oGlobalCat["UId"], oGlobalCat["id"]) }) if "codeActivity" in oGlobalCat: oSingleCat.update( {"codeActivity": oGlobalCat["codeActivity"]}) if "exceptSAT" in oGlobalCat: oSingleCat.update( {"exceptSAT": oGlobalCat["exceptSAT"]}) if "exceptSUN" in oGlobalCat: oSingleCat.update( {"exceptSUN": oGlobalCat["exceptSUN"]}) if "exceptHOL" in oGlobalCat: oSingleCat.update( {"exceptHOL": oGlobalCat["exceptHOL"]}) if "seeNOTAM" in oGlobalCat: oSingleCat.update({"seeNOTAM": oGlobalCat["seeNOTAM"]}) if "activationCode" in oGlobalCat: oSingleCat.update( {"activationCode": oGlobalCat["activationCode"]}) if "activationDesc" in oGlobalCat: oSingleCat.update( {"activationDesc": oGlobalCat["activationDesc"]}) if "timeScheduling" in oGlobalCat: oSingleCat.update( {"timeScheduling": oGlobalCat["timeScheduling"]}) if "desc" in oGlobalCat: oSingleCat.update({"desc": oGlobalCat["desc"]}) else: #Extract single parts of properties aSinglePorperties: list = [ "nameV", "class", "type", "codeActivity", "lower", "lowerMin", "upper", "upperMax", "ordinalLowerM", "ordinalUpperM", "lowerM", "upperM", "groundEstimatedHeight", "desc", "declassifiable", "activationCode", "activationDesc", "timeScheduling", "Mhz", "GUId", "UId", "id" ] #Exclude: zoneType, groupZone, srcClass, srcType, vfrZone, vfrZoneExt, freeFlightZone, freeFlightZoneExt, srcName, name; etc... for sProp in aSinglePorperties: value = oGlobalCat.get(sProp, None) if value != None: oSingleCat.update({sProp: value}) if sContext != "cfd": aixm2json.addColorProperties( oGlobalCat, oSingleCat, self.oLog ) #Ajout des propriétés pour colorisation de la zone #Extraction des coordonnées oAsGeo = self.oGlobalGeoJSON[sGlobalKey] if oAsGeo[poaffCst.cstGeoType].lower() == ( poaffCst.cstGeoPoint).lower(): oCoords: list = oAsGeo[ poaffCst. cstGeoCoordinates] #get coordinates of geometry elif oAsGeo[poaffCst.cstGeoType].lower() == ( poaffCst.cstGeoLine).lower(): oCoords: list = oAsGeo[ poaffCst. cstGeoCoordinates] #get coordinates of geometry elif oAsGeo[poaffCst.cstGeoType].lower() == ( poaffCst.cstGeoPolygon).lower(): oCoords: list = oAsGeo[poaffCst.cstGeoCoordinates][ 0] #get coordinates of geometry #Optimisation du tracé GeoJSON oCoordsDst: list = [] #if epsilonReduce<0: --> do not change ! if epsilonReduce == 0 or ( epsilonReduce > 0 and len(oCoords) > 40 ): #Ne pas optimiser le tracé des zones ayant moins de 40 segments (préservation des tracés de cercle minimaliste) oCoordsDst = rdp(oCoords, epsilon=epsilonReduce ) #Optimisation du tracé des coordonnées #Remplacement du tracé s'il a été optimisé iOrgSize: int = len(oCoords) iNewSize: int = len(oCoordsDst) if iNewSize > 0 and iNewSize != iOrgSize: percent: float = round((1 - (iNewSize / iOrgSize)) * 100, 1) percent = int(percent) if percent >= 1.0 else percent sOpti: str = "Segments optimisés à {0}% ({1}->{2}) [rdp={3}] ***".format( percent, iOrgSize, iNewSize, epsilonReduce) self.oLog.debug( "GeoJSON RDP Optimisation: {0} - {1}".format( oSingleCat["nameV"], sOpti), level=2, outConsole=False) #self.oCtrl.oLog.debug("RDP Src: {0}".format(oCoords), level=8) #self.oCtrl.oLog.debug("RDP Dst: {0}".format(oCoordsDst), level=8) if oAsGeo[poaffCst.cstGeoType].lower() == ( poaffCst.cstGeoPoint).lower(): oNewGeo: dict = { poaffCst.cstGeoType: oAsGeo[poaffCst.cstGeoType], poaffCst.cstGeoCoordinates: oCoordsDst } elif oAsGeo[poaffCst.cstGeoType].lower() == ( poaffCst.cstGeoLine).lower(): oNewGeo: dict = { poaffCst.cstGeoType: oAsGeo[poaffCst.cstGeoType], poaffCst.cstGeoCoordinates: oCoordsDst } elif oAsGeo[poaffCst.cstGeoType].lower() == ( poaffCst.cstGeoPolygon).lower(): oNewGeo: dict = { poaffCst.cstGeoType: oAsGeo[poaffCst.cstGeoType], poaffCst.cstGeoCoordinates: [oCoordsDst] } oArea: dict = { poaffCst.cstGeoType: poaffCst.cstGeoFeature, poaffCst.cstGeoProperties: oSingleCat, poaffCst.cstGeoGeometry: oNewGeo } else: oArea: dict = { poaffCst.cstGeoType: poaffCst.cstGeoFeature, poaffCst.cstGeoProperties: oSingleCat, poaffCst.cstGeoGeometry: oAsGeo } oArea: dict = { poaffCst.cstGeoType: poaffCst.cstGeoFeature, poaffCst.cstGeoProperties: oSingleCat, poaffCst.cstGeoGeometry: oAsGeo } oGeoFeatures.append(oArea) barre.update(idx) barre.reset() if sAreaKey: sContent += " / " + sAreaKey sFile = sFile.replace(".geojson", "-" + sAreaKey + ".geojson") if sContext in ["wrn"]: sContent += " / Dangerous areas" sFile = sFile.replace(".geojson", "-warning" + ".geojson") sMsg: str = " file {0} - {1} areas in map" if len(oGeoFeatures) == 0: self.oLog.info("GeoJSON unwritten" + sMsg.format(sFile, len(oGeoFeatures)), outConsole=False) bpaTools.deleteFile(sFile) else: self.oLog.info("GeoJSON write" + sMsg.format(sFile, len(oGeoFeatures)), outConsole=False) oSrcFiles = oNewHeader.pop(airspacesCatalog.cstKeyCatalogSrcFiles) oNewHeader.update( {airspacesCatalog.cstKeyCatalogContent: sContent}) if sAreaKey in self.oGeoRefArea.AreasRef: sAreaDesc: str = self.oGeoRefArea.AreasRef[sAreaKey][2] oNewHeader.update( {airspacesCatalog.cstKeyCatalogKeyAreaDesc: sAreaDesc}) del oNewHeader[airspacesCatalog.cstKeyCatalogNbAreas] oNewHeader.update( {airspacesCatalog.cstKeyCatalogNbAreas: len(oGeoFeatures)}) oNewHeader.update( {airspacesCatalog.cstKeyCatalogSrcFiles: oSrcFiles}) self.oOutGeoJSON = {} #Output reset self.oOutGeoJSON.update({ poaffCst.cstGeoType: poaffCst.cstGeoFeatureCol, poaffCst.cstGeoHeaderFile: oNewHeader, poaffCst.cstGeoFeatures: oGeoFeatures }) bpaTools.writeJsonFile(sFile, self.oOutGeoJSON) #Sérialisation du fichier return
def parcsConsolidation() -> None: sAllOpenair: str = "" oParcs: dict = { "Jura": ["Jura", "20210324_Parc-Jura.txt"], "Bauges": ["Bauges", "20210104_FFVL_ParcBauges.txt"], "AnnecyMarais": ["AnnecyMarais", "20200120_FFVL_ParcAnnecyMarais.txt"], "Passy": ["Passy", "20191129_FFVL_ParcPassy.txt"], "Sixt-Passy": ["Sixt-Passy", "20210305_Sixt-Passy.txt"], "AiguillesRouges": ["AiguillesRouges", "20210304_AiguillesRouges.txt"], "Contamines": ["Contamines", "20210304_Contamines.txt"], "GrandeSassiere": ["GrandeSassiere", "20210304_GrandeSassiere.txt"], "Vanoise": ["Vanoise", "20210325_Vanoise.txt"], "Vercors": ["Vercors", "20210305_Vercors.txt"], "Ecrins": ["Ecrins", "20210304_Ecrins.txt"], "Mercantour": ["Mercantour", "20210304_Mercantour.txt"], "Champagne": ["Champagne", "20210202_BPa_ParcsNat_Champagne.txt"], "Cevennes": ["Cevennes", "20210324_PascalW_ParcCevennes.txt"], "BaieDeSomme": ["BaieDeSomme", "20200729_SergeR_ParcNat_BaieDeSomme.txt"], "Vauville": ["Vauville", "20210528_Mare-de-Vauville.txt"], "Hourtin": ["Hourtin", "20200729_SergeR_ParcNat_Hourtin.txt"], "Pyrenees": ["Pyrenees", "20210323_Pyrenees_hr.txt"], "Ordessa": ["Ordessa", "20210304_Ordessa.txt"], "Calanques": ["Calanques", "20210420_Calanques.txt"], "PortCros": ["PortCros", "20210420_PortCros.txt"], "Scandola": ["Scandola", "20210420_Scandola.txt"], "Italie": ["Italie", "20210324_ParcsItaliens.txt"] } iConstructParc: int = 1 #Phase de construction des Parcs: 0=Rien, 1=Phase1, 2=Phase2 if iConstructParc == 1: ###(deb) Phase 1 - Construction des fichiers unitaire pour mise au point du tracé d'un unique parc aParc = oParcs["Vauville"] sInPath: str = cstPoaffInPath + "Parcs/" + aParc[0] + "/" sOutPath: str = sInPath + "map/" makeAllFiles(sInPath, aParc[1], sOutPath, cstPoaffInPath) ###(end) Phase 1 - Construction des fichiers unitaire pour mise au point du tracé d'un unique parc if iConstructParc == 2: ###(deb) Phase 2a - Consolidation de tous les parcs dans un unique fichier pour traitement automatisé via poaff oUniqueKey: dict = {} for sKey, aParc in oParcs.items(): sInPath: str = cstPoaffInPath + "Parcs/" + aParc[0] + "/" sSrcFile = sInPath + aParc[1] sAllOpenair += "*" * (20 + len(sSrcFile)) + "\n" sAllOpenair += "*" * 5 + " source file - " + sSrcFile + "\n" sAllOpenair += "*" * (20 + len(sSrcFile)) + "\n" fopen = open(sSrcFile, "rt", encoding="cp1252", errors="ignore") lines = fopen.readlines() sMsg = "Parsing openair file - {}".format(sSrcFile) oLog.info("{}".format(sMsg), outConsole=True) barre = bpaTools.ProgressBar(len(lines), 20, title=sMsg) idx = 0 for line in lines: idx += 1 #Verifications pour qualité des données sTocken: str = "*AUID" if line[:len(sTocken)] == sTocken: #Ex: '*AUID GUId=! UId=! Id=PJuraNord' aAUID = line.split(" ") #Contrôle que les identifiants ne sont pas renseignés if aAUID[1] != "GUId=!" or aAUID[2] != "UId=!": raise Exception( "Error - Identification error in line -" + line) #Contrôle que l'identifiant local est bien unique dans l'ensemble des zones consolidés aAUID[3] = aAUID[3].replace("\n", "") sLocKey: str = aAUID[3].split("=")[1] sFind: str = oUniqueKey.get(sLocKey, "...") if sFind == sLocKey: raise Exception("Error - Duplicate identification " + aAUID[3] + " with " + sFind) else: oUniqueKey.update( {sLocKey: aAUID[3] + " in " + sSrcFile}) sAllOpenair += line barre.update(idx) sAllOpenair += "\n" * 2 barre.reset() sOutPath: str = cstPoaffInPath + "Parcs/__All-Parcs/" sOutFile: str = bpaTools.getDate(bpaTools.getNow()) + "_All-Parcs.txt" if sAllOpenair: sMsg = "Construct consolidated openair file - {}".format(sOutFile) oLog.info("{}".format(sMsg), outConsole=True) bpaTools.writeTextFile(sOutPath + sOutFile, sAllOpenair) ###(end) Phase 2a - Consolidation de tous les parcs dans un unique fichier pour traitement automatisé via poaff ###(deb) Phase 2b - Construction de l'aixm et des fichiers assimilés sur la base des parcs consolidés sInPath: str = sOutPath sOutPath: str = sInPath + "map/" makeAllFiles(sInPath, sOutFile, sOutPath, cstPoaffInPath) ###(fin) Phase 2b - Construction de l'aixm et des fichiers assimilés sur la base des parcs consolidés return
def makeAirspacesKml(self, epsilonReduce: float = -1) -> None: sTitle = "KML analyse" #if self.oLog: # self.oLog.info("makeAirspacesKml() " + sTitle, outConsole=False) #Interprétation du contenu source if poaffCst.cstGeoFeatures in self.oGeo: oFeatures: dict = self.oGeo[poaffCst.cstGeoFeatures] barre = bpaTools.ProgressBar(len(oFeatures), 20, title=sTitle) idx = 0 for oAs in oFeatures: idx += 1 oAsPro = oAs[poaffCst.cstGeoProperties] #get properties oAsGeo = oAs[poaffCst.cstGeoGeometry] #get geometry sClassZone: str = oAsPro.get("class", oAsPro.get("category", "?class")) sTypezZone: str = oAsPro.get("type", "") sTmpName: str = oAsPro.get("name", oAsPro.get("nameV", "?name")) sNameZone: str = "[" + sClassZone + "] " + sTmpName sDeclassifiable: str = oAsPro.get("declassifiable", "") sOrdUpperM: str = oAsPro.get("ordinalUpperM", None) #Ordinal AGL value sOrdLowerM: str = oAsPro.get("ordinalLowerM", None) #Ordinal AGL value sUpperM: str = oAsPro.get("upperM", oAsPro.get("top_m", 9999)) sLowerM: str = oAsPro.get("lowerM", oAsPro.get("bottom_m", 0)) sMinGroundHeight: str = 0 sMaxGroundHeight: str = 0 aGroundEH: list = oAsPro.get("groundEstimatedHeight", None) if aGroundEH: sMinGroundHeight = aGroundEH[0] sMaxGroundHeight = aGroundEH[3] sDesc: str = self.makeAirspaceHtmlDesc( sName=sNameZone, sClass=sClassZone, sType=sTypezZone, sCodeActivity=oAsPro.get("codeActivity", ""), bDeclassifiable=sDeclassifiable, sLower=oAsPro.get("lower", oAsPro.get("bottom", "SFC")), sUpper=oAsPro.get("upper", oAsPro.get("top", "FL999")), sLowerMin=oAsPro.get("lowerMin", ""), sUpperMax=oAsPro.get("upperMax", ""), sLowerM=sLowerM, sUpperM=sUpperM, sDescription=oAsPro.get("desc", ""), sActivationDesc=oAsPro.get("activationDesc", ""), sTimeScheduling=oAsPro.get("timeScheduling", ""), sMhz=oAsPro.get("Mhz", "")) #Classification pour organisation des zones if "freeFlightZone" in oAsPro: if (oAsPro.get("freeFlightZoneExt", None) == True) and (oAsPro.get( "freeFlightZone", None) == False): sTypeZone: str = "vfrZoneExt" elif (oAsPro.get("vfrZoneExt", None) == True) and (oAsPro.get("vfrZone", None) == False): sTypeZone: str = "vfrZoneExt" elif oAsPro.get("vfrZone", None) == True: sTypeZone: str = "vfrZone" elif oAsPro.get("vfrZone", None) == False: sTypeZone: str = "ifrZone" elif "lowerM" in oAsPro: if float(sLowerM) < 3500: sTypeZone: str = "vfrZone" elif float(sLowerM) < 5940: sTypeZone: str = "vfrZoneExt" else: sTypeZone: str = "ifrZone" else: sTypeZone: str = "Airspace" oTypeZone: dict = self.oKmlTmp.get(sTypeZone, {}) oClassZone: list = oTypeZone.get(sClassZone, []) if oAsGeo[poaffCst.cstGeoType].lower() == ( poaffCst.cstGeoPoint).lower(): oCoords: list = [] #Don't show this point feature elif oAsGeo[poaffCst.cstGeoType].lower() == ( poaffCst.cstGeoLine).lower(): oCoords: list = oAsGeo[ poaffCst. cstGeoCoordinates] #get coordinates of geometry elif oAsGeo[poaffCst.cstGeoType].lower() == ( poaffCst.cstGeoPolygon).lower(): oCoords: list = oAsGeo[poaffCst.cstGeoCoordinates][ 0] #get coordinates of geometry if len(oCoords) > 1: #Optimisation du tracé if epsilonReduce == 0 or ( epsilonReduce > 0 and len(oCoords) > 40 ): #Ne pas optimiser le tracé des zones ayant moins de 40 segments (préservation des tracés de cercle minimaliste) oCoordsDst: list = rdp( oCoords, epsilon=epsilonReduce ) #Optimisation du tracé des coordonnées iOrgSize: int = len(oCoords) iNewSize: int = len(oCoordsDst) if iNewSize > 0 and iNewSize != iOrgSize: percent: float = round( (1 - (iNewSize / iOrgSize)) * 100, 1) percent = int( percent) if percent >= 1.0 else percent sOpti: str = "Segments optimisés à {0}% ({1}->{2}) [rdp={3}] ***".format( percent, iOrgSize, iNewSize, epsilonReduce) self.oLog.debug( "Kml RDP Optimisation: {0} - {1}".format( sNameZone, sOpti), level=2, outConsole=False) else: oCoordsDst: list = oCoords #Stockage organisationnel temporaire oZone: list = [ sNameZone, sDesc, sTypezZone, sDeclassifiable, sUpperM, sLowerM, sOrdUpperM, sOrdLowerM, sMinGroundHeight, sMaxGroundHeight, oCoordsDst ] oClassZone.append(oZone) oTypeZone.update({sClassZone: oClassZone}) self.oKmlTmp.update({sTypeZone: oTypeZone}) barre.update(idx) barre.reset() #Construction de l'organisation des polygons dans le KML if len(self.oKmlTmp) > 0: sTitle = "KML generator" #if self.oLog: # self.oLog.info("makeAirspacesKml() " + sTitle, outConsole=False) #https://developers.google.com/kml/documentation/kmlreference#color #The first two hex characters define the alpha band, or opacity. # ff = completely solid # 7f = 50% transparency # 4f = ~20% transparency # 1f = ~10% transparency # 00 = completely translucent ## Couleurs de bases # Rouge - ff0000ff # Jaune - ff00ffff # Bleu - ffff0000 # Vert - ff00ff00 # Violet - FF800080 # Orange - FF0080FF # Marron - FF336699 # Rose - ffff00ff self.createKmlStyle(self.oKmlDoc, "fillRedPoly", "ff0000ff", "6f0000ff") self.createKmlStyle(self.oKmlDoc, "transRedPoly", "ff0000ff", "3f0000ff") self.createKmlStyle(self.oKmlDoc, "noFillRedPoly", "ff0000ff", "1f0000ff") self.createKmlStyle(self.oKmlDoc, "fillRedPurplePoly", "ff0000ff", "6f800080") self.createKmlStyle(self.oKmlDoc, "transRedPurplePoly", "ff0000ff", "3f800080") self.createKmlStyle(self.oKmlDoc, "fillPurplePoly", "ff800080", "6F800080") self.createKmlStyle(self.oKmlDoc, "transPurplePoly", "ff800080", "3F800080") self.createKmlStyle(self.oKmlDoc, "noFillPurplePoly", "ff800080", "1f800080") self.createKmlStyle(self.oKmlDoc, "fillOrangePoly", "ff0080ff", "6f0080ff") self.createKmlStyle(self.oKmlDoc, "transOrangePoly", "ff0080ff", "3f0080ff") self.createKmlStyle(self.oKmlDoc, "noFillOrangePoly", "ff0080ff", "1f0080ff") self.createKmlStyle(self.oKmlDoc, "fillBrownPoly", "ff004b9c", "6f004b9c") self.createKmlStyle(self.oKmlDoc, "transBrownPoly", "ff004b9c", "3f004b9c") #self.createKmlStyle(self.oKmlDoc, "fillBluePoly", "ffff0000", "6fff0000") self.createKmlStyle(self.oKmlDoc, "transBluePoly", "ffff0000", "3fff0000") self.createKmlStyle(self.oKmlDoc, "noFillBluePoly", "ffff0000", "1fff0000") self.createKmlStyle(self.oKmlDoc, "transGreenPoly", "ff00ff00", "3f00ff00") self.createKmlStyle(self.oKmlDoc, "noFillGreenPoly", "ff00ff00", "1f00ff00") self.createKmlStyle(self.oKmlDoc, "transYellowPoly", "ff00ffff", "6f00ffff") #self.createKmlStyle(self.oKmlDoc, "noFillYellowPoly","ff00ffff", "1f00ffff") barre = bpaTools.ProgressBar(len(oFeatures), 20, title=sTitle) idx = 0 for sKeyType, oTypeZone in self.oKmlTmp.items(): sVisiblility: str = "0" #default value if sKeyType == "Airspace": sVisiblility: str = "1" oFolderType = self.createKmlFolder( self.oKmlDoc, "Folder", "Airspace", sVisiblility, "Espace aérien") elif sKeyType == "vfrZone": sVisiblility: str = "1" oFolderType = self.createKmlFolder( self.oKmlDoc, "Folder", "Couche VFR", sVisiblility, "Couche de l'espace aérien VFR ; dont le plancher s'étand depuis la surface de la terre (SFC/AGL) jusqu'à l'altitude limite de la surface 'S' (FL115)" ) elif sKeyType == "vfrZoneExt": oFolderType = self.createKmlFolder( self.oKmlDoc, "Folder", "Couche VFR-Ext", sVisiblility, "Couche de l'espace aérien VFR étandue ; zones dont le plafond s'élève jusqu'à la limite maximum de FL195" ) elif sKeyType == "ifrZone": oFolderType = self.createKmlFolder( self.oKmlDoc, "Folder", "Couche IFR", sVisiblility, "Couche de l'espace aérien IFR ; zones hautes (UTA=UpperControlArea, OCA=OceanicControlArea, OTA=OceanicTransitionArea, ...) et autres espaces-aériens nécessaires aux transmissions radards ou radios (FIR=FlightInformationRegion, UIR=UpperFlightInformationRegion, SECTOR=ControlSector, ..." ) for sKeyClass, oClassZone in oTypeZone.items(): oFolderClass = self.createKmlFolder( oFolderType, "Folder", "Classe " + sKeyClass, sVisiblility) for oZone in oClassZone: idx += 1 sZoneName: str = oZone[0] sDesc: str = oZone[1] sTypezZone: str = oZone[2] sDeclassifiable: str = oZone[3] sUpperM: str = oZone[4] sLowerM: str = oZone[5] sOrdUpperM: str = oZone[6] #Ordinal AGL value sOrdLowerM: str = oZone[7] #Ordinal AGL value sMinGroundHeight = oZone[8] sMaxGroundHeight = oZone[9] oCoords: list = oZone[10] #Red and fill if sKeyClass in ["P", "ZIT"]: sStyle = "#fillRedPoly" #Red and fill elif sKeyClass in [ "A", "B", "C", "CTR", "CTR-P", "TMA", "TMA-P", "TMZ", "RMZ/TMZ", "TMZ/RMZ" ]: if sLowerM == 0: sStyle = "#fillRedPoly" else: sStyle = "#transRedPoly" #Red and No-Fill elif sKeyClass in [ "CTA", "CTA-P", "FIR", "FIR-P", "NO-FIR", "PART", "CLASS", "SECTOR", "SECTOR-C", "OCA", "OCA-P", "OTA", "OTA-P", "UTA", "UTA-P", "UIR", "UIR-P", "TSA", "CBA", "RCA", "RAS", "TRA", "AMA", "ASR", "ADIZ", "POLITICAL", "OTHER", "AWY" ]: sStyle = "#noFillRedPoly" #Purple and No-fill elif sKeyClass == "D" and sTypezZone == "LTA": sStyle = "#noFillPurplePoly" #Red and fill elif sKeyClass in ["D", "D-AMC" ] and not bool(sDeclassifiable): if sLowerM == 0: sStyle = "#fillRedPoly" else: sStyle = "#transRedPoly" #Purple and fill elif sKeyClass in ["D", "D-AMC" ] and bool(sDeclassifiable): if sLowerM == 0: sStyle = "#fillRedPurplePoly" else: sStyle = "#transRedPurplePoly" #Purple and fill elif sKeyClass in ["R", "R-AMC" ] and sTypezZone != "RTBA": if sLowerM == 0: sStyle = "#fillPurplePoly" else: sStyle = "#transPurplePoly" #Brown and fill elif sKeyClass in ["R", "R-AMC" ] and sTypezZone == "RTBA": if sLowerM == 0: sStyle = "#fillBrownPoly" else: sStyle = "#transBrownPoly" #Orange elif sKeyClass in [ "GP", "Q", "VV", "VL", "BA", "PA" ]: #Danger; Vol à voile; Vol libre; Ballon; Parachutisme sStyle = "#noFillOrangePoly" #Orange and No-Fill elif sKeyClass in ["RMZ", "W"]: if sLowerM == 0: sStyle = "#fillOrangePoly" else: sStyle = "#transOrangePoly" #Blue elif sKeyClass in [ "ZSM", "BIRD", "PROTECT", "D-OTHER", "SUR", "AER", "TRPLA", "TRVL", "VOL", "REFUEL" ]: if sLowerM == 0: sStyle = "#transBluePoly" else: sStyle = "#noFillBluePoly" #Green elif sKeyClass in [ "E", "F", "G", "SIV", "FIS", "FFVL", "FFVP" ]: if sLowerM == 0: sStyle = "#transGreenPoly" else: sStyle = "#noFillGreenPoly" #Yellow else: sStyle = "#transYellowPoly" if self.oLog: self.oLog.warning( "KML Color not found for Class={0}". format(sKeyClass), outConsole=False) oPlacemark = self.createKmlFolder( oFolderClass, "Placemark", sZoneName, sVisibility=sVisiblility, sDescription=sDesc) self.oKml.addTag(oPlacemark, "styleUrl", sValue=sStyle) oMultiGeo = self.oKml.addTag( oPlacemark, "MultiGeometry") oPolygon: list = [] sAltitudeMode: str = "absolute" sFinalUpper: str = sUpperM #Standard AMSL value if sOrdUpperM: #Ordinal AGL value sFinalUpper = int(sMaxGroundHeight) + int( sOrdUpperM ) #New - Elevation au dessus du point géographique le plus élevé de la zone if sLowerM == 0: #Le plancher est plaqué au sol #Construct top panel for oAs in oCoords: sPoint: str = str(oAs[0]) + "," + str( oAs[1]) + "," oPolygon.append(sPoint + str(sFinalUpper)) #add top panel oKmlCoords = self.createKmlPolygon( oMultiGeo, sExtrude="1", sAltitudeMode=sAltitudeMode) oKmlCoords.text = " ".join(oPolygon) else: sFinalLower: str = sLowerM #Standard AMSL value if sOrdLowerM: #Ordinal AGL value sFinalLower = int(sMinGroundHeight) + int( sOrdLowerM ) #New - Elevation au dessus du point géographique le moins élevé de la zone #Construct top panel for oAs in oCoords: sPoint: str = str(oAs[0]) + "," + str( oAs[1]) + "," oPolygon.append(sPoint + str(sFinalUpper)) #add top panel oKmlCoords = self.createKmlPolygon( oMultiGeo, sExtrude="0", sAltitudeMode=sAltitudeMode) oKmlCoords.text = " ".join(oPolygon) #Construct bottom panel oPolygon: list = [] for oAs in oCoords: sPoint: str = str(oAs[0]) + "," + str( oAs[1]) + "," oPolygon.append(sPoint + str(sFinalLower)) #add bottom panel oKmlCoords = self.createKmlPolygon( oMultiGeo, sExtrude="0", sAltitudeMode=sAltitudeMode) oKmlCoords.text = " ".join(oPolygon) #Construct sides panels for idx, oAs in enumerate(oCoords): sPoint0: str = str( oCoords[idx][0]) + "," + str( oCoords[idx][1]) + "," if idx + 1 == len(oCoords): sPoint1: str = str( oCoords[0][0]) + "," + str( oCoords[0][1]) + "," else: sPoint1: str = str( oCoords[idx + 1][0]) + "," + str( oCoords[idx + 1][1]) + "," oPolygon: list = [] oPolygon.append(sPoint0 + str(sFinalLower)) oPolygon.append(sPoint0 + str(sFinalUpper)) oPolygon.append(sPoint1 + str(sFinalUpper)) oPolygon.append(sPoint1 + str(sFinalLower)) oPolygon.append(sPoint0 + str(sFinalLower)) #Side panel oKmlCoords = self.createKmlPolygon( oMultiGeo, sExtrude="0", sAltitudeMode=sAltitudeMode) oKmlCoords.text = " ".join(oPolygon) barre.update(idx) barre.reset() return
def saveOpenairAirspacesFile2(self, sFile: str, sContext: str = "all", gpsType: str = "", exceptDay: str = "", sAreaKey: str = "") -> None: oOutOpenair: list = [] sContent: str = "" aAddHeader: list = [] lNbExcludeZone: int = 0 oGlobalHeader = self.oAsCat.oGlobalCatalog[ poaffCst. cstGeoHeaderFile] #Récupération de l'entete du catalogue global oNewHeader: dict = deepcopy(self.oAsCat.oGlobalCatalogHeader) oGlobalCats = self.oAsCat.oGlobalCatalog[ airspacesCatalog. cstKeyCatalogCatalog] #Récupération de la liste des zones consolidés sTitle = "Openair save airspaces file - {0} / {1} / {2} / {3}".format( sContext, gpsType, exceptDay, sAreaKey) barre = bpaTools.ProgressBar(len(oGlobalCats), 20, title=sTitle) idx = 0 for sGlobalKey, oGlobalCat in oGlobalCats.items( ): #Traitement du catalogue global complet idx += 1 #if oGlobalCat["id"] in ["TMA16169","TMA16170"]: # print(oGlobalCat["id"]) #Filtrage des zones par typologie de sorties bIsInclude: bool = False if sContext == "ifr": bIsInclude = (not oGlobalCat["vfrZone"]) and ( not oGlobalCat["groupZone"]) sContent = "ifrZone" sFile = sFile.replace("-all", "-ifr") elif sContext == "vfr": bIsInclude = oGlobalCat["vfrZone"] bIsInclude = bIsInclude or oGlobalCat.get( "vfrZoneExt", False ) #Exporter l'extension de vol possible en VFR de 0m jusqu'au FL195/5944m sContent = "vfrZone" sFile = sFile.replace("-all", "-vfr") elif sContext in ["ff", "wrn"]: bIsIncludeLoc: bool = True if sAreaKey != "": sKey4Find: str = sAreaKey.replace("geo", "ExtOf") if sAreaKey[: 9] == "geoFrench": #Spec for all french territories sKey4Find = "ExtOfFrench" if sKey4Find in oGlobalCat: bIsIncludeLoc = not oGlobalCat[ sKey4Find] #Exclusion de zone bIsInclude = bIsIncludeLoc and oGlobalCat["freeFlightZone"] #Relevage du plafond de carte pour certaines zones situées en France if bIsIncludeLoc and ("freeFlightZoneExt" in oGlobalCat): aFrLocation = [ "geoFrenchAlps", "geoFrenchVosgesJura", "geoFrenchPyrenees" ] bIsExtAlt4Loc: bool = False for sLoc in aFrLocation: if oGlobalCat.get(sLoc, False): bIsExtAlt4Loc = True break if bIsExtAlt4Loc: bIsInclude = bIsInclude or ( bIsIncludeLoc and oGlobalCat["freeFlightZoneExt"]) if sContext in ["wrn"]: bIsInclude = bIsInclude and oGlobalCat[ "class"] == "Q" #Ne préserver que les zones DANGEREUSEs else: bIsInclude = bIsInclude and oGlobalCat[ "class"] != "Q" #Exclusion systématique des zones DANGEREUSEs sContent = "freeflightZone" sFile = sFile.replace("-all", "-freeflight") elif sContext == "cfd": bIsIncludeLoc: bool = True if "ExtOfFrench" in oGlobalCat: bIsIncludeLoc = not oGlobalCat[ "ExtOfFrench"] #Pour Exclure toutes zones hors de France bIsInclude = bIsIncludeLoc and oGlobalCat["freeFlightZone"] if "use4cfd" in oGlobalCat: bIsInclude = bIsInclude or oGlobalCat["use4cfd"] #Relevage systématique du plafond de la carte elif "freeFlightZoneExt" in oGlobalCat: bIsInclude = bIsInclude or ( bIsIncludeLoc and oGlobalCat["freeFlightZoneExt"]) bIsInclude = bIsInclude and oGlobalCat[ "class"] != "Q" #Exclusion systématique des zones DANGEREUSEs sContent = "freeflightZone for FFVL-CFD" sFile = sFile.replace("-all", "-ffvl-cfd") sAreaKey = "" else: sContext = "all" sContent = "allZone" bIsInclude = True #Exclude area if unkwown coordonnees if bIsInclude and "excludeAirspaceNotCoord" in oGlobalCat: if oGlobalCat["excludeAirspaceNotCoord"]: bIsInclude = False #Filtrage des zones par régionalisation bIsArea: bool = True if sContext == "cfd": bIsArea = oGlobalCat.get( "geoFrenchAll", False) #Filtrage sur la totalité des territoires Français elif bIsInclude and sAreaKey: if sAreaKey in oGlobalCat: bIsArea = oGlobalCat[sAreaKey] else: bIsArea = False #Maintenir ou Supprimer la LTA-France1 (originale ou spécifique) des cartes non-concernées par le territoire Français --> [D] LTA FRANCE 1 (Id=LTA13071) [FL115-FL195] if bIsArea and oGlobalCat["id"] in ["LTA13071", "BpFrenchSS"]: if sAreaKey in ["", "geoFrench", "geoFrenchAll"]: aAddHeader.append( "'{0}' {1} - Symbolisation de la surface 'S' - Afin de simplifier cette carte, vous pouvez éventuellement supprimer cette couche limite du vol-libre (hors masifs-montagneux...)" .format(oGlobalCat["nameV"], aixmReader.getSerializeAlt(oGlobalCat))) else: bIsArea = False #Ne pas afficher cette zone incohérente pour ces régions if not bIsArea: sKey4Find: str = sAreaKey.replace( "geo", "IncOf") #test d'inclusion volontaire de la zone ? bIsArea = oGlobalCat.get(sKey4Find, False) #Filtrage des zones par jour d'activation if bIsInclude and bIsArea and exceptDay: if exceptDay in oGlobalCat: bIsInclude = False lNbExcludeZone += 1 if bIsArea and bIsInclude and (sGlobalKey in self.oGlobalOpenair): oAs: OpenairZone = self.oGlobalOpenair[sGlobalKey] if len(oAs.oBorder) == 1: None #Exclure tout les points fixes - or (oAs.oBorder[0]!=errLocalisationPoint) elif len(oAs.oBorder) == 2 and oAs.oBorder[0][:4] != "V X=": None #Exclure les doubles points fixes (DP.. + DP..) mais autoriser les cercles (V X=.. + DP..) else: oOutOpenair.append(oAs) barre.update(idx) barre.reset() if gpsType == "-gpsWithTopo": sContent += " / " + gpsType[1:] elif gpsType == "-gpsWithoutTopo": sContent += " / " + gpsType[1:] sFile = sFile.replace("-gpsWithTopo", "-gpsWithoutTopo") else: sFile += "_err" if sAreaKey: sContent += " / " + sAreaKey sFile = sFile.replace(".txt", "-" + sAreaKey + ".txt") if sContext in ["wrn"]: sContent += " / Dangerous areas" sFile = sFile.replace(".txt", "-warning" + ".txt") if exceptDay: ext4exceptDay = exceptDay.replace("except", "for") sContent += " / " + ext4exceptDay sFile = sFile.replace(".txt", "-" + ext4exceptDay + ".txt") if lNbExcludeZone == 0: oOutOpenair = [ ] #Annulation de la sortie cause fichier non-utile (pas de différentiel d'exclusion de zones) sMsg: str = " file {0} - {1} areas in map".format( sFile, len(oOutOpenair)) if len(oOutOpenair) == 0: self.oLog.info("Openair unwritten" + sMsg, outConsole=False) bpaTools.deleteFile(sFile) else: self.oLog.info("Openair write" + sMsg, outConsole=False) #Entête des fichiers oSrcFiles = oNewHeader.pop(airspacesCatalog.cstKeyCatalogSrcFiles) oNewHeader.update( {airspacesCatalog.cstKeyCatalogContent: sContent}) sAreaDesc: str = "" if sAreaKey in self.oGeoRefArea.AreasRef: sAreaDesc: str = self.oGeoRefArea.AreasRef[sAreaKey][2] oNewHeader.update( {airspacesCatalog.cstKeyCatalogKeyAreaDesc: sAreaDesc}) del oNewHeader[airspacesCatalog.cstKeyCatalogNbAreas] oNewHeader.update( {airspacesCatalog.cstKeyCatalogNbAreas: len(oOutOpenair)}) oNewHeader.update( {airspacesCatalog.cstKeyCatalogSrcFiles: oSrcFiles}) #Contexte pour optimisation du tracé digit: float = poaffCst.cstOpenairDigitOptimize epsilonReduce: float = poaffCst.cstOpenairEpsilonReduce #Param d'optimisation standard de KML nbMaxSegment: int = -1 #Nombre maxi de segment admissible (nécessaire pour génération des fichiers FAF) if sFile.find("ffvl-cfd") > 0: epsilonReduce = poaffCst.cstOpenairCfdEpsilonReduce #Imposer l'optimisation pour les sorties Openair CFD else: aToken = [ "geoFrenchNorth", "geoFrenchSouth", "geoFrenchNESW", "geoFrenchVosgesJura", "geoFrenchPyrenees", "geoFrenchAlps" ] if sAreaKey in aToken: epsilonReduce = poaffCst.cstOpenairEpsilonReduceMR #Optimisation moyenne résolution pour les sorties Openair régionnales nbMaxSegment = 100 #100 segments maxi pour création des fichiers FAF oTools = aixmReader.AixmTools(None) sOutOpenair: str = oTools.makeHeaderOpenairFile( oNewHeader, oOutOpenair, sContext, gpsType, exceptDay, sAreaKey, sAreaDesc, aAddHeader, digit=digit, epsilonReduce=epsilonReduce) #Sérialisation de toutes les zones oOp: OpenairArea = None for oOp in oOutOpenair: sOutOpenair += oOp.serializeArea(gpsType, digit, epsilonReduce, nbMaxSegment, self.oLog) + "\n" bpaTools.writeTextFile(sFile, sOutOpenair) #Sérialisation du fichier return