コード例 #1
0
    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)
コード例 #2
0
    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)
コード例 #3
0
ファイル: Layer.py プロジェクト: codingforfun/pymagellan
    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)
コード例 #4
0
    def calculateDBBox(self):
        """Calculate estimated bounding box of layer in discrete coordinates"""
        if N.alltrue(self.dbboxmin == self.layer.float2discrete((
                N.array([180.0, 90.0]), ))):
            return None

        if N.all(self.dbboxmin != self.dbboxmax):
            dbbox = Rec(self.dbboxmin, self.dbboxmax)
        else:
            # Magellan Software cannot handle zero-area bounding boxes
            dbbox = Rec(self.dbboxmin, self.dbboxmax + N.array([1, 1]))

        if self.verbose:
            print "Estimated discretebbox", dbbox

        return dbbox
コード例 #5
0
ファイル: Layer.py プロジェクト: codingforfun/pymagellan
    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)            
コード例 #6
0
    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)
コード例 #7
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() + ')'
コード例 #8
0
def get_best_cell(bounds, r, max_cell_level):
    max_cell_level += 1  # To take account of the "shifted" level
    i = 1 << max_cell_level

    # Normalise rectangle's coordinates to within 0 to i-1
    v = (bounds.s - bounds.n) / i  # vertical normalisation factor
    h = (bounds.e - bounds.w) / i  # horizontal normalisation factor
    n = (r.n - bounds.n) / v
    s = (r.s - bounds.n) / v
    w = (r.w - bounds.w) / h
    e = (r.e - bounds.w) / h

    # Find the best "direct" cell (without half cell shifting)
    m = (n ^ s) | (w ^ e)
    # The most significant 1 (as a bit) in m indicates cell level
    # Now compute cell number
    l = 1 << (max_cell_level - 1)
    cell = 1
    shift = max_cell_level
    stride = 1
    i = -3  # compensation for the lack of Level 0' (shifted)
    while l > 1 and (l & m) == 0:
        cell = i + stride * stride + (stride + 1)**2
        i = cell
        stride <<= 1
        l >>= 1
        shift -= 1

    # Now l equals 1 << (level - 1) for unshifted cells
    # Compute the number of the best unshifted cell
    best_cell = cell + (n >> shift) * stride + (w >> shift)
    cell_size = 1 << shift
    cell_offset = 0
    cell_level = max_cell_level - shift

    # Check if a shifted cell of the same or more detailed level
    # can be used
    if cell == 1:  # Skip Level 0'
        l >>= 1
        shift -= 1
        stride <<= 1
        cell = 2

    while l > 0:
        cell += stride * stride
        m = ((n + l) ^ (s + l)) | ((w + l) ^ (e + l))
        if m < 2 * l:  # found a better cell
            best_cell = cell + ((n + l) >> shift) * (stride + 1) \
                             + ((w + l) >> shift)
            cell_size = 1 << shift
            cell_offset = l
            cell_level = max_cell_level - shift
        l >>= 1
        cell += (stride + 1)**2
        stride <<= 1
        shift -= 1

    # layer's bounding rectangle
    normn = ((n + cell_offset) & -cell_size) - cell_offset
    normw = ((w + cell_offset) & -cell_size) - cell_offset

    n = max(normn * v, 0)
    w = max(normw * h, 0)
    s = min((normn + cell_size) * v, bounds.s - bounds.n) - 1
    e = min((normw + cell_size) * h, bounds.e - bounds.w) - 1
    border = Rec((s, w), (n, e))

    # Return cell number, level, and border
    return int(best_cell), cell_level, border
コード例 #9
0
ファイル: Map.py プロジェクト: codingforfun/pymagellan
    def __init__(self, mapdirobj = None, gpsimage = True, maptype = MapTypeNormal,
                 mapnumber=0, bigendian = False, inifile = None, hasmultiplemaps=True):
        self.gpsimage = gpsimage
        self.maptype = maptype
        self._mapnumber = mapnumber

        if mapdirobj == None:
            self.mapdir = mapdir.MapDirectory()
        else:
            if not isinstance(mapdirobj, mapdir.MapDirectory):
                raise ValueError("mapdirobj should be a MapDirectory object")
            self.mapdir = mapdirobj

        self.name = "Map"

        self.mode = None      # Open mode ['r','w','a']

        self.debug=False

        self.has_zip = True

        self.has_marine = (maptype == MapTypeStreetRoute)

        self._inifile = inifile

        self.inmemory = False ## If true all processing will be done in memory

        if maptype == MapTypeStreetRoute:
            self.routingcfg = routing.RoutingConfig()
        else:
            self.routingcfg = None

        ## Set endian
        self.bigendian = bigendian

        # Groups
        self._poigroup=None
        self.groups=[]
        self._searchgroups = [] ## Searchable group indices

        # Tables
        if maptype == MapTypeStreetRoute:
            self._ziptables = (0,1,0,0)
        else:
            self._ziptables = (0,0)
        self._marinetables = (0,1,0,1)

        ## Bounding box and bounding rect
        self._bboxrec = None
        self._boundrec = Rec((-198.316818, -99.475197), (198.316818,  99.475197))

        ## Resolution and reference point
        self._scale = self.defaultscale
        self._refpoint = N.array([0.0, 0.0])

        ## Config file
        self._cfg = ConfigParserUpper()
        self._initcfg()

        ## Layer and POI config
        self._laycfg = LayerConfig()
        self._poiconfig = POILayerConfig()
        
        ## Database
        self._db = None

        ## Other parameters
        otherparameters = (
            ## Name, default value, description
            ('startscale', 4500.0, 'Start scale'),
            ('laycolor', 0, 'Unknown'),
            ('copyrightholders', ['Unknown'], 'Copyright holders'),
            )

        ## Create properties for these parameters
        for param in otherparameters:
            setattr(self, '_' + param[0], param[1])
            getfunc = lambda self, val: self.set_parameter('_'+param[0])
            setfunc = lambda self, val: self.set_parameter('_'+param[0], val)
            setattr(self.__class__, param[0], property(getfunc, setfunc, doc=param[2]))

        ## Update config
        self._updatecfg()

        ## hasmultiplemaps is True if mapdir can have multiple maps with prefixes before their files
        self.hasmultiplemaps = hasmultiplemaps
コード例 #10
0
ファイル: Map.py プロジェクト: codingforfun/pymagellan
    def open(self, mode='r'):
        if self.mode != None:
            return None

        self.mode = mode

        if mode == 'w':
            if self.gpsimage:
                if self._inifile == None:
                    self._inifile = self.mapnumstr + 'map.ini'

                ## Create database
                self._db = Database(self.mapdir, 'db' + self.mapnumstr, mode, self.bigendian)

                ## Create zip table
                if self.has_zip:
                    buildziprecord(self._db,
                                   zipfilename = self.mapnumstr + 'z.dat',
                                   auxfilename = self.mapnumstr + 'cn.dat',
                                   extended = self.maptype == MapTypeStreetRoute)

                ## Create marine table
                if self.has_marine:
                    buildmarinerecord(self._db,
                                   filenameprefix = self.mapnumstr)

                ## Create basic groups
                if self.maptype == MapTypeStreetRoute:
                    roads = GroupStreet(self, name=self.mapnumstr + "_Roads")
                else:
                    roads = GroupNormal(self, name=self.mapnumstr + "_Roads")
                roads.searchable = True
                self.addGroup(roads)
                self.addGroup(GroupNormal(self, name=self.mapnumstr + "_Railroads"))
                self.addGroup(GroupNormal(self, name=self.mapnumstr + "_Hydrography"))
                self.addGroup(GroupNormal(self, name=self.mapnumstr + "_Parks"))

            else:
                if self._inifile == None:
                    self._inifile = 'map.ini'
        elif mode in ('r','a'):
            ## Find ini file
            if self._inifile == None:
                filelist = self.mapdir.listdir()
                possibleinifiles = ('map.ini', self.mapnumstr + 'map.ini', 'lay_info.ini')
                found = False
                for filename in possibleinifiles:
                    if filename in filelist:
                        self._inifile = filename
                        found = True
                        break
                if not found:
                    raise ValueError('Could not find ini-file')

            self._cfg.readfp(IniFileFilter(self.mapdir.open(self._inifile)))

            # Get map type
            if self._cfg.has_option('MAP_INFO', 'MAPTYPE'):
                self.maptype = self._cfg.get("MAP_INFO", "MAPTYPE")

            # Get bounding box
            if self._cfg.has_option('MAP_INFO', 'BND_BOX'):
                bbox = map(float, self._cfg.get("MAP_INFO", "BND_BOX").split(" "))
                self._bboxrec = Rec([bbox[0],bbox[2]], [bbox[1], bbox[3]])

            # Get map name
            self.name = self._cfg.get('MAP_INFO', 'MAP_NAME')
#            self.date = self._cfg.get('MAP_INFO', 'MAP_DATE')

            self._laycfg.setupfromcfg(self._cfg, self)
            
            # Find database path
            dbname = self._cfg.get("LAYERS","DB_NAME")
            dbname = re.sub('\\\\', os.sep, dbname)

            # Since Windows is case insensitive, try to match the path case insensitive
            plist=dbname.split(os.sep)
            for i in range(0,len(plist)-2):
                path=os.sep.join(plist[0:i+1])
                if len(path)>0:
                    hits=[d for d in self.mapdir.listdir(path) if d.lower() == plist[i+1].lower()]
                    if len(hits)==0:
                        raise ValueError("Couldnt find database")
                    plist[i+1]=hits[0]
            dbname = os.sep.join(plist)

            if dbname:
                self._db = Database(self.mapdir, dbname, self.mode, self.bigendian)

            # Read groups
            if self.debug:
                print "Groups:"
            for i in range(0,self._cfg.getint("GROUPS","NUMBER")):
                thegroup = groupFactory(self, i, self._cfg, self._db)
                self.groups.append(thegroup)
                thegroup.initFromIni(self._cfg)

            # Read POI file and POI layers
            if self._cfg.has_section("POI"):
                poi_ini = self._cfg.get("POI","POI_CONFIG")
                self.poicfg = ConfigParserUpper()
                if self.mapdir.isfile(poi_ini):
                    self.poicfg.readfp(IniFileFilter(self.mapdir.open(poi_ini)))

                    if self.poicfg.has_section("LAYERS"):
                        self._poigroup = POIGroup(self)

                    self._poiconfig.setupfromcfg(self.poicfg, self)

            # Read routing info
            if self.maptype == MapTypeStreetRoute:
                self.routingcfg = routing.RoutingConfig()
                self.routingcfg.setupfromcfg(self._cfg, self)

            # Read unpack tables
            if self._cfg.has_section('PACK_LAYS'):
                i = 0

                while self._cfg.has_option('PACK_LAYS', str(i)):
                    fields = self._cfg.get('PACK_LAYS', str(i)).split(' ')

                    filename = fields[0]
                    n = int(fields[1])

                    layernumbers = map(int, fields[2:])

                    assert len(layernumbers) == n

                    for layernumber in layernumbers:
                        self.getLayerByIndex(layernumber).setUnpackTable(filename)
                    
                    i += 1
コード例 #11
0
ファイル: Map.py プロジェクト: codingforfun/pymagellan
 def set_bbox(self, points):
     self.bboxrec = Rec(tuple(points[0]), tuple(points[1]))
コード例 #12
0
ファイル: Layer.py プロジェクト: codingforfun/pymagellan
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() + ')'