Search code examples
pythongoogle-mapsgoogle-maps-api-3kmlgoogle-earth

Parsing KML to Google Maps only shows one of hundreds of pins


My Expectations

I'm working my way through Violent Python and am currently on a module where I'm grabbing ip-addresses from a .pcap-file, parsing the ip-addresses to longitudes and latitudes and finally parsing those longs and langs into a .kml file.

The idea is that my .kml file should be uploadable to Google Earth and Google Maps to show pins for the source and destination of each captured package.


What is actually happening:

  • On Google Earth the parsing fails with no error message other than

Parsing Aborted

  • On Google Maps I only see the a single pin in Amsterdam. It would seem to be the first <placemark> with IP: 212.204.214.114.

Code and other important stuffz:

You can find the whole .kml-file here.

The headers are correct (if the book and other SO threads are to believed):

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">

And all of the placemarks have this simple structure:

<Placemark>
    <name>212.204.214.114</name>
    <Point>
        <coordinates>4.834300, 52.404800</coordinates>
    </Point>
</Placemark>

My code follows the same structure as the module in Violent Python, with some small changes to better follow more modern best practices:

import dpkt, socket, pygeoip, argparse, os

gi = pygeoip.GeoIP('/opt/GeoIP/Geo.dat')

def ret_klm(ip):
    rec = gi.record_by_name(ip)

    try:
        long = rec['longitude']
        lat  = rec['latitude']
        kml = (
            '<Placemark>\n'
            '<name>%s</name>\n'
            '<Point>\n'
            '<coordinates>%6f, %6f</coordinates>\n'
            '</Point>\n'
            '</Placemark>\n'
            )%(ip, long, lat)
        return kml
    except:
        return ''


def plotIPs(pcap):
    kml_points = ''
    for (ts, buf) in pcap:
        try:
            eth = dpkt.ethernet.Ethernet(buf)
            ip  = eth.data
            src = socket.inet_ntoa(ip.src)
            src_kml = ret_klm(src)
            dst = socket.inet_ntoa(ip.dst)
            dst_kml = ret_klm(dst)
            kml_points = kml_points + src_kml + dst_kml
        except:
            pass

    return kml_points

def main():

    parser = argparse.ArgumentParser(
        description=u'GeoLocator to parse IP addresses to their '
                    u'long, lat in KML for Google Maps.'
    )

    parser.add_argument(
        u'-p',
        help=u'Pcap file for parsing',
        required=True,
        dest=u'pcap_file'
    )
    arguments = parser.parse_args()

    if not os.path.isfile(arguments.pcap_file):
        parser.print_usage()
        raise SystemExit

    with open(arguments.pcap_file) as file:
        pcap = dpkt.pcap.Reader(file)
        kmlheader = '<?xml version="1.0" encoding="UTF-8"?>'+\
            '\n<kml xmlns="http://www.opengis.net/kml/2.2">\n'
        kmlfooter = '</kml>\n'
        kmldoc = kmlheader + plotIPs(pcap) + kmlfooter
        print(kmldoc)

if __name__ == '__main__':
    main()

Solution

  • You need to add a <Document> element to your KML that will wrap all of your <Placemark> elements. Something like:

    <?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>
        <Placemark>
            <name>212.204.214.114</name>
            <Point>
                <coordinates>4.834300, 52.404800</coordinates>
            </Point>
        </Placemark>
        <Placemark>
            <name>212.204.214.114</name>
            <Point>
                <coordinates>4.834300, 52.404800</coordinates>
            </Point>
        </Placemark>
        <!-- the rest of your placemarks -->
    </Document>
    </kml>