From nhueffme at intevation.de Tue Mar 1 17:15:02 2005 From: nhueffme at intevation.de (Nina =?iso-8859-1?q?H=FCffmeyer?=) Date: Tue, 1 Mar 2005 17:15:02 +0100 Subject: OGR bindings Message-ID: <200503011715.02517.nhueffme@intevation.de> Hi, as you might know I am implementing ogr support for Thuban. I just wanted to let you know my state of progress: Recently, I didn't check in anything new because my work needed some changes of the Thuban core. These changes are done so far, but do need some redesigning. This affects especially the session saving and loading which should be more flexible and not depending on the availability of special extensions. Therefore, a new concept for extensions is required. The attached file gives you some more information on saving and loading of .thuban files and what changes would be required for extensions. Regards, Nina -------------- next part -------------- Task/Problem description: The new extension for OGR allows to manage additional file formats of shape resources. Thuban .thuban files should handle this generically and not require OGR for reading / storing such corresponding layers. A solution is sought to have Thuban react flexible on incoming .thuban shapestore elements and call appropriate extensions if available or ignore otherwise. Rephrased: The goal is to provide a possibility to access extension capabilities without calling methods explicitly or refering directly to a special extension. Background information: Binding the ogr library to Thuban allows a lot of new file formats to be read. If you now want to save the work with these new formats to sessions, Thuban needs to know how to treat these file formats while saving and loading. E.g. if you want to save a session containing a .dgn file, you need the filename and the layername to open the right layer later on whereas shapefiles loaded without ogr just need a filename. Or, if the ogr extension is not available and a session containing a .dgn file is tried to be opened, another extension could be able to read .dgn files. Thuban should search for a possibility to open the file instead of asking a special extension. It seems that any file related shape source can be described with filename and layername (some storage formats include a number of layers in one file). Database layers additonally require a id-column. Approach: Providing access to capabilities requires that extensions know their capabilities. Therefore each extension should have a list of capabilities like e.g. ["Open Shapestore/DGN", "Open Shapestore/Shapefile", "Open Shapestore/PostGIS"]. If then opening a .dgn file is required, Thuban simply searches for all extensions able to open such a file and then choose one of the extensions. The extensions should then have standard methods which allow for example to load a shapestore (e.g. LoadFileShapestore() which gets the filename and the layername). Implementation: Such a solution would need some changes in the following classes: save.py: Saving of a fileshapesource would have to be generalized in such a way, that all the information required for any file shape stores would be given. The required information is: - filetype - filename - layername - id (Thuban internal) - (id_column: depending on PostGIS) As ogrshapestores and shapefilestores should have to be treated the same way (-> calling isinstance() should be true for both kind of stores), maybe a base class should be defined and shapefilestore and ogrshapestore should inherit from this class. Example for .thuban: Currently shapefile stores are described this way: A DGN file could look like this: Also the shapefiles would then look like this: MapInfo is a format where different layernames could occur: For dbshapesources the current elements dbconnection and dbshapesource in a .thuban file are sufficient to search for postgis-readers and to execute them :-) load.py: Opening a session would identify the filetype by "filetype" given in the .thuban file. Thuban could then search for an extension able to read the filetype and then call an reader method of the extension. If an extension had the capability "Open Shapestore/DGN" it would have to have a reader method like "OpenShapestore" to assure that no exception occurs. extension.py/extensionregistry.py: the list of capabilities would have to be added to the extensions. This could be implemented in the Extension class (extension.py) or - as every extension should have an ExtensionDescription object - in the ExtensionDescription class (extensionregistry.py) which would be easy to access. data.py: base class for ogrshapestore and shapefilestore In order to keep the naming of capabilities and depending methods unique (e.g. "Open Shapestore/DGN" assures the existance of a method OpenShapestore() ) maybe a global list of available capabilities could be defined. From bernhard at intevation.de Tue Mar 1 20:14:14 2005 From: bernhard at intevation.de (Bernhard Reiter) Date: Tue, 1 Mar 2005 20:14:14 +0100 Subject: Finishing the extension registry feature In-Reply-To: <20050224224028.GA17919@intevation.de> References: <20050217112539.GA26955@intevation.de> <20050218171730.GD16375@intevation.de> <20050224224028.GA17919@intevation.de> Message-ID: <20050301191414.GH23103@intevation.de> Am 24. Feb 2005 um 23:40:28 schrieb Jan-Oliver Wagner: > On Fri, Feb 18, 2005 at 06:17:30PM +0100, Bernhard Reiter wrote: > > We could also change both to use a magic function in the package. > > Doing this for registration first and then later for tests. > > > > I tend to favour the magic function solution a little bit over the > > other, as we can later invent new magic functions for wx-dependent > > test or what ever without changing names all the time. > > here is a improved approach. Less magic names and more > automization via callbacks. > > >From there is it only small step to fully automize the > loading of extensions in the Extensions directory :-) > > Patch OK to commit? Why not? Put it in ... :) > + def init_extensions(self): > + """Call initialization callbacks for allregistered extensions.""" Typo ^^^^ -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 189 bytes Desc: not available Url : http://www.intevation.de/pipermail/thuban-devel/attachments/20050301/ea139a2c/attachment.bin From jan at intevation.de Thu Mar 3 09:14:34 2005 From: jan at intevation.de (Jan-Oliver Wagner) Date: Thu, 3 Mar 2005 09:14:34 +0100 Subject: Interface? Was: Base class for Shapes? In-Reply-To: References: <20041124214926.GA30763@intevation.de> <20041126165520.GB3139@intevation.de> Message-ID: <20050303081434.GA24586@intevation.de> Hello Bernhard, On Fri, Nov 26, 2004 at 07:31:22PM +0100, Bernhard Herzog wrote: > class IShape(Interface): What Interface class are you using here? At a quick search I only found the proposal of Gon?alo Rodrigues: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/164901 Jan -- Jan-Oliver Wagner http://intevation.de/~jan/ Intevation GmbH http://intevation.de/ FreeGIS http://freegis.org/ From thuban-bugs at intevation.de Thu Mar 3 23:59:18 2005 From: thuban-bugs at intevation.de (Request Tracker) Date: Thu, 3 Mar 2005 23:59:18 +0100 (CET) Subject: [bug #3068] (thuban) connetion with postgis database Message-ID: <20050303225918.0227F1005DB@lists.intevation.de> this bug's URL: http://intevation.de/rt/webrt?serial_num=3068 ------------------------------------------------------------------------- Subject: connetion with postgis database Operating System: GNU/Linux, conectiva 10 Thuban version: 1.0.1, Thuban-1.0.1.tar.gz wxPython version: 2.4.2.4, conecitva rpm Python version: other, 2.3.3 conectiva rpm PySQLite version: 0.5.0, conecitva rpm SQLite version: other, 2.8.13 conectiva rpm GDAL version: 1.2.5, compiled proj version: other, 4.4.9 compiles Please enter error description here. Did you compile Thuban yourself? yes ---------- trying to edit properties: ERROR: current transaction is aborted, commands ignored until end of transaction block SELECT f_table_name FROM geometry_columns; Traceback (most recent call last): File "/usr/local//lib/thuban/Thuban/UI/dbdialog.py", line 100, in OnRetrieve self.tables = self.selected_conn.GeometryTables() File "/usr/local//lib/thuban/Thuban/Model/postgisdb.py", line 139, in GeometryTables cursor.execute("SELECT f_table_name FROM geometry_columns;") ProgrammingError: ERROR: current transaction is aborted, commands ignored until end of transaction block SELECT f_table_name FROM geometry_columns; ----------- trying to ope table: Uma exceção não prevista ocorreu: ERROR: current transaction is aborted, commands ignored until end of transaction block SELECT count(*) FROM "geosetores" Traceback (most recent call last): File "/usr/local//lib/thuban/Thuban/UI/legend.py", line 215, in _OnShowTable self.mainWindow.LayerShowTable() File "/usr/local//lib/thuban/Thuban/UI/mainwindow.py", line 639, in LayerShowTable layer, table) File "/usr/local//lib/thuban/Thuban/UI/tableview.py", line 579, in __init__ QueryTableFrame.__init__(self, parent, name, title, table) File "/usr/local//lib/thuban/Thuban/UI/tableview.py", line 365, in __init__ TableFrame.__init__(self, parent, name, title, table) File "/usr/local//lib/thuban/Thuban/UI/tableview.py", line 312, in __init__ self.grid = self.make_grid(self.table) File "/usr/local//lib/thuban/Thuban/UI/tableview.py", line 608, in make_grid return LayerTableGrid(self, table) File "/usr/local//lib/thuban/Thuban/UI/tableview.py", line 165, in __init__ self.table = DataTable(table) File "/usr/local//lib/thuban/Thuban/UI/tableview.py", line 44, in __init__ self.SetTable(table) File "/usr/local//lib/thuban/Thuban/UI/tableview.py", line 49, in SetTable self.num_rows = table.NumRows() File "/usr/local//lib/thuban/Thuban/Model/postgisdb.py", line 269, in NumRows cursor.execute("SELECT count(*) FROM %s" % self.quoted_tablename) ProgrammingError: ERROR: current transaction is aborted, commands ignored until end of transaction block SELECT count(*) FROM "geosetores" ------------- closing thuban returns: LayerTableFrame instance has no attribute 'map' Traceback (most recent call last): File "/usr/local//lib/thuban/Thuban/UI/tableview.py", line 602, in OnDestroy self.map.Unsubscribe(MAP_LAYERS_REMOVED, self.map_layers_removed) AttributeError: LayerTableFrame instance has no attribute 'map' -------------------------------------------- Managed by Request Tracker From cvs at intevation.de Fri Mar 4 16:08:01 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Fri, 4 Mar 2005 16:08:01 +0100 (CET) Subject: nhueffme: thuban ChangeLog,1.791,1.792 Message-ID: <20050304150801.829E31006A6@lists.intevation.de> Author: nhueffme Update of /thubanrepository/thuban In directory doto:/tmp/cvs-serv30928 Modified Files: ChangeLog Log Message: Latest changes on OGR extension. Added another menu item to open OGR datasource, handling of geometry collections possible. Index: ChangeLog =================================================================== RCS file: /thubanrepository/thuban/ChangeLog,v retrieving revision 1.791 retrieving revision 1.792 diff -u -d -r1.791 -r1.792 --- ChangeLog 22 Feb 2005 11:10:00 -0000 1.791 +++ ChangeLog 4 Mar 2005 15:07:59 -0000 1.792 @@ -1,3 +1,19 @@ +2005-03-04 Nina Hüffmeyer + + * Extensions/ogr/ogrdialog.py: Added a dialog, which asks for + OGRConnection to open a datasource. Removed dialog to display all + available drivers. Added some doc strings. + + * Extensions/ogr/ogrstart.py: Added menu entry for opening an + OGRDatasource with a string. Added two opening methods which return an + OGRDatasource (either data from file or from DB). + + * Extensions/ogr/ogrshapes.py: Added class OGRGeometry, which + represents a geometry reference of an OGRFeature. OGRShape now has a + list of referenced geometry objects (important for geometry + collections). + For OGRShapeStores loaded from a DB an ID column can be specified now. + 2005-02-22 Jan-Oliver Wagner * test/test_map.py (TestMapWithContents.test_tree_info): From cvs at intevation.de Fri Mar 4 16:08:01 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Fri, 4 Mar 2005 16:08:01 +0100 (CET) Subject: nhueffme: thuban/Extensions/ogr/test test_OGRShapestore.py, 1.5, 1.6 Message-ID: <20050304150801.880331006A8@lists.intevation.de> Author: nhueffme Update of /thubanrepository/thuban/Extensions/ogr/test In directory doto:/tmp/cvs-serv30928/Extensions/ogr/test Modified Files: test_OGRShapestore.py Log Message: Latest changes on OGR extension. Added another menu item to open OGR datasource, handling of geometry collections possible. Index: test_OGRShapestore.py =================================================================== RCS file: /thubanrepository/thuban/Extensions/ogr/test/test_OGRShapestore.py,v retrieving revision 1.5 retrieving revision 1.6 diff -u -d -r1.5 -r1.6 --- test_OGRShapestore.py 8 Feb 2005 09:52:56 -0000 1.5 +++ test_OGRShapestore.py 4 Mar 2005 15:07:59 -0000 1.6 @@ -35,7 +35,7 @@ support.initthuban() from Extensions.ogr.ogrshapes import OGRShapeStore, OGRShape, \ - SHAPETYPE_ARC + SHAPETYPE_POINT, SHAPETYPE_ARC, SHAPETYPE_POLYGON from Thuban.Model.data import RAW_PYTHON from Thuban.Model.table import FIELDTYPE_INT from Thuban.Model.session import Session @@ -45,75 +45,136 @@ if ogr is None: raise support.SkipTest("Can't run ogr tests because ogr couldn't be imported.") -class TestOGRShapeStore(unittest.TestCase, support.FloatComparisonMixin): +class TestOGRShapeStore_shp(unittest.TestCase, support.FloatComparisonMixin): def setUp(self): - """Initialize self.session and open shapefile, if ogr could be + """Initialize self.session and open shapefile, if ogr could be imported. """ skip_if_no_ogr() - self.filename = os.path.join("..", "Data", "iceland","roads-line.shp") - self.store = OGRShapeStore(self.filename, "roads-line") + self.session = Session("TestSession") + self.filename_arc = os.path.join("..", "Data", "iceland", + "roads-line.shp") + self.store_arc = OGRShapeStore(self.session, self.filename_arc, + "roads-line") + + self.filename_point = os.path.join("..", "Data", "iceland", + "cultural_landmark-point.shp") + self.store_point = OGRShapeStore(self.session, self.filename_point, + "cultural_landmark-point") + + self.filename_poly = os.path.join("..", "Data", "iceland", + "political.shp") + self.store_poly = OGRShapeStore(self.session, self.filename_poly, + "political") def tearDown(self): """Call self.session.Destroy() and reset self.session to None""" - self.store = None + self.session = None + self.store_arc = None + self.store_point = None + self.store_poly = None def test_shape_type(self): """Test OGRShapeStore.ShapeType() with arc shapes""" - self.assertEquals(self.store.ShapeType(), SHAPETYPE_ARC) + self.assertEquals(self.store_arc.ShapeType(), SHAPETYPE_ARC) + self.assertEquals(self.store_point.ShapeType(), SHAPETYPE_POINT) + self.assertEquals(self.store_poly.ShapeType(), SHAPETYPE_POLYGON) def test_raw_format(self): """Test OGRShapeStore.RawShapeFormat() with ogr file""" - self.assertEquals(self.store.RawShapeFormat(), RAW_PYTHON) + self.assertEquals(self.store_arc.RawShapeFormat(), RAW_PYTHON) + self.assertEquals(self.store_point.RawShapeFormat(), RAW_PYTHON) + self.assertEquals(self.store_poly.RawShapeFormat(), RAW_PYTHON) def test_boundingbox(self): """Test OGRShapeStore.BoundingBox() with arc shapes""" - self.assertFloatSeqEqual(self.store.BoundingBox(), + self.assertFloatSeqEqual(self.store_arc.BoundingBox(), [-24.450359344482422, 63.426830291748047, -13.55668830871582, 66.520111083984375]) + self.assertFloatSeqEqual(self.store_point.BoundingBox(), + [-23.806047439575195, 63.405960083007812, + -15.12291431427002, 66.36572265625]) + self.assertFloatSeqEqual(self.store_poly.BoundingBox(), + [-24.546524047851562, 63.286754608154297, + -13.495815277099609, 66.563774108886719]) def test_num_shapes(self): """Test OGRShapeStore.NumShapes() with arc shapes""" - self.assertEquals(self.store.NumShapes(), 839) + self.assertEquals(self.store_arc.NumShapes(), 839) + self.assertEquals(self.store_point.NumShapes(), 34) + self.assertEquals(self.store_poly.NumShapes(), 156) def test_shapes_in_region(self): """Test OGRShapeStore.ShapesInRegion() with arc shapes""" - shapes = self.store.ShapesInRegion((-22.78, 63.96, -22.72, 64.0)) - self.assertEquals([s.ShapeID() for s in shapes], [769, 771]) + shapes_arc = self.store_arc.ShapesInRegion((-22.78, 63.96, + -22.72, 64.0)) + self.assertEquals([s.ShapeID() for s in shapes_arc], [769, 771]) + + shapes_point = self.store_point.ShapesInRegion((-23.0, 63.0, + -22.0, 64.0)) + self.assertEquals([s.ShapeID() for s in shapes_point], [29, 31]) + + shapes_poly = self.store_poly.ShapesInRegion((-23.0, 63.0, -22.0, 64.0)) + self.assertEquals([s.ShapeID() for s in shapes_poly], [144]) def test_all_shapes(self): """Test OGRShapeStore.AllShapes()""" - self.assertEquals([s.ShapeID() for s in self.store.AllShapes()], - range(self.store.NumShapes())) + self.assertEquals([s.ShapeID() for s in self.store_arc.AllShapes()], + range(self.store_arc.NumShapes())) + self.assertEquals([s.ShapeID() for s in self.store_point.AllShapes()], + range(self.store_point.NumShapes())) + self.assertEquals([s.ShapeID() for s in self.store_poly.AllShapes()], + range(self.store_poly.NumShapes())) def test_shape_Points(self): """Test OGRShapeStore.Shape() with arc shapes""" - self.assertPointListEquals(self.store.Shape(32).Points(), + self.assertPointListEquals(self.store_arc.Shape(32).Points(), [[(-15.08217430114746, 66.2773818969726), (-15.02635002136230, 66.2733917236328)]]) + self.assertPointListEquals(self.store_point.Shape(13).Points(), + [[(-15.364621162414551, 65.6103515625)]]) + self.assertPointListEquals(self.store_poly.Shape(20).Points(), + [[(-22.233562469482422, 64.4395751953125), + (-22.244234085083008, 64.441192626953125), + (-22.233894348144531, 64.446701049804688), + (-22.226711273193359, 64.44439697265625), + (-22.23356246948242, 64.4395751953125)]]) def test_shape_shapeid(self): """Test OGRShapeStore.Shape(i).ShapeID()""" - self.assertEquals(self.store.Shape(5).ShapeID(), 5) + self.assertEquals(self.store_arc.Shape(5).ShapeID(), 5) + self.assertEquals(self.store_point.Shape(13).ShapeID(), 13) + self.assertEquals(self.store_point.Shape(20).ShapeID(), 20) def test_shape_compute_bbox(self): """Test bbox of one shape""" - self.assertFloatSeqEqual(self.store.Shape(32).compute_bbox(), + self.assertFloatSeqEqual(self.store_arc.Shape(32).compute_bbox(), (-15.08217430114746, 66.2733917236328, -15.02635002136230, 66.2773818969726)) + self.assertFloatSeqEqual(self.store_point.Shape(13).compute_bbox(), + (-15.364621162414551, 65.6103515625, + -15.364621162414551, 65.6103515625)) + self.assertFloatSeqEqual(self.store_poly.Shape(20).compute_bbox(), + (-22.244234085083008, 64.4395751953125, + -22.226711273193359, 64.446701049804688)) class TestOGRTable(unittest.TestCase, support.FloatComparisonMixin): def setUp(self): """Initialize""" + self.session = Session("TestSession") self.filename = os.path.join("..","Data", "iceland","roads-line.shp") - self.store = OGRShapeStore(self.filename, "roads-line") - self.table = self.store.Table() + self.store_arc = OGRShapeStore(self.session, self.filename, + "roads-line") + self.table = self.store_arc.Table() def tearDown(self): """Call self.session.Destroy() and reset self.session to None""" + self.session = None + self.store_arc = None + self.table = None def test_Dependencies(self): """Test dependencies, which is always ()""" From cvs at intevation.de Fri Mar 4 16:08:01 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Fri, 4 Mar 2005 16:08:01 +0100 (CET) Subject: nhueffme: thuban/Extensions/ogr ogrdialog.py, 1.2, 1.3 ogrshapes.py, 1.5, 1.6 ogrstart.py, 1.4, 1.5 Message-ID: <20050304150801.795231006A5@lists.intevation.de> Author: nhueffme Update of /thubanrepository/thuban/Extensions/ogr In directory doto:/tmp/cvs-serv30928/Extensions/ogr Modified Files: ogrdialog.py ogrshapes.py ogrstart.py Log Message: Latest changes on OGR extension. Added another menu item to open OGR datasource, handling of geometry collections possible. Index: ogrdialog.py =================================================================== RCS file: /thubanrepository/thuban/Extensions/ogr/ogrdialog.py,v retrieving revision 1.2 retrieving revision 1.3 diff -u -d -r1.2 -r1.3 --- ogrdialog.py 8 Feb 2005 09:52:56 -0000 1.2 +++ ogrdialog.py 4 Mar 2005 15:07:59 -0000 1.3 @@ -16,18 +16,12 @@ try: import ogr except ImportError: - psycopg = None + ogr = None from Thuban import _ -from Thuban.UI.dialogs import NonModalDialog from Thuban.Model.table import FIELDTYPE_INT from Extensions.ogr import ogrshapes -from Thuban.Model.messages import DBCONN_ADDED, DBCONN_REMOVED -from Thuban.UI.messages import SESSION_REPLACED - -ID_DB_ADD = 9101 -ID_DB_REMOVE = 9102 ID_DBCHOOSE_RETRIEVE = 9201 ID_DBCHOOSE_OK = 9202 @@ -36,8 +30,11 @@ class ChooseFileFormat(wxDialog): - + """This dialog lists all available drivers. + """ def __init__(self, parent, session): + """Initialize the dialog. + """ wxDialog.__init__(self, parent, -1, _("Choose file format"), style = wxDIALOG_MODAL|wxCAPTION) self.session = session @@ -50,12 +47,12 @@ # Sizer for the entire dialog top = wxFlexGridSizer(2, 1, 0, 0) - # Sizer for the main part with the list boxes + # Sizer for the main part with the list box main_sizer = wxBoxSizer(wxHORIZONTAL) top.Add(main_sizer, 1, wxEXPAND, 0) # The list box with the drivers - static_box = wxStaticBoxSizer(wxStaticBox(self, -1, _("File formats")), + static_box = wxStaticBoxSizer(wxStaticBox(self, -1, "File formats"), wxHORIZONTAL) self.lb_drivers = wxListBox(self, -1) static_box.Add(self.lb_drivers, 0, wxEXPAND, 0) @@ -66,41 +63,6 @@ if self.lb_drivers.GetCount() > 0: self.lb_drivers.SetSelection(0, True) - # The button box between the connections list box and the table - # list box - buttons = wxFlexGridSizer(3, 1, 0, 0) - buttons.Add(20, 80, 0, wxEXPAND, 0) - retrieve_button = wxButton(self, ID_DBCHOOSE_RETRIEVE, _("Retrieve")) - EVT_BUTTON(self, ID_DBCHOOSE_RETRIEVE, self.OnRetrieve) - buttons.Add(retrieve_button, 0, wxALL - |wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 4) - buttons.Add(20, 80, 0, wxEXPAND, 0) - main_sizer.Add(buttons, 0, wxEXPAND, 0) - - # The list box with the tables - static_box = wxStaticBoxSizer(wxStaticBox(self, -1, _("Tables")), - wxHORIZONTAL) - self.lb_tables = wxListBox(self, ID_LB_DCLICK) - EVT_LISTBOX(self, ID_LB_DCLICK, self.OnTableSelect) - EVT_LISTBOX_DCLICK(self, ID_LB_DCLICK, self.OnLBDClick) - static_box.Add(self.lb_tables, 0, wxEXPAND, 0) - main_sizer.Add(static_box, 1, wxEXPAND, 0) - - # id column and geometry column selection - box = wxBoxSizer(wxVERTICAL) - box.Add(wxStaticText(self, -1, _("ID Column")), 0, - wxALL|wxALIGN_CENTER_VERTICAL, 4) - self.text_id_column = wxComboBox(self, -1, "") - box.Add(self.text_id_column, 0, - wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND, 4) - - box.Add(wxStaticText(self, -1, _("Geometry Column")), 0, - wxALL|wxALIGN_CENTER_VERTICAL, 4) - self.text_geo_column = wxComboBox(self, -1, "") - box.Add(self.text_geo_column, 0, - wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND, 4) - main_sizer.Add(box, 1, wxEXPAND, 0) - # The standard button box at the bottom of the dialog buttons = wxFlexGridSizer(1, 2, 0, 0) ok_button = wxButton(self, ID_DBCHOOSE_OK, _("OK")) @@ -120,48 +82,39 @@ def GetTable(self): - i = self.lb_tables.GetSelection() - if i >= 0: - return (self.selected_conn, self.tables[i], - self.text_id_column.GetValue(), - self.text_geo_column.GetValue()) + """no functionality + """ return None - def OnRetrieve(self, event): - i = self.lb_driver.GetSelection() - if i >= 0: - self.selected_conn = self.dbconns[i] - self.tables = self.selected_conn.GeometryTables() - self.lb_tables.Set(self.tables) - - def OnTableSelect(self, event): - i = self.lb_tables.GetSelection() - self.text_id_column.Clear() - self.text_geo_column.Clear() - if i >= 0: - for name, typ in self.selected_conn.table_columns(self.tables[i]): - if typ == "geometry": - self.text_geo_column.Append(name) - elif typ == FIELDTYPE_INT: - self.text_id_column.Append(name) - def OnLBDClick(self, event): + """Close dialog. + """ if self.lb_tables.GetSelection() >= 0: self.EndModal(wxID_OK) self.Show(False) def OnOK(self, event): + """Close dialog. + """ self.EndModal(wxID_OK) self.Show(False) def OnCancel(self, event): + """Close dialog. + """ self.EndModal(wxID_CANCEL) self.Show(False) class ChooseLayer(wxDialog): + """This dialog lists all the layers contained in the given datasource. + + One layer can be chosen, which is then opened. + """ def __init__(self, parent, filename): + """Initialize the dialog. + """ wxDialog.__init__(self, parent, -1, _("Choose layer"), style = wxDIALOG_MODAL|wxCAPTION) self.tables = [] @@ -210,6 +163,9 @@ self.Layout() def end_dialog(self, result): + """If the dialog is closed with OK, set chosen layer as layer + to be opened. + """ self.result = result if result is not None: self.EndModal(wxID_OK) @@ -218,18 +174,31 @@ self.Show(False) def OnOK(self, event): + """Dialog closed with OK button. + """ self.end_dialog(self.lb_drivers.GetSelection()) def OnCancel(self, event): + """Dialog closed with Cancel. + """ self.end_dialog(None) def GetLayer(self): + """Return the selected layer.""" return self.layer[self.lb_drivers.GetSelection()].GetName() class ChooseOGRDBTableDialog(wxDialog): + """This dialog opens a datasource from an existing database connection. + + A list of all available database connections is offered. If one connection + is selected and the button "Retrieve" is clicked, all layers are listed. + One of these layers can be chosen to be opened. + An ID column can be chosen, too.""" def __init__(self, parent, session): + """Initialize the dialog. + """ wxDialog.__init__(self, parent, -1, _("Choose layer from database"), style = wxDIALOG_MODAL|wxCAPTION) self.session = session @@ -279,6 +248,15 @@ static_box.Add(self.lb_tables, 0, wxEXPAND, 0) main_sizer.Add(static_box, 1, wxEXPAND, 0) + # id column and geometry column selection + box = wxBoxSizer(wxVERTICAL) + box.Add(wxStaticText(self, -1, _("ID Column")), 0, + wxALL|wxALIGN_CENTER_VERTICAL, 4) + self.text_id_column = wxComboBox(self, -1, "") + box.Add(self.text_id_column, 0, + wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND, 4) + main_sizer.Add(box, 1, wxEXPAND, 0) + # The standard button box at the bottom of the dialog buttons = wxFlexGridSizer(1, 2, 0, 0) ok_button = wxButton(self, ID_DBCHOOSE_OK, _("OK")) @@ -298,46 +276,141 @@ def GetTable(self): + """Return the chosen DB connection, the table to be opened, + the connection string for ogr and the ID column. + """ i = self.lb_tables.GetSelection() if i >= 0: - return (self.selected_conn, self.tables[i]) + return (self.selected_conn, self.connString, self.tables[i], + self.text_id_column.GetValue()) return None def OnRetrieve(self, event): + """Provide a list of all available layers in the selected datasource. + """ i = self.lb_connections.GetSelection() if i >= 0: self.selected_conn = self.dbconns[i] - connString = ("PG: host=%s dbname=%s user=%s port=%s" - %(self.selected_conn.host, self.selected_conn.dbname, - self.selected_conn.user, self.selected_conn.port)) - ds = ogr.Open(connString) + self.connString = "PG: dbname=%s" %self.selected_conn.dbname + if self.selected_conn.host is not "": + self.connString = (self.connString + " host=%s" + %self.selected_conn.host) + if self.selected_conn.user is not "": + self.connString = (self.connString + " user=%s" + %self.selected_conn.user) + if self.selected_conn.password is not "": + self.connString = (self.connString + " password=%s" + %self.selected_conn.password) + if self.selected_conn.port is not "": + self.connString = (self.connString + " port= %s" + %self.selected_conn.port) + ds = ogr.Open(self.connString) if ds: for i in range(ds.GetLayerCount()): self.tables.append(ds.GetLayer(i).GetName()) self.lb_tables.Set(self.tables) def OnTableSelect(self, event): + """If a table is selected, list all possible ID columns. + """ i = self.lb_tables.GetSelection() - # self.text_id_column.Clear() - # self.text_geo_column.Clear() - # if i >= 0: - # for name, typ in self.selected_conn.table_columns(self.tables[i]): - # if typ == "geometry": - # self.text_geo_column.Append(name) - # elif typ == FIELDTYPE_INT: - # self.text_id_column.Append(name) + self.text_id_column.Clear() + if i >= 0: + for name, typ in self.selected_conn.table_columns(self.tables[i]): + if typ == FIELDTYPE_INT: + self.text_id_column.Append(name) def OnLBDClick(self, event): + """Close dialog. + """ if self.lb_tables.GetSelection() >= 0: self.EndModal(wxID_OK) self.Show(False) def OnOK(self, event): + """Dialog closed with OK button. + """ self.EndModal(wxID_OK) self.Show(False) def OnCancel(self, event): + """Dialog closed with Cancel. + """ self.EndModal(wxID_CANCEL) self.Show(False) + +class OGRConnectionDialog(wxDialog): + """A string can be enteres, which is directly passed to ogr to open a + datasource. + """ + def __init__(self, parent, session): + """Initialize the dialog. + """ + wxDialog.__init__(self, parent, -1, "Enter string for OGRConnection", + style = wxDIALOG_MODAL|wxCAPTION) + self.session = session + + # Sizer for the entire dialog + top = wxBoxSizer(wxVERTICAL) + + # The list box with the drivers + box = wxBoxSizer(wxHORIZONTAL)#wxBox(self, -1, _("OGRConnection")), + # wxHORIZONTAL) + box.Add(wxStaticText(self, -1, _("URL:")), 0, + wxALL|wxALIGN_CENTER_VERTICAL, 4) + self.text_string = wxTextCtrl(self, -1, "") + box.Add(self.text_string, 0, wxEXPAND, 0) + top.Add(box, 0, wxEXPAND) + + # The standard button box at the bottom of the dialog + buttons = wxFlexGridSizer(1, 2, 0, 0) + ok_button = wxButton(self, ID_DBCHOOSE_OK, _("OK")) + EVT_BUTTON(self, ID_DBCHOOSE_OK, self.OnOK) + buttons.Add(ok_button, 0, wxALL|wxALIGN_RIGHT, 4) + cancel_button = wxButton(self, ID_DBCHOOSE_CANCEL, _("Cancel")) + EVT_BUTTON(self, ID_DBCHOOSE_CANCEL, self.OnCancel) + buttons.Add(cancel_button, 0, wxALL, 4) + top.Add(buttons, 1, wxALL|wxALIGN_CENTER_HORIZONTAL, 4) + + # Autosizing + self.SetAutoLayout(1) + self.SetSizer(top) + top.Fit(self) + top.SetSizeHints(self) + self.Layout() + + def RunDialog(self): + """Run dialog + """ + self.ShowModal() + self.Destroy() + return self.result + + def end_dialog(self, result): + """Close dialog + """ + self.result = result + if result is not None: + self.EndModal(wxID_OK) + else: + self.EndModal(wxID_CANCEL) + self.Show(False) + + def OnOK(self, event): + """Dialog closed with OK + """ + result = {} + result["string"] = getattr(self, "text_string").GetValue() + self.end_dialog(result) + + def OnCancel(self, event): + """Dialog closed with Cancel. + """ + self.end_dialog(None) + + def GetDatasourceName(self): + """Return the string to be used for opening the database + """ + return self.result["string"] Index: ogrshapes.py =================================================================== RCS file: /thubanrepository/thuban/Extensions/ogr/ogrshapes.py,v retrieving revision 1.5 retrieving revision 1.6 diff -u -d -r1.5 -r1.6 --- ogrshapes.py 8 Feb 2005 09:52:56 -0000 1.5 +++ ogrshapes.py 4 Mar 2005 15:07:59 -0000 1.6 @@ -17,17 +17,15 @@ ogr = None import os -import weakref -from math import ceil, log from Thuban import _ from Thuban.Model import table +from Thuban.Model import transientdb +from Thuban.Model.transientdb import TransientDatabase from Thuban.Model.data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT from Thuban.Model.data import RAW_PYTHON, RAW_SHAPEFILE, RAW_WKT -SHAPETYPE_UNKNOWN = ogr.wkbUnknown - def has_ogr_support(): """Return whether this Thuban instance supports ogr file formats @@ -37,53 +35,102 @@ return ogr is not None if ogr is not None: - # mapping from ogr-lib shapetype and table constants to our constants + SHAPETYPE_UNKNOWN = ogr.wkbUnknown + SHAPETYPE_GEOMCOLL = ogr.wkbGeometryCollection + SHAPETYPE_NONE = ogr.wkbNone + + # mapping from ogr-lib shapetypes and table constants to our constants ogrlib_shapetypes = {ogr.wkbPolygon: SHAPETYPE_POLYGON, ogr.wkbLineString: SHAPETYPE_ARC, ogr.wkbPoint: SHAPETYPE_POINT, - ogr.wkbUnknown: SHAPETYPE_UNKNOWN} + ogr.wkbUnknown: SHAPETYPE_UNKNOWN, + ogr.wkbNone: SHAPETYPE_NONE, + ogr.wkbGeometryCollection: SHAPETYPE_GEOMCOLL} fieldtype_map = {ogr.OFTString: table.FIELDTYPE_STRING, ogr.OFTInteger: table.FIELDTYPE_INT, ogr.OFTReal: table.FIELDTYPE_DOUBLE} +else: + ogrlib_shapetypes = {} + fieldtype_map = {} + SHAPETYPE_UNKNOWN = 0 + SHAPETYPE_GEOMCOLL = 7 + SHAPETYPE_NONE = 100 + class OGRShape: + """Represent one shape of an OGRShapeStore + """ - """Represent one shape of an OGRShapeStore""" + def __init__(self, shapestore, shape): + """Initialize the shape object. - def __init__(self, ogrlayer, shapeid): - self.ogrlayer = ogrlayer - self.feature = self.ogrlayer.GetFeature(shapeid) - self.shapeid = shapeid + shapestore should be an instance of OGRShapeStore, + shape should be an instance of an OGRFeature. + """ + self.ogrlayer = shapestore.ogrlayer + id_column = shapestore.Id_column() + self.feature = shape + if id_column is None: + self.shapeid = self.feature.GetFID() + else: + self.shapeid = self.feature.GetField(id_column) self.geom = self.feature.GetGeometryRef() - self.shapetype = self.geom.GetGeometryType() + if self.geom: + self.shapetype = self.geom.GetGeometryType() + self.bbox = self._compute_bbox() + self.points = self._points() + else: + self.shapetype = ogr.wkbNone + self.bbox = None + self.points = [[]] + try: + self.shapetype = ogrlib_shapetypes[self.shapetype] + except: + self.shapetype = ogrlib_shapetypes[ogr.wkbUnknown] - def compute_bbox(self): + self.geoms = self._geoms() + + def _geoms(self): + """Return a list of geometry objects. + + If the shape is a geometry collection, all contained geometry + objects are stored to the list as OGRGeometry objects. """ - Return the bounding box of the shape as a tuple (minx,miny,maxx,maxy) + geoms = [] + if self.shapetype == SHAPETYPE_GEOMCOLL: + for i in range(self.geom.GetGeometryCount()): + geoms.append(OGRGeometry(self, self.geom, i)) + return geoms + + def _compute_bbox(self): + """ + Compute the bounding box of the shape as a tuple (minx,miny,maxx,maxy) """ minx, maxx, miny, maxy = self.geom.GetEnvelope() return (minx, miny, maxx, maxy) + def compute_bbox(self): + """ + Return the bounding box of the shape as a tuple (minx,miny,maxx,maxy) + """ + return self.bbox + def ShapeID(self): + """Return the feature id of this shape. + """ return self.shapeid def Points(self): - """Return the coordinates of the shape as a list of lists of pairs""" - shape = [] - #spatialFilter = self.ogrlayer.GetSpatialFilter() - - #if spatialFilter is not None: - #self.ogrlayer.SetSpatialFilter(None) - #feature = self.ogrlayer.GetFeature(self.shapeid) - #self.ogrlayer.SetSpatialFilter(spatialFilter) - #else: - #feature = self.ogrlayer.GetFeature(self.shapeid) + """Return the coordinates of the shape as a list of lists of pairs + """ + return self.points - #if feature is None: - # return shape.append([]) - #geom = feature.GetGeometryRef() + def _points(self): + """Get the coordinates of the shape as a list of lists of pairs + """ + shape = [] if self.geom is None: return shape.append([]) @@ -107,7 +154,7 @@ y = geometry.GetY(point) points.append((x, y)) shape.append(points) - # if geometry object is of type multipolygon + # if geometry object is of type multipolygon or geometry collection else: for j in range(geometry.GetGeometryCount()): points = [] @@ -120,46 +167,99 @@ return shape def RawData(self): - """Return the shape id to use with the shapestore""" + """Return the shape id to use with the shapestore + """ return self.shapeid def OGRLayer(self): - """Return the ogrlayer object""" + """Return the ogrlayer object + """ return self.ogrlayer def ShapeType(self): + """Return the shapetype of this shape (may differ from the layer's + shapetype) + """ return self.shapetype + def GetGeoms(self): + """Return the list of geometries of this feature. -class OGRShapeStore: + If this feature is a geometry collection, all contained geometries + are given. Else the returned list is empty. + """ + return self.geoms - """Corresponds to an OGRLayer object, containing features/shapes and - providing all methods Thuban needs. + def GetGeom(self, index): + """Return the OGRGeometry object at the specified index. + + This is not none only if the shape is a geometry collection. + """ + if index < len(self.geoms): + return self.geoms[index] + else: + return None + + +class OGRGeometry: + """This class represents a geometry belonging to a specified feature. """ - def __init__(self, filename, layername, id_column = None): - # Make the filename absolute. The filename will be - # interpreted relative to that anyway, but when saving a - # session we need to compare absolute paths and it's usually - # safer to always work with absolute paths. + def __init__(self, shape, geom, index): + """Initialize the geometry object. - self.filename = filename + shape should be an OGRShape, which this geometry belongs to. + geom is the base geometry, index is the ReferenceID. + """ + self.shape = shape + self.index = index + + self.geom = geom.GetGeometryRef(index) + try: + self.shapetype = ogrlib_shapetypes[self.geom.GetGeometryType()] + except: + self.shapetype = ogrlib_shapetypes[ogr.wkbUnknown] + + + def ShapeType(self): + """Return the shapetype of this geometry object.""" + return self.shapetype + + +class OGRShapeStore: + """Corresponds to an OGRLayer object, containing features/shapes and + providing the same methods like ShapefileStore. + """ + + def __init__(self, session, filename, layername, id_column = None): + """Initialize the shapestore. + + All required information is loaded from the datasource. + """ + # if id_column is None, data is loaded from file, so we need path + # if id_column is not None, data is loaded from database + if id_column is None: + self.filename = os.path.abspath(filename) + else: + self.filename = filename self.layername = layername self.ogrdatasource = ogr.Open(filename) self.ogrlayer = (self.ogrdatasource).GetLayerByName(layername) - driver = self.ogrdatasource.GetDriver().GetName() - if driver == 'PostgreSQL': - self.id_column = 'gid' + if id_column is not None: + self.id_column = id_column else: - self.id_column = 'fid' + self.id_column = None - self.table = OGRTable(self.ogrdatasource, self.ogrlayer, self.id_column) + self.table = OGRTable(session, self.ogrdatasource, self.ogrlayer, + self.id_column) self._open_ogrlayer(layername) def _open_ogrlayer(self, layername): + """Get all required information from the datasource. + """ self.numshapes = self.ogrlayer.GetFeatureCount() self.shapetype = self.ogrlayer.GetLayerDefn().GetGeomType() @@ -169,38 +269,73 @@ else: self.bbox = None - if self.shapetype is not ogr.wkbUnknown: + try: self.shapetype = ogrlib_shapetypes[self.shapetype] - #else: - # this should be ogr.wkbUnknown, but Thuban does not know how - # to handle an unknown shapetype (e.g. Session Tree) - #self.shapetype = ogrlib_shapetypes[ogr.wkbPoint] + except: + # if shapetype is not contained in ogrlib_shapetypes + # treat it like SHAPETYPE_UNKNOWN + self.shapetype = ogrlib_shapetypes[ogr.wkbUnknown] + + self.shapes = self.shapes() + + def shapes(self): + """Return a collection of all features as OGRShape objects. + """ + shapes = {} + self.ogrlayer.ResetReading() + if self.id_column is None: + nextFeature = self.ogrlayer.GetNextFeature() + while nextFeature is not None: + fid = nextFeature.GetFID() + shape = OGRShape(self, nextFeature) + shapes[shape.ShapeID()] = shape + nextFeature = self.ogrlayer.GetNextFeature() + else: + lay = self.ogrdatasource.ExecuteSQL("SELECT %s, * from %s" + % (self.id_column, self.layername)) + if lay is not None: + lay.ResetReading() + nextFeature = lay.GetNextFeature() + while nextFeature is not None: + fid = nextFeature.GetField(0) + shape = OGRShape(self, nextFeature) + shapes[shape.ShapeID()] = shape + nextFeature = lay.GetNextFeature() + self.ogrdatasource.ReleaseResultSet(lay) + return shapes def OGRLayer(self): - """Return the OGRLayer object""" + """Return the OGRLayer object + """ return self.ogrlayer def FileName(self): - """Return the filename used to open the file""" + """Return the filename used to open the file + """ return self.filename def FileType(self): - """Return the filetype. This is always the string 'ogr-file'""" - return "ogr-file" + """Return the filetype. This is depending on the driver used to open + the file. + """ + return self.ogrdatasource.GetDriver().GetName() def ShapeType(self): """Return the type of the shapes in the shapestore. - This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON. + This is either SHAPETYPE_POINT, SHAPETYPE_ARC, SHAPETYPE_POLYGON, + SHAEPTYPE_GEOMCOLL, SHAPETYPE_NONE or SHAPETYPE_UNKNOWN. """ return self.shapetype def RawShapeFormat(self): - """Return the raw data format of the shape data, i.e. RAW_PYTHON""" + """Return the raw data format of the shape data, i.e. RAW_PYTHON + """ return RAW_PYTHON def NumShapes(self): - """Return the number of shapes in the shape store""" + """Return the number of shapes in the shape store + """ return self.numshapes def BoundingBox(self): @@ -214,53 +349,61 @@ The bbox parameter should be the bounding box as a tuple in the form (minx, miny, maxx, maxy) in the coordinate system of the shape store. - - The method GetFID() returns feature IDs starting from 0. """ - # Bind a few globals to locals to make it a bit faster - cls = OGRShape - ogrlayer = self.ogrlayer - left, bottom, right, top = bbox + # create a geometry which can be passed to the layer as spatial filter bboxpolygon = ogr.CreateGeometryFromWkt( ('Polygon((%s %s, %s %s, %s %s,%s %s, %s %s))' - %(left, bottom, left, top, right, top, + %(left, bottom, left, top, right, top, right, bottom, left, bottom))) - if ogrlayer.GetSpatialRef(): - bboxpolygon.AssignSpatialReference(ogrlayer.GetSpatialRef()) + if self.ogrlayer.GetSpatialRef(): + bboxpolygon.AssignSpatialReference(self.ogrlayer.GetSpatialRef()) - ogrlayer.ResetReading() + self.ogrlayer.ResetReading() #ogrlayer.SetSpatialFilterRect(left, bottom, right, top) - ogrlayer.SetSpatialFilter(bboxpolygon) + self.ogrlayer.SetSpatialFilter(bboxpolygon) - numFeatures = ogrlayer.GetFeatureCount() + numFeatures = self.ogrlayer.GetFeatureCount() + # if no features are in bbox, return all features as shapesInRegion + # (PostGIS sometimes returns no features even if they are within + # the bounding box) + if numFeatures == 0: + self.ogrlayer.SetSpatialFilter(None) + numFeatures = self.ogrlayer.GetFeatureCount() for feature in range(numFeatures): - nextFeature = ogrlayer.GetNextFeature() - yield cls(ogrlayer, nextFeature.GetFID()) + nextFeature = self.ogrlayer.GetNextFeature() + if self.id_column is None: + yield self.shapes[nextFeature.GetFID()] + else: + yield self.shapes[nextFeature.GetField(self.id_column)] - ogrlayer.SetSpatialFilter(None) + self.ogrlayer.SetSpatialFilter(None) bboxpolygon.Destroy() def AllShapes(self): - """Return an iterable over the shapes in the shape store.""" - self.ogrlayer.ResetReading() - nextFeature = self.ogrlayer.GetNextFeature() - while nextFeature is not None: - yield OGRShape(self.ogrlayer, nextFeature.GetFID()) - nextFeature = self.ogrlayer.GetNextFeature() + """Return an iterable over the shapes in the shape store. + """ + for id in range(len(self.shapes)): + yield self.shapes[id] - def Shape(self, index): - """Return the shape with index index""" - return OGRShape(self.ogrlayer, index) + def Shape(self, fid): + """Return the shape with fid = fid + """ + if fid in self.table.ids.keys(): + return self.shapes[fid] + else: + return None def Table(self): - """Return the table containing the attribute data""" + """Return the table containing the attribute data + """ return self.table def Dependencies(self): - """Return the empty tuple.""" + """Return the empty tuple. + """ return () def OrigShapeStore(self): @@ -268,24 +411,25 @@ return None def Id_column(self): - """Return the id_column.""" + """Return the id_column. + """ return self.id_column -class OGRTable: - - """A Table for an ogr file +class OGRTable(transientdb.AutoTransientTable): + """A Table for an ogr datasource. """ - def __init__(self, ds, layer, id_column): + def __init__(self, session, ds, layer, id_column): """Initialize the OGRTable. - ds should be an instance of OGRDatasource. - layer should be an instance of OGRLayer. + session - should be the current session. + ds - should be an instance of OGRDatasource. + layer - should be an instance of OGRLayer. + id_column - should be the name of the column used as ID column """ - self.datasource = ds self.layer = layer - self.tablename = layer.GetName() + self.tablename = self.layer.GetName() self.id_column = id_column # Map column names and indices to column objects. @@ -295,12 +439,20 @@ self._map_ords_and_ids() self._fetch_table_information() + self._fetch_table_content() + + transientdb.AutoTransientTable.__init__(self, session.TransientDB(), + self) def _fetch_table_information(self): - """Internal: Update information about the table""" + """Internal: Update information about the table + """ self.columns = [] layerdefn = self.layer.GetLayerDefn() + # if FID column is of interest + #col = OGRColumn("FID", table.FIELDTYPE_INT, layerdefn.GetFieldCount()) + #self.columns.append(col) for i in range(layerdefn.GetFieldCount()): fielddef = layerdefn.GetFieldDefn(i) fieldname = fielddef.GetName() @@ -314,86 +466,108 @@ self.column_map[col.name] = col self.column_map[col.index] = col + def _fetch_table_content(self): + """Internal: Update information contained in the table + """ + self.content = [] + layerdefn = self.layer.GetLayerDefn() + + self.layer.ResetReading() + for i in range(self.layer.GetFeatureCount()): + nextFeature = self.layer.GetNextFeature() + row = [] + for j in range(layerdefn.GetFieldCount()): + row.append(nextFeature.GetField(j)) + # if FID should be listed in the table + #if self.id_column is None: + # row.append(nextFeature.GetFID()) + #else: + # row.append(nextFeature.GetField(self.id_column)) + self.content.append(row) + def _map_ords_and_ids(self): + """Create collections which map ordinals to ids and verse visa. + """ self.ordinals = {} self.ids = {} - lay = self.datasource.ExecuteSQL( - "SELECT %s from %s" - %(self.id_column, self.tablename)) - lay.ResetReading() - nextFeature = lay.GetNextFeature() + if self.id_column is not None: + lay = self.datasource.ExecuteSQL("SELECT %s from %s" + %(self.id_column, self.tablename)) + lay.ResetReading() + nextFeature = lay.GetNextFeature() + else: + self.layer.ResetReading() + nextFeature = self.layer.GetNextFeature() + ord = 0 while nextFeature is not None: - id = nextFeature.GetFID() + if self.id_column is not None: + id = nextFeature.GetField(self.id_column) + nextFeature = lay.GetNextFeature() + else: + id = nextFeature.GetFID() + nextFeature = self.layer.GetNextFeature() self.ordinals[ord] = id self.ids[id] = ord - nextFeature = lay.GetNextFeature() ord = ord + 1 - self.datasource.ReleaseResultSet(lay) + if self.id_column is not None: + self.datasource.ReleaseResultSet(lay) def TableName(self): - """Return the name of the table, which is the name of the layer""" + """Return the name of the table, which is the name of the layer + """ return self.tablename def Title(self): """Return the title of the table. - - The title is currently """ return self.tablename def Dependencies(self): - """Return an empty tuple.""" + """Return an empty tuple. + """ return () def NumColumns(self): - """Return the number of columns.""" + """Return the number of columns. + """ return len(self.columns) def Columns(self): - """Return all columns.""" + """Return all columns. + """ return self.columns def Column(self, col): - """Return the column col. col can be either a string or an integer.""" + """Return the column col. col can be either a string or an integer. + """ return self.column_map[col] def HasColumn(self, col): - """Return if column col exists. col can be either a string or an + """Return if column col exists. col can be either a string or an integer. """ return self.column_map.has_key(col) def NumRows(self): - """Return the number of rows in the table, which equals the number of + """Return the number of rows in the table, which equals the number of features in the layer. """ - return self.layer.GetFeatureCount() + return len(self.ids) def RowIdToOrdinal(self, gid): - """Return the row ordinal given its id""" + """Return the row ordinal given its id + """ if gid < 0: return gid else: ord = self.ids[gid] return ord -# lay = self.datasource.ExecuteSQL( - # "SELECT COUNT(%s) From %s where FID < %s" - # %(self.id_column, self.tablename, gid)) - # ord = lay.GetFeature(0).GetField(0) - # self.datasource.ReleaseResultSet(lay) - # return ord def RowOrdinalToId(self, num): - """Return the rowid for given its ordinal""" -# lay = self.datasource.ExecuteSQL( - # "SELECT FID From %s" - # %(self.tablename)) - # for i in range(num): - # lay.GetNextFeature() - # id = lay.GetNextFeature().GetField(0) - # self.datasource.ReleaseResultSet(lay) + """Return the rowid for given its ordinal + """ if num >= 0: id = self.ordinals[num] return id @@ -401,32 +575,31 @@ return num def ReadRowAsDict(self, row, row_is_ordinal = 0): - """Return a dictionary which contains all the fields.""" - if row_is_ordinal == 1: - rowId = self.RowOrdinalToId(row) + """Return a dictionary which contains all the fields. + """ + if row_is_ordinal == 0: + rowId = self.RowIdToOrdinal(row) else: rowId = row - layerdef = self.layer.GetLayerDefn() - feature = self.layer.GetFeature(rowId) result = {} - for i in range(len(self.columns)): - fielddef = layerdef.GetFieldDefn(i) - if feature is not None: - result[self.columns[i].name] = feature.GetField(i) - else: - result[fielddef.GetName()] = None + for i in range(self.NumColumns()): + result[self.Column(i).name] = self.content[rowId][i] return result def ReadValue(self, row, col, row_is_ordinal = 0): - """Return the requested value.""" - if col is None: - return None + """Return the requested value. + """ + if row_is_ordinal == 0: + rowId = self.RowIdToOrdinal(row) else: - feature = self.layer.GetFeature(row) - return feature.GetField(col) + rowId = row + colIndex = self.column_map[col].index + return self.content[rowId][colIndex] def ValueRange(self, col): - """Return the value range of the given column (given as string).""" + """Return the value range of the given column (given as string). + """ + result = self.datasource.ExecuteSQL("SELECT min(%s), max(%s) FROM %s" %(col, col, self.layer.GetName())) result.ResetReading() @@ -456,7 +629,9 @@ return values def SimpleQuery(self, left, comparison, right): - """Return the FIDs resulting from the given query.""" + """Return the FIDs resulting from the given query. + """ + if comparison not in ("==", "!=", "<", "<=", ">=", ">"): raise ValueError("Comparison operator %r not allowed" %comparison) @@ -468,9 +643,13 @@ else: right_template = right - query = ("SELECT %s FROM %s WHERE '%s' %s %s ORDER BY FID" - % (self.id_column, self.tablename,left.name, comparison, - right_template)) + if self.id_column is None: + id = "FID" + else: + id = self.id_column + query = ("SELECT %s FROM %s WHERE %s %s %s ORDER BY %s" + % (id, self.tablename,left.name, comparison, + right_template, id)) lay = self.datasource.ExecuteSQL(query) result = [] @@ -484,12 +663,12 @@ return result def Id_column(self): - """Return the id_column.""" + """Return the id_column. + """ return self.id_column class OGRColumn: - """Column description for a table for an ogr file """ Index: ogrstart.py =================================================================== RCS file: /thubanrepository/thuban/Extensions/ogr/ogrstart.py,v retrieving revision 1.4 retrieving revision 1.5 diff -u -d -r1.4 -r1.5 --- ogrstart.py 8 Feb 2005 09:52:56 -0000 1.4 +++ ogrstart.py 4 Mar 2005 15:07:59 -0000 1.5 @@ -10,9 +10,7 @@ # $Id$ # Needed wx-toolkit classes -#from wxPython.wx import * -from wxPython.wx import wxFileDialog, wxOPEN, wxMULTIPLE, wxID_OK, \ - wxOK, wxICON_HAND +from wxPython.wx import wxFileDialog, wxOPEN, wxMULTIPLE, wxID_OK # We need os.path import os @@ -21,13 +19,13 @@ from Thuban import _ from Thuban.Model.layer import Layer -from Thuban.UI.dbdialog import DBDialog # Import ogr related classes from Extensions.ogr import ogrshapes, ogrdialog from Extensions.ogr.ogrdialog import ChooseOGRDBTableDialog from Thuban.UI.menu import Menu +from Thuban.UI.mainwindow import _has_dbconnections, _has_gdal_support def open_with_ogr(context): '''Open a file supported by ogr. @@ -37,9 +35,13 @@ # Get the file to be opened dlg = wxFileDialog(canvas, _("Select a data file"), - ".", "", - _("Shape/GML files (*.shp/*.gml)") + "|*.shp;*.gml|" + - _("All Files (*.*)") + "|*.*", + context.application.Path("data"), "", + _("Shapefiles (*.shp)") + "|*.shp|" + + _("GML files (*.gml)") + "|*.gml|" + + _("MapInfo files (*.tab)") + "|*.tab|" + + _("DGN files (*.dgn)") + "|*.dgn|" + + _("CSV files (*.csv)") + "|*.csv|" + + _("All Files (*.*)") + "|*.*|", wxOPEN | wxMULTIPLE) if dlg.ShowModal() == wxID_OK: @@ -50,22 +52,23 @@ layerDlg = ogrdialog.ChooseLayer(canvas, filename) if layerDlg.ShowModal() == wxID_OK: layername = layerDlg.GetLayer() - try: - session = context.application.Session() - store = ogrshapes.OGRShapeStore(filename, layername) - session.AddShapeStore(store) - except: - # the layer couldn't be opened - context.mainwindow.RunMessageBox(("Add Layer"), + try: + session = context.application.Session() + store = OpenFileShapestore(session, filename, layername) + session.AddShapeStore(store) + except: + # the layer couldn't be opened + context.mainwindow.RunMessageBox(("Add Layer"), ("Can't open the file '%s'.")%filename) - else: - layer = Layer(title, store) - map.AddLayer(layer) - if not has_layers: - # if we're adding a layer to an empty map, fit the - # new map to the window - canvas.FitMapToWindow() - context.application.SetPath("data",filename) + else: + if store is not None: + layer = Layer(title, store) + map.AddLayer(layer) + if not has_layers: + # if we're adding a layer to an empty map, fit the + # new map to the window + canvas.FitMapToWindow() + context.application.SetPath("data",filename) dlg.Destroy() def select_file_format(context): @@ -81,7 +84,7 @@ dlg = ogrdialog.ChooseFileFormat(canvas, session) if dlg.ShowModal() == wxID_OK: - context.mainwindow.RunMessageBox("dialog auf", "dialog auf") + pass dlg.Destroy() def open_db(context): @@ -91,17 +94,14 @@ canvas = context.mainwindow.canvas map = canvas.Map() - session = context.application.Session() + session = context.application.Session() dlg = ChooseOGRDBTableDialog(canvas, session) if dlg.ShowModal() == wxID_OK: - dbconn, dbtable = dlg.GetTable() + dbconn, connString, dbtable, id_column = dlg.GetTable() try: - # Choose the correct Interface for the database type - filename = ('PG: host=%s user=%s dbname=%s port=%s' - %(dbconn.host, dbconn.user, dbconn.dbname, dbconn.port)) - - store = ogrshapes.OGRShapeStore(filename, dbtable) + store = OpenDBShapestore(session, dbconn, dbtable, id_column, + None) session.AddShapeStore(store) layer = Layer(dbtable, store) @@ -117,7 +117,77 @@ % dbtable) dlg.Destroy() +def open_OGRConnection(context): + """Open a datasource with an OGRConnection string.""" + canvas = context.mainwindow.canvas + map = canvas.Map() + session = context.application.Session() + dlg = ogrdialog.OGRConnectionDialog(canvas, session) + + if dlg.ShowModal() == wxID_OK: + dsname = dlg.GetDatasourceName() + + layerDlg = ogrdialog.ChooseLayer(canvas, dsname) + if layerDlg.ShowModal() == wxID_OK: + layername = layerDlg.GetLayer() + try: + store = ogrshapes.OGRShapeStore(session, dsname, layername) + session.AddShapeStore(store) + except: + # the layer couldn't be opened + context.mainwindow.RunMessageBox(("Add Layer"), + ("Can't open the file '%s'.") % dsname) + else: + layer = Layer(dsname, store) + has_layers = map.HasLayers() + map.AddLayer(layer) + if not has_layers: + # if we're adding a layer to an empty map, fit the + # new map to the window + canvas.FitMapToWindow() + dlg.Destroy() + +def OpenFileShapestore(session, filename, layername): + """Open a datasource and add the required layer. + """ + try: + store = ogrshapes.OGRShapeStore(session, filename, layername) + return store + except: + # Some error occured while initializing the layer + context.mainwindow.RunMessageBox(_("Open datasource"), + _("Can't open the datasource '%s'") + % filename) + else: + return null + +def OpenDBShapestore(session, dbconn, layername, id_column, geo_column): + """Open a datasource and add the required layer. + + dbconn - shold be a DBConnection + layername - the name of the table which should opened as layer + id_column - the column name which should be used as ID column + geo_column - always None for ogr + """ + try: + filename = "PG: dbname=%s" %dbconn.dbname + if dbconn.host is not "": + filename = filename + " host=%s" % dbconn.host + if dbconn.user is not "": + filename = filename + " user=%s" % dbconn.user + if dbconn.password is not "": + filename = filename + " password=%s" % dbconn.password + if dbconn.port is not "": + filename = filename + " port=%s" % dbconn.port + store = ogrshapes.OGRShapeStore(session, filename, layername, + id_column = id_column) + return store + except: + # Some error occured while initializing the layer + context.mainwindow.RunMessageBox(_("Open datasource"), + _("Can't open the datasource '%s'") + % filename) # Thuban has named commands which can be registered in the central # instance registry. @@ -128,28 +198,37 @@ from Thuban.UI.mainwindow import main_menu -# find the map menu (create it a new if not found) +# find the map menu (create a new if not found) map_menu = main_menu.FindOrInsertMenu('map', _('Map')) ogr_menu = Menu("ogr", _("Open layer via OGR"),[]) +ogrsupport = ogrshapes.has_ogr_support() # create new commands and register them registry.Add(Command('open_ogr_files', 'Open an ogr-file', open_with_ogr, + sensitive = _has_gdal_support, helptext = 'Open a file supported from ogr')) -registry.Add(Command('select_file_format', 'Select a file format', - select_file_format, - helptext = "Select a file format supported from ogr")) +#registry.Add(Command('select_file_format', 'Select a file format', +# select_file_format, +# helptext = "Select a file format supported from ogr")) registry.Add(Command('open_db', 'Open a layer from a database', open_db, + sensitive = _has_dbconnections, helptext = "Open a layer from a database, e.g. PostGIS")) +registry.Add(Command('open_OGRConnection', + ("Open a datasource with an OGRConnection string"), + open_OGRConnection, + sensitive = _has_gdal_support, helptext = + "Open a datasource with an OGRConnection string")) + # finally bind the new command with an entry in the extensions menu ogr_menu.InsertItem("open_ogr_files") -ogr_menu.InsertItem('select_file_format') +#ogr_menu.InsertItem('select_file_format') ogr_menu.InsertItem('open_db') +ogr_menu.InsertItem('open_OGRConnection') # Add ogr menu to map menu map_menu.InsertItem(ogr_menu, after = "rasterlayer_add") - From nina_hueffmeyer at yahoo.de Fri Mar 4 16:17:38 2005 From: nina_hueffmeyer at yahoo.de (Nina Hueffmeyer) Date: Fri, 4 Mar 2005 16:17:38 +0100 (CET) Subject: OGR extension Message-ID: <20050304151738.48354.qmail@web26509.mail.ukl.yahoo.com> Hi all, not all features from Thuban work with the ogr extension yet. This would need some changes in the Thuban core files, which will be done after implementing a new concept for extensions. For anybody, who already wants to take a look, I attached a patch with all the changes you need to make also functions like SessionTree, Save Session etc. work. Regards, Nina ___________________________________________________________ Gesendet von Yahoo! Mail - Jetzt mit 250MB Speicher kostenlos - Hier anmelden: http://mail.yahoo.de -------------- next part -------------- A non-text attachment was scrubbed... Name: ogrextension.patch Type: application/octet-stream Size: 32380 bytes Desc: ogrextension.patch Url : http://www.intevation.de/pipermail/thuban-devel/attachments/20050304/9cc641ce/ogrextension.patch From nhueffme at intevation.de Fri Mar 4 16:56:57 2005 From: nhueffme at intevation.de (Nina =?iso-8859-1?q?H=FCffmeyer?=) Date: Fri, 4 Mar 2005 16:56:57 +0100 Subject: OGR extension Message-ID: <200503041656.57081.nhueffme@intevation.de> Hi, maybe you want to try things out and do not have test files, so I will attach some of my test files. fahrrad.gml is a GML file intellitrim.dgn is a DGN file political.* belong to a MapInfo file (open .tab file!) TestIceland.thuban is a Thuban file containing the iceland data Nina > Hi all, > > not all features from Thuban work with the ogr > extension yet. This would need some changes in the > Thuban core files, which will be done after > implementing a new concept for extensions. > > For anybody, who already wants to take a look, I > attached a patch with all the changes you need to make > also functions like SessionTree, Save Session etc. > work. > > Regards, Nina -------------- next part -------------- A non-text attachment was scrubbed... Name: fahrrad.gml Type: text/xml Size: 4811 bytes Desc: not available Url : http://www.intevation.de/pipermail/thuban-devel/attachments/20050304/b952cf72/fahrrad.gml -------------- next part -------------- A non-text attachment was scrubbed... Name: intellitrim.dgn Type: application/octet-stream Size: 15872 bytes Desc: not available Url : http://www.intevation.de/pipermail/thuban-devel/attachments/20050304/b952cf72/intellitrim.dgn -------------- next part -------------- A non-text attachment was scrubbed... Name: political.id Type: application/octet-stream Size: 624 bytes Desc: not available Url : http://www.intevation.de/pipermail/thuban-devel/attachments/20050304/b952cf72/political.id -------------- next part -------------- A non-text attachment was scrubbed... Name: political.dat Type: application/octet-stream Size: 11678 bytes Desc: not available Url : http://www.intevation.de/pipermail/thuban-devel/attachments/20050304/b952cf72/political.dat -------------- next part -------------- !table !version 300 !charset Neutral Definition Table Type NATIVE Charset "Neutral" Fields 8 AREA Float ; PERIMETER Float ; PONET_ Integer ; PONET_ID Integer ; POPYTYPE Integer ; POPYREG Char (2) ; POPYCOUN Char (2) ; POPYADMIN Char (40) ; -------------- next part -------------- A non-text attachment was scrubbed... Name: political.map Type: application/octet-stream Size: 38400 bytes Desc: not available Url : http://www.intevation.de/pipermail/thuban-devel/attachments/20050304/b952cf72/political.map -------------- next part -------------- A non-text attachment was scrubbed... Name: TestIceland.thuban Type: text/xml Size: 3547 bytes Desc: not available Url : http://www.intevation.de/pipermail/thuban-devel/attachments/20050304/b952cf72/TestIceland.thuban From bh at intevation.de Mon Mar 7 19:19:37 2005 From: bh at intevation.de (Bernhard Herzog) Date: Mon, 07 Mar 2005 19:19:37 +0100 Subject: Interface? Was: Base class for Shapes? In-Reply-To: <20050303081434.GA24586@intevation.de> (Jan-Oliver Wagner's message of "Thu, 3 Mar 2005 09:14:34 +0100") References: <20041124214926.GA30763@intevation.de> <20041126165520.GB3139@intevation.de> <20050303081434.GA24586@intevation.de> Message-ID: Jan-Oliver Wagner writes: > On Fri, Nov 26, 2004 at 07:31:22PM +0100, Bernhard Herzog wrote: >> class IShape(Interface): > > What Interface class are you using here? I just wanted to play around with interfaces a bit, so I wrote my own: class Interface: pass > At a quick search I only found the proposal of Gon?alo Rodrigues: > http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/164901 Better is probably http://www.zope.org/Wikis/Interfaces/FrontPage http://peak.telecommunity.com/PyProtocols.html Or for a bit of history (apparently the interface ideas date back to 1998): http://www.zope.org/Members/jim/PythonInterfaces/Summary Bernhard -- Intevation GmbH http://intevation.de/ Skencil http://skencil.org/ Thuban http://thuban.intevation.org/ From jan at intevation.de Tue Mar 8 15:36:43 2005 From: jan at intevation.de (Jan-Oliver Wagner) Date: Tue, 8 Mar 2005 15:36:43 +0100 Subject: OGR extension In-Reply-To: <20050304151738.48354.qmail@web26509.mail.ukl.yahoo.com> References: <20050304151738.48354.qmail@web26509.mail.ukl.yahoo.com> Message-ID: <20050308143643.GA18094@intevation.de> On Fri, Mar 04, 2005 at 04:17:38PM +0100, Nina Hueffmeyer wrote: > not all features from Thuban work with the ogr > extension yet. This would need some changes in the > Thuban core files, which will be done after > implementing a new concept for extensions. > > For anybody, who already wants to take a look, I > attached a patch with all the changes you need to make > also functions like SessionTree, Save Session etc. > work. your work is just great. The OGR functionality works nicely! Only minor issues we might want to fix in the future. I will start on generalizing the Thuban core to allow to have a more general interface for adding extensions that add capability to handle further ShapeStore types. Nina: The first thing you could improve would be the SHAPETYPE constants. These should not have the magix numbers of OGR. Best would be to make a mapping inside the ogr extension. Jan -- Jan-Oliver Wagner http://intevation.de/~jan/ Intevation GmbH http://intevation.de/ FreeGIS http://freegis.org/ From jan at intevation.de Tue Mar 8 15:44:26 2005 From: jan at intevation.de (Jan-Oliver Wagner) Date: Tue, 8 Mar 2005 15:44:26 +0100 Subject: Introduce FileShapeStore Message-ID: <20050308144426.GB18094@intevation.de> Hi, attached is a patch that introduces the class FileShapeStore. This holds anything you need for specifiying the store for a loader routine. The Thuban internal ShapefileStore is now derived from it. I also have a working modification of the ogr extension where I have a OGRFileShapeStore and where I can load (so far only) shapefiles via OGR and save correct .thuban files. This patch is only the first step. As Nina wrote in her email on March 1st, we need a concept to let Extension communicate their abilities and to consider this in the load routine. This will be tackled next. Any comment on my patch or should I commit? Jan -- Jan-Oliver Wagner http://intevation.de/~jan/ Intevation GmbH http://intevation.de/ FreeGIS http://freegis.org/ From jan at intevation.de Tue Mar 8 15:45:30 2005 From: jan at intevation.de (Jan-Oliver Wagner) Date: Tue, 8 Mar 2005 15:45:30 +0100 Subject: Introduce FileShapeStore In-Reply-To: <20050308144426.GB18094@intevation.de> References: <20050308144426.GB18094@intevation.de> Message-ID: <20050308144530.GC18094@intevation.de> On Tue, Mar 08, 2005 at 03:44:26PM +0100, Jan-Oliver Wagner wrote: > attached is a patch that introduces the class FileShapeStore. _now_ it is. -- Jan-Oliver Wagner http://intevation.de/~jan/ Intevation GmbH http://intevation.de/ FreeGIS http://freegis.org/ -------------- next part -------------- Index: data.py =================================================================== RCS file: /thubanrepository/thuban/Thuban/Model/data.py,v retrieving revision 1.15 diff -u -3 -p -r1.15 data.py --- data.py 24 Nov 2003 19:23:08 -0000 1.15 +++ data.py 8 Mar 2005 14:41:27 -0000 @@ -113,28 +113,101 @@ class ShapeTable(transientdb.AutoTransie return (self.store(),) -class ShapefileStore: +class FileShapeStore: + + """The base class to derive any file-based ShapeStore from. + + This class contains all information that is needed by a + loader routine to actually load the shapestore. + This essentially means that the class contains all required information + to save the shapestore specification (i.e. in a .thuban file). + """ + + def __init__(self, filename, filetype, layername = None): + """Initialize the base class with main parameters. + + filename -- the source filename. + This filename will be converted to an absolute filename. + The filename will be interpreted relative to that anyway, + but when saving a session we need to compare absolute paths + and it's usually safer to always work with absolute paths. + filetype -- The type of the filename. + Known and used types are: "shapefile" + layername -- a string representing a layer within the file shape store. + Some file formats support to contain several layers, or + at least the ogr library says so. + For those filetypes who don't, the layername can be ignored + and by default it is None. + """ + self._filename = os.path.abspath(filename) + self._filetype = filetype + self._layername = layername + self._bbox = None + self._table = None + + def FileName(self): + """Return the filename used to open the shapestore. + + The filename can only be set via __init__ method. + """ + return self._filename + + def FileType(self): + """Return the filetype. + + The filetype has to be set in via __init__ method. + """ + return self._filetype + + def layerName(self): + """Return the layername. + + This could be None if the shapestore type only supports a single + layer. + """ + return self._layername + + def setTable(self, table): + """Set the table containing the attribute data.""" + self._table = table + + def Table(self): + """Return the table containing the attribute data.""" + return self._table + + def setBoundingBox(self, bbox): + """Set the bounding box of this shape store + + The coordinate system used is whatever was used in the shape store. + """ + self._bbox = bbox + + def BoundingBox(self): + """Return the bounding box of the shapes in the shape store. + + The coordinate system used is whatever was used in the shape store. + If the shape store is empty, return None. + """ + return self._bbox + +class ShapefileStore(FileShapeStore): """Combine a shapefile and the corresponding DBF file into one object""" def __init__(self, session, filename): - # Make the filename absolute. The filename will be - # interpreted relative to that anyway, but when saving a - # session we need to compare absolute paths and it's usually - # safer to always work with absolute paths. - self.filename = os.path.abspath(filename) + FileShapeStore.__init__(self, filename, "shapefile") self.dbftable = table.DBFTable(filename) - self.table = ShapeTable(self, session.TransientDB(), self.dbftable) + self.setTable(ShapeTable(self, session.TransientDB(), self.dbftable)) self._open_shapefile() def _open_shapefile(self): - self.shapefile = shapelib.ShapeFile(self.filename) + self.shapefile = shapelib.ShapeFile(self.FileName()) self.numshapes, shapetype, mins, maxs = self.shapefile.info() if self.numshapes: - self.bbox = mins[:2] + maxs[:2] + self.setBoundingBox(mins[:2] + maxs[:2]) else: - self.bbox = None + self.setBoundingBox(None) self.shapetype = shapelib_shapetypes[shapetype] # estimate a good depth for the quad tree. Each depth multiplies @@ -148,22 +221,10 @@ class ShapefileStore: self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2, maxdepth) - def Table(self): - """Return the table containing the attribute data""" - return self.table - def Shapefile(self): """Return the shapefile object""" return self.shapefile - def FileName(self): - """Return the filename used to open the shapefile""" - return self.filename - - def FileType(self): - """Return the filetype. This is always the string 'shapefile'""" - return "shapefile" - def ShapeType(self): """Return the type of the shapes in the shapestore. @@ -192,14 +253,6 @@ class ShapefileStore: The ShapefileStore was not derived from another shapestore. """ return None - - def BoundingBox(self): - """Return the bounding box of the shapes in the shapestore. - - The coordinate system used is whatever was used in the shapefile. - If the shapefile is empty, return None. - """ - return self.bbox def ShapesInRegion(self, bbox): """Return an iterable over the shapes that overlap the bounding box. Index: save.py =================================================================== RCS file: /thubanrepository/thuban/Thuban/Model/save.py,v retrieving revision 1.43 diff -u -3 -p -r1.43 save.py --- save.py 28 Jan 2005 15:54:00 -0000 1.43 +++ save.py 8 Mar 2005 14:41:27 -0000 @@ -26,7 +26,7 @@ from Thuban.Model.classification import ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap from Thuban.Model.transientdb import AutoTransientTable, TransientJoinedTable from Thuban.Model.table import DBFTable, FIELDTYPE_STRING -from Thuban.Model.data import DerivedShapeStore, ShapefileStore, \ +from Thuban.Model.data import DerivedShapeStore, FileShapeStore, \ SHAPETYPE_POINT from Thuban.Model.xmlwriter import XMLWriter @@ -201,12 +201,12 @@ class SessionSaver(XMLWriter): continue idvalue = self.define_id(container) - if isinstance(container, ShapefileStore): + if isinstance(container, FileShapeStore): self.define_id(container.Table(), idvalue) filename = self.prepare_filename(container.FileName()) self.write_element("fileshapesource", {"id": idvalue, "filename": filename, - "filetype": "shapefile"}) + "filetype": container.FileType()}) elif isinstance(container, DerivedShapeStore): shapesource, table = container.Dependencies() self.write_element("derivedshapesource", From cvs at intevation.de Thu Mar 10 08:51:45 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Thu, 10 Mar 2005 08:51:45 +0100 (CET) Subject: jan: thuban/libraries/thuban gdalwarp.cpp,1.1,1.1.2.1 Message-ID: <20050310075145.A04AF1006A5@lists.intevation.de> Author: jan Update of /thubanrepository/thuban/libraries/thuban In directory doto:/tmp/cvs-serv19467 Modified Files: Tag: thuban-1-0-branch gdalwarp.cpp Log Message: (MFILENAME): Fixed macro to compile on amd64 with gcc4 (int/long). Thanks to Andreas Jochens for submitting this patch. This fixes Debian bug #298403. Index: gdalwarp.cpp =================================================================== RCS file: /thubanrepository/thuban/libraries/thuban/gdalwarp.cpp,v retrieving revision 1.1 retrieving revision 1.1.2.1 diff -u -d -r1.1 -r1.1.2.1 --- gdalwarp.cpp 19 Aug 2003 21:32:24 -0000 1.1 +++ gdalwarp.cpp 10 Mar 2005 07:51:43 -0000 1.1.2.1 @@ -30,6 +30,11 @@ ****************************************************************************** * * $Log$ + * Revision 1.1.2.1 2005/03/10 07:51:43 jan + * (MFILENAME): Fixed macro + * to compile on amd64 with gcc4 (int/long). Thanks to Andreas Jochens + * for submitting this patch. This fixes Debian bug #298403. + * * Revision 1.1 2003/08/19 21:32:24 jan * These files have been moved here from thuban/extensions/thuban/ * See there in the Attic for the older history. @@ -106,7 +111,7 @@ */ #define MFILENAME(name, ptr) \ char name[ 8 + 2 * sizeof( void* ) + 1]; \ -{snprintf( name, sizeof(name), "\3\1\4MFILE%0*x", 2*sizeof(void*), (int)(ptr)); \ +{snprintf( name, sizeof(name), "\3\1\4MFILE%0*lx", 2*sizeof(void*), (long)(ptr)); \ memset( ptr, 0, sizeof( ptr ) );} From cvs at intevation.de Thu Mar 10 08:52:18 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Thu, 10 Mar 2005 08:52:18 +0100 (CET) Subject: jan: thuban ChangeLog,1.624.2.38,1.624.2.39 Message-ID: <20050310075218.3FAC11006A5@lists.intevation.de> Author: jan Update of /thubanrepository/thuban In directory doto:/tmp/cvs-serv19498 Modified Files: Tag: thuban-1-0-branch ChangeLog Log Message: fix Debian bug #298403 Index: ChangeLog =================================================================== RCS file: /thubanrepository/thuban/ChangeLog,v retrieving revision 1.624.2.38 retrieving revision 1.624.2.39 diff -u -d -r1.624.2.38 -r1.624.2.39 --- ChangeLog 16 Feb 2005 23:22:28 -0000 1.624.2.38 +++ ChangeLog 10 Mar 2005 07:52:15 -0000 1.624.2.39 @@ -1,3 +1,9 @@ +2005-03-10 Jan-Oliver Wagner + + * libraries/thuban/gdalwarp.cpp (MFILENAME): Fixed macro + to compile on amd64 with gcc4 (int/long). Thanks to Andreas Jochens + for submitting this patch. This fixes Debian bug #298403. + 2005-02-17 Jan-Oliver Wagner Backport from HEAD: Docstring improvements and minor fixes for labellayer. From cvs at intevation.de Thu Mar 10 23:39:14 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Thu, 10 Mar 2005 23:39:14 +0100 (CET) Subject: jan: thuban/Thuban/UI extensionregistry.py,1.1,1.2 Message-ID: <20050310223914.2282B1006CD@lists.intevation.de> Author: jan Update of /thubanrepository/thuban/Thuban/UI In directory doto:/tmp/cvs-serv3346 Modified Files: extensionregistry.py Log Message: (ExtensionDesc.__init__): Added »···optional parameter init_callback. ExtensionDesc.init_ext): New. Executes the callback and sets a status. Index: extensionregistry.py =================================================================== RCS file: /thubanrepository/thuban/Thuban/UI/extensionregistry.py,v retrieving revision 1.1 retrieving revision 1.2 diff -u -d -r1.1 -r1.2 --- extensionregistry.py 28 Sep 2004 19:13:21 -0000 1.1 +++ extensionregistry.py 10 Mar 2005 22:39:11 -0000 1.2 @@ -1,6 +1,6 @@ -# Copyright (C) 2004 by Intevation GmbH +# Copyright (C) 2004, 2005 by Intevation GmbH # Authors: -# Jan-Oliver Wagner +# Jan-Oliver Wagner (2004, 2005) # # This program is free software under the GPL (>=v2) # Read the file COPYING coming with Thuban for details. @@ -23,13 +23,16 @@ # $Source$ # $Id$ +from Thuban import _ + class ExtensionDesc: """ A description of a Thuban Extension. """ - def __init__(self, name, version, authors, copyright, desc): + def __init__(self, name, version, authors, copyright, desc, + init_callback = None): """ Initialize a new Thuban Extension information. @@ -50,13 +53,39 @@ Converts GNS (Geographical Name Service of NIMA) to Shapefile format and displays the data. ''') + init_callback -- a callback method that is executed if not + None. This method should contain gui specific + initializations of the extension. + Default is None. + This callback should return None if + the intialisation as successful. Else + a string describing the problems during + intialization. """ self.name = name self.version = version self.authors = authors self.copyright = copyright self.desc = desc + self.init_callback = init_callback + self.status = _("Initialization not yet requested.") + + def init_ext(self): + """Execute the init callback for the extension. + + Do nothing if the callback is None. + + It is expected that the callback function returns + None if all was OK and a string if something was wrong. + The string is believed to describe the problems. + """ + if self.init_callback is not None: + result = self.init_callback() + if result is not None: + self.status = result + else: + self.status = _("Initialization successful.") class ExtensionRegistry: From cvs at intevation.de Thu Mar 10 23:44:21 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Thu, 10 Mar 2005 23:44:21 +0100 (CET) Subject: jan: thuban/Thuban/UI application.py,1.38,1.39 Message-ID: <20050310224421.0800E1006CF@lists.intevation.de> Author: jan Update of /thubanrepository/thuban/Thuban/UI In directory doto:/tmp/cvs-serv3421 Modified Files: application.py Log Message: (ThubanApplication.OnInit): Add the initialization of the extensions. (ThubanApplication.init_extensions): Init all extensions. Index: application.py =================================================================== RCS file: /thubanrepository/thuban/Thuban/UI/application.py,v retrieving revision 1.38 retrieving revision 1.39 diff -u -d -r1.38 -r1.39 --- application.py 13 Dec 2004 11:52:34 -0000 1.38 +++ application.py 10 Mar 2005 22:44:18 -0000 1.39 @@ -1,4 +1,4 @@ -# Copyright (C) 2001, 2002, 2003, 2004 by Intevation GmbH +# Copyright (C) 2001-2005 by Intevation GmbH # Authors: # Jan-Oliver Wagner # Bernhard Herzog @@ -30,6 +30,8 @@ from Thuban.Model.layer import RasterLayer import Thuban.Model.resource +from extensionregistry import ext_registry + import view import tree import mainwindow @@ -71,6 +73,7 @@ if self.splash is not None: self.splash.Show() self.read_startup_files() + self.init_extensions() self.top = self.CreateMainWindow() # The session was alredy created above and we need to get the # map into the mainwindow. maps_changed does that. @@ -121,6 +124,11 @@ else: # There's no .thuban directory sys.stderr.write(_("No ~/.thuban directory\n")) + + def init_extensions(self): + """Call initialization callbacks for all registered extensions.""" + for ext in ext_registry: + ext.init_ext() def splash_screen(self): """Create and return a splash screen. From cvs at intevation.de Thu Mar 10 23:47:16 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Thu, 10 Mar 2005 23:47:16 +0100 (CET) Subject: jan: thuban/Thuban/UI about.py,1.20,1.21 Message-ID: <20050310224716.C1AD81006CF@lists.intevation.de> Author: jan Update of /thubanrepository/thuban/Thuban/UI In directory doto:/tmp/cvs-serv3471 Modified Files: about.py Log Message: (About.__init__): Added status of the extensions to the about text. Index: about.py =================================================================== RCS file: /thubanrepository/thuban/Thuban/UI/about.py,v retrieving revision 1.20 retrieving revision 1.21 diff -u -d -r1.20 -r1.21 --- about.py 20 Jan 2005 13:14:14 -0000 1.20 +++ about.py 10 Mar 2005 22:47:14 -0000 1.21 @@ -1,4 +1,4 @@ -# Copyright (c) 2001, 2002, 2003, 2004 by Intevation GmbH +# Copyright (c) 2001-2005 by Intevation GmbH # Authors: # Jonathan Coles # Bernhard Reiter @@ -125,6 +125,8 @@ for author in ext.authors: text+= '\t%s\n' % author text += ext.desc + text += '\n' + text += 'Status: %s' % ext.status text += '\n\n' else: text += _('\tNone registered.\n') From cvs at intevation.de Thu Mar 10 23:51:05 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Thu, 10 Mar 2005 23:51:05 +0100 (CET) Subject: jan: thuban/Extensions/gns2shp __init__.py,1.2,1.3 Message-ID: <20050310225105.D1FB41006CF@lists.intevation.de> Author: jan Update of /thubanrepository/thuban/Extensions/gns2shp In directory doto:/tmp/cvs-serv3525 Modified Files: __init__.py Log Message: Added init method for Extension description. (init): New. Contains the initialization of the module. Index: __init__.py =================================================================== RCS file: /thubanrepository/thuban/Extensions/gns2shp/__init__.py,v retrieving revision 1.2 retrieving revision 1.3 diff -u -d -r1.2 -r1.3 --- __init__.py 16 Nov 2004 21:15:49 -0000 1.2 +++ __init__.py 10 Mar 2005 22:51:03 -0000 1.3 @@ -1,17 +1,19 @@ -# Copyright (c) 2003, 2004 by Intevation GmbH +# Copyright (c) 2003-2005 by Intevation GmbH # Authors: -# Jan-Oliver Wagner (2003, 2004) +# Jan-Oliver Wagner (2003-2005) # # This program is free software under the GPL (>=v2) # Read the file COPYING coming with Thuban for details. -# import the actual module -import gns2shp - # perform the registration of the extension from Thuban import _ from Thuban.UI.extensionregistry import ExtensionDesc, ext_registry +def init(): + """Initialize gns2shp extension module.""" + import gns2shp + return None + ext_registry.add(ExtensionDesc( name = 'gns2shp', version = '1.0.0', @@ -19,4 +21,5 @@ copyright = '2003, 2004 Intevation GmbH', desc = _("Converts GNS (Geographical Name Service\n" \ "of NIMA) to Shapefile format and\n" \ - "displays the data."))) + "displays the data."), + init_callback = init)) From cvs at intevation.de Thu Mar 10 23:52:32 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Thu, 10 Mar 2005 23:52:32 +0100 (CET) Subject: jan: thuban ChangeLog,1.792,1.793 Message-ID: <20050310225232.6C74E1006CF@lists.intevation.de> Author: jan Update of /thubanrepository/thuban In directory doto:/tmp/cvs-serv3556 Modified Files: ChangeLog Log Message: Introducing initialization callbacks for extensions. Index: ChangeLog =================================================================== RCS file: /thubanrepository/thuban/ChangeLog,v retrieving revision 1.792 retrieving revision 1.793 diff -u -d -r1.792 -r1.793 --- ChangeLog 4 Mar 2005 15:07:59 -0000 1.792 +++ ChangeLog 10 Mar 2005 22:52:30 -0000 1.793 @@ -1,3 +1,23 @@ +2005-03-10 Jan-Oliver Wagner + + Introducing initialization callbacks for extensions. + + * Thuban/UI/extensionregistry.py (ExtensionDesc.__init__): Added + optional parameter init_callback. + (ExtensionDesc.init_ext): New. Executes the callback and sets + a status. + + * Thuban/UI/application.py (ThubanApplication.OnInit): Add the + initialization of the extensions. + (ThubanApplication.init_extensions): Init all extensions. + + * Thuban/UI/about.py (About.__init__): Added status of the extensions + to the about text. + + * Extensions/gns2shp/__init__.py: Added init method for Extension + description. + (init): New. Contains the initialization of the module. + 2005-03-04 Nina Hüffmeyer * Extensions/ogr/ogrdialog.py: Added a dialog, which asks for From jan at intevation.de Thu Mar 10 23:53:52 2005 From: jan at intevation.de (Jan-Oliver Wagner) Date: Thu, 10 Mar 2005 23:53:52 +0100 Subject: Finishing the extension registry feature In-Reply-To: <20050301191414.GH23103@intevation.de> References: <20050217112539.GA26955@intevation.de> <20050218171730.GD16375@intevation.de> <20050224224028.GA17919@intevation.de> <20050301191414.GH23103@intevation.de> Message-ID: <20050310225352.GA7265@intevation.de> On Tue, Mar 01, 2005 at 08:14:14PM +0100, Bernhard Reiter wrote: > Am 24. Feb 2005 um 23:40:28 schrieb Jan-Oliver Wagner: > > On Fri, Feb 18, 2005 at 06:17:30PM +0100, Bernhard Reiter wrote: > > > We could also change both to use a magic function in the package. > > > Doing this for registration first and then later for tests. > > > > > > I tend to favour the magic function solution a little bit over the > > > other, as we can later invent new magic functions for wx-dependent > > > test or what ever without changing names all the time. > > > > here is a improved approach. Less magic names and more > > automization via callbacks. > > > > >From there is it only small step to fully automize the > > loading of extensions in the Extensions directory :-) > > > > Patch OK to commit? > > Why not? Put it in ... :) done. > > + def init_extensions(self): > > + """Call initialization callbacks for allregistered extensions.""" > > Typo ^^^^ fixed. -- Jan-Oliver Wagner http://intevation.de/~jan/ Intevation GmbH http://intevation.de/ FreeGIS http://freegis.org/ From cvs at intevation.de Mon Mar 14 09:16:27 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Mon, 14 Mar 2005 09:16:27 +0100 (CET) Subject: jan: thuban/Doc/manual thuban-manual-de.xml,1.8,1.9 Message-ID: <20050314081627.C7FA81006CA@lists.intevation.de> Author: jan Update of /thubanrepository/thuban/Doc/manual In directory doto:/tmp/cvs-serv22519 Modified Files: thuban-manual-de.xml Log Message: More translation. Index: thuban-manual-de.xml =================================================================== RCS file: /thubanrepository/thuban/Doc/manual/thuban-manual-de.xml,v retrieving revision 1.8 retrieving revision 1.9 diff -u -d -r1.8 -r1.9 --- thuban-manual-de.xml 26 Jan 2005 08:14:55 -0000 1.8 +++ thuban-manual-de.xml 14 Mar 2005 08:16:25 -0000 1.9 @@ -1039,30 +1039,34 @@ Informationssystemen geeignet. - All actions in the + Alle Aktionen im Menü - Layer - menu act on the currently selected layer in the legend. + Ebene + beziehen sich auf die in der Legende aktuell + ausgewählten Ebene. -
Properties +
Eigenschaften - To view the properties for a layer it must first be selected in the - legend. The menu option + Um die Eigenschaften einer Ebene anzusehen muss zunächst eine Ebene + in der Legende selektiert werden. + Die Menüoption - Layer - Properties - opens a dialog that displays a layer's properties. - All layers have a title which can be modified in the text field - provided. The type of layer is also shows. If the type is a type - of shape (polygon, arc, point) the classification table will be - shown. Image layers have no other properties other than title - and type. + Ebene + Eigenschaften + öffnet einen Dialog zu den Eigenschaften + der Ebene. + Alle Ebenen haben einen Titel der hier entsprechend geändert werden kann. + Der Typ der Ebene ist ebenfalls ersichtlich. + Handelt es sich um einen Shape-Typ (Polygon, Linie, Punkt) so + wird eine Tabelle für Klassifizierung dargestellt. + Bildebenen haben keineweiteren Eigenschaften als + den Titel und Typ.
- Properties Window + Eigenschaften Fenster @@ -1071,7 +1075,7 @@
- Properties Window + Eigenschaften Fenster @@ -1080,74 +1084,85 @@
-
Visibility +
Sichtbarkeit - Sometimes it is not desirable to view all layers at the same time. - Some layers may take a long time to draw and so while navigating - around the map the user may not want to wait for the map to redraw - all the layers each time the map is changed. Each layer can be - independently turned on or off using the + Manchmal ist es nicht erwünscht alle Ebenen gleichzeitig anzuzeigen. + Einige Ebenen könnten etwas länger für den Aufbau brauchen, was + z.B. störend bei schneller Navigation ist. + Jede Ebene kann einzeln an- oder ausgeschaltet werden mit - Layer - Show + Ebene + Zeigen - or + bzw. - Layer - Hide - options respectively. + Ebene + Verstecken + .
-
Duplication +
Duplizierung - Layers and all their properties, including classifications, can - be duplicated using + Ebenen mit allen Eigenschaften inklusive Klassifikation + können dupliziert werden mit - Layer - Duplicate - . Duplicating a layer is useful if the user wishes - to model a layer in several different ways. Even though the layers - overlap, by carefully selecting the shape properties it is possible - to display several pieces of information at once. For example, one - copy of a roads layer may be classified on a length property and - another copy may be classified on a type property. If the length - property was expressed with color and the type property expressed - with line thickness then it would be possible to view both - classifications by placing the type property copy over the - length property copy. + Ebene + Duplizieren + . Die Duplizierung von Ebenen ist sinnvoll + z.B. wenn eine Ebene auf verschiedene Weise modelliert werden + soll. Obwohl sich die Ebenen direkt überlagern ist es + durch sinnvolle Einstellung der Shape Eigenschaften möglich + mehrer Aspekte gleichzeitig zu visualisieren. + Beispielsweise kann eine Kopie einer Strassen-Ebene + bezüglich der Länge klassifiziert sein und eine + weitere Kopie bezüglich Strassen-Typ. + Wenn dann die Länge durch Farbe und der Typ durch Linienbreite + gesetzt wird, dann ist es möglich beide Eigenschaften zu + visualisieren indem die Kopie für den Typ über die Kopie mit + der Länge gelegt wird.
- Layer Classifications + Ebenen-Klassifizierung - A layer classification is a way of assigning drawing properties to - groups of shapes based on attributes stored in the layer's table. - Only layer's with shapes can have a classification; image layers - cannot be classified. + Die Klassifizierung einer Ebene bedeutet das zuordnen von + Zeichenvorschriften zu Shape-Gruppen basierend auf deren Attributen + die in zur Ebene gehörenden Tabelle gespeichert sind. + Es können daher auch nur Shape-Ebenen klassifiziert werden und + keine Bild-Ebenen. - A classification consists of a number of groups, each group - having a value or range of values to match against, and symbol - properties which control how a shape is drawn on the map. The user - selects which field in the table is used by the classification and - when the map is drawn the value for that field for each shape is - compared with each group's value. The properties of the first group - to match are used to draw the shape. This allows the user to get a - visual impression of not only how the data is laid out but also what - kind of data lies where. + Eine Klassifizierung besteht aus mehreren Grupppen. Jede Gruppe + hat einen einzelnen Wert oder einen Wertebereich auf den hin + verglichen wird. Ausserdem hat jede Gruppe eine Zeichenvorschrift + welche bestimmt wie die zugehörigen Shapes dargestellt werden + sollen. + Der Anwender gibt an welches Feld der Tabelle für Klassifizierung + verwendet werden soll. Wird die Karte dann gezeichnet so + wird für jedes Shape der entsprechende Wert aus der Tabelle + geholt und mit den definierten Gruppen-Wertebereichen + verglichen. Es werden dann die Zeichenvorschriften + derjenigen Gruppe benutzt, welche als erstes beim Vergleich + passte. + Dies gestattet es einen visuellen Eindruck z.B. + der räumlichen Verteilung von Objekten mit + bestimmten Eigenschaften zu erhalten. - A layer always has a classification. When a new layer is added to the - map, a default classification is created with the DEFAULT group. This - group cannot be removed but can be hidden (see below). Every shape in the - layer, regardless of its attributes, will match this group if no other - group matches. + Eine Ebene hat grundsätzlich eine Klassifizierung. + Wird zur Karte eine neue Ebene hinzugefügt, + wird sie mit einer vordefinierten Klassifizierung + versehen. Diese besteht nur aus der einen Gruppe DEFAULT. + Diese Gruppe kann man nicht entfernen, lässt sich aber + verstecken (siehe unten). + Jedes Shape der Ebene, unabhängig von dessen Attributen, wird + diese Gruppe zugeordnet, falls nicht vorher eine andere passt. -
Editing Classifications +
Klassifizierung bearbeiten A layer's classification can be modified under the properties dialog ( From cvs at intevation.de Mon Mar 14 09:17:10 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Mon, 14 Mar 2005 09:17:10 +0100 (CET) Subject: jan: thuban ChangeLog,1.793,1.794 Message-ID: <20050314081710.360E91006C7@lists.intevation.de> Author: jan Update of /thubanrepository/thuban In directory doto:/tmp/cvs-serv22589 Modified Files: ChangeLog Log Message: More translation. Index: ChangeLog =================================================================== RCS file: /thubanrepository/thuban/ChangeLog,v retrieving revision 1.793 retrieving revision 1.794 diff -u -d -r1.793 -r1.794 --- ChangeLog 10 Mar 2005 22:52:30 -0000 1.793 +++ ChangeLog 14 Mar 2005 08:17:08 -0000 1.794 @@ -1,3 +1,7 @@ +2005-03-14 Jan-Oliver Wagner + + * Doc/manual/thuban-manual-de.xml: More translations. + 2005-03-10 Jan-Oliver Wagner Introducing initialization callbacks for extensions. From bh at intevation.de Mon Mar 14 21:11:33 2005 From: bh at intevation.de (Bernhard Herzog) Date: Mon, 14 Mar 2005 21:11:33 +0100 Subject: Introduce FileShapeStore References: <20050308144426.GB18094@intevation.de> <20050308144530.GC18094@intevation.de> Message-ID: Jan-Oliver Wagner writes: > +class FileShapeStore: > + > + def layerName(self): Why is this not spelled LayerName or layer_name? LayerName would be consistent with the other public methods. If we want to start with the lower-case names we should prefer lower_case_with underscore over mixedCase because that's what Python's PEP 8 suggests as well and because I like it better, too :) > + def setTable(self, table): > + """Set the table containing the attribute data.""" > + self._table = table > + > + def Table(self): > + """Return the table containing the attribute data.""" > + return self._table I don't think these should be in the base class. They are suitable for the ShapefileStore and currently also the OGRShapeStore, but for the PostGISShapeStore these implementations would be useless. A PostGISShapeStore is its own table so the Table method simply returns self. The OGRShapeStore would be better off using the same design, I think, leaving only the ShapefileStore with a distinct table object. From cvs at intevation.de Wed Mar 16 15:55:23 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Wed, 16 Mar 2005 15:55:23 +0100 (CET) Subject: bh: thuban/test test_connector.py,1.4,1.5 Message-ID: <20050316145523.9890A1005D0@lists.intevation.de> Author: bh Update of /thubanrepository/thuban/test In directory doto:/tmp/cvs-serv25641/test Modified Files: test_connector.py Log Message: (DeletionTestMixin.check_deletions) (DeletionTestMixin.check_deletetions): renamed to check_deletions. update the callers. Index: test_connector.py =================================================================== RCS file: /thubanrepository/thuban/test/test_connector.py,v retrieving revision 1.4 retrieving revision 1.5 diff -u -d -r1.4 -r1.5 --- test_connector.py 18 Nov 2003 13:18:48 -0000 1.4 +++ test_connector.py 16 Mar 2005 14:55:21 -0000 1.5 @@ -1,4 +1,4 @@ -# Copyright (c) 2002, 2003 by Intevation GmbH +# Copyright (c) 2002, 2003, 2004 by Intevation GmbH # Authors: # Bernhard Herzog # @@ -143,7 +143,7 @@ """Append the id of obj to the self.deleted_objects""" self.deleted_objects.append(id(obj)) - def check_deletetions(self): + def check_deletions(self): """Assert equality of self.expected_deletions and self.deleted_objects This check simply compares the lists for equality and thus @@ -196,7 +196,7 @@ # make sure that all references have been deleted del rec - self.check_deletetions() + self.check_deletions() def test_issue_param(self): """Test connector issue with parameters""" @@ -217,7 +217,7 @@ # make sure that all references have been deleted self.connector.RemovePublisher(pub) del rec - self.check_deletetions() + self.check_deletions() def test_cyclic_references(self): """Test whether connector avoids cyclic references""" @@ -231,7 +231,7 @@ # method removes all subscriptions del pub del rec - self.check_deletetions() + self.check_deletions() def test_disconnect_in_receiver(self): """Test unsubscribing from a channel while receiving a message @@ -296,7 +296,7 @@ # make sure that all references have been deleted del rec - self.check_deletetions() + self.check_deletions() def test_issue_param(self): """Test Publisher message with parameters""" @@ -317,7 +317,7 @@ # make sure that all references have been deleted pub.Destroy() del rec - self.check_deletetions() + self.check_deletions() def test_cyclic_references(self): """Test whether Publisher avoids cyclic references""" @@ -331,7 +331,7 @@ # method removes all subscriptions del pub del rec - self.check_deletetions() + self.check_deletions() def test_unsubscribe_after_destroy(self): """Test that Unsubscribe() does not raise exceptions after a Destroy""" From cvs at intevation.de Wed Mar 16 15:55:23 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Wed, 16 Mar 2005 15:55:23 +0100 (CET) Subject: bh: thuban ChangeLog,1.794,1.795 Message-ID: <20050316145523.ABD6E1006BF@lists.intevation.de> Author: bh Update of /thubanrepository/thuban In directory doto:/tmp/cvs-serv25641 Modified Files: ChangeLog Log Message: (DeletionTestMixin.check_deletions) (DeletionTestMixin.check_deletetions): renamed to check_deletions. update the callers. Index: ChangeLog =================================================================== RCS file: /thubanrepository/thuban/ChangeLog,v retrieving revision 1.794 retrieving revision 1.795 diff -u -d -r1.794 -r1.795 --- ChangeLog 14 Mar 2005 08:17:08 -0000 1.794 +++ ChangeLog 16 Mar 2005 14:55:21 -0000 1.795 @@ -1,3 +1,9 @@ +2005-03-16 Bernhard Herzog + + * test/test_connector.py (DeletionTestMixin.check_deletions) + (DeletionTestMixin.check_deletetions): renamed to check_deletions. + update the callers. + 2005-03-14 Jan-Oliver Wagner * Doc/manual/thuban-manual-de.xml: More translations. From cvs at intevation.de Wed Mar 23 16:30:29 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Wed, 23 Mar 2005 16:30:29 +0100 (CET) Subject: jonathan: thuban/Thuban/Model layer.py,1.63,1.64 Message-ID: <20050323153029.A76C31006AA@lists.intevation.de> Author: jonathan Update of /thubanrepository/thuban/Thuban/Model In directory doto:/tmp/cvs-serv32375/Thuban/Model Modified Files: layer.py Log Message: Add support for adjusting the opacity of a raster layer. Index: layer.py =================================================================== RCS file: /thubanrepository/thuban/Thuban/Model/layer.py,v retrieving revision 1.63 retrieving revision 1.64 diff -u -d -r1.63 -r1.64 --- layer.py 16 Feb 2005 21:14:47 -0000 1.63 +++ layer.py 23 Mar 2005 15:30:27 -0000 1.64 @@ -372,7 +372,7 @@ self.bbox = -1 self.mask_type = self.MASK_BIT - self.alpha_opacity = 1 + self.opacity = 1 self.image_info = None @@ -481,16 +481,12 @@ self.mask_type = type self.changed(LAYER_CHANGED, self) - def AlphaOpacity(self): - """Return the level of opacity used in alpha blending, or None - if mask type is not MASK_ALPHA. + def Opacity(self): + """Return the level of opacity used in alpha blending. """ - if self.mask_type == self.MASK_ALPHA: - return self.alpha_opacity - else: - return None + return self.opacity - def SetAlphaOpacity(self, op): + def SetOpacity(self, op): """Set the level of alpha opacity. 0 <= op <= 1. @@ -500,7 +496,9 @@ if not (0 <= op <= 1): raise ValueError("op out of range") - self.alpha_opacity = op + if op != self.opacity: + self.opacity = op + self.changed(LAYER_CHANGED, self) def ImageInfo(self): return self.image_info From cvs at intevation.de Wed Mar 23 16:30:29 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Wed, 23 Mar 2005 16:30:29 +0100 (CET) Subject: jonathan: thuban ChangeLog,1.795,1.796 Message-ID: <20050323153029.A4D2D1005D7@lists.intevation.de> Author: jonathan Update of /thubanrepository/thuban In directory doto:/tmp/cvs-serv32375 Modified Files: ChangeLog Log Message: Add support for adjusting the opacity of a raster layer. Index: ChangeLog =================================================================== RCS file: /thubanrepository/thuban/ChangeLog,v retrieving revision 1.795 retrieving revision 1.796 diff -u -d -r1.795 -r1.796 --- ChangeLog 16 Mar 2005 14:55:21 -0000 1.795 +++ ChangeLog 23 Mar 2005 15:30:27 -0000 1.796 @@ -1,3 +1,30 @@ +2005-03-23 Jonathan Coles + + These changes add support for adjusting the opacity of a raster layer. + + * Thuban/Model/layer.py (RasterLayer.Opacity): Replaces AlphaOpacity. + (RasterLayer.SetOpacity): Replaces SetAlphaOpacity. Also triggers + a LAYER_CHANGED event if the opacity actually changes. + + * Thuban/UI/baserenderer.py (BaseRenderer.draw_raster_data): Added + layer parameter needed in the implementation of this method in + renderer.py. + + * Thuban/UI/layerproperties.py (LayerProperties.dialog_layout): Fixed + typo 00 -> 0. + + * Thuban/UI/rasterlayerproperties.py (RasterLayerProperties): Added + control to adjust opacity. + + * Thuban/UI/renderer.py (MapRenderer.draw_raster_data): Scale the + alpha data based on the opacity level of the layer. + + * test/test_baserenderer.py (SimpleRenderer.draw_raster_data): Now + accepts a layer parameter. + + * test/test_layer.py (TestLayerModification.test_raster_layer): + Rename opacity method calls and add test for LAYER_CHANGED. + 2005-03-16 Bernhard Herzog * test/test_connector.py (DeletionTestMixin.check_deletions) From cvs at intevation.de Wed Mar 23 16:30:29 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Wed, 23 Mar 2005 16:30:29 +0100 (CET) Subject: jonathan: thuban/test test_baserenderer.py, 1.13, 1.14 test_layer.py, 1.35, 1.36 Message-ID: <20050323153029.EFACC1006AA@lists.intevation.de> Author: jonathan Update of /thubanrepository/thuban/test In directory doto:/tmp/cvs-serv32375/test Modified Files: test_baserenderer.py test_layer.py Log Message: Add support for adjusting the opacity of a raster layer. Index: test_baserenderer.py =================================================================== RCS file: /thubanrepository/thuban/test/test_baserenderer.py,v retrieving revision 1.13 retrieving revision 1.14 diff -u -d -r1.13 -r1.14 --- test_baserenderer.py 18 Feb 2005 19:28:45 -0000 1.13 +++ test_baserenderer.py 23 Mar 2005 15:30:27 -0000 1.14 @@ -91,7 +91,7 @@ def label_font(self): return "label font" - def draw_raster_data(self, x, y, data, format='BMP'): + def draw_raster_data(self, layer, x, y, data, format='BMP'): self.raster_data = data self.raster_format = format Index: test_layer.py =================================================================== RCS file: /thubanrepository/thuban/test/test_layer.py,v retrieving revision 1.35 retrieving revision 1.36 diff -u -d -r1.35 -r1.36 --- test_layer.py 16 Feb 2005 21:14:47 -0000 1.35 +++ test_layer.py 23 Mar 2005 15:30:27 -0000 1.36 @@ -485,18 +485,25 @@ layer.SetMaskType(layer.MASK_ALPHA) self.failIf(layer.MaskType() != layer.MASK_ALPHA) - layer.SetAlphaOpacity(0) - self.assertEquals(layer.AlphaOpacity(), 0) - layer.SetAlphaOpacity(0.5) - self.assertEquals(layer.AlphaOpacity(), 0.5) - layer.SetAlphaOpacity(1) - self.assertEquals(layer.AlphaOpacity(), 1) + layer.SetOpacity(0) + self.assertEquals(layer.Opacity(), 0) + layer.SetOpacity(0.5) + self.assertEquals(layer.Opacity(), 0.5) - self.assertRaises(ValueError, layer.SetAlphaOpacity, -0.1) - self.assertRaises(ValueError, layer.SetAlphaOpacity, 1.1) + self.clear_messages() + layer.SetOpacity(1) + self.assertEquals(layer.Opacity(), 1) + self.check_messages([(layer, LAYER_CHANGED)]) + self.clear_messages() + + self.assertRaises(ValueError, layer.SetOpacity, -0.1) + self.assertRaises(ValueError, layer.SetOpacity, 1.1) layer.SetMaskType(layer.MASK_NONE) - self.assertEquals(layer.AlphaOpacity(), None) + self.clear_messages() + self.assertEquals(layer.Opacity(), 1) + self.check_messages([]) + self.clear_messages() self.assertRaises(ValueError, layer.SetMaskType, -1) self.assertRaises(ValueError, layer.SetMaskType, 4) From cvs at intevation.de Wed Mar 23 16:30:29 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Wed, 23 Mar 2005 16:30:29 +0100 (CET) Subject: jonathan: thuban/Thuban/UI baserenderer.py, 1.18, 1.19 layerproperties.py, 1.1, 1.2 rasterlayerproperties.py, 1.2, 1.3 renderer.py, 1.58, 1.59 Message-ID: <20050323153029.D4E2F1006AF@lists.intevation.de> Author: jonathan Update of /thubanrepository/thuban/Thuban/UI In directory doto:/tmp/cvs-serv32375/Thuban/UI Modified Files: baserenderer.py layerproperties.py rasterlayerproperties.py renderer.py Log Message: Add support for adjusting the opacity of a raster layer. Index: baserenderer.py =================================================================== RCS file: /thubanrepository/thuban/Thuban/UI/baserenderer.py,v retrieving revision 1.18 retrieving revision 1.19 diff -u -d -r1.18 -r1.19 --- baserenderer.py 18 Feb 2005 14:54:17 -0000 1.18 +++ baserenderer.py 23 Mar 2005 15:30:27 -0000 1.19 @@ -488,7 +488,8 @@ if img_data is not None: data = (width, height, img_data) - self.draw_raster_data(fmin[0]+offx, offy-fmax[1], data, "RAW") + self.draw_raster_data(layer, fmin[0]+offx, offy-fmax[1], + data, "RAW") data = None def projected_raster_layer(self, layer, srcProj, dstProj, extents, @@ -522,8 +523,8 @@ raise NotImplementedError - def draw_raster_data(self, x, y, data, format="BMP"): - """Draw the raster image in data onto the DC with the top + def draw_raster_data(self, layer, x, y, data, format="BMP"): + """Draw the layer's raster image held in data onto the DC with the top left corner at (x,y) The raster image data is a tuple of the form Index: layerproperties.py =================================================================== RCS file: /thubanrepository/thuban/Thuban/UI/layerproperties.py,v retrieving revision 1.1 retrieving revision 1.2 diff -u -d -r1.1 -r1.2 --- layerproperties.py 27 Jan 2005 14:19:41 -0000 1.1 +++ layerproperties.py 23 Mar 2005 15:30:27 -0000 1.2 @@ -56,7 +56,7 @@ text_title = wxTextCtrl(panel, ID_PROPERTY_TITLE, self.layer.Title()) text_title.SetInsertionPointEnd() - sizer.Add(text_title, 1, wxGROW | wxRIGHT, 00) + sizer.Add(text_title, 1, wxGROW | wxRIGHT, 0) panelBox.Add(sizer, 0, wxGROW | wxALL, 4) Index: rasterlayerproperties.py =================================================================== RCS file: /thubanrepository/thuban/Thuban/UI/rasterlayerproperties.py,v retrieving revision 1.2 retrieving revision 1.3 diff -u -d -r1.2 -r1.3 --- rasterlayerproperties.py 16 Feb 2005 21:14:47 -0000 1.2 +++ rasterlayerproperties.py 23 Mar 2005 15:30:27 -0000 1.3 @@ -19,6 +19,8 @@ from Thuban.version import versions +ID_RB_MASK = 4002 + class RasterLayerProperties(LayerProperties): def __init__(self, parent, name, layer, *args, **kw): @@ -88,16 +90,32 @@ else: choices = ["None", "Bitmap", "Alpha"] - self.maskRadioBox = wxRadioBox(panel, -1, _("Mask Type"), + self.maskRadioBox = wxRadioBox(panel, ID_RB_MASK, _("Mask Type"), choices=choices) #self.maskCB = wxCheckBox(panel, -1, _("Use Mask")) maskBox.Add(self.maskRadioBox, 0, wxRIGHT, 10) + self.opBox = wxBoxSizer(wxHORIZONTAL) + self.opSpinLabel = wxStaticText(panel, -1, _("Opacity:")) + self.opBox.Add(self.opSpinLabel, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 4) + self.opSpin = wxSpinCtrl(panel, -1, + str(self.layer.Opacity()*255), + initial = self.layer.Opacity()*255, + min=0, max=255) + self.opBox.Add(self.opSpin, 0, wxALL, 4) + maskBox.Add(self.opBox, 0, wxALL|wxALIGN_CENTER_VERTICAL, 4) + rasterBox.Add(maskBox, 0, wxALL, 4) + #rasterBox.Add(opBox, 0, wxALL, 4) + panelBox.Add(rasterBox, 1, wxGROW | wxALL, 4) self.maskRadioBox.SetSelection(self.old_state["mask_type"]) + self.OnMaskSelect(None) + + EVT_RADIOBOX(self, ID_RB_MASK, self.OnMaskSelect) + def OnTry(self, event): self.set_state() @@ -109,6 +127,12 @@ self.maskRadioBox.SetSelection(self.old_state["mask_type"]) self.set_state() + def OnMaskSelect(self, event): + allowOpacity = self.maskRadioBox.GetSelection()==2 + self.opSpin.Enable(allowOpacity) + self.opSpinLabel.Enable(allowOpacity) + def set_state(self): self.layer.SetMaskType(self.maskRadioBox.GetSelection()) + self.layer.SetOpacity(self.opSpin.GetValue()/255.0) return True Index: renderer.py =================================================================== RCS file: /thubanrepository/thuban/Thuban/UI/renderer.py,v retrieving revision 1.58 retrieving revision 1.59 diff -u -d -r1.58 -r1.59 --- renderer.py 18 Feb 2005 14:54:17 -0000 1.58 +++ renderer.py 23 Mar 2005 15:30:27 -0000 1.59 @@ -16,6 +16,8 @@ import cStringIO +import array + from Thuban import _ from wxPython.wx import wxPoint, wxRect, wxPen, wxBrush, wxFont, \ @@ -135,9 +137,10 @@ return ret - def draw_raster_data(self, x,y, data, format = 'BMP'): + def draw_raster_data(self, layer, x,y, data, format = 'BMP'): mask = None + alpha = None width = data[0] height = data[1] image_data, mask_data, alpha_data = data[2] @@ -152,7 +155,9 @@ mask = wxBitmapFromBits(mask_data, width, height, 1) mask = wxMask(mask) elif alpha_data is not None: - image.SetAlphaData(alpha_data) + # alpha_data is already in the right format + alpha = alpha_data + else: stream = cStringIO.StringIO(image_data) image = wxImageFromStream(stream, raster_format_map[format]) @@ -163,10 +168,29 @@ elif alpha_data is not None: stream = cStringIO.StringIO(alpha_data) alpha = wxImageFromStream(stream, raster_format_map[format]) - image.SetAlpha(alpha.GetData()[:]) + alpha = alpha.GetData()[:] # XXX: do we need to copy this? + + # + # if we are using the alpha_data then scale down the alpha values + # by the layer's opacity using a string translation table + # + if alpha is not None: + lo = layer.Opacity() + if lo == 0: + return + elif lo == 1: + a = alpha + else: + tr = [int(i*lo) for i in range(256)] + table = array.array('B', tr).tostring() + a = alpha.translate(table) + + image.SetAlphaData(a) bitmap = wxBitmapFromImage(image) - bitmap.SetMask(mask) + + if mask is not None: + bitmap.SetMask(mask) self.dc.DrawBitmap(bitmap, int(round(x)), int(round(y)), True) From jonathan at jpcoles.com Wed Mar 23 16:41:44 2005 From: jonathan at jpcoles.com (Jonathan Coles) Date: Wed, 23 Mar 2005 10:41:44 -0500 Subject: Raster layer opacity Message-ID: <1111592504.14081.14.camel@localhost.localdomain> Hi all, sorry I disappeared for a couple of months, but I had a little matter of a master's thesis to attend to. :) I've just commited changes that allow the opacity of a raster layer to be changed. Previously, (in anticipation of these changes) a raster layer had an opacity property but it was not used. I've added a new control to the layer properties box for raster layers. The level is then used in MapRenderer.draw_raster_data() to scale the alpha values to the proper opacity. I chose to implement the scaling in MapRenderer.draw_raster_data() as opposed to BaseRenderer.draw_raster_layer() because doing the scaling late in the rendering process allows for all types of raster layer formats to be adjusted, not just those that are handled in BaseRenderer.draw_raster_layer(). This decision, however, required that I add the layer to the parameter list of MapRenderer.draw_raster_data(). For a simple demonstration, move the iceland raster layer to the top and set the opacity to about 128. remember: completely opaque=255, completely transparent=0. Here'a question: Is opacity a property of all layers or just raster layers? perhaps we can't support opacity in vectors layers just yet, but should that mean we shouldn't anticipate it? If opacity is a property of all layers then I can refactor the opacity methods in RasterLayer into BaseLayer. --jonathan -- ===================================================================== Jonathan Coles http://www.jpcoles.com jonathan at jpcoles.com GnuPG Key: /gpg_pub_key.asc ===================================================================== From jonathan at jpcoles.com Fri Mar 25 04:43:41 2005 From: jonathan at jpcoles.com (Jonathan Coles) Date: Thu, 24 Mar 2005 22:43:41 -0500 Subject: Raster layer opacity In-Reply-To: <1111592504.14081.14.camel@localhost.localdomain> References: <1111592504.14081.14.camel@localhost.localdomain> Message-ID: <1111722221.14081.43.camel@localhost.localdomain> Am Mittwoch, den 23.03.2005, 10:41 -0500 schrieb Jonathan Coles: > Here'a question: Is opacity a property of all layers or just raster > layers? perhaps we can't support opacity in vectors layers just yet, but > should that mean we shouldn't anticipate it? If opacity is a property of > all layers then I can refactor the opacity methods in RasterLayer into > BaseLayer. further questions: a) if alpha blending is not available (i.e. wxWidgets < 2.5.3) should the renderer fall back to using a bitmask, or should it just draw the entire image as though no masking was selected. the latter is the current behaviour. even if the renderer falls back to a bitmask, the selected option in the properties dialog should remain alpha. b) should the masking information be added to the session file? now there are three options for masking: none, bitmap, and alpha. alpha has a level setting too. c) this is question which addresses an HCI issue. i'll give a specific example first, but this applies to a more general problem. i'll assume for the sake of argument that we decide to save the masking setting in the session file. let's say that alpha blending is selected with opacity level 128. the session is saved. later the method is changed to use a bitmap (for whatever reason). the session is saved again. in the RasterLayer class, as it stands now, the mask type and the opacity are two independent settings. conceivably, the opacity level can stay in the session file regardless of the mask setting. the benefit would be that if the user decides to revert back to using alpha blending, the original opacity level is still available. so the question here is: is this acceptable behaviour? to make this a more general question: should we save information about the state of an option that has used in the past, whether it is currently used by the active session or not, so that when/if the user decides to return to past settings (even across multiple sessions), they don't have to remember (or waste time recalculating) past values? i think this can be a very powerful feature if we decide that "yes" is the answer. although there may be some objection in that this can increase the size and/or complexity of the session file, please remember that our objective should be to make a product that improves the lives of the *users* not the developers (although this is a good thing too :) thanks, --jonathan -- ===================================================================== Jonathan Coles http://www.jpcoles.com jonathan at jpcoles.com GnuPG Key: /gpg_pub_key.asc ===================================================================== From dcalvelo at minag.gob.pe Fri Mar 25 05:57:48 2005 From: dcalvelo at minag.gob.pe (Daniel Calvelo Aros) Date: Thu, 24 Mar 2005 23:57:48 -0500 Subject: Raster layer opacity In-Reply-To: <1111592504.14081.14.camel@localhost.localdomain> References: <1111592504.14081.14.camel@localhost.localdomain> Message-ID: <20050325044941.M35422@minag.gob.pe> From: Jonathan Coles Sent: Wed, 23 Mar 2005 10:41:44 -0500 > > Here'a question: Is opacity a property of all layers or just raster > layers? perhaps we can't support opacity in vectors layers just yet, > but should that mean we shouldn't anticipate it? If opacity is a > property of all layers then I can refactor the opacity methods in > RasterLayer into BaseLayer. I'd say opacity should be considered for vectors as well. Imagine the possibility of visually several merging polygonal filled areas with raster background info. I'm not sure what must be done about it, though. BTW, has anyone looked at the Anti-Grain Geometry library (http://antigrain.com)? How difficult would it be to use that for rendering to a bitmap and copy this into a DC instead of using wxDC functions? Is the cost of such a development worth the gain of antialiasing and transparency? Daniel. PS. For a very nice application of agg, see http://matplotlib.sourceforge.net, and particularly http://matplotlib.sourceforge.net/matplotlib.toolkits.basemap.basemap.html From bernhard at intevation.de Tue Mar 29 12:15:24 2005 From: bernhard at intevation.de (Bernhard Reiter) Date: Tue, 29 Mar 2005 12:15:24 +0200 Subject: Vector opacity (Re: Raster layer opacity) In-Reply-To: <1111592504.14081.14.camel@localhost.localdomain> References: <1111592504.14081.14.camel@localhost.localdomain> Message-ID: <20050329101524.GI22363@intevation.de> Am 23. Mar 2005 um 10:41:44 schrieb Jonathan Coles: > sorry I disappeared for a couple of months, but I had a little matter of > a master's thesis to attend to. :) Hi Jonathan, glad to see you are back and in good shape, this is a good sign for the thetis I presume. ;) > I've just commited changes that allow the opacity of a raster layer to > be changed. Previously, (in anticipation of these changes) a raster > layer had an opacity property but it was not used. I've added a new > control to the layer properties box for raster layers. The level is then > used in MapRenderer.draw_raster_data() to scale the alpha values to the > proper opacity. Very Cool! I had to change laptops and my development environment is not there yet, so I cannot immedeately try it out. It will take few more days... :) > Here'a question: Is opacity a property of all layers or just raster > layers? perhaps we can't support opacity in vectors layers just yet, but > should that mean we shouldn't anticipate it? If opacity is a property of > all layers then I can refactor the opacity methods in RasterLayer into > BaseLayer. I also think that vectors can have opacity, the question is if this will be per vector layer or per drawing style per class. And it we have that drawing style option, do we want another layer wide option? Both seems to be nice features with the per style opacity being more general, but the per layer one might be consistant and easier to add. I have not real conclusion, I consider a layer side attribute for okay and useful, though. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 189 bytes Desc: not available Url : http://www.intevation.de/pipermail/thuban-devel/attachments/20050329/3229ac13/attachment.bin From bernhard at intevation.de Tue Mar 29 12:22:04 2005 From: bernhard at intevation.de (Bernhard Reiter) Date: Tue, 29 Mar 2005 12:22:04 +0200 Subject: Raster layer opacity In-Reply-To: <1111722221.14081.43.camel@localhost.localdomain> References: <1111592504.14081.14.camel@localhost.localdomain> <1111722221.14081.43.camel@localhost.localdomain> Message-ID: <20050329102204.GJ22363@intevation.de> Am 24. Mar 2005 um 22:43:41 schrieb Jonathan Coles: > Am Mittwoch, den 23.03.2005, 10:41 -0500 schrieb Jonathan Coles: > > Here'a question: Is opacity a property of all layers or just raster > > layers? perhaps we can't support opacity in vectors layers just yet, but > > should that mean we shouldn't anticipate it? If opacity is a property of > > all layers then I can refactor the opacity methods in RasterLayer into > > BaseLayer. > > further questions: > > a) if alpha blending is not available (i.e. wxWidgets < 2.5.3) should > the renderer fall back to using a bitmask, Yes. I think having at least a bitmask available is important for users. > b) should the masking information be added to the session file? now > there are three options for masking: none, bitmap, and alpha. alpha has > a level setting too. I don't really know. Probably yes, though I do not know about Thuban's session file design. > c) this is question which addresses an HCI issue. i'll give a specific > example first, but this applies to a more general problem. i'll assume > for the sake of argument that we decide to save the masking setting in > the session file. > > let's say that alpha blending is selected with opacity level 128. the > session is saved. later the method is changed to use a bitmap (for > whatever reason). the session is saved again. in the RasterLayer class, > as it stands now, the mask type and the opacity are two independent > settings. conceivably, the opacity level can stay in the session file > regardless of the mask setting. the benefit would be that if the user > decides to revert back to using alpha blending, the original opacity > level is still available. so the question here is: is this acceptable > behaviour? IMO yes. > to make this a more general question: should we save information about > the state of an option that has used in the past, whether it is > currently used by the active session or not, so that when/if the user > decides to return to past settings (even across multiple sessions), they > don't have to remember (or waste time recalculating) past values? If we find a way of letting plugins add values to the sesssion file, there will be the common situation that some properties will not be understood by a current Thuban, because it lacks the plugins. In that case the unknown values need to be preserved. I wonder in this specific case if you should save the bitmap option at all. Do we expect a speed gain when using bitmap instead of alpha when alpha is available. Otherwise we might say that we do not need the setting bitmap, as it will be automatically used if alpha is not available to approximate what we want to render. > i think this can be a very powerful feature if we decide that "yes" is > the answer. although there may be some objection in that this can > increase the size and/or complexity of the session file, please remember > that our objective should be to make a product that improves the lives > of the *users* not the developers (although this is a good thing too :) Wisely spoken, I hope that we try to make this as simple for the users as possible without taking control out of their hands. ;) Bernhard -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 189 bytes Desc: not available Url : http://www.intevation.de/pipermail/thuban-devel/attachments/20050329/d49807a8/attachment.bin From bernhard at intevation.de Tue Mar 29 12:28:27 2005 From: bernhard at intevation.de (Bernhard Reiter) Date: Tue, 29 Mar 2005 12:28:27 +0200 Subject: agg and vector render options (was: Raster layer opacity) In-Reply-To: <20050325044941.M35422@minag.gob.pe> References: <1111592504.14081.14.camel@localhost.localdomain> <20050325044941.M35422@minag.gob.pe> Message-ID: <20050329102827.GK22363@intevation.de> Am 24. Mar 2005 um 23:57:48 schrieb Daniel Calvelo Aros: > BTW, has anyone looked at the Anti-Grain Geometry library > (http://antigrain.com)? I did not, but when checking the website I found the proprietry license of General Polygon Clipper (GPC) appalling. Do you know how important gpc is for agg? If it is not important, why is it still in there? > How difficult would it be to use that for rendering to > a bitmap and copy this into a DC instead of using wxDC functions? > Is the cost > of such a development worth the gain of antialiasing and transparency? I cannot say, it would be interesting to see how wxWidgets people thing about their drawing capabilities and what they plan in this area. For www.skencil.org (former Bernhard Herzog's Sketch) I thought about Cairo. Skenicl 0.7 uses libart, though this library did not continue as strong as it started, so Cairo seems to be the future within Gnome. In my personal list of what I want to do with Thuban the antialiasing and transparency options are in the mid range. They look a lot nicer, but functionality counts first for most users. Bernhard -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 189 bytes Desc: not available Url : http://www.intevation.de/pipermail/thuban-devel/attachments/20050329/cd49b20d/attachment.bin From cvs at intevation.de Tue Mar 29 20:05:03 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Tue, 29 Mar 2005 20:05:03 +0200 (CEST) Subject: bh: thuban/Thuban/UI legend.py,1.39,1.40 Message-ID: <20050329180503.2C0811006B3@lists.intevation.de> Author: bh Update of /thubanrepository/thuban/Thuban/UI In directory doto:/tmp/cvs-serv14309/Thuban/UI Modified Files: legend.py Log Message: (BMP_SIZE_W, BMP_SIZE_H): Set both to 16 to match the site of the legend_icon_layer icon. Otherwise wxpython 2.5 complains when the legend is created with the error: PyAssertionError: C++ assertion "(bitmap.GetWidth() == m_width && bitmap.GetHeight() == m_height) || (m_width == 0 && m_height == 0)" failed in ./src/generic/imaglist.cpp(81): invalid bitmap size in wxImageList: this might work on this platform but definitely won't under Windows. Index: legend.py =================================================================== RCS file: /thubanrepository/thuban/Thuban/UI/legend.py,v retrieving revision 1.39 retrieving revision 1.40 diff -u -d -r1.39 -r1.40 --- legend.py 16 Feb 2005 21:14:47 -0000 1.39 +++ legend.py 29 Mar 2005 18:05:00 -0000 1.40 @@ -45,8 +45,8 @@ ID_LEGEND_SHOWLAYER = 4007 ID_LEGEND_HIDELAYER = 4008 -BMP_SIZE_W = 15 -BMP_SIZE_H = 15 +BMP_SIZE_W = 16 +BMP_SIZE_H = 16 TOP_BMP = "top_layer" RAISE_BMP = "raise_layer" From cvs at intevation.de Tue Mar 29 20:05:03 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Tue, 29 Mar 2005 20:05:03 +0200 (CEST) Subject: bh: thuban ChangeLog,1.796,1.797 Message-ID: <20050329180503.38E6A1006BB@lists.intevation.de> Author: bh Update of /thubanrepository/thuban In directory doto:/tmp/cvs-serv14309 Modified Files: ChangeLog Log Message: (BMP_SIZE_W, BMP_SIZE_H): Set both to 16 to match the site of the legend_icon_layer icon. Otherwise wxpython 2.5 complains when the legend is created with the error: PyAssertionError: C++ assertion "(bitmap.GetWidth() == m_width && bitmap.GetHeight() == m_height) || (m_width == 0 && m_height == 0)" failed in ./src/generic/imaglist.cpp(81): invalid bitmap size in wxImageList: this might work on this platform but definitely won't under Windows. Index: ChangeLog =================================================================== RCS file: /thubanrepository/thuban/ChangeLog,v retrieving revision 1.796 retrieving revision 1.797 diff -u -d -r1.796 -r1.797 --- ChangeLog 23 Mar 2005 15:30:27 -0000 1.796 +++ ChangeLog 29 Mar 2005 18:05:01 -0000 1.797 @@ -1,3 +1,15 @@ +2005-03-29 Bernhard Herzog + + * Thuban/UI/legend.py (BMP_SIZE_W, BMP_SIZE_H): Set both to 16 to + match the site of the legend_icon_layer icon. Otherwise wxpython + 2.5 complains when the legend is created with the error: + + PyAssertionError: C++ assertion "(bitmap.GetWidth() == m_width && + bitmap.GetHeight() == m_height) || (m_width == 0 && m_height == + 0)" failed in ./src/generic/imaglist.cpp(81): invalid bitmap size + in wxImageList: this might work on this platform but definitely + won't under Windows. + 2005-03-23 Jonathan Coles These changes add support for adjusting the opacity of a raster layer. From jan at intevation.de Tue Mar 29 20:23:26 2005 From: jan at intevation.de (Jan-Oliver Wagner) Date: Tue, 29 Mar 2005 20:23:26 +0200 Subject: Introduce FileShapeStore In-Reply-To: References: <20050308144426.GB18094@intevation.de> <20050308144530.GC18094@intevation.de> Message-ID: <20050329182326.GB26855@intevation.de> Hi Bernhard, On Mon, Mar 14, 2005 at 09:11:33PM +0100, Bernhard Herzog wrote: >... I considered all of your comments. Attached is the updated patch. Any comments left? Jan -- Jan-Oliver Wagner http://intevation.de/~jan/ Intevation GmbH http://intevation.de/ FreeGIS http://freegis.org/ -------------- next part -------------- Index: data.py =================================================================== RCS file: /thubanrepository/thuban/Thuban/Model/data.py,v retrieving revision 1.15 diff -u -3 -p -r1.15 data.py --- data.py 24 Nov 2003 19:23:08 -0000 1.15 +++ data.py 29 Mar 2005 18:10:31 -0000 @@ -112,29 +112,106 @@ class ShapeTable(transientdb.AutoTransie """Return a tuple containing the shapestore""" return (self.store(),) +# XXX: (this statement should be kept in mind when re-engeneering) +# +# From a desing POV it was wrong to distinguish between table and +# shapestore. In hindsight the only reason for doing so was that the +# shapelib has different objects for the shapefile(s) and the dbf file, +# which of course matches the way the data is organized into different +# files. So the distinction between shapestore and table is an artifact +# of the shapefile format. When we added the postgis support we should +# have adopted the table interface for the entire shape store, making the +# geometry data an additional column for those shape stores that don't +# store the geometries in columns in the first place. + +class FileShapeStore: + + """The base class to derive any file-based ShapeStore from. + + This class contains all information that is needed by a + loader routine to actually load the shapestore. + This essentially means that the class contains all required information + to save the shapestore specification (i.e. in a .thuban file). + """ + + def __init__(self, filename, filetype, layername = None): + """Initialize the base class with main parameters. + + filename -- the source filename. + This filename will be converted to an absolute filename. + The filename will be interpreted relative to that anyway, + but when saving a session we need to compare absolute paths + and it's usually safer to always work with absolute paths. + filetype -- The type of the filename. + Known and used types are: "shapefile" + layername -- a string representing a layer within the file shape store. + Some file formats support to contain several layers, or + at least the ogr library says so. + For those filetypes who don't, the layername can be ignored + and by default it is None. + """ + self._filename = os.path.abspath(filename) + self._filetype = filetype + self._layername = layername + self._bbox = None + self._table = None + + def FileName(self): + """Return the filename used to open the shapestore. + + The filename can only be set via __init__ method. + """ + return self._filename + + def FileType(self): + """Return the filetype. + + The filetype has to be set in via __init__ method. + """ + return self._filetype + + def layer_name(self): + """Return the layername. + + This could be None if the shapestore type only supports a single + layer. + """ + return self._layername + + # Design/Implementation note: + # It is not a good idea to have a implementation for a + # "setBoundingBox" or BoundingBox # in this base class. + # In future this data might change during + # a Thuban session and thus can not be kept statically here. + # It is expected that for many derived classes the bbox must + # be retrieved each time anew. + + def BoundingBox(self): + """Return the bounding box of the shapes in the shape store. + + The coordinate system used is whatever was used in the shape store. + If the shape store is empty, return None. + """ + raise NotImplementedError -class ShapefileStore: +class ShapefileStore(FileShapeStore): """Combine a shapefile and the corresponding DBF file into one object""" def __init__(self, session, filename): - # Make the filename absolute. The filename will be - # interpreted relative to that anyway, but when saving a - # session we need to compare absolute paths and it's usually - # safer to always work with absolute paths. - self.filename = os.path.abspath(filename) + FileShapeStore.__init__(self, filename, "shapefile") self.dbftable = table.DBFTable(filename) - self.table = ShapeTable(self, session.TransientDB(), self.dbftable) + self._table = ShapeTable(self, session.TransientDB(), self.dbftable) self._open_shapefile() def _open_shapefile(self): - self.shapefile = shapelib.ShapeFile(self.filename) + self.shapefile = shapelib.ShapeFile(self.FileName()) self.numshapes, shapetype, mins, maxs = self.shapefile.info() if self.numshapes: - self.bbox = mins[:2] + maxs[:2] + self._bbox = mins[:2] + maxs[:2] else: - self.bbox = None + self._bbox = None self.shapetype = shapelib_shapetypes[shapetype] # estimate a good depth for the quad tree. Each depth multiplies @@ -148,22 +225,10 @@ class ShapefileStore: self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2, maxdepth) - def Table(self): - """Return the table containing the attribute data""" - return self.table - def Shapefile(self): """Return the shapefile object""" return self.shapefile - def FileName(self): - """Return the filename used to open the shapefile""" - return self.filename - - def FileType(self): - """Return the filetype. This is always the string 'shapefile'""" - return "shapefile" - def ShapeType(self): """Return the type of the shapes in the shapestore. @@ -186,6 +251,10 @@ class ShapefileStore: """ return () + def Table(self): + """Return the table containing the attribute data.""" + return self._table + def OrigShapeStore(self): """Return None. @@ -193,14 +262,6 @@ class ShapefileStore: """ return None - def BoundingBox(self): - """Return the bounding box of the shapes in the shapestore. - - The coordinate system used is whatever was used in the shapefile. - If the shapefile is empty, return None. - """ - return self.bbox - def ShapesInRegion(self, bbox): """Return an iterable over the shapes that overlap the bounding box. @@ -225,6 +286,13 @@ class ShapefileStore: """Return the shape with index index""" return ShapefileShape(self.shapefile, index) + def BoundingBox(self): + """Return the bounding box of the shapes in the shape store. + + The coordinate system used is whatever was used in the shape store. + If the shape store is empty, return None. + """ + return self._bbox class DerivedShapeStore: Index: save.py =================================================================== RCS file: /thubanrepository/thuban/Thuban/Model/save.py,v retrieving revision 1.43 diff -u -3 -p -r1.43 save.py --- save.py 28 Jan 2005 15:54:00 -0000 1.43 +++ save.py 29 Mar 2005 18:10:31 -0000 @@ -26,7 +26,7 @@ from Thuban.Model.classification import ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap from Thuban.Model.transientdb import AutoTransientTable, TransientJoinedTable from Thuban.Model.table import DBFTable, FIELDTYPE_STRING -from Thuban.Model.data import DerivedShapeStore, ShapefileStore, \ +from Thuban.Model.data import DerivedShapeStore, FileShapeStore, \ SHAPETYPE_POINT from Thuban.Model.xmlwriter import XMLWriter @@ -201,12 +201,12 @@ class SessionSaver(XMLWriter): continue idvalue = self.define_id(container) - if isinstance(container, ShapefileStore): + if isinstance(container, FileShapeStore): self.define_id(container.Table(), idvalue) filename = self.prepare_filename(container.FileName()) self.write_element("fileshapesource", {"id": idvalue, "filename": filename, - "filetype": "shapefile"}) + "filetype": container.FileType()}) elif isinstance(container, DerivedShapeStore): shapesource, table = container.Dependencies() self.write_element("derivedshapesource", From cvs at intevation.de Tue Mar 29 20:36:55 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Tue, 29 Mar 2005 20:36:55 +0200 (CEST) Subject: bh: thuban/test postgissupport.py,1.14,1.15 Message-ID: <20050329183655.406DE1005A2@lists.intevation.de> Author: bh Update of /thubanrepository/thuban/test In directory doto:/tmp/cvs-serv14817/test Modified Files: postgissupport.py Log Message: (find_postgis_sql): Added yet another potential location for (lw)postgis.sql because the file has moved again in postgis 1.0.0 rc4. Index: postgissupport.py =================================================================== RCS file: /thubanrepository/thuban/test/postgissupport.py,v retrieving revision 1.14 retrieving revision 1.15 diff -u -d -r1.14 -r1.15 --- postgissupport.py 21 Jan 2005 16:58:31 -0000 1.14 +++ postgissupport.py 29 Mar 2005 18:36:53 -0000 1.15 @@ -514,6 +514,7 @@ postgis 0.7.5 $datadir/contrib/postgis.sql postgis 0.8.1 $datadir/postgis.sql postgis 1.0.0-rc1 $datadir/lwpostgis.sql + postgis 1.0.0-rc4 $datadir/contrib/lwpostgis.sql To support both versions, we look in both places and return the first one found (looking under contrib first). If the file is not @@ -523,7 +524,8 @@ datadir = os.path.join(bindir, "..", "share", "postgresql") for filename in [os.path.join(datadir, "contrib", "postgis.sql"), os.path.join(datadir, "postgis.sql"), - os.path.join(datadir, "lwpostgis.sql")]: + os.path.join(datadir, "lwpostgis.sql"), + os.path.join(datadir, "contrib", "lwpostgis.sql")]: if os.path.exists(filename): return filename From cvs at intevation.de Tue Mar 29 20:36:55 2005 From: cvs at intevation.de (cvs@intevation.de) Date: Tue, 29 Mar 2005 20:36:55 +0200 (CEST) Subject: bh: thuban ChangeLog,1.797,1.798 Message-ID: <20050329183655.46C741005DC@lists.intevation.de> Author: bh Update of /thubanrepository/thuban In directory doto:/tmp/cvs-serv14817 Modified Files: ChangeLog Log Message: (find_postgis_sql): Added yet another potential location for (lw)postgis.sql because the file has moved again in postgis 1.0.0 rc4. Index: ChangeLog =================================================================== RCS file: /thubanrepository/thuban/ChangeLog,v retrieving revision 1.797 retrieving revision 1.798 diff -u -d -r1.797 -r1.798 --- ChangeLog 29 Mar 2005 18:05:01 -0000 1.797 +++ ChangeLog 29 Mar 2005 18:36:53 -0000 1.798 @@ -1,5 +1,11 @@ 2005-03-29 Bernhard Herzog + * test/postgissupport.py (find_postgis_sql): Added yet another + potential location for (lw)postgis.sql because the file has moved + again in postgis 1.0.0 rc4. + +2005-03-29 Bernhard Herzog + * Thuban/UI/legend.py (BMP_SIZE_W, BMP_SIZE_H): Set both to 16 to match the site of the legend_icon_layer icon. Otherwise wxpython 2.5 complains when the legend is created with the error: From dcalvelo at minag.gob.pe Wed Mar 30 20:38:00 2005 From: dcalvelo at minag.gob.pe (Daniel Calvelo Aros) Date: Wed, 30 Mar 2005 13:38:00 -0500 Subject: agg and vector render options (was: Raster layer opacity) References: <1111592504.14081.14.camel@localhost.localdomain> <20050325044941.M35422@minag.gob.pe> <20050329102827.GK22363@intevation.de> Message-ID: <20050330183742.M31413@minag.gob.pe> From: Bernhard Reiter Sent: Tue, 29 Mar 2005 12:28:27 +0200 > Am 24. Mar 2005 um 23:57:48 schrieb Daniel Calvelo Aros: > > BTW, has anyone looked at the Anti-Grain Geometry library > > (http://antigrain.com)? > > I did not, but when checking the website I found the proprietry > license of General Polygon Clipper (GPC) appalling. > Do you know how important gpc is for agg? > If it is not important, why is it still in there? AFAICT, GPC is only used for certain (vectorial) back-ends; otherwise, for bitmap output, the equivalent operations are performed scanline-wise, outside of GPC. So the horrid license should not be a major drawback. > > How difficult would it be to use that for rendering to > > a bitmap and copy this into a DC instead of using wxDC functions? > > Is the cost > > of such a development worth the gain of antialiasing and transparency? > > I cannot say, it would be interesting to see how wxWidgets people > thing about their drawing capabilities and what they plan in this area. Mmmmmh... Just thinking out loud: what do you think would be better a) having some agg/gd/libart wxDC backend, or b) abstracting away wxDC in thuban so that drawing backends could be switched? Maybe it's a stupid question, I haven't studied the Renderer infrastructure. > For www.skencil.org (former Bernhard Herzog's Sketch) I thought > about Cairo. Skenicl 0.7 uses libart, though this library did not > continue as strong as it started, so Cairo seems to be the future > within Gnome. Yes, SVG seems to be the overall trend. I just read the OGC spec for SLD, and its level of interoperation with SVG is almost scary. > In my personal list of what I want to do with Thuban the antialiasing > and transparency options are in the mid range. They look a lot nicer, > but functionality counts first for most users. Agreed. Although transparency to me is higher priority than antialising. It's a feature that users might crave for. From jonathan at jpcoles.com Thu Mar 31 16:03:24 2005 From: jonathan at jpcoles.com (Jonathan Coles) Date: Thu, 31 Mar 2005 09:03:24 -0500 Subject: Thuban more modular? Message-ID: <1112277804.14081.120.camel@localhost.localdomain> what are people's thoughts on moving some of what is now core-functionality into Extensions? i'm thinking specifically of RasterLayer, but only because that's what i tend to play around with most. we expect additional layer types to be extensions to thuban, but why not make even our standard options behave as extensions for consistency? there are some optimizations in place for rendering (Shape)Layer objects that make the extraction harder, so i'm not going to touch that right now. in general, thuban should provide a very basic framework onto which all the real functionality is added. the concept of a layer is part of that framework, but specific implementations of that concept are extensions, no? --jonathan -- ===================================================================== Jonathan Coles http://www.jpcoles.com jonathan at jpcoles.com GnuPG Key: /gpg_pub_key.asc =====================================================================