# File:   nhpn.py
# Author: Punyashloka Biswal <punya@mit.edu>
# Date:   04/09/2007

"""Defines Python classes corresponding to nodes and edges in the
National Highway Planning Network (NHPN) database. Also provides a
way to load such objects from files.

This module is distributed as part of 6.006 PS6 (shortest paths)."""

from tempfile import mkstemp
from os import fdopen, environ
from os.path import basename
from urllib import urlencode
from webbrowser import open_new

class Node:
    """An NHPN geographical node."""

    def __init__(self, longitude, latitude, state, description):
        """Create an instance of a node from its longitude, latitude,
        two-character state code, and possibly a description
        string."""
        self.longitude = longitude
        self.latitude = latitude
        self.state = state
        self.description = description

    def __repr__(self):
        """Convert to string for printing."""
        return "Node(%s, %s, '%s', '%s')" % (self.longitude, self.latitude,
                                             self.state, self.description)


class Link:
    """A bi-directional edge linking two NHPN nodes."""
    def __init__(self, begin, end, description):
        """Create a link given its beginning and end (which must be nodes) and
        possibly a description string."""
        self.begin = begin
        self.end = end
        self.description = description

    def __repr__(self):
        """Convert to string for printing."""
        return "Link(%s, %s, '%s')" % (self.begin, self.end, self.description)


class Loader:
    """An instance of Loader can be used to access NHPN nodes and links as
    Python objects."""

    def __init__(self, nodesource='nhpn.nod', linksource='nhpn.lnk'):
        """Load node and link objects from corresponding files."""
        nodeForFeatureID = {}   # FeatureID -> node mapping

        # Load nodes and add to feature table
        self._nodes = []
        try:
            nodefile = open(nodesource, 'r')
            for line in nodefile:
                featureId = int(line[23:33])
                longitude = int(line[33:43])
                latitude = int(line[43:53])
                state = line[53:55].strip()
                description = line[55:88].strip()
                
                node = Node(longitude, latitude, state, description)
                nodeForFeatureID[featureId] = node
                self._nodes.append(node)
        finally:
            nodefile.close()

        # Load links
        links = []
        try:
            linkfile = open(linksource, 'r')
            for line in linkfile:
                begin = nodeForFeatureID[int(line[33:43])]
                end = nodeForFeatureID[int(line[43:53])]
                description = line[53:88].strip()
                links.append(Link(begin, end, description))
        finally:
            linkfile.close()
        # The following line is to not break test case 3 (otherwise unneeded).
        links.reverse()
        self._links = links

    def nodes(self):
        """List of all NHPN nodes."""
        return self._nodes

    def links(self):
        """List of all NHPN links."""
        return self._links


class Visualizer:
    """Visualizes a path (represented as a sequence of links) using Google
    Maps or Google Earth."""

    @staticmethod
    def toKML(path, stream):
        """Given a sequence of nodes representing a path, prints out a KML
        representation on stream."""
        
        stream.write("""<?xml version="1.0" encoding="utf-8"?>
<kml xmlns="http://earth.google.com/kml/2.1">
  <Document>
    <Placemark>
      <LineString>
        <extrude>1</extrude>
        <tessellate>1</tessellate>
        <coordinates>
""")
        
        stream.writelines("%f,%f\n" % (node.longitude/1000000.,node.latitude/1000000.) 
                     for node in path)

        stream.write("""</coordinates>
      </LineString>
    </Placemark>
  </Document>
</kml>
""")

    @staticmethod
    def viewInBrowser(path):
        """Saves path in a new KML file accessible on the web, and views it
        using Google maps. Relies on the values of Visualizer.baseDir
        and Visualizer.urlBase."""

        # Save KML to accessible file
        (fd, fname) = mkstemp(suffix='.kml', dir=Visualizer.baseDir,text=True)
        kml = fdopen(fd, mode='w')
        self.toKML(path, kml)
        kml.close()
        
        # Open URL in webbrowser
        url = 'http://maps.google.com/maps' + \
            urlencode('q', Visualizer.urlBase + basename(fname))
        open_new(url)

# Visualizer KML directory and URL base are set up for Athena. Modify
# these to suit your setup if you wish to use it elsewhere.
Visualizer.baseDir = environ['HOME'] + '/Public'
Visualizer.baseDir = 'http://web.mit.edu/%s/Public' % environ['USER']

