3 from decimal
import Decimal
5 from django
.utils
.copycompat
import copy
7 from django
.contrib
.gis
.gdal
import DataSource
8 from django
.contrib
.gis
.tests
.utils
import mysql
9 from django
.contrib
.gis
.utils
.layermapping
import LayerMapping
, LayerMapError
, InvalidDecimal
, MissingForeignKey
11 from models
import City
, County
, CountyFeat
, Interstate
, ICity1
, ICity2
, State
, city_mapping
, co_mapping
, cofeat_mapping
, inter_mapping
13 shp_path
= os
.path
.realpath(os
.path
.join(os
.path
.dirname(__file__
), '..', 'data'))
14 city_shp
= os
.path
.join(shp_path
, 'cities', 'cities.shp')
15 co_shp
= os
.path
.join(shp_path
, 'counties', 'counties.shp')
16 inter_shp
= os
.path
.join(shp_path
, 'interstates', 'interstates.shp')
18 # Dictionaries to hold what's expected in the county shapefile.
19 NAMES
= ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
20 NUMS
= [1, 2, 1, 19, 1] # Number of polygons for each.
21 STATES
= ['Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado']
23 class LayerMapTest(unittest
.TestCase
):
25 def test01_init(self
):
26 "Testing LayerMapping initialization."
28 # Model field that does not exist.
29 bad1
= copy(city_mapping
)
30 bad1
['foobar'] = 'FooField'
32 # Shapefile field that does not exist.
33 bad2
= copy(city_mapping
)
34 bad2
['name'] = 'Nombre'
36 # Nonexistent geographic field type.
37 bad3
= copy(city_mapping
)
38 bad3
['point'] = 'CURVE'
40 # Incrementing through the bad mapping dictionaries and
41 # ensuring that a LayerMapError is raised.
42 for bad_map
in (bad1
, bad2
, bad3
):
44 lm
= LayerMapping(City
, city_shp
, bad_map
)
48 self
.fail('Expected a LayerMapError.')
50 # A LookupError should be thrown for bogus encodings.
52 lm
= LayerMapping(City
, city_shp
, city_mapping
, encoding
='foobar')
56 self
.fail('Expected a LookupError')
58 def test02_simple_layermap(self
):
59 "Test LayerMapping import of a simple point shapefile."
60 # Setting up for the LayerMapping.
61 lm
= LayerMapping(City
, city_shp
, city_mapping
)
64 # There should be three cities in the shape file.
65 self
.assertEqual(3, City
.objects
.count())
67 # Opening up the shapefile, and verifying the values in each
68 # of the features made it to the model.
69 ds
= DataSource(city_shp
)
72 city
= City
.objects
.get(name
=feat
['Name'].value
)
73 self
.assertEqual(feat
['Population'].value
, city
.population
)
74 self
.assertEqual(Decimal(str(feat
['Density'])), city
.density
)
75 self
.assertEqual(feat
['Created'].value
, city
.dt
)
77 # Comparing the geometries.
78 pnt1
, pnt2
= feat
.geom
, city
.point
79 self
.assertAlmostEqual(pnt1
.x
, pnt2
.x
, 6)
80 self
.assertAlmostEqual(pnt1
.y
, pnt2
.y
, 6)
82 def test03_layermap_strict(self
):
83 "Testing the `strict` keyword, and import of a LineString shapefile."
84 # When the `strict` keyword is set an error encountered will force
85 # the importation to stop.
87 lm
= LayerMapping(Interstate
, inter_shp
, inter_mapping
)
88 lm
.save(silent
=True, strict
=True)
89 except InvalidDecimal
:
90 # No transactions for geoms on MySQL; delete added features.
91 if mysql
: Interstate
.objects
.all().delete()
93 self
.fail('Should have failed on strict import with invalid decimal values.')
95 # This LayerMapping should work b/c `strict` is not set.
96 lm
= LayerMapping(Interstate
, inter_shp
, inter_mapping
)
99 # Two interstate should have imported correctly.
100 self
.assertEqual(2, Interstate
.objects
.count())
102 # Verifying the values in the layer w/the model.
103 ds
= DataSource(inter_shp
)
105 # Only the first two features of this shapefile are valid.
106 valid_feats
= ds
[0][:2]
107 for feat
in valid_feats
:
108 istate
= Interstate
.objects
.get(name
=feat
['Name'].value
)
111 self
.assertEqual(Decimal(str(feat
['Length'])), istate
.length
)
113 # Everything but the first two decimal digits were truncated,
114 # because the Interstate model's `length` field has decimal_places=2.
115 self
.assertAlmostEqual(feat
.get('Length'), float(istate
.length
), 2)
117 for p1
, p2
in zip(feat
.geom
, istate
.path
):
118 self
.assertAlmostEqual(p1
[0], p2
[0], 6)
119 self
.assertAlmostEqual(p1
[1], p2
[1], 6)
121 def county_helper(self
, county_feat
=True):
122 "Helper function for ensuring the integrity of the mapped County models."
123 for name
, n
, st
in zip(NAMES
, NUMS
, STATES
):
124 # Should only be one record b/c of `unique` keyword.
125 c
= County
.objects
.get(name
=name
)
126 self
.assertEqual(n
, len(c
.mpoly
))
127 self
.assertEqual(st
, c
.state
.name
) # Checking ForeignKey mapping.
129 # Multiple records because `unique` was not set.
131 qs
= CountyFeat
.objects
.filter(name
=name
)
132 self
.assertEqual(n
, qs
.count())
134 def test04_layermap_unique_multigeometry_fk(self
):
135 "Testing the `unique`, and `transform`, geometry collection conversion, and ForeignKey mappings."
136 # All the following should work.
138 # Telling LayerMapping that we want no transformations performed on the data.
139 lm
= LayerMapping(County
, co_shp
, co_mapping
, transform
=False)
141 # Specifying the source spatial reference system via the `source_srs` keyword.
142 lm
= LayerMapping(County
, co_shp
, co_mapping
, source_srs
=4269)
143 lm
= LayerMapping(County
, co_shp
, co_mapping
, source_srs
='NAD83')
145 # Unique may take tuple or string parameters.
146 for arg
in ('name', ('name', 'mpoly')):
147 lm
= LayerMapping(County
, co_shp
, co_mapping
, transform
=False, unique
=arg
)
149 self
.fail('No exception should be raised for proper use of keywords.')
151 # Testing invalid params for the `unique` keyword.
152 for e
, arg
in ((TypeError, 5.0), (ValueError, 'foobar'), (ValueError, ('name', 'mpolygon'))):
153 self
.assertRaises(e
, LayerMapping
, County
, co_shp
, co_mapping
, transform
=False, unique
=arg
)
155 # No source reference system defined in the shapefile, should raise an error.
157 self
.assertRaises(LayerMapError
, LayerMapping
, County
, co_shp
, co_mapping
)
159 # Passing in invalid ForeignKey mapping parameters -- must be a dictionary
160 # mapping for the model the ForeignKey points to.
161 bad_fk_map1
= copy(co_mapping
); bad_fk_map1
['state'] = 'name'
162 bad_fk_map2
= copy(co_mapping
); bad_fk_map2
['state'] = {'nombre' : 'State'}
163 self
.assertRaises(TypeError, LayerMapping
, County
, co_shp
, bad_fk_map1
, transform
=False)
164 self
.assertRaises(LayerMapError
, LayerMapping
, County
, co_shp
, bad_fk_map2
, transform
=False)
166 # There exist no State models for the ForeignKey mapping to work -- should raise
167 # a MissingForeignKey exception (this error would be ignored if the `strict`
168 # keyword is not set).
169 lm
= LayerMapping(County
, co_shp
, co_mapping
, transform
=False, unique
='name')
170 self
.assertRaises(MissingForeignKey
, lm
.save
, silent
=True, strict
=True)
172 # Now creating the state models so the ForeignKey mapping may work.
173 co
, hi
, tx
= State(name
='Colorado'), State(name
='Hawaii'), State(name
='Texas')
174 co
.save(), hi
.save(), tx
.save()
176 # If a mapping is specified as a collection, all OGR fields that
177 # are not collections will be converted into them. For example,
178 # a Point column would be converted to MultiPoint. Other things being done
179 # w/the keyword args:
180 # `transform=False`: Specifies that no transform is to be done; this
181 # has the effect of ignoring the spatial reference check (because the
182 # county shapefile does not have implicit spatial reference info).
184 # `unique='name'`: Creates models on the condition that they have
185 # unique county names; geometries from each feature however will be
186 # appended to the geometry collection of the unique model. Thus,
187 # all of the various islands in Honolulu county will be in in one
188 # database record with a MULTIPOLYGON type.
189 lm
= LayerMapping(County
, co_shp
, co_mapping
, transform
=False, unique
='name')
190 lm
.save(silent
=True, strict
=True)
192 # A reference that doesn't use the unique keyword; a new database record will
193 # created for each polygon.
194 lm
= LayerMapping(CountyFeat
, co_shp
, cofeat_mapping
, transform
=False)
195 lm
.save(silent
=True, strict
=True)
197 # The county helper is called to ensure integrity of County models.
200 def test05_test_fid_range_step(self
):
201 "Tests the `fid_range` keyword and the `step` keyword of .save()."
202 # Function for clearing out all the counties before testing.
203 def clear_counties(): County
.objects
.all().delete()
205 # Initializing the LayerMapping object to use in these tests.
206 lm
= LayerMapping(County
, co_shp
, co_mapping
, transform
=False, unique
='name')
208 # Bad feature id ranges should raise a type error.
210 bad_ranges
= (5.0, 'foo', co_shp
)
211 for bad
in bad_ranges
:
212 self
.assertRaises(TypeError, lm
.save
, fid_range
=bad
)
214 # Step keyword should not be allowed w/`fid_range`.
215 fr
= (3, 5) # layer[3:5]
216 self
.assertRaises(LayerMapError
, lm
.save
, fid_range
=fr
, step
=10)
217 lm
.save(fid_range
=fr
)
219 # Features IDs 3 & 4 are for Galveston County, Texas -- only
220 # one model is returned because the `unique` keyword was set.
221 qs
= County
.objects
.all()
222 self
.assertEqual(1, qs
.count())
223 self
.assertEqual('Galveston', qs
[0].name
)
225 # Features IDs 5 and beyond for Honolulu County, Hawaii, and
226 # FID 0 is for Pueblo County, Colorado.
228 lm
.save(fid_range
=slice(5, None), silent
=True, strict
=True) # layer[5:]
229 lm
.save(fid_range
=slice(None, 1), silent
=True, strict
=True) # layer[:1]
231 # Only Pueblo & Honolulu counties should be present because of
232 # the `unique` keyword. Have to set `order_by` on this QuerySet
233 # or else MySQL will return a different ordering than the other dbs.
234 qs
= County
.objects
.order_by('name')
235 self
.assertEqual(2, qs
.count())
237 hi_idx
, co_idx
= tuple(map(NAMES
.index
, ('Honolulu', 'Pueblo')))
238 self
.assertEqual('Pueblo', co
.name
); self
.assertEqual(NUMS
[co_idx
], len(co
.mpoly
))
239 self
.assertEqual('Honolulu', hi
.name
); self
.assertEqual(NUMS
[hi_idx
], len(hi
.mpoly
))
241 # Testing the `step` keyword -- should get the same counties
242 # regardless of we use a step that divides equally, that is odd,
243 # or that is larger than the dataset.
244 for st
in (4,7,1000):
246 lm
.save(step
=st
, strict
=True)
247 self
.county_helper(county_feat
=False)
249 def test06_model_inheritance(self
):
250 "Tests LayerMapping on inherited models. See #12093."
251 icity_mapping
= {'name' : 'Name',
252 'population' : 'Population',
253 'density' : 'Density',
258 # Parent model has geometry field.
259 lm1
= LayerMapping(ICity1
, city_shp
, icity_mapping
)
262 # Grandparent has geometry field.
263 lm2
= LayerMapping(ICity2
, city_shp
, icity_mapping
)
266 self
.assertEqual(6, ICity1
.objects
.count())
267 self
.assertEqual(3, ICity2
.objects
.count())
270 s
= unittest
.TestSuite()
271 s
.addTest(unittest
.makeSuite(LayerMapTest
))