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
Exemple #2
0
 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
Exemple #3
0
    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
Exemple #4
0
    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
Exemple #5
0
 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
Exemple #6
0
    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
Exemple #7
0
    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
Exemple #8
0
    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
Exemple #9
0
    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
Exemple #10
0
    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
Exemple #11
0
 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
Exemple #12
0
    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
Exemple #13
0
    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
Exemple #14
0
    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
Exemple #15
0
    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
Exemple #16
0
 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
Exemple #18
0
    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
Exemple #19
0
    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
Exemple #20
0
    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
Exemple #21
0
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
Exemple #22
0
    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
Exemple #23
0
    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