Exemple #1
0
 def test_pop_element(self):
     cache = LRUCache(3)
     cache[1] = '1'
     cache[2] = '2'
     cache[3] = '3'
     self.assertEqual("[(1, '1'), (2, '2'), (3, '3')]", str(cache))
     self.assertEqual('1', cache.pop(1))
     self.assertEqual("[(2, '2'), (3, '3')]", str(cache))
     self.assertEqual(2, len(cache))
     self.assertRaises(KeyError, cache.pop, 1)
Exemple #2
0
class Layer(object):
    """
    Class Layer

     Description:

     Constructors:
              Layer(map, layername)

     Methods:
              IO operations
              --------------
              open(mode)                Opens a MapSend layer filepair
                                        .lay/.clt for read (mode='r') or
                                        write (mode='w').
              close()                   Closes an opened MapSend layer filepair

              read()                    Read index file and header of layer file

              write(outlayername)       Read contents of layer that is opened for
                                        read and write a copy of the layer to
                                        layer name outlayername.


    """
    def __init__(self,
                 m,
                 name,
                 filename,
                 layertype=None,
                 nlevels=0,
                 fileidentifier=None):
        self.map = m

        ## Copy resolution and reference point from map
        self._scale = m.scale  # [xscale, yscale]
        self._refpoint = m.refpoint  # Reference point for conversion to discrete coordinate
        self._bbox = None
        self._dbbox = None

        self.nlevels = nlevels  # Cell levels

        if m.bboxrec:
            self.dbboxrec = m.bboxrec.todiscrete(self._refpoint, self._scale)
            self.estimator = None
        else:
            ## Create layer parameter estimator object
            self.estimator = LayerParamEstimator(self)

        if filename[0:len(m.mapnumstr)] != m.mapnumstr:
            filename = m.mapnumstr + filename

        if len(filename) > 8:
            raise Exception('Length of filename %s must not exceed 8' %
                            filename)

        self.name = name
        self.filename = filename
        self.cellelementid = 0
        self.isopen = False

        self.clearCells()

        self.mode = None
        self.bigendian = m.bigendian
        self.writedrc = False  # Write DRC file needed by mapsend software

        self.nobjects = 0
        self.category = 0  # 0=Normal layer, 1=Artificial layer
        self.fileidentifier = fileidentifier
        self.layertype = layertype

        ## Statistics from header
        self.firstcell = None
        self.lastcell = None

        ## Cell position in layer file (for use in read mode)
        self.cellfilepos = {}

        self.fhlay = None

        self.draworder = 0

        self.packed = False
        self.packer = None

    def __eq__(self, a):
        return isinstance(a, Layer) and self.name == a.name

    def __hash__(self):
        return hash(self.name)

    def clearCells(self):
        self.modifiedcells = {
        }  # Dictionary of modified cells keyed by cellnumber
        self.cellcache = LRUCache(size=32)
        self.cellfilepos = {}
        self.cellnumbers = []

    def setUnpackTable(self, filename):
        self.packed = True
        self.packer = layerpacker.LayerPacker(
            self.map.mapdir.open(filename).read())

    def open(self, mode):
        self.mode = mode
        if not self.isopen:
            if mode == 'r' or mode == 'a':
                try:
                    # First try open the layer as little endian
                    self.layerfilename = self.filename + ".lay"
                    self.indexfilename = self.filename + ".clt"
                    self.fhlay = self.map.mapdir.open(self.layerfilename, "r")
                    self.bigendian = False
                except IOError:
                    self.layerfilename = self.filename + ".yal"
                    self.indexfilename = self.filename + ".tlc"
                    self.fhlay = self.map.mapdir.open(self.layerfilename, "r")
                    self.bigendian = True
            elif mode == 'w':
                if not self.bigendian:
                    self.layerfilename = self.filename + ".lay"
                    self.indexfilename = self.filename + ".clt"
                else:
                    self.layerfilename = self.filename + ".yal"
                    self.indexfilename = self.filename + ".tlc"

                if not self.map.inmemory:
                    self.shelffile = tempfile.mktemp()
                    self.shelf = shelve.open(self.shelffile)

                self.fhlay = self.map.mapdir.open(self.layerfilename, "wb")

            isopen = True

            if self.mode in ('r', 'a'):
                self.read_index()
                self.read_header()

    def optimize(self):
        """Optimize nlevels parameter and calculate bounding box

        This function only works when the cell is opened in write only mode
        without bounding box.

        The function works like this. A proper value for the nlevel parameter and a bounding box
        are first estimated.
        At the beginning there is only one cell but with the new nlevel value the number of cells
        may grow. Hence the cell elements might have to be placed in new cells.

        Returns a dictionary of mapping between old and new cellreferences
        
        """

        if self.mode == 'w' and self._bbox == None:
            remapdict = {}

            logging.debug("Optimizing layer " + self.name)

            dbboxrec = self.estimator.calculateDBBox()

            if dbboxrec:
                self.dbboxrec = dbboxrec

            ## Get nlevels estimate
            self.nlevels = self.estimator.calculateNlevels()

            ## Adjust bounding box borders to get integer cellsize
            if dbboxrec:
                self.dbboxrec = dbboxrec

            ## Update the bounding box of cell 1
            self.getCell(1).setbbox()

            if self.nlevels > 0:
                cellelements = []
                cellelementrefs = []

                oldcell1 = self.getCell(1)

                self.clearCells()

                ## Loop over the elements in the old cell 1
                ## The elements need to be accessed in reversed order, otherwise the
                ## cellelement numbers would change
                ## during the loop
                for i in range(len(oldcell1) - 1, -1, -1):
                    ce = oldcell1.pop(i)
                    newcellrefs = self.addCellElement(ce)
                    remapdict[(oldcell1.cellnum, i)] = newcellrefs[0]

            return remapdict
        else:
            return {}

    def close(self):
        if (self.mode
                == 'w') or (self.mode == 'a') and len(self.modifiedcells) > 0:

            ## Use estimator to calculate bounding box
            if self._bbox == None:
                self.optimize()

            tmplay = tempfile.NamedTemporaryFile('wb', delete=False)

            self.write_header(tmplay)

            ## The cells must be written in cell number order
            self.cellnumbers.sort()

            # Merge unchanged cells with modified cells
            for cellnum in self.cellnumbers:
                if cellnum in self.modifiedcells:
                    cell = self.modifiedcells[cellnum]
                    celldata = cell.serialize()
                    # Update index
                    self.cellfilepos[cellnum] = [tmplay.tell(), len(celldata)]
                    tmplay.write(celldata)
                else:
                    self.fhlay.seek(self.cellfilepos[cellnum][0])
                    celldata = self.fhlay.read(self.cellfilepos[cellnum][1])
                    self.cellfilepos[cellnum][0] = tmplay.tell()
                    tmplay.write(celldata)

            # Rewind and write the header again with the new cell index statistics
            tmplay.seek(0)
            self.write_header(tmplay)

            # Copy temporary file to layer file
            tmplay.close()
            tmplay = open(tmplay.name, 'rb')
            shutil.copyfileobj(tmplay, self.fhlay)
            tmplay.close()
            os.unlink(tmplay.name)

            # Create index file
            fhidx = self.map.mapdir.open(self.indexfilename, "wb")
            fhdrc = None
            if self.writedrc:
                fhdrc = self.map.mapdir.open(self.filename + ".drc", "wb")

            if fhdrc:
                fhdrc.write(self.pack("I", len(self.cellnumbers)))

            for cellnum in self.cellnumbers:
                fhidx.write(
                    self.pack("III", cellnum, self.cellfilepos[cellnum][0],
                              self.cellfilepos[cellnum][1]))
                if fhdrc:
                    fhdrc.write(
                        self.pack("III", cellnum, self.cellfilepos[cellnum][0],
                                  self.cellfilepos[cellnum][1]))

            fhidx.close()
            if fhdrc:
                fhdrc.close()

            if self.fhlay:
                self.fhlay.close()

        if self.mode == 'w' and not self.map.inmemory:
            os.unlink(self.shelffile)

        isopen = False

    def read_index(self):
        if self.mode in ['r', 'a']:
            fhidx = self.map.mapdir.open(self.indexfilename, "r")

            self.cellfilepos = {}
            self.cellnumbers = []
            while 1:
                data = fhidx.read(12)

                if len(data) == 0: break

                [cellnum, offset, cellsize] = self.unpack("3i", data)
                self.cellfilepos[cellnum] = [offset, cellsize]
                self.cellnumbers.append(cellnum)

    def read_header(self):
        self.dheader = self.fhlay.read(0x80)

        [self.category] = self.unpack("i", self.dheader[4:])
        [self.fileidentifier] = self.unpack("H", self.dheader[8:])

        tmp = self.unpack("4f", self.dheader[0xa:])
        self._bbox = Rec(N.array([tmp[0], tmp[2]]), N.array([tmp[1], tmp[3]]))

        [self.nlevels] = self.unpack("h", self.dheader[0x1a:])
        [self.nobjects] = self.unpack("i", self.dheader[0x1c:])

        [xscale] = self.unpack("d", self.dheader[0x20:])
        [yscale] = self.unpack("d", self.dheader[0x28:])

        self._scale = N.array([xscale, yscale])

        self._refpoint = N.array(self.unpack("2f", self.dheader[0x30:]))

        tmp = self.unpack("4i", self.dheader[0x38:])
        self._dbbox = Rec(N.array([tmp[0], tmp[1]]), N.array([tmp[2], tmp[3]]))

        [self.layertype] = self.unpack("b", self.dheader[0x48:])
        [unknown49] = self.unpack("b", self.dheader[0x49:])
        [self.largestcellsize] = self.unpack("i", self.dheader[0x4a:])
        [self.firstcell] = self.unpack("i", self.dheader[0x4e:])
        [self.lastcell] = self.unpack("i", self.dheader[0x52:])

        assert (unknown49, 0)

    def header_info(self):
        info = ""
        info += "Category: 0x%x" % self.category + '\n'
        info += "Fileidentifier: 0x%x" % self.fileidentifier + '\n'
        info += "Number of levels: %d" % self.nlevels + '\n'
        info += "Number of elements: %d" % self.nobjects + '\n'
        info += "Bbox: %s" % self._bbox + '\n'
        info += "Scale: %s" % str(self._scale) + '\n'
        info += "Refpoint: %s" % str(self._refpoint) + '\n'
        info += "Layer type: 0x%x" % self.layertype + '\n'
        info += "Largest cell size: 0x%x" % self.largestcellsize + '\n'
        info += "First cell: 0x%x" % self.firstcell + '\n'
        info += "Last cell: 0x%x" % self.lastcell + '\n'

        info += "Discrete Bbox: %s" % self._dbbox + '\n'
        return info

    def write_header(self, fh):
        header = "MHGO"
        header = header + self.pack("i", self.category)
        if self.fileidentifier == None:
            fileidentifier = 0xc000 | self.map.getLayerIndex(self)
        else:
            fileidentifier = self.fileidentifier

        header = header + self.pack("H", fileidentifier)

        header = header + self.pack("4f", self._bbox.minX(), self._bbox.maxX(),
                                    self._bbox.minY(), self._bbox.maxY())
        header = header + self.pack("h", self.nlevels)
        header = header + self.pack("i", self.nobjects)
        header = header + self.pack("d", self._scale[0])
        header = header + self.pack("d", self._scale[1])
        header = header + self.pack("f", self._refpoint[0])
        header = header + self.pack("f", self._refpoint[1])
        header = header + self.pack("4i", self._dbbox.minX(),
                                    self._dbbox.minY(), self._dbbox.maxX(),
                                    self._dbbox.maxY())

        header = header + self.pack("b", self.layertype)
        header = header + self.pack("b", 0)

        if len(self.cellfilepos) > 0:
            largestcellsize = max([d[1] for d in self.cellfilepos.values()])
        else:
            largestcellsize = 0
        header = header + self.pack("i", largestcellsize)

        if len(self.cellnumbers) == 0:
            header = header + self.pack("i", 0)  # First cell number
            header = header + self.pack("i", 0)  # Last cell number
        else:
            header = header + self.pack(
                "i", self.cellnumbers[0])  # First cell number
            header = header + self.pack(
                "i", self.cellnumbers[-1])  # Last cell number

        header = header + chr(0) * (128 - len(header))

        fh.write(header)
        return len(header)

    def unpack(self, types, data):
        if self.bigendian:
            prefix = ">"
        else:
            prefix = "<"
        return struct.unpack(prefix + types,
                             data[0:struct.calcsize(prefix + types)])

    def pack(self, types, *data):
        if self.bigendian:
            prefix = ">"
        else:
            prefix = "<"
        return struct.pack(prefix + types, *data)

    def markCellModified(self, cellnum):
        self.modifiedcells[cellnum] = self.getCell(cellnum)

    def getCells(self):
        for cn in self.cellnumbers:
            yield self.getCell(cn)

    def getCell(self, cellnum):
        if cellnum in self.modifiedcells:
            return self.modifiedcells[cellnum]

        if cellnum in self.cellcache:
            return self.cellcache[cellnum]

        # New cell
        if self.mode == 'w':
            if self.map.inmemory:
                cell = CellInMemory(self, cellnum)
            elif self.nlevels == 0:
                cell = CellShelve(self, cellnum)
            else:
                cell = CellCommonShelve(self, cellnum, self.shelf)
        else:
            cell = CellInMemory(self, cellnum)

        # Deserialize cell if present in the cell index
        if cellnum in self.cellfilepos:
            self.fhlay.seek(self.cellfilepos[cellnum][0])
            celldata = self.fhlay.read(self.cellfilepos[cellnum][1])

            if self.packed:
                celldata = self.packer.unpack(celldata)

            cell.deSerialize(celldata)

        self.cellcache[cellnum] = cell

        return cell

    def close_cell(self, cellnum):
        self.cellcache.pop(cellnum)

    def getCellElements(self):
        for c in self.getCells():
            for s in c.getCellElements():
                yield s

    def getCellElementsAndRefs(self):
        for c in self.getCells():
            for nincell, s in enumerate(c.getCellElements()):
                yield (s, (c.cellnum, nincell))

    def getCellElement(self, cellref):
        """Get cell element from a (cellnum, num_in_cell) pair """
        (cellnum, num_in_cell) = cellref

        if self.map.debug:
            print "Cellcache: " + str(self.cellcache.keys())
        cell = self.getCell(cellnum)
        try:
            cellelement = cell.getCellElement(num_in_cell)
        except IndexError:
            raise IndexError, "num_in_cell (%d) is outside the # of cellelements (%d) in cell %d, layer %s" % (
                num_in_cell, len(cell), cellnum, self.name)

        return cellelement

    def addCellElement(self, cellelem, cellnum=None):
        """Add cell element to layer. The element might be divided into smaller elements.
         Returns list of (cellnum,# in cell) pairs"""
        if self.mode in ('r', None):
            raise ValueError(
                'Layer must be opened in write or append mode to add cell elements'
            )

        ## Calculate the minimum cell that contains the extents of the new element
        if self._bbox != None:
            if cellnum == None:
                cellnum, level, dcellrec = get_best_cell(
                    self._dbbox, cellelem.dbboxrec.negY(), self.nlevels)


#            assert cellelem.bboxrec(self).iscoveredby(self.bboxrec, xmargin=self._scale[0], ymargin=self._scale[1]), "CellElement is outside layer boundaries:" + \
#                   str(self.bboxrec(self)) + " cellelement:" + str(cellelem.bboxrec(self))
        else:
            if self.nlevels > 0:
                raise ValueError(
                    'Cannot add cell element to layer with nlevels>0 and no bounding box'
                )
            cellnum = 1

            self.estimator.addCellElement(cellelem)

        cellelem.cellnum = cellnum

        cell = self.getCell(cellnum)

        assert cell.bboxrec == None or \
            cellelem.dbboxrec.iscoveredby(cell.dbboxrec), \
            "Incorrect cell %d with bbox %s for cell element with bbox %s"%(cellnum, cell.dbboxrec, str(cellelem.dbboxrec))

        nincell = cell.addCellElement(cellelem)

        assert self.nlevels == 0 or nincell < 2**16

        if not cell in self.modifiedcells:
            self.modifiedcells[cellnum] = cell
        if not cellnum in self.cellnumbers:
            self.cellnumbers.append(cellnum)

        self.nobjects += 1

        return [(cellnum, nincell)]

    def updateCellElement(self, cellelementref, cellelement):
        """the updateCellElement must be called when a cell element has been updated"""
        self.getCell(cellelementref[0]).updateElement(cellelementref[1],
                                                      cellelement)

    def getName(self):
        return self.name

    def getFileName(self):
        return self.filename

    def getNObjects(self):
        return self.nobjects

    ## Bounding box property
    def get_bboxrec(self):
        if self._bbox:
            return self._bbox.negY()
        else:
            return None

    def set_bboxrec(self, rec):
        if self.mode == 'r':
            raise ValueError(
                "Can't change boundary rectangle in read-only mode")

        self.dbboxrec = rec.todiscrete(self._refpoint, self._scale)

        # If in append mode all cell elements must be re-added to fit the new
        # cell boundaries
        if self.mode == 'a':
            cellelements = [e for e in self.getCellElements()]

            self.clearCells()

            for e in cellelements:
                self.addCellElement(e)

    bboxrec = property(get_bboxrec, set_bboxrec, "Bounding box rectangle")

    def get_dbboxrec(self):
        if self._dbbox:
            return self._dbbox.negY()
        else:
            return None

    def set_dbboxrec(self, drec):
        first_time = self._dbbox == None

        if not first_time and self.mode == 'r':
            raise ValueError(
                "Can't change boundary rectangle in read-only mode")

        self._dbbox = drec.negY()
        self._bbox = self._dbbox.tocontinous(self._refpoint, self._scale)

        if self._dbbox.width % (2**self.nlevels) != 0 or self._dbbox.height % (
                2**self.nlevels) != 0:
            logging.warn(
                "bbox should be a multiple of minimum cell size, adjusting bbox borders"
            )
            n = 2**(self.nlevels + 1)
            width = self._dbbox.width
            height = self._dbbox.height

            width += -width % n
            height += -height % n

            self._dbbox = Rec(self._dbbox.c1,
                              self._dbbox.c1 + N.array(width, height))

        # If in append mode all cell elements must be re-added to fit the new
        # cell boundaries
        if not first_time and self.mode == 'a':
            cellelements = [e for e in self.getCellElements()]

            self.clearCells()

            for e in cellelements:
                self.addCellElement(e)

    dbboxrec = property(get_dbboxrec, set_dbboxrec,
                        "Bounding box rectangle discrete coordinates")

    @property
    def refpoint(self):
        return self._refpoint

    @property
    def scale(self):
        return self._scale

    def getLayerType(self):
        return self.layertype

    def calc_cell_extents(self, cellnum):
        """
        Calculate discrete bounding box of a cell

        Note, the extents return is in the internal coordinates with negated Y-values
        """
        ## Calculate cell level
        level = 0
        while cellnum > totcells_at_level(level):
            level = level + 1

        n = 2**level  # Number of rows/cols

        lbb = self._dbbox
        layerwidth = lbb.width
        layerheight = lbb.height
        layersize = N.array([layerwidth, layerheight])

        relcnum = cellnum - (totcells_at_level(level - 1) + 1)

        cellsize = layersize / n

        if relcnum < n * n:
            mincorner = N.array([relcnum % n, relcnum / n]) * cellsize
            maxcorner = mincorner + cellsize
        else:
            relcnum = relcnum - n * n
            mincorner = N.array([relcnum % (n + 1), relcnum /
                                 (n + 1)]) * cellsize - cellsize / 2
            maxcorner = mincorner + layersize / n

            mincorner[N.where(mincorner < 0)] = 0
            maxcorner[0] = min(maxcorner[0], layerwidth)
            maxcorner[1] = min(maxcorner[1], layerheight)

        return Rec(mincorner + lbb.c1, maxcorner + lbb.c1)

    def layer_header_nok(self, pcnt):
        """Header check from magsendtool"""

        pcnt /= 100.0
        rc = 0
        if abs(((self._bbox.maxY() - self._bbox.minY()) / self._scale[1]) -
               (self._dbbox.maxY() - self._dbbox.minY())) > pcnt * (
                   self._dbbox.maxY() - self._dbbox.minY()):
            rc |= 1
        if abs(((self._bbox.maxX() - self._bbox.minX()) / self._scale[0]) -
               (self._dbbox.maxX() - self._dbbox.minX())) > pcnt * (
                   self._dbbox.maxX() - self._dbbox.minX()):
            rc |= 2
        if self._refpoint[1] != 0.0 and abs(self._bbox.centerY(
        ) - self._refpoint[1]) / self._scale[1] > 0.75:
            rc |= 4
        if self._refpoint[0] != 0.0 and abs(self._bbox.centerX(
        ) - self._refpoint[0]) / self._scale[0] > 0.75:
            rc |= 8
        return rc

    def check(self):
        version = 1
        if self.layer_header_nok(0.1):
            version += 1
            if self.layer_header_nok(1.0):
                version += 1
                if self.layer_header_nok(5.0):
                    raise ValueError(
                        'Incorrect layer format rc=%d at 5%% error' %
                        self.layer_header_nok(5.0))
        return version

    @property
    def ncells(self):
        """Return the number of cells in the layer"""
        return len(self.cellnumbers)

    @property
    def info(self):
        res = "Name: " + self.getName() + "\n"
        res += "Number of objects: " + str(self.getNObjects()) + "\n"
        res += "Number of cells: " + str(len(self.cellnumbers)) + "\n"
        res += "Reference point: " + str(self._refpoint) + "\n"
        res += "Scale: " + str(self._scale) + "\n"
        res += "Boundaries: " + str(self._bbox) + "\n"
        res += "Discrete Boundaries: " + str(self._dbbox) + "\n"
        if self.fileidentifier:
            res += "Identifier: 0x%x\n" % self.fileidentifier
        res += "# of levels: %d\n" % self.nlevels
        res += "category: %d\n" % self.category
        if self.layertype != None:
            res += "layertype: %d\n" % self.layertype
        res += 'reflat: %f\n' % self._refpoint[1]
        res += 'reflon: %f\n' % self._refpoint[0]
        if self.firstcell:
            res += 'first cell: %d\n' % self.firstcell
        if self.lastcell:
            res += 'last cell: %d\n' % self.lastcell
        return res

    def float2discrete(self, points):
        """Convert list of coordinates from floating point to discrete coordinates"""
        return ((N.array(points) - self.refpoint) /
                self.scale).round().astype(int)

    def discrete2float(self, points):
        """Convert list of coordinates from discrete to floating point coordinates"""
        return N.array(points) * self.scale + self.refpoint

    def __repr__(self):
        return self.__class__.__name__ + '(' + self.getName() + ')'
Exemple #3
0
class Layer(object):
    """
    Class Layer

     Description:

     Constructors:
              Layer(map, layername)

     Methods:
              IO operations
              --------------
              open(mode)                Opens a MapSend layer filepair
                                        .lay/.clt for read (mode='r') or
                                        write (mode='w').
              close()                   Closes an opened MapSend layer filepair

              read()                    Read index file and header of layer file

              write(outlayername)       Read contents of layer that is opened for
                                        read and write a copy of the layer to
                                        layer name outlayername.


    """
    def __init__(self, m, name, filename, layertype=None, nlevels=0,
                 fileidentifier=None):
        self.map = m

        ## Copy resolution and reference point from map
        self._scale = m.scale                           # [xscale, yscale]
        self._refpoint = m.refpoint                     # Reference point for conversion to discrete coordinate
        self._bbox = None
        self._dbbox = None

        self.nlevels = nlevels # Cell levels

        if m.bboxrec:
            self.dbboxrec = m.bboxrec.todiscrete(self._refpoint, self._scale)
            self.estimator = None
        else:
            ## Create layer parameter estimator object
            self.estimator = LayerParamEstimator(self)

        if filename[0:len(m.mapnumstr)] != m.mapnumstr:
            filename = m.mapnumstr + filename

        if len(filename) > 8:
            raise Exception('Length of filename %s must not exceed 8'%filename)

        self.name = name
        self.filename = filename
        self.cellelementid = 0
        self.isopen = False

        self.clearCells()

        self.mode = None
        self.bigendian = m.bigendian
        self.writedrc = False         # Write DRC file needed by mapsend software

        self.nobjects = 0
        self.category = 0 # 0=Normal layer, 1=Artificial layer
        self.fileidentifier = fileidentifier
        self.layertype = layertype

        ## Statistics from header
        self.firstcell = None
        self.lastcell = None

        ## Cell position in layer file (for use in read mode)
        self.cellfilepos = {}                          
        
        self.fhlay = None

        self.draworder = 0

        self.packed = False
        self.packer = None

    def __eq__(self, a):
        return isinstance(a, Layer) and self.name == a.name
    
    def __hash__(self):
        return hash(self.name)

    def clearCells(self):
        self.modifiedcells = {}        # Dictionary of modified cells keyed by cellnumber
        self.cellcache = LRUCache(size=32)
        self.cellfilepos = {}
        self.cellnumbers = []

    def setUnpackTable(self, filename):
        self.packed = True
        self.packer = layerpacker.LayerPacker(self.map.mapdir.open(filename).read())

    def open(self, mode):
        self.mode = mode
        if not self.isopen:
            if mode=='r' or mode=='a':
                try:
                    # First try open the layer as little endian
                    self.layerfilename = self.filename+".lay"
                    self.indexfilename = self.filename+".clt"
                    self.fhlay = self.map.mapdir.open(self.layerfilename,"r")
                    self.bigendian = False
                except IOError:
                    self.layerfilename = self.filename+".yal"
                    self.indexfilename = self.filename+".tlc"
                    self.fhlay = self.map.mapdir.open(self.layerfilename,"r")
                    self.bigendian = True
            elif mode=='w':
                if not self.bigendian:
                    self.layerfilename = self.filename+".lay"
                    self.indexfilename = self.filename+".clt"
                else:
                    self.layerfilename = self.filename+".yal"
                    self.indexfilename = self.filename+".tlc"

                if not self.map.inmemory:
                    self.shelffile = tempfile.mktemp()
                    self.shelf = shelve.open(self.shelffile)

                self.fhlay = self.map.mapdir.open(self.layerfilename,"wb")

            isopen=True

            if self.mode in ('r','a'):
                self.read_index()
                self.read_header()

    def optimize(self):
        """Optimize nlevels parameter and calculate bounding box

        This function only works when the cell is opened in write only mode
        without bounding box.

        The function works like this. A proper value for the nlevel parameter and a bounding box
        are first estimated.
        At the beginning there is only one cell but with the new nlevel value the number of cells
        may grow. Hence the cell elements might have to be placed in new cells.

        Returns a dictionary of mapping between old and new cellreferences
        
        """

        if self.mode == 'w' and self._bbox == None:
            remapdict = {}

            logging.debug("Optimizing layer "+self.name)

            dbboxrec = self.estimator.calculateDBBox()

            if dbboxrec:
                self.dbboxrec = dbboxrec

            ## Get nlevels estimate
            self.nlevels = self.estimator.calculateNlevels()

            ## Adjust bounding box borders to get integer cellsize
            if dbboxrec:
                self.dbboxrec = dbboxrec

            ## Update the bounding box of cell 1
            self.getCell(1).setbbox()

            if self.nlevels > 0:
                cellelements = []
                cellelementrefs = []

                oldcell1 = self.getCell(1)

                self.clearCells()

                ## Loop over the elements in the old cell 1 
                ## The elements need to be accessed in reversed order, otherwise the 
                ## cellelement numbers would change
                ## during the loop
                for i in range(len(oldcell1)-1,-1,-1):
                    ce = oldcell1.pop(i)
                    newcellrefs = self.addCellElement(ce)
                    remapdict[(oldcell1.cellnum, i)] = newcellrefs[0]

            return remapdict
        else:
            return {}

    def close(self):
        if (self.mode=='w') or (self.mode=='a') and len(self.modifiedcells)>0:

            ## Use estimator to calculate bounding box
            if self._bbox == None:
                self.optimize()
            
            tmplay = tempfile.NamedTemporaryFile('wb', delete=False)

            self.write_header(tmplay)

            ## The cells must be written in cell number order
            self.cellnumbers.sort()

            # Merge unchanged cells with modified cells
            for cellnum in self.cellnumbers:
                if cellnum in self.modifiedcells:
                    cell = self.modifiedcells[cellnum]
                    celldata = cell.serialize()
                    # Update index
                    self.cellfilepos[cellnum] = [tmplay.tell(), len(celldata)]
                    tmplay.write(celldata)
                else:
                    self.fhlay.seek(self.cellfilepos[cellnum][0])
                    celldata = self.fhlay.read(self.cellfilepos[cellnum][1])
                    self.cellfilepos[cellnum][0] = tmplay.tell()
                    tmplay.write(celldata)

            # Rewind and write the header again with the new cell index statistics
            tmplay.seek(0)
            self.write_header(tmplay)

            # Copy temporary file to layer file
            tmplay.close()
            tmplay = open(tmplay.name, 'rb')
            shutil.copyfileobj(tmplay, self.fhlay)
            tmplay.close()
            os.unlink(tmplay.name)

            # Create index file
            fhidx = self.map.mapdir.open(self.indexfilename,"wb")
            fhdrc = None
            if self.writedrc:
                fhdrc = self.map.mapdir.open(self.filename+".drc", "wb")

            if fhdrc:
                fhdrc.write( self.pack("I", len(self.cellnumbers)))


            for cellnum in self.cellnumbers:
                fhidx.write( self.pack("III", cellnum, self.cellfilepos[cellnum][0], self.cellfilepos[cellnum][1]) )
                if fhdrc:
                    fhdrc.write( self.pack("III", cellnum, self.cellfilepos[cellnum][0], self.cellfilepos[cellnum][1]) )
                
            fhidx.close()
            if fhdrc:
                fhdrc.close()
            
            if self.fhlay:
                self.fhlay.close()

        if self.mode == 'w' and not self.map.inmemory:
            os.unlink(self.shelffile)
        
        isopen=False

    def read_index(self):
        if self.mode in ['r','a']:
            fhidx = self.map.mapdir.open(self.indexfilename, "r")

            self.cellfilepos = {}
            self.cellnumbers = []
            while 1:
                data = fhidx.read(12)

                if len(data) == 0: break

                [cellnum,offset,cellsize] = self.unpack("3i",data)
                self.cellfilepos[cellnum] = [offset,cellsize]
                self.cellnumbers.append(cellnum)

    def read_header(self):
        self.dheader = self.fhlay.read(0x80)

        [self.category] = self.unpack("i", self.dheader[4:])
        [self.fileidentifier] = self.unpack("H", self.dheader[8:])

        tmp = self.unpack("4f", self.dheader[0xa:])
        self._bbox = Rec(N.array([tmp[0],tmp[2]]), 
                         N.array([tmp[1],tmp[3]]))

        [self.nlevels] = self.unpack("h", self.dheader[0x1a:])
        [self.nobjects] = self.unpack("i", self.dheader[0x1c:])

        [xscale] = self.unpack("d", self.dheader[0x20:])
        [yscale] = self.unpack("d", self.dheader[0x28:])

        self._scale = N.array([xscale, yscale])

        self._refpoint = N.array(self.unpack("2f", self.dheader[0x30:]))

        tmp = self.unpack("4i", self.dheader[0x38:])
        self._dbbox = Rec(N.array([tmp[0],tmp[1]]), 
                          N.array([tmp[2],tmp[3]]))

        [self.layertype] = self.unpack("b", self.dheader[0x48:])
        [unknown49] = self.unpack("b", self.dheader[0x49:])
        [self.largestcellsize] = self.unpack("i", self.dheader[0x4a:])
        [self.firstcell] = self.unpack("i", self.dheader[0x4e:])
        [self.lastcell] = self.unpack("i", self.dheader[0x52:])

        assert(unknown49, 0)

    def header_info(self):
        info = ""
        info += "Category: 0x%x"%self.category + '\n'
        info += "Fileidentifier: 0x%x"%self.fileidentifier +'\n'
        info += "Number of levels: %d"%self.nlevels + '\n'
        info += "Number of elements: %d"%self.nobjects + '\n'
        info += "Bbox: %s"%self._bbox +'\n'
        info += "Scale: %s"%str(self._scale) + '\n'
        info += "Refpoint: %s"%str(self._refpoint) + '\n'
        info += "Layer type: 0x%x"%self.layertype + '\n'
        info += "Largest cell size: 0x%x"%self.largestcellsize + '\n'
        info += "First cell: 0x%x"%self.firstcell + '\n'
        info += "Last cell: 0x%x"%self.lastcell + '\n'

        info += "Discrete Bbox: %s"%self._dbbox +'\n'
        return info

    def write_header(self, fh):
        header = "MHGO"
        header = header + self.pack("i", self.category)
        if self.fileidentifier == None:
            fileidentifier = 0xc000 | self.map.getLayerIndex(self)
        else:
            fileidentifier = self.fileidentifier

        header = header + self.pack("H", fileidentifier)

        header = header + self.pack("4f", self._bbox.minX(), self._bbox.maxX(),
                                    self._bbox.minY(), self._bbox.maxY())
        header = header + self.pack("h", self.nlevels)
        header = header + self.pack("i", self.nobjects)
        header = header + self.pack("d", self._scale[0])
        header = header + self.pack("d", self._scale[1])
        header = header + self.pack("f", self._refpoint[0])
        header = header + self.pack("f", self._refpoint[1])
        header = header + self.pack("4i", self._dbbox.minX(), self._dbbox.minY(), self._dbbox.maxX(), self._dbbox.maxY())

        header = header + self.pack("b", self.layertype)
        header = header + self.pack("b", 0)

        if len(self.cellfilepos)>0:
            largestcellsize = max([d[1] for d in self.cellfilepos.values()])
        else:
            largestcellsize = 0
        header = header + self.pack("i", largestcellsize)

        if len(self.cellnumbers) == 0:
            header = header + self.pack("i", 0) # First cell number
            header = header + self.pack("i", 0) # Last cell number
        else:
            header = header + self.pack("i", self.cellnumbers[0]) # First cell number
            header = header + self.pack("i", self.cellnumbers[-1]) # Last cell number
        
        header = header + chr(0)*(128-len(header))
                                    
        fh.write(header)
        return len(header)

    def unpack(self,types,data):
        if self.bigendian:
            prefix=">"
        else:
            prefix="<"
        return struct.unpack(prefix + types,
                             data[0:struct.calcsize(prefix+types)])

    def pack(self, types, *data):
        if self.bigendian:
            prefix=">"
        else:
            prefix="<"
        return struct.pack(prefix+types, *data)

    def markCellModified(self, cellnum):
        self.modifiedcells[cellnum] = self.getCell(cellnum)

    def getCells(self):
        for cn in self.cellnumbers:
            yield self.getCell(cn)
    
    def getCell(self, cellnum):
        if cellnum in self.modifiedcells:
            return self.modifiedcells[cellnum]
        
        if cellnum in self.cellcache:
            return self.cellcache[cellnum]

        # New cell
        if self.mode == 'w':
            if self.map.inmemory:
                cell = CellInMemory(self, cellnum)
            elif self.nlevels == 0:
                cell = CellShelve(self, cellnum)
            else:
                cell = CellCommonShelve(self, cellnum, self.shelf)
        else:
            cell = CellInMemory(self, cellnum)

        # Deserialize cell if present in the cell index
        if cellnum in self.cellfilepos:
            self.fhlay.seek(self.cellfilepos[cellnum][0])
            celldata = self.fhlay.read(self.cellfilepos[cellnum][1])

            if self.packed:
                celldata = self.packer.unpack(celldata)
            
            cell.deSerialize(celldata)
        
        self.cellcache[cellnum] = cell

        return cell

    def close_cell(self, cellnum):
        self.cellcache.pop(cellnum)

    def getCellElements(self):
        for c in self.getCells():
            for s in c.getCellElements():
                yield s
	
    def getCellElementsAndRefs(self):
        for c in self.getCells():
            for nincell, s in enumerate(c.getCellElements()):
                yield (s, (c.cellnum, nincell))
	
    def getCellElement(self, cellref):
        """Get cell element from a (cellnum, num_in_cell) pair """
        (cellnum, num_in_cell) = cellref
        
        if self.map.debug:
            print "Cellcache: "+str(self.cellcache.keys())
        cell = self.getCell(cellnum)
        try:
            cellelement = cell.getCellElement(num_in_cell)
        except IndexError:
            raise IndexError,"num_in_cell (%d) is outside the # of cellelements (%d) in cell %d, layer %s"%(num_in_cell,len(cell),cellnum, self.name)

        return cellelement
	
    def addCellElement(self, cellelem, cellnum = None):
        """Add cell element to layer. The element might be divided into smaller elements.
         Returns list of (cellnum,# in cell) pairs"""
        if self.mode in ('r', None):
            raise ValueError('Layer must be opened in write or append mode to add cell elements')

        ## Calculate the minimum cell that contains the extents of the new element
        if self._bbox != None:
            if cellnum == None:
                cellnum, level, dcellrec = get_best_cell(self._dbbox,
                                                        cellelem.dbboxrec.negY(),
                                                        self.nlevels)

#            assert cellelem.bboxrec(self).iscoveredby(self.bboxrec, xmargin=self._scale[0], ymargin=self._scale[1]), "CellElement is outside layer boundaries:" + \
#                   str(self.bboxrec(self)) + " cellelement:" + str(cellelem.bboxrec(self))
        else:
            if self.nlevels > 0:
                raise ValueError('Cannot add cell element to layer with nlevels>0 and no bounding box')
            cellnum = 1

            self.estimator.addCellElement(cellelem)
         
        cellelem.cellnum = cellnum
        
        cell = self.getCell(cellnum)

        assert cell.bboxrec == None or \
            cellelem.dbboxrec.iscoveredby(cell.dbboxrec), \
            "Incorrect cell %d with bbox %s for cell element with bbox %s"%(cellnum, cell.dbboxrec, str(cellelem.dbboxrec))
        
        nincell = cell.addCellElement(cellelem)
        
        assert self.nlevels == 0 or nincell < 2**16
        
        if not cell in self.modifiedcells:
            self.modifiedcells[cellnum] = cell
        if not cellnum in self.cellnumbers:
            self.cellnumbers.append(cellnum)

        self.nobjects += 1

        return [(cellnum, nincell)]

    def updateCellElement(self, cellelementref, cellelement):
        """the updateCellElement must be called when a cell element has been updated"""
        self.getCell(cellelementref[0]).updateElement(cellelementref[1], cellelement)

    def getName(self):
        return self.name

    def getFileName(self):
        return self.filename

    def getNObjects(self):
        return self.nobjects

    ## Bounding box property
    def get_bboxrec(self):
        if self._bbox:
            return self._bbox.negY()
        else:
            return None
    def set_bboxrec(self, rec):
        if self.mode == 'r':
            raise ValueError("Can't change boundary rectangle in read-only mode")

        self.dbboxrec = rec.todiscrete(self._refpoint, self._scale)

        # If in append mode all cell elements must be re-added to fit the new
        # cell boundaries
        if self.mode == 'a':
            cellelements = [e for e in self.getCellElements()]
            
            self.clearCells()

            for e in cellelements:
                self.addCellElement(e)            

    bboxrec = property(get_bboxrec, set_bboxrec, "Bounding box rectangle")

    def get_dbboxrec(self):
        if self._dbbox:
            return self._dbbox.negY()
        else:
            return None

    def set_dbboxrec(self, drec):
        first_time = self._dbbox == None

        if not first_time and self.mode == 'r':
            raise ValueError("Can't change boundary rectangle in read-only mode")

        self._dbbox = drec.negY()
        self._bbox = self._dbbox.tocontinous(self._refpoint, self._scale)

        if self._dbbox.width % (2 ** self.nlevels) != 0 or self._dbbox.height % (2 ** self.nlevels) != 0:
            logging.warn("bbox should be a multiple of minimum cell size, adjusting bbox borders")
            n = 2 ** (self.nlevels + 1)
            width = self._dbbox.width
            height = self._dbbox.height

            width += -width % n
            height += -height % n
                
            self._dbbox = Rec(self._dbbox.c1, self._dbbox.c1 + N.array(width, height))

        # If in append mode all cell elements must be re-added to fit the new
        # cell boundaries
        if not first_time and self.mode == 'a':
            cellelements = [e for e in self.getCellElements()]
            
            self.clearCells()

            for e in cellelements:
                self.addCellElement(e)            

    dbboxrec = property(get_dbboxrec, set_dbboxrec, "Bounding box rectangle discrete coordinates")

    @property
    def refpoint(self): return self._refpoint
    @property
    def scale(self): return self._scale

    def getLayerType(self): return self.layertype    

    def calc_cell_extents(self, cellnum): 
        """
        Calculate discrete bounding box of a cell

        Note, the extents return is in the internal coordinates with negated Y-values
        """
        ## Calculate cell level
        level=0
        while cellnum > totcells_at_level(level):
           level=level+1

        n = 2**level             # Number of rows/cols 

        lbb = self._dbbox
        layerwidth = lbb.width
        layerheight = lbb.height
        layersize = N.array([layerwidth, layerheight])

        relcnum = cellnum - (totcells_at_level(level-1)+1)

        cellsize = layersize / n

        if relcnum < n*n:
            mincorner = N.array([relcnum % n, relcnum / n]) * cellsize
            maxcorner = mincorner + cellsize
        else:
            relcnum = relcnum-n*n
            mincorner = N.array([relcnum % (n + 1), relcnum / (n + 1)]) * cellsize - cellsize/2
            maxcorner = mincorner + layersize / n

            mincorner[N.where(mincorner < 0)] = 0
            maxcorner[0] = min(maxcorner[0], layerwidth)
            maxcorner[1] = min(maxcorner[1], layerheight)

        return Rec(mincorner + lbb.c1, maxcorner + lbb.c1)

    def layer_header_nok(self, pcnt):
        """Header check from magsendtool"""
	
 	pcnt /= 100.0
        rc = 0
        if abs(((self._bbox.maxY() -self._bbox.minY())/self._scale[1]) - (self._dbbox.maxY() - self._dbbox.minY())) > pcnt * (self._dbbox.maxY() - self._dbbox.minY()):
            rc |= 1
        if abs(((self._bbox.maxX() -self._bbox.minX())/self._scale[0]) - (self._dbbox.maxX() - self._dbbox.minX())) > pcnt * (self._dbbox.maxX() - self._dbbox.minX()):
            rc |= 2
        if self._refpoint[1] != 0.0 and abs(self._bbox.centerY() - self._refpoint[1])/self._scale[1] > 0.75:
            rc |= 4
        if self._refpoint[0] != 0.0 and abs(self._bbox.centerX() - self._refpoint[0])/self._scale[0] > 0.75:
            rc |= 8
 	return rc


    def check(self):
        version=1
        if self.layer_header_nok(0.1):
            version+=1
            if self.layer_header_nok(1.0):
                version+=1
                if self.layer_header_nok(5.0):
                    raise ValueError('Incorrect layer format rc=%d at 5%% error'%self.layer_header_nok(5.0))
        return version

    @property
    def ncells(self):
        """Return the number of cells in the layer"""
        return len(self.cellnumbers)

    @property
    def info(self):
        res = "Name: "+self.getName()+"\n"
        res += "Number of objects: "+str(self.getNObjects())+"\n"
        res += "Number of cells: "+str(len(self.cellnumbers))+"\n"
        res += "Reference point: "+str(self._refpoint)+"\n"
        res += "Scale: "+str(self._scale)+"\n"
        res += "Boundaries: "+str(self._bbox)+"\n"
        res += "Discrete Boundaries: "+str(self._dbbox)+"\n"
        if self.fileidentifier:
            res += "Identifier: 0x%x\n"%self.fileidentifier
        res += "# of levels: %d\n"%self.nlevels
        res += "category: %d\n"%self.category
        if self.layertype != None:
            res += "layertype: %d\n"%self.layertype
        res += 'reflat: %f\n'%self._refpoint[1]
        res += 'reflon: %f\n'%self._refpoint[0]
        if self.firstcell:
            res += 'first cell: %d\n'%self.firstcell
        if self.lastcell:
            res += 'last cell: %d\n'%self.lastcell
        return res


    def float2discrete(self, points):
        """Convert list of coordinates from floating point to discrete coordinates"""
        return ((N.array(points) - self.refpoint) / self.scale).round().astype(int)

    def discrete2float(self, points):
        """Convert list of coordinates from discrete to floating point coordinates"""
        return N.array(points) * self.scale + self.refpoint
    
    def __repr__(self):
        return self.__class__.__name__ + '(' + self.getName() + ')'