Source code for davies.compass.plt

"""
davies.compass.plt: Module for parsing and working with Compass .PLT plot files
"""

import logging
import datetime
from collections import OrderedDict

from davies.compass import ParseException, name_from_filename

log = logging.getLogger(__name__)


__all__ = 'Plot', 'Segment', 'MoveCommand', 'DrawCommand', 'CompassPltParser'


class Command(object):
    """
    Base class for Compass .PLT plot commands.

    Note that coordinates are passed in Y, X, Z (North, East, Elevation) order!
    """
    cmd = None

    def __init__(self, y, x, z, name, l, r, u, d, edist, flags=None):
        self.y, self.x, self.z = y, x, z
        self.name = name
        self.l, self.r, self.u, self.d = l, r, u, d
        self.edist = edist
        self.flags = flags


[docs]class MoveCommand(Command): """Compass .PLT plot command for moving the "plotting pen" to a specified Y,X,Z coordinate.""" cmd = 'M'
[docs]class DrawCommand(Command): """Compass .PLT plot command for drawing a line segment between two points.""" cmd = 'D'
[docs]class Segment(object): """Compass .PLT segment. A segment is a container for :class:`Command` objects.""" def __init__(self, name=None, date=None, comment=None): self.name = name self.date = date self.comment = comment self.xmin = self.xmax = self.ymin = self.ymax = self.zmin = self.zmax = None self.commands = []
[docs] def set_bounds(self, ymin, ymax, xmin, xmax, zmin, zmax): """Set Y,X,Z bounds for the segment.""" self.xmin, self.xmax = xmin, xmax self.ymin, self.ymax = ymin, ymax self.zmin, self.zmax = zmin, zmax
[docs] def add_command(self, command): """Add a :class:`Command` to :attr:`commands`.""" self.commands.append(command)
def __len__(self): return len(self.commands) def __iter__(self): for command in self.commands: yield command
[docs]class Plot(object): """Compass .PLT plot file. A Plot is a container for :class:`Segment` objects.""" def __init__(self, name=None): self.name = name self.utm_zone = None self.datum = None self.xmin = self.xmax = self.ymin = self.ymax = self.zmin = self.zmax = self.edist = None self.segments = [] self.fixed_points = OrderedDict() # name -> (y, x, z) self.loop_count = 0 self.loops = [] # list of (n, common_sta, from_sta, to_sta, [stations])
[docs] def set_bounds(self, ymin, ymax, xmin, xmax, zmin, zmax, edist=None): """Set Y,X,Z bounds for the plot.""" self.ymin, self.ymax = ymin, ymax self.xmin, self.xmax = xmin, xmax self.zmin, self.zmax = zmin, zmax self.edist = None
[docs] def add_segment(self, segment): """Add a :class:`Segment` to :attr:`segments`.""" self.segments.append(segment)
[docs] def add_fixed_point(self, name, coordinate): """Add a (Y, X, Z) tuple to :attr:`fixed_points`.""" self.fixed_points[name] = coordinate
[docs] def add_loop(self, n, common_sta, from_sta, to_sta, stations): """Add a loop tuple to :attr:`loops`.""" self.loops.append((n, common_sta, from_sta, to_sta, stations))
def __len__(self): return len(self.segments) def __iter__(self): for segment in self.segments: yield segment def __contains__(self, item): for segment in self.segments: if item == segment.name: return True return False def __getitem__(self, item): for segment in self.segments: if item == segment.name: return segment raise KeyError(item)
[docs]class CompassPltParser(object): """Parser for Compass .PLT plot files.""" # See: http://www.fountainware.com/compass/Documents/FileFormats/PlotFileFormat.htm
[docs] def __init__(self, pltfilename, strict_mode=False): """:param pltfilename: string filename""" self.pltfilename = pltfilename self.strict_mode = strict_mode
[docs] def parse(self): """Parse our .PLT file and return :class:`Plot` object or raise :exc:`ParseException`.""" plt = Plot(name_from_filename(self.pltfilename)) with open(self.pltfilename, 'rb') as pltfile: segment = None for line in pltfile: if not line: continue c, val = line[:1], line[1:] if c == 'Z': edist = None try: ymin, ymax, xmin, xmax, zmin, zmax = (float(v) for v in val.split()) except ValueError: ymin, ymax, xmin, xmax, zmin, zmax, _, edist = val.split() ymin, ymax, xmin, xmax, zmin, zmax, edist = \ (float(v) for v in (ymin, ymax, xmin, xmax, zmin, zmax, edist)) plt.set_bounds(ymin, ymax, xmin, xmax, zmin, zmax, edist) elif c == 'S': if not plt.name: plt.name = val.strip() elif c == 'G': plt.utm_zone = int(val) elif c == 'O': plt.datum = val elif c == 'N': date, comment = None, '' # both date and comment are optional try: name, _, m, d, y, comment = val.split(None, 5) date = datetime.date(int(y), int(m), int(d)) except ValueError: try: name, _, m, d, y = val.split() date = datetime.date(int(y), int(m), int(d)) except ValueError: name = val comment = comment[1:].strip() segment = Segment(name, date, comment) elif c == 'M': flags = None # flags are optional try: y, x, z, name, _, l, u, d, r, _, edist = val.split() except ValueError: y, x, z, name, _, l, u, d, r, _, edist, flags = val.split() cmd = MoveCommand(float(y), float(x), float(z), name[1:], float(l), float(r), float(u), float(d), float(edist), flags) segment.add_command(cmd) elif c in ('D', 'd'): # 'D' for normal stations, 'd' for "hidden" stations with the 'P' flag flags = None try: y, x, z, name, _, l, u, d, r, _, edist = val.split() except ValueError: y, x, z, name, _, l, u, d, r, _, edist, flags = val.split() cmd = DrawCommand(float(y), float(x), float(z), name[1:], float(l), float(r), float(u), float(d), float(edist), flags) cmd.cmd = c segment.add_command(cmd) elif c == 'X': segment.set_bounds(*(float(v) for v in val.split())) # An X-bounds command signifies end of segment plt.add_segment(segment) segment = None elif c == 'P': name, y, x, z = val.split() plt.add_fixed_point(name, (float(y), float(x), float(z))) elif c == 'C': plt.loop_count = int(val) elif c == 'R': count, common, from_sta, to_sta, stations = val.split(None, 4) plt.add_loop(int(count), common, from_sta, to_sta, stations.split()) elif c == '\x1A': continue # "soft EOF" ascii SUB ^Z else: msg = "Unknown PLT control code '%s': %s" % (c, val) if self.strict_mode: raise ParseException(msg) else: log.warning(msg) return plt