#coding: utf-8
# version 1.5
import itertools
import math
import re
import sys
from decimal import Decimal
try:
import workflow
action_in = workflow.get_input()
except ImportError:
# not running in Editorial
action_in = """
retrieved: 27Mar/0429zAF 009 KJFK/LFPG 27Mar2015/0545z3OFP 9/0/1Main OFP (Long copy #1)LFPG/27R 206 ..../.... 44.8/7.3 206TSUITABLE DESTINATION ALTERNATE SUMMARY ALTN DIST LVL WC TIME FUEL VIA LFPO/26 82 100 T003 0020 2530 OLDGT INFO/LFQQ/26 128 140 H010 0029 3520 NURMO9A NURMO N874 CMB P990 CMB1B INFO/EGKK/26L 177 200 H035 0038 4670 OPALE9A OPALE UT421 KUNAV P2140 TIMBA3B INFO/EBBR/25L 195 260 H011 0038 4710 NURMO9A NURMO UN874 CMB P2180 UZ373 ARVOL ARVOL6ADESTINATION ALTERNATE LFPO--------------------------------------------------------------------WAYPNT RTE- MAG M ETO /ATO FL TP OAT WIND CONS/EFOB AWY MORA TRU DIS GS ETE /TTE--------------------------------------------------------------------LFPG/27R 200 ..../.... CLB -2 294/017 5.6 22 200T 0011 0003/0003 .../...T-O-C 200 533 ..../.... 100 38 -6 307/029 1.3/4.3 22 200T 0027 349 0005/0008 .../...T-O-D 200 ..../.... DSC 38 -5 305/027 1.8/3.8 22 200T 0004 0001/0008 .../...OLDGT 018 ..../.... DSC +0 288/014 1.8/3.7 22 019T 0041 0012/0020 .../...LFPO/26 018 ..../.... 2.5/3.0 019TWPT COORDINATESKJFK N4038.4W07346.7 GREKI N4128.8W07318.8 MARTN N4234.9W07316.2EBONY N4454.1W06709.4 ALLRY N5030.0W05200.0 N5100.0W05000.0 N5300.0W04000.0 N5500.0W03000.0 N5500.0W02000.0RESNO N5500.0W01500.0 NETKI N5500.0W01400.0 BAKUR N5214.5W00540.8STU N5159.7W00502.4 NUMPO N5136.6W00317.0 OKESI N5126.6W00203.7BEDEK N5122.3W00133.5 NIGIT N5118.8W00110.3 VAPID N5115.2W00102.7MID N5103.2W00037.5 SFD N5045.6E00007.3 WAFFU N5035.0E00021.0HARDY N5028.3E00029.5 XIDIL N5021.1E00038.5 PETAX N5011.2E00050.9BIBAX N5003.5E00100.5 KOLIV N4918.0E00134.2 MOPAR N4917.5E00145.4PG534 N4915.7E00223.3 CRL N4915.3E00230.9 PG536 N4913.9E00242.9LFPG N4900.6E00232.9--------------------------------------------------------------------LFPG N4900.6E00232.9 OLDGT N4825.8E00213.8 LFPO N4843.4E00222.8ATC FLIGHT PLANFF KZNYZQZX KNYCZZZX CZQMZQZX CZQMZQZR CZQXZQZX EUCHZMFP EUCBZMFP270425 LFPGYEYX(FPL-AFR009-IS-B77W/H-SDE2E3FGHIJ3J5J6M1M2RWXY/LB1D1-KJFK0545-N0476F350 DCT GREKI DCT MARTN DCT EBONY/M084F350 N247A ALLRY/M084F370 DCT 51N050W 53N040W 55N030W 55N020W DCT RESNO DCT NETKI/N0479F350 DCT BAKUR/N0463F350 UN546 STU UP2 NIGIT UL18 SFD/N0414F250 UM605 BIBAX BIBAX7W-LFPG0554 LFPO-PBN/A1B1C1D1L1S1 DOF/150327 REG/FGSQB EET/KZBW0004 CZQM0047 CZQX0119 ALLRY0156 51N050W0205 53N040W0247 EGGX0328 55N020W0403 RESNO0420 NETKI0423 EISN0432 EGTT0457 LFFF0526 SEL/HPLM OPR/AFR RALT/CYQX EINN RVR/100 RMK/ACAS)Generated a
t 27Mar/0433zPage 10 of
retrieved: 27Mar/0429zAF 009 KJFK/LFPG 27Mar2015/0545z3OFP 9/0/1Main OFP (Long copy #1)TRACKSNAT WESTBND TRACKS FL 310/390 INCLUSIVE 27MAR1130-27MAR1900Z* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *Generated at 27Mar/0433zPage 11 ofA RESNO 56N020W 57N030W 58N040W 58N050W CUDDY LVLS WB 310 320 330 340 350 360 370 380 390 LVLS EB NIL EUR RTS WEST NIL NAR NIL B DOGAL 55N020W 56N030W 57N040W 57N050W HOIST LVLS WB 310 320 330 340 350 360 370 380 390 LVLS EB NIL EUR RTS WEST NIL NAR NIL C MALOT 54N020W 55N030W 56N040W 56N050W JANJO LVLS WB 310 320 330 340 350 360 370 380 390 LVLS EB NIL EUR RTS WEST NIL NAR NIL D SOMAX 50N020W 49N030W 48N040W 45N050W VODOR LVLS WB 310 320 330 350 360 370 390 LVLS EB NIL EUR RTS WEST NIL NAR NIL E BEDRA 49N020W 48N030W 47N040W 44N050W BOBTU JAROM LVLS WB 310 320 330 350 360 370 390 LVLS EB NIL EUR RTS WEST NIL NAR NIL F OMOKO 48N015W 48N020W 47N030W 46N040W 43N050W JEBBY CARAC LVLS WB 310 320 330 350 360 370 390 LVLS EB NIL EUR RTS WEST NIL NAR NIL G ETIKI 47N015W 47N020W 46N030W 45N040W 42N050W 42N060W DOVEY LVLS WB 310 320 330 350 360 370 390 LVLS EB NIL EUR RTS WEST VIA REGHI NAR NIL H 43N040W 41N050W 41N060W JOBOC LVLS WB 330 350 370 390 LVLS EB NIL EUR RTS WEST NIL NAR NIL J 42N040W 38N050W 33N060W NUMBR LVLS WB 320 340 360 380 LVLS EB NIL EUR RTS WEST NIL NAR NIL NOTES:1. TMI IS 086 AND OPERATORS ARE REMINDED TO INCLUDE THETMI NUMBER AS PART OF THE OCEANIC CLEARANCE READ BACK.2. ADS-C AND CPDLC MANDATED OTS ARE AS FOLLOWSTRACK A 350 360 370 380 390TRACK B 350 360 370 380 390TRACK C 350 360 370 380 390TRACK D 350 360 370 390TRACK E 350 360 370 390TRACK F 350 360 370 390TRACK G 350 360 370 390END OF ADS-C AND CPDLC MANDATED OTS3. FOR STRATEGIC LATERAL OFFSET AND CONTINGENCY PROCEDURES RELATEDTO OPS IN NAT FLOW PLEASE REFER TO THE NAT PROGRAMME COORDINATIONWEB SITE AT WWW.NAT PCO.ORG. SLOP SHOULD BE USED AS A STANDARD
"""
# print action_in
# filter the main route part
try:
main = action_in.split('WPT COORDINATES', 1)[1]
except IndexError:
print "WPT COORDINATES not found"
print "retry or send OFP to Yammer's group Maps.me"
sys.exit()
try:
main = main.split('----', 1)[0]
except IndexError:
pass # will process till the end of file
# all coordinates for main route regex
# returns a list of tuples (name, lat, long)
# lat and long are in text format NSEW prefixed, in degree/minute format
m = re.findall(r'(\S+|\s+)\s+([NS]\d{4}\.\d)([EW]\d{5}\.\d)', main)
def dm2dec(coord):
"""convert lido coordinates to decimal"""
sign = 1 if coord[0] in ('N', 'E') else -1
offset = 3 if coord[0] in ('N', 'S') else 4
degrees = Decimal(coord[1:offset])
minutes = Decimal(coord[offset:])
return sign * (degrees + minutes/60)
# coordinates suitable for kml (name, long, lat)
main_coordinates = [(t[0], # name
str(dm2dec(t[2])), # long as str
str(dm2dec(t[1]))) # lat as str
for t in m]
# Optional tracks put as a list in natmarks variable
natmarks = []
def waypoint_todec(wpt):
"""convert Arinc track waypoint to decimal point usable by maps.me"""
def signed(letter, lon, lat):
# W=S=- E=N=+
if letter == 'N': # NW
return -lon, lat
elif letter == 'E': # NE
return lon, lat
elif letter == 'S': # SE
return lon, -lat
elif letter == 'W': # SW
return -lon, -lat
else:
raise ValueError('unknow letter %s in %s' % (letter, wpt))
if wpt[0] in 'NESW':
# N5520 lon<100
lat = Decimal(wpt[1:3]) + Decimal(0.5)
lon = Decimal(wpt[3:5])
return signed(wpt[0], lon, lat)
elif wpt[1] in 'NESW':
# 5N520 lon>=100
lat = Decimal(wpt[0] + wpt[2]) + Decimal(0.5)
lon = Decimal('1' + wpt[3:5])
return signed(wpt[1], lon, lat)
elif wpt[4] in 'NS':
# 5530N020W => N5530.0W02000.0 => (55.5, -20)
lat = dm2dec(wpt[4] + wpt[0:4] + '.0')
lon = dm2dec((wpt[-1] + wpt[5:-1] + '00')[:5] + '.0')
return lon, lat
else:
# 55N020W => N5500.0W02000.0 => (-20.0, 55.0)
lat = dm2dec(wpt[2] + wpt[0:2] + '00.0')
lon = dm2dec(wpt[6] + wpt[3:6] + '00.0')
return lon, lat
def tracks_coordinates(text):
"""extracts track geographic waypoint, returns a list of decimal (lon,lat)
"""
text = text.split('LVLS')[0]
m = re.findall(r'(\d{2,4}[NS]\d{3,5}[EW]|[NESW]\d{4}|\d[NESW]\d{3}[^EW])\s', text)
output = []
for wpt in m:
output.append(waypoint_todec(wpt))
return output
nat_tpl ="""
<Placemark>
<name>NAT {track_id}</name>
<styleUrl>{style}</styleUrl>
<description>{description}</description>
<LineString>
<tessellate>1</tessellate>
<coordinates>{coordinates}</coordinates>
</LineString>
</Placemark>
{nat_label}
"""
nat_label_tpl = """
<Placemark>
<name>NAT {track_id}</name>
<styleUrl>{style}</styleUrl>
<description>{description}</description>
<Point>
<coordinates>{coordinates}</coordinates>
</Point>
</Placemark>
"""
# find the optional TRACKS part
try:
s = action_in.split('TRACKSNAT', 1)[1]
s = s.split('NOTES:', 1)[0]
if 'REMARKS' in s:
s = s.split('REMARKS:', 1)[0]
s = s.split('Generated at')[0]
if ' LVLS ' in s:
it = iter(re.split(r'(?:\s|[^A-Z])([A-Z])\s{3}', s)[1:])
tracks = zip(it, it)
else:
def updated_mar2016_generator():
# Letter is lost in the middle
# track route starts with something like ELSIR 50
l = [m.start() for m in re.finditer('[A-Z]{5} \d\d', s)]
for start, end in itertools.izip_longest(l, l[1:]):
t = s[start:end]
# letter is here
parts = re.split('([A-Z])LVLS', t)
# adds some missing spaces
parts[2] = parts[2].replace('LVLS', ' LVLS').replace('NIL', 'NIL ')
yield parts[1], "%s LVLS%s" % (parts[0], parts[2])
tracks = list(updated_mar2016_generator())
except IndexError:
tracks =[]
# render natmarks
# and keep track of all point having a nat_label
nat_labels = []
for t in tracks:
try:
coordinates = tracks_coordinates(t[1])
coordinates_text = " ".join(["%s,%s" %c for c in coordinates])
nat_label = ''
if coordinates:
lon, lat = coordinates[0] # coordinates[-1] for exit point
nat_labels.append((str(lon), str(lat)))
nat_label = nat_label_tpl.format(
style="#placemark-yellow",
track_id=t[0],
description=t[1],
coordinates="%s,%s" % (lon, lat))
# render the track line
natmarks.append(nat_tpl.format(
style="#rnat",
track_id=t[0],
description=t[1],
nat_label=nat_label, # or '' to remove bbthe label
coordinates=coordinates_text))
except ValueError:
print "Error while building track %s: input was %s" % t
print "Please send OFP to Yammer's group Maps.me"
# rendering of all route placemarks in a list
# we skip all points having already a nat_label set
main_tpl = """
<Placemark>
<name>{name}</name>
<styleUrl>{style}</styleUrl>
<description>{description}</description>
<Point>
<coordinates>{coordinates}</coordinates>
</Point>
</Placemark>
"""
main_placemarks = []
for c in main_coordinates:
if c[1:] in nat_labels:
continue
main_placemarks.append(
main_tpl.format(
style="#placemark-purple",name=c[0],
description=c[0],
coordinates=",".join(c[1:])))
def splitgeodesics(longitude1,latitude1,longitude2,latitude2,num_of_segments='auto'):
if num_of_segments == 'auto':
num_of_segments = int(math.ceil(max(
abs(longitude1 - longitude2), abs(latitude1 - latitude2))/5))
ptlon1 = longitude1
ptlat1 = latitude1
ptlon2 = longitude2
ptlat2 = latitude2
numberofsegments = num_of_segments
onelessthansegments = numberofsegments - 1
fractionalincrement = (1.0/onelessthansegments)
ptlon1_radians = math.radians(ptlon1)
ptlat1_radians = math.radians(ptlat1)
ptlon2_radians = math.radians(ptlon2)
ptlat2_radians = math.radians(ptlat2)
distance_radians=2*math.asin(math.sqrt(math.pow((math.sin((ptlat1_radians-ptlat2_radians)/2)),2) + math.cos(ptlat1_radians)*math.cos(ptlat2_radians)*math.pow((math.sin((ptlon1_radians-ptlon2_radians)/2)),2)))
# 6371.009 represents the mean radius of the earth
# shortest path distance
distance_km = 6371.009 * distance_radians
mylats = []
mylons = []
# write the starting coordinates
mylats.append([])
mylons.append([])
mylats[0] = ptlat1
mylons[0] = ptlon1
f = fractionalincrement
icounter = 1
while (icounter < onelessthansegments):
icountmin1 = icounter - 1
mylats.append([])
mylons.append([])
# f is expressed as a fraction along the route from point 1 to point 2
A=math.sin((1-f)*distance_radians)/math.sin(distance_radians)
B=math.sin(f*distance_radians)/math.sin(distance_radians)
x = A*math.cos(ptlat1_radians)*math.cos(ptlon1_radians) + B*math.cos(ptlat2_radians)*math.cos(ptlon2_radians)
y = A*math.cos(ptlat1_radians)*math.sin(ptlon1_radians) + B*math.cos(ptlat2_radians)*math.sin(ptlon2_radians)
z = A*math.sin(ptlat1_radians) + B*math.sin(ptlat2_radians)
newlat=math.atan2(z,math.sqrt(math.pow(x,2)+math.pow(y,2)))
newlon=math.atan2(y,x)
newlat_degrees = math.degrees(newlat)
newlon_degrees = math.degrees(newlon)
mylats[icounter] = newlat_degrees
mylons[icounter] = newlon_degrees
icounter += 1
f = f + fractionalincrement
# write the ending coordinates
mylats.append([])
mylons.append([])
mylats[onelessthansegments] = ptlat2
mylons[onelessthansegments] = ptlon2
return zip(mylons, mylats)
# kml file template
# copied from www.notreavion.net/convert/
# kml does not support inline base 64 image, may be we should create
# a kmz file instead with local copy of images
xml_tpl = """<?xml version='1.0' encoding='UTF-8'?>
<kml xmlns='http://www.opengis.net/kml/2.2' xmlns:gx='http://www.google.com/kml/ext/2.2' xmlns:kml='http://www.opengis.net/kml/2.2' xmlns:atom='http://www.w3.org/2005/Atom'>
<Document>
<name>{route_name}</name>
<Style id='rnat'>
<LineStyle>
<color>60DA25A8</color>
<width>1</width>
</LineStyle>
</Style>
<Style id='no_icone'>
<IconStyle>
<Icon>
<href>http://www.notreavion.net/convert/images/no_icon.png</href>
</Icon>
<hotSpot x='0.5' y='0.0' xunits='fraction' yunits='fraction' />
</IconStyle>
</Style>
<Style id='placemark-purple'>
<IconStyle>
<Icon>
<href>http://www.notreavion.net/convert/images/icone_route.png</href>
</Icon>
<hotSpot x='0.5' y='0.5' xunits='fraction' yunits='fraction' />
</IconStyle>
</Style>
<Style id='placemark-pink'>
<IconStyle>
<Icon>
<href>http://www.notreavion.net/convert/images/icone_deg.png</href >
</Icon>
<hotSpot x='0.5' y='0.5' xunits='fraction' yunits='fraction' />
</IconStyle>
</Style>
<Style id='rmain'>
<LineStyle>
<color>FFDA25A8</color>
<width>3</width>
</LineStyle>
<PolyStyle>
<color>FFDA25A8</color>
</PolyStyle>
</Style>
<Style id='great_circle'>
<LineStyle>
<color>5000FFFF</color>
<width>3</width>
</LineStyle>
<PolyStyle>
<color>5000FFFF</color>
</PolyStyle>
</Style>
<Folder>
<name>Lignes</name>
<open>1</open>
{natmarks}
<Placemark>
<name>Rmain</name>
<styleUrl>#rmain</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>{main_coordinates}</coordinates>
</LineString>
</Placemark>
<Placemark>
<name>{great_circle_name}</name>
<styleUrl>#great_circle</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>{great_circle_coordinates}</coordinates>
</LineString>
</Placemark>
</Folder>
<Folder>
{placemarks}
</Folder>
</Document>
</kml>
"""
try:
great_circle_coordinates = splitgeodesics(
float(main_coordinates[0][1]), float(main_coordinates[0][2]),
float(main_coordinates[-1][1]), float(main_coordinates[-1][2]))
except:
great_circle_coordinates = []
action_out = xml_tpl.format(
route_name="%s-%s" % (main_coordinates[0][0], main_coordinates[-1][0]),
great_circle_name='Orthdromie %s-%s' % (main_coordinates[0][0], main_coordinates[-1][0]),
great_circle_coordinates=" ".join(["%s,%s" % c for c in great_circle_coordinates]),
main_coordinates=" ".join(["%s,%s" % c[1:] for c in main_coordinates]),
natmarks="\n".join(natmarks),
placemarks="\n".join(main_placemarks))
try:
workflow.set_output(action_out)
except NameError:
# not running in Editorial
print action_out
There are no comments yet.