def mouseReleaseEvent(self, event): rxy = RadarCoords.fromQPointF(event.scenePos()) if self.measuring_tool.isVisible() and event.button() in [ Qt.LeftButton, Qt.RightButton ]: if self.using_special_tool: stxy = RadarCoords.fromQPointF(self.measuring_tool.pos()) signals.specialTool.emit(EarthCoords.fromRadarCoords(stxy), self.measuring_tool.measuredHeading()) self.measuring_tool.stop(False) else: # using normal measuring tool if settings.measuring_tool_logs_coordinates: print('RELEASE: %s' % EarthCoords.fromRadarCoords(rxy).toString()) text, ok = QInputDialog.getText( self.parent(), 'Logging coordinates', 'Text label for logged coordinates (optional):') if ok: print('TEXT: %s' % text) self.measuring_tool.stop(True) elif event.button() == Qt.LeftButton: if self.prevent_mouse_release_deselect or self.mouseGrabberItem( ) != None: self.prevent_mouse_release_deselect = False else: selection.deselect() self._mouseInfo_flyToMouse(rxy) QGraphicsScene.mouseReleaseEvent(self, event)
def get_airport_data(icao): result = AirportData() result.navpoint = world_navpoint_db.findAirfield(icao) # START WITH SIMPLE ONE-LINERS with open_airport_file(icao) as f: for line in f: row_type = line_code(line) if is_xplane_airport_header(line): # HEADER LINE; get elevation tokens = line.split(maxsplit=2) result.field_elevation = float(tokens[1]) elif row_type == 100: # RUNWAY tokens = line.split() width = float(tokens[1]) surface = int(tokens[2]) name, lat, lon, disp_thr = tokens[8:12] rwy1 = DirRunway(name, EarthCoords(float(lat), float(lon)), float(disp_thr)) name, lat, lon, disp_thr = tokens[17:21] rwy2 = DirRunway(name, EarthCoords(float(lat), float(lon)), float(disp_thr)) result.addPhysicalRunway(width, surface, rwy1, rwy2) elif row_type == 102: # HELIPAD tokens = line.split() row_code, name, lat, lon, ori, l, w, surface = tokens[:8] centre = EarthCoords(float(lat), float(lon)) result.helipads.append( Helipad(name, centre, int(surface), float(l), float(w), Heading(float(ori), True))) elif row_type == 14: # VIEWPOINT (NOTE: ATC-pie allows for more than one, though X-plane specifies one or zero) row_code, lat, lon, height, ignore, name = line.split( maxsplit=5) result.viewpoints.append( (EarthCoords(float(lat), float(lon)), float(height), name.strip())) elif row_type == 19: # WINDSOCK row_code, lat, lon, ignore_rest_of_line = line.split( maxsplit=3) result.windsocks.append(EarthCoords(float(lat), float(lon))) elif row_type == 1302: # METADATA RECORD tokens = line.split() if len(tokens) == 3 and tokens[1] == 'transition_alt': result.transition_altitude = int(tokens[2]) # NOW COMPLEX MULTI-LINE READS result.ground_net = get_ground_network(icao) return result
def open_ad_positions_file(): try: return open(custom_ad_pos_file, encoding='utf8') except FileNotFoundError: # No custom airfield position file found; fall back on extracted X-plane inventory. try: return open(extracted_ad_pos_file, encoding='utf8') except FileNotFoundError: # Airport positions not extracted yet; build file from packaged X-plane world file. with open(fallback_world_apt_dat_file, encoding='iso-8859-15' ) as f: # WARNING: X-plane data encoded in ISO-8859-15 with open(extracted_ad_pos_file, 'w', encoding='utf8') as exf: line = f.readline() ad_count = 0 while line != '': # not EOF if is_xplane_airport_header(line): row_code, ignore1, ignore2, ignore3, icao_code, long_name = line.split( maxsplit=5) if icao_code.isalpha( ): # Ignoring airports with numbers in them---to many of them, hardly ever useful # we are inside the airport section looking for its coordinates coords = None line = f.readline() while line != '' and not is_xplane_airport_header( line): if line_code( line ) == 14: # X-plane viewpoint, unconditionally used as coords row_code, lat, lon, ignore_rest_of_line = line.split( maxsplit=3) coords = EarthCoords( float(lat), float(lon)) elif coords == None and line_code( line ) == 100: # falls back near a RWY end if no viewpoint for AD tokens = line.split() coords = EarthCoords( float(tokens[9]), float(tokens[10])).moved( Heading(360, True), .15) line = f.readline() if coords != None: # Airfields with unknown world coordinates are ignored exf.write('%s %s %s\n' % (icao_code, coords.toString(), long_name.strip())) ad_count += 1 else: line = f.readline() else: line = f.readline() # Terminate with the footer to mark a finished process exf.write('%d\n' % ad_count) # Now file should exist return open(extracted_ad_pos_file, encoding='utf8')
def get_ground_network(icao): with open_airport_file(icao) as f: ground_net = GroundNetwork() source_edges = [ ] # GroundNetwork pretty labelling breaks if we add duplicate edges line = f.readline() line_number = 1 while line != '': # not EOF if line_code(line) == 1201: # TWY node tokens = line.strip().split(maxsplit=5) lat, lon, ignore, nid = tokens[1:5] ground_net.addNode(nid, EarthCoords(float(lat), float(lon))) elif line_code(line) == 1202: # TWY edge tokens = line.strip().split(maxsplit=5) v1, v2 = tokens[1:3] twy_name = rwy_spec = None if len(tokens) == 6: if tokens[4] == 'runway': rwy_spec = tokens[5].rstrip() elif tokens[4].startswith( 'taxiway' ): # can be suffixed with "_X" to specify wing span twy_name = tokens[5].rstrip() if {v1, v2} in source_edges: print( 'WARNING: Ignoring duplicate ground route edge (%s, %s) in airport data file.' % (v1, v2)) else: source_edges.append({v1, v2}) try: ground_net.addEdge(v1, v2, rwy_spec, twy_name) except KeyError: print('Line %d: Invalid node for taxiway edge spec' % line_number) elif line_code(line) == 1300: # parking_position tokens = line.strip().split(maxsplit=6) if len(tokens) == 7: lat, lon, hdg, typ, who, pkid = tokens[1:7] if typ in ['gate', 'hangar', 'tie-down']: pos = EarthCoords(float(lat), float(lon)) cats = [] if who == 'all' else who.split('|') ground_net.addParkingPosition( pkid, pos, Heading(float(hdg), True), typ, cats) else: print('Line %d: Invalid parking position spec' % line_number) line = f.readline() # for new loop (more TWYs) line_number += 1 return ground_net
def import_airfield_data(): footer_line_count = None ad_added = 0 with open_ad_positions_file() as f: for line in f: if line.startswith('#'): continue split = line.split(maxsplit=2) if len(split) == 3: icao_code, lat_lon, name = split coords = EarthCoords.fromString(lat_lon) world_navpoint_db.add(Airfield(icao_code, coords, name.strip())) ad_added += 1 elif len(split) == 0: continue elif len(split) == 1 and footer_line_count == None: footer_line_count = int(split[0]) else: raise ValueError( 'Bad or illegal spec line in AD positions file: %s' % line.strip()) if footer_line_count == None or footer_line_count != ad_added: print('ERROR: inconsistencies detected in the AD positions file.') print('This is usually caused by an interrupted extraction process. ' \ 'Running the "cleanUp.sh" script should solve the problem in this case.') raise ValueError('AD data corrupt')
def closeEvent(self, event): if settings.session_manager.isRunning(): settings.session_manager.stop() if settings.controlled_tower_viewer.running: settings.controlled_tower_viewer.stop(wait=True) if speech_recognition_available: cleanup_SR_language_files() print('Closing main window.') settings.saved_strip_racks = env.strips.rackNames() settings.saved_strip_dock_state = self.strip_pane.stateSave() settings.saved_workspace_windowed_view = self.central_workspace.windowedView() settings.saved_workspace_windows = self.central_workspace.workspaceWindowsStateSave() signals.mainWindowClosing.emit() signals.disconnect() settings.saveGeneralAndSystemSettings() settings.saveLocalSettings(env.airport_data) settings.savePresetChatMessages() env.resetEnv() settings.resetSession() EarthCoords.clearRadarPos() QMainWindow.closeEvent(self, event)
def import_ILS_capabilities(airport_data): with open_data_file_fallback(custom_navaid_file, fallback_navaid_file) as f: for line in f: # all lines with ILS codes [4..9] have similar structure: tokens = line.split(maxsplit=10) if not (len(tokens) == 11 and tokens[0] in '456789'): continue row_code, lat, lon, elev, frq, rng, qdm, ignore, ad, rwy, last_to_strip = tokens if ad == airport_data.navpoint.code: try: drwy = airport_data.runway(rwy) coords = EarthCoords(float(lat), float(lon)) except KeyError: # unknown RWY print('Unknown RWY %s or bad LOC spec' % rwy) else: # we are interested in the line spec if row_code in ['4', '5']: # LOC drwy.ILS_cat = last_to_strip.strip() drwy.LOC_freq = '%s.%s' % (frq[:3], frq[3:]) drwy.LOC_bearing = Heading(float(qdm), True) drwy.LOC_range = drwy.threshold(dthr=True).distanceTo( coords.moved(drwy.LOC_bearing.opposite(), float(rng))) elif row_code == '6': # GS (angle prefixes the bearing) try: iqdm = qdm.index('.') - 3 except ValueError: iqdm = len(qdm) - 3 fpa_degrees = int(qdm[:iqdm]) / 100 drwy.param_FPA = 100 * tan(radians(fpa_degrees)) drwy.GS_range = drwy.threshold(dthr=True).distanceTo( coords.moved( Heading(float(qdm[iqdm:]), True).opposite(), float(rng))) elif row_code == '7': # OM drwy.OM_pos = coords elif row_code == '8': # MM drwy.MM_pos = coords elif row_code == '9': # IM drwy.IM_pos = coords
def get_ground_elevation_map(location_code): try: with open(elev_map_file_fmt % location_code, encoding='utf8') as f: nw = se = None line = f.readline() while nw == None and line != '': tokens = line.split('#', maxsplit=1)[0].split() if tokens == []: line = f.readline() elif len(tokens) == 2: nw = EarthCoords.fromString(tokens[0]) se = EarthCoords.fromString(tokens[1]) else: raise ValueError('invalid header line') if nw == None: raise ValueError('missing header line') matrix = [] xprec = None line = f.readline() while line.strip() != '': values = [ float(token) for token in line.split('#', maxsplit=1)[0].split() ] if xprec == None: xprec = len(values) elif len(values) != xprec: raise ValueError('expected %d values in row %d' % (xprec, len(matrix) + 1)) matrix.append(values) # add row line = f.readline() # Finished reading file. result = ElevationMap(nw.toRadarCoords(), se.toRadarCoords(), len(matrix), xprec) for i, row in enumerate(matrix): for j, elev in enumerate(row): result.setElevation(i, j, elev) return result except ValueError as err: print('Error in elevation map: %s' % err)
def parse_xplane_node_line(line): ''' returns a 5-value tuple: - node position - bezier ctrl point if not None - int paint type if not None - int light type if not None - bool if ending node (True if closes path), or None if path goes on ''' if not is_xplane_node_line(line): raise ValueError('Not a node line: %s' % line) tokens = line.split() row_code = tokens[0] node_coords = EarthCoords(float(tokens[1]), float(tokens[2])).toQPointF() bezier_ctrl = EarthCoords(float(tokens[3]), float( tokens[4])).toQPointF() if row_code in ['112', '114', '116'] else None ending_spec = None if row_code in ['111', '112' ] else row_code in ['113', '114'] paint_lights = [int(tk) for tk in tokens[3 if bezier_ctrl == None else 5:]] paint_type = next((t for t in paint_lights if t < 100), None) light_type = next((t for t in paint_lights if t >= 100), None) return node_coords, bezier_ctrl, paint_type, light_type, ending_spec
def import_navfix_data(): with open_data_file_fallback(custom_navfix_file, fallback_navfix_file) as f: for line in f: match = navfix_line_regexp.search(line) if match: # Fix spec line lat = float(match.group(1)) lon = float(match.group(2)) name = match.group(3) coordinates = EarthCoords(lat, lon) if name.isalpha() and len(name) == 5: world_navpoint_db.add(Fix(name, coordinates)) else: world_navpoint_db.add(Rnav(name, coordinates))
def import_airway_data(): with open_data_file_fallback(custom_airway_file, fallback_airway_file) as f: for line in f: tokens = line.split() if len(tokens) == 10: p1, lat1, lon1, p2, lat2, lon2, hi_lo, fl_lo, fl_hi, awy_name = tokens try: navpoint1 = world_navpoint_db.findClosest( EarthCoords(float(lat1), float(lon1)), code=p1, maxDist=awy_wp_max_dist) navpoint2 = world_navpoint_db.findClosest( EarthCoords(float(lat2), float(lon2)), code=p2, maxDist=awy_wp_max_dist) world_routing_db.addAwy(navpoint1, navpoint2, awy_name, fl_lo, fl_hi) world_routing_db.addAwy( navpoint2, navpoint1, awy_name, fl_lo, fl_hi) # FUTURE better data with unidirectional AWYs except NavpointError: #DEBUGprint('Ignoring AWY %s' % awy_name) pass
def import_navaid_data(): with open_data_file_fallback(custom_navaid_file, fallback_navaid_file) as f: dmelst = [] # list of DMEs to try to couple with NDBs/VOR(TAC)s for line in f: if line_code(line) in [2, 3]: # NDB or VOR row_code, lat, lon, ignore1, frq, ignore2, ignore3, short_name, xplane_name = line.split( maxsplit=8) long_name = xplane_name.strip() coords = EarthCoords(float(lat), float(lon)) if row_code == '2': # NDB world_navpoint_db.add( NDB(short_name, coords, frq, long_name)) else: # VOR/VORTAC is_vortac = 'VORTAC' in long_name world_navpoint_db.add( VOR(short_name, coords, '%s.%s' % (frq[:3], frq[3:]), long_name, tacan=is_vortac)) elif line_code(line) in [ 12, 13 ]: # DME: 12 = coupled with VOR/VORTAC; 13 = standalone or coupled with NDB row_code, lat, lon, ignore1, ignore2, ignore3, ignore4, short_name, xplane_name = line.split( maxsplit=8) coords = EarthCoords(float(lat), float(lon)) t = Navpoint.VOR if row_code == '12' else Navpoint.NDB dmelst.append((short_name, coords, t)) for name, pos, t in dmelst: try: p = world_navpoint_db.findClosest(pos, code=name, types=[t]) p.dme = True except NavpointError: #debug('Not coupling DME for:', name, t) pass
def read_point(s): match = re_point.fullmatch(s) if match: if match.group('named') == None: # lat/lon coordinates in SCT format lat, lon = s.split() lat_d, lat_m, lat_s = lat[1:].split('.', maxsplit=2) lon_d, lon_m, lon_s = lon[1:].split('.', maxsplit=2) return EarthCoords.fromString('%sd%sm%ss%s,%sd%sm%ss%s' \ % (lat_d, lat_m, lat_s, lat[0].upper(), lon_d, lon_m, lon_s, lon[0].upper())) else: # named point try: return env.navpoints.findUnique( match.group('named')).coordinates except NavpointError as err: raise ValueError('Named point out of range or not unique: %s' % s) else: raise ValueError('Not a valid point spec: %s' % s)
def mousePressEvent(self, event): QGraphicsScene.mousePressEvent(self, event) if not event.isAccepted(): if event.button() == Qt.RightButton: self.using_special_tool = settings.session_manager.session_type == SessionType.TEACHER \ and settings.session_manager.isRunning() and event.modifiers() & Qt.ShiftModifier self.measuring_tool.setPos(event.scenePos()) if self.using_special_tool: # using measuring tool with ulterior motive (creating teacher traffic) self.measuring_tool.start(False) else: # using normal measuring tool self.measuring_tool.start(True) rxy = RadarCoords.fromQPointF(event.scenePos()) self._mouseInfo_elevation(rxy) if settings.measuring_tool_logs_coordinates: print( 'Logging coordinates of new radar measurement...') print('PRESS: %s' % EarthCoords.fromRadarCoords(rxy).toString()) if not event.button() == Qt.LeftButton or event.modifiers( ) & Qt.ShiftModifier: # not panning event.accept()
def read_point_spec(specstr, db): mvlst = specstr.split('>') pbase = mvlst.pop(0) try: if ',' in pbase and '~' not in pbase: result = EarthCoords.fromString(pbase) else: result = navpointFromSpec(pbase, db).coordinates except NavpointError: raise ValueError( 'No navpoint for "%s" or navpoint not unique (consider using `~\' operator)' % pbase) else: while mvlst != []: mv = mvlst.pop(0).split(',') if len(mv) == 2: radial = Heading(float(mv[0]), True) distance = float(mv[1]) result = result.moved(radial, distance) else: raise ValueError('Bad use of `>\' in point spec "%s"' % specstr) return result
def radarPos(self): return EarthCoords.getRadarPos()
def run(self): ## PREPARING QUERY pos = env.radarPos() qdict = { 'username': settings.MP_social_name, 'lon': pos.lon, 'lat': pos.lat, 'range': some(settings.ORSX_handover_range, settings.radar_range), 'xmlVersion': '1.0', 'contacts': ','.join( acft.identifier for acft in env.radar.contacts()) # should this be all FGMS connections? } if settings.publicised_frequency != None: qdict['frequency'] = str(settings.publicised_frequency) server_response = server_query('getFlightplans', qdict) ## USING RESPONSE if server_response != None: try: ww_root = ElementTree.fromstring(server_response) except ElementTree.ParseError as parse_error: print('Parse error in SX server data: %s' % parse_error) return new_ATCs = [] # ATCs first for ww_atc in ww_root.find('atcsInRange').iter( 'atc'): # NOTE the server sends the full list each time atc = ATC(ww_atc.find('callsign').text) atc.social_name = ww_atc.find('username').text atc.position = EarthCoords(float(ww_atc.find('lat').text), float(ww_atc.find('lon').text)) ww_frq = ww_atc.find('frequency').text try: atc.frequency = CommFrequency(ww_frq) except ValueError: atc.frequency = None new_ATCs.append(atc) self.ATCs_on_last_run = new_ATCs # Then strip data (contact claims and handover) for ww_flightplan in ww_root.iter( 'flightplan' ): # NOTE the server only sends those when something changes ww_header = ww_flightplan.find('header') ww_callsign = ww_header.find('callsign').text ww_owner = ww_header.find('owner').text if ww_owner == None: if ww_callsign in self.current_contact_claims: del self.current_contact_claims[ww_callsign] else: self.current_contact_claims[ww_callsign] = ww_owner if ww_header.find( 'handover' ).text == settings.session_manager.myCallsign( ): # RECEIVE A STRIP! strip = Strip() strip.writeDetail(received_from_detail, ww_owner) strip.writeDetail( assigned_SQ_detail, ck_int(ww_header.find('squawk').text, base=8)) strip.writeDetail(assigned_altitude_detail, ww_header.find('assignedAlt').text) # Ignored from WW header above: <flags>, <assignedRunway>, <assignedRoute>, <status>, <flight> # Ignored from WW data below: <fuelTime>; used with ulterior motive: <pilot> ww_data = ww_flightplan.find('data') # ATC-pie hides a token in <pilot>, wake turb. on its left and callsign to its right # e.g. <pilot>M__ATC-pie__X-FOO</pilot> for M turb. and X-FOO strip callsign # If the token is absent, we know the strip is from OpenRadar hidden_tokens = some(ww_data.find('pilot').text, '').split(ATCpie_hidden_string, maxsplit=1) if len( hidden_tokens ) == 1: # hidden marker NOT present; previous strip editor was OpenRadar strip.writeDetail(FPL.CALLSIGN, ww_callsign) else: # recognise strip edited with ATC-pie strip.writeDetail(FPL.WTC, hidden_tokens[0]) strip.writeDetail(FPL.CALLSIGN, hidden_tokens[1]) strip.writeDetail(FPL.FLIGHT_RULES, ww_data.find('type').text) strip.writeDetail(FPL.ACFT_TYPE, ww_data.find('aircraft').text) strip.writeDetail(FPL.ICAO_DEP, ww_data.find('departure').text) strip.writeDetail(FPL.ICAO_ARR, ww_data.find('destination').text) strip.writeDetail(FPL.ROUTE, ww_data.find('route').text) strip.writeDetail(FPL.CRUISE_ALT, ww_data.find('cruisingAlt').text) spd = ck_int(ww_data.find('trueAirspeed').text) if spd != None: strip.writeDetail(FPL.TAS, Speed(spd)) strip.writeDetail(FPL.COMMENTS, ww_data.find('remarks').text) # Possibly ignored details (OpenRadar confuses FPLs and strips): DEP time, EET, alt. AD, souls [*] signals.receiveStrip.emit(strip) send_update(ww_callsign, strip) # Acknowledge strip
def updateMouseXY(self, sceneXY): self.target_point = EarthCoords.fromRadarCoords( RadarCoords.fromQPointF(sceneXY)) p_acft = self.acft.coords().toRadarCoords() acft_qpoint = p_acft.toQPointF() # Get node route sequence if QPointF.dotProduct( sceneXY - acft_qpoint, sceneXY - acft_qpoint ) < min_taxi_drag * min_taxi_drag: # min mouse move not reached self.node_route = None else: # get route end nodes src_node = None dest_node = None if env.airport_data != None: src_node = env.airport_data.ground_net.closestNode( self.acft.coords()) if src_node != None: dest_node = env.airport_data.ground_net.closestNode( self.target_point, maxdist=taxi_tool_snap_dist) if src_node == None or dest_node == None: self.node_route = None else: try: self.node_route = env.airport_data.ground_net.shortestTaxiRoute( src_node, dest_node, settings.taxi_instructions_avoid_runways) p_src = env.airport_data.ground_net.nodePosition( src_node).toRadarCoords() if self.node_route == []: # src and dest nodes are identical if p_acft.distanceTo( p_src) > groundnet_pos_taxi_precision: self.node_route = [src_node] else: # first node of list is the one following src; check if we must insert src p_next = env.airport_data.ground_net.nodePosition( self.node_route[0]).toRadarCoords() if not p_acft.isBetween(p_src, p_next, groundnet_pos_taxi_precision): self.node_route.insert(0, src_node) except ValueError: # no taxi route found self.node_route = None # Get parking position self.parking_position = None if self.node_route == None or self.node_route == []: d_max_snap = taxi_tool_snap_dist else: d_last_node = env.airport_data.ground_net.nodePosition( self.node_route[-1]).distanceTo(self.target_point) d_max_snap = min(taxi_tool_snap_dist, d_last_node) self.parking_position = env.airport_data.ground_net.closestParkingPosition( self.target_point, maxdist=d_max_snap) # Update bounding box and specify the lines to draw self.prepareGeometryChange() if self.node_route == None and self.parking_position == None: self.snapped_OK = False line_tip = sceneXY - acft_qpoint self.lines = [(QPointF(0, 0), line_tip)] self.bbox = QRectF(QPointF(0, 0), line_tip).normalized() else: self.snapped_OK = True self.lines = [] self.bbox = QRectF(0, 0, 0, 0) prev = QPointF(0, 0) if self.node_route != None: for n in self.node_route: p = env.airport_data.ground_net.nodePosition( n).toQPointF() - acft_qpoint self.lines.append((prev, p)) self.bbox |= QRectF(prev, p).normalized() prev = p if self.parking_position != None: pk_point = env.airport_data.ground_net.parkingPosition( self.parking_position).toQPointF() - acft_qpoint self.lines.append((prev, pk_point)) self.bbox |= QRectF(prev, pk_point).normalized() self.update()
def earthCoords(self): return EarthCoords.fromRadarCoords( RadarCoords.fromQPointF(self.scenePos()))
def SEcoords(self): return EarthCoords.fromRadarCoords( RadarCoords.fromQPointF( self.mapToScene(self.boundingRect().bottomRight())))