troggle-unchained/core/views_caves.py

525 lines
22 KiB
Python
Raw Normal View History

#!/usr/bin/python
# -*- coding: utf-8 -*-
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
2011-07-11 02:10:22 +01:00
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
2011-07-11 02:10:22 +01:00
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect
2011-07-11 02:10:22 +01:00
from django.conf import settings
import re, urlparse
from django.shortcuts import get_object_or_404, render
import settings
from PIL import Image, ImageDraw, ImageFont
import string, os, sys, subprocess
2011-07-11 02:10:22 +01:00
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
2012-08-05 18:26:24 +01:00
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))
2012-08-05 18:28:34 +01:00
def caveCmp(x, y):
if x.kataster_number:
if y.kataster_number:
2012-08-05 18:28:34 +01:00
return numericalcmp(x.kataster_number, y.kataster_number) # Note that cave kataster numbers are not generally integers.
else:
return -1
else:
2012-08-05 18:26:24 +01:00
if y.kataster_number:
return 1
else:
return numericalcmp(x.unofficial_number, y.unofficial_number)
2011-07-11 02:10:22 +01:00
def caveindex(request):
caves = Cave.objects.all()
notablecavehrefs = settings.NOTABLECAVESHREFS
2011-07-11 02:10:22 +01:00
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})
2011-07-11 02:10:22 +01:00
2019-02-25 22:24:33 +00:00
def millenialcaves(request):
#RW messing around area
return HttpResponse("Test text", content_type="text/plain")
2019-02-25 22:24:33 +00:00
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
2011-07-11 02:10:22 +01:00
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})
2011-07-11 02:10:22 +01:00
else:
return render(request,'cave.html', {'settings': settings, 'cave': cave, 'cavepage': True, 'cave_id': cave_id})
2011-07-11 02:10:22 +01:00
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})
2011-07-11 02:10:22 +01:00
else:
return render(request,'cave_entrances.html', {'cave': cave})
2011-07-11 02:10:22 +01:00
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})
2011-07-11 02:10:22 +01:00
else:
return render(request,'cave_uground_description.html', {'cave': cave})
2011-07-11 02:10:22 +01:00
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})
2011-07-11 02:10:22 +01:00
else:
return render(request,'cave_qms.html', {'cave': cave})
2011-07-11 02:10:22 +01:00
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})
2011-07-11 02:10:22 +01:00
else:
return render(request,'cave_logbook.html', {'cave': cave})
2011-07-11 02:10:22 +01:00
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})
2011-07-11 02:10:22 +01:00
else:
return render(request,'cave.html', {'cave': cave, 'cave_editable': slug})
2011-07-11 02:10:22 +01:00
@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
})
2011-07-11 02:10:22 +01:00
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())
2011-07-11 02:10:22 +01:00
except QM.DoesNotExist:
url=urlparse.urljoin(settings.URL_ROOT, r'/admin/core/qm/add/'+'?'+ r'number=' + qm_id)
if grade:
url += r'&grade=' + grade
return HttpResponseRedirect(url)
2011-07-11 02:10:22 +01:00
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,
2011-07-11 02:10:22 +01:00
'entrance': cave_and_ent.entrance,
'letter': cave_and_ent.entrance_letter,})
def entranceSlug(request, slug):
entrance = Entrance.objects.get(entranceslug__slug = slug)
2011-07-11 02:10:22 +01:00
if entrance.non_public and not request.user.is_authenticated():
return render(request,'nonpublic.html', {'instance': entrance})
2011-07-11 02:10:22 +01:00
else:
return render(request,'entranceslug.html', {'entrance': entrance})
2011-07-11 02:10:22 +01:00
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, })
2011-07-11 02:10:22 +01:00
def surveyindex(request):
surveys=Survey.objects.all()
expeditions=Expedition.objects.order_by("-year")
return render(request,'survey.html',locals())
2011-07-11 02:10:22 +01:00
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())
2011-07-11 02:10:22 +01:00
def cave_description(request, cavedescription_name):
cave_description = get_object_or_404(CaveDescription, short_name = cavedescription_name)
return render(request,'cave_description.html', locals())
2011-07-11 02:10:22 +01:00
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()]})
2011-07-11 02:10:22 +01:00
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 = [
2015-06-28 13:39:50 +01:00
#('', 'Location unclear'),
('1a', '1a – Plateau: around Top Camp'),
('1b', '1b – Western plateau near 182'),
('1c', '1c – Eastern plateau near 204 walk-in path'),
('1d', '1d – Further plateau around 76'),
('2a', '2a – Southern Schwarzmooskogel near 201 path and the Nipple'),
('2b', '2b – Eishöhle area'),
('2b or 4 (unclear)', '2b or 4 (unclear)'),
('2c', '2c – Kaninchenhöhle area'),
('2d', '2d – Steinbrückenhöhle area'),
('3', '3 – Bräuning Alm'),
('4', '4 – Kratzer valley'),
('5', '5 – Schwarzmoos-Wildensee'),
('6', '6 – Far plateau'),
('1626 or 6 (borderline)', '1626 or 6 (borderline)'),
('7', '7 – Egglgrube'),
('8a', '8a – Loser south face'),
('8b', '8b – Loser below Dimmelwand'),
('8c', '8c – Augst See'),
('8d', '8d – Loser-Hochganger ridge'),
('9', '9 – Gschwandt Alm'),
('10', '10 – Altaussee'),
('11', '11 – 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öhle"],
"76": [35440.0, 83220.0, 36090.0, 82670.0, 1.3,
"Eislufthöhle"],
"204": [36354.1, 84154.5, 37047.4, 83300, 3.0,
"Steinbrückenhö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 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",
"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("—","")
(x,y) = 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 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äuning Nase laser point", name, draw, img)
plot("226-96", "BZkn", "Reference", "Brä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ß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ä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) = 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)