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

"""
Test the Layer class
"""

__version__ = "$Revision: 1.29.2.1 $"
# $Source: /thubanrepository/thuban/test/test_layer.py,v $
# $Id: test_layer.py,v 1.29.2.1 2004/06/03 15:29:16 bh Exp $

import os
import unittest

import mockgeo
import support
support.initthuban()

import shapelib
import dbflib

from Thuban.Model.session import Session
from Thuban.Model.layer import BaseLayer, Layer, RasterLayer
from Thuban.Model.data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT
from Thuban.Model.messages import LAYER_LEGEND_CHANGED, \
     LAYER_VISIBILITY_CHANGED, LAYER_SHAPESTORE_REPLACED, LAYER_CHANGED
from Thuban.Model.table import FIELDTYPE_DOUBLE, FIELDTYPE_STRING, MemoryTable
from Thuban.Model.proj import Projection
from Thuban.Model.data import DerivedShapeStore
from Thuban.Model.classification import Classification, ClassGroupSingleton, \
     ClassGroupRange

import Thuban.Model.resource

class TestLayer(unittest.TestCase, support.FileTestMixin,
                support.FloatComparisonMixin):

    """Test cases for different layer (shape) types"""

    def setUp(self):
        """Create a session self.session and initialize self.layer to None"""
        self.session = Session("Test session for %s" % self.__class__)
        self.layer = None

    def tearDown(self):
        """Call the layer's Destroy method and set session and layer to None"""
        self.session.Destroy()
        self.session = None
        if self.layer is not None:
            self.layer.Destroy()
            self.layer = None

    def build_path(self, filename):
        return os.path.join("..", "Data", "iceland", filename)

    def open_shapefile(self, filename):
        """Open and return a shapestore for filename in the iceland data set"""
        return self.session.OpenShapefile(self.build_path(filename))

    def test_base_layer(self):
        layer = self.layer = BaseLayer("Test BaseLayer")
        self.assertEquals(layer.Title(), "Test BaseLayer")
        self.failUnless(layer.Visible())

        # toggle visibility
        layer.SetVisible(False)
        self.failIf(layer.Visible())

        layer.SetVisible(True)
        self.failUnless(layer.Visible())

        self.failIf(layer.HasClassification())
        self.failIf(layer.HasShapes())

        self.assertEquals(layer.GetProjection(), None)

        # set/get projection
        proj = Projection(["proj=utm", "zone=26", "ellps=clrk66"])

        layer.SetProjection(proj)
        self.failUnless(layer.GetProjection() is proj)

        # __init__ with other arguments
        layer = BaseLayer("Test BaseLayer", False, proj)
        self.failIf(layer.Visible())
        self.failUnless(layer.GetProjection() is proj)

    def test_arc_layer(self):
        """Test Layer with arc shapes"""
        layer = self.layer = Layer("Test Layer",
                                   self.open_shapefile("roads-line.shp"))
        self.failUnless(layer.HasClassification())
        self.failUnless(layer.HasShapes())
        self.assertEquals(layer.ShapeType(), SHAPETYPE_ARC)
        self.assertEquals(layer.NumShapes(), 839)
        shape = layer.Shape(32)
        self.assertPointListEquals(shape.Points(),
                                   [[(-15.082174301147461, 66.27738189697265),
                                     (-15.026350021362305, 66.27339172363281)]])
        self.assertFloatSeqEqual(layer.BoundingBox(),
                                 [-24.450359344482422, 63.426830291748047,
                                  -13.55668830871582, 66.520111083984375])
        shapes = layer.ShapesInRegion((-24.0, 64.0, -23.75, 64.25))
        self.assertEquals([s.ShapeID() for s in shapes],
                          [613, 726, 838])

        self.assertFloatSeqEqual(layer.ShapesBoundingBox([32]), 
                          [-15.082174301147461, 66.27339172363281,
                           -15.026350021362305, 66.27738189697265])

        shape = layer.Shape(33)
        self.assertPointListEquals(shape.Points(),
                                   [[(-22.24850654602050, 66.30645751953125),
                                     (-22.23273086547851, 66.29407501220703),
                                     (-22.23158073425293,  66.2876892089843),
                                     (-22.24631881713867, 66.27006530761718)]])

        self.assertFloatSeqEqual(layer.ShapesBoundingBox([32, 33]), 
                                 [-22.248506546020508, 66.270065307617188,
                                  -15.026350021362305, 66.30645751953125])

        self.assertEquals(layer.ShapesBoundingBox([]), None)
        self.assertEquals(layer.ShapesBoundingBox(None), None)

    def test_polygon_layer(self):
        """Test Layer with polygon shapes"""
        layer = self.layer = Layer("Test Layer",
                                   self.open_shapefile("political.shp"))
        self.failUnless(layer.HasClassification())
        self.failUnless(layer.HasShapes())
        self.assertEquals(layer.ShapeType(), SHAPETYPE_POLYGON)
        self.assertEquals(layer.NumShapes(), 156)
        shape = layer.Shape(4)
        self.assertPointListEquals(shape.Points(),
                                   [[(-22.40639114379882, 64.714111328125),
                                     (-22.41621208190918, 64.7160034179687),
                                     (-22.40605163574218, 64.719200134277),
                                     (-22.40639114379882, 64.714111328125)]])
        self.assertFloatSeqEqual(layer.BoundingBox(),
                                 [-24.546524047851562, 63.286754608154297,
                                  -13.495815277099609, 66.563774108886719])
        shapes = layer.ShapesInRegion((-24.0, 64.0, -23.9, 64.1))
        self.assertEquals([s.ShapeID() for s in shapes],
                          [91, 92, 144, 146, 148, 150, 152, 153])

    def test_point_layer(self):
        """Test Layer with point shapes"""
        layer = self.layer = Layer("Test Layer",
                           self.open_shapefile("cultural_landmark-point.shp"))
        self.failUnless(layer.HasClassification())
        self.failUnless(layer.HasShapes())
        self.assertEquals(layer.ShapeType(), SHAPETYPE_POINT)
        self.assertEquals(layer.NumShapes(), 34)
        shape = layer.Shape(0)
        self.assertPointListEquals(shape.Points(),
                                   [[(-22.711074829101562, 66.36572265625)]])
        self.assertFloatSeqEqual(layer.BoundingBox(),
                                 [-23.806047439575195, 63.405960083007812,
                                  -15.12291431427002, 66.36572265625])
        shapes = layer.ShapesInRegion((-24.0, 64.0, -23.80, 64.1))
        self.assertEquals([s.ShapeID() for s in shapes],
                          [0, 1, 2, 3, 4, 5, 27, 28, 29, 30, 31])

    def test_arc_layer_with_projection(self):
        """Test Layer with point shapes and a projection"""
        # We use mock data here so that we have precise control over the
        # values
        table = MemoryTable([("FOO", FIELDTYPE_STRING)], [("bla",)])
        store = mockgeo.SimpleShapeStore(SHAPETYPE_ARC,
                              [[[(9884828.7209840547, 5607720.9774499247),
                                 (11298336.04640449, 9287823.2044059951)]]],
                                         table)
        layer = self.layer = Layer("Test Layer", store)

        proj = Projection(["proj=lcc", "lon_0=0", "lat_1=20n", "lat_2=60n",
                           "ellps=clrk66"])
        layer.SetProjection(proj)

        self.assertFloatSeqEqual(layer.BoundingBox(),
                                 (9884828.7209840547, 5607720.9774499247,
                                  11298336.04640449, 9287823.2044059951))
        self.assertFloatSeqEqual(layer.LatLongBoundingBox(),
                                 (90.0, -8.90043373, 120, 11.1616263))
        shapes = layer.ShapesInRegion((100, -10, 150, +10))
        self.assertEquals([s.ShapeID() for s in shapes], [0])
        self.assertFloatSeqEqual(layer.ShapesBoundingBox([0]),
                                 (90.0, -8.90043373, 120, 11.1616263))

    def test_empty_layer(self):
        """Test Layer with empty shape file"""
        # create an empty shape file
        shapefilename = self.temp_file_name("layer_empty.shp")
        shp = shapelib.create(shapefilename, shapelib.SHPT_POLYGON)
        shp.close()
        # create an empty DBF file too because Thuban can't cope yet
        # with missing DBF file.
        dbffilename = self.temp_file_name("layer_empty.dbf")
        dbf = dbflib.create(dbffilename)
        dbf.add_field("NAME", dbflib.FTString, 20, 0)
        dbf.close()

        # Now try to open it.
        layer = self.layer = Layer("Empty Layer",
                                   self.session.OpenShapefile(shapefilename))
        self.assertEquals(layer.BoundingBox(), None)
        self.assertEquals(layer.LatLongBoundingBox(), None)
        self.assertEquals(layer.NumShapes(), 0)

    def test_get_field_type(self):
        """Test Layer.GetFieldType()"""
        layer = self.layer = Layer("Test Layer",
                                   self.open_shapefile("roads-line.shp"))
        self.assertEquals(layer.GetFieldType("LENGTH"), FIELDTYPE_DOUBLE)
        self.assertEquals(layer.GetFieldType("non existing"), None)

    def test_raster_layer(self):
        if not Thuban.Model.resource.has_gdal_support():
            raise support.SkipTest("No gdal support")

        filename = self.build_path("island.tif")
        layer = RasterLayer("Test RasterLayer", filename)
        self.failIf(layer.HasClassification())
        self.failIf(layer.HasShapes())
        self.assertEquals(layer.GetImageFilename(), os.path.abspath(filename))
        self.assertFloatSeqEqual(layer.BoundingBox(),
                                 [-24.5500000, 63.2833330,
                                  -13.4916670, 66.5666670])
        self.assertFloatSeqEqual(layer.LatLongBoundingBox(),
                                 [-24.5500000, 63.2833330,
                                  -13.4916670, 66.5666670])

    def test_derived_store(self):
        """Test layer with derived store"""
        layer = self.layer = Layer("Test Layer",
                                   self.open_shapefile("roads-line.shp"))
        try:
            store = layer.ShapeStore()
            derived = DerivedShapeStore(store, store.Table())
            layer.SetShapeStore(derived)
            self.assert_(layer.ShapeStore() is derived)

            self.assertEquals(layer.ShapeType(), SHAPETYPE_ARC)
            self.assertEquals(layer.NumShapes(), 839)
            shape = layer.Shape(32)
            self.assertPointListEquals(shape.Points(),
                                       [[(-15.082174301147, 66.277381896972),
                                         (-15.026350021362, 66.273391723632)]])
            self.assertFloatSeqEqual(layer.BoundingBox(),
                                     [-24.450359344482422, 63.426830291748047,
                                      -13.55668830871582, 66.520111083984375])
            shapes = layer.ShapesInRegion((-24.0, 64.0, -23.75, 64.25))
            self.assertEquals([s.ShapeID() for s in shapes],
                              [613, 726, 838])

            self.assertFloatSeqEqual(layer.ShapesBoundingBox([32]),
                                     [-15.082174301147461, 66.27339172363281,
                                      -15.026350021362305, 66.27738189697265])

        finally:
            store = derived = None


class SetShapeStoreTests(unittest.TestCase, support.SubscriberMixin):

    def setUp(self):
        """Create a layer with a classification as self.layer"""
        self.clear_messages()
        self.session = Session("Test session for %s" % self.__class__)
        self.shapefilename = os.path.join("..", "Data", "iceland",
                                          "cultural_landmark-point.dbf")
        self.store = self.session.OpenShapefile(self.shapefilename)
        self.layer = Layer("test layer", self.store)
        self.classification = Classification()
        self.classification.AppendGroup(ClassGroupSingleton("FARM"))
        self.layer.SetClassificationColumn("CLPTLABEL")
        self.layer.SetClassification(self.classification)
        self.layer.UnsetModified()
        self.layer.Subscribe(LAYER_SHAPESTORE_REPLACED,
                             self.subscribe_with_params,
                             LAYER_SHAPESTORE_REPLACED)
        self.layer.Subscribe(LAYER_CHANGED,
                             self.subscribe_with_params, LAYER_CHANGED)

    def tearDown(self):
        self.clear_messages()
        self.layer.Destroy()
        self.session.Destroy()
        self.session = self.layer = self.store = self.classification = None

    def test_sanity(self):
        """SetShapeStoreTests sanity check

        Test the initial state of the test case instances after setUp.
        """
        cls = self.layer.GetClassification()
        self.assert_(cls is self.classification)
        field = self.layer.GetClassificationColumn()
        self.assertEquals(field, "CLPTLABEL")
        self.assertEquals(self.layer.GetFieldType(field), FIELDTYPE_STRING)
        self.assertEquals(self.layer.GetClassification().GetNumGroups(), 1)
        self.failIf(self.layer.WasModified())

    def test_set_shape_store_modified_flag(self):
        """Test whether Layer.SetShapeStore() sets the modified flag"""
        memtable = MemoryTable([("FOO", FIELDTYPE_STRING)],
                      [("bla",)] * self.layer.ShapeStore().Table().NumRows())
        self.layer.SetShapeStore(DerivedShapeStore(self.store, memtable))

        self.assert_(self.layer.WasModified())

    def test_set_shape_store_different_field_name(self):
        """Test Layer.SetShapeStore() with different column name"""
        memtable = MemoryTable([("FOO", FIELDTYPE_STRING)],
                      [("bla",)] * self.layer.ShapeStore().Table().NumRows())
        self.layer.SetShapeStore(DerivedShapeStore(self.store, memtable))
        # The classification should contain only the default group now.
        self.assertEquals(self.layer.GetClassification().GetNumGroups(), 0)
        self.check_messages([(self.layer, LAYER_CHANGED),
                             (self.layer, LAYER_SHAPESTORE_REPLACED)])

    def test_set_shape_store_same_field(self):
        """Test Layer.SetShapeStore() with same column name and type"""
        memtable = MemoryTable([("CLPTLABEL", FIELDTYPE_STRING)],
                      [("bla",)] * self.layer.ShapeStore().Table().NumRows())
        self.layer.SetShapeStore(DerivedShapeStore(self.store, memtable))
        # The classification should be the same as before
        self.assert_(self.layer.GetClassification() is self.classification)
        self.check_messages([(self.layer, LAYER_SHAPESTORE_REPLACED)])

    def test_set_shape_store_same_field_different_type(self):
        """Test Layer.SetShapeStore() with same column name but different type
        """
        memtable = MemoryTable([("CLPTLABEL", FIELDTYPE_DOUBLE)],
                      [(0.0,)] * self.layer.ShapeStore().Table().NumRows())
        self.layer.SetShapeStore(DerivedShapeStore(self.store, memtable))
        # The classification should contain only the default group now.
        self.assertEquals(self.layer.GetClassification().GetNumGroups(), 0)
        self.check_messages([(self.layer, LAYER_CHANGED),
                             (self.layer, LAYER_SHAPESTORE_REPLACED)])


class TestLayerModification(unittest.TestCase, support.SubscriberMixin):

    """Test cases for Layer method that modify the layer.
    """

    def setUp(self):
        """Clear the list of received messages and create a layer and a session

        The layer is bound to self.layer and the session to self.session.
        """
        self.clear_messages()
        self.session = Session("Test session for %s" % self.__class__)
        self.filename = os.path.join("..", "Data", "iceland", "political.shp")
        self.layer = Layer("Test Layer",
                           self.session.OpenShapefile(self.filename))
        self.layer.Subscribe(LAYER_LEGEND_CHANGED, self.subscribe_with_params,
                             LAYER_LEGEND_CHANGED)
        self.layer.Subscribe(LAYER_VISIBILITY_CHANGED,
                             self.subscribe_with_params,
                             LAYER_VISIBILITY_CHANGED)
        self.layer.Subscribe(LAYER_CHANGED, self.subscribe_with_params,
                             LAYER_CHANGED)

    def tearDown(self):
        """Clear the list of received messages and explictly destroy self.layer
        """
        self.layer.Destroy()
        self.layer = None
        self.session.Destroy()
        self.session = None
        self.clear_messages()

    def test_sanity(self):
        """TestLayerModification Sanity Checks"""
        # test default settings
        self.failIf(self.layer.WasModified())
        self.assertEquals(self.layer.Visible(), 1)
        # no messages should have been produced
        self.check_messages([])

    def test_visibility(self):
        """Test Layer visibility"""
        self.layer.SetVisible(0)
        self.assertEquals(self.layer.Visible(), 0)
        self.check_messages([(self.layer, LAYER_VISIBILITY_CHANGED)])

        # currently, modifying the visibility doesn't count as changing
        # the layer.
        self.failIf(self.layer.WasModified())

    def test_set_classification(self):
        """Test Layer.SetClassification"""
        classification = Classification()
        classification.AppendGroup(ClassGroupRange((0.0, 0.1)))

        self.layer.SetClassification(classification)
        self.layer.SetClassificationColumn("AREA")

        self.check_messages([(self.layer, LAYER_CHANGED),
                             (self.layer, LAYER_CHANGED)])
        self.failUnless(self.layer.WasModified())

        self.clear_messages()
        self.layer.UnsetModified()

        # change only the classification column. This should issue a
        # LAYER_CHANGED message as well.
        self.layer.SetClassificationColumn("PERIMETER")

        self.check_messages([(self.layer, LAYER_CHANGED)])
        self.failUnless(self.layer.WasModified())


    def test_tree_info(self):
        """Test Layer.TreeInfo"""
        self.assertEquals(self.layer.TreeInfo(),
                          ("Layer 'Test Layer'",
                           ['Filename: %s' % os.path.abspath(self.filename),
                            'Shown',
                            'Shapes: 156',
                   'Extent (lat-lon): (-24.5465, 63.2868, -13.4958, 66.5638)',
                            'Shapetype: Polygon',
                            self.layer.GetClassification()]))


if __name__ == "__main__":
    support.run_tests()
