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