1
# Copyright (c) 2002, 2003 by Intevation GmbH
3
# Bernhard Herzog <bh@intevation.de>
5
# This program is free software under the GPL (>=v2)
6
# Read the file COPYING coming with Thuban for details.
9
Test saving a thuban session as XML
12
__version__ = "$Revision: 1.31 $"
13
# $Source: /thubanrepository/thuban/test/test_save.py,v $
14
# $Id: test_save.py,v 1.31 2003/12/22 17:49:43 bh Exp $
18
from StringIO import StringIO
28
from Thuban.Lib.fileutil import relative_filename
29
from Thuban.Model.save import XMLWriter, save_session, sort_data_stores
30
from Thuban.Model.session import Session
31
from Thuban.Model.map import Map
32
from Thuban.Model.layer import Layer, RasterLayer
33
from Thuban.Model.proj import Projection
34
from Thuban.Model.table import DBFTable
35
from Thuban.Model.transientdb import TransientJoinedTable
36
from Thuban.Model.data import DerivedShapeStore
38
from Thuban.Model.classification import ClassGroupSingleton, ClassGroupRange, \
41
from Thuban.Model.range import Range
43
from Thuban.Model.postgisdb import PostGISConnection, PostGISShapeStore
46
class XMLWriterTest(unittest.TestCase):
49
"""Test XMLWriter.encode"""
51
eq = self.assertEquals
53
eq(writer.encode("hello world"), "hello world")
54
eq(writer.encode(unicode("hello world")), unicode("hello world"))
56
eq(writer.encode("\x80\x90\xc2\x100"),
57
"\xc2\x80\xc2\x90\xc3\x82\x100")
58
eq(writer.encode(u"\x80\x90\xc2\x100"),
59
"\xc2\x80\xc2\x90\xc3\x82\x100")
60
eq(writer.encode(u"\xFF5E"), "\xc3\xbf5E")
62
eq(writer.encode('&"\'<>'), "&"'<>")
63
eq(writer.encode(unicode('&"\'<>')), "&"'<>")
65
class SaveSessionTest(unittest.TestCase, support.FileTestMixin,
66
xmlsupport.ValidationTest):
68
dtd = "http://thuban.intevation.org/dtds/thuban-1.0.0.dtd"
69
thubanids = [((dtd, n), (None, "id")) for n in
70
["fileshapesource", "filetable", "jointable",
71
"derivedshapesource", "dbshapesource", "dbconnection"]]
72
thubanidrefs = [((dtd, n), (None, m)) for n, m in
73
[("layer", "shapestore"),
74
("jointable", "left"),
75
("jointable", "right"),
76
("derivedshapesource", "table"),
77
("derivedshapesource", "shapesource"),
78
("dbshapesource", "dbconn")]]
82
"""Call self.session.Destroy
84
Test cases that create session should bind it to self.session so
85
that it gets destroyed properly
87
if hasattr(self, "session"):
88
self.session.Destroy()
91
def compare_xml(self, xml1, xml2):
92
list1 = xmlsupport.sax_eventlist(xml1, ids = self.thubanids,
93
idrefs = self.thubanidrefs)
94
list2 = xmlsupport.sax_eventlist(xml2, ids = self.thubanids,
95
idrefs = self.thubanidrefs)
97
for a, b in zip(list1, list2):
99
self.fail("%r != %r" % (a, b))
102
def testEmptySession(self):
103
"""Save an empty session"""
104
session = Session("empty session")
105
filename = self.temp_file_name("save_emptysession.thuban")
106
save_session(session, filename)
109
file = open(filename)
110
written_contents = file.read()
112
self.compare_xml(written_contents,
113
'<?xml version="1.0" encoding="UTF-8"?>\n'
114
'<!DOCTYPE session SYSTEM "thuban-1.0.dtd">\n'
115
'<session title="empty session" '
116
'xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">'
119
self.validate_data(written_contents)
121
def testSingleLayer(self):
122
"""Save a session with a single map with a single layer"""
123
# deliberately put an apersand in the title :)
124
session = Session("single map&layer")
125
proj = Projection(["proj=utm", "zone=27", "ellps=WGS84",
126
"datum=WGS84", "units=m"],
127
name = "WGS 84 / UTM zone 27N",
129
map = Map("Test Map", projection = proj)
131
# use shapefile from the example data
132
shpfile = os.path.join(os.path.dirname(__file__),
133
os.pardir, "Data", "iceland", "political.shp")
134
layer = Layer("My Layer", session.OpenShapefile(shpfile))
137
filename = self.temp_file_name("save_singlemap.thuban")
138
save_session(session, filename)
140
file = open(filename)
141
written_contents = file.read()
143
expected_template = '''<?xml version="1.0" encoding="UTF-8"?>
144
<!DOCTYPE session SYSTEM "thuban-1.0.dtd">
145
<session title="single map&layer"
146
xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
147
<fileshapesource id="D1"
148
filename="../../Data/iceland/political.shp"
149
filetype="shapefile"/>
150
<map title="Test Map">
151
<projection epsg="32627" name="WGS 84 / UTM zone 27N">
152
<parameter value="proj=utm"/>
153
<parameter value="zone=27"/>
154
<parameter value="ellps=WGS84"/>
155
<parameter value="datum=WGS84"/>
156
<parameter value="units=m"/>
158
<layer title="My Layer" shapestore="D1"
159
fill="None" stroke="#000000" stroke_width="1" visible="%s"/>
163
expected_contents = expected_template % "true"
165
self.compare_xml(written_contents, expected_contents)
167
self.validate_data(written_contents)
169
# Repeat with an invisible layer
170
layer.SetVisible(False)
171
save_session(session, filename)
173
file = open(filename)
174
written_contents = file.read()
176
expected_contents = expected_template % "false"
177
self.compare_xml(written_contents, expected_contents)
178
self.validate_data(written_contents)
182
def testLayerProjection(self):
183
"""Test saving layers with projections"""
184
# deliberately put an apersand in the title :)
185
session = self.session = Session("single map&layer")
186
proj = Projection(["zone=26", "proj=utm", "ellps=clrk66"])
187
map = Map("Test Map", projection = proj)
189
# use shapefile from the example data
190
shpfile = os.path.join(os.path.dirname(__file__),
191
os.pardir, "Data", "iceland", "political.shp")
192
layer = Layer("My Layer", session.OpenShapefile(shpfile))
193
proj = Projection(["proj=lcc", "ellps=clrk66",
194
"lat_1=0", "lat_2=20"],
196
layer.SetProjection(proj)
199
filename = self.temp_file_name("save_layerproj.thuban")
200
save_session(session, filename)
202
file = open(filename)
203
written_contents = file.read()
205
expected_contents = '''<?xml version="1.0" encoding="UTF-8"?>
206
<!DOCTYPE session SYSTEM "thuban-1.0.dtd">
207
<session title="single map&layer"
208
xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
209
<fileshapesource id="D1"
210
filename="../../Data/iceland/political.shp"
211
filetype="shapefile"/>
212
<map title="Test Map">
213
<projection name="Unknown">
214
<parameter value="zone=26"/>
215
<parameter value="proj=utm"/>
216
<parameter value="ellps=clrk66"/>
218
<layer title="My Layer" shapestore="D1"
219
fill="None" stroke="#000000" stroke_width="1" visible="true">
220
<projection name="Layer Projection">
221
<parameter value="proj=lcc"/>
222
<parameter value="ellps=clrk66"/>
223
<parameter value="lat_1=0"/>
224
<parameter value="lat_2=20"/>
229
#print written_contents
230
#print "********************************************"
231
#print expected_contents
232
self.compare_xml(written_contents, expected_contents)
234
self.validate_data(written_contents)
236
def testRasterLayer(self):
237
# deliberately put an apersand in the title :)
238
session = Session("single map&layer")
239
map = Map("Test Map")
241
# use shapefile from the example data
242
imgfile = os.path.join(os.path.dirname(__file__),
243
os.pardir, "Data", "iceland", "island.tif")
244
layer = RasterLayer("My RasterLayer", imgfile)
247
filename = self.temp_file_name("%s.thuban" % self.id())
248
save_session(session, filename)
251
file = open(filename)
252
written_contents = file.read()
254
expected_contents = '''<?xml version="1.0" encoding="UTF-8"?>
255
<!DOCTYPE session SYSTEM "thuban-1.0.dtd">
256
<session title="single map&layer"
257
xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
258
<map title="Test Map">
259
<rasterlayer title="My RasterLayer"
260
filename="../../Data/iceland/island.tif"
265
#print written_contents
266
#print "********************************************"
267
#print expected_contents
268
self.compare_xml(written_contents, expected_contents)
270
self.validate_data(written_contents)
272
def testClassifiedLayer(self):
273
"""Save a session with a single map with classifications"""
274
# deliberately put an apersand in the title :)
275
session = Session("Map with Classifications")
276
proj = Projection(["zone=26", "proj=utm", "ellps=clrk66"])
277
map = Map("Test Map", projection = proj)
279
# use shapefile from the example data
280
shpfile = os.path.join(os.path.dirname(__file__),
281
os.pardir, "Data", "iceland", "political.shp")
282
layer = Layer("My Layer", session.OpenShapefile(shpfile))
284
layer2 = Layer("My Layer", layer.ShapeStore())
287
clazz = layer.GetClassification()
289
layer.SetClassificationColumn("AREA")
291
clazz.AppendGroup(ClassGroupSingleton(42, ClassGroupProperties(),
293
clazz.AppendGroup(ClassGroupSingleton("text", ClassGroupProperties(),
296
clazz.AppendGroup(ClassGroupRange((0, 42),
297
ClassGroupProperties(),
300
range = ClassGroupRange(Range("[0;42]"))
301
range.SetProperties(ClassGroupProperties())
302
range.SetLabel("new-range")
303
clazz.AppendGroup(range)
306
clazz = layer2.GetClassification()
307
layer2.SetClassificationColumn("POPYCOUN")
309
# Classification with Latin 1 text
310
clazz.AppendGroup(ClassGroupSingleton('\xe4\xf6\xfc', # ae, oe, ue
311
ClassGroupProperties(),
312
'\xdcml\xe4uts')) # Uemlaeuts
315
filename = self.temp_file_name("%s.thuban" % self.id())
316
save_session(session, filename)
318
file = open(filename)
319
written_contents = file.read()
321
expected_contents = '''<?xml version="1.0" encoding="UTF-8"?>
322
<!DOCTYPE session SYSTEM "thuban-1.0.dtd">
323
<session title="Map with Classifications"
324
xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
325
<fileshapesource id="D1"
326
filename="../../Data/iceland/political.shp"
327
filetype="shapefile"/>
328
<map title="Test Map">
329
<projection name="Unknown">
330
<parameter value="zone=26"/>
331
<parameter value="proj=utm"/>
332
<parameter value="ellps=clrk66"/>
334
<layer title="My Layer" shapestore="D1"
335
fill="None" stroke="#000000" stroke_width="1" visible="true">
336
<classification field="AREA" field_type="double">
338
<cldata fill="None" stroke="#000000" stroke_width="1"/>
340
<clpoint value="42" label="single">
341
<cldata fill="None" stroke="#000000" stroke_width="1"/>
343
<clpoint value="text" label="single-text">
344
<cldata fill="None" stroke="#000000" stroke_width="1"/>
346
<clrange range="[0;42[" label="range">
347
<cldata fill="None" stroke="#000000" stroke_width="1"/>
349
<clrange range="[0;42]" label="new-range">
350
<cldata fill="None" stroke="#000000" stroke_width="1"/>
354
<layer title="My Layer" shapestore="D1"
355
fill="None" stroke="#000000" stroke_width="1" visible="true">
356
<classification field="POPYCOUN" field_type="string">
358
<cldata fill="None" stroke="#000000" stroke_width="1"/>
360
<clpoint value="\xc3\xa4\xc3\xb6\xc3\xbc"
361
label="\xc3\x9cml\xc3\xa4uts">
362
<cldata fill="None" stroke="#000000" stroke_width="1"/>
369
#print written_contents
370
#print "********************************************"
371
#print expected_contents
372
self.compare_xml(written_contents, expected_contents)
374
self.validate_data(written_contents)
378
def test_dbf_table(self):
379
"""Test saving a session with a dbf table link"""
380
session = self.session = Session("a DBF Table session")
381
# use shapefile from the example data
382
dbffile = os.path.join(os.path.dirname(__file__),
383
os.pardir, "Data", "iceland", "political.dbf")
384
table = session.AddTable(DBFTable(dbffile))
386
filename = self.temp_file_name("save_singletable.thuban")
387
save_session(session, filename)
389
file = open(filename)
390
written_contents = file.read()
392
expected_contents = '''<?xml version="1.0" encoding="UTF-8"?>
393
<!DOCTYPE session SYSTEM "thuban-1.0.dtd">
394
<session title="a DBF Table session"
395
xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
396
<filetable id="D1" filename="../../Data/iceland/political.dbf"
397
filetype="DBF" title="political"/>
400
self.compare_xml(written_contents, expected_contents)
401
self.validate_data(written_contents)
403
def test_joined_table(self):
404
"""Test saving a session with joined table"""
405
# Create a simple table to use in the join
406
dbffile = self.temp_file_name("save_joinedtable.dbf")
407
dbf = dbflib.create(dbffile)
408
dbf.add_field("RDTYPE", dbflib.FTInteger, 10, 0)
409
dbf.add_field("TEXT", dbflib.FTString, 10, 0)
410
dbf.write_record(0, {'RDTYPE': 8, "TEXT": "foo"})
411
dbf.write_record(1, {'RDTYPE': 2, "TEXT": "bar"})
412
dbf.write_record(2, {'RDTYPE': 3, "TEXT": "baz"})
415
# Create the session and a map
416
session = Session("A Joined Table session")
418
map = Map("Test Map")
421
# Add the dbf file to the session
422
dbftable = session.AddTable(DBFTable(dbffile))
424
# Create a layer with the shapefile to use in the join
425
shpfile = os.path.join(os.path.abspath(os.path.dirname(__file__)),
426
os.pardir, "Data", "iceland",
428
layer = Layer("My Layer", session.OpenShapefile(shpfile))
432
store = layer.ShapeStore()
433
#for col in store.Table().Columns():
435
joined = TransientJoinedTable(session.TransientDB(),
436
store.Table(), "RDLNTYPE",
439
store = session.AddShapeStore(DerivedShapeStore(store, joined))
440
layer.SetShapeStore(store)
443
filename = self.temp_file_name("save_joinedtable.thuban")
444
save_session(session, filename)
446
# Read it back and compare
447
file = open(filename)
448
written_contents = file.read()
450
expected_contents = '''<?xml version="1.0" encoding="UTF-8"?>
451
<!DOCTYPE session SYSTEM "thuban-1.0.dtd">
452
<session title="A Joined Table session"
453
xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
454
<fileshapesource filename="../../Data/iceland/roads-line.shp"
455
filetype="shapefile" id="D142197204"/>
456
<filetable filename="save_joinedtable.dbf"
457
title="save_joinedtable"
458
filetype="DBF" id="D141881756"/>
459
<jointable id="D142180284"
460
title="Join of roads-line and save_joinedtable"
461
leftcolumn="RDLNTYPE" left="D142197204"
462
rightcolumn="RDTYPE" right="D141881756"
463
jointype="LEFT OUTER" />
464
<derivedshapesource id="D141915644"
466
shapesource="D142197204"/>
467
<map title="Test Map">
468
<layer title="My Layer"
469
shapestore="D141915644" visible="true"
470
stroke="#000000" stroke_width="1" fill="None"/>
474
self.compare_xml(written_contents, expected_contents)
475
self.validate_data(written_contents)
481
def test_save_postgis(self):
482
"""Test saving a session with a postgis connection"""
484
class NonConnection(PostGISConnection):
485
"""connection class that doesn't actually connect """
489
class NonConnectionStore(PostGISShapeStore):
490
"""Shapestore that doesn't try to access the server"""
491
def _fetch_table_information(self):
494
session = Session("A PostGIS Session")
496
dbconn = NonConnection(dbname="plugh", host="xyzzy", port="42",
498
session.AddDBConnection(dbconn)
499
map = Map("Test Map")
501
store = NonConnectionStore(dbconn, "roads")
502
session.AddShapeStore(store)
503
layer = Layer("Roads to Nowhere", store)
507
filename = self.temp_file_name(self.id() + ".thuban")
508
save_session(session, filename)
510
# Read it back and compare
511
file = open(filename)
512
written = file.read()
514
expected = '''<?xml version="1.0" encoding="UTF-8"?>
515
<!DOCTYPE session SYSTEM "thuban-1.0.dtd">
516
<session title="A PostGIS Session"
517
xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
518
<dbconnection id="DB"
519
dbtype="postgis" dbname="plugh"
520
host="xyzzy" port="42"
522
<dbshapesource id="roads" dbconn="DB" tablename="roads"/>
523
<map title="Test Map">
524
<layer title="Roads to Nowhere"
525
shapestore="roads" visible="true"
526
stroke="#000000" stroke_width="1" fill="None"/>
529
self.compare_xml(written, expected)
530
self.validate_data(written)
537
"""A very simple data store that only has dependencies"""
539
def __init__(self, name, *dependencies):
541
self.dependencies = dependencies
546
def Dependencies(self):
547
return self.dependencies
550
class TestStoreSort(unittest.TestCase):
552
def check_sort(self, containers, sorted):
553
"""Check whether the list of data containers is sorted"""
554
# check whether sorted is in the right order
556
for container in sorted:
557
self.failIf(id(container) in seen,
558
"Container %r at least twice in %r" % (container,
560
for dep in container.Dependencies():
561
self.assert_(id(dep) in seen,
562
"Dependency %r of %r not yet seen" % (dep,
564
seen[id(container)] = 1
565
# check whether all of containers is in sorted
566
for container in containers:
567
self.assert_(id(container) in seen,
568
"Container %r in containers but not in sorted")
569
self.assertEquals(len(containers), len(sorted))
571
def test_sort_data_stores(self):
572
"""Test Thuban.Model.save.sort_data_stores"""
573
d1 = MockDataStore("d1")
574
d2 = MockDataStore("d2")
575
d3 = MockDataStore("d3", d1)
576
d4 = MockDataStore("d4", d1, d3)
578
containers = [d4, d1, d2, d3]
579
self.check_sort(containers, sort_data_stores(containers))
580
containers = [d1, d3, d2, d4]
581
self.check_sort(containers, sort_data_stores(containers))
585
if __name__ == "__main__":
586
# Fake the __file__ global because it's needed by a test
588
__file__ = sys.argv[0]