# Copyright (C) 2003 by Intevation GmbH
# Authors:
# Bernhard Herzog <bh@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with the software for details.

"""Mock geometric/geographic objects"""

from __future__ import generators

__version__ = "$Revision: 1.2 $"
# $Source: /thubanrepository/thuban/test/mockgeo.py,v $
# $Id: mockgeo.py,v 1.2 2003/08/15 14:10:27 bh Exp $


class SimpleShape:

    def __init__(self, shapeid, points):
        self.shapeid = shapeid
        self.points = points

    def Points(self):
        return self.points

    def ShapeID(self):
        return self.shapeid

    def RawData(self):
        return self.points

    def compute_bbox(self):
        xs = []
        ys = []
        for part in self.Points():
            for x, y in part:
                xs.append(x)
                ys.append(y)
        return (min(xs), min(ys), max(xs), max(ys))


class SimpleShapeStore:

    """A simple shapestore object which holds its data in memory"""

    def __init__(self, shapetype, shapes, table):
        """Initialize the simple shapestore object.

        The shapetype should be one of the predefined SHAPETYPE_*
        constants. shapes is a list of shape definitions. Each
        definitions is a list of lists of tuples as returned by the
        Shape's Points() method. The table argument should be an object
        implementing the table interface and contain with one row for
        each shape.
        """
        self.shapetype = shapetype
        self.shapes = shapes
        self.table = table
        assert table.NumRows() == len(shapes)

    def ShapeType(self):
        return self.shapetype

    def Table(self):
        return self.table

    def NumShapes(self):
        return len(self.shapes)

    def Shape(self, index):
        return SimpleShape(index, self.shapes[index])

    def BoundingBox(self):
        xs = []
        ys = []
        for shape in self.shapes:
            for part in shape:
                for x, y in part:
                    xs.append(x)
                    ys.append(y)
        return (min(xs), min(ys), max(xs), max(ys))

    def ShapesInRegion(self, bbox):
        left, bottom, right, top = bbox
        if left > right:
            left, right = right, left
        if bottom > top:
            bottom, top = top, bottom
        for i in xrange(len(self.shapes)):
            shape = SimpleShape(i, self.shapes[i])
            sleft, sbottom, sright, stop = shape.compute_bbox()
            if (left <= sright and right >= sleft
                and top >= sbottom and bottom <= stop):
                yield shape

    def AllShapes(self):
        for i in xrange(len(self.shapes)):
            yield SimpleShape(i, self.shapes[i])


class AffineProjection:

    """Projection-like object implemented with an affine transformation

    The transformation matrix is defined by a list of six floats:

           [m11, m21, m12, m22, v1, v2]

    This list is essentially in the same form as used in PostScript.

    This interpreted as the following transformation of (x, y):

            / x \   / m11 m12 \ / x \   / v1 \
        T * |   | = |         | |   | + |    |
            \ y /   \ m21 m22 / \ y /   \ v2 /

    or, in homogeneous coordinates:

                     / m11 m12 v1 \ / x \
                     |            | |   |
                  ^= | m21 m22 v2 | | y |
                     |            | |   |
                     \ 0   0   1  / \ 1 /

    Obviously this is not a real geographic projection, but it's useful
    in test cases because it's simple and the result is easily computed
    in advance.
    """

    def __init__(self, coeff):
        self.coeff = coeff

        # determine the inverse transformation right away. We trust that
        # an inverse exist for the transformations used in the Thuban
        # test suite.
        m11, m21, m12, m22, v1, v2 = coeff
        det = float(m11 * m22 - m12 * m21)
        n11 = m22 / det
        n12 = -m12 / det
        n21 = -m21 / det
        n22 = m11 / det
        self.inv = [n11, n21, n12, n22, -n11*v1 - n12*v2, -n21*v1 - n22*v2]

    def _apply(self, matrix, x, y):
        m11, m21, m12, m22, v1, v2 = matrix
        return (m11 * x + m12 * y + v1), (m21 * x + m22 * y + v2)

    def Forward(self, x, y):
        return self._apply(self.coeff, x, y)

    def Inverse(self, x, y):
        return self._apply(self.inv, x, y)
