diff options
| author | makefu <github@syntax-fehler.de> | 2015-09-16 08:30:04 +0200 | 
|---|---|---|
| committer | makefu <github@syntax-fehler.de> | 2015-09-16 08:30:04 +0200 | 
| commit | 147044be891c92b3c0c1bcc3a4e53e2d0eef9963 (patch) | |
| tree | 13ed3ac36d514347f503043b454dddaee51b03ac /retiolum/scripts/adv_graphgen/tinc_graphs | |
| parent | 9bf9f8d045801b17956d12f599bb166e608ed6dd (diff) | |
| parent | 219fab970c7fe455d3dd9bc48e909d96a234046b (diff) | |
Merge branch 'master' of github.com:krebscode/painload
Diffstat (limited to 'retiolum/scripts/adv_graphgen/tinc_graphs')
9 files changed, 742 insertions, 0 deletions
| diff --git a/retiolum/scripts/adv_graphgen/tinc_graphs/Availability.py b/retiolum/scripts/adv_graphgen/tinc_graphs/Availability.py new file mode 100755 index 00000000..888335a7 --- /dev/null +++ b/retiolum/scripts/adv_graphgen/tinc_graphs/Availability.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +# TODO: Rewrite this shitty piece of software ... +# -*- coding: utf8 -*- + +import sys,json,os +""" TODO: Refactoring needed to pull the edges out of the node structures again, +it should be easier to handle both structures""" +DUMP_FILE = os.environ.get("AVAILABILITY_FILE","tinc-availability.json") +hostpath=os.environ.get("TINC_HOSTPATH", "/etc/tinc/retiolum/hosts") + +def get_all_nodes(): +  return os.listdir(hostpath) + +def generate_stats(): +  """ Generates availability statistics of the network and nodes +  """ +  import json +  jlines = [] +  try: +    f = open(DUMP_FILE,'r+') +    for line in f: +      jlines.append(json.loads(line)) +    f.close() +  except Exception as e: +    print("Unable to open and parse Availability DB: {} (override with AVAILABILITY_FILE)".format(DUMP_FILE)) +    sys.exit(1) + +  all_nodes = {} +  for k in get_all_nodes(): +    all_nodes[k] = get_node_availability(k,jlines) +  print (json.dumps(all_nodes)) + +def get_node_availability(name,jlines): +  """ calculates the node availability by reading the generated dump file +  adding together the uptime of the node and returning the time +	parms: +          name - node name +          jlines - list of already parsed dictionaries node archive +  """ +  begin = last = current = 0 +  uptime = 0 +  for stat in jlines: +    if not stat['nodes']: +      continue +    ts = stat['timestamp'] +    if not begin: +      begin = last = ts +    current = ts +    if stat['nodes'].get(name,{}).get('to',[]): +      uptime += current - last +    else: +      pass +    last = ts +  all_the_time = last - begin +  try: +    return uptime/ all_the_time +  except: +    return 1 + +if __name__ == "__main__": +  generate_stats() diff --git a/retiolum/scripts/adv_graphgen/tinc_graphs/BackwardsReader.py b/retiolum/scripts/adv_graphgen/tinc_graphs/BackwardsReader.py new file mode 100644 index 00000000..6bdbf43c --- /dev/null +++ b/retiolum/scripts/adv_graphgen/tinc_graphs/BackwardsReader.py @@ -0,0 +1,35 @@ +import sys +import os +import string + +class BackwardsReader: +  """ Stripped and stolen from : http://code.activestate.com/recipes/120686-read-a-text-file-backwards/ """ +  def readline(self): +    while len(self.data) == 1 and ((self.blkcount * self.blksize) < self.size): +      self.blkcount = self.blkcount + 1 +      line = self.data[0] +      try: +        self.f.seek(-self.blksize * self.blkcount, 2)  +        self.data = string.split(self.f.read(self.blksize) + line, '\n') +      except IOError:   +        self.f.seek(0) +        self.data = string.split(self.f.read(self.size - (self.blksize * (self.blkcount-1))) + line, '\n') + +    if len(self.data) == 0: +      return "" + +    line = self.data[-1] +    self.data = self.data[:-1] +    return line + '\n' + +  def __init__(self, file, blksize=4096): +    """initialize the internal structures""" +    self.size = os.stat(file)[6] +    self.blksize = blksize +    self.blkcount = 1 +    self.f = open(file, 'rb') +    if self.size > self.blksize: +      self.f.seek(-self.blksize * self.blkcount, 2)  +    self.data = string.split(self.f.read(self.blksize), '\n') +    if not self.data[-1]: +      self.data = self.data[:-1] diff --git a/retiolum/scripts/adv_graphgen/tinc_graphs/Geo.py b/retiolum/scripts/adv_graphgen/tinc_graphs/Geo.py new file mode 100755 index 00000000..bfa4ee56 --- /dev/null +++ b/retiolum/scripts/adv_graphgen/tinc_graphs/Geo.py @@ -0,0 +1,78 @@ +#!/usr/bin/python3 +# -*- coding: utf8 -*- +import sys,json,os +from .Graph import delete_unused_nodes,resolve_myself +GEODB=os.environ.get("GEODB","GeoLiteCity.dat") + +def copy_map(): +    from shutil import copy +    from os.path import dirname,join,realpath +    if len(sys.argv) != 2 or sys.argv[1] == "--help" : +        print("usage: {} <destination>".format(sys.argv[0])) +        print("  copies the map.html file to the <destination>") +        sys.exit(1) +    dstdir=sys.argv[1] +    copy(realpath(join(dirname(__file__),'static/map.html')),dstdir) + + +def add_geo(nodes): +  from pygeoip import GeoIP +  gi = GeoIP(GEODB) + +  for k,v in nodes.items(): +    try: +      nodes[k].update(gi.record_by_addr(v["external-ip"])) +    except Exception as e: +      sys.stderr.write(str(e)) +      sys.stderr.write("Cannot determine GeoData for %s\n"%k) +  return nodes + +def add_coords_to_edges(nodes): +  from pygeoip import GeoIP +  gi = GeoIP(GEODB) + +  for k,v in nodes.items(): +    for i,j in enumerate(v.get("to",[])): +      data=gi.record_by_addr(j["addr"]) +      try: +        j["latitude"]=data["latitude"] +        j["longitude"]=data["longitude"] +      except Exception as e: pass + +  return nodes + +def add_jitter(nodes): +  from random import random +  #add a bit of jitter to all of the coordinates +  max_jitter=0.005 +  for k,v in nodes.items(): +    jitter_lat= max_jitter -random()*max_jitter*2 +    jitter_long= max_jitter -random()*max_jitter*2 +    try: +      v["latitude"]= v["latitude"] + jitter_lat +      v["longitude"]= v["longitude"] + jitter_long +      for nodek,node in nodes.items(): +        for to in node['to']: +          if to['name'] == k: +            to['latitude'] = v["latitude"] +            to['longitude'] = v["longitude"] +    except Exception as e: pass +  return nodes + +def main(): +  import json +  try: +      with open(GEODB,'rb') as f: f.read() +  except Exception as e: +      print("cannot open {} (GEODB in env)".format(GEODB)) +      print(e) +      sys.exit(1) +  try: +    nodes = add_jitter(add_coords_to_edges(add_geo(resolve_myself(delete_unused_nodes(json.load(sys.stdin)))))) +    print (json.dumps(nodes)) +  except Exception as e: +      print("cannot parse data received via stdin") +      print(e) + +if __name__ == "__main__": +    main() diff --git a/retiolum/scripts/adv_graphgen/tinc_graphs/Graph.py b/retiolum/scripts/adv_graphgen/tinc_graphs/Graph.py new file mode 100755 index 00000000..29491997 --- /dev/null +++ b/retiolum/scripts/adv_graphgen/tinc_graphs/Graph.py @@ -0,0 +1,265 @@ +#!/usr/bin/python +from .BackwardsReader import BackwardsReader +import sys,json,os +from .Supernodes import check_all_the_super +from .Services import add_services +from .Availability import get_node_availability +import sys,json +from time import time +DUMP_FILE = os.environ.get("AVAILABILITY_FILE", "tinc-availability.json") +hostpath=os.environ.get("TINC_HOSTPATH", "/etc/tinc/retiolum/hosts") + +# will be filled later +supernodes= [] + +def resolve_myself(nodes): +  #resolve MYSELF to the real ip +  for k,v in nodes.items(): +    if v["external-ip"] == "MYSELF": +      for nodek,node in nodes.items(): +         for to in node['to']: +           if to['name'] == k: +             v["external-ip"] = to["addr"] +  return nodes + + +def dump_graph(nodes): +  from time import time +  graph = {} +  graph['nodes'] = nodes +  graph['timestamp'] = time() +  f = open(DUMP_FILE,'a') +  json.dump(graph,f) +  f.write('\n') +  f.close() + +def generate_availability_stats(nodes): +  """ generates stats of from availability +  """ +  jlines = [] +  # try: +  #   f = BackwardsReader(DUMP_FILE) +  #   lines_to_use = 1000 +  #   while True: +  #     if lines_to_use == 0: break +  #     line = f.readline() +  #     if not line: break +  #     jline = json.loads(line) +  #     if not jline['nodes']: continue + +  #     jlines.append(jline) +  #     lines_to_use -=1 +  # except Exception as e: sys.stderr.write(str(e)) + +  for k,v in nodes.items(): +    # TODO: get this information in a different way +    v['availability'] = get_node_availability(k,[]) + + +def generate_stats(nodes): +  """ Generates some statistics of the network and nodes +  """ +  for k,v in nodes.items(): +    conns = v.get('to',[]) +    for c in conns: #sanitize weights +      if float(c['weight']) > 9000: c['weight'] = str(9001) +      elif float(c['weight']) < 0: c['weight'] = str(0) +    v['num_conns'] = len(conns) +    v['avg_weight'] = get_node_avg_weight(conns) + +def get_node_avg_weight(conns): +  """ calculates the average weight for the given connections """ +  if not conns: +    sys.syderr.write("get_node_avg_weight: connection parameter empty") +    return 9001 +  else: +    return sum([float(c['weight']) for c in conns])/len(conns) + +def delete_unused_nodes(nodes): +  """ Deletes all the nodes which are currently not connected to the network""" +  new_nodes = {} +  for k,v in nodes.items(): +    if v['external-ip'] == "(null)": +      continue +    if v.get('to',[]): +      new_nodes[k] = v +  for k,v in new_nodes.items(): +    if not [ i for i in v['to'] if i['name'] in new_nodes]: +      del(k) +  return new_nodes + +def merge_edges(nodes): +  """ merge back and forth edges into one +  DESTRUCTS the current structure by deleting "connections" in the nodes +  """ +  for k,v in nodes.items(): +    for con in v.get('to',[]): +      for i,secon in enumerate(nodes.get(con['name'],{}).get('to',[])): +        if k == secon['name']: +          del (nodes[con['name']]['to'][i]) +          con['bidirectional'] = True + + +def print_head(): +  print ('digraph retiolum {') +  print ('  graph [center=true packMode="clust"]') +  print ('  node[shape=box,style=filled,fillcolor=grey]') +  print ('  overlap=false') + +def print_stat_node(nodes): +  ''' Write a `stats` node in the corner +      This node contains infos about the current number of active nodes and connections inside the network +  ''' +  from time import localtime,strftime +  num_conns = 0 +  num_nodes = len(nodes) +  try:  +    msg = '%s.num_nodes %d %d\r\n' %(g_path,num_nodes,begin) +    s.send(msg) +  except Exception as e: pass +  for k,v in nodes.items(): +    num_conns+= len(v['to']) +  node_text = "  stats_node [label=\"Statistics\\l" +  node_text += "Build Date  : %s\\l" % strftime("%Y-%m-%d %H:%M:%S",localtime()) +  node_text += "Active Nodes: %s\\l" % num_nodes +  node_text += "Connections : %s\\l" % num_conns +  node_text += "\"" +  node_text += ",fillcolor=green" +  node_text += "]" +  print(node_text) + +def print_node(k,v): +  """ writes a single node and its edges  +      edges are weightet with the informations inside the nodes provided by +      tinc +  """ + +  node = "  "+k+"[label=<<TABLE border='0' title='%s' cellborder='1' >" %k +  node += "<TR><TD colspan='2'><B>%s</B></TD></TR>"%k +  if 'availability' in v: +    node += "<TR><TD>availability:</TD><TD>%f</TD></TR>" % v['availability']  + +  if 'num_conns' in v: +    node += "<TR><TD>Num Connects:</TD><TD>%s</TD></TR>"%str(v['num_conns']) + +  node += "<TR><TD>external:</TD><TD>"+v['external-ip']+":"+v['external-port']+"</TD></TR>" +  for addr in v.get('internal-ip',['dunno lol']):  +    node += "<TR><TD>internal:</TD><TD>%s</TD></TR>"%addr + +  if 'services' in v: +    node +="<TR><TD colspan='2'><B>Services:</B></TD></TR>" +    for service in v['services']: +      try:uri,comment = service.split(" ",1) +      except:  +        uri = service +        comment ="" +      node +="<TR >" +      uri_proto=uri.split(":")[0] +      uri_rest = uri.split(":")[1]  +      if not uri_rest: +        node +="<TD title='{0}' align='left' colspan='2' \ +href='{0}'><font color='darkred'>{0}</font>".format(uri) +      else: +        node +="<TD title='{0}' align='left' colspan='2' \ +href='{0}'><U>{0}</U>".format(uri) +      if comment: +        node += "<br align='left'/>    <I>{0}</I>".format(comment) +      node +="</TD></TR>" +  # end label +  node +="</TABLE>>" + +  if v['num_conns'] == 1: +    node += ",fillcolor=red" +  elif k in supernodes: +    node += ",fillcolor=steelblue1" +  node += "]" + +  print(node) + +def print_anonymous_node(k,v): +  """ writes a single node and its edges  +      edges are weightet with the informations inside the nodes provided by +      tinc +  """ +   +  node = "  "+k #+"[label=\"" +  print(node) + +def print_edge(k,v): +  for con in v.get('to',[]): +    label  = con['weight'] +    w = int(con['weight']) +    weight = str(1000 - (((w - 150) * (1000 - 0)) / (1000 -150 )) + 0) + +    length = str(float(w)/1500) +    if float(weight) < 0 : +      weight= "1" + +    edge = "  "+k+ " -> " +con['name'] + " [label="+label + " weight="+weight  +    if con.get('bidirectional',False): +      edge += ",dir=both" +    edge += "]" +    print(edge) + +def anonymize_nodes(nodes): +  #anonymizes all nodes +  i = "0" +  newnodes = {} +  for k,v in nodes.items(): +    for nodek,node in nodes.items(): +      for to in node['to']: +        if to['name'] == k: +          to['name'] = i +    newnodes[i] = v +    i = str(int(i)+1) +  return newnodes + +def main(): +  if len(sys.argv) != 2 or  sys.argv[1] not in ["anonymous","complete"]:  +    print("usage: %s (anonymous|complete)") +    sys.exit(1) + +  nodes = json.load(sys.stdin) +  nodes = delete_unused_nodes(nodes) +  print_head() +  generate_stats(nodes) +  merge_edges(nodes) + + +  if sys.argv[1] == "anonymous": +    nodes = anonymize_nodes(nodes) + +    for k,v in nodes.items(): +      print_anonymous_node(k,v) +      print_edge(k,v) + +  elif sys.argv[1] == "complete": +    try: +      for supernode,addr in check_all_the_super(hostpath): +        supernodes.append(supernode) +    except FileNotFoundError as e: +      print("!! cannot load list of supernodes ({})".format(hostpath)) +      print("!! Use TINC_HOSTPATH env to override") +      sys.exit(1) + +    generate_availability_stats(nodes) +    add_services(nodes) +    for k,v in nodes.items(): +      print_node(k,v) +      print_edge(k,v) + +    #TODO: get availability somehow else +    # try: +    #   dump_graph(nodes) +    # except Exception as e: +    #   sys.stderr.write("Cannot dump graph: %s" % str(e)) +  else: +    pass + +  print_stat_node(nodes) +  print ('}') + +if __name__ == "__main__": +  main() + +# vim: set sw=2:ts=2 diff --git a/retiolum/scripts/adv_graphgen/tinc_graphs/Log2JSON.py b/retiolum/scripts/adv_graphgen/tinc_graphs/Log2JSON.py new file mode 100755 index 00000000..b0bc209b --- /dev/null +++ b/retiolum/scripts/adv_graphgen/tinc_graphs/Log2JSON.py @@ -0,0 +1,119 @@ +#!/usr/bin/python +import subprocess +import os +import re +import sys +import json + + +TINC_NETWORK =os.environ.get("TINC_NETWORK","retiolum") + +# is_legacy is the parameter which defines if the tinc config files are handled old fashioned (parse from syslog),  +# or if the new and hip tincctl should be used + + +# Tags and Delimiters +TINC_TAG="tinc.%s" % TINC_NETWORK +BEGIN_NODES = "Nodes:" +END_NODES = "End of nodes." +BEGIN_SUBNET = "Subnet list:" +END_SUBNET = "End of subnet list" +BEGIN_EDGES = "Edges:" +END_EDGES = "End of edges." +def usage(): +  from sys import argv,exit +  print("""usage: %s +This tool dumps all tinc node informations as json + +ENVIRONMENT VARIABLES: +  TINC_NETWORK   The tinc network to dump +                      (default: retiolum) +""" % argv[0]) +  exit(1) + +def debug(func): +  from functools import wraps +  @wraps(func) +  def with_debug(*args,**kwargs): +    print( func.__name__ + " (args: %s | kwargs %s)"% (args,kwargs)) +    return func(*args,**kwargs) +  return with_debug + + +def parse_tinc_stats(): +  import subprocess +  from time import sleep +  from distutils.spawn import find_executable as which +  #newest tinc +  if which("tinc"): +    return parse_new_input("tinc") +  #new tinc +  elif which("tincctl"): +    return parse_new_input("tincctl") +  #old tinc +  elif which("tincd"): +      print("old tincd not supported") +      sys.exit(1) +  else: +    raise Exception("no tinc executable found!") + + +def parse_new_input(tinc_bin): +  nodes = {} +  pnodes = subprocess.Popen( +          [tinc_bin,"-n",TINC_NETWORK,"dump","reachable","nodes"], +          stdout=subprocess.PIPE).communicate()[0].decode() +  for line in pnodes.split('\n'): +    if not line: continue +    l = line.split() +    n = l[0] + +    token = l[1] +    if token == 'id': +        # new format +        # <name> id <ident> at <ip> port <port> +        ident = l[1] +        l = l[2:]  #shift over 'id <ident>' +    # else: # token = 'at' +        # old format: +        # <name> at <ip> port <port> +    _,_,ip,_,port = l[:5] +    nodes[n]= { 'external-ip': ip, 'external-port' : l[4] } + +  psubnets = subprocess.check_output( +          [tinc_bin,"-n",TINC_NETWORK,"dump","subnets"]).decode() +  for line in psubnets.split('\n'): +    if not line: continue +    l = line.split() +    try: +      if not nodes[l[2]].get('internal-ip',False): +         nodes[l[2]]['internal-ip'] = [] +      nodes[l[2]]['internal-ip'].append(l[0].split('#')[0]) +    except KeyError: +      pass # node does not exist (presumably) + +  pedges = subprocess.check_output( +          [tinc_bin,"-n",TINC_NETWORK,"dump","edges"]).decode() +  for line in pedges.split('\n'): +    if not line: continue +    l = line.split() +    # TODO: tokenize this and parse the line +    n = l[0] +    try: +      if not 'to' in nodes[l[0]] : +        nodes[n]['to'] = [] +      nodes[n]['to'].append( +          {'name':l[2],'addr':l[4],'port':l[6],'weight' : l[-1] }) +    except KeyError: +      pass #node does not exist +  return nodes + +def main(): +  from sys import argv +  if len(argv) > 1: +    usage() +  else: +    print (json.dumps(parse_tinc_stats())) + +if __name__ == '__main__': +    main() diff --git a/retiolum/scripts/adv_graphgen/tinc_graphs/Services.py b/retiolum/scripts/adv_graphgen/tinc_graphs/Services.py new file mode 100644 index 00000000..9581e21e --- /dev/null +++ b/retiolum/scripts/adv_graphgen/tinc_graphs/Services.py @@ -0,0 +1,25 @@ +import os,sys +services_dir=os.environ.get("SERIVCES_DIR","/home/reaktor/nag.services") +def add_services(nodes): +    for k,v in nodes.items(): +        n = nodes[k] +        try: +            with open("{0}/{1}".format(services_dir,k)) as f: +                n["services"] = [] +                for line in f.readlines(): +                    n["services"].append(line.strip()) +        except Exception as e: +            n["services"] = ["Error: No Service File!"] +    return nodes +def main(): +    import json,sys +    try: +        nodes = add_services(json.load(sys.stdin)) +        print (json.dumps(nodes,indent=4)) +    except: +        print("unable to parse json data from stdin") +        sys.exit(1) + +if __name__ == "__main__": +    main() +# vim: set expandtab:ts=4:sw=4 diff --git a/retiolum/scripts/adv_graphgen/tinc_graphs/Supernodes.py b/retiolum/scripts/adv_graphgen/tinc_graphs/Supernodes.py new file mode 100755 index 00000000..bc66b337 --- /dev/null +++ b/retiolum/scripts/adv_graphgen/tinc_graphs/Supernodes.py @@ -0,0 +1,70 @@ +#!/usr/bin/python3 + +def find_potential_super(path="/etc/tinc/retiolum/hosts"): +  import os +  import re + +  needle_addr = re.compile("Address\s*=\s*(.*)") +  needle_port = re.compile("Port\s*=\s*(.*)") +  for f in os.listdir(path): +    try: +      with open(path+"/"+f) as of: +        addrs = [] +        port = "655" + +        for line in of.readlines(): + +          addr_found = needle_addr.match(line) +          if addr_found: +            addrs.append(addr_found.group(1)) + +          port_found = needle_port.match(line) +          if port_found: +            port = port_found.group(1) + +        if addrs : yield (f ,[(addr ,int(port)) for addr in addrs]) +    except FileNotFoundError as e: +        print("Cannot open hosts directory to be used to find potential supernodes") +        print("Directory used: {}".format(path)) +        raise + + +def try_connect(addr): +  try: +    from socket import socket,AF_INET,SOCK_STREAM +    s = socket(AF_INET,SOCK_STREAM) +    s.settimeout(2) +    s.connect(addr) +    s.settimeout(None) +    s.close() +    return addr +  except Exception as e: +    pass + + +def check_one_super(ha): +    host,addrs = ha +    valid_addrs = [] +    for addr in addrs: +      ret = try_connect(addr) +      if ret: valid_addrs.append(ret) +    if valid_addrs: return (host,valid_addrs) + + +def check_all_the_super(path): +  from multiprocessing import Pool +  p = Pool(20) +  return filter(None,p.map(check_one_super,find_potential_super(path))) + + +def main(): +  import os +  hostpath=os.environ.get("TINC_HOSTPATH", "/etc/tinc/retiolum/hosts") + +  for host,addrs in check_all_the_super(hostpath): +    print("%s %s" %(host,str(addrs))) + +if __name__ == "__main__": +    main() + +# vim: set expandtab:ts=:sw=2 diff --git a/retiolum/scripts/adv_graphgen/tinc_graphs/__init__.py b/retiolum/scripts/adv_graphgen/tinc_graphs/__init__.py new file mode 100644 index 00000000..414ffe99 --- /dev/null +++ b/retiolum/scripts/adv_graphgen/tinc_graphs/__init__.py @@ -0,0 +1 @@ +__version__="0.2.3" diff --git a/retiolum/scripts/adv_graphgen/tinc_graphs/static/map.html b/retiolum/scripts/adv_graphgen/tinc_graphs/static/map.html new file mode 100644 index 00000000..ef8a0565 --- /dev/null +++ b/retiolum/scripts/adv_graphgen/tinc_graphs/static/map.html @@ -0,0 +1,88 @@ + + +<!DOCTYPE html> +<html> +  <head> +    <title>Simple Map</title> +    <meta name="viewport" content="initial-scale=1.0, user-scalable=no"> +    <meta charset="utf-8"> +    <style> +      html, body, #map-canvas { +        margin: 0; +        padding: 0; +        height: 100%; +      } +    </style> +    <script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script> +    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script> +    <script type="text/javascript" src="jquery.ui.map.js"></script> +    <script> +      var map; +      var markerlist ={}; +      function addInfoWindow(marker, message) { +        var info = message; + +        var infoWindow = new google.maps.InfoWindow({ +          content: message +        }); + +        google.maps.event.addListener(marker, 'click', function () { + +          // close all the infowindows +          $.each(markerlist,function (k,v){ +            v["infowin"].close() +          }); +          infoWindow.open(map, marker); +        }); +        return infoWindow; +      } +      function initialize() { +        var mapOptions = { +          zoom: 5, +          center: new google.maps.LatLng(51,9), +          mapTypeId: google.maps.MapTypeId.ROADMAP +        }; +        map = new google.maps.Map(document.getElementById('map-canvas'), +        mapOptions); + +        $.getJSON("marker.json",function (data){ +          $.each(data,function (k,v) { + +            // add initial marker +            var pt=new google.maps.LatLng(v["latitude"],v["longitude"]) +            var marker= new google.maps.Marker({ +              id: k,map:map,title: k,position:pt,content:k}) + +            // add edges +            $.each(v["to"],function(iter,val){ +              var line = [ +              pt, +              new google.maps.LatLng(val['latitude'],val['longitude']) +        ] +               +              var  connector = new google.maps.Polyline({ +                path:line, +                strokeColor: "#FF0000", +                strokeOpacity: 1.0, +                strokeWeight: 2 +              }) +              connector.setMap(map); +              val["line"]=connector + +            }) + +           +            marker["infowin"]=addInfoWindow(marker,k+'<br/>'+v["city"]); +            markerlist[k] = marker; +          });  +        }) +      };  + +      google.maps.event.addDomListener(window, 'load', initialize); + +    </script> +  </head> +  <body> +    <div id="map-canvas"></div> +  </body> +</html> | 
