Python 2.7
pandas 0.7.0.dev
pyproj 1.9.0
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# See: http://bit.ly/GKgHU2 | |
import pandas | |
import pyproj | |
from string import Template | |
# Dataframe from table of airports | |
airports = pandas.read_csv("data/airports_new.csv") | |
# Dataframe from table of flights departing from Beijing airport (PEK) | |
flights = pandas.read_csv("data/PEK.csv") | |
# Count number of flights from PEK | |
counts = flights.groupby(['From','To'])['Distance'].count().reset_index() | |
vector_columns = ['latitude','longitude','altitude','IATA','name'] | |
# Dataframe merging counts with additional destination airport information | |
flights_ll = pandas.merge(counts, airports, left_on='To', right_on='IATA')[[0]+vector_columns] | |
# lat, lon, z and name simulated vector of PEK airport | |
PEK = airports.ix[ airports['IATA']=='PEK', vector_columns].as_matrix()[0] | |
all_airports_from_pek = flights_ll[vector_columns].as_matrix() | |
# Geodetic converter | |
geod = pyproj.Geod(ellps='WGS84') | |
# Number of points of linestring that approximates each great circle | |
STEPS = 100 | |
# KML output. | |
# NOTE: I could have used pyKML but preferred manual string templating assembly. | |
def toKML(great_circles): | |
linestyle_tpl = Template( """ | |
<Style id="linestyle$count"> | |
<LineStyle> | |
<color>7f0000ff</color> | |
<width>$count</width> | |
<gx:labelVisibility>1</gx:labelVisibility> | |
</LineStyle> | |
</Style>""" | |
) | |
airports_tpl = Template(""" | |
<Placemark> | |
<name>$name ($IATA)</name> | |
<styleUrl>#sn_shaded_dot</styleUrl> | |
<Point> | |
<extrude>true</extrude> | |
<altitudeMode>relativeToGround</altitudeMode> | |
<coordinates>$lon,$lat,2</coordinates> | |
</Point> | |
</Placemark>""" | |
) | |
linestring_tpl = Template(""" | |
<Placemark> | |
<name>PEK to $IATA</name> | |
<styleUrl>#linestyle$count</styleUrl> | |
<LineString> | |
<extrude>1</extrude> | |
<tessellate>1</tessellate> | |
<coordinates> | |
$coordinates | |
</coordinates> | |
</LineString> | |
</Placemark>""" | |
) | |
KML_tpl = Template("""<?xml version="1.0" encoding="UTF-8"?> | |
<kml xmlns="http://www.opengis.net/kml/2.2"> | |
<Document> | |
<name>Flights from PEK</name> | |
<open>1</open> | |
<Style id="sn_shaded_dot"> | |
<IconStyle> | |
<scale>1.2</scale> | |
<Icon> | |
<href>http://maps.google.com/mapfiles/kml/shapes/shaded_dot.png</href> | |
</Icon> | |
</IconStyle> | |
</Style> | |
$linestyles | |
<Folder> | |
<name>Airports</name> | |
$airports | |
</Folder> | |
<Folder> | |
<name>Flights</name> | |
$linestrings | |
</Folder> | |
</Document> | |
</kml>""" | |
) | |
return KML_tpl.substitute( | |
dict( | |
linestyles = ''.join([ linestyle_tpl.substitute(dict(count = i)) for i in xrange(1,11)]), | |
airports = ''.join([ airports_tpl.substitute(dict(name=a[-1],IATA=a[-2],lat=a[0],lon=a[1])) for a in all_airports_from_pek]), | |
linestrings =''.join([ linestring_tpl.substitute(gc) for gc in great_circles]) | |
) | |
) | |
def great_arc(FROM, TO): | |
# geod.npts takes points as (lon, lat), not as (lat, lon) | |
great_circle = geod.npts(FROM[1], FROM[0], TO[1], TO[0], STEPS) | |
#az1, az2, D = geod.inv(FROM[1], FROM[0], TO[1], TO[0]) | |
for i, (lat, lon) in enumerate(great_circle): | |
# Altitude is 0, irrelevant at this scale | |
yield (lat, lon, 0) | |
def main(): | |
great_circles = [ | |
dict( | |
count = F[0], | |
name = F[-1], | |
IATA = F[-2], | |
coordinates = ' '.join(['%s,%s,%s' % p for p in great_arc(PEK, F[1:])]) | |
) for F in flights_ll.as_matrix() # F[0] is the flight count and F[:1] destination vector | |
] | |
return toKML(great_circles) | |
if __name__ == '__main__': | |
print main() |
The CSV data files with airport locations and flight information from Beijing (PEK) airport are airports_new.csv and PEK.csv, respectively. These data were derived from openflights.org.
The KML generated is easily visualized in Google Earth:
As expected, the vector layer is displayed in other GIS desktop clients unpleasantly. See for instance how it looks in QGIS:
I decided not to alter the coordinates and recenter the world map around Beijing and fix the split polygons problem. Google Earth does the trick.
Produce the output KML file from command line (UNIX) like so:
$ python great_circles.py > out.kml
Here is the compressed KML output file.
Alternative partial implementations
Shown next is how I use pyKML (0.1.0) to generate 'almost' the same output above:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Programmatic KML output generation using | |
# pyKML http://pypi.python.org/pypi/pykml | |
def toKML_pykml(great_circles): | |
from pykml.factory import KML_ElementMaker as KML | |
from pykml.factory import GX_ElementMaker as GX | |
from lxml import etree | |
doc = KML.kml( | |
KML.Document( | |
KML.name('Flights from PEK'), | |
KML.Style( | |
KML.IconStyle( | |
KML.scale('1.2'), | |
KML.Icon( | |
KML.href('http://maps.google.com/mapfiles/kml/shapes/shaded_dot.png') | |
) | |
), | |
id = 'sn_shaded_dot' | |
), | |
KML.Folder( | |
KML.name('Airports'), | |
*[ | |
KML.Placemark( | |
KML.name("%s (%s)"% (a[-1], a[-2])), | |
KML.styleUrl("#sn_shaded_dot"), | |
KML.Point( | |
KML.extrude('true'), | |
KML.altitudeMode('relativeToGround'), | |
KML.coordinates('%s,%s,2' % (a[1],a[0])) | |
) | |
) for a in all_airports_from_pek | |
], | |
id = 'a' | |
), | |
KML.Folder( | |
KML.name('Flights'), | |
id = 'f', | |
*[ | |
KML.Placemark( | |
KML.name('PEK to %s' % gc['IATA']), | |
KML.styleUrl('#linestyle%s' % gc['count']), | |
KML.LineString( | |
KML.extrude('1'), | |
KML.tessellate('1'), | |
KML.coordinates(gc['coordinates']) | |
) | |
) for gc in great_circles | |
] | |
), | |
*[ | |
KML.Style( | |
KML.LineStyle( | |
KML.color('7f0000ff'), | |
KML.width('%d' % i), | |
GX.labelVisibility('1'), | |
), | |
id='linestyle%d' % i | |
) for i in xrange(1,11) | |
]) | |
) | |
return etree.tostring(doc, pretty_print=True) |
No comments:
Post a Comment