#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
                        print_function, unicode_literals)

from troggle.core.models import CaveSlug, Cave, CaveAndEntrance, Survey, Expedition, QM, CaveDescription, EntranceSlug, Entrance, Area, SurvexStation
from troggle.core.forms import CaveForm, CaveAndEntranceFormSet, VersionControlCommentForm, EntranceForm, EntranceLetterForm
import troggle.core.models as models
import troggle.settings as settings
from troggle.helper import login_required_if_public

from django.forms.models import modelformset_factory
from django import forms
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect
from django.conf import settings
import re
import urlparse
#import urllib.parse
from django.shortcuts import get_object_or_404, render
import settings


from PIL import Image, ImageDraw, ImageFont
import string, os, sys, subprocess

def getCave(cave_id):
    """Returns a cave object when given a cave name or number. It is used by views including cavehref, ent, and qm."""
    try:
        cave = Cave.objects.get(kataster_number=cave_id)
    except Cave.DoesNotExist:
        cave = Cave.objects.get(unofficial_number=cave_id)
    return cave

def pad5(x):
   return "0" * (5 -len(x.group(0))) + x.group(0) 
def padnumber(x):
   return re.sub("\d+", pad5, x)
def numericalcmp(x, y):
   return cmp(padnumber(x), padnumber(y))



def caveCmp(x, y): 
    if x.kataster_number:
      if y.kataster_number:
        return numericalcmp(x.kataster_number, y.kataster_number) # Note that cave kataster numbers are not generally integers.
      else:
        return -1
    else:
      if y.kataster_number:
        return 1
      else:        
        return numericalcmp(x.unofficial_number, y.unofficial_number)

def caveindex(request):
    caves = Cave.objects.all()
    notablecavehrefs = settings.NOTABLECAVESHREFS
    notablecaves = [Cave.objects.get(kataster_number=kataster_number)  for kataster_number in notablecavehrefs ]
    caves1623 = list(Cave.objects.filter(area__short_name = "1623"))
    caves1626 = list(Cave.objects.filter(area__short_name = "1626"))
    caves1623.sort(caveCmp)
    caves1626.sort(caveCmp)
    return render(request,'caveindex.html', {'caves1623': caves1623, 'caves1626': caves1626, 'notablecaves':notablecaves, 'cavepage': True})

def millenialcaves(request):
    #RW messing around area
    return HttpResponse("Test text", content_type="text/plain")
    


def cave3d(request, cave_id=''):
    cave = getCave(cave_id)
    survexfilename = settings.SURVEX_DATA + cave.survex_file
    threedfilename = settings.THREEDCACHEDIR + '%s.3d' % cave_id
    if True or os.path.getmtime(survexfilename) > os.path.getmtime(threedfilename):
        subprocess.call(["cavern", "--output=%s" % threedfilename, survexfilename])
    test_file = open(threedfilename, 'rb')
    response = HttpResponse(content=test_file, content_type='application/3d')#mimetype is replaced by content_type for django 1.7
    response['Content-Disposition'] = 'attachment; filename=%s.3d' % cave_id
    # response['X-Sendfile'] = "%s.3d" % cave_id
    # It's usually a good idea to set the 'Content-Length' header too.
    # You can also set any other required headers: Cache-Control, etc.
    return response

def cave(request, cave_id='', offical_name=''):
    cave=getCave(cave_id)
    if cave.non_public and settings.PUBLIC_SITE and not request.user.is_authenticated():
        return render(request,'nonpublic.html', {'instance': cave, 'cavepage': True, 'cave_id': cave_id})
    else:
        return render(request,'cave.html', {'settings': settings, 'cave': cave, 'cavepage': True, 'cave_id': cave_id})

def caveEntrance(request, slug):
    cave = Cave.objects.get(caveslug__slug = slug)
    if cave.non_public and settings.PUBLIC_SITE and not request.user.is_authenticated():
        return render(request,'nonpublic.html', {'instance': cave})
    else:
        return render(request,'cave_entrances.html', {'cave': cave})

def caveDescription(request, slug):
    cave = Cave.objects.get(caveslug__slug = slug)
    if cave.non_public and settings.PUBLIC_SITE and not request.user.is_authenticated():
        return render(request,'nonpublic.html', {'instance': cave})
    else:
        return render(request,'cave_uground_description.html', {'cave': cave})

def caveQMs(request, slug):
    cave = Cave.objects.get(caveslug__slug = slug)
    if cave.non_public and settings.PUBLIC_SITE and not request.user.is_authenticated():
        return render(request,'nonpublic.html', {'instance': cave})
    else:
        return render(request,'cave_qms.html', {'cave': cave})
def caveLogbook(request, slug):
    cave = Cave.objects.get(caveslug__slug = slug)
    if cave.non_public and settings.PUBLIC_SITE and not request.user.is_authenticated():
        return render(request,'nonpublic.html', {'instance': cave})
    else:
        return render(request,'cave_logbook.html', {'cave': cave})

def caveSlug(request, slug):
    cave = Cave.objects.get(caveslug__slug = slug)
    if cave.non_public and settings.PUBLIC_SITE and not request.user.is_authenticated():
        return render(request,'nonpublic.html', {'instance': cave, 'cave_editable': slug})
    else:
        return render(request,'cave.html', {'cave': cave, 'cave_editable': slug})

@login_required_if_public
def edit_cave(request, slug=None):
    if  slug is not None:
        cave = Cave.objects.get(caveslug__slug = slug)
    else:
        cave = Cave()
    if request.POST:
        form = CaveForm(request.POST, instance=cave)
        ceFormSet = CaveAndEntranceFormSet(request.POST)
        versionControlForm = VersionControlCommentForm(request.POST)
        if form.is_valid() and ceFormSet.is_valid() and versionControlForm.is_valid():
            cave = form.save(commit = False)
            if slug is None:
                for a in form.cleaned_data["area"]:
                    if a.kat_area():
                        myArea = a.kat_area()
                if form.cleaned_data["kataster_number"]:
                    myslug =  "%s-%s" % (myArea, form.cleaned_data["kataster_number"])
                else:
                    myslug =  "%s-%s" % (myArea, form.cleaned_data["unofficial_number"])
            else:
                myslug = slug
            cave.filename = myslug + ".html"
            cave.save()
            form.save_m2m()
            if slug is None:
              cs = CaveSlug(cave = cave, slug = myslug, primary = True)
              cs.save()
            ceinsts = ceFormSet.save(commit=False)
            for ceinst in ceinsts:
                ceinst.cave = cave
                ceinst.save()
            cave.writeDataFile()
            return HttpResponseRedirect("/" + cave.url)    
    else:
        form = CaveForm(instance=cave)
        ceFormSet = CaveAndEntranceFormSet(queryset=cave.caveandentrance_set.all())
        versionControlForm = VersionControlCommentForm()
        
    return render(request, 
                               'editcave2.html', 
                               {'form': form,
                                'caveAndEntranceFormSet': ceFormSet,
                                'versionControlForm': versionControlForm
                                })

@login_required_if_public
def editEntrance(request, caveslug, slug=None):
    cave = Cave.objects.get(caveslug__slug = caveslug) 
    if  slug is not None:
        entrance = Entrance.objects.get(entranceslug__slug = slug)
    else:
        entrance = Entrance()
    if request.POST:
        form = EntranceForm(request.POST, instance = entrance)
        versionControlForm = VersionControlCommentForm(request.POST)
        if slug is None:
            entletter = EntranceLetterForm(request.POST)
        else:
            entletter = None
        if form.is_valid() and versionControlForm.is_valid() and (slug is not None or entletter.is_valid()):
            entrance = form.save(commit = False)
            if slug is None:
                slugname = cave.slug() + entletter.cleaned_data["entrance_letter"]
                entrance.cached_primary_slug = slugname
                entrance.filename = slugname + ".html"
            entrance.save()
            if slug is None:
                es = EntranceSlug(entrance = entrance, slug = slugname, primary = True)
                es.save()
                el = entletter.save(commit = False)
                el.cave = cave
                el.entrance = entrance
                el.save()
            entrance.writeDataFile()
            return HttpResponseRedirect("/" + cave.url) 
    else:
        form = EntranceForm(instance = entrance)
        versionControlForm = VersionControlCommentForm()
        if slug is None:
            entletter = EntranceLetterForm(request.POST)
        else:
            entletter = None
    return render(request, 
                               'editentrance.html', 
                               {'form': form,
                                'versionControlForm': versionControlForm,
                                'entletter': entletter
                                })

def qm(request,cave_id,qm_id,year,grade=None):
    year=int(year)
    try:
        qm=getCave(cave_id).get_QMs().get(number=qm_id,found_by__date__year=year)
        return render(request,'qm.html',locals())

    except QM.DoesNotExist:
        url=urllib.parse.urljoin(settings.URL_ROOT, r'/admin/core/qm/add/'+'?'+  r'number=' + qm_id)
        if grade:
            url += r'&grade=' + grade
        return HttpResponseRedirect(url)
    

 
def ent(request, cave_id, ent_letter):
    cave = Cave.objects.filter(kataster_number = cave_id)[0]
    cave_and_ent = CaveAndEntrance.objects.filter(cave = cave).filter(entrance_letter = ent_letter)[0]
    return render(request,'entrance.html', {'cave': cave,
                                                'entrance': cave_and_ent.entrance,
                                                'letter': cave_and_ent.entrance_letter,})

def entranceSlug(request, slug):
    entrance = Entrance.objects.get(entranceslug__slug = slug)
    if entrance.non_public and not request.user.is_authenticated():
        return render(request,'nonpublic.html', {'instance': entrance})
    else:
        return render(request,'entranceslug.html', {'entrance': entrance})

def survexblock(request, survexpath):
    survexpath = re.sub("/", ".", survexpath)
    print("jjjjjj", survexpath)
    survexblock = models.SurvexBlock.objects.get(survexpath=survexpath)
    #ftext = survexblock.filecontents()
    ftext = survexblock.text
    return render(request,'survexblock.html', {'survexblock':survexblock, 'ftext':ftext, })

def surveyindex(request):
    surveys=Survey.objects.all()
    expeditions=Expedition.objects.order_by("-year")
    return render(request,'survey.html',locals())

def survey(request,year,wallet_number):
    surveys=Survey.objects.all()
    expeditions=Expedition.objects.order_by("-year")
    current_expedition=Expedition.objects.filter(year=year)[0]
    
    if wallet_number!='':
            current_survey=Survey.objects.filter(expedition=current_expedition,wallet_number=wallet_number)[0]
            notes=current_survey.scannedimage_set.filter(contents='notes')
            planSketches=current_survey.scannedimage_set.filter(contents='plan')
            elevationSketches=current_survey.scannedimage_set.filter(contents='elevation')

    return render(request,'survey.html', locals())

def cave_description(request, cavedescription_name):
    cave_description = get_object_or_404(CaveDescription, short_name = cavedescription_name)
    return render(request,'cave_description.html', locals())

def get_entrances(request, caveslug):
    cave = Cave.objects.get(caveslug__slug = caveslug)
    return render(request,'options.html', {"items": [(e.entrance.slug(), e.entrance.slug()) for e in cave.entrances()]})

def get_qms(request, caveslug):
    cave = Cave.objects.get(caveslug__slug = caveslug)
    return render(request,'options.html', {"items": [(e.entrance.slug(), e.entrance.slug()) for e in cave.entrances()]})

areanames = [
	#('',     'Location unclear'),
	('1a',   '1a &ndash; Plateau: around Top Camp'),
	('1b',   '1b &ndash; Western plateau near 182'),
	('1c',   '1c &ndash; Eastern plateau near 204 walk-in path'),
	('1d',   '1d &ndash; Further plateau around 76'),
	('2a',   '2a &ndash; Southern Schwarzmooskogel near 201 path and the Nipple'),
	('2b',   '2b &ndash; Eish&ouml;hle area'),
	('2b or 4 (unclear)', '2b or 4 (unclear)'),
	('2c',   '2c &ndash; Kaninchenh&ouml;hle area'),
	('2d',   '2d &ndash; Steinbr&uuml;ckenh&ouml;hle area'),
	('3',    '3 &ndash; Br&auml;uning Alm'),
	('4',	'4 &ndash; Kratzer valley'),
	('5',	'5 &ndash; Schwarzmoos-Wildensee'),
	('6',	'6 &ndash; Far plateau'),
	('1626 or 6 (borderline)', '1626 or 6 (borderline)'),
	('7',	'7 &ndash; Egglgrube'),
	('8a',	'8a &ndash; Loser south face'),
	('8b',	'8b &ndash; Loser below Dimmelwand'),
	('8c',	'8c &ndash; Augst See'),
	('8d',	'8d &ndash; Loser-Hochganger ridge'),
	('9',	'9 &ndash; Gschwandt Alm'),
	('10',	'10 &ndash; Altaussee'),
	('11',	'11 &ndash; Augstbach')
	]


def prospecting(request):
    #for key, name in areanames:
    #    print key, Area.objects.get(short_name = key)
    areas = []
    for key, name in areanames:
        a = Area.objects.get(short_name = key)
        caves = list(a.cave_set.all())
        caves.sort(caveCmp)
        areas.append((name, a, caves))
    return render(request, 'prospecting.html', {"areas": areas})
    
# Parameters for big map and zoomed subarea maps:
# big map first (zoom factor ignored)

maps = {
# id	left		top			right		bottom		zoom
#		G&K			G&K			G&K			G&K			factor
"all": [33810.4,	85436.5,	38192.0,		81048.2,	0.35,
		"All"],
"40":  [36275.6,	82392.5,	36780.3,	81800.0,	3.0,
		"Eish&ouml;hle"],
"76":  [35440.0,	83220.0,	36090.0,	82670.0,	1.3,
		"Eislufth&ouml;hle"],
"204": [36354.1,	84154.5,	37047.4,	83300,	3.0,
		"Steinbr&uuml;ckenh&ouml;hle"],
"tc":  [35230.0,	82690.0,	36110.0,	82100.0,	3.0,
		"Near Top Camp"],
"grieß":
       [36000.0,	86300.0,	38320.0,	84400.0,	4.0,
		"Grießkogel Area"],
}

for n in list(maps.keys()):
    L, T, R, B, S, name = maps[n]
    W = (R-L)/2
    H = (T-B)/2
    for i in range(2):
        for j in range(2):
            maps["%s%i%i" % (n, i, j)] = [L + i * W, T - j * H, L + (i + 1) * W, T - (j + 1) * H, S, name]
# Keys in the order in which we want the maps output
mapcodes = ["all", "grieß","40", "76", "204", "tc"] 
# Field codes
L = 0
T = 1
R = 2
B = 3
ZOOM = 4
DESC = 5

areacolours = {
	'1a' : '#00ffff',
	'1b' : '#ff00ff',
	'1c' : '#ffff00',
	'1d' : '#ffffff',
	'2a' : '#ff0000',
	'2b' : '#00ff00',
	'2c' : '#008800',
	'2d' : '#ff9900',
	'3'  : '#880000',
	'4'  : '#0000ff',
	'6'  : '#000000', # doubles for surface fixed pts, and anything else
	'7'  : '#808080'
	}


for FONT in [
		"/usr/share/fonts/truetype/freefont/FreeSans.ttf",
		"/usr/X11R6/lib/X11/fonts/truetype/arial.ttf",
		"/mnt/c/windows/fonts/arial.ttf",
		"C:\WINNT\Fonts\ARIAL.TTF"
		]:
	if os.path.isfile(FONT): break
TEXTSIZE = 16
CIRCLESIZE =8
LINEWIDTH = 2
myFont = ImageFont.truetype(FONT, TEXTSIZE)

def mungecoord(x, y, mapcode, img):
	# Top of Zinken is 73 1201 = dataset 34542 81967
	# Top of Hinter is 1073 562 = dataset 36670 83317
	# image is 1417 by 2201
	# FACTOR1 = 1000.0 / (36670.0-34542.0)
	# FACTOR2 = (1201.0-562.0) / (83317 - 81967)
	# FACTOR = (FACTOR1 + FACTOR2)/2 
	# The factors aren't the same as the scanned map's at a slight angle. I
	# can't be bothered to fix this.  Since we zero on the Hinter it makes
	# very little difference for caves in the areas round 76 or 204.
	# xoffset = (x - 36670)*FACTOR
	# yoffset = (y - 83317)*FACTOR
	# return (1073 + xoffset, 562 - yoffset)

	m = maps[mapcode]
	factorX, factorY = img.size[0] / (m[R] - m[L]), img.size[1] / (m[T] - m[B])
	return ((x - m[L]) * factorX, (m[T] - y) * factorY)
	
COL_TYPES = {True: "red",
             False: "#dddddd",
             "Reference": "#dddddd"}
	
def plot(surveypoint, number,  point_type, label, mapcode, draw, img):
    try:
        ss = SurvexStation.objects.lookup(surveypoint)
        E, N = ss.x, ss.y
        shortnumber = number.replace("&mdash;","")
        (x,y) = list(map(int, mungecoord(E, N, mapcode, img)))
        #imgmaps[maparea].append( [x-4, y-SIZE/2, x+4+draw.textsize(shortnumber)[0], y+SIZE/2, shortnumber, label] )
        draw.rectangle([(x+CIRCLESIZE, y-TEXTSIZE/2), (x+CIRCLESIZE*2+draw.textsize(shortnumber)[0], y+TEXTSIZE/2)], fill="#ffffff")
        draw.text((x+CIRCLESIZE * 1.5,y-TEXTSIZE/2), shortnumber, fill="#000000")
        draw.ellipse([(x-CIRCLESIZE,y-CIRCLESIZE),(x+CIRCLESIZE,y+CIRCLESIZE)], fill=COL_TYPES[point_type], outline="#000000")
    except:
        pass

def prospecting_image(request, name):

    mainImage = Image.open(os.path.join(settings.SURVEY_SCANS, "location_maps", "pguidemap.jpg"))
    if settings.PUBLIC_SITE and not request.user.is_authenticated():
        mainImage = Image.new("RGB", mainImage.size, '#ffffff')
    m = maps[name]
    #imgmaps = []
    if name == "all":
        img = mainImage
    else:
        M = maps['all']
        W, H = mainImage.size
        l = int((m[L] - M[L]) / (M[R] - M[L]) * W)
        t = int((m[T] - M[T]) / (M[B] - M[T]) * H)
        r = int((m[R] - M[L]) / (M[R] - M[L]) * W)
        b = int((m[B] - M[T]) / (M[B] - M[T]) * H)
        img = mainImage.crop((l, t, r, b))
        w = int(round(m[ZOOM] * (m[R] - m[L]) / (M[R] - M[L]) * W))
        h = int(round(m[ZOOM] * (m[B] - m[T]) / (M[B] - M[T]) * H))
        img = img.resize((w, h), Image.BICUBIC)
    draw = ImageDraw.Draw(img)
    draw.setfont(myFont)
    if name == "all":
        for maparea in list(maps.keys()):
            if maparea == "all":
                continue
            localm = maps[maparea]
            l,t = mungecoord(localm[L], localm[T], "all", img)
            r,b = mungecoord(localm[R], localm[B], "all", img)
            text = maparea + " map"
            textlen = draw.textsize(text)[0] + 3
            draw.rectangle([l, t, l+textlen, t+TEXTSIZE+2], fill='#ffffff')
            draw.text((l+2, t+1), text, fill="#000000")
            #imgmaps.append( [l, t, l+textlen, t+SIZE+2, "submap" + maparea, maparea + " subarea map"] )
            draw.line([l, t, r, t], fill='#777777', width=LINEWIDTH)
            draw.line([l, b, r, b], fill='#777777', width=LINEWIDTH)
            draw.line([l, t, l, b], fill='#777777', width=LINEWIDTH)
            draw.line([r, t, r, b], fill='#777777', width=LINEWIDTH)
            draw.line([l, t, l+textlen, t], fill='#777777', width=LINEWIDTH)
            draw.line([l, t+TEXTSIZE+2, l+textlen, t+TEXTSIZE+2], fill='#777777', width=LINEWIDTH)
            draw.line([l, t, l, t+TEXTSIZE+2], fill='#777777', width=LINEWIDTH)
            draw.line([l+textlen, t, l+textlen, t+TEXTSIZE+2], fill='#777777', width=LINEWIDTH)
    #imgmaps[maparea] = []
    # Draw scale bar
    m100 = int(100 / (m[R] - m[L]) * img.size[0])
    draw.line([10, TEXTSIZE*3, 10, TEXTSIZE*2], fill='#000000', width=LINEWIDTH)
    draw.line([10, TEXTSIZE*2, 10+m100, TEXTSIZE*2], fill='#000000', width=LINEWIDTH)
    draw.line([10+m100, TEXTSIZE * 3, 10+m100, TEXTSIZE*2], fill='#000000', width=LINEWIDTH)
    label = "100m"
    draw.text([10 + (m100 - draw.textsize(label)[0]) / 2, TEXTSIZE/2], label, fill='#000000')
    plot("laser.0_7", "BNase", "Reference", "Br&auml;uning Nase laser point", name, draw, img)
    plot("226-96", "BZkn", "Reference", "Br&auml;uning Zinken trig point", name, draw, img)
    plot("vd1","VD1","Reference", "VD1 survey point", name, draw, img)
    plot("laser.kt114_96","HSK","Reference", "Hinterer Schwarzmooskogel trig point", name, draw, img) 
    plot("2000","Nipple","Reference", "Nipple (Wei&szlig;e Warze)", name, draw, img)
    plot("3000","VSK","Reference", "Vorderer Schwarzmooskogel summit", name, draw, img)
    plot("topcamp", "TC", "Reference", "Top Camp", name, draw, img)
    plot("laser.0", "LSR0", "Reference", "Laser Point 0", name, draw, img)
    plot("laser.0_1", "LSR1", "Reference", "Laser Point 0/1", name, draw, img)
    plot("laser.0_3", "LSR3", "Reference", "Laser Point 0/3", name, draw, img)
    plot("laser.0_5", "LSR5", "Reference", "Laser Point 0/5", name, draw, img)
    plot("225-96", "BAlm", "Reference", "Br&auml;uning Alm trig point", name, draw, img)
    for entrance in Entrance.objects.all():
        station = entrance.best_station()
        if station:
            #try:
                areaName = entrance.caveandentrance_set.all()[0].cave.getArea().short_name
                plot(station, "%s-%s" % (areaName, str(entrance)[5:]), entrance.needs_surface_work(), str(entrance), name, draw, img)
            #except:
            #    pass
                
    for (N, E, D, num) in [(35975.37,    83018.21,    100,"177"),    # Calculated from bearings
                           (35350.00,    81630.00,    50,    "71"),    # From Auer map
                           (36025.00,    82475.00,    50,    "146"),    # From mystery map
                           (35600.00,    82050.00,    50,    "35"),    # From Auer map
                           (35650.00,    82025.00,    50,    "44"),    # From Auer map
                           (36200.00,    82925.00,    50,    "178"),    # Calculated from bearings
                           (35232.64,    82910.37,    25,    "181"),    # Calculated from bearings
                           (35323.60,    81357.83,    50,    "74")    # From Auer map
                           ]:
        (N,E,D) = list(map(float, (N, E, D)))
        maparea = Cave.objects.get(kataster_number = num).getArea().short_name
        lo = mungecoord(N-D, E+D, name, img)
        hi = mungecoord(N+D, E-D, name, img)
        lpos = mungecoord(N-D, E, name, img)
        draw.ellipse([lo,hi], outline="#000000")
        draw.ellipse([lo[0]+1, lo[1]+1, hi[0]-1, hi[1]-1], outline=areacolours[maparea])
        draw.ellipse([lo[0]+2, lo[1]+2, hi[0]-2, hi[1]-2], outline=areacolours[maparea])
        draw.rectangle([lpos[0],lpos[1]-TEXTSIZE/2, lpos[0] + draw.textsize(name)[0], lpos[1]+TEXTSIZE/2], fill="#ffffff")
        draw.text((lpos[0], lpos[1]-TEXTSIZE/2), num, fill="#000000")
    response = HttpResponse(content_type = "image/png")
    del draw
    img.save(response, "PNG")
    return response
 
STATIONS = {}   
poslineregex = re.compile("^\(\s*([+-]?\d*\.\d*),\s*([+-]?\d*\.\d*),\s*([+-]?\d*\.\d*)\s*\)\s*([^\s]+)$")
def LoadPos():
    call([settings.CAVERN, "--output=%s/all.3d" % settings.SURVEX_DATA, "%s/all.svx" % settings.SURVEX_DATA])
    call([settings.THREEDTOPOS, '%sall.3d' % settings.SURVEX_DATA], cwd = settings.SURVEX_DATA)
    posfile = open("%sall.pos" % settings.SURVEX_DATA)
    posfile.readline()#Drop header
    for line in posfile.readlines():
        r = poslineregex.match(line)                
        if r:
            x, y, z, name = r.groups()
            STATIONS[name] = (x, y, z)

